ChartJS Doughnout Chart Pie Offset on Hover - javascript

I have this:
Is it possible to animate one section of this chart, a Pie, on hover to make it grow, as in offset by either giving it padding or a different height?
I think this should be possible because on their site it says " Animate everything!", but haven't had any luck yet. Tried using events but not working.
// Doughnut chart
var myDoughnutChart = new Chart(ctx, {
type: 'doughnut',
data: {
datasets: [{
data: [11, 47, 53],
backgroundColor: ['rgb(137, 207, 191)', 'rgb(140, 187, 206)', 'rgb(144, 156, 209)']
}],
labels: [
'Elementary',
'Middle School',
'High School'
],
},
options: {
cutoutPercentage: 60,
title: {
display: true,
text: 'Grade',
position: 'top',
fontFamily: 'sans-serif',
fontSize: 18,
fontColor: 'rgb(97, 98, 116)',
padding: '20'
},
layout: {
padding: {
top: 20,
}
},
legend: {
display: true,
},
onHover: stuff,
slices: {
1: {
offset: .5
}
}
}
});
function stuff(e) {
var activePoints = myDoughnutChart.getElementsAtEvent(e);
console.log(activePoints);
}
Thanks for any help.

Add this code in update function of doughnut
var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
if (index == doughnutIndex) {
innerRadius = innerRadius + 10;
}
And add a new function setHoverStyle
setHoverStyle: function(arc) {
doughnutIndex = arc._index;
this.update();
},

If what you are wanting is for the section to move outward on hover, that is done with simply setting hoverOffset with a number. See this example and documentation here.

Related

React-Chart.js : How do I increase the space between the legends and chart?

There are a couple of questions that run along the same lines as mine. However, these questions focus on simply chart.js. I have a similar problem but on react-chart.js. How do I increase the space between the legend and chart? I have used padding but it only increases the space between the legends. Not quite what I wanted. Below is my doughnut chart component.
<div className="dougnut-chart-container">
<Doughnut
className="doughnut-chart"
data={
{
labels: ["a", "b", "c", "d", "e", "f"],
datasets: [
{
label: "Title",
data: [12821, 34581, 21587, 38452, 34831, 48312],
backgroundColor: [
'rgb(61, 85, 69)',
'rgb(115, 71, 71)',
'rgb(166, 178, 174)',
'rgb(209, 191, 169)',
'rgb(66, 63, 62)',
'rgb(43, 43, 43)',
]
}
],
}
}
options={
{
plugins: {
legend: {
labels: {
color: "white",
font: {
size: 12
},
padding: 10,
},
position: "left",
title: {
display: true,
text: "Title",
color: "grey",
padding: 10
}
}
},
elements: {
arc: {
borderWidth: 0
}
},
responsive: true,
maintainAspectRatio: true,
}
}
/>
</div>
What my chart looks like:
You can write a custom plugin as showed by this answer, but instead of adding some extra space to the height you will need to add some extra spacing to the width of the legend boxes:
var options = {
type: 'doughnut',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
}]
},
options: {
plugins: {
legend: {
position: 'left'
},
legendDistance: {
padding: 50
}
}
},
plugins: [{
id: 'legendDistance',
beforeInit(chart, args, opts) {
// Get reference to the original fit function
const originalFit = chart.legend.fit;
// Override the fit function
chart.legend.fit = function fit() {
// Call original function and bind scope in order to use `this` correctly inside it
originalFit.bind(chart.legend)();
// Change the height as suggested in another answers
this.width += opts.padding || 0;
}
}
}]
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.0/chart.js"></script>
</body>
This answer suggested by #LeeLenalee works for me. But for those who are confused and wants to see this integrated in their components, here is what I did:
<div className="dougnut-chart-container">
<Doughnut
className="doughnut-chart"
data={
{
labels: ["label_1", "label_2", "label_3", "label_4", "label_5", "label_6"],
datasets: [
{
label: "Title",
data: [12821, 34581, 21587, 38452, 34831, 48312],
backgroundColor: [
'rgb(61, 85, 69)',
'rgb(115, 71, 71)',
'rgb(166, 178, 174)',
'rgb(209, 191, 169)',
'rgb(66, 63, 62)',
'rgb(43, 43, 43)',
]
}
],
}
}
options={
{
plugins: {
legend: {
labels: {
color: "white",
font: {
size: 12
},
padding: 10,
},
title: {
display: true,
text: "A Longer Title To Occupy Space",
color: "grey",
padding: {
bottom: 10
},
font: {
size: 13
}
},
position: "left"
},
// this is the id that is specified below
legendDistance: {
padding: 130 // dictates the space
}
},
elements: {
arc: {
borderWidth: 0
}
},
responsive: true,
maintainAspectRatio: true,
}
}
plugins={
[
{
id: 'legendDistance',
beforeInit(chart, args, opts) {
// Get reference to the original fit function
const originalFit = chart.legend.fit;
// Override the fit function
chart.legend.fit = function fit() {
// Call original function and bind scope in order to use `this` correctly inside it
originalFit.bind(chart.legend)();
// Specify what you want to change, whether the height or width
this.width += opts.padding || 0;
}
}
}
]
}
/>
</div>
This is the result: result
Take note: You need to reload your page to see the changes.
for react you can try this code ->
const legendMargin = {
id: "legendMargin",
beforeInit: function (chart) {
const fitValue = chart.legend.fit;
chart.legend.fit = function fit() {
fitValue.bind(chart.legend)();
return (this.height += 40);
};
}
};
then just need to pass legendMargin as a props like this way
<Bar options={options} data={data} plugins={[legendMargin]} />

