Highlighting individual rings in ring chart - d3.js

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.

Related

How to add label or custom value on Mapchart's path using geoChoroplethChart and dc.js?

var IndChart = dc.geoChoroplethChart("#india-chart");
var states = data.dimension(function (d) {
return d["state_name"];
});
var stateRaisedSum = states.group().reduceSum(function (d) {
return d["popolation"];
});
IndChart
.width(700)
.height(500)
.dimension(states)
.group(stateRaisedSum)
.colors(d3.scale.ordinal().domain().range(["#27AE60", "#F1C40F", "#F39C12","#CB4335"]))
.overlayGeoJson(statesJson.features, "state", function (d) { //console.log(d.properties.name);
return d.id;
})
.projection(d3.geo.mercator().center([95, 22]).scale(940))
.renderLabel(true)
.title(function (d) { console.log(d); return d.key + " : " + d.value ;
})
.label(function (d) { console.log(d);}) ;
wanted to add Label or custom value(25%, added in Map chart screen-shots) in map chart for each path using dc.js.
In the comments above, you found or created a working example that answers your original question. Then you asked how to make it work for two charts on the same page.
This is just a matter of getting the selectors right, and also understanding how dc.js renders and redraws work.
First off, that example does
var labelG = d3.select("svg")
which will always select the first svg element on the page. You could fix this by making the selector more specific, i.e. #us-chart svg and #us-chart2 svg, but I prefer to use the chart.select() function, which selects within the DOM tree of the specific chart.
Next, it's important to remember that when you render a chart, it will remove everything and start from scratch. This example calls dc.renderAll() twice, so any modifications made to the first chart will be lost on the second render.
In contrast, a redraw happens when any filter is changed, and it incrementally changes the chart, keeping the previous content.
I prefer to listen to dc.js chart events and make my modifications then. That way, every time the chart is rendered or redrawn, modifications can be made.
In particular, I try to use the pretransition event whenever possible for modifying charts. This happens right after drawing, so you have a chance to change things without any glitches or pauses.
Always add event listeners before rendering the chart.
Adding (the same) handler for both charts and then rendering, looks like this:
usChart.on('pretransition', function(chart) {
var project = d3.geo.albersUsa();
var labelG = chart.select("svg")
.selectAll('g.Title')
.data([0])
.enter()
.append("svg:g")
.attr("id", "labelG")
.attr("class", "Title");
labelG.selectAll("text")
.data(labels.features)
.enter().append("svg:text")
.text(function(d){return d.properties.name;})
.attr("x", function(d){return project(d.geometry.coordinates)[0];})
.attr("y", function(d){return project(d.geometry.coordinates)[1];})
.attr("dx", "-1em");
});
usChart2.on('pretransition', function(chart) {
var project = d3.geo.albersUsa();
var labelG = chart.select("svg")
.selectAll('g.Title')
.data([0])
.enter()
.append("svg:g")
.attr("id", "labelG")
.attr("class", "Title");
labelG.selectAll("text")
.data(labels.features)
.enter().append("svg:text")
.text(function(d){return d.properties.name;})
.attr("x", function(d){return project(d.geometry.coordinates)[0];})
.attr("y", function(d){return project(d.geometry.coordinates)[1];})
.attr("dx", "-1em");
});
dc.renderAll();
I used one more trick there: since pretransition happens for both renders and redraws, but we only want to add these labels once, I use this pattern:
.selectAll('g.Title')
.data([0])
.enter()
.append("svg:g")
.attr("class", "Title");
This is the simplest data binding there is: it says we want one g.Title and its data is just the value 0. Since we give the g element the Title class, this ensures that we'll add this element just once.
Finally, the result of this expression is an enter selection, so we will only add text elements when the Title layer is new.
Fork of your fiddle.

D3 circle element remains when mouse over on data update

