How to add labels to c3.js scatter plot graph? - d3.js

Is it possible to add labels to scatter plot points in c3.js like in this google charts example?
https://google-developers.appspot.com/chart/interactive/docs/gallery/bubblechart#javascript

c3 doesn't support this currently - https://github.com/masayuki0812/c3/issues/481. But you can easily add the functionality - just loop through the chart series and points and add the labels as necessary.
var labels = [
['AA', 'BB', 'CC', 'DD', 'EE', 'FF', 'GG', 'HH'],
['ZA', 'ZB', 'ZC', 'ZD', 'ZE', 'ZF', 'ZG', 'ZH']
];
// series
var series = chart.internal.main
.selectAll('.' + c3.chart.internal.fn.CLASS.circles)[0];
// text layers
var texts = chart.internal.main
.selectAll('.' + c3.chart.internal.fn.CLASS.chartTexts)
.selectAll('.' + c3.chart.internal.fn.CLASS.chartText)[0]
series.forEach(function (series, i) {
var points = d3.select(series).selectAll('.' + c3.chart.internal.fn.CLASS.circle)[0]
points.forEach(function (point, j) {
d3.select(texts[i])
.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '0.3em')
.attr('x', d3.select(point).attr('cx'))
.attr('y', d3.select(point).attr('cy'))
.text(labels[i][j])
})
});
Fiddle - http://jsfiddle.net/6phuuans/

Currently C3.js doesnt provide us with the option to add labels to a scatter plot chart. But the following method can be used to add responsive data labels:
After the chart is rendered (in the "onrendered" property of the chart), identify the data points ( tags) and add tags with the x and y coodinates picked from the relevant circle, as the tags sibling.
Code Snippet:
onrendered: function(){
// get the parent of the the <circles> to add <text as siblings>
var g = d3.selectAll('.c3-circles');
//Get all circle tags
var circles = d3.selectAll('circle')[0];
//go to each circle and add a text label for it
for(var i = 0; i < circles.length; i++){
//fetch x-coordinate
var x = $(circles[i])[0].cx;
//fetch y-coordinate
var y = $(circles[i])[0].cy;
//create and append the text tag
g.append('text')
.attr('y', y.baseVal.value - 15) // (-15) places the tag above the circle, adjust it according to your need
.attr('x', x.baseVal.value)
.attr('text-anchor', 'middle')
.attr('class', 'c3-text c3-text-'+i)
.text(data[i].<data point key>) // the text that needs to be added can be hard coded or fetched for the original data.
//Since I am using a JSON to plot the data, I am referencing it and using the key of the value to be shown.
}
}
This will add the label, but on resize , multiple data labels will be plotted. To handle that, on the charts resize, we must remove the previous data labels (in the "onresize" property of the chart).
Code Snippet:
onresize: function () {
$('.c3-shapes.c3-circles text').remove();
}

Related

D3 Transition to count up

