I have looked for possible solutions but nothing worked for me.
Problem is when I try to update the data and the pie chart accordingly, the transition does not work and prints error, mentioned in the topic, more than once. I am kinda new to JS, so I am looking for some help.
Code:
var pie = d3.pie();
var pathArc = d3.arc()
.innerRadius(200)
.outerRadius(250);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var t = d3.transition()
.duration(500);
var path = piesvg.selectAll("path")
.data(pie(gdp_values));
path.exit()
.transition(t)
.remove();
path.transition(t)
.attr("d",function (d) {
return pathArc(d);
})
.attr("fill",function(d, i){return color(i);});
path.enter()
.append("path")
.transition(t)
.attr("d",pathArc)
.attr("fill",function(d, i){return color(i);});
Initial dataset(gdp_values);
[407500000000, 417300000000, 439800000000, 680900000000, 980900000000, 1160000000000, 1727000000000, 2249000000000, 2389000000000, 3074000000000]
It does work when data changed to the another similar data, however when changes to the data as follows, transitions doesnot work and throws the same error 40 times.
[7714000000, 123900000000, 846200000000]
Any thoughts?
You have to invert the order of your selections: the enter selection should come before the update selection:
path.enter()
.append("path")
.transition(t)
.attr("d", pathArc)
.attr("fill", function(d, i) {
return color(i);
});
path.transition(t)
.attr("d", function(d) {
return pathArc(d);
})
.attr("fill", function(d, i) {
return color(i);
});
Here is the demo:
var piesvg = d3.select("svg").append("g").attr("transform", "translate(250,250)")
var gdp_values = [407500000000, 417300000000, 439800000000, 680900000000, 980900000000, 1160000000000, 1727000000000, 2249000000000, 2389000000000, 3074000000000];
var gdp_values2 = [7714000000, 123900000000, 846200000000];
var pie = d3.pie();
var pathArc = d3.arc()
.innerRadius(200)
.outerRadius(250);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var t = d3.transition()
.duration(500);
update(gdp_values)
setTimeout(function() {
update(gdp_values2);
}, 1000)
function update(data) {
var path = piesvg.selectAll("path")
.data(pie(data));
path.exit()
.transition(t)
.remove();
path.enter()
.append("path")
.transition(t)
.attr("d", pathArc)
.attr("fill", function(d, i) {
return color(i);
});
path.transition(t)
.attr("d", function(d) {
return pathArc(d);
})
.attr("fill", function(d, i) {
return color(i);
});
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="500"></svg>
I got this error when making data updates to a chart. Solution in my case was to prevent drawing of the chart during the time the data was loading. Guessing the arcTween code doesn't handle data changes gracefully.
Related
I’ve created a line graph in D3. To ensure the line doesn’t overlap the y-axis, I have altered the range of the x-axis. As a result of this, there is a gap between the x-axis and the y-axis which I am trying to fill with another line.
The rest of the graph uses the D3 update pattern. However, when I try to use the pattern on this simple line, two path elements are drawn (one on top of the other). I have tried numerous solutions to correct this issue but I’m not having any luck. Does anyone have any suggestions?
The code below is what is drawing two of the same path elements
var xAxisLineData = [
{ x: margins.left , y: height - margins.bottom + 0.5 },
{ x: margins.left + 40, y: height - margins.bottom + 0.5 }];
var xAxisLine = d3.line()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
var update = vis.selectAll(".xAxisLine")
.data(xAxisLineData);
var enter = update.enter()
.append("path")
.attr("d", xAxisLine(xAxisLineData))
.attr("class", "xAxisLine")
.attr("stroke", "black");
Your problem is here:
var update = vis.selectAll(".xAxisLine")
.data(xAxisLineData);
this is a null selection, assuming there are no elements with the class xAxisLine, which means that using .enter().append() will append one element for each item in the xAxisLineData array.
You want to append one path per set of points representing a line, not one path for each in a set of points representing a line.
You really just want one line to be drawn, so you could do:
.data([xAxisLineData]);
or, place all the points in an array when defining xAxisLineData
Now you are passing a data array to the selection that contains one item: an array of points - as opposed to many items representing individual points. As the data array has one item, and your selection is empty, using .enter().append() will append one element:
var svg = d3.select("body").append("svg").attr("width",500).attr("height",400);
var lineData = [{x:100,y:100},{x:200,y:200}]
var xAxisLine = d3.line()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
var colors = ["steelblue","orange"];
var line = svg.selectAll(null)
.data([lineData])
.enter()
.append("path")
.attr("d", xAxisLine(lineData))
.attr("class", "xAxisLine")
.attr("stroke-width", function(d,i) { return (1-i) * 10 + 10; })
.attr("stroke", function(d,i) { return colors[i]; });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Compare without using an array to hold all the data points:
var svg = d3.select("body").append("svg").attr("width",500).attr("height",400);
var lineData = [{x:100,y:100},{x:200,y:200}]
var xAxisLine = d3.line()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
var colors = ["steelblue","orange"];
var line = svg.selectAll(null)
.data(lineData)
.enter()
.append("path")
.attr("d", xAxisLine(lineData))
.attr("class", "xAxisLine")
.attr("stroke-width", function(d,i) { return (1-i) * 10 + 10; })
.attr("stroke", function(d,i) { return colors[i]; });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
But, we can make one last change. Since each item in the data array is bound to the element, we can reference the datum, not the data array xAxisLineData, which would make adding multiple lines much easier:
.attr("d", function(d) { return xAxisLine(d) })
Note in the demo below that the variable xAxisLineData is defined as an array of arrays of points, or an array of multiple lines.
var svg = d3.select("body").append("svg").attr("width",500).attr("height",400);
var lineData = [[{x:100,y:100},{x:200,y:200}],[{x:150,y:150},{x:260,y:150}]]
var xAxisLine = d3.line()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; });
var colors = ["steelblue","orange"];
var line = svg.selectAll(null)
.data(lineData)
.enter()
.append("path")
.attr("d", function(d) { return xAxisLine(d) }) // use the element's datum
.attr("class", "xAxisLine")
.attr("stroke-width", function(d,i) { return (1-i) * 10 + 10; })
.attr("stroke", function(d,i) { return colors[i]; });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
I am using d3.js with the force layout. Now, with the help of the dynamically changing array data it is possible to highlight nodes dynamically based on the array. Also with the code below i am able to show up dynamically the names of the nodes, which are part of the array, as a text.
So, when the array has for example 3 entries, then 3 nodes are shown up and also 3 names of the nodes appear. Let's say their names are "a", "b", "c", so the text "a", "b", "c" appears on screen.
Now, when i click on the new appeared text "a", then i want the node, which contains that name, to be filled green. I tried this with the function called specialfunction. The problem is, that all nodes fill green when i click
on the text "a". Can someone of you guys maybe help? Thanks.
var texts = svg.selectAll(".texts")
.data(data);
textsExit = texts.exit().remove();
textsEnter = texts.enter()
.append("text")
.attr("class", "texts");
textsUpdate = texts.merge(textsEnter)
.attr("x", 10)
.attr("y", (d, i) => i * 16)
.text(d => d.name)
.on("click", specialfunction);
function specialfunction(d) {
node.style("fill", function(d){ return this.style.fill = 'green';});
};
Right now, your specialFunction function is only taking the nodes selection and setting the style of all its elements to the returned value of...
this.style.fill = 'green';
... which is, guess what, "green".
Instead of that, filter the nodes according to the clicked text:
function specialFunction(d) {
nodes.filter(function(e) {
return e === d
}).style("fill", "forestgreen")
}
In this simple demo, d is the number for both texts and circles. Just change d in my demo to d.name or any other property you want. Click the text and the correspondent circle will change colour:
var svg = d3.select("svg");
var data = d3.range(5);
var nodes = svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cy", 50)
.attr("cx", function(d) {
return 30 + d * 45
})
.attr("r", 20)
.style("fill", "lightblue")
.attr("stroke", "gray");
var texts = svg.selectAll(null)
.data(data)
.enter()
.append("text")
.attr("y", 88)
.attr("x", function(d) {
return 26 + d * 45
})
.attr("fill", "dimgray")
.attr("cursor", "pointer")
.text(function(d) {
return d
})
.on("click", specialFunction);
function specialFunction(d) {
nodes.filter(function(e) {
return e === d
}).style("fill", "forestgreen")
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
EDIT: Answering your comment, this even simpler function can set the circles to the original colour:
function specialFunction(d) {
nodes.style("fill", function(e){
return e === d ? "forestgreen" : "lightblue";
})
}
Here is the demo:
var svg = d3.select("svg");
var data = d3.range(5);
var nodes = svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cy", 50)
.attr("cx", function(d) {
return 30 + d * 45
})
.attr("r", 20)
.style("fill", "lightblue")
.attr("stroke", "gray");
var texts = svg.selectAll(null)
.data(data)
.enter()
.append("text")
.attr("y", 88)
.attr("x", function(d) {
return 26 + d * 45
})
.attr("fill", "dimgray")
.attr("cursor", "pointer")
.text(function(d) {
return d
})
.on("click", specialFunction);
function specialFunction(d) {
nodes.style("fill", function(e){
return e === d ? "forestgreen" : "lightblue";
})
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
I have a graph with a line, two areas and a few circles in it:
I wrote an update method, which works just fine for everything except the circles:
function updateGraph() {
xScale.domain([startDate.getTime(), endDate.getTime()]);
addTimedTickPadding(xAxis);
yScale.domain([0, d3.max(interpolatedData, function (d) {
return d.y0 + d.y1;
})]);
var updateGroup = parentGroup.transition();
updateGroup.select('.xaxis')
.duration(750)
.call(xAxis);
updateGroup.select('.yxaxis')
.duration(750)
.call(yAxis);
updateGroup.select('.xgrid')
.duration(750)
.call(verticalGridLine());
updateGroup.select('.area-top')
.duration(750)
.attr('d', areaTop(interpolatedData));
updateGroup.select('.area-bottom')
.duration(750)
.attr('d', areaBottom(interpolatedData));
updateGroup.select('.line')
.duration(750)
.attr('d', line(interpolatedData));
updateGroup.select('.post')
.duration(750)
.data(interpolatedData)
.attr('cx', function (d) {
return xScale(dateParser.parse(d.x))
})
.attr('cy', function (d) {
return yScale(d.y0 + d.y1)
});
}
Everything transitions as expected but the circles. They just stay in place. My thought was: I append the updated data to each element and alter the cx and cy attributes accordingly, so the circles transition to their new place. But they don't move :( What am I doing wrong here?
Here is the initial code for the circles:
dataGroup
.selectAll('.post')
.data(interpolatedData)
.enter()
.append('circle')
.classed('post', true)
.attr({
'r': circleRadius,
'fill': circleFillColor,
'stroke-width': circleStrokeWidth,
'stroke': circleStrokeColor,
'cx': (function (d) {
return xScale(dateParser.parse(d.x))
}),
'cy': (function (d) {
return yScale(d.y0 + d.y1)
})
});
PS: I read abaout enter() and exit() here but don't really know, how they could be useful for updating the data which is appended on the elements.
PPS: I tried selectAll() too, but it doesn't seem to have a data() function.
I am a TopoJson newbie and I have some data that looks like this....
{"type":"Polygon","properties":{"name":"Arkansas"},"arcs":[[0,1,2,3,4,5]]}
I am trying to say just output Arkansas so I cam up with the following (I am using underscore.js)...
var collection = topojson.feature(us, us.objects.subunits).features;
var final = [];
_.forEach(collection, function(item){
if(item.properties.name == "Arkansas"){
final.push(item);
}
});
svg.selectAll(".subunit")
.data(final)
.enter()
.append("path")
.attr("class", function(d) { return "subunit " + d.id; })
.attr("d", path);
This works great but isn't there an easier way? Is there something like us.objects.subunits["Arkansas"] I can do?
(From my mobile and memory, try out the following)
Usually, the way to go is something like :
var final = topojson.feature(us, us.objects.subunits).features;
svg.selectAll(".subunit")
.data(final)
.enter()
.append("path")
.attr("class", function(d) { return "subunit " + d.id; })
.attr("d", function(d){ if(d.properties.name == "Arkansas"){ return d } });
The filtering is directly within the .attr('d', function(d){…}). If not working, try .attr('d', function(d, path){…}) and return the path.
I am trying to use the enter(), update(), exit() pattern for a line chart, and I'm not getting my lines to appear properly.
A fiddle example. http://jsfiddle.net/wy6h1jcg/
THey do show up in the dom, but have no x or y values (though they are styled)
My svg is already created as follows:
var chart = d3.select("#charts")
.append("svg")
chart
.attr("width", attributes.chartsWidth)
.attr("height", attributes.chartsHeight);
I want to create a new object for my update binding, as follows:
var thechart = chart.selectAll("path.line").data(data, function(d){return d.x_axis} )
thechart.enter()
.append("path")
thechart.transition().duration(100).attr('d', line).attr("class", "line");
But this is no good.
Note, this does work (but can't be used for our update):
chart.append("path")
.datum(data, function(d){return d.x_axis})
.attr("class", "line")
.attr("d", line);
One other note:
I have a separate function that binds data for creating another chart on the svg.
var thechart = chart.selectAll("rect")
.data(data, function(d){return d.x_axis});
thechart.enter()
.append("rect")
.attr("class","bars")
Could these two bindings be interacting?
This is the update logic I ended on, still a closured pattern:
function updateScatterChart(chartUpdate) {
var wxz = (wx * 37) + c;
var x = d3.scale.linear()
.range([c, wxz]);
var y = d3.scale.linear()
.range([h, hTopMargin]);
var line = d3.svg.line()
.x(function(d) { return x(+d.x_axis); })
.y(function(d) { return y(+d.percent); }).interpolate("basis");
if (lastUpdateLine != chartUpdate) {
console.log('updating..')
d3.csv("./data/multiline.csv", function(dataset) {
console.log(chartUpdate);
var data2 = dataset.filter(function(d){return d.type == chartUpdate});
x.domain(d3.extent(data2, function(d) { return +d.x_axis; }));
y.domain([0, d3.max(data2, function(d) { return +d.percent; })]);
var thechart2 = chart.selectAll("path.line").data(data2, function(d){return d.neighborhood;});
thechart2.enter()
.append("svg:path")
.attr("class", "line")
.attr("d", line(data2))
thechart2.transition()
.duration(800)
.attr("d", line(data2))
.style("opacity", (chartUpdate == 'remove') ? 0 : 1 )
thechart2.exit()
.transition()
.duration(400)
.remove();
})
}
lastUpdateLine = chartUpdate;
}