I'm building a "burnup" d3 line chart attempting to forecast trends based on scope and historic data.
A dropdown box selects the data to be displayed, ideally transitioning between lines, but I'm having troubles clearing the previous data displayed, and instead the new lines are written over the existing lines.
Link to the jsfiddle: https://jsfiddle.net/dgf1vts8/
Currently doing it this way (line 329):
svg.append("path")
.datum(selectedData)
.attr("class", "line")
.attr("d", line);
other approach I've tried and did not work (line 318)
var lines = svg.selectAll(".line").data(selectedData).attr("class","line");
lines.transition().duration(500).attr("d",line);
lines.enter()
.append("path")
.attr("class","line")
.attr("d",line);
lines.exit()
.remove();
Any guidance with this would be much appreciated
Since you only append new object in your update function, every time that function is invoked a new line is added to the chart.
The easiest workaround would be to just remove all path objects before you add the new ones.
svg.selectAll("path").remove();
However, the enter-update-exit logic for this would be
var lines = svg.selectAll(".line").data(selectedGroup);
lines.enter() // enter any new data
.append("path")
.attr("class", "line")
.merge(lines)
.datum(selectedData)
.attr("d", line);
lines.exit() // exit
.remove();
The logic for lines is different as for e. g. points or bars, where you need to create an object for every data point of the time series. For lines you only have to create one path object. This is why the data binding to the selection
svg.selectAll(".line")
should be the label of the line. If you work with multiple lines, it is often best to nest the array.
If you start a new project you should probably use the current version d3v7 and not d3v3, since there have been major changes which break compatibility. I reduced your fiddle and changed all the parts to make it work with d3v7: https://jsfiddle.net/esd4kofr/1/
Related
While studying animated line chart of d3.
I ran into two different ways of making line charts and still can't differentiate the difference between the two.
Two cases are as below.
1) case one - drawing line by .attr(d, line(data))
2) case two - drawing line by .attr (d, function(d){return line(d)})
Why some cases line is drawn by just calling the line function,
while the other case asks me to make a anonymous function and put the line function within that?
Here are the sample cases I ran into.
1) case one example
https://bl.ocks.org/pjsier/28d1d410b64dcd74d9dab348514ed256
2) case two example
https://bl.ocks.org/phvaillant/53b90038b9c5ac5f6b817a4f63fbc2af
Whenever you see function(d) {...} as a parameter of .attr(), .style(), .data(), .datum(), .each() and a few others, d refers to the bound datum of each element in the selection.
But if you see a line drawn as in the first approach:
.attr("d", line(data))`
Every element in the selection will be given the same line: line(data) will return the same value for every element in the selection. This approach might be taken if you haven't bound any data to the selection. To draw multiple different lines we'd need to use a loop of some sort and change the value of data. If binding data to the selection (which is a key purpose of D3), you should use that bound data as in the second approach - it'll be easier if you decide to have more than one line.
In the second approach:
.attr("d", function(d) { return line(d); })
The bound datum each element in the selection is passed to line(), as the bound datum for each line can be different, you can have different lines without an explicit loop. For multiple lines, this would certainly be the idiomatic method, though for single lines, the difference is, honestly, fairly negligible.
I didn't actually see .attr("d", line(data)) in the first example's link, the first example's link seems to have instead .attr("d", line), which is equivalent to the second example:
In the second example, this:
.attr("d", function(d) { return line(d); })
Is equivalent to:
.attr("d", line);
In the simpler line the bound datum is passed to line and line is executed for each item in the selection. Here's the same block updated to demonstrate.
I have a question about creating paths using d3.
I was trying to study a code as below.
However, to create a path, the code made an argument svg.selectAll then tie the data set. I was curious about that and manipulate the object to an arbitrary thing, and it still works.
I think I roughly understand the concept of enter(), update() and exit().
However, this case, it challenges me a lot.
Why do I have to select dummy object first and make something?
and according to the concept of enter() in D3, once enter is executed, the newly assigned object should be assigned for all the path I made but it wasn't
The dummy argument selectAll('random') did nothing literally.
The svg part I'm questioning is below.
svg.selectAll("random")
.data(allDensity)
.enter()
.append("path")
.attr("transform", function(d){console.log(d3.values(d));return("translate(0," + (yName(d.key)-height) +")" )})
.datum(function(d){return(d.density)})
.attr("fill", "#69b3a2")
.attr("stroke", "#000")
.attr("stroke-width", 1)
.attr("d", d3.line()
.curve(d3.curveBasis)
.x(function(d,i) { return x(d[0]); })
.y(function(d,i) { return y(d[1]); })
)
entire code:https://codepen.io/jotnajoa/pen/dyogmOz
The strong part of d3 is in modifying objects it previously created. If you don't provide a dummy object, d3 would need an explicit check to see whether it is already created or not. By providing a dummy object, such test isn't needed any more. Also, on a complicated page, the dummy object gives the exact position into the html tree to place the d3 object.
Note that d3 is very open minded, and if you really want you can create the element yourself, especially for examples where the whole page is just one d3 object.
I have a project that is about data visualization, however I am encountering problems. I need to render a map of a country (Brazil) using d3.js. When I move the mouse through the state it should appear the acronym of the state with the income per capita. In addition each state must be in a color tint (in the case I chose green) based on per capita income. I am sending my code because I am not getting my map to render the correct colors and are not showing the acronym and the income. If anyone can help, I appreciate it.
Here´s a link of the code
I see a couple problems with the code that builds each country's path -- try updating this section:
svg.append("g")
.selectAll("path")
.data(topojson.feature(br, br.objects.states).features)
.enter().append("path")
.attr("class", "states")
.attr("fill", function(d) { return color(valueByÚF.get(d.UF)); })
.attr("d", path)
.append("title")
.text(function(d) { return "UF: "+ d.UF; });
Honestly, I didn't check to see if the quantize scale is the right one to use for your data, so you may want to try other scales...
In referencing this chord diagram example: https://bl.ocks.org/mbostock/4062006
The following block sets up the ribbon:
var ribbon = d3.ribbon()
.radius(innerRadius);
But then later in the code ribbon is then used in a way I can't understand:
g.append("g")
.attr("class", "ribbons")
.selectAll("path")
.data(function(chords) { return chords; })
.enter().append("path")
.attr("d", ribbon)
.style("fill", function(d) { return color(d.target.index); })
.style("stroke", function(d) { return d3.rgb(color(d.target.index)).darker(); });
How is it that .attr("d", ribbon) knows how to pass in the chord data to ribbon? Is this just part of D3 magic that I have to just remember?
In your example, d3.chord() computes the layout of the data. It's important to note that, in d3, "layout" has a different meaning of layout in data visualisation: here, layout has to do with preparing the data to create a given chart (not the visual "layout" of the chart).
Basically, d3.chord() takes your data matrix and creates another data matrix with a bunch of starting angles and ending angles, associating sources to targets.
After d3.chord() modifies the data, they are passed to d3.ribbon(). d3.ribbon is the real path generator, creating the actual path that will be painted in the SVG. How does it knows the data? You have previously bounded the modified data to your group:
.datum(chord(matrix));
That's the data passed to d3.ribbon().
According to the API, d3.ribbon()...
...generates a ribbon for the given arguments. The arguments are arbitrary; they are simply propagated to the ribbon generator’s accessor functions along with the this object [...] with the default settings, a chord object expected.
I have a series of horizon graphs that have been created like this:
d3.select("body")
.selectAll(".horizon")
.data(metrics)
.enter()
.append("div")
.attr("class", "horizon")
.attr("id", function(d){return d.toString();})
.call(context.horizon().height(['75']));
where metrics is an array of metric objects.
I want to redefine the extent for one of those horizon objects.
I've tried calling the extent function on the one graph, but I'm not sure if I'm calling it, or even selecting it, correctly:
d3.select("#id-attribute")
.call(context.horizon().extent(function(d,i){return([0,1000]);}));
This sort-of seems to work, but the graph display gets screwed up, with additional whitespace being added below the graph and the motion of the graph not accounting for that and leaving the top of the graph unanimated. I suspect that it's in some way due to it being a new instance of the extent object, so I tried this:
d3.select("#id-attribute")
.call(extent(function(d,i){return([0,1000]);}));
but that generates: "ReferenceError: extent is not defined".
I've tried redefining the metric's extent function, effectively:
metrics[3].extent = function(d,i) {return([0,100]);};
but that causes the graph to be blank (although mousing over it reveals numbers in the readout), and causes its readout and the readouts of the graphs that appear below it to be blank when the mouse is not hovering over any of the graphs.
I honestly have no comprehension of how this stuff fits together, nor am I particularly experienced with JavaScript, so I'm not sure where my error lies. I'm totally out of my depth. Any tips would be greatly appreciated.
Do you need to redefine the extent after the initial call where the horizons are generated?
If not, try something like this:
d3.select("body")
.selectAll(".horizon")
.data(metrics)
.enter()
.append("div")
.attr("class", "horizon")
.attr("id", function(d){return d.toString();})
.call(context.horizon()
.height(['75'])
.extent(function(d,i){ //use this function to change extents
if (d.toString() == 'Whatever the name is'){
return [0,1000];
} else {
return [ <default min>, <default max>]
}
})
)
You can either set your own default extent, or use a function like d3.min()/d3.max() to get the value for the horizons. You just need to return an array with two elements.