I have a D3 timeseries chart made up of line path and at each data point i use a circle which is appended to the lines. The circles have a mouse enter event attached to it and when the mouse is moved over the circle it displays the tooltip with the information about that data point and i also change the class of the circle so that it looks highlighted.
The problem i have got is, when the mouse is over the circle and the circle is highlighted and the tooltip is showing, at the same time if i get some new data and the chart is updated, the circle my mouse is over does not disappear even when the mouse is removed off the circle and it shows that circle hanging in the middle without being attached to any line.
I have attached the image of the chart showing the problem.
Any help to fix this problem will be highly appreciated.
Image showing d3 issue
Here's the jsfiddle code showing the issue. Try pointing your mouse to the circle and wait for the chart to update every 5 seconds
(Moving this from the comment section)
Take a look at this: https://jsfiddle.net/xvLgq8mn/
In the updateChart function you select by the circle class:
// update the circles at each data points
svg.selectAll('.circle') // here you correctly select all with the circle class
.data(this.props.data)
.attr('class', 'circle all')
.transition()
.duration(500)
.attr('cx', (d) => { return this.axis.x(d.time);})
.attr('cy', (d) => { return this.axis.y(d.count);});
but here, on mouseover, you remove the circle class and replace it with circle--highlight:
group.selectAll()
.data(this.props.data)
.enter().append('circle')
.attr('class', 'circle all')
.attr('cx', (d) => { return this.axis.x(d.time);})
.attr('cy', (d) => { return this.axis.y(d.count);})
.attr('r', 4)
.on('mousemove', function(d) {
// START: Show tooltip
div.transition()
.duration(1000)
.style('opacity', 1);
div.html('<div class="date--time">'
+ d.time
+ '</div><div class="count">' + d.count + ' incidents</div>')
.style('left', (d3.event.pageX) + 'px')
.style('top', (d3.event.pageY - 70) + 'px');
d3.select(this)
.attr('r', 6)
.attr('class', 'circle--highlight'); // here you change the class from circle all
// to just circle--highlight,
// so when you are hovering a circle and the chart changes,
// the circle you have hovered won't be updated because
// it won't be selected due to the class difference
// END: Show tooltip
})
.on('mouseleave', function() {
// hide tooltip and return the circles to original style
div.transition()
.duration(500)
.style('opacity', 0);
// set the circle back to normal
d3.select(this)
.attr('r', 4)
.attr('class', 'circle all');
});
So a solution would be to also add the circle class along with the circle--highlight like this:
d3.select(this)
.attr('r', 6)
.attr('class', 'circle circle--highlight');
Or change your select in the updateChart like this:
svg.selectAll('circle')
but that would need many more adjustments to your script in order for it to work as expected.
Hope this helps! Good luck!

How do I add a transition delay between multiple individual transitioning polygons in D3?

