D3 styling SVG elements when defined - javascript

I have a D3 chart with an axis that I want to style via the original calls to create it. However it only seems to work on the first call. I'll show you what I mean:
When I create the axis via
svg.select('g.y.axis').call(yaxis)
.selectAll("path")
.attr("fill","none")
.attr("stroke", "#000")
.selectAll("line")
.attr("fill","none")
.attr("stroke", "#000");
Only the path is styled correctly. You can check out my jsfiddle to see what I mean. I know this may be slower than just having CSS styles but I need it to be styled in the original call for what I'm working on. Thanks in advance!

It's because d3.selectAll("foo").selectAll("bar") will try to find <bar>s that are inner elements to founded <foo>s. And in your case svg finds no <line>s in <path>s.
Just call separately:
svg.selectAll("path")...
svg.selectAll("line")...
UPD
To find path/line in .y.axis:
svg.select(".y.axis").selectAll("path")
or
svg.selectAll(".y.axis path")

Related

Overriding parents' color does not work

I am trying to set color of text node in nvd3 but it is not working because parent g node overrides it somehow. Here is what I am trying to do:
g.append("text")
.attr("x", center[0])
.attr("y", center[1])
.attr("dy", ".35em")
.style("fill", "white")
.text(key);
So, the .style("fill", "white") is not working.
Here is how the DOM looks like:
Text node is appended (as a child of the last g, I am just not showing it since it is way below), I see it with the right fill property, but the color of the appended text is still rgb(44, 123, 182).
Any suggestions would be greatly appreciated.
I decided to just append the text nodes to the parent of the nv-group element that was determining the color and it started to work. If anyone knows how to actually override the color, please, share the answer.

Change D3 line style back to regular

This question might be really simple but for some reason I can't figure it out, or find anything online about it.
I create the following dashed lines like so:
linkContainer.enter()
.append("line")
.style("stroke-dasharray", ("3, 3"))
.attr("class", "link")
.on("click", clickLine);
What I want to do, is when the line is clicked, I want to change it back to a continuous line, i.e. no more dashes.
function clickLine() {
d3.select(this).transition()
.duration(750)
.style("stroke", "lightsteelblue");
}
Is there any style feature to transition the line from dashed to continuous? Thanks again in advance.
For a transition, I would modify the stroke-dasharray value to contain no gaps anymore -- the first number is the (relative) length of the dash and the second of the gap. So all you need to do is set the second number to 0:
d3.select(this).transition()
.style("stroke-dasharray", "3,0");
Complete demo here.

Gradient along links in D3 Sankey diagram

Here is jsfiddle of a Sankey diagram:
I am trying to modify colors of the links so that the color of each link is actually gradient from its source node color to its target node color. (it is assumed that opacity will remain 0.2 or 0.5 depending whether a mouse hovers or not over the link; so links will remain a little "paler" than nodes)
I took a look at this nice and instructive example, which draws this gradient filled loop:
However, I simply couldn't integrate that solution to mine, it looks too complex for the given task.
Also, note that links in original Sankey diagram move while node is being dragged, and must display gradient even in those transitory states. A slight problem is also transparency of links and nodes, and order of drawing. I would appreciate ideas, hints.
#VividD: Just saw your comment, but I was about done anyway. Feel free to ignore this until you've figured it out on the own, but I wanted to make sure I knew how to do it, too. Plus, it's a really common question, so good to have for reference.
How to get a gradient positioned along a line
With the caveat for anyone reading this later, that it will only work because the paths are almost straight lines, so a linear gradient will look half-decent -- setting a path stroke to a gradient does not make the gradient curve with the path!
In initialization, create a <defs> (definitions) element in the SVG and save the selection to a variable:
var defs = svg.append("defs");
Define a function that will create a unique id for your gradient from a link data object. It's also a good idea to give a name to the function for determining node colour:
function getGradID(d){return "linkGrad-" + d.source.name + d.target.name;}
function nodeColor(d) { return d.color = color(d.name.replace(/ .*/, ""));}
Create a selection of <linearGradient> objects within <defs> and join it to your link data, then set the stop offsets and line coordinates according to the source and target data objects.
For your example, it probably will look fine if you just make all the gradients horizontal. Since that's conveniently the default I thought all we would have to do is tell the gradient to fit to the size of the path it is painting:
var grads = defs.selectAll("linearGradient")
.data(graph.links, getLinkID);
grads.enter().append("linearGradient")
.attr("id", getGradID)
.attr("gradientUnits", "objectBoundingBox"); //stretch to fit
grads.html("") //erase any existing <stop> elements on update
.append("stop")
.attr("offset", "0%")
.attr("stop-color", function(d){
return nodeColor( (d.source.x <= d.target.x)? d.source: d.target)
});
grads.append("stop")
.attr("offset", "100%")
.attr("stop-color", function(d){
return nodeColor( (d.source.x > d.target.x)? d.source: d.target)
});
Unfortunately, when the path is a completely straight line, its bounding box doesn't exist (no matter how wide the stroke width), and the net result is the gradient doesn't get painted.
So I had to switch to the more general pattern, in which the gradient is positioned and angled along the line between source and target:
grads.enter().append("linearGradient")
.attr("id", getGradID)
.attr("gradientUnits", "userSpaceOnUse");
grads.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;});
/* and the stops set as before */
Of course, now that the gradient is defined based on the coordinate system instead of based on the length of the path, you have to update those coordinates whenever a node moves, so I had to wrap those positioning statements in a function that I could call in the dragmove() function.
Finally, when creating your link paths, set their fill to be a CSS url() function referencing the corresponding unique gradient id derived from the data (using the pre-defined utility function):
link.style("stroke", function(d){
return "url(#" + getGradID(d) + ")";
})
And Voila!

