Chart.js redraw chart for which rendering is in progress - javascript

I am using a chart.js 3.x JavaScript library for displaying charts.
I need to update the chart for which rendering is in progress.
I am using JavaScript Worker for updating the chart data.
for (var index=0;index < myjson.length;index++) {
chart.data.datasets[index].data.push(myjson[index]);
}
chart.update();
I am doing something like below to clean the chart dataset.
// Set the empty data and render the chart to clear the data
for (var index=0;index < chart.data.datasets.length;index++) {
chart.data.datasets[index].data = [];
}
chart.update();
But I see a few of the lines are still on the chart and not cleared properly.
May be continuous drawing is causing this issue.
I have also tried to clear() and reset() the chart but no effect.
So how to clear the chart data properly and redraw new dataset.

use stop() to end the current animation loop,
then clear the data, and update the chart without animation.
see following working snippet,
once the animation progress reaches 50%,
the animation is stopped, the data is cleared, and the chart is updated without animation...
$(document).ready(function() {
var offset = [100, 100, 100, 100];
var data = [7900, 5400, 4300, 4300];
var ctx = document.getElementById('bar-chart');
var data = {
labels: ['June 9', 'June 11', 'June 13', 'June 15'],
datasets: [{
data: offset,
backgroundColor: 'transparent'
}, {
data: data.map(function (value, index) {
return value - offset[index];
}),
backgroundColor: 'orange'
}]
}
var chart = new Chart(ctx, {
type: 'bar',
data: data,
options: {
animation: {
onProgress: function(animation) {
var progress = animation.currentStep / animation.numSteps;
// determine if progress is above 50%
if (progress >= 0.5) {
// stop current animation loop
animation.chart.stop();
// remove labels and data
for (var i = chart.data.labels.length - 1; i >= 0; i--) {
animation.chart.data.labels.pop();
}
for (var i = chart.data.datasets.length - 1; i >= 0; i--) {
animation.chart.data.datasets.pop();
}
// update without animation
animation.chart.update(0);
}
}
},
legend: {
display: false
},
tooltips: {
enabled: false
},
scales: {
yAxes: [{
stacked: true
}]
}
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.0.0-beta/chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="bar-chart"></canvas>

Related

Can i use crosshairs with OHLC/candlestick charts using ChartJS

I have installed the crosshair plugin and am using chartJS 3.0 to take advantage of the candlestick charts but the crosshair does not appear. are these things compatible together? my data appear no problem but the crosshair never appears. how do i use these two things together? are there any working examples?
the tags i am using
<script src="https://cdn.jsdelivr.net/npm/luxon#1.24.1"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.0.0-beta.9/dist/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon#0.2.1"></script>
<script src="https://dd7tel2830j4w.cloudfront.net/f1614793727236x392906938665549250/chartjs-chart-financial.js"></script>
<script scr="https://cdn.jsdelivr.net/npm/chartjs-plugin-crosshair"></script>
and the chart code which also works
var divID = "chartContainer" + properties.chartid
var chartID = "myChart" + properties.chartid
instance.canvas.append('<div id="' + divID + '"></div>')
document.getElementById(divID).innerHTML = ' ';
document.getElementById(divID).innerHTML = '<canvas id=' + chartID + ' width="' + properties.bubble.width() + '" height="' + properties.bubble.height() + '"></canvas>';
var ctx = document.getElementById(chartID).getContext('2d');
var chart = new Chart(ctx, {
type: 'candlestick',
data: {
datasets: [{
label: 'CHRT - Chart.js Corporation',
data: getData()
}]
},
options: {
scales: {
y: {
min: 0,
max: 500
}
},
tooltips: {
mode: "interpolate",
intersect: false
},
plugins: {
crosshair: {
line: {
color: '#F66', // crosshair line color
width: 3, // crosshair line width
dashPattern: [5, 5] // crosshair line dash pattern
},
sync: {
enabled: true, // enable trace line syncing with other charts
group: 1, // chart group
suppressTooltips: false // suppress tooltips when showing a synced tracer
},
zoom: {
enabled: true, // enable zooming
zoomboxBackgroundColor: 'rgba(66,133,244,0.2)', // background color of zoom box
zoomboxBorderColor: '#48F', // border color of zoom box
zoomButtonText: 'Reset Zoom', // reset zoom button text
zoomButtonClass: 'reset-zoom', // reset zoom button class
},
callbacks: {
beforeZoom: function(start, end) { // called before zoom, return false to prevent zoom
return true;
},
afterZoom: function(start, end) { // called after zoom
}
}
}
}
}
});
function getData() {
var dates = properties.time.get(0, properties.time.length())
var opens = properties.open.get(0, properties.open.length())
var highs = properties.high.get(0, properties.high.length())
var lows = properties.low.get(0, properties.low.length())
var closes = properties.close.get(0, properties.close.length())
let data = []
for (i = 0; i < dates.length; i++) {
data.push({
t: dates[i].valueOf(),
o: opens[i],
h: highs[i],
l: lows[i],
c: closes[i]
})
}
console.log(data)
return data
}
chart.update()
The crosair plugin is not yet compatible with the new beta of version 3. They have a pr to be up to date with beta 11 but after that there have been breaking changes again. So you will have to update the plugin yourself or wait till it has been updated to support v3

Graph 1 hangs when function called second time

I want to generate graphs dynamically when I click on a button. My problem is when I click the button second time, the first graphs blocks displaying data.
The problem is with setInterval used within addChart function but I can't figure out how to rectify the issue. Is there some sanity in my logic or not?
<script>
function addChart() {
chartCount++;
var name = document.getElementById("chartName").value;
chartDict[name] = chartCount;
chart[chartCount] = new CanvasJS.Chart("chartContainer" + chartCount, {
title: {
text: name
},
axisY: {
includeZero: false,
maximum: 70
},
axisX: {
valueFormatString: "hh:mm tt"
},
data: [{
type: "line",
dataPoints: dps[chartCount]
}]
});
updateChart[chartCount] = function(count) {
count = count || 1;
for (var j = 0; j < count; j++) {
dps[chartCount].push({
x: xVal[chartCount],
y: yVal[chartCount]
});
xVal[chartCount] = new Date()
//console.log(xVal)
//xVal++;
}
if (dps[chartCount].length > dataLength) {
dps[chartCount].shift();
}
chart[chartCount].render();
};
intervals[chartCount] = setInterval(updateChart[chartCount], 1000)
}
</script>
You can try below workaround :
Remove the chart's inner content from DOM before reassigning the new values to chart element
Below is link to use remove() method of jquery:
https://api.jquery.com/remove/

dynamically update the scale type of chartjs graph

I have a bar chart on a page that has data displayed that is dynamically updated. The problem I have is that I need to change the type of the y-axis based on the data that is displayed. I need it to be a linear scale when the data has a range of less than 100 (e.g. 0-99) and logarithmic if it is greater (e.g. 0-1000).
I can calculate whether I need a logarithmic scale or not fine, but when it comes to changing the scale type I am at a loss. I have tried changing the options object of the chart and calling update():
myChart.options/scales.yAxes.map(function (axis) { axis.type = 'logarithmic' });
myChart.update();
and also tried changing the scale itself directly:
Object.keys(mychart.scales).forEach((function (axisName) {
var axis = myChart.scales[axisName];
if (axis && axis.id.startsWith('y')) {
axis.options.type = 'logarithmic'
}
}));
myChart.update();
then I tried using the beforeBuildTicks callback, thinking that changing the type while updating the data and redrawing the graph maybe too late or just at the wrong time:
beforeBuildTicks: function (scale) {
if (scale.chart) {
console.log('logarithmic');
scale.options.type = 'logarithmic';
}
}
all of these approaches change the chart object, but the display is not affected.
Is there a way to dynamically change the scale type of a chartjs bar chart without destroying and recreating the entire chart?
I finally found a solution to this, but it isn't straightforward.
Thanks to #jordanwillis for the pointer in the right direction in terms of using the scaleMerge() helper function.
I created a plugin that does all the work:
var changeScaleTypePlugin = {
beforeUpdate: function(chartInstance) {
var self = this;
chartInstance.beforeInit = null;
if (chartInstance.options.changeScale) {
self.changeScales(chartInstance);
if (chartInstance.options.scaleTypeChanged) {
chartInstance.options.scaleTypeChanged = false;
Object.keys(chartInstance.scales).forEach(function(axisName) {
var scale = chartInstance.scales[axisName];
Chart.layoutService.removeBox(chartInstance, scale);
});
chartInstance.initialize();
}
}
},
changeScales: function(chartInstance) {
var maxValue = Math.max.apply(null, chartInstance.data.datasets.map(function(dataset) {
return Math.max.apply(null, dataset.data);
}));
var minValue = Math.min.apply(null, chartInstance.data.datasets.map(function(dataset) {
return Math.min.apply(null, dataset.data);
}));
var logMax = Math.floor(Math.log(maxValue) / Math.LN10);
var logMin = Math.floor(Math.log(minValue) / Math.LN10);
if (logMax - logMin > chartInstance.options.maxRankDifference) {
if (!chartInstance.options.scaleTypeChanged && chartInstance.options.scales.yAxes.filter(function(axis) {
return axis.type !== 'logarithmic';
}).length) {
console.log('logarithmic');
chartInstance.options.scaleTypeChanged = true;
chartInstance.options.scales.yAxes = Chart.helpers.scaleMerge(Chart.defaults.scale, {
yAxes: chartInstance.options.logarithmicScaleOptions
}).yAxes;
}
} else {
if (!chartInstance.options.scaleTypeChanged && chartInstance.options.scales.yAxes.filter(function(axis) {
return axis.type !== 'linear';
}).length) {
console.log('linear');
chartInstance.options.scaleTypeChanged = true;
chartInstance.options.scales.yAxes = Chart.helpers.scaleMerge(Chart.defaults.scale, {
yAxes: chartInstance.options.linearScaleOptions
}).yAxes;
}
}
}
};
Chart.pluginService.register(changeScaleTypePlugin);
To apply this simply add a few properties to the options of the chart:
options: {
linearScaleOptions: [{
id: 'y-axis-0',
type: 'linear',
tick: {
// callback: Chart.Ticks.formatters.linear,
min: 0
}
}],
logarithmicScaleOptions: [{
id: 'y-axis-0',
type: 'logarithmic',
ticks: {
// callback: function (tickValue, index, ticks) {
// var remain = tickValue / (Math.pow(10, Math.floor(Math.log(tickValue) / Math.LN10)));
// if (tickValue === 0) {
// return '0';
// } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
// return tickValue;
// }
// return '';
//},
min: 0
}
}],
changeScale: true,
maxRankDifference: 1,
}
The commented out callback on the ticks is for the situation where (like me) you do not want the tick values shown in scientific notation on the log scale.
This gives the output that looks like:
with the max rank property set to 1
or
with it set to 3
or with the callback set:
You were on the right track, but the problem is for each scale type, there is a corresponding set of properties that are used by the underlying scale service to render the scale.
So when you configure your scale to be 'linear', that triggers a set of hidden axis properties to be set that produce the rendered linear scale. However, when you configure your scale to be 'logarithmic', the hidden axis properties need to be set differently to produce the rendered logarithmic scale.
Long story short, you must use the Chart.helpers.scaleMerge() function to handle all this for you (you can't just simple change the scale type in the chart options and re-render).
I created a working example demonstrating how to achieve what you are after. The essence of how this works is displayed below.
document.getElementById('changeScaleAndData').addEventListener('click', function() {
if (isLinear) {
myBar.options.scales.yAxes = Chart.helpers.scaleMerge(Chart.defaults.scale, {yAxes: logarithmicScaleOptions}).yAxes;
isLinear = false;
} else {
myBar.options.scales.yAxes = Chart.helpers.scaleMerge(Chart.defaults.scale, {yAxes: linearScaleOptions}).yAxes;
isLinear = true;
}
myBar.data.datasets[0].data = [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
];
myBar.update();
});
I find it easier to delete and recreate the chart after changing the axis type. IE if I have a checkbox with the id logScale to set whether my axis is log, I can just say:
$("#logScale").change(function () {
$('#chart').remove();
createChart();
})
where createChart is the code to create the chart. Note that I reference #logScale to set the Y axis type:
$('#chartContainer').append('<canvas id="chart" style="max-height:300px;max-width:95%"></canvas>');
var axisType = $("#logScale").prop("checked") ? 'logarithmic' : 'linear';
ctx = document.getElementById('chart').getContext('2d');
chartObject = new Chart(ctx, {
options: {
scales: {
xAxes: [{
yAxes: [{
type: axisType
}]
} //and whatever other chart data you have

How can I add a label above just the last bar in a Chart.JS bar chart?

I am creating a bar chart like so:
var ctxForecastChart = $("#forecastLineChart").get(0).getContext("2d");
var forecastChartData = {
labels: [
"Total Sales"
],
datasets: [
{
label: "8/28/2016 - 9/3/2016",
backgroundColor: "rgba(255,0,0,0.75)",
hoverBackgroundColor: "rgba(255,0,0,1)",
data: [240]
},
{
label: "9/25/2016 - 10/2/2016",
backgroundColor: "rgba(255,153,0,0.75)",
hoverBackgroundColor: "rgba(255,153,0,1)",
data: [272]
},
{
label: "9/18/2016 - 9/24/2016",
backgroundColor: "rgba(255,255,0,0.75)",
hoverBackgroundColor: "rgba(255,255,0,1)",
data: [250]
},
{
label: "9/4/2016 - 9/10/2016",
backgroundColor: "rgba(0,255,0,0.75)",
hoverBackgroundColor: "rgba(0,255,0,1)",
data: [232]
},
{
label: "9/11/2016 - 9/17/2016",
backgroundColor: "rgba(0,0,255,0.75)",
hoverBackgroundColor: "rgba(0,0,255,1)",
data: [244]
}]
};
var forecastOptions = {
tooltips: {
enabled: true
}
};
var forecastBarChart = new Chart(ctxForecastChart,
{
type: 'bar',
data: forecastChartData,
options: forecastOptions
});
This looks like so:
What I want to do is to add a label above the last bar (the blue one) with a percentage difference between the previous/4th one and that one. In this case, the value should be "+5.2%" so that it looks like this:
I reckon this will require the registring of an afterDraw() event, but the nitty-gritty of how it should look is beyond me.
UPDATE
If I add this to the proposed code:
if (chartInstance.id !== 2) return; // affect this one only
In context:
afterDraw: function (chartInstance) {
if (chartInstance.id !== 2) return; // affect this one only
// We get the canvas context
var ctx = chartInstance.chart.ctx;
...the results are a little better than without it (which mangles my first (pie) chart and completely obliterates the next two (including the one being discussed here):
As you can see, the pie chart is still hosed, and the values in the two bar charts are shrunken down as if a cannibalistic tribe has perpetrated its inicuous tricks on them. And, there is no value added atop the final bar.
First, you were right thinking about using the afterDraw event with a plugin and I understand it can be quite a mess to find what we really want in all the data and options.
Yet, follows a plugin that will help you do what you are looking for :
var myPlugin = {
afterDraw: function(chartInstance) {
// We get the canvas context
var ctx = chartInstance.chart.ctx;
// And set all the properties we need
ctx.font = Chart.helpers.fontString(14, 'bold', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = '#666';
// We get the number of datasets we have in your chart
var numOfDatasets = chartInstance.config.data.datasets.length;
// For every dataset in our chart ...
chartInstance.data.datasets.forEach(function(dataset) {
// If it is not the last dataset, we return now (no action at all)
if (dataset._meta[0].controller.index != numOfDatasets - 1) return;
// For every data in the dataset ...
for (var i = 0; i < dataset.data.length; i++) {
// We get the previous dataset (to compare later)
var previousDataset = chartInstance.config.data.datasets[dataset._meta[0].controller.index - 1];
// And get the model of the current value
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
// We calculate the percentage difference with the previous
var value = ((dataset.data[i] - previousDataset.data[i]) / previousDataset.data[i]) * 100;
// And write it with a "%" symbol
// The ternary adds a "+" symbol if it is a positive value
ctx.fillText((value > 0 ? "+" : "") + value.toFixed(1) + "%", model.x, model.y);
}
});
}
};
Chart.pluginService.register(myPlugin);
You can see this code working on this jsFiddle and here is its result :

Google Charts - how to add a fixed scale on an axis

I'm having trouble scaling my chart correctly. My chart represents data for every hour of the day in a 24 hour format, meaning that I need the numbers 0-24 on my linechart.
I've tried adding the logScale, minValue and maxValue properties to the hAxis, but nothing is working.
As you can see on the chart, the hour axis is not spanning a fixed axis from 0-24 hours, but instead from 9-15 hours.
I also only have 3 rows in my data set, which reside on the hours 9, 14 and 15. Despite this, the lines are spanning from 9-14 as if they have values; however there is no data there, so the lines should be running along the bottom at 0 between these two points.
How can I put a fixed horizontal scale on my chart, and have individual values on my lines for each hour?
Here's my code:
google.load('visualization', '1.1', {packages: ['line']});
google.setOnLoadCallback(drawChart);
function drawChart()
{
var json = $.getJSON('my JSON data link', function(data)
{
var chartStructure = new google.visualization.DataTable();
var chartData = [];
chartStructure.addColumn('number', 'Hour');
chartStructure.addColumn('number', 'Pageviews');
chartStructure.addColumn('number', 'Unique Pageviews');
chartStructure.addColumn('number', 'Sales');
chartStructure.addColumn('number', 'Earnings in $AUD');
for (i = 0; i < data.length; i++)
{
chartData[i] = [];
chartData[i][0] = parseInt(data[i].hour);
chartData[i][1] = parseFloat(data[i].profit);
chartData[i][2] = parseFloat(data[i].profit);
chartData[i][3] = parseFloat(data[i].sales);
chartData[i][4] = parseFloat(data[i].profit);
// These chartData values are not correct because I am testing
chartStructure.addRows(chartData);
}
var options = {
hAxis: {
'minValue': 0,
'maxValue': 24
}
};
var chart = new google.charts.Line(document.getElementById('todays-total-sales'));
chart.draw(chartStructure, options);
});
}
$(window).resize(function()
{
drawChart();
});
Use viewWindow.
hAxis: {
title: 'Time',
viewWindow:{
max:1000,
min:-100
}
},
JSFiddle
UPDATE
If you are using MaterialCharts please note that the options have different syntax!
In order for you to be able to use the classic options, you need to change
chart.draw(data, options);
to
chart.draw(data, google.charts.Line.convertOptions(options));
HERE is your updated fiddle.
var options = {
title: "Posted Memes",
width: 450,
height: 300,
is3D:true,
bar: { groupWidth: "95%" },
legend: { position: "none" },
vAxis: {
title: 'No of Memes',
viewWindowMode: 'explicit',
viewWindow: {
max: 180,
min: 0,
interval: 1,
},
}
};

Categories

Resources