Why is label not getting added to all my paths? - javascript

Plunker: https://next.plnkr.co/edit/17t5ujwC71IK3PCi
Why is following not adding a "test" label to all my polygons?
/* NOT Working code */
groups.selectAll('.path_placeholder')
.enter()
.append('text')
.text("test")
Update
.enter() wasn't required as mentioned by Xavier. Removing it showed "test" for all nodes. But why then its not working when I do provide data and use enter() as following:
groups.selectAll('.path_placeholder')
.data(groupIds, function(d) {
return d;
})
.enter()
.append('text')
.text(function(d){
console.log(d);
return d;
})
I am trying to be able to show label for each of my polygon and for now just trying to add a dummy label to each of them.

Your problem here is the paths is a <path> selection, not a <g> one:
paths = groups.selectAll('.path_placeholder')
.data(groupIds, function(d) { return +d; })
.enter()
.append('g')
.attr('class', 'path_placeholder')
.append('path')//this makes the selection pointing to <path> elements
.attr('stroke', function(d) { return color(d); })
.attr('fill', function(d) { return color(d); })
.attr('opacity', 0);
Because of that, when you do...
groups.selectAll('.path_placeholder')
.data(groupIds, function(d) {
return d;
})
.enter()
//etc...
... your "enter" selection is empty, because you already have data associated to that paths selection.
Besides that, it makes little sense using a proper "enter" selection for the texts, since the data is the same data bound to the groups.
Solution: the solution here, which is the idiomatic D3 for this situation, is creating an actual <g> selection.
We can do that by breaking the paths selection, and giving it another name:
pathGroups = groups.selectAll('.path_placeholder')
.data(groupIds, function(d) {
return +d;
})
.enter()
.append('g')
.attr('class', 'path_placeholder');
Then you can just do:
paths = pathGroups.append('path')
.attr('stroke', function(d) {
return color(d);
})
.attr('fill', function(d) {
return color(d);
})
.attr('opacity', 0)
texts = pathGroups.append('text')
.text(function(d) {
return d;
});
Here is the forked Plunker: https://next.plnkr.co/edit/31ZPXIvSI287RLgO

Related

dot (symbol) color on d3.js multiline chart

I am trying to replicate this example of a multiline chart with dots. My data is basically the same, where I have an object with name and values in the first level, and then a couple of values in the second level inside values. For the most part, my code works, but for some reason, the j index in the anonymous function for the fill returns an array of repeated circle instead of returning the parent of the current element. I believe this may have something to do with the way I created the svg and selected the elements, but I can't figure it out. Below is an excerpt of my code that shows how I created the svg, the line path and the circles.
var svgb = d3.select("body")
.append("svg")
.attr("id","svg-b")
.attr("width", width)
.attr("height", height)
var gameb = svgb.selectAll(".gameb")
.data(games)
.enter()
.append("g")
.attr("class", "gameb");
gameb.append("path")
.attr("class", "line")
.attr("d", function(d) {return line_count(d.values); })
.style("stroke", function(d) { return color(d.name); })
.style("fill", "none");
gameb.selectAll("circle")
.data(function(d) {return d.values;})
.enter()
.append("circle")
.attr("cx", function(d) {return x(d.date);})
.attr("cy", function(d) {return y_count(d.count);})
.attr("r", 3)
.style("fill", function(d,i,j) {console.log(j)
return color(games[j].name);});
j (or more accurately, the third parameter) will always be the nodes in the selection (the array of circles here), not the parent. If you want the parent datum you can use:
.attr("fill", function() {
let parent = this.parentNode;
let datum = d3.select(parent).datum();
return color(datum.name);
})
Note that using ()=> instead of function() will change the this context and the above will not work.
However, rather than coloring each circle independently, you could use a or the parent g to color the circles too:
gameb.append("g")
.style("fill", function(d) { return color(d.name); })
.selectAll("circle")
.data(function(d) {return d.values;})
.enter()
.append("circle")
.attr("cx", function(d) {return x(d.date);})
.attr("cy", function(d) {return y_count(d.count);})
.attr("r", 3);
Here we add an intermediate g (though we could use the original parent with a few additional modifications), apply a fill color to it, and then the parent g will color the children circles for us. The datum is passed on to this new g behind the scenes.

