Updating Chartjs with new data - javascript

My goal is to update my chart with new data from the server. Here's my code:
(function () {
'use strict';
let color = Chart.helpers.color;
window.chartColors = {
red: 'rgb(255, 99, 132)',
orange: 'rgb(255, 159, 64)',
yellow: 'rgb(255, 205, 86)',
green: 'rgb(75, 192, 192)',
blue: 'rgb(54, 162, 235)',
purple: 'rgb(153, 102, 255)',
grey: 'rgb(201, 203, 207)'
};
let timeAxis = [{
type: 'time',
}];
let percentAxis = [{
ticks: {
beginAtZero: false,
callback: function(value) {
return Math.round(value * 1000) / 10 + '%';
}
}
}];
let buildChartObject = function (ctx, type, xAxes, yAxes) {
return new Chart(ctx, {
type: type,
data: null,
options: {
responsive: true,
title: {
display: true,
fontStyle: 'normal',
padding: 10,
fontSize: 12
},
scales: {
xAxes: xAxes,
yAxes: yAxes
},
legend: {
display: false
}
}
});
};
let loadChartData = function (endpoint, chart, params) {
$.ajax({
url: '/api/v1/' + endpoint,
method: 'GET',
dataType: 'json',
params: params,
success: function (d) {
let bgColors = null, bdColors = null;
if (chart.config.type === 'line') {
bgColors = color(window.chartColors.blue).alpha(0.5).rgbString();
bdColors = window.chartColors.blue;
} else {
bgColors = d.data.map(
(value) => value < 0 ? color(window.chartColors.red).alpha(0.5).rgbString() :
color(window.chartColors.green).alpha(0.5).rgbString()
);
bdColors = d.data.map(
(value) => value < 0 ? window.chartColors.red : window.chartColors.green
);
}
if (chart.options.scales.xAxes[0].type === 'time') {
let dateUnits = {
daily: 'day',
weekly: 'week',
monthly: 'month',
yearly: 'year'
};
chart.options.scales.xAxes[0].time.unit = dateUnits[d.params.convertTo];
}
chart.options.title.text = d.name;
chart.data.labels = d.index;
chart.data.datasets[0] = {
backgroundColor: bgColors,
borderColor: bdColors,
borderWidth: 1,
data: d.data
};
chart.update();
}
});
};
let loadCharts = function () {
let params = {
convertTo: $('#convert-to').val()
}
let returnsChart = buildChartObject($('#chart'), 'bar', timeAxis, percentAxis);
loadChartData('endpoint', returnsChart, params);
}
loadCharts();
$('#convert-to').on('change', function() {
loadCharts();
});
}());
The initial call to loadCharts() correctly populates the chart. However when when the #convert-to event is triggered, the loadCharts reloads the data but I have this flickering effect of both charts drawn on the same canvas. This is not a bug or a related issue, rather a canvas drawing one.
Have a look here: https://www.dropbox.com/s/9onofdazkvp9uas/clip.mov?dl=0
I've read countless threads on this and it seems like chart.update() should solve the issue. From the docs: "[update()] triggers an update of the chart. This can be safely called after updating the data object. This will update all scales, legends, and then re-render the chart."
What am I doing wrong?

You don't need to call buildChartObject(...) again when you are trying to just update the data for the chart. If you held onto a reference of the chart you would be fine to skip that call. Doing this will allow Chart.js to just update the chart when you call chart.update() instead of creating a new chart and then updating it.
...
let returnsChart = buildChartObject($('#chart'), 'bar', timeAxis, percentAxis);
let loadCharts = function () {
let params = {
convertTo: $('#convert-to').val()
};
loadChartData('endpoint', returnsChart, params);
}
loadCharts();
$('#convert-to').on('change', function() {
loadCharts();
});
...

Related

Is there a better way to create an 'n' number of charts in ChartJS and ASP.NET C#?

