How to size/scale a chart in Chart.js - javascript

I use chart.js in my code. Basically, it works well, but I have an issue with creating nth time a chart in the same canvas. I got error message telling me that I suppose to destroy chart first.
Then I found here some ideas how to do this and adapted this part of code as a solution:
let chartStatus = Chart.getChart("line-chart");
if (chartStatus != undefined) {
chartStatus.destroy();
//$("div.line-chart").remove();
//$("div.line-chart").append('<canvas id="line-chart" style="width: 1221px; height: 280px;"></canvas>');
}
It works fine - at least I do not get any errors any more, but when I create chart for second and more times, it gets resized. Please look at the attached pictures:
If you look at the scale you notice it is changed.
My question is: How can I destroy a chart and recreate its size/scale etc correctly or how can I update a chart instead of destroying it?
The code looks like this:
javascript:
let chartStatus = Chart.getChart("line-chart");
if (chartStatus != undefined) {
chartStatus.destroy();
//$("div.line-chart").remove();
//$("div.line-chart").append('<canvas id="line-chart" style="width: 1221px; height: 280px;"></canvas>');
}
new Chart(document.getElementById("line-chart"), {
type: 'line',
data: {
labels: labelX,
datasets: [{
data: waga,
label: "Waga",
borderColor: "#3e95cd",
borderWidth: 1,
fill: false
}
]
},
options: {
title: {
display: true,
responsive: true,
maintainAspectRatio: false
}
}
});
HTML:
<div><canvas id="line-chart" style="width: 1221px; height: 280px;"></canvas></div>

I have found a solution that works for me here.
Now, my code looks like this:
let chartStatus = Chart.getChart("line-chart");
if (chartStatus != undefined) {
document.getElementById('line-chart').remove();
let canvas = document.createElement('canvas');
canvas.setAttribute('id','line-chart');
canvas.setAttribute('width','1221');
canvas.setAttribute('height','280');
document.querySelector("#line-chart-wrapper").appendChild(canvas);
}
and HTML part...
<div id="line-chart-wrapper"><canvas id="line-chart" style="width: 1221px; height: 280px;"></canvas></div>
Now chart looks OK no matter how many times it is created.

