c3 chart data label overlap issue - javascript

I am using C3.js to create a line chart with data labels.
the problem is that some labels overlap when data from 2 lines are very close to each other.
Is there any way to fix this data overlapping issue in C3
var chart = c3.generate({
data: {
labels:true,
columns: [
['data1', 30, 20, 50, 40, 60, 230],
['data2', 40, 130, 90, 240, 130, 220],
['data3', 20, 200, 160, 400, 250, 250]
]
}
});
http://jsfiddle.net/e60o24d0/238/

There are no built-in way to do so.
But you can try to identify and shift problematic labels in labels format function:
labels: {
format: function(v, id, point, line) {
if (point === undefined || line === undefined) return;
var label = d3
.selectAll('.c3-chart-text')
.selectAll('.c3-text')[line][point];
if (...) { // set your condition
var shift = ...; // set your calculation
d3.select(label)
.style('transform', 'translateY(' + shift + 'px)');
}
return v;
}
Some inspiration can be found in your updated fiddle.

Related

Highcharts - dynamic data label positioning on multi series doughtnut charts

Background info:
I am trying to create a multi series doughnut chart with the data labels positioned in the center of each slice like this (see below) however I want the data labels to reposition itself if the div is resized:
Problem:
I am struggling to reposition the data labels accurately when the div is resized.
I need to get the current doughnut chart plot size in percentage with which I could calculate accurate distance from doughnut edges to place the data labels.
Example Fiddle
Here's a mockup in fiddle: http://jsfiddle.net/simkus/bo2eumkd/30/
$(function() {
var container = $('#container')[0];
$('#resizer').resizable({
// On resize, set the chart size to that of the
// resizer minus padding. If your chart has a lot of data or other
// content, the redrawing might be slow. In that case, we recommend
// that you use the 'stop' event instead of 'resize'.
// TODO we need to calculate the distance of dataLabels from the edges of the
// doughnut chart. The main thing is that the chart might be resized, to e.g. 200pxx200px
// and then the labels now gets scatared by the current algorythm where we calculate `distance`
// the `distance` should be calculated I think not in pixels by % of the current
// chart placeholder size. The chart it self has a `size` of 80% for all series.
// the inner size is also measured in %, so the distance somehow has to be done
// similary, but at this point, the `distance` doesn't work with % as size and innerSize
resize: function() {
chart.setSize(
this.offsetWidth - 20,
this.offsetHeight - 20,
false
);
}
});
let seriesData = [{
"base": 1949,
"data": [106, 105, 26, 43, 55, 136],
"name": "Total",
},
{
"base": 871,
"data": [38, 44, 14, 20, 25, 75],
"name": "Male",
},
{
"base": 1063,
"data": [65, 61, 12, 23, 31, 60],
"name": "Female",
}
]
let categories = [
"Don't know",
"Australia",
"India",
"Japan",
"Brazil",
"I don’t know"
]
let series = [];
let plotArea = 350;
let centerInnerSize = 40; // 40%
for (let i = 0; i < seriesData.length; i++) {
var showInLegend = false,
data = [],
innerSize = centerInnerSize + ((100 - centerInnerSize) / seriesData.length) * i,
width = (plotArea / 2) * ((1 - centerInnerSize / 100) / seriesData.length),
distance = (plotArea / 2) * (1 - innerSize / 100) - width / 2;
if (i == 0) {
showInLegend = true;
}
for (let j = 0; j < categories.length; j++) {
data.push({
'name': categories[j],
'y': seriesData[i].data[j]
})
}
series.push({
name: seriesData[i].name,
data: data,
innerSize: innerSize + '%',
size: "80%",
showInLegend: true,
dataLabels: {
enabled: true,
distance: -distance,
}
})
}
var chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
plotBackgroundColor: null,
plotBorderWidth: 1,
plotShadow: false,
type: 'pie',
events: {
render() {
let chart = this;
chart.series.forEach(s => {
s.points.forEach(p => {
let label = p.dataLabel;
p.dataLabel.translate(label.translateX, label.translateY)
})
})
}
}
},
subtitle: {
text: 'Drag the handle in the lower right to resize'
},
tooltip: {
pointFormat: '{series.name}: <b>{point.percentage:.2f}%</b>'
},
plotOptions: {
pie: {
allowPointSelect: false,
cursor: 'pointer',
minSize: 1,
dataLabels: {
enabled: true,
color: '#000000',
format: '{percentage:.2f} %',
}
}
},
series: series,
});
});
Any ideas how I can resolve this problem?
I need to get the current doughnut chart plot size in pixels with which I could calculate accurate distance from doughnut edges to place the data labels.
I am not sure what is your idea to place the data labels, but you should be able to achieve it by using the render callback where you can get access to each point and his label.
Demo: http://jsfiddle.net/BlackLabel/5m41p986/
events: {
render() {
let chart = this;
chart.series.forEach(s => {
s.points.forEach(p => {
let label = p.dataLabel;
p.dataLabel.translate(label.translateX, label.translateY)
})
})
}
}
API: https://api.highcharts.com/highcharts/chart.events.render
Updated the distance calculation using percentage and now it works.
http://jsfiddle.net/4g1pxvqb/36/
for (let i = 0; i < seriesData.length; i++) {
var showInLegend = false,
data = [],
innerSize = centerInnerSize + ((100 - centerInnerSize) / seriesData.length) * i,
width = (100 - centerInnerSize) / 3;
distance = '-' + (10 + width * (2 - i)) + '%';

How to draw an allipse (oval) on a Highchart graph

I have two series and their intersection point. I want to have an oval (ellipse) on a chart with center in intersection. Oval radiuses should be set in terms of axis units to show area of interest for each axis.
Highcharts.chart('container', {
series: [
// first series
{
name: 'IPR',
data: [[0, 30.5],[18.5, 25.4],[30, 19.4],[38, 9.7],[42, 0.02]]
},
// second series
{
name: 'VLP',
data: [[2, 0.5],[7, 1],[14, 6],[21, 22],[29, 29.6],[40, 30.3],[50, 27.2]]
},
// intersection
{
name: 'Operating point',
data: [
[22.42, 23.35]
]
}
],
})
How can I programmatically draw an oval in intersection and make zoom work?
You can use Renderer.createElement to create other SVG elements in Highcharts:
this.renderer.createElement('ellipse').attr({
cx: 60,
cy: 60,
rx: 50,
ry: 25,
'stroke-width': 2,
stroke: 'red',
fill: 'yellow',
zIndex: 3
}).add();
For translating to axis units use toPixels as #Anton Rybalko suggested.
Live demo: http://jsfiddle.net/kkulig/ds6aj5yp/
API references:
https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#createElement
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse
https://api.highcharts.com/class-reference/Highcharts.Axis#toPixels
The SVG Renderer is not really a great answer.
The polygon feature should be used (with many points, which you can generate offline in a backend, or analytically in the front end):
series: [{
name: 'Target',
type: 'polygon',
data: [[153, 42], [149, 46], [149, 55], [152, 60], [159, 70], [170, 77], [180, 70],
[180, 60], [173, 52], [166, 45]],
color: Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0.5).get(),
enableMouseTracking: false
}
https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/polygon/
To draw a circle, line etc SVGRenderer can be used. But there is no method to draw an ellipse. However rect() with rounded corners can be used.
Following code can be used to draw an ellipse in point (100, 200) px, horizontal radius 20 px and vertical radius 10 px:
chart.renderer.rect(100, 100, 20, 10, '50%')
.attr({
'stroke-width': 1,
'stroke': 'green',
'fill': 'yellow',
zIndex: 0
})
.add();
To specify x, y and radiuses in terms of axis untits Axis.toPixels() can be used. If we need to convert point (22.42, 23.35) to pixels it can be done like:
var x = chart.xAxis[0].toPixels(22.42),
y = chart.yAxis[0].toPixels(23.35)
So function to draw an ellipse would be:
var drawEllipse = function(chart, x, y, xr, yr) {
var x1 = chart.xAxis[0].toPixels(x-xr)
var x2 = chart.xAxis[0].toPixels(x+xr)
var y1 = chart.yAxis[0].toPixels(y-yr)
var y2 = chart.yAxis[0].toPixels(y+yr)
$('.' + rectClass).remove()
chart.renderer.rect(x1, y2, x2 - x1, y1 - y2, '50%')
.attr({
'stroke-width': 1,
'stroke': 'green',
'fill': 'yellow',
'zIndex': 0
})
.add();
};
Finnaly redraw event can be used to redraw ellipse after zoom:
$(function() {
var drawEllipse = function(chart, x, y, xr, yr) {
// get pixel coordinates of rect
var x1 = chart.xAxis[0].toPixels(x-xr)
var x2 = chart.xAxis[0].toPixels(x+xr)
var y1 = chart.yAxis[0].toPixels(y-yr)
var y2 = chart.yAxis[0].toPixels(y+yr)
// remove previous ellipse
var rectClass = 'operating-point-ellipse'
$('.' + rectClass).remove()
// draw ellipse using rect()
chart.renderer.rect(x1, y2, x2 - x1, y1 - y2, '50%')
.attr({
'stroke-width': 1,
'stroke': 'green',
'fill': 'green',
'fill-opacity': 0.2,
'zIndex': 0
})
.addClass(rectClass)
.add();
};
$('#container').highcharts({
chart: {
events: {
redraw: function() {
drawEllipse(this, 22.42, 23.35, 6, 3);
},
load: function() {
drawEllipse(this, 22.42, 23.35, 6, 3);
}
}
},
//...
});
});
See full code on jsFiddle: http://jsfiddle.net/arybalko/rcct2r0b/

Highcharts Label redraw

I am working off this JSFIDDLE which shows how to add a label to a point in a chart. This works fine for fixing a position for the label in the case of the chart not being responsive. Change the width of the fiddle and the label sits in the original position. I want the label to stay on top of the last column.
I have tried using load and redraw events and I am close, but I need to destroy the label before creating a new one.
events: {
load: function () {
var point = this.series[0].points[2];
this.renderer.label(
'<div class="text-center">GOAL TO REACH</div>', 270, 50, 'callout', point.plotX + this.plotLeft, point.plotY + this.plotTop
)
.css({
color: '#FFFFFF'
})
.attr({
fill: 'rgba(0, 0, 0, 0.75)',
padding: 8,
r: 5,
zIndex: 6
})
.add();
},
redraw: function() {
var point = this.series[0].points[2];
this.renderer.label(
'<div class="text-center">GOAL TO REACH</div>', 270, 50, 'callout', point.plotX + this.plotLeft, point.plotY + this.plotTop
)
.css({
color: '#FFFFFF'
})
.attr({
fill: 'rgba(0, 0, 0, 0.75)',
padding: 8,
r: 5,
zIndex: 6
})
.add();
}
}
I want the label to follow the point when the chart's width is changed.
I tried using a tooltip, but has problems disabling it from the first two columns. I can redraw the tooltip ok, but could not disable it from all other columns
Check out this updated JSFiddle
I added a variable to the global scope var theLabel;
Then on chart initial draw, set theLabel = chart.renderer.label(...)
Then
redraw: function() {
theLabel.destroy();
point = this.series[0].points[2];
theLabel = this.renderer.label('<div class="text-center">Reach 10 active subscriptions<br/>and enjoy a one year FREE membership!</div>', this.plotWidth - 200, 50, 'callout', point.plotX + this.plotLeft, point.plotY + this.plotTop)
.css({
color: '#FFFFFF'
})
.attr({fill: 'rgba(0, 0, 0, 0.75)',
padding: 8,
r: 5,
zIndex: 6
})
.add();
}
Note that in redraw::
I call theLabel.destroy() to remove the original label
I changed chart to this
Instead of plotting it at x = 270 I plot it at this.plotWidth - 200 to get it more-or-less on the right hand side of the plot with the chevron always pointing to the red bar.
Hope this works for you

Google Chart hAxis.gridlines.count does not work as expected

I have a google Chart with percentage values from 0 to 100% (i.e. 0 to 1)
When I set hAxis.gridlines.count to 10 expecting to get gridlines at 0, 10, 20, ... 90, 100
I get gridlines at 0, 12, 24, 36, ..., 96, 108
I could not find any solution by now.
Here is my Code:
function getData() {
// Create the data table.
var data = new google.visualization.arrayToDataTable([
['Category', 'Fulfillment (%)'],
['A', .75],
['B', .50],
['C', .50],
['D', .30],
['E', 0]
]);
// Set chart options
var options = {
'title': 'My Title',
'height': 300,
'backgroundColor': 'none',
'hAxis': {
'maxValue': 1,
format: '#%',
gridlines: {
count: 10
}
},
'colors': ['#F5821E'],
legend: { position: "none" }
};
var formatter = new google.visualization.NumberFormat({ pattern: '#%' });
formatter.format(data, 1);
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.BarChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
Reading the documentation, you can see that hAxis.gridlines.count specifies how many lines to draw, not where to draw them. What you're looking for is probably hAxis.ticks instead.
Use count: 11 and not 10. This will change the labels of the grid lines.
I had the same problem (bug) and I realized that if you set a height for a chart (column chart) then number of horizontal grid will be 2. I you want to use propriety hAxis.gridlines.count than you should to remove height propriety.

Dojo Gauges - can I restrict the settable values?

I have an interactive dojox gauge with a range from 0 - 100. But I only want my users to be able to move the needle to 0, 25, 50, 75 and 100. Is that possible?
// create the gauge
var gauge = new dojox.gauges.GlossyCircularGauge({
background : [255, 255, 255, 0],
color : color,
id : "gauge_" + item,
width : 150,
height : 150,
value : itemProgress,
noChange : Login.isLoggedIn(),
majorTicksInterval : 25
}, dojo.byId("gaugeDiv_" + item));
gauge.startup();
Cheers,
JP
Have a look at my fiddle: http://jsfiddle.net/v7WwD/1/
require(["dojox/dgauges/components/grey/CircularLinearGauge"],
function (CircularLinearGauge) {
var myGauge = new CircularLinearGauge({
value: 20,
minimum: 0,
maximum: 150,
majorTickInterval: 25,
minorTickIntervall: 5,
indicatorColor: "#000080", //Zeiger
fillColor: "#FFFFFF"
}, dojo.byId("gauge"));
myGauge.startup();
});
//more code in the fiddle
//......
I have created a CircularLinearGauge programatically.
Regards
First I would consider using dojox/dgauges (http://dojotoolkit.org/reference-guide/1.9/dojox/dgauges.html) instead of dojox/gauges. These are the only well maintained gauges in the toolkit as of today. Second with dojox/dgauges you can set the snapInterval of your LinearScaler to 25 and you should get the expected behavior.

Categories

Resources