First element in data getting rerendered rather than updated

Here is something similar to what is happening in my code : https://codepen.io/anon/pen/zJmvXa?editors=1010
Press the Update button to see the issue.
The problem in this codePen, is its only redrawing the first element (Myriel).
I think it must be around the enter,exit or the merge, but I don't fully grasp what's going on.
I thought the merge was to merge existing data with new, and the only data that should go into the enter should be the new data. And the exit is for removing redundant data?
As this graph has both circles and text, should the merge be made on the 'g' element that contains these ?
Perhaps it's to do with the data function :
.data(dataset1.nodes, function(d, i) {
return d;
});
If i change this to use d.id, it re renders everything again. I presume it's due to the data having an id attribute. How is this suppose to be done ?
// Apply the general update pattern to the nodes.
forceNetwork.node = d3
.select("#nodesContainer")
.selectAll("g.network-node")
.data(dataset1.nodes, function(d, i) {
return d
});
forceNetwork.node.exit().remove();
forceNetwork.nodeEnter = forceNetwork.node
.enter()
.append("g")
.attr("id", function(d, i) {
return "network-g-node-" + d.id;
})
.attr("class", function(d, i) {
return "network-node";
})
.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
).merge(forceNetwork.node);
forceNetwork.nodeEnter
.append("circle")
.attr("id", function(d) {
return d.id;
})
.attr("class", "networkviewer-node")
.attr("fill", "red")
.attr("r", 20);
// Append images
forceNetwork.nodeEnter
.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.id });
The problem here is that you'er merging the update and enter selections...
forceNetwork.nodeEnter = forceNetwork.node
.enter()
.merge(forceNetwork.node);
... and, after that, appending the circles (and lines):
forceNetwork.nodeEnter
.append("circle")
That will necessarily duplicate the circles and lines.
Instead of that, merge the selection after appending the circles and lines to the enter selection, and also change the selection in your tick function.
Here is the refactored code: https://codepen.io/anon/pen/GXYozm?editors=1010

D3js - Multiple Instances of Single External SVG File

This is somewhat of an abstract question. I'm referencing Mike Bostock's code for importing an external svg file: http://bl.ocks.org/mbostock/1014829
My plan is to use an external SVG as an icon (or sprite) that will be used multiple times in one interface. Is there a method for creating a copy of the xml.documentElement below? In summary, how does one create multiple instances of one svg file without loading the file for each instance?
d3.xml("rect01.svg", "image/svg+xml", function(xml) {
document.body.appendChild(xml.documentElement);
});
The end goal is to add an external svg to each cell in the grid created from this example: http://bl.ocks.org/bunkat/2605010
Referencing the link above, how does one work the xml.documentElement data into the grid in the code below, so that the external svg is visible, rather than a rectangle?
var col = row.selectAll(".cell")
.data(function (d) { return d; })
.enter().append("svg:rect")
.attr("class", "cell")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("width", function(d) { return d.width; })
.attr("height", function(d) { return d.height; })
.on('mouseover', function() {
d3.select(this)
.style('fill', '#0F0');
})
.on('mouseout', function() {
d3.select(this)
.style('fill', '#FFF');
})
.on('click', function() {
console.log(d3.select(this));
})
.style("fill", '#FFF')
.style("stroke", '#555');
}
Referencing this jsfiddle, the line of code I needed is cloneNode(true):
.each(function(d, i){
var plane = this.appendChild(importedNode.cloneNode(true));
d3.select(plane).select("path").attr("fill", "blue");
})

d3 Multiline Graph Update

