Related
I'm currently struggling with building a web frontend with a responsive chart, which should be filled with live data while the user is visiting the application.
My goal was to have the x-axis always scale automatically to perfectly cover the data. Then I found the bounds attribute of Chart.js, which when set to 'data' should do exactly what I want.
As you can see in this example, the whole thing only works well for the first few datasets - from x-values > 5 onwards, the scaling of the x-axis somehow gets messed up.
Did anyone else have this problem before? Did I assign other attributes incorrectly?
Here's my JavaScript code:
var data = [];
var graph = new Chart("chart", {
type: 'scatter',
data: {
datasets: [{
data: data,
pointRadius: 0,
borderWidth: 2,
pointHitRadius: 4,
pointHoverRadius: 6,
tension: 0,
showLine: true,
xAxisID: "xAxis",
yAxisID: "yAxis",
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: false,
},
},
interaction: {
mode: 'nearest',
intersect: true,
},
scales: {
xAxis: {
type: 'linear',
position: 'bottom',
title: {
display: true,
text: 't (s)',
color: "black",
font: {
size: 15
}
},
ticks: {
color: "black",
callback: (value, index, values) => (index == (values.length - 1)) ? "" : value,
},
bounds: 'data'
},
yAxis: {
type: 'linear',
position: 'left',
ticks: {
color: "black",
maxTicksLimit: 7,
}
}
}
}
});
var x = 0;
setInterval(function() {
var newData = {
x: Math.round(x * 100) / 100,
y: Math.round(x * 100) / 100
};
data.push(newData);
graph.update();
x += 0.1;
}, 100);
I'm trying to create a Chart with Chart.js. I Got everything to work as I want it to, only thing left is that the Animation always floats from the bottom up into position. I just want a Fade-In animation, but couldn't find anything on how to do this.
Is it even possible to do a Fade Animation in Chart.js? Here is my Code so far:
#code
EDIT:
I got v3 to work, but a function, I created for this Chart doesn't work properly now. I start the Chart Animation 1s after its scrolled in view. By that time the Line chart is hidden and only the bar charts appear. After another 1.5s the Line is supposed to fade in on its spot.
I used to do this by giving the Line hidden at first and after a delay unhide it and update the chart. Updating the Chart now resets the Bar animation too. In v2 it didn't do this. Is there a way to play the animations shortly after another?
Also due to the Update the Plugin Datalabels doesn't work anymore. Is there a alternative in v3?
Here is my Code again:
<div class="chartcontainer">
<div class="chartcontainer--inner">
<script src="https://unpkg.com/chart.js#3.0.0-beta/dist/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-deferred"></script>
<style type="text/css">
#media screen and (min-width: 1100px) {
#myChart {min-height: 540px}
}
.chartcontainer {max-width: 67.625rem; margin-right: auto; margin-left: auto; overflow: hidden;}
#media screen and (max-width: 700px){
.chartcontainer--inner {width: 50rem;}
.chartcontainer {overflow: scroll;}
}
</style>
<canvas id="myChart"></canvas>
<script type="text/javascript">
var inView = false;
function isScrolledIntoView(elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return ((elemTop <= docViewBottom) && (elemBottom >= docViewTop));
}
$(window).scroll(function() {
if (isScrolledIntoView('#myChart') && !inView) {
// if (inView) { return; }
inView = true;
console.log('in View: '+ inView);
setTimeout(function(){
var ctx = document.getElementById('myChart').getContext('2d');
myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['2018', '2019', '2020'],
datasets: [{
label: ['a'],
data: [7.1, 6.6, 6.6],
backgroundColor: [
'#05368b',
'#05368b',
'#05368b'
],
order: 2
},{
label: ['b'],
data: [6.5, 6.4, 6.7],
backgroundColor: [
'#1792d7',
'#1792d7',
'#1792d7'
],
order: 2
},{
label: ['c'],
data: [6.1, 5.8, 6.1],
backgroundColor: [
'#6c2475',
'#6c2475',
'#6c2475'
],
order: 2
},{
label: ['d'],
data: [5.7, 5.8, 5.8],
backgroundColor: [
'#0d60b5',
'#0d60b5',
'#0d60b5'
],
order: 2
},{
label: ['e'],
data: [4.3, 6.1, 6.3],
type: 'line',
lineTension: 0.4,
borderColor: "#f48d3b",
fill: false,
pointBackgroundColor: function() {
var color = "rgba(244, 141, 59, 1)";
console.log(color);
return color;
},
pointRadius: function(context) {
var width = context.chart.width;
var size = Math.round(width / 42);
console.log(size);
return size
},
borderWidth: 1,
order: 1,
hidden: true,
borderWidth: 5
}]
},
options: {
tooltips: {enabled: false},
hover: {mode: null},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
color: '#eee'
}
}],
xAxes: [{
gridLines: {
color: '#eee'
}
}]
},
legend: {
position: 'bottom',
labels: {
padding: 20
}
},
plugins: {
datalabels: {
align: 'top',
anchor: 'start',
color: 'white',
// offset: 6,
font: function(context) {
var width = context.chart.width;
var size = Math.round(width / 42);
return {
size: size
};
}
}
}
}
});
}, 1000);
setTimeout(function(){
console.log(myChart.config.data.datasets[4].hidden);
myChart.config.data.datasets[4].hidden = false;
myChart.config.options.animation = {numbers: { duration: 0 },colors: {type: "color",duration: 1000,from: "transparent",}};
myChart.update();
}, 2500);
} else {
// inView = false;
}
});
</script>
</div>
</div>
For Chart.js v3 you can use the following animation options:
chart.options.animation = {
numbers: { duration: 0 },
colors: {
type: "color",
duration: 1000,
from: "transparent",
}
}
For v2 I don't think they provide any fine-grain control over animations. If this is important to you, you might want to update to v3.
Snippet attached to show the effect.
var ctx = document.getElementById('myChart').getContext('2d');
myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['2018', '2019', '2020'],
datasets: [{
label: ['x'],
data: [7.1, 6.6, 6.6],
backgroundColor: [
'#05368b',
'#05368b',
'#05368b'
],
order: 2
},{
label: ['y'],
data: [6.5, 6.4, 6.7],
backgroundColor: [
'#1792d7',
'#1792d7',
'#1792d7'
],
order: 2
},{
label: ['z'],
data: [6.1, 5.8, 6.1],
backgroundColor: [
'#6c2475',
'#6c2475',
'#6c2475'
],
order: 2
},{
label: ['n'],
data: [5.7, 5.8, 5.8],
backgroundColor: [
'#0d60b5',
'#0d60b5',
'#0d60b5'
],
order: 2
},{
label: ['av'],
data: [4.3, 6.1, 6.3],
type: 'line',
lineTension: 0.4,
borderColor: "#f48d3b",
fill: false,
pointBackgroundColor: function() {
var color = "rgba(244, 141, 59, 1)";
console.log(color);
return color;
},
pointRadius: function(context) {
var width = context.chart.width;
var size = Math.round(width / 42);
console.log(size);
return size
},
borderWidth: 1,
order: 1,
hidden: true,
borderWidth: 5
}]
},
options: {
animation: {
numbers: { duration: 0 },
colors: {
type: "color",
duration: 1000,
from: "transparent",
},
},
tooltips: {enabled: false},
hover: {mode: null},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
},
gridLines: {
color: '#eee'
}
}],
xAxes: [{
gridLines: {
color: '#eee'
}
}]
},
legend: {
position: 'bottom',
labels: {
padding: 20
}
},
plugins: {
datalabels: {
align: 'top',
anchor: 'start',
color: 'white',
// offset: 6,
font: function(context) {
var width = context.chart.width;
var size = Math.round(width / 42);
return {
size: size
};
}
}
}
}
});
<script src="https://unpkg.com/chart.js#3.0.0-beta/dist/chart.min.js"></script>
<canvas id="myChart"></canvas>
I Got it to work as intended! I went back to v2, because v3 brought too much trouble with it.
While tinkering with v3 I realized that my v2 Version worked, because Updating in v2 doesn't reset the Animation. So my answer is based on that. If you call .update(0) the update happens without the Animation. So I crated this:
setTimeout(function(){
myChart.config.data.datasets[4].hidden = false;
myChart.update(0);
fadeLoop();
}, 2500);
var i = 0.15;
function fadeLoop() {
setTimeout(function() {
myChart.config.data.datasets[4].pointBackgroundColor = "rgba(244, 141, 59, " + i +")";
myChart.config.data.datasets[4].borderColor = "rgba(244, 141, 59, " + i +")";
myChart.update(0);
i += 0.05;
i = Math.round(i * 100) / 100;
if (i <= 1) fadeLoop();
}, 10)
}
Since my Line Fade-in Animation was supposed to start after the Bar animation I initialized the with the hidden attribute. After 2.5 sec I removed the hidden attribute and started the FadeLoop.
I count i from 0.15 to 1 in 0.05 increments every 0.01 sec. In the Loop I Updated the BG and Border Color of my Line and Updated it "without animation".
This is not the most efficient Method, and I don't recommend it for larger Charts or slow sites. But for me this is sufficient.
I'm using Charts.js library to display a line graph from data which is constantly updated. Is there a way to display only the final point value at all times? I want it to do this to monitor the added values at all times.
javascript code:
var config2 = {
type: 'line',
data: {
labels: 0,
datasets: [{
label: "No selection",
lineTension: 0,
borderColor: "rgba(222, 44, 31)",
pointBackgroundColor: "#fff675",
fill: false,
data: 0,
}
]
},
options: {
responsive: true,
title: {
display: false,
text: 'Chart.js Time Point Data'
},
scales: {
x: {
type: 'time',
display: true,
scaleLabel: {
display: true,
labelString: 'Date'
},
ticks: {
major: {
enabled: true
},
fontStyle: function(context) {
return context.tick && context.tick.major ? 'bold' : undefined;
},
fontColor: function(context) {
return context.tick && context.tick.major ? '#FF0000' : undefined;
}
}
},
y: {
display: true,
scaleLabel: {
display: true,
labelString: 'value'
}
}
}
}
};
Each time, a new value is available, you can simply remove outdated labels and dataset.data values once a certain limit is reached. This can be done using Array.shift(), which removes the first element from an array. Once these array are updated, you need to invoke chart.update().
var maxValues = 4;
setInterval(() => {
chart.data.labels.push(new Date());
chart.data.datasets[0].data.push(Math.floor((Math.random() * 20) + 1));
if (chart.data.labels.length > maxValues) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
chart.update();
}, 1000);
For displaying the value on the last added data point, you can use the Plugin Core API. It offers different hooks that may be used for executing custom code. In below runnable code snippet, I use the afterDraw hook to draw text directly on the canvas.
var chart = new Chart('chart', {
type: "line",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
var iLastValue = chart.data.labels.length - 1;
var lastValue = chart.data.datasets[0].data[iLastValue];
var x = xAxis.getPixelForValue(chart.data.labels[iLastValue]);
var y = yAxis.getPixelForValue(lastValue);
ctx.save();
ctx.textAlign = 'center';
ctx.font = '14px Arial';
ctx.fillStyle = "red";
ctx.fillText('Value: ' + lastValue, x, y - 15);
ctx.restore();
}
}],
responsive: true,
maintainAspectRatio: false,
data: {
labels: [],
datasets: [{
label: "Data",
data: [],
fill: false,
lineTension: 0,
backgroundColor: "white",
borderColor: "red",
}]
},
options: {
layout: {
padding: {
right: 50
}
},
scales: {
xAxes: [{
type: 'time',
ticks: {
source: 'auto'
},
time: {
unit: 'second',
displayFormats: {
second: 'mm:ss'
},
tooltipFormat: 'mm:ss'
},
}],
yAxes: [{
ticks: {
min: 0,
max: 20,
stepSize: 5
}
}]
}
}
});
var maxValues = 4;
setInterval(() => {
chart.data.labels.push(new Date());
chart.data.datasets[0].data.push(Math.floor((Math.random() * 20) + 1));
if (chart.data.labels.length > maxValues) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
chart.update();
}, 1000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<canvas id="chart" height="90"></canvas>
This is how options for my chart look like
options: {
legend: {
display: false
},
scales: {
xAxes: [
{
type: "time",
gridLines: {
display: false
},
ticks: {
padding: 15
}
}
],
yAxes: [
{
ticks: {
padding: 22,
min: 1,
max: 5,
stepSize: 1
},
position: "left",
gridLines: {
drawTicks: false
}
}
]
}
}
as you can see there is an option
gridLines: {
display: false
},
because of this there is no vertical lines on the chart.
What I want to do is to add only one vertical line on the end of the chart(right side of the chart)
Here is my live example https://codesandbox.io/s/line-chart-with-vanilla-js-and-chartjs-pi9fn?file=/src/index.js
This can be solved as follows:
Define data.labels in the chart configuration. Given your data, this could be done by extracting the x values of of the data objects using Array.map().
const labels = data.map(o => o.x);
Then you need to adapt xAxis.ticks in order to instruct Chart.js that ticks (including grid lines) have to be generated from user given labels (see Tick Source from Chart.js documentation).
ticks: {
source: 'labels'
}
Further you have to change the definition of xAxis.gridLines. Make them displayed again and define lineWidth instead. This should be an array of numbers that specify the stroke width of individual grid lines. Basically all 0 except 1 for the last number.
gridLines: {
lineWidth: labels.map((l, i) => i < labels.length - 1 ? 0 : 1)
}
Please have a look at the runnable code snippet below.
const data = [
{ x: "1-01", y: 1 },
{ x: "1-07", y: 3 },
{ x: "1-14", y: 2 },
{ x: "1-21", y: 4 },
{ x: "1-28", y: 5 },
{ x: "2-04", y: 1 }
];
const labels = data.map(o => o.x);
new Chart('line-chart', {
type: "scatter",
responsive: true,
maintainAspectRatio: false,
data: {
labels: labels,
datasets: [
{
data: data,
label: "A",
showLine: true,
fill: false,
borderWidth: 4,
pointRadius: 5,
tension: 0,
backgroundColor: "#ffffff",
borderColor: "red"
}
]
},
options: {
legend: {
display: false
},
scales: {
xAxes: [
{
type: 'time',
unit: 'day',
time: {
parser: 'M-D'
},
gridLines: {
lineWidth: labels.map((l, i) => i < labels.length - 1 ? 0 : 1)
},
ticks: {
source: 'labels',
padding: 15
}
}
],
yAxes: [
{
ticks: {
padding: 22,
min: 1,
max: 5,
stepSize: 1
},
position: "left",
gridLines: {
drawTicks: false
}
}
]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<canvas id="line-chart" height="90"></canvas>
I am trying to achieve something like this using chart.js. I wanted to show data of male/female according to each age group:
Here is my chart options:
var options = {
layout: {
padding: {
top: 5,
}
},
scales:
{
yAxes: [{
display: true,
barPercentage: 0.4,
ticks: {
fontSize: 12
},
stacked: true,
}],
xAxes: [{
stacked: true,
}]
},
responsive: true,
maintainAspectRatio: false,
legend: {
display: false,
},
animation: {
animateScale: true,
animateRotate: true
},
};
var opt = {
type: "horizontalBar",
data: {
labels: ageGroup,
datasets: [{
label: 'Male',
data: maleData,
backgroundColor: '#2196F3',
hoverBackgroundColor: '#2196F3'
},
{
label: 'Female',
data: femaleData,
backgroundColor: '#E91E63',
hoverBackgroundColor: '#E91E63'
}]
},
options: options
};
I changed the positive in femaleData array into a negative number to achieve the result above:
for (var i = 0; i < femaleData.length; i++) {
femaleData[i] = -Math.abs(femaleData[i]);
}
However, the y-axis at 0 is not centralized as it pushed to the right hand side since left hand side got more data. I not even sure if this is the correct way to set the chart in opposite direction. How can I do this correctly?
as per the requirements mentioned in OP's comment section
ꜱʜᴏᴡ ᴘᴏꜱɪᴛɪᴠᴇ ᴠᴀʟᴜᴇꜱ ᴏɴ x-ᴀxɪꜱ
use the following callback function for x-axis ticks :
callback: function(t, i) {
return t < 0 ? Math.abs(t) : t;
}
ꜱʜᴏᴡ ᴘᴏꜱɪᴛɪᴠᴇ ᴠᴀʟᴜᴇ ᴏɴ ᴛᴏᴏʟᴛɪᴘ
use the following callback function for tooltips :
callbacks: {
label: function(t, d) {
var datasetLabel = d.datasets[t.datasetIndex].label;
var xLabel = Math.abs(t.xLabel);
return datasetLabel + ': ' + xLabel;
}
}
ᴡᴏʀᴋɪɴɢ ᴇxᴀᴍᴘʟᴇ ⧩
var ageGroup = ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61-70', '71-80', '80+'];
var maleData = [30, 0, 0, 0, 10, 0, 0, 0, 0];
var femaleData = [0, 0, 0, -20, -50, -20, 0, 0, 0];
var options = {
layout: {
padding: {
top: 5,
}
},
scales: {
yAxes: [{
display: true,
barPercentage: 0.4,
ticks: {
fontSize: 12
},
stacked: true,
}],
xAxes: [{
stacked: true,
ticks: {
callback: function(t, i) {
return t < 0 ? Math.abs(t) : t;
}
}
}]
},
tooltips: {
callbacks: {
label: function(t, d) {
var datasetLabel = d.datasets[t.datasetIndex].label;
var xLabel = Math.abs(t.xLabel);
return datasetLabel + ': ' + xLabel;
}
}
},
responsive: true,
//maintainAspectRatio: false,
legend: {
display: false,
},
animation: {
animateScale: true,
animateRotate: true
},
};
var opt = {
type: "horizontalBar",
data: {
labels: ageGroup,
datasets: [{
label: 'Male',
data: maleData,
backgroundColor: '#2196F3',
hoverBackgroundColor: '#2196F3'
}, {
label: 'Female',
data: femaleData,
backgroundColor: '#E91E63',
hoverBackgroundColor: '#E91E63'
}]
},
options: options
};
new Chart(ctx, opt);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="ctx"></canvas>