D3: data, enter, append pattern adds data to outer block - javascript

I'm using the D3 javascript library to render some basic web charts. I want to add three <path> elements to an <svg> block, but D3 is adding the elements to the end of the <html> block instead. Here's the complete html source:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="d3.v2.js"></script>
<script>
var chartData = [ 1, 2, 3 ];
d3.select("html").select("body").append("svg")
.data(chartData, function(d) { console.log("data d:", d); return d; })
.enter()
.append("path")
.attr("d", function(d) { return d; });
</script>
</body>
Chrome's developer console shows the resulting html to be:
<html><head><meta charset="utf-8">
<style type="text/css"></style></head><body>
<script src="d3.v2.js"></script>
<script>
var chartData = [ 1, 2, 3 ];
d3.select("html").select("body").append("svg")
.data(chartData, function(d) { console.log("data d:", d); return d; })
.enter()
.append("path")
.attr("d", function(d) { return d; });
</script><svg></svg>
</body><path d="1"></path><path d="2"></path><path d="3"></path></html>
The <svg> block was created, but for some reason, the <path> blocks are outside of it. How can I correct this error, and put them inside the <svg> where they belong?

First off, you should have a high-level variable that only refers to the svg. Secondly, you should specify the height and width of the SVG when creating it. You don't need to specify a select of html, so:
var width = 400,
height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
Now you can use the svg variable to append paths. It is recommended that you group the paths together (hence the "g" appended first). And you should also do a selectAll for paths, because you want to bind the data you are adding to the path elements. So:
svg.append("g").selectAll("path")
.data(chartData)
.enter().append("path")
.attr("d", function(d) { return d; });
I recommend the D3 tutorials here: http://alignedleft.com/tutorials/d3/

For future visitors having a similar issue:
It appears the difference of behavior in this case (and similar others) between select and selectAll hinges on the fact that selectAll creates a new group.
D3's selection object is an array of arrays with some additional special properties. Each array corresponds to a group of objects. One property of the group is the parentNode, which is the node from which the initial selectAll was executed.
When the outermost selection is a select, the root is implicitly the document's root node--in this case, html.
Since selectAll creates a new group within the selection for each match (or entry), each group is assigned as its parent the node from which it derives. With select, since no new group is created, the original parentNode for each group is preserved, even if it no longer accurately represents the parent of the nodes within the group.
The parentNode also appears (empirically) to be the node to which enter selections' appended nodes are attached.
This is why select produced the observed behavior, and selectAll remedied the problem.

Related

Multiple force-directed graphs in 1 svg (problems with text display)

I want to display multiple force directed graphs. Preferably with only using 1 svg element as well (as I think that increases performance as well as let me make a width and height for whole simulation as this may differ based on data).
My first thought and with some research I just did a for loop, re-evaluated my nodes/edges arrays and put that in a function that generates the force-directed graph.
for (var i = 0; i < sampleData.length; i++)
{
var nodes = [];
var edges = [];
x = x+100; //update position (i want to show grpahs side by side.)
root = sampleData[i];
nodes.push({"name": root, "x": x, "y": y, "fixed": true, "color": "purple"});
//These 2 recurisve functions generate my nodes and edges array just fine.
buildParents(childs, parents, test, counter);
buildChildren(childs, parents, test, counter);
//apply id to each node
nodes.forEach((d,i)=>d.generatedId='id'+i);
//build makes the force-directed layout.
build(nodes, edges);
}
This actually appears to work fine for me with my nodes and links. My issue is the text does not display for all nodes like it does if I only pass in one set of data. I have text defined as so in the force simulation:
var nodes_text = svg.selectAll(".nodetext")
.data(nodes)
.enter()
.append("text")
.attr("class", "nodetext slds-text-heading--label")
.attr("text-anchor", "middle")
.attr("dx", -20)
.attr("dy", 20)
.text(d=>d.name)
.attr('opacity',0)
I was able to reproduce this error by just making 2 arrays for nodes and 2 arrays for edges and passing it into a simulation. Here is a simple program that reproduces my error:
https://jsfiddle.net/mg8b46aj/9/
I think fixing this JFiddle would give me the right idea on how to put it in my program.
So I just call the build function twice (either order is error). The left node has text but one of them isn't the correct text field. Also dragging it around a little bit makes it "leave" its text behind. The right graph has nothing.
Edit: And clicking the a node on the right graph seems to reset the text positions.
The problem is with the d3 selector use for selecting labels. As you need two separate force layout diagrams, you should use a selector as shown below for labels.
var nodes_text = svg.append('g') //Append new group for labels in new diagram
.attr("class", "labels")
.selectAll(".nodetext")
.data(nodes)
.enter()
.append("text");
Updated Fiddle: https://jsfiddle.net/gilsha/qe7bbnwn/1/

