Reordering svg dom with d3js - svg

I'm using d3 to render an svg. I have an array of objects, each with a color property, like so:
data = [{'color': 'red'}, {'color': 'blue'}];
I have an update function to draw circles with those colors, like so:
function update(data) {
var circle = svg.selectAll('circle').data(data, function(d) {return d.color})
.enter()
.append('circle').attr('r', 50)
.attr('cx', function (d, i) {return 50 + (i * 50)}).attr('cy', 50)
.attr('fill', function (d) {return d.color});
circle.order();
}
My understanding is that the last line in the function, circle.order(), should reorder the nodes in the svg dom to match the order of the data. However, I change the array order and call the update function again, and it doesn't seem to do anything.
jsfiddle demo: http://jsfiddle.net/du7mh/
I need to control the dom order to bring certain elements to the foreground, since there's no z-index in svg. Any help would be appreciated, thanks!

The append selection doesn't have anything in it after update has run once. Setting circle equal to the both current and new elements works:
function update(data) {
var circle = svg.selectAll('circle').data(data, function(d) {return d.color});
circle.enter()
.append('circle').attr('r', 50)
.attr('cx', function (d, i) {return 50 + (i * 50)}).attr('cy', 50)
.attr('fill', function (d) {return d.color});
circle.order();
}
http://jsfiddle.net/du7mh/3/

Related

How to sort bar charts synchronous with the ticks of x axis

I am adopting the example from Mike Bostock to sort bar charts - http://bl.ocks.org/mbostock/3885705.
However, the transition of the bars and the ticks of the x axis are not occuring at the same time.
The reason is that the transition is called separately for the bars and the ticks (in the function change():
transition.selectAll(".bar")
.delay(delay)
.attr("x", function(d) { return x0(d.letter); });
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
The solution should be when constructing the svg, I would like to add each bar to the corresponding tick.
However I didn't succeed to correctly adjusting the line
svg.selectAll(".bar")
.data(data)
.enter().append("rect")...
e.g.
svg.selectAll("g.tick")
.data(data)
.enter().append("rect")...// would put all the bars under each g.tick
or
svg.selectAll(".x.axis")
.data(data)
.enter().append("rect")...// would put the bars after all g.tick's at the end. So again I can't grap the whole group together later.
Interesting idea.
You can get the rects grouped with each tick's g by:
svg.selectAll(".x>.tick")
.data(data)
.append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return -x.rangeBand()/2; // set x to be half width, tick g will position it
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return -(height - y(d.frequency)); // set y to negative
})
.attr("height", function(d) {
return height - y(d.frequency);
});
You then have to rewrite the transition since calling .call(xAxis); will recreate the tick g and remove the bars.
transition.selectAll(".x>.tick")
.delay(delay)
.attr("transform", function(d) {
return "translate(" + x0(d.letter) + ",0)";
});
Putting this together.

.style of child nodes in d3js