Apexcharts cursor pointer

I used apexcharts.js for making chartbar on js. So i want to change cursor to pointer. help please! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
my-code!
var options = {
series: [{
name: 'series1',
data: [60, 85, 75, 120, 100, 109, 97]
}],
toolbar: {
show: false,
},
chart: {
height: 350,
type: 'area',
fontFamily: 'Proxima Nova',
toolbar: {
show: false
},
},
dataLabels: {
enabled: false
},
stroke: {
curve: 'smooth'
},
xaxis: {
categories: ["Янв", "Фев", "Март", "Апр", "Май", "Июнь", "Июль", "Авг", "Сен", "Окт", "Ноя", "Дек"]
},
tooltip: {
x: {
format: 'dd/MM/yy HH:mm'
},
},
};
var chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();
I had encountered the same problem. I will present you two solutions:
1st method : Found on Github
You can set the cursor to point with:
chart: {
...
events: {
dataPointMouseEnter: function(event) {
event.path[0].style.cursor = "pointer";
}
}
}
See more details in this github link : https://github.com/apexcharts/apexcharts.js/issues/1466
2nd method : My own method
You can target the class name of the apexchart component via Inspector, then at the code level add the following property to this class :
cursor: pointer
Example :
// Change cursor on hover
.apexcharts-pie {
cursor: pointer;
}
I had same problem. Here is two solutions:
chart: {
width: 320,
type: ...,
events: {
dataPointMouseEnter: function(event) {
event.target.style.cursor = "pointer";
// or
event.fromElement.style.cursor = "pointer";
}
},
}

Horizontal stacked angular bar charts

