I am trying to create svg paths with d3 and trying to understand how can I ask d3 to create paths based on separate data, notebook
For example, here I have fixed data for path Value and two separate sets as index1 and index2. I want to create paths based on these, meaning I want to create 11 paths altogether, whereas the code creates only 8 paths.
My best guess is, d3 is probably overwriting the first 3 paths from index1 when it starts working on path2 as the first 3 paths were already available in the DOM.
How can I ask d3 to ignore the first 3 paths that are available in the DOM and start from scratch for index2? I donât want to combine index1 and index 2. What is a d3 way to reach the desired output?
As an alternative, I can do this. But is this the only way?
There are a few things Iâd suggest. First off, an Observable tip: having one cell modify the contents of another is going to lead to trouble as you get more complex. In my notebook below I converted your cell to return an SVG element directly instead of having a separate âoutputâ cell.
On to my recommended solutions. First, you could simply combine the two data arrays into one call to data. That way d3 doesnât have a chance to overwrite the existing lines the second time. The core of that is a line like this.
This side steps the problem though. To understand whatâs going on here, lets look at what join does. Here is some documentation from selection.join / D3 | Observable
If the joining selection isnât emptyâas on subsequent iterations of the loop aboveâ selection.join appendsentering elements andremovesexiting elements to match the data!
So in other words, the second time you call join, d3 will modify the existing elements you selected to match the new data set. To do this you would need to have different selectors for the two data sets. You could do this in several ways, including by putting each set into itâs own <g> element. In my notebook, I gave each line a class, either index1 or index2 and update the selectAll calls to only select elements with that particular class. Since the second selectAll wonât select the existing index1 elements, .join() wonât remove them to match the data.
I am stunned by this that we can distinguish the selector like this.
It does not have to be svg.selectAll('path.index1') or svg.selectAll('path.index2').
It could simply be anything - svg.selectAll('foo') or svg.selectAll('bar'). As far as I know, that d3.selectAll takes a valid CSS selector.
This technically works for this case, but wouldnât work if you try and update the visualization over time. One of the powers of d3âs selection model and the join function is that you can update the data over time and change the visualization to match by simply calling this same code again. That only works however if the selectAll actually selects the previous elements.
In this case however, the elements you add wonât match the selector. Thatâs ok for a one shot visualization. What happens is that svg.selectAll('foo') selects nothing (because no element has that tag), and then join appends elements for every data entry. Those elements donât need to match the original selection (they should, but nothing enforces that). Then you are able to style the recently added elements since they are implicitly selected.
Sorry for stretching this one as I am trying my best to understand the side-effects before I turn this into a production code.
Will that not be exactly the same if I use svg.selectAll('path.index1'), cause it will not return anything as well or it would be significantly different than svg.selectAll('foo')
The result in this specific case wonât be different, since you are only rendering it once. To see the difference, consider this specific example from the join docs:
It needs to be able to reselect the previous elements so it can remove some of them, and update others. If it used a bogus selector like âfooâ, then it could never reselect the already existing items, and it would simply add more and more line every time.