How To Match Left and Right Tick Intervals with Chartjs - javascript

When I use a left and right line plot in Chartjs, I sometimes get inconsistent Y Axis tick interval counts. So, I might have like 7 intervals on the left, and Chartjs automatically might put 10 on the right. An example of a hard-to-read chart would look like this:
Therefore, the question is -- how do I set the Y Axis tick interval on the right so that it is consistent with the left?

When defining the options.scales.yAxes[1] (the right Y axis), add a beforeUpdate callback so that you can tweak its stepSize, like so:
beforeUpdate: function(scale) {
// get the max data point on the right
var nMax = Math.max.apply(Math,scale.chart.config.data.datasets[1].data);
// Get the count of ticks on the left that Chartjs automatically created.
// (Change the 'Clicks' to the 'id' property of that left Y Axis.)
var nLeftTickCount = scale.chart.scales['Clicks'].ticks.length;
// Add some exception logic so that we don't go less than 7 (a failsafe).
// Also, we need the count of spaces between the ticks,
// not the count of total ticks.
nLeftTickCount = (nLeftTickCount < 7) ? 7 : nLeftTickCount - 1;
// compute our tick step size
var nStepSize = nMax / nLeftTickCount;
// Assign the right Y Axis step size.
scale.chart.options.scales.yAxes[1].ticks.stepSize = nStepSize;
return;
}
This creates a consistent chart like so:
Here is the entire example of the area chart with a left and right Y Axis:
<script src="vendor/chartjs/chart.js/dist/Chart.min.js"></script>
<div class="chart-container">
<canvas id="my-canvas" width="400" height="200" style="width:100%;"></canvas>
</div>
<script>
var tsCanvas = document.getElementById('my-canvas');
var tsChart = new Chart(tsCanvas, {
type: 'line',
data: {
labels: ["Feb 1","Feb 16","Mar 1","Mar 16","Mar 22"],
datasets: [
{
label: 'Clicks',
yAxisID: 'Clicks',
data: [10706, 12847, 11516, 10464, 1204],
backgroundColor: 'rgba(26, 187, 156, 0.2)',
borderColor: 'rgba(26, 187, 156, 1)',
pointBackgroundColor: 'rgba(26, 187, 156, 1)',
borderWidth: 0.5,
pointRadius:2,
tension:0
},
{
label: 'Revenue',
yAxisID: 'Revenue',
data: [106.66, 342.86, 313.67, 461.18, 25.84],
backgroundColor: 'rgba(90, 144, 197, 0.2)',
borderColor: 'rgba(90, 144, 197, 1)',
pointBackgroundColor: 'rgba(90, 144, 197, 1)',
borderWidth: 0.5,
pointRadius:2,
tension:0
}
]
},
options: {
maintainAspectRatio:false,
hover: {
animationDuration:0
},
tooltips: {
mode: 'index',
multiKeyBackground: 'rgba(255,255,255,0.55)'
},
scales: {
yAxes: [
{
id: 'Clicks',
type: 'linear',
position: 'left',
scaleLabel: {
display:true,
labelString: 'Clicks'
},
ticks: {
beginAtZero:true
}
},
{
beforeUpdate: function(scale) {
var nMaxRev = Math.max.apply(Math,scale.chart.config.data.datasets[1].data);
var nLeftTickCount = scale.chart.scales['Clicks'].ticks.length;
nLeftTickCount = (nLeftTickCount < 7) ? 7 : nLeftTickCount - 1;
var nTickInterval = nMaxRev / nLeftTickCount;
scale.chart.options.scales.yAxes[1].ticks.stepSize = nTickInterval;
return;
},
id: 'Revenue',
type: 'linear',
position: 'right',
scaleLabel: {
display:true,
labelString: 'Revenue'
},
ticks: {
beginAtZero:true
}
}
],
xAxes: [
{
type: 'category',
ticks: {
minRotation:50,
maxRotation:50
}
}
]
}
}
});
</script>

Related

How to put images on Y axis of line chart in Chart.js? [duplicate]