EDIT: I have narrowed it down to something like this:
for (i = 0; i < data.length; i++) {
const newCanvas = document.createElement("canvas");
newCanvas.id = data[i].design_name;
const currentDiv = document.getElementById("chartSpace");
var parentDiv = document.getElementById("gridHere");
parentDiv.insertBefore(newCanvas, currentDiv);
createChart([data[i].design_name], [data[i].design_start, data[i].design_end]);
}
With the create chart making the chart id = to the array 'labels':
const myChart = new Chart(
document.getElementById(labels),
config
);
I am attempting to create a tool that creates an 'n' number of charts in ChartJS and save each of them as images. Currently, designButtonClick() sends the 'event_fky' value to
getDesigns(event_fky) in my controller. This method returns all designs with that foreign key. In turn, the chart plots each design on the chart. I need to evolve this into
something that can make a group individual charts for each design based on how many designs there are. My current solution, still conceptual, is to have methods in my controller
create chart variables 'chartData [data here]' and 'labels[datahere]' while looping through the designs returned from getDesigns, and sending those back to the JS script createChart
'n' number of times for each design. It would also send html chart/html element ids based on the design_name attribute to send back to createChart. This way, it is create a unique
chart 'n' number of times.
To save the charts as images, I would use the same set of element ids generated by getDesigns to send the charts to images using JS' toBase64Image() function and saving them to the
user's system.
Is this the best way of solving this problem? Or is this spaghetti, and is there a better method for this? My attempts to find better online answers have only resulted in docs on
updating one chart dynamically, not creating a dynamic number of charts. Much help is appreciated, code is below as well as a screenshot of the current chart output.
JavaScript:
var labels = [];
var cData = [];
function designButtonClick() {
var event_fky = 3;
$.ajax({
url: 'Tree/getDesigns',
type: 'POST',
data: { event_fky }
}).done(function (data) {
for (i = 0; i < data.length; i++) {
labels.push(data[i].design_name);
cData.push([data[i].design_start, data[i].design_end])
}
createChart(labels, cData);
});
}
function createChart(labels, cData) {
const data = {
labels: labels,
datasets: [{
barThickness: 2,
categoryPercentage: .5,
label: 'Design Time',
data: cData,
backgroundColor: [
'rgba(255, 26, 104, 0.2)'
],
borderColor: [
'rgba(255, 26, 104, 1)'
],
borderWidth: 1,
borderSkipped: false,
borderRadius: 20
}]
};
const config = {
type: 'bar',
data,
options: {
indexAxis: 'y',
scales: {
y: {
beginAtZero: true
},
x: {
min: 0,
max: 6000,
ticks: {
stepSize: 1000
}
}
}
}
};
const myChart = new Chart(
document.getElementById('myChart'),
config
);
}
C# Controller:
public ActionResult getDesigns(int? event_fky)
{
var designs = from e in _context.designs
where (event_fky.HasValue ? e.event_fky == event_fky : e.event_fky == null)
select new
{
design_pky = e.design_pky,
design_name = e.design_name,
design_start = e.design_start,
design_end = e.design_end
};
return this.Json(designs, JsonRequestBehavior.AllowGet);
}
Designs Table:
--------Design--------
design_pky |int
event_fky |int
design_name |varchar
design_start |number
design_end |number
Screenshot of Chart
This is a working answer for the javascript:
var eventList = function () {
var tmp = null;
$.ajax({
'async': false,
url: 'Tree/getEventIDs',
type: 'POST',
data: {},
'success': function (data) {
tmp = data;
}
});
return tmp;
}();
for (var i = 0; i < eventList.length; i++) {
event_fky = eventList[i].event_pky;
event_name = eventList[i].event_name;
event_length = eventList[i].event_end;
var designList = function () {
var tmpi = null;
$.ajax({
'async': false,
url: 'Tree/getDesigns',
type: 'POST',
data: {event_fky},
'success': function (data1) {
tmpi = data1;
}
});
console.log(event_fky);
console.log(tmpi);
return tmpi;
}();
var dLabels = [];
var dLengths = [];
for (var j = 0; j < designList.length; j++) {
dLabels.push(designList[j].design_name);
dLengths.push([designList[j].design_start, designList[j].design_end]);
}
const newCanvas = document.createElement("canvas");
newCanvas.id = event_name;
const currentDiv = document.getElementById("chartSpace");
var parentDiv = document.getElementById("gridHere");
parentDiv.insertBefore(newCanvas, currentDiv);
if (dLabels.length != 0) {
createChart(dLabels, dLengths, event_name, event_length);
}
}
}
function createChart(labels, cData, evName, evLen) {
// setup
const data = {
labels: labels,
datasets: [{
barThickness: 4,
categoryPercentage: .5,
label: evName,
data: cData,
backgroundColor: [
'rgba(' + Math.random() * 85 + ', ' + Math.random() * 170 + ', ' + Math.random() * 255 + ', 1)'
],
borderColor: [
'rgba(255, 26, 104, 1)'
],
borderWidth: 0,
borderSkipped: false,
borderRadius: 20
}]
};
// config
const config = {
type: 'bar',
data,
options: {
indexAxis: 'y',
scales: {
y: {
beginAtZero: true
},
x: {
min: 0,
max: evLen,
ticks: {
stepSize: 100
}
}
}
}
};
// render init block
const myChart = new Chart(
document.getElementById(evName),
config
);
return myChart;
}

