Text inside each bubble in c3js Scatter plot - c3

I am generating scatter plot using c3 js.I wanted to display some text inside the bubble.Text can be either its value(y axis) or x axis value.The property (labels :true) which works for bar graph does not work in case of scatter.Please help
Thanks

Adding Labels to c3 Scatter Plot
You can select the points using d3 and add whatever text you want using the point coordinates. For example, here's how you add the serei-index.point-index
function drawLabels(chartInternal) {
var textLayers = chartInternal.main.selectAll('.' + c3.chart.internal.fn.CLASS.texts);
for (var i = 0; i < textLayers[0].length; i++) {
// select each of the scatter points
chartInternal.mainCircle[i].forEach(function (point, index) {
var d3point = d3.select(point)
d3.select(textLayers[0][i])
.append('text')
// center horizontally and vertically
.style('text-anchor', 'middle').attr('dy', '.3em')
.text(i + '.' + index)
// same as at the point
.attr('x', d3point.attr('cx')).attr('y', d3point.attr('cy'))
})
}
}
and call it like this
drawLabels(chart.internal);
You can easily use the index to pick out labels from an array instead.
Responding to Legend Clicks
To update the label positions when you show / hide each series by clicking on the legends you hook onto the legend click handlers remove the existing labels and draw them again at the new positions once the scatter points are in their final place. You use a timeout to make sure the label draw is triggered after the animation completes
Here's your legend option for that
legend: {
item: {
onclick: function (id) {
var $$ = this;
// remove existing labels
this.main.selectAll('.' + c3.chart.internal.fn.CLASS.texts).selectAll('*').remove();
// this block is a copy paste from c3 code
if (this.d3.event.altKey) {
this.api.hide();
this.api.show(id);
} else {
this.api.toggle(id);
this.isTargetToShow(id) ? this.api.focus(id) : this.api.revert();
}
setTimeout(function () {
drawLabels($$)
// add a small duration to make sure the points are in place
}, this.config.transition_duration + 100)
}
}
},
Fiddle - http://jsfiddle.net/mn6qn09d/

Related

Chart.js: How to get bar chart labels clickable?

