I have some code that create a bunch of circles, updates those circles, removes some of the circles, and lastly I'd like to find out how to use the enter() correctly to add more circles to my current ones.
This is my code for creating circles when there are none:
var circle = SVGbody
.selectAll("circle")
.data(graphDataY/*, function(d){return d;}*/);
circle.enter()
.append("circle");
circle
.attr("cx",xScale(0))
.attr("cy", yScale(minAxisY))
.attr("r",4)
.style('opacity', 0)
.style("fill", colorCircles())
.transition()
.duration(1000)
.attr("cx", function(d, i){ return xScale(graphDataX[i]);})
//.attr("cy", function (d, i){ return yScale(i); })
.style('opacity', 0.8)
.transition()
.duration(1000)
.attr("cy", function(d){return yScale(parseFloat(d));} );
I use the following code to update my circles and remove the ones that aren't being used (sometimes that's the case):
var circle = SVGbody
.selectAll("circle")
.data(graphDataY);
circle
.style("fill", colorCircles())
.transition().duration(1000)
.attr("cx", function(d, i){ return xScale(graphDataX[i]);})
.attr("cy",function(d){return yScale(parseFloat(d));});
circle.exit()
.style("opacity", 1)
.transition()
.duration(300)
.style("opacity", 0)
.remove();
And when lastly, let's assume the last data i used had a length of 100, that means I currently have 100 circles displayed. If I used a new dataset with 120 of length, I use my update code to move the existing circle ( except for the exit().remove() ), and then I try to use the following to "create" the remaining circles out of the remaining unbound data (which in this case would be 20 circles remaining):
circle.enter().append("circle")
.attr("cx", function(d, i){ return xScale(graphDataX[i]);})
.attr("cy",function(d){return yScale(parseFloat(d));})
.style("opacity", 0)
.transition()
.duration(300)
.style("opacity", 1)
Related
I need to draw horizontal lines in a d3.js chart.
Using the below code I was able to draw lines.
g.append("line")
.attr("x1", x(0.5))
.attr("y1", y(1))
.attr("x2", x(3.5))
.attr("y2", y(1))
.style("stroke-width", 5)
.style("stroke", "blue")
.style("fill", "none");
But I need to draw around 8 lines in the chart.
Currently I have copy pasted the above code 8 times and entered the values manually.
Is there a way to pass the coordinates from an array, instead of copy pasting it 8?
I tried to do something like this but it did not work.
method 1:
var dataTest = [{x1:4,y1:3,x2:6,y2:3}]
g.append("g")
.selectAll('line')
.data(dataTest)
.enter()
.append('g').attr('class', 'line').append('g')
.attr({'x1':function(d,i){ console.log(d); return d.x1; },
'y1':function(d){ return d.y1; },
'x2':function(d,i){ return d.x2; },
'y2':function(d){ return d.y2; },
})
.style("stroke-width", 2)
.style("stroke", "blue")
.style("fill", "none");
method 2:
g.append("line")
.attr("d", line(dataTest))
.style("stroke-width", 2)
.style("stroke", "green")
.style("fill", "none");
where the line function returned the x and y values.
var dataTest = [{x:4,y:3},{x:6,y:3}]
var line = d3.svg.line()
.x(function (d) {
console.log(d.x);
return (d.x);
})
.y(function (d) {
return (d.y);
});
I have 2 lines in the form of waves plotted in x-y axis based on randomly generated data and i am showing circles on the waves denoting the data points on it.
Based on setInterval of 200 ms, I am updating the original data and the lines(waves) are moving to the left, but the issue is that the only circles which are there in the initial interval are moving and for 2nd interval onward the circles are not showing up on the waves.
see the jsfiddle for the running code : https://jsfiddle.net/rajatmehta/tm5166e1/10/
here is the code :
chartBody.append("path") // Add the valueline path
.datum(globalData)
.attr("id", "path1")
.attr("class", "line")
.attr("d", valueline);
chartBody.selectAll(null)
.data(globalData)
.enter()
.append("circle")
.attr("class", "dot1")
.attr("r", 3)
.attr("cx", function(d) {
console.log(d);
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.selectAll(null)
.data(globalDataNew)
.enter()
.append("circle")
.attr("class", "dot2")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.append("path") // Add the valueline path
.datum(globalDataNew)
.attr("id", "path2")
.attr("class", "line")
.attr("d", valueline2);
any idea how to do that ?
You need to create new circles based on the updated data. Currently, you are only updating the data to selection, but not appending circles, and then moving existing circles to the left.
For example, you could to this:
chartBody.selectAll(".dot1")
.data(globalData, function(d){ return d.timestamp; })
.enter()
.append("circle")
.attr("class", "dot1")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.selectAll(".dot2")
.data(globalDataNew, function(d){ return d.timestamp; })
.enter()
.append("circle")
.attr("class", "dot2")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
d3.selectAll(".dot1")
//.data(globalData)
.transition()
.duration(duration)
.ease("linear")
.attr("transform", "translate(" + String(dx) + ")");
d3.selectAll(".dot2")
//.data(globalDataNew)
.transition()
.ease("linear")
.duration(duration)
.attr("transform", "translate(" + String(dx) + ")");
See here: https://jsfiddle.net/tm5166e1/11/
This appends the data, using the timestamp as a key so you only create new circles for newly added datums.
(There is an issue when they are first added which is beyond the scope of this question, but it will be worth checking out these examples: https://bl.ocks.org/tomshanley/15a2b09a95ccaf338650e50fd207fcbf and https://bl.ocks.org/mbostock/1642874)
This is a very basic question, but how do I access the value of attributes in d3?
I just started learning today, so I haven't figured this out yet
Suppose I have this as part of my code here
http://jsfiddle.net/matthewpiatetsky/nCNyE/9/
var node = svg.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function (d) {
if (width < height){
return d.count * width/100;
} else {
return d.count * height/100;
}
})
.on("mouseover", animateFirstStep)
.on("mouseout",animateSecondStep)
.style("fill", function(d,i){return color(i);})
.call(force.drag);
For my animation the circle gets bigger when you mouse over it, and I want the circle to return to its normal size when you move the mouse away. However, i'm not sure how to get the value of the radius.
i set the value here
.attr("r", function (d) {
if (width < height){
return d.count * width/100;
} else {
return d.count * height/100;
}
I tried to do node.r and things like that, but i'm not sure what the correct syntax is
Thanks!
You can access an attribute of a selection with:
var node = svg.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function (d) { return rScale(d.count); })
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(1000)
.attr('r', 1.8 * rScale(d.count));
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(1000)
.attr('r', rScale(d.count));
})
.style("fill", function (d, i) {
return color(i);
})
.call(force.drag);
in this context, this points to the DOM element binded with d. Normally, the area of a circle must be proportional to the quantities that you are showing, take a look at the documentation of Quantitative Scales. A fork of your fiddle is here.
I have been setting up graphs to dynamically scale in reference to the width and height of the svg or bounding box, and with nodes and node borders this is relatively simple. By using d.depth and assigning the desired ratio to assign a relative radius to a node at a certain depth, this is giving me no problems and nodes at lower depths become smaller to avoid overlap. Then, for the node border I assign a ratio between the node radius and the thickness of the border.
This has proven to be an ideal solution to a problem that arises when adding a zoom feature by scaling the container. This is the problem:
The text, the nodes, and the node borders no longer bunch up, but the links remain the same weight.
I would also like to scale the thickness of the edges between nodes but I do not know how to identify edges so that I can either assign a thickness in relation to the radius of the parent or child node
d3.json("aviation.json", function(root) {
var nodes = balloon.nodes(root),
links = balloon.links(nodes);
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",diagonal)
.attr("stroke", "black")
.attr("shape-rendering", "auto")
.attr("fill", "none")
.data(nodes)
.attr("stroke-width", function(d) {return (1/5000*diameter);});
var node = svg.selectAll("g.node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node");
node.append("circle")
.attr("r", function(d) { return d.r;})
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("fill", "white")
.attr("opacity", "0.05")
.attr("stroke-width", function(d) {return d.r/600;}) //here I am referecing d.r
//to find the stroke width.
.attr("stroke-opacity", ".5")
.on("Click", function(d) { return zoomClick});
var text = svg.selectAll("g.text")
.data(nodes)
.enter()
.append("text")
.attr("dx", function(d) { return d.x })
.attr("dy", function(d) { return d.y })
.attr("font-family", "Helvetica")
.attr("font-size", function(d) {return d.children ? d.r/7 : d.r/4;})
.attr("stroke-width", function(d) {return d.r/400;})
.attr("stroke-opacity", "1")
.attr("fill", "white")
.attr("pointer-events", "all")
.style("text-anchor", "middle")
.text(function(d) { return d.name; })
I am trying to modify this D3.js example (Dynamic Node-Link Tree) by adding a specific label (SVG text) to each node, but unsucessfully.
If I understand correctly, after a brief look at SVG specs and D3 documentation, the best way would be to create SVG groups and move them around.
Unfortunately, this is not working, as the transitions have no effect on the groups.
Is there a simple(r) way I am not aware of?
Many thanks.
If you're looking for an effect where you switch the circles for text labels, you can do the following:
// Enter any new nodes at the parent's previous position.
node.enter().append("svg:text")
.attr("class", "node")
.attr("x", function(d) { return d.parent.data.x0; })
.attr("y", function(d) { return d.parent.data.y0; })
.attr("text-anchor", "middle")
.text(function(d) { return "Node "+(nodeCount++); })
.transition()
.duration(duration)
.attr("x", x)
.attr("y", y);
See the fiddle here: http://jsfiddle.net/mccannf/pcwMa/4/
Edit
However, if you're looking to add labels alongside the circles, I would not recommend using svg:g in this case, because then you would have to use transforms to move the groups around. Instead, just double up on the circle nodes and text nodes like so in the update function:
// Update the nodes…
var cnode = vis.selectAll("circle.node")
.data(nodes, nodeId);
cnode.enter().append("svg:circle")
.attr("class", "node")
.attr("r", 3.5)
.attr("cx", function(d) { return d.parent.data.x0; })
.attr("cy", function(d) { return d.parent.data.y0; })
.transition()
.duration(duration)
.attr("cx", x)
.attr("cy", y);
var tnode = vis.selectAll("text.node")
.data(nodes, nodeId);
tnode.enter().append("svg:text")
.attr("class", "node")
.text(function(d) { return "Node "+(nodeCount++); })
.attr("x", function(d) { return d.parent.data.x0; })
.attr("y", function(d) { return d.parent.data.y0; })
.transition()
.duration(duration)
.attr("x", x)
.attr("y", y);
// Transition nodes to their new position.
cnode.transition()
.duration(duration)
.attr("cx", x)
.attr("cy", y);
tnode.transition()
.duration(duration)
.attr("x", x)
.attr("y", y)
.attr("dx", 4)
.attr("dy", 4); //padding-left and padding-top
A fiddle that demonstrates this can be found here: http://jsfiddle.net/mccannf/8ny7w/19/