add a unit to label with chartjs plugin datalabels

I am opening a new question following Rohit's answer on this question Chart.js Show labels on Pie chart
Is it possible to modify the labels with the plugin chartjs datalabels, for exemple add a unit to it?
Thanks for your answer!
EDIT:
Following Ty answer, I tried this:
if(type==='pie'){
GraphOpt['plugins'] = {
datalabels: {
formatter: (item) => {
return item + ' ' + label_unit;
}
}
}
}
else{
GraphOpt['tooltips'] = {
callbacks: {
label: (item) => `${item.yLabel} ${label_unit}`,
}
}
}
but it's still not showing the units on the graph.
Also, I can't get rid of the values showing on bar graphes with the plugin
EDIT2: My graph function
function setChart(Graph, ctx, type, graph_data, labels, title, label_graph, label_unit){
var GraphOpt = {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
title: {
display:true,
text: title,
},
tooltips: {},
plugins : {}
}
//graph config
var Graph_config = {
type: type,
data: {
labels: labels,
datasets: [{
label: label_graph,
data: graph_data,
backgroundColor: backgroundColor,
borderColor: backgroundColor,
borderWidth: 1
}]
},
options: GraphOpt,
}
if(type==='pie'){
GraphOpt['plugins'] = {
datalabels: {
formatter: (item) => {
return item + ' ' + label_unit;
}
}
}
}
else{
GraphOpt['tooltips'] = {
callbacks: {
label: (item) => `${item.yLabel} ${label_unit}`,
}
}
}
//create a new graph if graph doesn't exist (init)
console.log(typeof window[Graph])
if(typeof window[Graph] === "undefined") {
window[Graph] = new Chart(ctx, Graph_config);
console.log(typeof window[Graph])
//update if graph exist
}else{
console.log('Graph Update')
//updating with new chart data
window[Graph].config=Graph_config;
window[Graph].title=title;
//redraw the chart
window[Graph].update();
}
}
Yes, have a look at the chartjs-plugin-datalabels documentation. In particular, you want to use the options.plugins.datalabels.formatter option to specify a custom function and append units to every label string.
Here's an example Chart.js config that adds a unit to every data label:
{
type: 'pie',
data: {
datasets: [
{
data: [84, 28, 57, 97],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
],
label: 'Dataset 1',
},
],
labels: ['Red', 'Orange', 'Yellow', 'Green'],
},
options: {
plugins: {
datalabels: {
formatter: (val) => {
return val + ' kg';
}
}
}
}
}

How can I highlight a particular datapoint in chartjs, where my data is coming from json array?