I use chart.js 2.8.0 to create mainly pie and bar charts. The clickable legend on pie charts is really useful, filtering out unwanted data from the result.
When creating a chart there are two kinds of labels:
* An array of labels on chart level, label 1 corresponding to item 1 in each dataset.
* Dataset labels, one for each dataset.
A pie chart as standard get the chart label array turned into a legend with clickable labels, click on a label and that item is filtered out from the chart.
A bar chart, on the other hand, gets the labels shown below the bar but not clickable. Instead the legend here is made out of the dataset label. If you have more than one dataset, a whole dataset is filtered out if you click on that label.
Since I sometimes have several datasets I can not use the "trick" that consists of putting data item into a separate dataset (that was otherwise the closest to what I wanted that I found in my search, the "extra" clickable legend that would create would work as well). The situation is also that the end user should get a drop-down (or similar) so he, from the same data, can select chart type. So the soultion need to work both for pie and bar charts. the same data and (standard) code creates the two shown charts (except for the colors).
The question is now, as stated in the title: Is it possible to get clickable labels for a bar chart with the same filtering functionality as when the chart is of pie type?
I understand that it isn't doable by just setting some options, it would probably have to be done by creating a plugin, but is it at all doable? if so, any pointers for help?
If not clickable labels, maybe make the bars themselves clickable (with the same result)...?
With a slight change to the fiddle given by https://stackoverflow.com/users/3963330/tob%c3%adas in his answer here: Click events on Pie Charts in Chart.js I get a fiddle that also can handle multiple datasets, and on my second try I managed to hide a segment when I clicked on it. And then I relized that if it wasn't a pie chart there would be no clickable legend to use for unhiding that element - so that's not a solution for my bar charts.
Tried combining a couple of SO questions/answers (generating labels by #GRUNT : Bar labels in Legend) but can't get legend labels for bar charts to filter out segments instead of datasets.
Fiddle: https://jsfiddle.net/tommypeters/24ra6egy/9/
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="canvas"></canvas>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
var myNewChart = new Chart(ctx, {
type: 'bar',
data: {
datasets: [{
data: [300, 50, 100],
backgroundColor: [
"#F7464A",
"#46BFBD",
"#FDB45C"
]
},
{
data: [400, 60, 101],
backgroundColor: [
"#F7464A",
"#46BFBD",
"#FDB45C"
]
}
],
labels: [
"Red",
"Green",
"Yellow"
]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
legend: {
labels: {
generateLabels: function(chart) {
var labels = chart.data.labels;
var dataset = chart.data.datasets[0];
var legend = labels.map(function(label, index) {
return {
datasetIndex: 0,
fillStyle: dataset.backgroundColor && dataset.backgroundColor[index],
strokeStyle: dataset.borderColor && dataset.borderColor[index],
lineWidth: dataset.borderWidth,
text: label
}
});
return legend;
}
}
}
}
});
canvas.onclick = function(evt) {
var activePoints = myNewChart.getElementsAtEvent(evt);
if (activePoints[0]) {
var chartData = activePoints[0]['_chart'].config.data;
var idx = activePoints[0]['_index'];
var dIndex = myNewChart.getDatasetAtEvent(evt)[0]._datasetIndex;
var label = chartData.labels[idx];
var value = chartData.datasets[dIndex].data[idx];
// Doesn't hide a slice but a whole dataset...
// var meta = myNewChart.getDatasetMeta(dIndex);
// meta.hidden = meta.hidden === null ? !myNewChart.data.datasets[dIndex].hidden : null;
// myNewChart.update();
var i, ilen, meta;
for (i = 0, ilen = (myNewChart.data.datasets || []).length; i < ilen; ++i) {
meta = myNewChart.getDatasetMeta(i);
if (meta.data[idx]) {
meta.data[idx].hidden = !meta.data[idx].hidden;
}
}
myNewChart.update();
var url = "http://example.com/?label=" + label + "&value=" + value;
console.log(url);
alert(url);
}
}

How to highlight the axis?

I have many axes in the chart.
How to highlight the axis when hovering on a line(axis-value)?
It is necessary to highlight the axis to which the line(axis-value) belongs (when hovering on a line(axis-value))
(Highlight = make bold or change color)
Sorry for the bad english :)
You could use the seriesHover event:
seriesHover: function(e) {
var axis = e.sender.getAxis( e.series.axis);
for (var i=0; i<e.sender.options.valueAxis.length; i++){
if (i ==axis._axis.axisIndex){
e.sender.options.valueAxis[i].line.width = 3;
} else {
e.sender.options.valueAxis[i].line.width = 1;
}
}
e.sender.refresh();
}
From the series you can get theassociated axis, then set the axis line width and refresh the chart.
DEMO

Disable resize of brush on range chart from focus charts (dc.js, d3.js) - Solved

