I have a stacked area chart like the one at : http://nvd3.org/ghpages/stackedArea.html
Is it possible to add a stroke to the top of the area bit of the plot so it looks like it has a border/stroke?
I tried adding in a stroke with the webkit inspector but nothing seems to happen (assuming this is like using .style('stroke','#000000')
So if there was just one series on the stackedAreaExample and it was blue in colour, the border would make it look something like this:
There's no border as such in SVG, so you have to add a rectangle that determines the border and assign the appropriate style. NVD3 doesn't have an option for this, but you can select the relevant element after it has been drawn and add the new content.
d3.select(".nv-stackedWrap")
.append("rect")
.attr("width", chart.xAxis.scale().range()[1])
.attr("height", chart.yAxis.scale().range()[0])
.style("fill", "none")
.style("stroke", "black");
This works for the stacked area chart; for other types of charts the name of the class of the element to select will be different.
Setting a border on the top area is tricker, as SVG doesn't allow you to set the stroke for only a single side of a path. You can do it with stroke-dasharray however -- you just need the total length of the path.
d3.select(".nv-area-" + (data.length-1))
.style("stroke", "black")
.style("stroke-opacity", 1)
.style("stroke-dasharray", function() {
return this.getTotalLength()/2 + "," + this.getTotalLength();
});
This selects the last (i.e. top) area and sets the stroke for it. The specified dasharray means that there will be a stroke for half of the path (i.e. the top) and then nothing for the length of the path (to make it appear as if there was only a stroke on the top).
The problem with this and NVD3 is the transition that "grows" the areas. If you run this code when the graph is created, the length of the line may be shorter than what it will be in the end. To make sure that the length is correct, you would need to (re)run this code after the transition is complete, e.g. using setTimeout.
Instead you can just draw a line chart with same data with darker color which will look like a border.
var areaFunc = d3.svg.area()
.interpolate("monotone")
.x(function(d) { return d.index; })
.y0(THIS.height)
.y1(function(d) { return d.delay });
var lineFunc = d3.svg.line()
.x(function(d) { return d.index; })
.y(function(d) { return d.delay });
.......
svg.append("path")
.datum(myData)
.attr("class", "area")
.attr("d", areaFunc);
svg.append("path")
.datum(myData)
.attr("class", "line") // with a darker color
.attr("d", lineFunc);
Related
I would like to recreate something similar to the following examples:
http://bl.ocks.org/mbostock/3888852
http://bl.ocks.org/mbostock/1305111
The only difference is that I want to control the radius of each donut, rather than having it be the same for all of them. How do I dynamically vary the radius of the donut charts?
For this, you need to adjust the .innerRadius() and/or .outerRadius() dynamically for each appended pie chart, for example
svg.selectAll(".arc")
.data(function(d) { return pie(d.ages); })
.enter().append("path")
.attr("class", "arc")
.attr("d", function(d, i) { return arc.innerRadius(radius - 30 * Math.random())(d, i); })
.style("fill", function(d) { return color(d.data.name); });
Complete example here. In a real example, you'd want to specify the radius in the data and reference that instead of making up a random number for each segment of the pie chart. Then you can also have the same radius for all the segments in the same pie chart.
I am trying to draw a circular heat or ring-chart. There are several options it seems with d3js. The most popular appears to use the pie layout to make several donut rings Another option is to use a circular heat chart like this one -
Both of these however use filling segments as their way of depicting area size. I wanted however to use lines to depict events over time. With each line occurring within a particular ring.
To get this effect, I've adapted this radial weather chart - http://bl.ocks.org/susielu/b6bdb82045c2aa8225f5
This is my attempt so far:
http://blockbuilder.org/jalapic/12a3a23651f40283d489
It does not have labeling, but each ring (12 total) represents an individual subject. Each segment represents a sample of time (says months here but could be anything). The lines are drawn within each ring that they belong to. I have kept the same variable names as the weather example to enable comparisons between my stripped down code and the author's original code.
This is what it looks like:
My question is how might it be possible to mouseover each ring to make only that ring's contents (i.e. lines) remain visible, i.e. to hide the other rings - this would make viewing the chart easier.
Here is the code for how the rings are made up:
var mycircles = [110,100, 90, 80, 70, 60,50,40,30,20,10,0]
origin.selectAll('circle.axis-green')
.data(mycircles) //original circles
.enter()
.append('circle')
.attr('r', function(d) { return rScale(d)})
.style("fill", "#fff8ee")
.style("opacity", .05)
.attr('class', 'axis record')
.on("mouseover", function(d) {d3.select(this).style("fill", "red");})
.on("mouseout", function(d) {d3.select(this).style("fill", "#fff8ee");
});
As can be seen the rings are actually overlapping circles. Is there a way to achieve what I'm trying to do using the approach I'm taking, or would I have to go back to working something out with segments like in the heatchart or pie layouts?
Looking at your data and code, one method would be to assign a class to each line representing it's ring position. You can then use mouseover and mouseout events to toggle the opacity of those lines.
First, create a couple helper functions:
// which ring is currently highlighted
var curRing = null;
// show all rings
function unShowRing(){
d3.selectAll(".record")
.style("opacity", 1);
curRing = null;
}
// only show current ring
function showRing(ringId){
// all lines that are not in my ring, hide them
d3.selectAll(".record:not(.ring" + ringId + ")")
.style("opacity", 0);
curRing = ringId;
}
Set up the lines a little different:
...
.enter().append('line')
// assign a unique class to each ring's lines
.attr('class', function(d) {
return cl + " ring" + d.recLow/10;
})
// on mouseover only show my ring
.on("mouseover", function(d){
var ringId = d.recLow/10;
showRing(ringId);
})
// on mouseout show all rings
.on("mouseout", function(d){
unShowRing();
})
// this will prevent lines transitioning in from being shown
.style('opacity', function(d){
if (!curRing){
return 1;
} else {
var ringId = d.recLow/10;
return ringId === curRing ? 1 : 0;
}
})
Finally, you'll need to handle the ring "circle" mouseovers as well in case the user mouses over lines or rings:
origin.selectAll('circle.axis-green')
.data(mycircles) //original circles
...
.on("mouseover", function(d) {
d3.select(this).style("fill", "red");
var ringId = d/10;
showRing(ringId);
})
.on("mouseout", function(d) {
d3.select(this).style("fill", "#fff8ee");
unShowRing();
});
Here's the whole thing working.
I would like to recreate something similar to the following examples:
http://bl.ocks.org/mbostock/3888852
http://bl.ocks.org/mbostock/1305111
The only difference is that I want to control the radius of each donut, rather than having it be the same for all of them. How do I dynamically vary the radius of the donut charts?
For this, you need to adjust the .innerRadius() and/or .outerRadius() dynamically for each appended pie chart, for example
svg.selectAll(".arc")
.data(function(d) { return pie(d.ages); })
.enter().append("path")
.attr("class", "arc")
.attr("d", function(d, i) { return arc.innerRadius(radius - 30 * Math.random())(d, i); })
.style("fill", function(d) { return color(d.data.name); });
Complete example here. In a real example, you'd want to specify the radius in the data and reference that instead of making up a random number for each segment of the pie chart. Then you can also have the same radius for all the segments in the same pie chart.
I'm appending some text to D3.js circles and want the circles to change color mouseover, also on mouseover on the text.
Currently the circles do change color on mouseover, but when hovering over the text, the circle mouseover doesn't work anymore (logical: I'm hovering over the text). How do I get the circles to also change color when hovering over the text?
My code (gnode is a earlier defined circle):
var label = gnode.append("text")
.text(function(d) { return d.key ; })
.attr("font-size", function(d) {return 12 + d.value[0]/4})
.attr("text-anchor", "middle")
.call(force.drag)
.on("mouseover", function(d){
this.style.cursor='pointer';
d3.select( "#" + d.key.toLowerCase().replace(/ /g, '_'))
.attr("id", "none")
.classed("mouse_over",true)
.classed("mouse_out",false);
thanks
You can achieve this by simply putting all the elements belonging together inside a group. Then attach the mouse events to the group instead of the elements themselves.
First create svg element and append data:
var svg = d3.select("#main")
.append("svg")
.attr("id", "svgElement")
.attr("width", width)
.attr("height", height);
var svgg = svg.selectAll("g.myGroup")
.data(myData)
.enter()
.append("g");
Then add your elements via the each function:
svgg.each(function (d, i) {
selection = d3.select(this);
// ... append to this selection
});
Now attach mouse events to the group:
svgg.on("mouseover", function(d) {
d3.select(this) // Select and do something
});
You can find the working fiddle here:
http://jsfiddle.net/77XLD/1/
Note that the event fires when either moving over the line of the circle and also when hovering over the text.
So I am playing around with the d3.js Sankey diagram.
In this example (pictured above) color is defined using
var color = d3.scale.category20();
For each node there is a rectangle, and that rectangle is filled by altering the style:
.style("fill", function(d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
I'm looking for suggestions on using custom colors. If I wanted to use only say 6 colors, but have the node rectangle colors chosen based on a value in the .json file.
For example, lets say I wanted to show a snakey chart of teams in the NFL. The colours each represent which division the teams play in. So if they move to a different division, the color changes. And the nodes are created for every season. Something along those lines.
So is it possible to run the
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("stroke", function(d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
with the color based on a value in the json file? I am thinking just an if statement, but is there an easier way? Could I just include the hex color code in the json?
Alternatively, you could map the colors to the division explicitly with a d3 ordinal scale as mentioned in the documentation. See Colorbrewer at the bottom.
var color = d3.scale.ordinal()
.domain(["foo", "bar", "baz"])
.range(["#fff","#000","#333"]);
and then
.attr("fill", function(d) { return color(d.division); });
Sounds like you want to include the colour in the JSON in this case. You can include it in any way that the browser recognises, e.g. as a name ("white") or hex ("#fff"). See the SVG spec for a full list of supported colour specifications.
Replace const color = d3.scaleOrdinal(d3.schemeCategory20); with:
const color = d3.scaleOrdinal()
.domain(["Crude oil","Natural gas",...])
.range(["#FD3E35","#FFCB06",...]);
And stay with:
.style('fill', (d,i) => {
d.color = color(d.name.replace(/ .*/, ''));
return d.color;})