// here I am taking another json encoded data from phpfile
$(document).ready(function () {
showGraph();
});
function showGraph()
{
{
$.post("phpfile.php",
function (data)
{
console.log(data);
var name = [];
var marks = [];
var height=[];
//and here as I couldn't encode two json array's separetly I'm declaring it to a variable and then using it
var jsonfile =[{"height":"85","st_name":"Name1"},{"height":"100","st_name":"Name3"},{"height":"92","st_name":"Name4"},{"height":"104","st_name":"Name5"},{"height":"91","st_name":"Name2"},{"height":"99","st_name":"Name6"},{"height":"140","st_name":"AI346"},{"height":"139","st_name":"abc"},{"height":"141","st_name":"def"},{"height":"140","st_name":"ghi"},{"height":"144","st_name":"jkl"},{"height":"130","st_name":"lmn"},{"height":"142","st_name":"opq"},{"height":"132","st_name":"rst"},{"height":"135","st_name":"xyz"},{"height":"135","st_name":"asdfsf"}];
//here I am reading the data from phpfile(1st Json array)
for (var i in data) {
name.push(data[i].st_name);
marks.push(data[i].height);
}
//here i am trying to access data from second json
for (var i=0;i<jsonfile.length;i++){
if(jsonfile[i].height==100)
{ height.push(jsonfile[i].height)}
}
//my graph function ,when I do this I am getting a single point with second json(height variable) but I need to highlight the particular point under a condition... I am not understanding how to do it.
var chartdata = {
labels: name,
datasets: [
{
label: 'height',
fill:false,
lineTension:0.5,
backgroundColor: '#5B2C6F',
borderColor: '#5B2C6F',
pointHoverBackgroundColor: '#5B2C6F',
pointHoverBorderColor: '#5B2C6F',
data: marks
//data:height
},
{
label: 'weight',
fill:false,
lineTension:0.1,
backgroundColor: '#C0392B',
borderColor: '#C0392B',
pointHoverBackgroundColor: '#C0392B',
pointHoverBorderColor: '#C0392B',
data:height,
//data:height
}
]
};
var graphTarget = $("#graphCanvas");
var lineGraph = new Chart(graphTarget, {
type: 'line',
data: chartdata,
options :{
scales:{
xAxes: [{
display: false //this will remove all the x-axis grid lines
}]
}
}
});
});
}
}
</script>
i will try to improve this.
var data =[{"height":"85","st_name":"Name1","color":"rgba(85, 85, 255, 255)"},{"height":"100","st_name":"Name3","color":"rgba(255, 0, 0, 2)"},{"height":"92","st_name":"Name4","color":"rgba(85, 85, 255, 255)"},{"height":"104","st_name":"Name5","color":"rgba(85, 85, 255, 255)"}];
var height = [];
var label = [];
var color = [];
for(i = 0; i<data.length; i++){
height.push(data[i]['height']);
label.push(data[i]['st_name']);
color.push(data[i]['color']);
}
var ctx = document.getElementById('myLine').getContext('2d');
var myLineChart = new Chart(ctx, {
type: 'line',
data: {
labels: label,
datasets: [{
data: height,
pointBorderColor: color,
}]
}
});

Update ChartJs chart using $scope in AngularJS