Please note - there is a solution for part of this problem by Gordon Woodhull in Disable brush on range chart before selecting a scale from the dropdown/on page load(dc.js,d3.js)
In addition,there is a partial solution at the end this question.
Furthermore there are two fiddles:
1) https://jsfiddle.net/dani2011/4dsjjdan/1/
2) https://jsfiddle.net/dani2011/uzg48yk7/1/ (with partial solution)
Need to disable resize of the brush on range chart (timeSlider) by dragging the line within the focus charts (bitChart,bitChart2). As Gordon Woodhull suggested (Disable brush resize (DC.js, D3.js) try to enable only pan without zoom.
Current behavior:
1)
Dragging the line on bitChart2 (focus chart) pans the brush until the borders of the timeChart. Once reaching the borders,the brush shrinks. The other focus chart (bitChart) resizes the brush of the range chart during drag of its line.
2)
When selecting a span for the brush from the dropdown only the .on('zoomed', function (chart, filter) { of bitChart2 is loaded and not the .on("zoomed"... of bitChart.
Print screens from the console:
a) Selecting scale from the dropdown
b) Dragging line on bitChart:
c) Dragging line on bitChart2:
3)
For both bitChart and bitChart2 the value of scale is 1 and the position
is 0,0:
.on('zoomed', function (chart, filter) {
//var zoom = d3.behavior.zoom()
// .translate([0, 0])
//.scale(1).scaleExtent([1, 1])
var zoom = d3.behavior.zoom()
var scale = zoom.scale(); var position = zoom.translate();
js file
The following implementations did not solve the issue:
**option 1**
bitChart.on('zoomed', function (chart, filter) {
d3.behavior.zoom().on("zoom", null);//doesn't stop zoom
//event needs svg element(tried different options),doesn't work
d3.behavior.zoom().scale(1).scaleExtent([1,1]).translate([0,0]).event(chart.select('g.stack_0')))
**option 2**
//Applied on timeslider,bitChart,bitChart2 to eliminate zoom and
//maintain pan
.zoomScale([1, 1])//dc.js
//And also
.on('zoomed', function (chart, filter) {
bitChart.zoomScale([1, 1]);
//Nothing pans with
chart.focus(chart.xOriginalDomain())
**option 3**
bitChart.on('zoomed', function (chart, filter) {
var svg = d3.select("body")
.append("svg")
.call(d3.behavior.zoom().on("zoom", function () {
svg.attr("transform", "translate(" + d3.event.translate + ")" +"
scale(" + 1 + ")")
}))
//.append("g")
**option 4**
.on('zoomed', function (chart, filter) {
var chartBody = chart.select('g.stack _0');
var path = chartBody.selectAll('path.line').data([extra_data]);
path.enter().append('path').attr({
class: 'line',
});
path.attr('transform', 'translate(0,50)');
**option 5**
bitChart.on('zoomed', function (chart, filter) {
var zoom = d3.behavior.zoom()
.scaleExtent([1, 1])
chart.select('g.stack _0').call(zoom);
zoom.scale(1);
zoom.event(chart.select('g.stack _0'));
**option 6**
bitChart.on('zoomed', function (chart, filter) {
svg.call(d3.behavior.zoom().scale(1));
**option 7**
var min_zoom = 1;
var max_zoom = 1;
var svg = d3.select("body").append("svg");
var zoom = d3.behavior.zoom().scaleExtent([min_zoom, max_zoom])
bitChart.on('zoomed', function (chart, filter) {
svg.call(zoom);
My fiddle:
https://jsfiddle.net/dani2011/4dsjjdan/1/ was forked from https://jsfiddle.net/gordonwoodhull/272xrsat/9/.
When selecting span from the dropdown and clicking on the range chart,The range chart (timeSlider) acts strange on the fiddle, but behaves as expected when run it in my environment. Please note in this fiddle that bitChart2 pans the brush as expected.The resize of the brush when reaching the edge happens in my enviroment. bitChart still resizes the brush.
A partial solution:
To enable multi focus charts on a single range chart as in https://github.com/dc-js/dc.js/blob/master/web/examples/multi-focus.html written by Gordon Woodhull.Used the focus chart which worked properly in my code (bitChart2) as the main reference chart:
bitChart2.focusCharts = function (chartlist) {
if (!arguments.length) {
return this._focusCharts;
}
this._focusCharts = chartlist; // only needed to support the getter above
this.on('filtered', function (range_chart) {
if (!range_chart.filter()) {
dc.events.trigger(function () {
chartlist.forEach(function(focus_chart) {
focus_chart.x().domain(focus_chart.xOriginalDomain());
});
});
} else chartlist.forEach(function(focus_chart) {
if (!rangesEqual(range_chart.filter(), focus_chart.filter())) {
dc.events.trigger(function () {
focus_chart.focus(range_chart.filter());
});
}
});
});
return this;
};
bitChart2.focusCharts([bitChart]);
My second fiddle:
https://jsfiddle.net/dani2011/uzg48yk7/1/ was forked from https://jsfiddle.net/gordonwoodhull/272xrsat/9/.
1) When clicking on the range chart in the fiddle it does not function properly, but works in my environment.
2) The brush does not resize at the edges of the range chart in the fiddle as it does in my environment
3) It does show in the fiddle that the whole range chart is selected when panning/clicking on the lines in the focus charts and when clicking in the range chart
4) It does show in the fiddle that after selecting the brush span from the dropdown, panning the lines in the focus charts moves the brush properly on the range chart.
5) It does show in the fiddle that dragging the brush on the range chart is possible again in no span is selected from the dropdown
Still needs to solve:
1) When reaching the ends of the range chart (timeSlider) the brush resizes
solved by updating versions to be the same as the version of the external resources in the fiddle https://jsfiddle.net/gordonwoodhull/272xrsat/9/. Thank you Gordon!
2) Before selecting a scale from the dropdown:
a) When panning /translating the line of the focus charts(bitChart,bitChart2) the brush resizes
b) It is possible again to drag the brush on the range chart
Any help would be appreciated !

d3 update number - count up/down instead of replacing number immediately

Hei,
I'm updating a bar chart when user presses a button. That works fine with the .transition- property. However, if I do that on text, it replaces the text immediately. Instead what I'd like to happen is that it would count from the old to the new number (while the label moves with the bar). So as an example: a bar is updated from value 1453 to 1102. Instead of replacing 1453 immediately when the user clicks it should count up from 1102 to 1453 over the specified transition time.
Can I achieve that? Is there any d3 function for that?
I uploaded a quick example of text interpolation on bl.ocks. The relevant parts are the custom interpolator:
function interpolateText(a, b) {
var regex = /^(\d+), (\d+)$/;
var matchA = regex.exec(a);
var matchB = regex.exec(b);
if (matchA && matchB) {
var x = d3.interpolateRound(+matchA[1], +matchB[1]);
var y = d3.interpolateRound(+matchA[2], +matchB[2]);
return function(t) {
var result = [x(t), y(t)].join(", ");
return result;
};
}
}
d3.interpolators.push(interpolateText);
And using d3.transition.tween:
.on("dragend", function(d, i) {
var prev = [d.x, d.y].join(", ");
d.x = d.origin[0];
d.y = d.origin[1];
var next = [d.x, d.y].join(", ");
var selection = d3.select(this)
.transition()
.duration(1000)
.call(draw);
selection
.select("text")
.tween("textTween", function() {
var i = d3.interpolate(prev, next);
return function(t) {
this.textContent = i(t);
}
});
});
In my case, I am listening for a drag start/end but you can hook it up to a button press very easily.
The reason the above code works is because .tween will get the same animation "ticks" that the standard interpolators use. This causes the inner t parameter to match the progress of the animation and when you set this.textContent it will update the inner value of the DOM element.
The example I use is interpolating between two points which is fairly trivial but if all you want is to update text containing exactly one number it is even easier.

HTML 5 Canvas Mouse over event on element (show tooltip)

I am working on a visualization project. Based on my data I am plotting hundreds of small circle on canvas. I want to add a mouse over event so that whenever a mouse is the enclosing area of a circle it will show some node property from my data as a tool tip or as text on the canvas.
My current drawCircle method
function drawCircle(canvas,x,y,r)
{
canvas.strokeStyle = "#000000";
canvas.fillStyle = "#FFFF00";
canvas.lineWidth = 2;
canvas.beginPath();
canvas.arc(x,y,r,0,Math.PI*2,true);
canvas.stroke();
canvas.fill();
canvas.closePath();
}
I have looked into kinetic.js
But can't figure it out how I can call my drawCircle [repetitively] method using their library.
Any help will be highly appreciated.
If you still want to use KineticJS, you would put the Kinetic shape stuff inside your drawCircle routine. This is basically pulled out of their tutorial and stripped down:
function drawCircle(stage,x,y,r) {
var circle = new Kinetic.Shape(function(){
var context = this.getContext();
// draw the circle here: strokeStyle, beginPath, arc, etc...
});
circle.addEventListener("mouseover", function(){
// do something
});
stage.add(circle);
}
If you don't want to use KineticJS after all, you will need to remember for yourself the positions and radii of every circle you drew, and then do something like this:
canvas.onmouseover = function onMouseover(e) {
var mx = e.clientX - canvas.clientLeft;
var my = e.clientY - canvas.clientTop;
// for each circle...
if ((mx-cx)*(mx-cx)+(my-cy)*(my-cy) < cr*cr)
// the mouse is over that circle
}

Resources