Newbie here. I am building a D3 bar-chart, and was able to animate the bar height; however, I have a text field next to each bar that shows the values, I was unable to make the text count up as the bar height grows.
I am using the .text attribute with I think where the problem is:
g.selectAll(".myText")
.transition()
.text(function(d){return (d.m6+"%")}) //code for counting from previous d.m6 value?
.attr("transform", function(d) { ...code for moving text location...)
.duration(700)
.ease(d3.easeLinear);
Any help will be greatly appreciated
Transitioning plain text will just result in the end value being displayed on transition start (after any delay):
For each selected element, sets the text content to the specified target value when the transition starts. .... Text is not interpolated by default because it is usually undesirable. (docs).
Here's an example of this at work:
var svg = d3.select("body")
.append("svg");
var text = svg.append("text")
.attr("x", 50)
.attr("y", 50)
.text(1);
text.transition()
.text(1000)
.duration(1000)
.delay(1000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
However, you can interpolate text, the most canonical method would be to use transition.tween() (documentation). Though you could use .attrTween or even d3-timer. There are also a few other options out there but they are less straight forward.
A tween takes two arguments. The first argument is the name of the property being modified. The second argument is a tween value which returns a function of the form:
return function(t) { interpolator(t); })
Where t is a number between 0 and 1 representing the progress of the transition (0 = start, 1 = end), and interpolator is some function for interpolating a transition value for any point in the transition. With d3 this might be a d3-interpolator.
var svg = d3.select("body")
.append("svg");
var text = svg.append("text")
.attr("x", 50)
.attr("y", 50)
.text(1);
text.transition()
.tween("text", function() {
var selection = d3.select(this); // selection of node being transitioned
var start = d3.select(this).text(); // start value prior to transition
var end = 1000; // specified end value
var interpolator = d3.interpolateNumber(start,end); // d3 interpolator
return function(t) { selection.text(Math.round(interpolator(t))); }; // return value
})
.duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>
I've just used rounding to keep the number formatted, otherwise the decimal points get pretty obnoxious. You could always apply some actual formatting.

Heatmap and sparklines

I created an heatmap using this example and this data:
NAME,YEAR,M1,M2
A,2000,20,5
B,2000,30,1
C,2000,,10
D,2000,,88
E,2000,,21
F,2000,84,3
G,2000,,64
A,2001,44,48
B,2001,15,51
C,2001,20,5
D,2001,95,2
E,2001,82,9
F,2001,,77
G,2001,3,80
A,2002,8,99
B,2002,92,52
C,2002,62,
D,2002,41,
E,2002,66,
F,2002,21,21
G,2002,62,4
A,2003,2,5
B,2003,89,78
C,2003,9,
D,2003,7,9
E,2003,2,45
F,2003,92,58
G,2003,2,14
A,2004,2,55
B,2004,89,58
C,2004,9,55
D,2004,7,59
E,2004,2,70
F,2004,92,
G,2004,2,
Now I would like to add to the right of the heatmap a sparkline for each row, so there must be a sparkline associated with A, to B, etc.
And I wish they were positioned right next to each other.
To make the sparklines I saw this example.
This is the result: PLUNKER.
As you can see, I can't get the data correctly from the data.csv file to create the sparklines. Also I don't know how to place them in the correct position.
I tried this way but without success.
var sparkSvg = d3.select("#container-sparkline")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.data(dataNest)
.enter()
.append("path")
.attr("class", "sparkline-path")
.attr("d", function(d) {
console.log("i");
console.log(d);
});
Also I'm not sure using two div is the correct way to put a chart near another chart.
Anyone would know how to help me?
Approach:
I've created a sparkline for every name in data set with values on x axis as a year and y as a m2 value from data set. For the demo purposes I've hardcoded number of years to 5 so x axis have only 5 values, but that can be computed with some additional script based on input data.
I've also added tome padding for sparkline container so they're aligned with the heatmap
Code:
As you can see in the plunker I've introduced a function to group data by name, so for each name we have an array with objects:
var groupBy = function(array, key) {
return array.reduce(function(a, v) {
(a[v[key]] = a[v[key]] || []).push(v);
return a;
}, {});
};
// data grouped by name
var groupedData = groupBy(data, 'name');
Since we assumed for demo purposes that X axis has fixed number of values we need to find max value for Y axis to properly scale charts. To do that I reduce array of values to get only m2 values and find a max number whthin that array:
var maxYvalue = Math.max(...data.map(function(d){return Number(d.m2)}));
Now we can create scales for the sparklines
var x = d3.scaleLinear().domain([0, 4]).range([0, 60]);
var y = d3.scaleLinear().domain([0, maxYvalue]).range([2, itemSize-2 ]);
I'm assuming that chart have width of 60px and height of itemSize, I also introduce 2px of vertical padding so its easier to read those sparklines being on next to each-other.
Now we can define d3.line(as you already did in your plunker) which we'll use fro rendering sparklines .
var line = d3.line()
.x(function(d, i) { return x(i); })
.y(function(d) { return y(d); })
And last step is to render sparklines inside "#container-sparkline" container. To do that we can iterate over every array in groupedData and render sparkline for each name:
// for each name render sparkline
Object.keys(groupedData).forEach(function(key){
const sparkData = groupedData[key].map(function(datum){
return Number(datum['m2']);
})
var sparkSvg = d3.select("#container-sparkline")
.append("svg")
.attr("width", "100%")
.attr("height", itemSize-1)
.append("path")
.attr("class", "sparkline-path")
.attr("d", line(sparkData));
})
I've also slightly changed styles for #container-sparkline and added borders for sparkline svg's. I hope this is what you've asked for.
Here you can find your plunker with my changes
http://plnkr.co/edit/9vUFI76Ghieq4yZID5B7?p=preview

dc.js Can a Pie Legend be recentered after filtering

I would like to be able to recenter a Pie charts legend after it has been filtered. Slices/Legends will removed when filtering because we remove empty bins. I added a pretransition listener to chart2, but that seems to be to late because the legend y value is the previous value and not current.
.on('pretransition', chart => buildLegend (chart))
If Male is selected on the Gender Pie chart I want the 4 legend items on the Job Pie chart to be re-centered. Any suggestions?
You can see a jsFiddle example.
A little more digging around showed me how to reference and update SVG elements.
function recenterLegend(chart) {
chart.selectAll('g.dc-legend')
.attr('transform', function(d) {
let legendY = (300 - (chart.group().all().length * 16)) / 2;
let translate = 'translate(220,' + legendY + ')';
return translate ;
});
}
Here is the updated jsfiddle.

DimpleJS barchart styling columns

I'm basically using a modified version of : http://dimplejs.org/advanced_examples_viewer.html?id=advanced_bar_labels .
I'd like to be able to add for each value a border on the left as high as the value (with a specific color for that border).
I'm not really sure where to start for adding that.
Any ideas?
Thanks.
More details : This is what I'd like to obtain : https://dl.dropboxusercontent.com/u/2227188/Image%202.png - the border on the left is the issue. (jsfiddle.net/mkzTk/5/ this what I currently have which is pretty much what's in the example - I don't know where to start really for adding a border)
You could append a rectangle after drawing for each element of the series as follows:
mySeries.afterDraw = function (s, d) {
var shape = d3.select(s);
svg.append("rect")
.attr("x", shape.attr("x"))
.attr("y", shape.attr("y"))
.attr("height", shape.attr("height"))
.attr("width", "10px")
.style("fill", shape.style("stroke"))
.style("pointer-events", "none");
};
The example you mention already uses the afterDraw function so just add the contents above to the existing method for labelling.
It looks nice, here's an example:
http://jsbin.com/lorin/9/edit?js,output#J:L20
I would set up each bar + edge pair as its own group based on a certain data point, and then append two rect elements to that group. Differences in color can be used to give them their distinctive colors.
Your code would look something like this:
var monthBars = d3.selectAll('.monthBar') //These will be for each chart
.data(allMyData, idFunction) //Assign and key your data
.enter()
.append('g')
.classed('monthBar', true);
.each(function(d){
var taskGroups = d3.select(this).selectAll('.taskGroup')
.data(d.dataForThisMonth, taskIdFn)
.enter()
.append('g')
.classed('.taskGroup', true);
.attr('transform', ...) //Define the x and y positioning for the group
taskGroups.append('rect')
//Make this the 'body' rect with the text in it
taskGroups.append('rect')
//Make this the edge rect
})

Reload nested data in D3.js

I do not manage to update a bar-chart with nested data in D3.js with new data.
I have nested data of the form:
data = [[1,2,3,4,5,6],[6,5,4,3,2,1]];
I managed to visualize the data by first appending a group for every subarray.
In the groups I then add the arrays as data (simplified):
function createGraph(l, svg){
var g = svg.selectAll("g")
.data(l)
.enter().append("g");
var rect = g.selectAll("rect)
.data(function(d){return d;})
.enter().append("rect")
. ...
}
However, when call the function again with different data, nothing happens.
It seems like in the second row, the rects do not get updated.
I have created a full example over at jsBin: http://jsbin.com/UfeCaGe/1/edit?js,output
A little more explanation of Lars' bug-catch, since I'd already started playing around...
The key was in this section of the code:
var group = svg.selectAll("g")
.data(l)
.enter().append("g");
The variable group is assigned the enter selection, not the raw selection. Then in the next line:
var bar = group.selectAll("rect")
.data(function(d){
return d;
});
You end up defining bar as only the rectangles that are children of just-entered groups. So even though you were handling update correctly for the rectangles, that whole section of code wasn't even running. You need to save the group selection before branching the chain to deal with entering groups:
var group = chart.selectAll("g")
.data(dt);
group.enter().append("g");
var bar = group.selectAll("rect")
.data(function(d){
return d;
});
Also, you're missing a j in your function declaration in your update. And you can reduce code duplication by putting your rectangle update code after your rectangle enter code, and then any attributes that get set in the update don't have to be specified for enter. (Some older examples don't use this pattern, because the original versions of d3 didn't automatically transfer newly-entered elements to the main selection.)
// enter
bar.enter().append("rect")
.attr("fill", function(d,i,j){
return colors(j);})
.attr("height", 0);
// update
bar.attr("transform", function(d, i, j) {
x = "translate("+(i*2.2*w+j*w)+",0)";
return x; })
.transition()
.duration(750)
.attr("width", w)
.attr("height", function(d){return d*10;});

Resources