I am generating a chart.js canvas bar chart. What I am trying to do is, inside of the labels array, add images that go with each label, as opposed to just the text label itself. Here is the code for the chart: The json object that I am getting data from has an image url that I want to use to display the picture:
$.ajax({
method: "get",
url: "http://localhost:3000/admin/stats/show",
dataType: "json",
error: function() {
console.log("Sorry, something went wrong");
},
success: function(response) {
console.log(response)
var objectToUse = response.top_dogs
var updateLabels = [];
var updateData = [];
for (var i = 0; i < objectToUse.length; i+=1) {
updateData.push(objectToUse[i].win_percentage * 100);
updateLabels.push(objectToUse[i].title);
}
var data = {
labels: updateLabels,
datasets: [
{
label: "Top Winners Overall",
fillColor: get_random_color(),
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: get_random_color(),
highlightStroke: "rgba(220,220,220,1)",
data: updateData
}
]
};
var options = {
//Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
scaleBeginAtZero : true,
//Boolean - Whether grid lines are shown across the chart
scaleShowGridLines : true,
//String - Colour of the grid lines
scaleGridLineColor : "rgba(0,0,0,.05)",
//Number - Width of the grid lines
scaleGridLineWidth : 1,
//Boolean - Whether to show horizontal lines (except X axis)
scaleShowHorizontalLines: true,
//Boolean - Whether to show vertical lines (except Y axis)
scaleShowVerticalLines: true,
//Boolean - If there is a stroke on each bar
barShowStroke : true,
//Number - Pixel width of the bar stroke
barStrokeWidth : 2,
//Number - Spacing between each of the X value sets
barValueSpacing : 5,
//Number - Spacing between data sets within X values
barDatasetSpacing : 2,
};
var loadNewChart = new Chart(barChart).Bar(data, options);
}
});
If anyone has a solution it would be greatly appreciated!
I'm aware that this is an old post but since it has been viewed so many times, I'll describe a solution that works with the current Chart.js version 2.9.3.
The Plugin Core API offers a range of hooks that may be used for performing custom code. You can use the afterDraw hook to draw images (icons) directly on the canvas using CanvasRenderingContext2D.
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
xAxis.ticks.forEach((value, index) => {
var x = xAxis.getPixelForTick(index);
ctx.drawImage(images[index], x - 12, yAxis.bottom + 10);
});
}
}],
The position of the labels will have to be defined through the xAxes.ticks.padding as follows:
xAxes: [{
ticks: {
padding: 30
}
}],
Please have a look at the following runnable code snippet.
const labels = ['Red Vans', 'Blue Vans', 'Green Vans', 'Gray Vans'];
const images = ['https://i.stack.imgur.com/2RAv2.png', 'https://i.stack.imgur.com/Tq5DA.png', 'https://i.stack.imgur.com/3KRtW.png', 'https://i.stack.imgur.com/iLyVi.png']
.map(png => {
const image = new Image();
image.src = png;
return image;
});
const values = [48, 56, 33, 44];
new Chart(document.getElementById("myChart"), {
type: "bar",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
xAxis.ticks.forEach((value, index) => {
var x = xAxis.getPixelForTick(index);
ctx.drawImage(images[index], x - 12, yAxis.bottom + 10);
});
}
}],
data: {
labels: labels,
datasets: [{
label: 'My Dataset',
data: values,
backgroundColor: ['red', 'blue', 'green', 'lightgray']
}]
},
options: {
responsive: true,
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}],
xAxes: [{
ticks: {
padding: 30
}
}],
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="myChart" height="90"></canvas>
Chart.js v3+ solution to pie, doughnut and polar charts
With version 3 of Chart.js and the updated version of chart.js-plugin-labels, this is now incredbly simple.
in options.plugins.labels, add render: image and the nested array images with objects containing the properties src, width and height.
const data = {
labels: ['Label 1', 'Label 2', 'Label 3', 'Label 4', 'Label 5', 'Label 6', 'Label 7', 'Label 8'],
datasets: [{
label: 'Image labels',
// Making each element take up full width, equally divided
data: [100, 100, 100, 100, 100, 100, 100, 100],
backgroundColor: [
'rgba(255, 26, 104, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(0, 0, 0, 0.2)',
'rgba(20, 43, 152, 0.2)'
]
}]
};
const config = {
type: 'doughnut',
data,
options: {
plugins: {
// Accessing labels and making them images
labels: {
render: 'image',
images: [{
src: 'https://cdn0.iconfinder.com/data/icons/google-material-design-3-0/48/ic_book_48px-256.png',
height: 25,
width: 25
},
{
src: 'https://cdn3.iconfinder.com/data/icons/glypho-free/64/pen-checkbox-256.png',
height: 25,
width: 25
},
{
src: 'https://cdn1.iconfinder.com/data/icons/jumpicon-basic-ui-glyph-1/32/-_Home-House--256.png',
height: 25,
width: 25
},
{
src: 'https://cdn1.iconfinder.com/data/icons/social-media-vol-3/24/_google_chrome-256.png',
height: 25,
width: 25
},
{
src: 'https://cdn0.iconfinder.com/data/icons/google-material-design-3-0/48/ic_book_48px-256.png',
height: 25,
width: 25
},
{
src: 'https://cdn3.iconfinder.com/data/icons/glypho-free/64/pen-checkbox-256.png',
height: 25,
width: 25
},
{
src: 'https://cdn1.iconfinder.com/data/icons/jumpicon-basic-ui-glyph-1/32/-_Home-House--256.png',
height: 25,
width: 25
},
{
src: 'https://cdn1.iconfinder.com/data/icons/social-media-vol-3/24/_google_chrome-256.png',
height: 25,
width: 25
},
]
}
}
}
};
// render init block
const myChart = new Chart(
document.getElementById('myChart').getContext('2d'),
config
);
.chartCard {
width: 100vw;
height: 500px;
display: flex;
align-items: center;
justify-content: center;
}
.chartBox {
width: 600px;
padding: 20px;
}
<div class="chartCard">
<div class="chartBox">
<canvas id="myChart"></canvas>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://unpkg.com/chart.js-plugin-labels-dv#3.0.5/dist/chartjs-plugin-labels.min.js"></script>