I have a multiline graph that displays 10 series of data, I am trying to get the lines to update with new data but for some reason I can't get that happening.
The transition with the new data is working for the points on the lines so I assume I am not selecting the right elements but for the life of me I can't figure out where my mistake is.
At one point I had one line changing which indicated it was only updating from the first index of the data array.
Any insight would be appreciated:
Initial Series creation-
var series = svg.selectAll(".series")
.data(seriesData)
.enter().append("g")
.attr("class", "series");
series.append("path")
.attr("id", function (d) {
return d.name;
})
.attr("stay", "false")
.attr("class", "line")
.attr("d", function (d) {
d.line = this;
return line(d.values);
})
.attr("opacity", ".2")
.on("click", function () {
fadeOuts(this);
})
.style("stroke", function (d) {
return strokeCol;
})
.style("stroke-width", "4px")
.style("fill", "none");
Update function:
This is where I am stuck, the points respond to the new data but the paths do not.
series.data(newseriesData);
series.selectAll("path")
.attr("id", function (d) {
return d.name;
})
.attr("d", function (d) {
d.line = this;
return line(d.values);
})
.attr("opacity", ".2")
.on("click", function () {
fadeOuts(this);
})
.style("stroke", function (d) {
return strokeCol;
})
.style("stroke-width", "4px")
.style("fill", "none");
series.selectAll(".point")
.data(function (d) {
return d.values;
})
.transition()
.attr("cx", function (d) {
return x(d.label) + x.rangeBand() / 2;
})
.attr("cy", function (d) {
return y(d.value);
})
.style("fill", function (d) {
return color(d.name);
})
.style("stroke", "grey")
.style("stroke-width", "2px")
.on("mouseover", function (d) {
showPopover.call(this, d);
})
.on("mouseout", function (d) {
removePopovers();
})
Yes this is a university project, this is the last piece of work in a solid 50+ hour effort on this and I'd just like to get it knocked out.
The short answer is that instead of series.selectAll("path") you should use series.select("path"). Remember that series is already a selection, and the subselection is done for each element in it. You've appended exactly one element to each of the selection, so .select() is fine and no .selectAll() is required.
The main difference this makes is that .select() inherits the data from the parent selection, while .selectAll() doesn't -- when doing .selectAll() the data is simply not updated and therefore no change occurs.

d3.js replace circle with a foreignObject

i played with this example of a force directed graph layout.
www.bl.ocks.org/GerHobbelt/3071239
or to manipulate directly, here with fiddle,
http://jsfiddle.net/BeSAb/
what i want was to replace the circle element
node = nodeg.selectAll("circle.node").data(net.nodes, nodeid);
node.exit().remove();
node.enter().append("circle")
.attr("class", function(d) { return "node" + (d.size?"":" leaf"); })
.attr("r", function(d) { return d.size ? d.size + dr : dr+1; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.style("fill", function(d) { return fill(d.group); })
.on("click", function(d) {
console.log("node click", d, arguments, this, expand[d.group]);
expand[d.group] = !expand[d.group];
init();
});
with a group (g) element that contains a svg foreignObject
node = nodeg.selectAll("g.node").data(net.nodes, nodeid);
node.exit().remove();
var nodeEnter = node.enter().append("foreignObject")
//simplified for this demo
.attr("class", function(d) { return "node" + (d.size?"":" leaf"); })
.attr('width', '22px')
.attr('height', '22px')
.attr('x', -11)
.attr('y', -11)
.append('xhtml:div')
.style("background",function(d){return fill(d.group)})
.style("width","20px")
.style("height","20px")
.style("padding","2px")
.on("click", function(d) {
console.log("node click", d, arguments, this, expand[d.group]);
expand[d.group] = !expand[d.group];
init();
});
The Graph is build correct but if i try to expand a node by clicking it, it seems that the graph isn't updated. So that all old nodes are duplicated.
i make an other Fiddle where you can show this problem by clicking a node.
http://jsfiddle.net/xkV4b/
does anyone know what i forgot, or what the issue is?
Thank you very much!
Your enter append should probably match your selection on nodeg. But even then it appears that d3 has some trouble selecting 'foreignObject' things. That may be a question/issue to bring up on the d3 google group - it may be a bug.
However you can get around it by just selecting on the class. I updated the code to read:
node = nodeg.selectAll(".fo-node").data(net.nodes, nodeid);
node.exit().remove();
var nodeEnter = node.enter().append("foreignObject")
.attr("class", function(d) { return "fo-node node" + (d.size?"":" leaf"); })
.attr('width', '22px')
...
Which seems to work.

Categories

Resources