The original Code can be found at: http://bl.ocks.org/Guerino1/3a51eeb95d3a8345bc27370e8c9d5b77
I have numerous polygons that are transitioning onto an svg canvas (from left to right, at the bottom of the HTML page).
The code I use to create an transition the chevrons leverages D3 Polygon:
// Create Polygons for SDLC
svgCanvas.selectAll("a")
.data(dataSet)
.enter().append("a")
.attr("xlink:href", function(d) { return d.link; })
.append("svg:polygon")
//svgCanvas.selectAll("polygon")
//.data(dataSet)
//.enter().append("polygon")
.attr("id", function(d,i){ return (selectString.replace(/ /g,'_').replace(/#/g,'') + "_index_" + i); })
.attr("originalcolor","violet")
.style("stroke","blue")
.style("fill","violet")
.style("stroke-width",2)
.attr("points", origin)
.on('mouseover', chevronMouseOver)
.on("mouseout", chevronMouseOut)
.on("click", chevronMouseOut)
.transition() // <------- TRANSITION STARTS HERE --------
.duration(3000)
.attr("points", calculateChevron);
Currently, all polygons transition into the svg canvas, together. I'd like to put a delay between each of them, so that it looks more like dealing from a deck of cards, one at a time.
How would I properly add a D3 delay to make this happen?
Thanks for any help you can offer.
try this..
.transition() // <------- TRANSITION STARTS HERE --------
.delay(function(d,i){ return 100*i; })
.duration(3000)
.attr("points", calculateChevron);

Transition chaining and cancellation

I have two functions that perform animations on lines. The first function, one is executed at the beginning, in order to perform operations on the enter selection, and only animates the lines' horizontal movement (x1 and x2). The second function two only animates the lines' height (only y2,y1 stays fixed).
Through user events, function one cannot be interrupted by function two, but vice versa (also because the animation in two is considerably longer). This means that, when the transition from two is still running, the user can trigger one.
This gave me serious headaches, because one would somehow take the values of the last state of the running transition of two instead of correctly assigning a data-driven value (i.e. .attr('y2', function(d){ ... });).
http://jsfiddle.net/h39WN/6/ - Please do the following: Click on one. You see that only the horizontal movement is animated as the data changes. You also see that at the end of its execution, the lines should always be ordered from lowest to highest.
Click on two once and wait the full 2 seconds until its animation is completed. Then click on one again. This is the desired behavior.
Now click on two, wait a few ms and then click on one - you see that the lines keep the height of the last state of two's animation, even though they are correctly ordered. (I know the data are not realistic and maybe a bit confusing in this example, but they still allow to replicate the problem).
I then came up with the solution to schedule another, "empty", transition on the lines in one - according to the docs this should cancel the one still running in two when one is invoked:
var one = function () {
var svg = d3.select('svg');
vis.mobileTeams = svg
.selectAll('.teamGroup')
.data(data.values, function (d) {
return d.ID;
});
// ENTER
var teamEnter = vis.mobileTeams
.enter()
.append('g')
.attr('class', 'teamGroup');
// enter line
teamEnter
.append('line')
.attr('class', 'teamWeightedLine');
// UPDATE THE LINE
// HEIGHT - SHOULD NOT BE ANIMATED
svg
.selectAll('.teamGroup line')
.attr('y1', paddingY)
// I inserted a transition here to cancel
// the one triggered by the other function
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.attr('y2', function(d){ ... });
// I need to use transition chaining so changing 'y2'
// with a transition does not get
// overwritten by the following transition
// HORIZONTAL POSITION - SHOULD BE ANIMATED
lineTransition
.transition()
.duration(500)
// 'x1' and 'x2' are the only values that
// need to be animated in this function
.attr('x1', function (d) {
return function(d){ ... });
})
.attr('x2', function (d) {
return function(d){ ... });
});
};
And here is the second function.
var two = function () {
var svg = d3.select('svg');
// this is where the only transition concerning HEIGHT is supposed to take place
svg
.selectAll('.teamGroup line')
.transition()
.ease('circle-out')
.duration(2000)
.attr('y2', vis.mobileCalculateScoreToHeight);
console.log('mobile vis updated');
};
Even though this fixes the "interference" problem as two's transition is canceled because another one is scheduled, it brings another problem:
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.attr('y2', function(d){ ... });
http://jsfiddle.net/96uN6/8/ This is the fiddle that incorporates this change. Even when two is interrupted by one, do the correct heights result in the end - but:
y2 is now being animated in one too! I know that .transition() brings with it a default duration of 250ms, so I did this:
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.duration(0)
.attr('y2', function(d){ ... });
This, in turn, brings another problem: y2 is not set at all now, i.e. the <line>s don't even have it as an attribute:
Weirdly, it works when using a very short duration (so the animation is barely visible), but it only works sometimes and is probably browser-dependent:
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.duration(10)
.attr('y2', function(d){ ... });
Setting y2 in the regular selection instead of the transition selection does not work either as it brings back the "interference" problem - as y2 is set when the animation from two is still running.
svg
.selectAll('.teamGroup line')
.attr('y1', paddingY)
.attr('y2', function(d){ ... });
var lineTransition = svg
.selectAll('.teamGroup line')
.transition();
The approach without transition chaining does not work either, of course, because the first transition is immediately canceled by the second and y2 is never set.
svg
.selectAll('.teamGroup line')
.transition()
.duration(10)
.attr('y2', function(d){ ... });
svg
.selectAll('.teamGroup line')
.transition()
.duration(TRANSDURATION)
.attr('x1', function (d) {
return function(d){ ... };
})
.attr('x2', function (d) {
return function(d){ ... };
});
So the only possible solution working for me (the one with the short duration) seems very quirky, there must be a better solution, mustn't it?
Feel free to ask if something is unclear.
Through Mike Bostock (https://github.com/mbostock/d3/issues/1877), I found out that I can use selection.interrupt() to cancel any previous transitions, i.e., already running transitions.
So, the quirky
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.duration(0)
.attr('y2', function(d){ ... });
becomes
var lineTransition = svg
.selectAll('.teamGroup line')
.interrupt()
.attr('y2', function(d){ ... });
It's as easy as that.
See: http://jsfiddle.net/96uN6/9/

NVD3 add a border/stroke to area chart

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);

Resources