D3 - force layout, circle within circle

In the process of learning D3.js.
Is it possible using a force layout to place a circle within another circle shape as per the picture. I am hoping to transition between a single circle per node to a display showing two circles per node. The size of the effective donut is used to illustrate another variable in the data.
Is this possible?
You don't even need to use anything other than a basic svg circle, as you find in most examples. Just bind the data to it, apply a stroke, and set the stroke-width attr to your other variable. Or r - otherVar, I'm sure you can figure that part out.
If this doesn't satisfy, build your own shape. The 'g' svg element is a container element, and lets you build whatever you like. Add two circles to a g, fill them how you like. Make sure to add them in the right order, since svg has no concept of 'on top', things just get painted in the order that you add them.
edit: okay, quick demo so you can learn some syntax. I didn't add any comments but hopefully the code is very verbose and straightforward. Find it here.
d3/svg is something that you have to just bash your head against for a while. I highly recommend spending some time creating a sandbox environment where you can quickly test new things, save, refresh browser to see results. Minimizing that turnaround time is key.
Thanks to roippi I was able to create a group containing two circle shapes.
var nodeCircles = svg.selectAll("g")
.data(nodes);
// Outer circle
var outer = nodeCircles
.enter()
.append("circle")
.attr("class", "node_circle")
.attr("r", function(d) { return d.radius_plus; })
.style("fill", function(d) { return d.color_plus; })
.style("opacity", 0);
// Inner circle
var inner = nodeCircles
.enter()
.append("circle")
.attr("class", "node_circle")
.attr("r", function(d) { return d.radius; })
.style("fill", function(d) { return d.color; })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.on("mouseover", mouseOver)
.on("mouseout", mouseOut)
.call(force.drag);
Outer circle visibility is toggled via a button.
As mentioned, I use a desktop based IDE to run/test visualisation languages. Currently the IDE supports studies written in D3.js, Raphael, Processin.js, Paper.js and Dygraphs. Picture below...

d3 tooltip removes my axis (and doesn't work)

I am trying to add tooltips to my D3 graph here:
http://jsfiddle.net/ericps/b5v4R/1/
but adding these mouseevents mess up how everything is rendered and I don't know why. It is a line graph with axis
dots.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", open_line.x())
.attr("cy", open_line.y())
.attr("r",3.5)
.on("mouseover", myMouseOverFunction)
.on("mouseout", myMouseOutFunction);
commenting out both .on methods at line 144 makes everything render how I expect it to
any insight into this?
The tooltips are based on this fiddle
http://jsfiddle.net/ericps/E4vrX/
You're missing myMouseOverFunction.
Simply defining the function will render your graph correctly (with the axes), allowing you to properly define your MouseOver functionality.
var myMouseOverFunction = function() {}
You can see an updated response to your jsfiddle: http://jsfiddle.net/sahhhm/hQgbc/
This could be implemented differently, but just a quick change was to remove the .FILL definition in your CSS and instead populating that value when creating the dot itself.
Few problems:
mouse:
The d3.mouse(container) function gives mouse location relative to the container (a node). You specified d3.mouse(this), but this is referring to the circle node, while you want to refer to the svg container: d3.select("svg").node().
infobox:
The infobox div was not defined anywhere, so nothing to be shown.
See updated Fiddle: http://jsfiddle.net/b5v4R/4/

Categories