If you set the size of the canvas in css, it will be resized each time the chart is drawn, and the second time you lose its height completely.
The solution you propose - to size the canvas in code is less than ideal because chart.js draws the plot at its own devised size and then you just scale the resulted image to your desired size, which may look OK in your specific case, but in most cases you lose the quality of the result with possible overlapping items or lost resolution.
The standard way to have chart.js (re)draw the plot at your intended size is to have a div that includes only the canvas and set the css size of the div, not the canvas, together with the option maintainAspectRatio: false (which you already set). See also this section from the docs.
The following example, loosely based on your code excerpt, does that.
let labelX = [], waga = [];
function resetData(){
const d0 = Date.now() - 3*365*24*3600*1000*Math.random();
// change the whole arrays, since the chart is destroyed and rebuilt
labelX = Array.from({length: 10}, (_, i) =>
new Date(d0 + i * 24 * 3600 * 1000).toLocaleDateString(undefined,
{year: 'numeric', month: 'numeric', day: 'numeric'}));
waga = Array.from({length: 10}, (_, i) => Math.random());
}
let chart;
function drawChart(){
chart = new Chart(document.getElementById("line-chart"), {
type: 'line',
data: {
labels: labelX,
datasets: [{
data: waga,
label: "Waga",
borderColor: "#3e95cd",
borderWidth: 1,
fill: false
}]
},
options: {
title: {
display: true,
responsive: true,
maintainAspectRatio: false
},
scales: {
x: {
bounds: 'ticks',
type: 'category',
},
y: {
type: 'linear',
display: true,
min: 0,
max: 1
}
}
}
});
}
resetData();
drawChart();
document.getElementById('reset').onclick = function(){
resetData();
document.querySelector('canvas#line-chart').innerHTML = '';
chart.destroy();
drawChart();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.1.2/chart.umd.js"
integrity="sha512-t41WshQCxr9T3SWH3DBZoDnAT9gfVLtQS+NKO60fdAwScoB37rXtdxT/oKe986G0BFnP4mtGzXxuYpHrMoMJLA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="line-chart-container" style="height:300px; width:1000px; border: 1px solid red">
<canvas id="line-chart"></canvas>
</div>
<button id="reset">Reset</button>
Alternatively (with the same setting for styling the div not the canvas), the more efficient solution would be to just change the data and then call chart.update, if it fits your purpose, like this:
const labelX = [], waga = [];
function resetData(){
const d0 = Date.now() - 3*365*24*3600*1000*Math.random();
// using splice 0 to infinity to replace all data *inside* the arrays
labelX.splice(0, 1/0, ...Array.from({length: 10}, (_, i) =>
new Date(d0 + i * 24 * 3600 * 1000).toLocaleDateString(undefined,
{year: 'numeric', month: 'numeric', day: 'numeric'})));
waga.splice(0, 1/0, ...Array.from({length: 10}, (_, i) => Math.random()));
}
resetData();
const chart = new Chart(document.getElementById("line-chart"), {
type: 'line',
data: {
labels: labelX,
datasets: [{
data: waga,
label: "Waga",
borderColor: "#3e95cd",
borderWidth: 1,
fill: false
}]
},
options: {
title: {
display: true,
responsive: true,
maintainAspectRatio: false
},
scales: {
x: {
bounds: 'ticks',
type: 'category',
},
y: {
type: 'linear',
display: true,
min: 0,
max: 1
}
}
}
});
document.getElementById('reset').onclick = function(){
resetData();
chart.update();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.1.2/chart.umd.js"
integrity="sha512-t41WshQCxr9T3SWH3DBZoDnAT9gfVLtQS+NKO60fdAwScoB37rXtdxT/oKe986G0BFnP4mtGzXxuYpHrMoMJLA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<body>
<div id="line-chart-container" style="height:300px; width:1000px; border: 1px solid red">
<canvas id="line-chart"></canvas>
</div>
<button id="reset">Reset</button>
</body>

Related

Category on chartjs graph shows undefined

// Get a reference to the form element
const form = document.querySelector('form');
// Get a reference to the canvas element
const canvas = document.querySelector('#myChart');
const chart2Canvas = document.querySelector('#chart2');
console.log(chart2Canvas)
// Declare variables that will hold the input values
let homeworkHours = 0;
let extraStudyHours = 0;
let hobbiesHours = 0;
let sportsHours = 0;
let socialActivityHours = 0;
let breaksHours = 0;
// Add an event listener to the form to listen for submit events
form.addEventListener('submit', (event) => {
// Prevent the default form submission behavior
event.preventDefault();
// Get the values of the form inputs
const homeworkInput = document.querySelector('#homework');
const extraStudyInput = document.querySelector('#extra-study');
const hobbiesInput = document.querySelector('#hobbies');
const sportsInput = document.querySelector('#sports');
const socialActivityInput = document.querySelector('#social-activity');
const breaksInput = document.querySelector('#breaks');
// Convert the input values to numbers
homeworkHours = parseInt(homeworkInput.value, 10);
extraStudyHours = parseInt(extraStudyInput.value, 10);
hobbiesHours = parseInt(hobbiesInput.value, 10);
sportsHours = parseInt(sportsInput.value, 10);
socialActivityHours = parseInt(socialActivityInput.value, 10);
breaksHours = parseInt(breaksInput.value, 10);
// Calculate the total number of hours spent on activities
const totalHours = homeworkHours + extraStudyHours + hobbiesHours + sportsHours + socialActivityHours + breaksHours;
// Create a chart using the Chart.js library
const chart = new Chart(canvas, {
type: 'pie',
data: {
labels: ['Homework', 'Extra Study', 'Hobbies', 'Sports', 'Social Activities', 'Breaks'],
datasets: [{
data: [homeworkHours, extraStudyHours, hobbiesHours, sportsHours, socialActivityHours, breaksHours],
backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#00FF00', '#0000FF', '#FFFF00'],
hoverBackgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#00FF00', '#0000FF', '#FFFF00'],
}],
},
options: {
responsive: false,
maintainAspectRatio: false,
aspectRatio: 1.5/1, // width:height
title: {
display: true,
text: `Total Hours: ${totalHours}`,
},
},
});
const chart2 = new Chart(chart2Canvas, {
type: 'bar',
data: {
labels: ['Homework', 'Extra Study', 'Hobbies', 'Sports', 'Social Activities', 'Breaks'],
datasets: [{
data: [homeworkHours, extraStudyHours, hobbiesHours, sportsHours, socialActivityHours, breaksHours],
backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#E7E9ED', '#F9CB9C']
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
aspectRatio: 1.5/1,
title: {
display: true,
text: 'Hours Spent on Activities'
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
The Homework category for the bar chart keeps showing an undefined category even though it is defined. Not sure what the problem is, however, is it something to do with the scope of the variables? I've ran my code through console.log and there does not seem to be any referencing mistakes.
My HTML code is here in case there is a problem with it.
<canvas id="myChart" width="400" height="400"></canvas>
<canvas id="chart2" width="400" height="400"></canvas>
<script src="dashboard.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
I've tried running the canvas id through console.log on JS to see if there is a referencing mistake. I've tried to think of other reasons as to why it may not work, such as trying to access the elements before the DOM has finished loading, or is it something to do with the global and local variables in my program?

How to start line series from start of y axis in bar chart - chart.js

We have a new requirement from the business related to chart.js.
Consider the below image where we have bar and line in a single chart. The line chart only has only few points in the chart.
In the application user has the feasiblity to select particular week range which he wantes to get displayed. Suppose if user selected “w16/22” to “w28/22”. As we have one point before “w16/22” i.e. on “w14/22” we need to draw a line chart from y-axis to the first point on the chart i.e. on “w18/22”. Then this is connected to the second point and so on.
Please find the image below for more detailed view of the expected output.
In chart.js is it practically possible to get this output without plotting any value on starting of the y-axis or do we need to approach any other way to get this expected output.
Kindly help me in getting a solution for this issue.
I'm not 100% sure if this works for your specific chart, that said you could simply set the min and max values from the x axes. (For more details here is the link to the documentation).
Like in this example:
/** DEMO TESTDATA START **/
const labels = [
'w01/22', 'w02/22', 'w03/22', 'w04/22',
'w05/22', 'w06/22', 'w07/22', 'w08/22',
'w09/22', 'w10/22', 'w11/22'
];
const clampedLabels = labels.slice(1, labels.length -2)
const dataSet1 = [50, 40, 10, 100, 90 ,60, 20, 10, 100, 90, 90];
const dataSet2 = [... dataSet1]
.map( (n,i) => {
return {y: n, x: labels[i]}
})
.filter((n,i) => i % 2 == 0);
/** DEMO TESTDATA END **/
const data = {
labels: labels,
datasets: [{
label: 'Bar Chart',
data: dataSet1,
backgroundColor: '#0000ff',
order: 1
}, {
label: 'Line Chart',
data: dataSet2,
backgroundColor: '#ff0000',
borderColor: '#ff0000',
type: 'line',
order: 0
}]
};
const config = {
type: 'bar',
data: data,
options: {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
text: 'Unclamped',
display: true
}
}
}
};
const data2 = {
labels: labels,
datasets: [{
label: 'Bar Chart',
data: dataSet1,
backgroundColor: '#0000ff',
order: 1,
fill: false,
}, {
label: 'Line Chart',
data: dataSet2,
backgroundColor: '#ff0000',
borderColor: '#ff0000',
type: 'line',
}]
};
const config2 = {
type: 'bar',
data: data2,
options: {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
text: 'Clamped from W02/22 - W08/22',
display: true,
}
},
scales: {
x: {
min: clampedLabels[0],
max: clampedLabels[clampedLabels.length-2],
}
}
}
};
new Chart( document.getElementById('chart'), config );
new Chart( document.getElementById('chart2'), config2 );
<script src="//cdn.jsdelivr.net/npm/chart.js"></script>
<div class="chart" style="width:500px;">
<canvas id="chart2" ></canvas>
</div>
<div class="chart" style="width:500px;">
<canvas id="chart" ></canvas>
</div>
Your requirement can be achieved by changing the minimum property value of the x-axis. Initially, we have rendered the chart like in the image. We can render the line chart from the start of the y-axis by setting the minimum value of the x-axis based on your requirement. We have created a simple angular application to demonstrate the same. Please find the below stackblitz link for your reference.
Sample link: https://stackblitz.com/edit/angular-a3xvsv-2iblmd?file=app.component.ts
Code Snippet:
<button (click)="changeMinValue($event)">Change value</button>
<ejs-chart style='display:block;' #chart [chartArea]='chartArea' [width]='width' align='center' id='chartcontainer' [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis' [zoomSettings]='zoom'
[tooltip]='tooltip' (load)='load($event)'>
<e-series-collection>
<e-series [dataSource]='data' fill="red" type='StackingColumn' xName='x' yName='y' width=2> </e-series>
<e-series [dataSource]='data' fill="yellow" type='StackingColumn' xName='x' yName='y1' width=2 > </e-series>
<e-series [dataSource]='data' fill="blue" type='StackingColumn' xName='x' yName='y2' width=2 > </e-series>
<e-series [dataSource]='data' fill="green" type='StackingColumn' xName='x' yName='y3' width=2 > </e-series>
<e-series [dataSource]='data1' fill="red" type='Line' xName='x' yName='y' width=3 [marker]='marker'> </e-series>
</e-series-collection>
</ejs-chart>
public changeMinValue(e: Event): void {
this.chart.primaryXAxis.minimum = 3;
this.chart.refresh();
}
Screenshot:
Initial rendering:
After clicking on the button:

Chart.js identification of min and max value before rendering

I am using chart.js for my project. As you can see in the following code, chart.js uses different minimum and maximum values for y-axis for line and bar graphs for same data. In the given code, 1100-1700 is the value range for line graph whereas 0-2000 is the value range used for bar graph.
I like to make same minimum and maximum values for both line and bar graphs. One option is to find minimum, maximum and step-size values my own and use the properties min, max and ticks.stepSize under scales. But finding minimum, maximum and step-size for a given data data is a serious task.
For my requirement, default minimum ,maximum and stepSize used by chart.js for line graph is fine and I would like to use the same values for bar graph also.
I could first render line graph, get these values from this graph (like scales.y.min) and then use it for bar graph, which is perfectly working
Is there a way I could get the default min, max, stepSize values used by chart.js for line graph before actually drawing the graph? Any answers or pointers are really appreciated. Thanks
var chart1 = new Chart(document.getElementById('chart1'),
{
type: 'line',
data:
{
labels: ['a','b','c'],
datasets:
[
{
label: 'A',
data: [1212,1122, 1188, 1617, 1116],
borderColor: 'green',
}
]
}
});
var chart2 = new Chart(document.getElementById('chart2'),
{
type: 'bar',
data:
{
labels: ['a','b','c'],
datasets:
[
{
label: 'A',
data: [1212,1122, 1188, 1617, 1116],
backgroundColor: 'green',
}
]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<div style="width:300px;height:200px"><canvas id="chart1"></canvas></div>
<div style="width:300px;height:200px"><canvas id="chart2"></canvas></div>
The first thing you could do is to set beginAtZero to false to Y axis of bar chart because is set to true by default:
https://github.com/chartjs/Chart.js/blob/v3.9.1/src/controllers/controller.bar.js#L636-L650
Then you can set the same scale config to both charts.
options: { // options of charts
scales: {
y: {
beginAtZero: false,
}
}
}
var chart1 = new Chart(document.getElementById('myChart1'),
{
type: 'line',
data:
{
labels: ['a','b','c'],
datasets:
[
{
label: 'A',
data: [1212,1122, 1188, 1617, 1116],
borderColor: 'green',
}
]
},
options: {
scales: {
y: {
beginAtZero: false,
}
}
}
});
var chart2 = new Chart(document.getElementById('myChart2'),
{
type: 'bar',
data:
{
labels: ['a','b','c'],
datasets:
[
{
label: 'A',
data: [1212,1122, 1188, 1617, 1116],
backgroundColor: 'green',
}
]
},
options: {
scales: {
y: {
beginAtZero: false,
}
}
}
});
.myChartDiv {
max-width: 300px;
max-height: 200px;
}
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.9.1/dist/chart.min.js"></script>
<html>
<body>
<div class="myChartDiv">
<canvas id="myChart1" width="300" height="200"/>
</div>
<div class="myChartDiv">
<canvas id="myChart2" width="300" height="200"/>
</div>
</body>
</html>

How to get radar chart coordinates using getValueForDistanceFromCenter with Chart.js?

I am experimenting with Chart.js to build radar charts. I mastered the basics (see basic chart below), but I would like to use the x y coordinates of the graph to place texts directly on the canvas.
After some digging, I found out that it is not possible to use getValueForPixel or getPixelForTick in a radar chart. See this github issue. In the connecting thread, a new method getValueForDistanceFromCenter is introduced.
As I understand it, it would be possible to calculate the distance from the center with this method, and use it to get coordinates. I searched the Chart.js documentation and other sites, but cannot find any code examples or information on how to implement this.
Can somebody point me in the right direction how to implement the method in the code?
var data = {
labels: ["Ball Skills", "Shooting", "Physical"],
datasets: [{
label: [`ikke`, `jij`],
backgroundColor: "rgba(38,120,255,0.2)",
borderColor: "rgba(38,120,255, 1)",
data: [90, 90, 90]
}]
};
var options = {
responsive: true,
tooltips: false,
title: {
text: 'Basic example',
display: true,
position: `bottom`,
},
scale: {
angleLines: {
display: true
},
ticks: {
suggestedMin: 0,
suggestedMax: 100,
stepSize: 25,
maxTicksLimit: 11,
display: false,
}
},
legend: {
labels: {
padding: 10,
fontSize: 14,
lineHeight: 30,
},
},
};
var myChart = new Chart(document.getElementById("chart"), {
type: 'radar',
data: data,
options: options
});
The radialLinear scale (in version 2.9.4 that I have seen your are using version 2) there is the method getValueForDistanceFromCenter(value) to get the distance from center but there is another method getPointPositionForValue(index, value) which can provide you the point at a specif index of your data.
To use them and to draw what you want on chart using those points, you need to implement a plugin.
In the below snippet, I'm drawing a rect between the points at a specific value.
const ctx = document.getElementById("myChart");
const data = {
labels: ["Ball Skills", "Shooting", "Physical"],
datasets: [{
label: [`ikke`, `jij`],
backgroundColor: "rgba(38,120,255,0.2)",
borderColor: "rgba(38,120,255, 1)",
data: [50, 50, 50]
}]
};
const options = {
responsive: true,
tooltips: false,
title: {
text: 'Basic example',
display: true,
position: `bottom`,
},
scale: {
angleLines: {
display: true
},
ticks: {
suggestedMin: 0,
suggestedMax: 100,
stepSize: 25,
maxTicksLimit: 11,
display: false,
}
},
legend: {
labels: {
padding: 10,
fontSize: 14,
lineHeight: 30,
},
},
};
const plugin = {
id: 'getDistance',
afterDraw(chart) {
const c = chart.ctx;
const rScale = chart.scale;
c.save();
chart.data.datasets[0].data.forEach(function(item, index) {
const point = rScale.getPointPositionForValue(0.5 + index, 50);
c.beginPath();
c.fillStyle = 'red';
c.fillRect(point.x - 5, point.y - 5, 10, 10);
c.fill();
});
c.restore();
}
};
const myChart = new Chart(ctx, {
type: 'radar',
plugins: [plugin],
data: data,
options: options
});
.myChartDiv {
max-width: 600px;
max-height: 400px;
}
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.9.4/dist/Chart.min.js"></script>
<html>
<body>
<div class="myChartDiv">
<canvas id="myChart" width="600" height="400"/>
</div>
</body>
</html>

Make Chart.js horizontal bar labels multi-line

Just wondering if there is any way to set the horizontal bar labels for y-axis using chart.js. Here is how I set up the chart:
<div class="box-body">
<canvas id="chart" style="position: relative; height: 300px;"></canvas>
</div>
Javascript:
var ctx = document.getElementById('chart').getContext("2d");
var options = {
layout: {
padding: {
top: 5,
}
},
responsive: true,
animation: {
animateScale: true,
animateRotate: true
},
};
var opt = {
type: "horizontalBar",
data: {
labels: label,
datasets: [{
data: price,
}]
},
options: options
};
if (chart) chart.destroy();
chart= new Chart(ctx, opt);
chart.update();
As you all can see, the first and third labels are too long and cut off. Is there a way to make the label multi-line?
If you want to have full control over how long labels are broken down across lines you can specify the breaking point by providing labels in a nested array. For example:
var chart = new Chart(ctx, {
...
data: {
labels: [["Label1 Line1:","Label1 Line2"],["Label2 Line1","Label2 Line2"]],
datasets: [{
...
});
You can use the following chart plugin :
plugins: [{
beforeInit: function(chart) {
chart.data.labels.forEach(function(e, i, a) {
if (/\n/.test(e)) {
a[i] = e.split(/\n/);
}
});
}
}]
add this followed by your chart options
ᴜꜱᴀɢᴇ :
add a new line character (\n) to your label, wherever you wish to add a line break.
ᴅᴇᴍᴏ
var chart = new Chart(ctx, {
type: 'horizontalBar',
data: {
labels: ['Jan\n2017', 'Feb', 'Mar', 'Apr'],
datasets: [{
label: 'BAR',
data: [1, 2, 3, 4],
backgroundColor: 'rgba(0, 119, 290, 0.7)'
}]
},
options: {
scales: {
xAxes: [{
ticks: {
beginAtZero: true
}
}]
}
},
plugins: [{
beforeInit: function(chart) {
chart.data.labels.forEach(function(e, i, a) {
if (/\n/.test(e)) {
a[i] = e.split(/\n/);
}
});
}
}]
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="ctx"></canvas>

Categories