How exactly the bars(or rectangles) are created in d3 barChart demo part 1?

I understand every line of the barchart source code . However, besides code on setting each div's width using .style("width", function(d) { return x(d) + "px"; }), I don't see codes specifically saying "let's make bars or rectangles".
var data = [4, 8, 15, 16, 23, 42];
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, 420]);
d3.select(".chart")
.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function(d) { return x(d) + "px"; })
.text(function(d) { return d; });
My question:
Was the demo code a quick and dirty way of producing bars? Is there a formal or standard way of creating rectangles or bars using d3?
Thanks
First, those are not rectangles (as an SVG rectangle), but simply divs. Those divs have a rectangular shape and a background-color set in the CSS, so, they look like rectangles. Most of the D3 books (like Zhu's, Murray's etc) teach how to make charts with divs before moving to actual SVG rectangles.
But if what you don't understand is how these divs are created (judging by the title of your question), the code is right here:
d3.select(".chart")
.selectAll("div")
.data(data)
.enter()
.append("div");
What does it say? Let's see:
.selectAll("div"): This selects all the "div". But there is none so far... so, this is just a placeholder. Then:
.data(data). This binds the data: the data is data. data is an array of 6 numbers. So, recapitulating, right now, there is no div, and these inexistent divs are bound to 6 numbers. So, our "enter" selection will be a selection of 6 divs, one for each number in the array.
.enter(): this is the "enter" selection. We have 6 numbers in our data, and zero div in the chart. So, our enter selection has 6 (new) divs.
.append("div"): This creates the divs. With append, we create the actual DOM elements.
This is a way to visually understand the enter selection:
In the first selectAll, we selected DOM elements that didn't exist at that time (they are just placeholders). Then, we bound data to those elements. Once we have 6 data numbers and 0 elements, our enter selection (corresponding to data without elements) has 6 new elements.

Appending child nodes to parent nodes in D3 sankey

Currently, I am working on clubbing together all the child nodes together in a rectangle rather than creating a seperate parent circle for future reference. Following is an example of the sankey chart i am working on:
bi-directional sankey chart by Neilos.
However i am not able to understand the logic used for collapsers here. For example:
collapser = svg.select("#collapsers").selectAll(".collapser")
.data(biHiSankey.expandedNodes(), function (d) { return d.id; });
This will create collapser to which you will append the circle. Now, is there any way we can append all the child nodes to this circle(by making it big enough), so that graphically we can show that all the child nodes belong to same parent class?
At line 433 of the default sankey code you see:
collapserEnter.append("circle")
.attr("r", 100)
.style("fill", function (d) {
d.color = colorScale(d.type.replace(/ .*/, ""));
return d.color;
});
Does this mean than i can try appending the child nodes to this collapserEnter object? If not, is there any other way we can do it?
All help will be appreciated.

d3.js path dom element

