I'm new here and to d3.js/JavaScript in general. I would like to add a transition to a multi line graph so that each line (and associated label) is 'drawn' one after the other. I've managed to get this to work for the first line (and to a lesser extent all lines at the same time) but am struggling to see how I can stagger the transitions. I've tried using a for loop and .each() to call the transition via a function but haven't really got anywhere with either approach. Any help would be gratefully received. Relevant part of the code below. Thanks.
var country = svg.selectAll(".country")
.data(countries)
.enter().append("g")
.attr("class", "country");
var path = country.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.country); })
var totalLength = path.node().getTotalLength();
d3.select(".line")
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(1000)
.ease("linear")
.attr("stroke-dashoffset", 0)
.each("end", function() {
d3.select(".label")
.transition()
.style("opacity", 1);
});
var labels = country.append("text")
.datum(function(d) { return {country: d.country, value: d.values[d.values.length - 1]}; })
.attr("class", "label")
.attr("transform", function(d) { return "translate(" + x(d.value.month) + "," + y(d.value.rainfall) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.style("opacity", 0)
.text(function(d) { return d.country; });
You can use .delay() with a function to do this:
d3.select(".line")
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.delay(function(d, i) { return i * 1000; })
.duration(1000)
.ease("linear")
.attr("stroke-dashoffset", 0)
.each("end", function() {
d3.select(".label")
.transition()
.style("opacity", 1);
});
Related
Solved: jsfiddle
Issue #1: Have a grouped bar chart. I'd like the group to highlight if any bars in the group are moused-over.
Currently on mouseover sets all rects with class 'bar' to opacity 0.5 and then the specific rect to opacity 1. But how can I set the node or group of bars to opacity 1, so that they are highlighted against the others?
.on("mouseover", function(d, i, node) { //this is repeated should be in a function
d3.selectAll(".bar").transition()
.style("opacity", 0.3); //all groups given opacity 0
d3.select(this).transition()
.style("opacity", 1); //give opacity 1 to group on which it hovers.
return tooltip
.style("visibility", "visible")
.html("<span style=font-size:30px;> " + "name:" + d.name +
"</span>"
)
})
.on("mouseout", function(d) {
d3.selectAll(".bar").transition()
.style("opacity", 1);
return tooltip.style("visibility", "hidden");
})
Issue #2: Also I would like the bar's x axis labels to behave similarly. So that the names of all but the current bar would have opacity 0.5
I did try adding a clas of bar to the xAxis text, but doesn't work,
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.style("font", "20px times")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("class", "bar")
.attr("transform", "rotate(-65)");
I this try implementing ideas from this post
D3 Grouped Bar Chart - Selecting entire group?
but I haven't been able to get it to work.
My attempt to give a class of d.name + index to each bar in a group. But I can't select then, the return "." + d.name isn't working as I'd expect.
.attr("class", function(d, i) {
return d.name.replace(/\s/g, '') + i + " bar"
})
.on("mouseover", function(d, i, node) {
d3.selectAll(".bar").transition()
.style("opacity", 0.3);
d3.selectAll("." + d.name.replace(/\s/g) + i)
.transition()
.style("opacity", 1);
return tooltip
.style("visibility", "visible")
.html("<span style=font-size:30px;> " + "name:" + d.name +
"</span>");
})
The select should be,
d3.selectAll("." + d.name.replace(/\s/g, '') + i)
Actually each bar in each group could just be given a class of "group + index". There is no need for the regular expression.
Except for the text on the xAxis the highlighting is now working fine.
Any help would be greatly appreciated,
Thanks
you could base the opacity for all bars on its .name value (which is common attribute per group in your example), eg
.on("mouseover", function(d) {
let selectedName = d.name;
d3.selectAll(".bar")
.style("opacity", function(d) {
return d.name == selectedName ? 1 : 0.3;
})
//etc
This works jsFiddle
.on("mouseover", function(d, i, node) {
d3.selectAll(".bar").transition()
.style("opacity", 0.3);
d3.selectAll(".xAxisText").transition()
.style("fill", "lightgray")
.style("font-weight", "100"); //all groups given opacity 0
d3.selectAll(".groupText" + i)
.transition()
.style("font-weight", "900")
.style("fill", "black"); //give opacity 1 to group on which it hovers.
d3.selectAll(".group" + i)
.transition()
.style("opacity", 1);
return tooltip
.style("visibility", "visible")
.html("<span style=font-size:30px;> " + "name: " + d.name +
" (on blue bar)</span>");
})
.on("mouseout", function(d) {
d3.selectAll(".bar").transition()
.style("opacity", 1);
d3.selectAll(".xAxisText").transition()
.style("fill", "black")
.style("font-weight", "normal");
return tooltip.style("visibility", "hidden");
})
I creating a stacked bar chart using this example. The chart works and renders but I can't add a mouseover label.
I tried this...
DATE.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
.append("svg:title")
.text(functino(d){return "foo"});
But this after adding the .append("svg:title... the graph stops rendering. If I remove the .style("fill... line, the graph renders, however it's not stacked and there's no mouseover feature.
I have also tried using the tooltip route. (Source)
.on("mouseover", function() { tooltip.style("display", null); })
.on("mouseout", function() { tooltip.style("display", "none"); })
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
But still not luck. Is there a library I need to load? Not sure what's going on.
The graph stop rendering when you try to append the title because you have a typo: it's function, not functino.
Besides that, this is what you need to get the value of each stacked bar:
.append("title")
.text(function(d){
return d[1]-d[0]
});
Here is the demo: https://bl.ocks.org/anonymous/raw/886d1749c4e01e191b94df23d97dcaf7/
But I don't like <title>s. They are not very versatile. Thus, instead of creating another <text>, as the second code you linked does, I prefer creating a div:
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
Which we position and set the HTML text this way:
.on("mousemove", function(d) {
tooltip.html("Value: " + (d[1] - d[0]))
.style('top', d3.event.pageY - 10 + 'px')
.style('left', d3.event.pageX + 10 + 'px')
.style("opacity", 0.9);
}).on("mouseout", function() {
tooltip.style("opacity", 0)
});
And here is the demo: https://bl.ocks.org/anonymous/raw/f6294c4d8513dbbd8152770e0750efd9/
I am trying to create something similar to this: http://photogrammar.yale.edu/labs/crossfilter/california/ But I am unclear about how clicking on a county is updating the dataTablesm. My version is using a leaflet map with circles created in d3 for markers. I'd like to have it so that when a circle is clicked the dataTablesm only shows those items associated that location. Here is my plnk. In the photogrammar example I am not seeing any references to clicking or updating or rendering.
var feature = g.selectAll("circle")
.data(stateCountGroup.all())
.enter().append("circle")
.style("stroke", "black")
.style("opacity", .6)
.style("fill", "red")
.attr("r", function(d) {
//console.log(d.value);
return d.value*5; })
//.on("click", click)
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html(d.value + " Items")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
dataTablesm
.dimension(yearDimension)
.group(function(d) { return ""
})
.size(100)
.columns([
function(d) { return '<a target="_blank" href="https://arc.lib.montana.edu /ivan-doig/item.php?id=' + d.Item+ '"><img src="https://arc.lib.montana.edu/ivan-doig/' + d.Thumb + '" /></a><div class="byline">' + d.Title + '</div>'; },
]);
map.on("viewreset", update);
update();
function update() {
feature.attr("transform", function(d){
//console.log(d.key);
var coor = map.latLngToLayerPoint(d.key);
return "translate(" +
coor.x + "," +
coor.y + ")";
});
}
//});
// register handlers
//d3.selectAll('a#all').on('click', function () {
//dc.filterAll();
//dc.renderAll();
//});
dc.renderAll();
//dc.redrawAll();
I am trying to put linkText for the link in tree layout d3js but it is not wlrking, created the jsfiddle project here. Can anybody let me know, why the linkText is not coming.
var link = svg.selectAll(".link")
.data(links, function (d) {
return d.target.id;
});
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function (d) {
var o = {
x : source.x0,
y : source.y0
};
return diagonal({
source : o,
target : o
});
});
link.transition().duration(duration)
.style("stroke", function (d) {
return "#99FFCC";
})
.attr("d", diagonal);
link.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial").attr("transform", function(d) {
return "translate(" +
(d.target.y - 50) + "," +
(d.target.x - 10) + ")";
}).attr("text-anchor", "middle").text(function (d) {
alert(d.target.label);
return d.target.label;
});
Your mistake is in here:
link.enter().insert("path", "g")
.attr("class", "link")
...
...
link.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
This code will put the text within the path DOM, which is incorrect.
Text DOM should never be within the path DOM.
Correct code should have been this:
// Update the links…
var link = svg.selectAll("path.link")
.data(links);
//adding the text to the svg
link.enter().insert("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function (d) {
return "translate(" + (d.target.y - 30) + "," + (d.target.x - 10) + ")";
})
.attr("text-anchor", "middle")
.text(function (d) {
console.log(d.target.label);
return d.target.label;
});
Full working code here.
I have a chart with 2 lines using d3.js.Both the line comes from different dataset but y axis range is same for both the lines. I am displaying the nodes for both the line.Nodes for first line appears fine.But nodes for second line doesn't appear.Can anybody please tell me what is the problem?? Here is my code for nodes and line.
var vis = d3.select('#visualisation'),
WIDTH = 400,
HEIGHT = 400,
MARGINS = { top: 20, right: 20, bottom: 20, left: 50 },
xRange = d3.scale.ordinal().rangeBands([MARGINS.left, WIDTH - MARGINS.right], 0.4).domain(barData.map(function (d) { return d.x; })),
y1Range = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([0, d3.max(barData1, function (d) { return d.y1; })]),
xAxis = d3.svg.axis().scale(xRange).tickSize(5);
y1Axis = d3.svg.axis().scale(y1Range).tickSize(5).orient("right").tickSubdivide(true);
/*Draw X Axis values*/
vis.append('svg:g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + (HEIGHT-MARGINS.bottom) + ')')
.call(xAxis);
/*Draw Y1 Axis values*/
vis.append('svg:g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + (HEIGHT - MARGINS.bottom) + ',0)')
.call(y1Axis);
/*Draw the First Line*/
var lineFunc = d3.svg.line()
.x(function (d) {
return (xRange(d.x))+MARGINS.right;
})
.y(function (d) {
return y1Range(d.y1);
})
.interpolate('linear');
/*Animate the line*/
var path1 = vis.append('svg:path')
.attr("d", lineFunc(barData1))
.attr("stroke", "#00CC66")
.attr("stroke-width", 2)
.attr("fill", "none");
var totalLength = path1.node().getTotalLength();
path1.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(1000)
.ease("linear")
.attr("stroke-dashoffset", 0);
/*Draw the circles*/
var circles = vis.selectAll("circle").data(barData1);
circles.enter()
.insert("circle")
.attr("cx", function (d) { return (xRange(d.x))+MARGINS.right; })
.attr("cy", function (d) { return y1Range(d.y1); })
.attr("r", 3)
.style("fill", "#00CC66");
/*Draw the Second Line*/
var lineFunc1 = d3.svg.line()
.x(function (d) {
return (xRange(d.x))+MARGINS.right;
})
.y(function (d) {
return y1Range(d.y2);
})
.interpolate('linear');
var path2= vis.append('svg:path')
.attr("d", lineFunc1(barData2))
.attr("stroke", "#CB347D")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr('class', 'line');
var totalLength = path1.node().getTotalLength();
path2.attr("stroke-dasharray", totalLength + " " +totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(1000)
.ease("linear")
.attr("stroke-dashoffset", 0);
/*Draw the circles for second line*/
var circles = vis.selectAll("circle").data(barData2);
circles.enter()
.insert("circle")
.attr("cx1", function (d) { return (xRange(d.x)) + MARGINS.right; })
.attr("cy2", function (d) { return y1Range(d.y2); })
.attr("r", 3)
.style("fill", "#CB347D");
The problem is that when adding the second set of circles, you're selecting the first set that has just been created:
vis.selectAll("circle").data(barData2)
This selection will contain all the circles you've just added. Then you're matching the data to it, which is fine, but the enter selection will be empty (all data items matched to existing circles). Therefore, the following code, which operates only on the enter selection, does nothing.
The easiest way to fix this is to add a distinguishing class to the second set of circles (and ideally the first one as well):
var circles = vis.selectAll("circle.second").data(barData2);
circles.enter()
.insert("circle")
.attr("class", "second")
// ...