I'm having some issues when trying to update a chart's data using $scope.
I know there's a function to update charts myChart.update(); but I can't get to update the char when I put it in a $scope.
The following code gets the chart's data and then tries to update the chart. The problem comes at $scope.lineChart.update();. It looks like chartjs can't detect any changes.
The following code is executed after triggering a select, so the chart has an initial data and the following code just tries to update it.
This does not work: $scope.lineChart.update();
$scope.getLineChartMaxData().then(function () {
$scope.getLineChartMinData().then(function () {
$scope.lineChart.update();
});
});
The chart function:
$scope.fillLineChart = function () {
console.log("FILLING LINE CHART");
const brandProduct = 'rgba(0,181,233,0.5)'
const brandService = 'rgba(0,173,95,0.5)'
var data1 = $scope.lineChartMaxWeekData;
var data2 = $scope.lineChartMinWeekData;
var maxValue1 = Math.max.apply(null, data1)
var maxValue2 = Math.max.apply(null, data2)
var minValue1 = Math.min.apply(null, data1)
var minValue2 = Math.min.apply(null, data2)
var maxValue;
var minValue;
if (maxValue1 >= maxValue2) {
maxValue = maxValue1;
} else {
maxValue = maxValue2;
}
if (minValue1 >= minValue2) {
minValue = minValue2;
} else {
minValue = minValue1;
}
$scope.minValue = minValue;
$scope.maxValue = maxValue;
var ctx = document.getElementById("recent-rep-chart");
if (ctx) {
ctx.height = 250;
$scope.lineChart = new Chart(ctx, {
type: 'line',
data: {
labels: $scope.lineChartMaxWeekLabels,
datasets: [{
label: 'Valor',
backgroundColor: brandService,
borderColor: 'transparent',
pointHoverBackgroundColor: '#fff',
borderWidth: 0,
data: data1
},
{
label: 'My Second dataset',
backgroundColor: brandProduct,
borderColor: 'transparent',
pointHoverBackgroundColor: '#fff',
borderWidth: 0,
data: data2
}
]
},
options: {
maintainAspectRatio: true,
legend: {
display: false
},
responsive: true,
scales: {
xAxes: [{
gridLines: {
drawOnChartArea: true,
color: '#f2f2f2'
},
ticks: {
fontFamily: "Poppins",
fontSize: 12
}
}],
yAxes: [{
ticks: {
beginAtZero: true,
maxTicksLimit: 5,
stepSize: 50,
max: maxValue,
fontFamily: "Poppins",
fontSize: 12
},
gridLines: {
display: true,
color: '#f2f2f2'
}
}]
},
elements: {
point: {
radius: 0,
hitRadius: 10,
hoverRadius: 4,
hoverBorderWidth: 3
}
}
}
});
}
};
UPDATE: $scope.lineChart.destroy(); works well, but I don't want to destroy the chart and build it again because it is built with another sizes.

Chart.js | Trouble refreshing line chart with "setInterval"

Wondering if anyone can help me out please.
I'm having trouble getting a Chart.js LineChart (with AJAX data) to refresh every "X" seconds. I've tried to put the code below in a function inside a setInterval and eventhough it does refresh, it redraws itself in a "zoomed-in" manner...
I want the chart to refresh itself every 10 seconds.
Code:
$.ajax({
type: "POST",
url: '#Url.Action("ChartRT")',
contentType: "application/json",
dataType: "json",
success:
function (chartsdata_RT) {
var aData = chartsdata_RT;
var aLabels = aData.map(c => c.Period);
var aDatasets1 = aData.map(c => c.FAILED);
var aDatasets2 = aData.map(c => c.OTHER);
var aDatasets3 = aData.map(c => c.DELIVERED);
var aDatasets4 = aData.map(c => c.PENDING);
var dataT = {
labels: aLabels,
datasets: [
{
label: "FAILED",
data: aDatasets1,
borderColor: '#ff0000',
backgroundColor: "rgba(255, 0, 0, 0.3)",
fill: 'true'
},
{
label: "OTHER",
data: aDatasets2,
borderColor: '#3366ff',
backgroundColor: "rgba(32, 162, 219, 0.3)",
},
{
label: "DELIVERED",
data: aDatasets3,
borderColor: '#009900',
backgroundColor: "rgba(0, 102, 0, 0.3)",
},
{
label: "PENDING",
data: aDatasets4,
borderColor: '#ff9900',
backgroundColor: "rgba(255, 153, 219, 0.3)",
}
]
};
var ctx = $("#chart_last2hours").get(0).getContext("2d");
ctx.canvas.height = "50";
var myNewChart = new Chart(ctx, {
type: 'line',
data: dataT,
options: {
legend: {
display: true,
position: 'right',
fullWidth: false,
labels: {
fontColor: '#484848',
fontsize: 10,
boxWidth: 20,
padding: 5,
lineWideth: 0
}
}
}
});
}
});
ended wrapping the above code in a function (drawChartRT). I then:
1) Call the function drawChartRT() to draw the LineChart.
2) Inside a setInterval remove and add the canvas and call the function drawChartRT().
drawChartRT();
setInterval(function () {
$("canvas#chart_last2hours").remove();
$("#chartdiv_rt").append('<canvas id="chart_last2hours" height="200"></canvas>');
var div = document.getElementById("chart_last2hours");
//console.log(div);
drawChartRT();
}, 10000);
Works like a charm!
Thanks. José

Categories