I am trying to implement an Angular horizontal stacked bar chart- Something like this example.
However, I want just one bar that is stacked.
I am working with AngularJS and Chart.js. I have the example showing on the page.
In the PieController, ChartData contains:
{"data":["63","38"],"labels":["Ford","GM"]}
In the example, instead of the label on the outside, I would like the label and then the number to be inside the chart. Like [=====Ford 63====|===GM 38===] the equals represent bar colors. There will be more data points than the current two.
Here is my page
<div ng-controller="PieController">
data {{ChartData}} //testing purposes
<div ng-init="getBarChart()">
<canvas id="Chart1"></canvas>
</div>
Here is my JavaScript controller
app.controller('PieController', function($scope, ChartService) {
$scope.getBarChart = function(){
ChartService.get({name: 'main'}, function(data) {
$scope.ChartData = data;
var barOptions_stacked = {
tooltips: {
enabled: false
},
hover: {
animationDuration: 0
},
scales: {
xAxes: [{
ticks: {
beginAtZero: true,
fontFamily: "'Open Sans Bold', sans-serif",
fontSize: 11
},
scaleLabel: {
display: false
},
gridLines: {},
stacked: true
}],
yAxes: [{
gridLines: {
display: false,
color: "#fff",
zeroLineColor: "#fff",
zeroLineWidth: 0
},
ticks: {
fontFamily: "'Open Sans Bold', sans-serif",
fontSize: 11
},
stacked: true
}]
},
legend: {
display: false
},
animation: {
onComplete: function () {
var chartInstance = this.chart;
var ctx = chartInstance.ctx;
ctx.textAlign = "left";
ctx.font = "9px Open Sans";
ctx.fillStyle = "#fff";
Chart.helpers.each(this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
Chart.helpers.each(meta.data.forEach(function (bar, index) {
data = dataset.data[index];
if (i == 0) {
ctx.fillText(data, 50, bar._model.y + 4);
} else {
ctx.fillText(data, bar._model.x - 25, bar._model.y + 4);
}
}), this)
}), this);
}
},
pointLabelFontFamily: "Quadon Extra Bold",
scaleFontFamily: "Quadon Extra Bold",
};
var ctx = document.getElementById("Chart1");
var myChart = new Chart(ctx, {
type: 'horizontalBar',
data: {
labels: ["2014", "2013", "2012", "2011"],
datasets: [{
data: [727, 589, 537, 543, 574],
backgroundColor: "rgba(63,103,126,1)",
hoverBackgroundColor: "rgba(50,90,100,1)"
}, {
data: [238, 553, 746, 884, 903],
backgroundColor: "rgba(163,103,126,1)",
hoverBackgroundColor: "rgba(140,85,100,1)"
}, {
data: [1238, 553, 746, 884, 903],
backgroundColor: "rgba(63,203,226,1)",
hoverBackgroundColor: "rgba(46,185,235,1)"
}]
},
options: barOptions_stacked,
})
});//end chat service.get
}
});
Instead of hard coding in the dataset which is what its doing now, is there a way I can use the data "ChartData" that I outlined in the beginning of the post?
I'm not sure how to do it or if its even possible.
In order to get the label to show in the bar, reference the labels property of $scope.ChartData:
ctx.fillText($scope.ChartData.labels[index] +" "+ data, 50, bar._model.y + 4);
Instead of hard coding in the dataset which is what its doing now, is there a way I can use the data "ChartData" that I outlined in the beginning of the post?
Use properties from the data variable (supplied by the ChartService get function) i.e. data.labels and data.data instead of the hard-coded values.
So update the labels line from:
labels: ["2014", "2013", "2012", "2011"],
To this:
labels: data.labels,
And similarly for the datasets:
datasets: [{
data: data.data,
So when creating the Chart it should look like this:
var myChart = new Chart(ctx, {
type: 'horizontalBar',
data: {
labels: data.labels,
datasets: [{
data: data.data,
backgroundColor: "rgba(63,103,126,1)",
hoverBackgroundColor: "rgba(50,90,100,1)"
}]
},
options: barOptions_stacked,
});
Expand the code snippet below for a demonstration.
Note: there is currently an issue with the resize-listener, blocked by a CORS issue - I am trying to find a way to disable that.
Update:
Per your comment about stacking the bars (horizontally) - yes that is possible. Just have one element in the datasets array for each item. One simple way to have this is to use Array.map() to create an array similar to the example:
var bgColors = [
"rgba(63,103,126,1)",
"rgba(50,90,100,1)"
];
var hoverBgColors = [
"rgba(50,90,100,1)",
"rgba(140,85,100,1)"
];
var datasets = data.data.map(function(value, index) {
return {
data: [value],
backgroundColor: bgColors[index],
hoverBackgroundColor: hoverBgColors[index]
}
});
Then use variable datasets when creating the Chart object:
var myChart = new Chart(ctx, {
type: 'horizontalBar',
data: {
labels: data.labels,
datasets: datasets
},
options: barOptions_stacked,
});
Also, there is some weird math going on for the location of the second label but the helper function can be updated like below (I tried dividing the x value by 75 but that may need to be adjusted - I am not sure what "appropriate" values are for that ...):
if (i == 0) {
ctx.fillText($scope.ChartData.labels[i] + " " + data, 50, bar._model.y + 4);
} else {
ctx.fillText($scope.ChartData.labels[i] + " " + data, (bar._model.x - 25) / 75, bar._model.y + 4);
}
var app = angular.module('myApp', []);
app.factory('ChartService', function() {
return { //dummy chart service
get: function(obj, callback) {
var data = {
"data": ["63", "38"],
"labels": ["Ford", "GM"]
};
callback(data);
}
};
});
app.controller('PieController', function($scope, ChartService) {
$scope.getBarChart = function() {
ChartService.get({
name: 'main'
}, function(data) {
$scope.ChartData = data;
var barOptions_stacked = {
tooltips: {
enabled: false
},
hover: {
animationDuration: 0
},
scales: {
xAxes: [{
ticks: {
beginAtZero: true,
fontFamily: "'Open Sans Bold', sans-serif",
fontSize: 11
},
scaleLabel: {
display: false
},
gridLines: {},
stacked: true
}],
yAxes: [{
gridLines: {
display: false,
color: "#fff",
zeroLineColor: "#fff",
zeroLineWidth: 0
},
ticks: {
fontFamily: "'Open Sans Bold', sans-serif",
fontSize: 11
},
stacked: true
}]
},
legend: {
display: false
},
animation: {
onComplete: function() {
var chartInstance = this.chart;
var ctx = chartInstance.ctx;
ctx.textAlign = "left";
ctx.font = "9px Open Sans";
ctx.fillStyle = "#fff";
Chart.helpers.each(this.data.datasets.forEach(function(dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
Chart.helpers.each(meta.data.forEach(function(bar, index) {
data = dataset.data[index];
if (i == 0) {
ctx.fillText($scope.ChartData.labels[i] + " " + data, 50, bar._model.y + 4);
} else {
ctx.fillText($scope.ChartData.labels[i] + " " + data, (bar._model.x - 25) / 75, bar._model.y + 4);
}
}), this)
}), this);
}
},
pointLabelFontFamily: "Quadon Extra Bold",
scaleFontFamily: "Quadon Extra Bold",
};
var ctx = document.getElementById("Chart1");
var bgColors = [
"rgba(63,103,126,1)",
"rgba(50,90,100,1)"
];
var hoverBgColors = [
"rgba(50,90,100,1)",
"rgba(140,85,100,1)"
];
var datasets = data.data.map(function(value, index) {
return {
data: [value],
backgroundColor: bgColors[index],
hoverBackgroundColor: hoverBgColors[index]
}
});
var myChart = new Chart(ctx, {
type: 'horizontalBar',
data: {
//use empty labels because the labels are on the bars
labels: data.labels.map(function() {
return '';
}),
datasets: datasets
},
options: barOptions_stacked,
})
}); //end chat service.get
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.3/Chart.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="PieController">
data {{ChartData}} //testing purposes
<div ng-init="getBarChart()">
<canvas id="Chart1"></canvas>

How Align the Legend Items in Chart.js 2?

I just need to align the Chart Legend so it don't look too messy as the default shows, here is an example what I'm trying to achieve:
Please give some code suggestions: https://jsfiddle.net/holp/68wf75r8/
new Chart(document.getElementById("field-0"), {
type: 'pie',
data: {
labels: ["Chat", "Prospeção", "Whatsapp", "Trial", "Site", "Telefone", "E-mail", "Evento"],
datasets: [{
data: [700, 400, 200, 150, 80, 50, 20, 10],
borderWidth: 2,
hoverBorderWidth: 10,
backgroundColor: pieColors,
hoverBackgroundColor: pieColors,
hoverBorderColor: pieColors,
borderColor: pieColors
}]
},
options: {
legend: {
labels: {
padding: 20
}
}
}
});
There is legend.labels.generateLabels hook you generally can use to customise your legend labels.
I found out, that you can put something like below to adjust Chart.js calculations.
generateLabels: function (chart) {
chart.legend.afterFit = function () {
var width = this.width; // guess you can play with this value to achieve needed layout
this.lineWidths = this.lineWidths.map(function(){return width;});
};
// here goes original or customized code of your generateLabels callback
}
Weird thing that there is no actual configuration option to achieve this.
Chartjs v2 creates an overhead to handle the legends. Basically what you are looking for is to leverage the usage of generateLabels.
The key point to bare in mind is that you need to return an valid array of legend objects.
This plunker describes the solution.
Main focus on this part:
generateLabels: (chart) => {
chart.legend.afterFit = function () {
var width = this.width;
console.log(this);
this.lineWidths = this.lineWidths.map( () => this.width-12 );
this.options.labels.padding = 30;
this.options.labels.boxWidth = 15;
};
var data = chart.data;
//https://github.com/chartjs/Chart.js/blob/1ef9fbf7a65763c13fa4bdf42bf4c68da852b1db/src/controllers/controller.doughnut.js
if (data.labels.length && data.datasets.length) {
return data.labels.map((label, i) => {
var meta = chart.getDatasetMeta(0);
var ds = data.datasets[0];
var arc = meta.data[i];
var custom = arc && arc.custom || {};
var getValueAtIndexOrDefault = this.getValueAtIndexOrDefault;
var arcOpts = chart.options.elements.arc;
var fill = custom.backgroundColor ? custom.backgroundColor : getValueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor);
var stroke = custom.borderColor ? custom.borderColor : getValueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor);
var bw = custom.borderWidth ? custom.borderWidth : getValueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth);
return {
text: label,
fillStyle: fill,
strokeStyle: stroke,
lineWidth: bw,
hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
// Extra data used for toggling the correct item
index: i
};
});
}
return [];
}
I tried to do as advised by the comments above. But to see that it is really difficult. It’s better and easier for me to set:
legend: {display: FALSE, ..} `, and then render the legend using html (angular, react, view .. another render template):
// part of angualr model class
public dataSets = [{
label: "New Deals",
backgroundColor: "#88B2FF",
data: [26, 15, 5],
},
{
label: "Active Deals",
backgroundColor: "#397FFF",
data: [7, 13, 22],
},
....
this.chart = new Chart(ctx, {
type: "roundedBar",
data: {
labels: this.xLabels,
datasets: this.dataSets,
},
<div style="width: 380px;height: 200px; display: inline-block;">
<canvas id="chart" aria-label="Hello ARIA World" role="img"></canvas>
</div>
<!-- This is angular template -->
<ul class="legend">
<li *ngFor="let set of dataSets">
<i [style.backgroundColor]="set.backgroundColor" class="icon"></i>
<label>
{{ set.label }}
</label>
</li>
</ul>
<style>
.legend {
display: flex;
text-align: center;
justify-content: space-between;
font-size: 10px;
line-height: 12px;
}
.icon {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
}
</style>

ChartJS New Lines '\n' in X axis Labels or Displaying More Information Around Chart or Tooltip with ChartJS V2

I'm using chart.js (V2) to try to build a bar chart that has more information available to user without having to hover over or click anywhere. I've provided two examples of how I hope to edit my chart.
Two edited versions of what I hope to achieve
As can be seen, I hope to place (somewhere), some extra information outside of the labels. I had hope that by adding '\n' to the labels I might have been able to get what I was looking for similar to option A.
Some edited code is provided blow:
var barChartData = {
labels: playerNames,
datasets: [{
label: 'Actual Score/Hour',
backgroundColor: "rgba(0, 128, 0,0.5)",
data: playerScores
}, {
label: 'Expected Score/Hour',
backgroundColor: "rgba(255,0,0,0.5)",
data: playerExpected
}]
};
function open_win(linktosite) {
window.open(linktosite)
}
canvas.onclick = function(evt){
var activePoints = myBar.getElementsAtEvent(evt);
console.log(activePoints);
linktosite = 'https://www.mytestsite.com/' + activePoints[1]['_model']['label'];
open_win(linktosite);
};
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
window.myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
title:{
display:true,
text:"Player Expected and Actual Score per Hour"
},
tooltips: {
mode: 'label'
},
responsive: true,
scales: {
xAxes: [{
stacked: false,
}],
yAxes: [{
stacked: false
}]
},
animation: {
onComplete: function () {
var ctx = this.chart.ctx;
ctx.textAlign = "center";
Chart.helpers.each(this.data.datasets.forEach(function (dataset) {
Chart.helpers.each(dataset.metaData.forEach(function (bar, index) {
// console.log("printing bar" + bar);
ctx.fillText(dataset.data[index], bar._model.x, bar._model.y - 10);
}),this)
}),this);
}
}
}
});
// Chart.helpers.each(myBar.getDatasetMeta(0).data, function(rectangle, index) {
// rectangle.draw = function() {
// myBar.chart.ctx.setLineDash([5, 5]);
// Chart.elements.Rectangle.prototype.draw.apply(this, arguments);
// }
// }, null);
};
At this point I'd be satisfied with having the extradata anywhere on the bar. Any help would be appreciated. Thanks~
Chart.js v2.1.5 allows for multi-line labels using nested arrays (v2.5.0 fixes it for radar graphs):
...
data: {
labels: [["Jake", "Active: 2 hrs", "Score: 1", "Expected: 127", "Attempts: 4"],
["Matt", "Active: 2 hrs", "Score: 4", "Expected: 36", "Attempts: 4"]],
...
However, this does mean that you will have to pre-calculate the label values.
var config = {
type: 'line',
data: {
labels: [["January","First Month","Jellyfish","30 of them"], ["February","Second Month","Foxes","20 of them"], ["March","Third Month","Mosquitoes","None of them"], "April", "May", "June", "July"],
datasets: [{
label: "My First dataset",
data: [65, 40, 80, 81, 56, 85, 45],
backgroundColor: "rgba(255,99,132,0.2)",
}, {
label: "My Second dataset",
data: [40, 80, 21, 56, 85, 45, 65],
backgroundColor: "rgba(99,255,132,0.2)",
}]
},
scales : {
xAxes : [{
gridLines : {
display : false,
lineWidth: 1,
zeroLineWidth: 1,
zeroLineColor: '#666666',
drawTicks: false
},
ticks: {
display:true,
stepSize: 0,
min: 0,
autoSkip: false,
fontSize: 11,
padding: 12
}
}],
yAxes: [{
ticks: {
padding: 5
},
gridLines : {
display : true,
lineWidth: 1,
zeroLineWidth: 2,
zeroLineColor: '#666666'
}
}]
},
spanGaps: true,
responsive: true,
maintainAspectRatio: true
};
var ctx = document.getElementById("myChart").getContext("2d");
new Chart(ctx, config);
<div class="myChart">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
<canvas id="myChart"></canvas>
</div>
If a label is an array as opposed to a string i.e. [["June","2015"], "July"] then each element is treated as a separate line. The appropriate calculations are made to determine the correct height and width, and rotation is still supported.
charJS version 2.7.2 used
this also works in https://github.com/jtblin/angular-chart.js
If you are using Chart.js v2.7.1, the above solution might not work.
The solution that actually worked for us was adding a small plugin right in the data and options level:
const config = {
type: 'bar',
data: {
// ...
},
options: {
// ...
},
plugins: [{
beforeInit: function (chart) {
chart.data.labels.forEach(function (label, index, labelsArr) {
if (/\n/.test(label)) {
labelsArr[index] = label.split(/\n/)
}
})
}
}]
};
A full description of how to fix this issue can be found here.
With Chart.js v2.1, you can write a chart plugin to do this
Preview
Script
Chart.pluginService.register({
beforeInit: function (chart) {
var hasWrappedTicks = chart.config.data.labels.some(function (label) {
return label.indexOf('\n') !== -1;
});
if (hasWrappedTicks) {
// figure out how many lines we need - use fontsize as the height of one line
var tickFontSize = Chart.helpers.getValueOrDefault(chart.options.scales.xAxes[0].ticks.fontSize, Chart.defaults.global.defaultFontSize);
var maxLines = chart.config.data.labels.reduce(function (maxLines, label) {
return Math.max(maxLines, label.split('\n').length);
}, 0);
var height = (tickFontSize + 2) * maxLines + (chart.options.scales.xAxes[0].ticks.padding || 0);
// insert a dummy box at the bottom - to reserve space for the labels
Chart.layoutService.addBox(chart, {
draw: Chart.helpers.noop,
isHorizontal: function () {
return true;
},
update: function () {
return {
height: this.height
};
},
height: height,
options: {
position: 'bottom',
fullWidth: 1,
}
});
// turn off x axis ticks since we are managing it ourselves
chart.options = Chart.helpers.configMerge(chart.options, {
scales: {
xAxes: [{
ticks: {
display: false,
// set the fontSize to 0 so that extra labels are not forced on the right side
fontSize: 0
}
}]
}
});
chart.hasWrappedTicks = {
tickFontSize: tickFontSize
};
}
},
afterDraw: function (chart) {
if (chart.hasWrappedTicks) {
// draw the labels and we are done!
chart.chart.ctx.save();
var tickFontSize = chart.hasWrappedTicks.tickFontSize;
var tickFontStyle = Chart.helpers.getValueOrDefault(chart.options.scales.xAxes[0].ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
var tickFontFamily = Chart.helpers.getValueOrDefault(chart.options.scales.xAxes[0].ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
var tickLabelFont = Chart.helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
chart.chart.ctx.font = tickLabelFont;
chart.chart.ctx.textAlign = 'center';
var tickFontColor = Chart.helpers.getValueOrDefault(chart.options.scales.xAxes[0].fontColor, Chart.defaults.global.defaultFontColor);
chart.chart.ctx.fillStyle = tickFontColor;
var meta = chart.getDatasetMeta(0);
var xScale = chart.scales[meta.xAxisID];
var yScale = chart.scales[meta.yAxisID];
chart.config.data.labels.forEach(function (label, i) {
label.split('\n').forEach(function (line, j) {
chart.chart.ctx.fillText(line, xScale.getPixelForTick(i + 0.5), (chart.options.scales.xAxes[0].ticks.padding || 0) + yScale.getPixelForValue(yScale.min) +
// move j lines down
j * (chart.hasWrappedTicks.tickFontSize + 2));
});
});
chart.chart.ctx.restore();
}
}
});
and then
...
data: {
labels: ["January\nFirst Month\nJellyfish\n30 of them", "February\nSecond Month\nFoxes\n20 of them", "March\nThird Month\nMosquitoes\nNone of them", "April", "May", "June", "July"],
...
Note - we assume that the maximum content of one line will fit between the ticks (i.e. that no rotation logic is needed. I'm sure it's possible to incorporate rotation logic too, but it would be a tad more complicated)
You should format the tooltips to not show the x axis label, or format it to show a shorter version of the label.
Fiddle - http://jsfiddle.net/m0q03wpy/

Categories

Resources