Creating charts

I need to make a chart at the level with a row in the table, are there any tips on how to implement this enter image description here
I need the chart lines to match the row level in the table
and this code draws a separate chart
const diag = () => {
document.getElementById("canvasic").innerHTML = ' ';
document.getElementById("canvasic").innerHTML = '<canvas id="densityChart" className="canav"></canvas>';
densityCanvas = document.getElementById("densityChart");
//remove canvas from container
Chart.defaults.global.defaultFontFamily = "Arial";
Chart.defaults.global.defaultFontSize = 16;
var densityData = {
label: 'CallVol',
data:calloiList1,
backgroundColor: 'rgba(0,128,0, 0.6)',
borderColor: 'rgba(0,128,0, 1)',
borderWidth: 2,
hoverBorderWidth: 0
};
var densityData1 = {
label: 'PutVol',
data:calloiList3 ,
backgroundColor: 'rgba(255,0,0, 0.6)',
borderColor: 'rgba(255,0,0, 1)',
borderWidth: 2,
hoverBorderWidth: 0
};
var chartOptions = {
scales: {
yAxes: [{
barPercentage: 0.5
}]
},
elements: {
rectangle: {
borderSkipped: 'left',
}
}
};
var barChart = new Chart(densityCanvas, {
type: 'horizontalBar',
data: {
labels: calloiList4,
datasets: [densityData,densityData1],
},
options: chartOptions
}
);
}
enter image description here

How to plot a single value with line in line chart graph using charts.js?