I'm trying to make a simple graph with nodes and links. I have "g" elements containing a circle and its text, and links on their own. I have, for example, this bit on code called on a mouseover event:
//check if circle is connected to current "viewed" (mouseover-ed)
//circle via a link referenced by "that" and paint it green if so
circles.filter(function() {
return d3.select(this).attr("index") == d3.select(that).attr("src");
}).attr("viewed",1).style("stroke", "green");
});
This was really a long shot as nodes is the 'g' element container and I wasn't sure what calling .style would do, but to my surprise it did change the color - but only for the text!
Is there a way to make it change the stroke style of the circle as well?
The declaration code:
var circles = svg.append("g")
.attr("class","nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()
.append("g")
.attr("transform",function(d,i){d.x = getX(i);d.y=getY(i);return "translate(" + d.x + "," + d.y + ")";})
.attr("name", function(d){return d.name;})
.attr("viewed", 0)
.attr("focused", 0)
.attr("index", function(d, i) {return i;});
circles.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", node_radius_wo_pad)
.on("mouseover", function(){...};
circles.append("text")
.attr("text-anchor","middle")
.text(function(d){return d.name});
The reason this is working is that you haven't explicitly declared a stroke colour for the text and so it inherits what you set for the parent g element. To make this work for the circles, you have to select them explicitly:
var toChange = circles.filter(function() {
return d3.select(this).attr("index") == d3.select(that).attr("src");
});
toChange.attr("viewed", 1);
toChange.selectAll("circle").style("stroke", "green");
toChange.selectAll("text").style("stroke", "green");

Fade/change the color of an SVG Shape over time after being added to a canvas?

I have created an SVG map that plots tweets live containing particular keywords. I'm drawing each tweet to the screen as a circle (or dot), and after 50 tweets have been added to the map, the oldest one will disappear.
I'd like to have some sort of color decay for the circles depending on how long they've been on the map.
New tweets would pop onto the map and be red. As time passes, points already plotted on the map will slowly fade to black.
Here's where I add the circles to the map:
function mapTweet(tweetData) {
var tipText; // Ignore this. For tweet dot hovering.
var coordinates = projection([tweetData.geo.coordinates[1], tweetData.geo.coordinates[0]]);
addCircle(coordinates, tipText);
}
function addCircle(coordinates, tipText, r) {
tweetNumber++;
// too many tweets
if (tweetNumber == 50) {
tweetNumber = 0;
}
//removes expired circles
$('#' + tweetNumber).remove();
var rad;
//determine if radius size needs to be bumped
if (arguments.length == 3) {
rad = r;
} else {
rad = 3;
}
// add radar-style ping effect
map.append('svg:circle')
.style("stroke", "rgba(255,49,49,.7)")
.style("stroke-width", 1)
.style("fill", "rgba(0,0,0,0)")
.attr('cx', coordinates[0])
.attr('cy', coordinates[1])
.attr('r', 3)
.transition()
.delay(0)
.duration(2000)
.attr("r", 60)
.style("stroke-width", 2)
.style("stroke", "rgba(255,49,49,0.0001)").transition().duration(2000).remove();
// add circles representing tweets
map.append('svg:circle').attr("class", "tweetCircles")
.attr("id", tweetNumber)
.style("stroke", "rgba(255,49,49,.7)")
.style("stroke-width", 1)
.style("fill", "rgba(240,49,49,1)")
.attr('cx', coordinates[0])
.attr('cy', coordinates[1])
.attr('r', rad);
addTipsy(tipText, tweetNumber); // Ignore this. For tweet dot hovering.
}
Once a circle is drawn, does it have to be redrawn to change the color? Or can dots have their attributes changed after being added to the canvas?
How can I decay the color over, say, 20 seconds?
Append an animate element as a child of the circle
.append('svg:animate')
.attr('attributeName', 'fill')
.attr('from', 'red')
.attr('to', 'blue')
.attr('dur', '20s');
This will interpolate from red to blue or whatever colours you choose.

How to avoid the overlapping of text elements on the TreeMap when child elements are opened in D3.js?

I created a Tree in D3.js based on Mike Bostock's Node-link Tree. The problem I have and that I also see in Mike's Tree is that the text label overlap/underlap the circle nodes when there isn't enough space rather than extend the links to leave some space.
As a new user I'm not allowed to upload images, so here is a link to Mike's Tree where you can see the labels of the preceding nodes overlapping the following nodes.
I tried various things to fix the problem by detecting the pixel length of the text with:
d3.select('.nodeText').node().getComputedTextLength();
However this only works after I rendered the page when I need the length of the longest text item before I render.
Getting the longest text item before I render with:
nodes = tree.nodes(root).reverse();
var longest = nodes.reduce(function (a, b) {
return a.label.length > b.label.length ? a : b;
});
node = vis.selectAll('g.node').data(nodes, function(d, i){
return d.id || (d.id = ++i);
});
nodes.forEach(function(d) {
d.y = (longest.label.length + 200);
});
only returns the string length, while using
d.y = (d.depth * 200);
makes every link a static length and doesn't resize as beautiful when new nodes get opened or closed.
Is there a way to avoid this overlapping? If so, what would be the best way to do this and to keep the dynamic structure of the tree?
There are 3 possible solutions that I can come up with but aren't that straightforward:
Detecting label length and using an ellipsis where it overruns child nodes. (which would make the labels less readable)
scaling the layout dynamically by detecting the label length and telling the links to adjust accordingly. (which would be best but seems really difficult
scale the svg element and use a scroll bar when the labels start to run over. (not sure this is possible as I have been working on the assumption that the SVG needs to have a set height and width).
So the following approach can give different levels of the layout different "heights". You have to take care that with a radial layout you risk not having enough spread for small circles to fan your text without overlaps, but let's ignore that for now.
The key is to realize that the tree layout simply maps things to an arbitrary space of width and height and that the diagonal projection maps width (x) to angle and height (y) to radius. Moreover the radius is a simple function of the depth of the tree.
So here is a way to reassign the depths based on the text lengths:
First of all, I use the following (jQuery) to compute maximum text sizes for:
var computeMaxTextSize = function(data, fontSize, fontName){
var maxH = 0, maxW = 0;
var div = document.createElement('div');
document.body.appendChild(div);
$(div).css({
position: 'absolute',
left: -1000,
top: -1000,
display: 'none',
margin:0,
padding:0
});
$(div).css("font", fontSize + 'px '+fontName);
data.forEach(function(d) {
$(div).html(d);
maxH = Math.max(maxH, $(div).outerHeight());
maxW = Math.max(maxW, $(div).outerWidth());
});
$(div).remove();
return {maxH: maxH, maxW: maxW};
}
Now I will recursively build an array with an array of strings per level:
var allStrings = [[]];
var childStrings = function(level, n) {
var a = allStrings[level];
a.push(n.name);
if(n.children && n.children.length > 0) {
if(!allStrings[level+1]) {
allStrings[level+1] = [];
}
n.children.forEach(function(d) {
childStrings(level + 1, d);
});
}
};
childStrings(0, root);
And then compute the maximum text length per level.
var maxLevelSizes = [];
allStrings.forEach(function(d, i) {
maxLevelSizes.push(computeMaxTextSize(allStrings[i], '10', 'sans-serif'));
});
Then I compute the total text width for all the levels (adding spacing for the little circle icons and some padding to make it look nice). This will be the radius of the final layout. Note that I will use this same padding amount again later on.
var padding = 25; // Width of the blue circle plus some spacing
var totalRadius = d3.sum(maxLevelSizes, function(d) { return d.maxW + padding});
var diameter = totalRadius * 2; // was 960;
var tree = d3.layout.tree()
.size([360, totalRadius])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
Now we can call the layout as usual. There is one last piece: to figure out the radius for the different levels we will need a cumulative sum of the radii of the previous levels. Once we have that we simply assign the new radii to the computed nodes.
// Compute cummulative sums - these will be the ring radii
var newDepths = maxLevelSizes.reduce(function(prev, curr, index) {
prev.push(prev[index] + curr.maxW + padding);
return prev;
},[0]);
var nodes = tree.nodes(root);
// Assign new radius based on depth
nodes.forEach(function(d) {
d.y = newDepths[d.depth];
});
Eh voila! This is maybe not the cleanest solution, and perhaps does not address every concern, but it should get you started. Have fun!

SVG markers don't orient properly on a d3.svg.diagonal curve used as a D3 force layout link

I'm a bit new to SVG and d3.js.
While drawing a graph with D3 force layout, I'm using a simple diagonal line generator and using marker-end to draw arrow heads.
When using arc instead of diagonal generator the arrow heads appear just fine. But using diagonal generator like in the code below doesn't produce proper markers:
var vis = this.vis = d3.select(el).append("svg:svg")
.attr("width", w)
.attr("height", h);
var force = d3.layout.force()
.gravity(0.03)
.distance(120)
.charge(-800)
.size([w, h]);
var linkDiag = d3.svg.diagonal()
.projection(function(d)
{
return [d.x, d.y];
});
vis.append("svg:defs")
.selectAll("marker")
.data(["normal", "special", "resolved"])
.enter()
.append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M 0,-5 L 10,0 L0,5");
...and then also:
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; })
.attr("d", linkDiag)
.attr("marker-end", function(d) { return "url(#special)"; });
});
The markers are not oriented at all with the vertices.
Any help would be appreciated!
It only appears as if the arrows aren't pointing in the right direction because you're moving the arrowhead to a new position via refX and refY.
For example, check out this code which draws diagonals in various directions. The arrowheads appear correctly, with the exception of the one at 180 degrees, but that's probably due to a rounding error.
Now, try changing refX on line 10 to a value of, say, 5. Now, the arrowheads close to the horizontal appear incorrect. To see this more dramatically, try changing the value to 8.
What's happening is you're hiding part of the diagonal so the line appears to be ending at an earlier point, which is curved slightly differently from the actual end-point. The same thing will happen if the arrowhead is too large so that it overlays part of the curve. Note that for diagonals in d3, which are symmetrical bezier curves, the arrowheads should always appear pointing perfectly horizontally or vertically. You can see exactly what's happening by reducing the arrowhead's opacity.
Can you specify you question a little more?
However with even running you code I think you problem might be
.attr("orient", "auto")
Try to specify pos

Resources