I am creating a streamgraph, having used the example code from http://bl.ocks.org/mbostock/4060954 as a template.
I draw the streamgraph:
var svg = d3.select("#graph_area").append("svg")
.attr("width", width)
.attr("height", height)
....
svg.selectAll("path")
.data(layers)
.enter().append("path")
.attr("d", function (d) {return area(d.layer);})
Now it seems that I am not allowed to choose a different name from "path" for the DOM element here. If I do, then streamgraph no longer plots. Why is that?
Then I want to add a legend with bullets, but they don't plot. The elements show up in my web inspector (firebug), but their graphical representation is just not there. I figured it might be a similar problem with the DOM element name, but I don't actually know. Here is the code for my bullets:
//draw all the legend bullets and effects
svg.selectAll("bullets")
.data(layers)
.enter().append("bullets")
.attr("cx", 200)
.attr("cy", function(d, i) { return 20 + i*10; })
.style("fill", function(d) { return d.color; })
.attr("r", 5)
I briefly looked at API's for paths here and here, but I didn't find my answers there.
The element names you can use are defined in the SVG specification. This is a specific set (e.g. path, circle) -- using anything else will work in terms of appending the element to the DOM, but the browser won't know how to interpret and render it.
It's the same way in HTML -- there's a specific set of defined element names that browsers know how to render.

what is the point of calling selectAll when there are no existing nodes yet on D3.js

when i do this :
var link = svg.selectAll('.link')
.data(links)
.enter().append('path')
.attr('class', 'link')
.attr('d', diagonal)
There is no node with the .link class. So selectAll returns en empty selection. But i've found that, when you call this for the first time, you can selectAll('whaterverYouWant')
That is because D3 doesn't matter about what you select, as you provide the tag name and the classes later .append('path'), .attr(class ...).
And, if you want to select elements that already exist, i read in the doc that .enter returns a placeholder selection. But if it returns a selection of placeholders (anonymous tags with .link class ?), there is no point to append a path to a path.
When i call .append, it does what i want, i.e. append a path to svg. But i don't understand the logic behind that. (I'm glad it works though, because d3 is powerful)
So, ok i selectAll('anything') and append what i want, regardless of what i selected. But if i try this:
d3.select('#savestring-debug')
.selectAll('div')
.data(debugobjs)
.enter().append('span')
.attr('style', function(d) { return 'background:#'+d.color })
.text(function(d) { return d.aff });
This would create placeholders for divs, but i append spans. Actually spans are created but i'm still looking for my divs ;)
So, what is the principle behind selectAll >> data >> enter >> append ?
thanks
The principle behind selectAll > data > enter > append is explained pretty well by
Mike Bostock here: http://bost.ocks.org/mike/join/ where he explains the concept of the data-join. I can't speak with any authority on the right way to use selectAll, but the way I use it is to select all of the elements I am going to be modifying-appending-removing within the part of the SVG that I need to modify.
So if I'm working with "rects" in a certain area, I'll do something like this:
var svg = d3.select('#graphID')
.append("svg")
.attr("width", 300)
.attr("height", 500);
var graphGroup = self.svg.append("g");
//...Inside a render function
//just want all the "rect" elements in graphGroup
var rects = graphGroup.selectAll("rect")
.data(dataset);
//depending on dataset new rects will need to be appendend
rects.enter()
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 0)
.attr("height", 0)
//all rects are transitioned to new co-ordinates
rects.transition().duration(500)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d){
return yScale(d);
})
//rects that have no data associated with them are removed
rects.exit()
.transition()
.duration(500)
.attr("x", -xScale.rangeBand())
.remove();
With the idea that I could have other rects in the SVG that do not belong to graphGroup. I just selectAll the rects in a certain area and work on them when needed.
This is a great question and a slightly odd property of D3. If you look carefully how anything is done in D3 you'll notice that everything is added by appending to what is previously created. So the logic behind having the svg.selectAll('whatever class of stuff you're going to add') is that you are kinda making a placeholder for where whatever you are about append to go. It's like the svg is a wall and you're hanging hooks on the upper ridge for you to THEN hang your paintings from. If you don't have the selectAll, I just tried this, you will still append whatever you were gonna make to the page, but it won't be appended to the svg.
The data-->enter-->append is basically saying for each element in the larger data file that you are passing into the data function, make a new element, and append this element to my selection with such and such properties (set when you use the .attr).

Categories

Resources