I need to plot a single value in line chart. Currently i am using charts.JS library for line graph purpose.
The data will be varied some times i'll get the single data inside the data set at that time i need to plot the single value with line in the line chart.
I tried with the charts.js annotation plugin but it wasn't met my requirements. which is like it wis overlapping the plotted point in the graph area.
CODE WHICH I HAD TRIED
createLineChart() {
this.lineChart = new Chart(this.lineCanvas.nativeElement, {
type: "line",
data: {
labels:[],
datasets: [
{
fill: false,
backgroundColor: "#0168FF",
borderColor: "#0168FF",
pointBackgroundColor: "white", // wite point fill
pointBorderWidth: 1, // point border width
lineTension: 0,
pointBorderColor: "blue",
pointRadius: 4,
},
],
},
options: {
scales: {
yAxes: [
{
ticks: {
padding: 20,
beginAtZero: true,
min: 0,
stepSize: 100,
},
gridLines: {
drawBorder: false,
},
},
],
xAxes: [
{
// offset: true,
ticks: {
display: false,
//beginAtZero: true,
min: 0,
},
gridLines: {
zeroLineColor: "transparent",
drawBorder: false,
display: false,
},
//offset:true,
},
],
legend: {
display: false,
},
tooltips: {
enabled: false,
},
},
drawTime: "afterDraw", // (default)
} as ChartOptions,
// plugins: [ChartAnnotation]
},
});
}
To generate dynamic data and plot in the graph area.
generateRandomDataSet(size) {
let yaxisArr = [];
let xaxisArr = [];
let random_data:any = this.getRandomData(size)
let maxYTickVal = Math.max.apply(Math, random_data.map((val) => {return val.yaxis}));
let maxVal = Math.ceil((maxYTickVal+1) / 10) * 10
for(let data of random_data) {
yaxisArr.push(data.yaxis)
xaxisArr.push(data.xaxis)
}
console.log("X-Axis array values : "+xaxisArr)
console.log("Y-Axis array values : "+yaxisArr)
this.lineChart.data.datasets[0].data = yaxisArr
this.lineChart.config.data.labels = []
this.lineChart.config.data.labels = xaxisArr
this.lineChart.config.options.scales.yAxes[0].ticks.max =maxVal
this.lineChart.config.options.scales.yAxes[0].ticks.stepSize = maxVal/2
this.lineChart.update()
}
getRandomData(arraySize) {
let data = []
for(var i=1; i<=arraySize; i++) {
let number = Math.floor(Math.random() * 200) + 1
data.push({'xaxis':i,'yaxis':number})
}
return data
}
with the above code i am getting like
what i need to have
You can define an animation.onComplete function as follows to draw the line in case a single data value is present.
animation: {
onComplete: e => {
var ctx = chart.chart.ctx;
var data = chart.config.data.datasets[0].data;
if (data[0] == null) {
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
var y = yAxis.getPixelForValue(data[1]);
ctx.save();
ctx.globalCompositeOperation='destination-over';
ctx.strokeStyle = 'blue'
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(xAxis.left, y);
ctx.lineTo(xAxis.right, y);
ctx.stroke();
ctx.restore();
}
}
},
This function expects the data array to be of format [null, <value>, null] in case a single value is present, otherwise it will be hard to horizontally center the data point (see this answer). It's up to you to change the generateRandomDataSet() function in a way that it provides such data.
Please have a look at your changed code below.
const chart = new Chart('line-chart', {
type: "line",
data: {
labels: ['', 'A', ''],
datasets: [{
data: [null, 120, null],
fill: false,
backgroundColor: "#0168FF",
borderColor: "#0168FF",
pointBackgroundColor: "white",
pointBorderWidth: 1,
lineTension: 0,
pointBorderColor: "blue",
pointRadius: 4,
}],
},
options: {
legend: {
display: false
},
tooltips: {
enabled: false
},
animation: {
onComplete: e => {
var ctx = chart.chart.ctx;
var data = chart.config.data.datasets[0].data;
if (data[0] == null) {
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
var y = yAxis.getPixelForValue(data[1]);
ctx.save();
ctx.globalCompositeOperation='destination-over';
ctx.strokeStyle = 'blue'
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(xAxis.left, y);
ctx.lineTo(xAxis.right, y);
ctx.stroke();
ctx.restore();
}
}
},
scales: {
yAxes: [{
ticks: {
padding: 20,
min: 0,
stepSize: 100
},
gridLines: {
drawBorder: false
}
}],
xAxes: [{
ticks: {
display: false
},
gridLines: {
zeroLineColor: "transparent",
drawBorder: false,
display: false
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="line-chart" height="80"></canvas>

Chart.js : How I change the x axes ticks labels alignment in any sizes?

How can I move my labels on my x axes in between another x axes label. Nothing seems to work and I was unable to find anything on the docs. Is there a workaround? I'm using line chart time series.
https://www.chartjs.org/samples/latest/scales/time/financial.html
Currently, with the code I have its generating the figure below:
var cfg = {
elements:{
point: {
radius: 4
}
},
data: {
datasets: [
{
label: 'vsy',
backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
borderColor: window.chartColors.red,
data: firstData,
type: 'line',
pointRadius: 2,
fill: false,
lineTension: 0,
borderWidth: 2
},
{
label: 'de vsy',
backgroundColor: color(window.chartColors.blue).alpha(0.5).rgbString(),
borderColor: window.chartColors.blue,
data: dataMaker(15),
type: 'line',
pointRadius: 2,
fill: false,
lineTension: 0,
borderWidth: 2
}
],
},
options: {
animation: {
duration: 0
},
scales: {
xAxes: [{
type: 'time',
distribution: 'series',
offset: true,
time: {
unit: 'month',
displayFormats: {
month: 'MMM'
}
},
ticks: {
autoSkip: true,
autoSkipPadding: 75,
sampleSize: 100
},
}],
yAxes: [{
gridLines: {
drawBorder: false
}
}]
},
tooltips: {
intersect: false,
mode: 'index',
}
}
};
This is what I have now:
I want the labels on the x-axis to be on center instead of below the y axis grid line.
Thanks to uminder, with his comment it solves the issue but now I have a conflicting tooltip which lie on a same grid. When I hover to april line first point it shows me mar 30 which lies just above it and vice versa.
I fixed it by changing the mode to nearest but why is it activating the another point?
The option you're looking for is offsetGridLines.
If true, grid lines will be shifted to be between labels.
xAxes: [{
...
gridLines: {
offsetGridLines: true
}
In most cases, this produces the expected result. Unfortunately it doesn't work for time axes as documented in Chart.js issue #403. Thanks to Antti Hukkanen, there exists a workaround.
Please have a look at below runnable code snippet to see how it works.
function generateData() {
var unit = 'day';
function randomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function randomPoint(date, lastClose) {
var open = randomNumber(lastClose * 0.95, lastClose * 1.05).toFixed(2);
var close = randomNumber(open * 0.95, open * 1.05).toFixed(2);
return {
t: date.valueOf(),
y: close
};
}
var date = moment().subtract(1, 'years');
var now = moment();
var data = [];
for (; data.length < 600 && date.isBefore(now); date = date.clone().add(1, unit).startOf(unit)) {
data.push(randomPoint(date, data.length > 0 ? data[data.length - 1].y : 30));
}
return data;
}
var TimeCenterScale = Chart.scaleService.getScaleConstructor('time').extend({
getPixelForTick: function(index) {
var ticks = this.getTicks();
if (index < 0 || index >= ticks.length) {
return null;
}
// Get the pixel value for the current tick.
var px = this.getPixelForOffset(ticks[index].value);
// Get the next tick's pixel value.
var nextPx = this.right;
var nextTick = ticks[index + 1];
if (nextTick) {
nextPx = this.getPixelForOffset(nextTick.value);
}
// Align the labels in the middle of the current and next tick.
return px + (nextPx - px) / 2;
},
});
// Register the scale type
var defaults = Chart.scaleService.getScaleDefaults('time');
Chart.scaleService.registerScaleType('timecenter', TimeCenterScale, defaults);
var cfg = {
data: {
datasets: [{
label: 'CHRT - Chart.js Corporation',
backgroundColor: 'red',
borderColor: 'red',
data: generateData(),
type: 'line',
pointRadius: 0,
fill: false,
lineTension: 0,
borderWidth: 2
}]
},
options: {
animation: {
duration: 0
},
scales: {
xAxes: [{
type: 'timecenter',
time: {
unit: 'month',
stepSize: 1,
displayFormats: {
month: 'MMM'
}
},
gridLines: {
offsetGridLines: true
}
}],
yAxes: [{
gridLines: {
drawBorder: false
}
}]
},
tooltips: {
intersect: false,
mode: 'index'
}
}
};
var chart = new Chart('chart1', cfg);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart1" height="90"></canvas>
For chartJs v3 you can use offset property:
scales: {
x: {
grid: {
offset: true
}
},
...
}

How to maintain chartjs / ng2-charts gradient on window resize?

I had applied some gradient rule to my chartjs chart. And it looks great as you can see on the below
However, when the browser window is resized (i.e. width of window is smaller), the gradient is ruined (bottom blue colors disappeared). Screenshot:
I want to maintain the graph's gradient with all values and fit the different widths (responsive). Is there any way to do that? Here is what I had tried but didn't work:
.TS File
ngAfterViewInit() {
const ctx = (<HTMLCanvasElement>this.myChart.nativeElement).getContext('2d');
const purple_orange_gradient = ctx.createLinearGradient(0, 200, 0, 20);
purple_orange_gradient.addColorStop(0.1, "#000279");
purple_orange_gradient.addColorStop(0.2, "#0000F2");
purple_orange_gradient.addColorStop(0.3, "#0362FD");
purple_orange_gradient.addColorStop(0.4, "#04D3FD");
purple_orange_gradient.addColorStop(0.5, "#45FFB7");
purple_orange_gradient.addColorStop(0.6, "#B7FF46");
purple_orange_gradient.addColorStop(0.7, "#FFD401");
purple_orange_gradient.addColorStop(0.8, "#FE6500");
purple_orange_gradient.addColorStop(0.9, "#F30004");
purple_orange_gradient.addColorStop(1, "#7E0100");
const bar_chart = new Chart(ctx, {
type: "horizontalBar",
data: {
labels: []=this.histogramLabels.reverse(),
datasets: [{
borderColor: purple_orange_gradient,
pointBorderColor: purple_orange_gradient,
pointBackgroundColor: purple_orange_gradient,
pointHoverBackgroundColor: purple_orange_gradient,
pointHoverBorderColor: purple_orange_gradient,
pointBorderWidth: 10,
pointHoverRadius: 10,
pointHoverBorderWidth: 1,
pointRadius: 3,
fill: true,
backgroundColor: purple_orange_gradient,
borderWidth: 4,
data: []=this.histogramGraphData
}]
},
options: {
legend: {
display:false,
position: "bottom"
},
scales: {
yAxes: [{
ticks: {
display: false,
fontColor: "rgba(0,0,0,0.5)",
fontStyle: "bold",
beginAtZero: true,
maxTicksLimit: 1,
padding: 20,
},
gridLines: {
drawTicks: false,
display: false
}
}],
xAxes: [{
gridLines: {
zeroLineColor: "transparent",
},
ticks: {
padding: 20,
beginAtZero: true,
fontColor: "rgba(0,0,0,0.5)",
fontStyle: "bold"
}
}]
}
}
}
)
}
.HTML
<div class="row my-2">
<div class="col-md-6">
<canvas id=”myChart” #myChart height="130"></canvas>
</div>
</div>
HTML Canvas' createLinearGradient() depends on the y axis coordinates that you pass in as argument. You had passed in a static 200 every time (i.e. ctx.createLinearGradient(0, 200, 0, 20);).
That's why the gradient's steps remains the same everytime. For the gradient to update, you have to recalculate the height of the <canvas> element on window resize and pass it in to createLinearGradient() again.
You can accomplish this by:
Separating the block where you create the gradient into a separate function. eleHeight retrieves the height of the canvas element.
generateGradient(){
let eleHeight = this.myChart.nativeElement.offsetHeight;
// console.log(eleHeight)
let purple_orange_gradient: CanvasGradient = this.myChart.nativeElement.getContext('2d').createLinearGradient(0, eleHeight, 0, 20);
purple_orange_gradient.addColorStop(0.1, "#000279");
purple_orange_gradient.addColorStop(0.2, "#0000F2");
purple_orange_gradient.addColorStop(0.3, "#0362FD");
purple_orange_gradient.addColorStop(0.4, "#04D3FD");
purple_orange_gradient.addColorStop(0.5, "#45FFB7");
purple_orange_gradient.addColorStop(0.6, "#B7FF46");
purple_orange_gradient.addColorStop(0.7, "#FFD401");
purple_orange_gradient.addColorStop(0.8, "#FE6500");
purple_orange_gradient.addColorStop(0.9, "#F30004");
purple_orange_gradient.addColorStop(1, "#7E0100");
return purple_orange_gradient;
}
Add a onresize event handler to your containing <div> and generate the gradient again. You also need to programatically update the chart every time you make a change to re-render it.
<div style="display: block; max-height: 100%" (window:resize)="onResize($event)" >
...
</div>
onResize(event?){
// console.log("onResize");
this.barChartData.forEach((d, i) => {
d.backgroundColor = this.generateGradient();
})
this.chart.chart.update(); //update the chart to re-render it
}
Update the barchartData's properties (that uses gradient) in ngAfterViewInit. We need to do this here because we only want the height of the <canvas> element with data populated. Without data populated, the element is much smaller.
ngAfterViewInit(){
this.barChartData.forEach((d, i) => {
d.backgroundColor = this.generateGradient();
});
this.chart.chart.update(); //update the chart to re-render it
}
Have a look at this Stackblitz example⚡⚡ I have created.
You have to change the gradient whenever your canvas is resizing. Took me a while to figure out a good structure to minimize lines of code and optimize performance. This is the best I could achieve.
There are exeptions when the chart.js onResize() fires though but I couldn't solve this issue completly bulletproof. But for simple resizes it should work.
Complete code (same code in JSBin with live preview):
let sData = {}
sData.labels = []
sData.data = []
const count = 50
for (let x = 0; x < count; x++) {
sData.data.push(Math.floor(Math.random()*100))
sData.labels.push(x)
}
const canvas = document.getElementById('chart')
const ctx = canvas.getContext("2d")
let purple_orange_gradient
function updateGradient() {
let bottom = bar_chart.chartArea.bottom
let top = bar_chart.chartArea.top
purple_orange_gradient = ctx.createLinearGradient(0, bottom+top, 0, top)
purple_orange_gradient.addColorStop(0.1, "#000279")
purple_orange_gradient.addColorStop(0.2, "#0000F2")
purple_orange_gradient.addColorStop(0.3, "#0362FD")
purple_orange_gradient.addColorStop(0.4, "#04D3FD")
purple_orange_gradient.addColorStop(0.5, "#45FFB7")
purple_orange_gradient.addColorStop(0.6, "#B7FF46")
purple_orange_gradient.addColorStop(0.7, "#FFD401")
purple_orange_gradient.addColorStop(0.8, "#FE6500")
purple_orange_gradient.addColorStop(0.9, "#F30004")
purple_orange_gradient.addColorStop(1.0, "#7E0100")
return purple_orange_gradient
}
const bar_chart = new Chart(ctx, {
type: "horizontalBar",
data: {
labels: sData.labels,
datasets: [{
borderColor: purple_orange_gradient,
pointBorderColor: purple_orange_gradient,
pointBackgroundColor: purple_orange_gradient,
pointHoverBackgroundColor: purple_orange_gradient,
pointHoverBorderColor: purple_orange_gradient,
pointBorderWidth: 10,
pointHoverRadius: 10,
pointHoverBorderWidth: 1,
pointRadius: 3,
fill: true,
backgroundColor: purple_orange_gradient,
borderWidth: 4,
data: sData.data
}]
},
options: {
legend: {
display: false,
position: "bottom"
},
scales: {
yAxes: [{
ticks: {
display: false,
fontColor: "rgba(0,0,0,0.5)",
fontStyle: "bold",
beginAtZero: true,
maxTicksLimit: 1,
padding: 20,
},
gridLines: {
drawTicks: false,
display: false
}
}],
xAxes: [{
gridLines: {
zeroLineColor: "transparent",
},
ticks: {
padding: 20,
beginAtZero: true,
fontColor: "rgba(0,0,0,0.5)",
fontStyle: "bold"
}
}]
},
onResize: function(chart, size) {
// onResize gradient change
changeGradient()
}
}
});
// Initial gradient change
changeGradient()
function changeGradient() {
let newGradient = updateGradient()
bar_chart.data.datasets[0].borderColor = newGradient
bar_chart.data.datasets[0].pointBorderColor = newGradient
bar_chart.data.datasets[0].pointBackgroundColor = newGradient
bar_chart.data.datasets[0].pointHoverBackgroundColor = newGradient
bar_chart.data.datasets[0].pointHoverBorderColor = newGradient
bar_chart.data.datasets[0].backgroundColor = newGradient
bar_chart.update()
}

Categories