Chartjs cannot read property datasets of undefined - javascript

I'm developing a React app and trying to get Chartjs to work having imported it from its npm package. The following is the initialization code:
//in my constructor
this.usageChart = null;
//in componentDidMount
let chartContext = document.getElementById("proc_usage");
let initialDataIdle = [100, 100, 100, 100, 100, 100, 100, 100, 100, 100];
let initialDataOther = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
console.log("Creating chart");
this.usageChart = new Chart(chartContext, {
type: "line",
data: {
datasets: [
{ label: "User", fill: true, data: initialDataOther, backgroundColor: "rgba(244, 143, 177, 0.8)" },
{ label: "System", fill: true, data: initialDataOther, backgroundColor: "rgba(255, 235, 59, 0.8)" },
{ label: "IRQ", fill: true, data: initialDataOther, backgroundColor: "rgba(100, 181, 246, 0.8)" },
{ label: "Idle", fill: true, data: initialDataIdle, backgroundColor: "rgba(150, 150, 150, 0.4)" }
]
},
options: {
scales: {
xAxes: [{ stacked: true }],
yAxes: [{ stacked: true }]
},
plugins: {
stacked100: { enable: true }
}
}
});
console.log("Chart created: " + this.usageChart.data);
The problem is when I try to update the chart, this.usageChart.data is undefined. In fact, in that last console.log() call during initialization, it is also undefined. I cannot see what I am doing wrong. I am loading a plugin which allows a Line chart to be drawn as stacked with area between lines representing percentage. I don't know if perhaps this plugin is the issue, but I am getting no errors about it and the code was more or less taken verbatim from the plugin's example code.
Here is the code where I update the chart:
//from componentDidUpdate
usages['User'] = userUsage;
usages['System'] = sysUsage;
usages['IRQ'] = irqUsage;
usages['Idle'] = idleUsage;
console.log("updating chart");
this.usageChart.data.datasets.forEach((dataset) => {
dataset.data.shift();
dataset.data.push(usages[dataset.label]);
});
this.usageChart.update();
console.log("chart updated");

I did create a fiddle (I just copied your code into custom component) for you and there is no error in my case.
I think that there is error around here, as your chart is not being updated properly:
this.usageChart.data.datasets.forEach((dataset) => {
dataset.data.shift();
dataset.data.push(usages[dataset.label]);
});
Corrected version:
You were wrongly updating the data sets:
this.usageChart.data.datasets.forEach((dataset) => {
dataset.data.shift();
dataset.data = usages[dataset.label];
});
Please check the working fiddle and compare it with your project.

Turns out I was using the wrong npm package.
Be warned, somebody has been sitting for 2 years on the name chartjs with an outdated, stale github repo. The real package name is chart.js. Very annoying.

Related

Javascript - ChartJS Trying to change the borderColor and the fill color for negative numbers

I am currently creating a LineChart with ChartJs that represents someone's profits. These can be positive or negative.
I want the lines above zero to be green and the lines below to be red. Also, the background must be red when the values are negative.
I have already seen solutions with previous versions of ChartJS on Stackoverflow, however I think with this new version there may be a more elegant version to solve this problem.
I am using version 4.2.0 of chartJs.
Here is what I have done so far following the documentation :
const lineChartData = {
labels: [
'01/22',
'02/22',
'03/22',
'04/22',
'05/22',
'06/22',
'07/22',
'08/22',
],
datasets: [
{
label: 'Profit',
data: [2.4, 2.6, 2.23, 1.2, -2.2, -3.6, -1, 0.2],
borderColor: '#0B9564',
pointBackgroundColor: 'transparent',
pointBorderColor: 'transparent',
borderJoinStyle: 'bevel',
fill: {
target: 'origin',
above: 'rgba(11, 149, 100, 0.08)',
below: 'rgba(218, 96, 96, 0.08)',
},
},
],
};
And here is the actual result:
Finally, here is a preview of what I would like it to look like. This is a mock-up, so it's not quite accurate.
Do you have any idea how I could reproduce this? It doesn't seem to me that in the documentation this problem is mentioned.
Out of the Box you won't be able to do this, the best would be something like in the example below (using the segementation styling, and a linear color gradient).
If you really need this exact look, you could check if someone has created a plugin, or create your own plugin (or inline plugin), that would override the normal rendering. (link to plugin documentation).
Here a demo, how I would do this:
(based on the configuration, you posted)
const lineChartData = {
labels: [
'01/22',
'02/22',
'03/22',
'04/22',
'05/22',
'06/22',
'07/22',
'08/22',
],
datasets: [
{
label: 'Profit',
data: [2.4, 2.6, 2.23, 1.2, -2.2, -3.6, -1, 0.2],
borderColor: '#0B9564',
pointBackgroundColor: 'transparent',
pointBorderColor: 'transparent',
borderJoinStyle: 'bevel',
fill: {
value: -25,
above: 'rgba(11, 149, 100, 0.08)'
},
segment: {
borderColor: ctx => segmentColor(ctx, '#0B9564', 'red')
}
},
],
};
function segmentColor(ctx, color1, color2){
if(ctx.p0.parsed.y < 0 && ctx.p1.parsed.y < 0 ) {
return color2;
} else if (ctx.p0.parsed.y < 0){
var gradient = myChart.ctx.createLinearGradient(ctx.p0.x, ctx.p0.y, ctx.p1.x, ctx.p1.y);
gradient.addColorStop(.5, color2);
gradient.addColorStop(1, color1);
return gradient
} else if (ctx.p1.parsed.y < 0){
var gradient = myChart.ctx.createLinearGradient(ctx.p1.x, ctx.p1.y, ctx.p0.x, ctx.p0.y);
gradient.addColorStop(.5, color2);
gradient.addColorStop(1, color1);
return gradient
}
return color1
}
const config = {
type: 'line',
data: lineChartData,
options: {
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
labels: {
usePointStyle: true,
},
}
},
}
};
var myChart = new Chart(
document.getElementById('chart'),
config
);
<script src="//cdn.jsdelivr.net/npm/chart.js"></script>
<div class="chart" style="height:184px; width:350px;">
<canvas id="chart" ></canvas>
</div>

Chartjs using array in label field

I am making a simple bar chart using Chartjs 3.x
I make requests to a server to fetch json data and then store certain parts of it into arrays, here is the code for this:
serverData = JSON.parse(http.responseText);
console.log(serverData);
stundenProjekt = serverData.abzurechnen.nachProjekt.map((s) => {
return s.second.summe;
});
labelsP = serverData.abzurechnen.nachProjekt.map((p) => {
return p.first;
});
I then want to use these arrays in the data and label fields of the chart. I'm using stundenProjekt as data and it works fine, but when I use labelsP as label for the chart, it doesn't work. Here is the code of the chart:
const data = {
labels: labelsP,
datasets: [{
label: 'Projekte',
data: stundenProjekt,
backgroundColor: [
'#f4f40b',
],
borderColor: [
'#B1B101',
],
borderWidth: 3,
}]
};
if (barChartProjekt) {
data.datasets.forEach((ds, i) => {
barChartProjekt.data.datasets[i].data = ds.data;
barChartProjekt.labels = newLabelsArray;
})
barChartProjekt.update();
} else {
barChartProjekt = new Chart(chart, {
type: 'bar',
data: data,
options: {
responsive: true,
plugins: {
legend: {
labels: {
color: "white",
font: {
size: 18
}
}
}
},
scales: {
y: {
ticks: {
color: "white",
font: {
size: 18,
},
stepSize: 1,
beginAtZero: true
}
},
x: {
ticks: {
color: "white",
font: {
size: 14
},
stepSize: 1,
beginAtZero: true
}
}
}
}
});
}
The only workaround I have found is to copy the contents of labelsP and paste them in the label field. These are the contents of labelsP and how I did the workaround:
["nexnet-SB-Cloud", "AUTEC - PSK-System²", "Fritzsche", "nexnet-eBalance", "IfT - Neuentwicklung", "wattform", "Migration", "Fahrwerkregelkreis", "bmp greengas", "nexnet-SQL-Abfragen über API", "lambda9", "Nord Stadtwerke", "edifact", "SOLVIT", "BürgerGrünStrom", "SOLVCPM", "lambda captis", "SOLVEDI", "green city power", "max.power"]
const data = {
labels: ["nexnet-SB-Cloud", "AUTEC - PSK-System²", "Fritzsche", "nexnet-eBalance", "IfT - Neuentwicklung", "wattform", "Migration", "bmp greengas", "Fahrwerkregelkreis", "nexnet-SQL-Abfragen über API", "lambda9", "Nord Stadtwerke", "edifact", "SOLVIT", "BürgerGrünStrom", "SOLVCPM", "lambda captis", "SOLVEDI", "green city power", "max.power"],
datasets: [{
label: 'Projekte',
data: stundenProjekt,
backgroundColor: [
'#f4f40b',
],
borderColor: [
'#B1B101',
],
borderWidth: 3,
}]
};
In this way, the chart works and everything shows up as it should, however, I want to use it as shown in the first snippet of code, as labelsP gets updated every some seconds with new data extracted from the server. So, why is it that if I put labelsP alone in the label field it doesn't work, but if I copy and paste the contents of labelsP in the label field, it does work?
The problem is that you add the labels to the wrong position in the chart configuration.
Instead of...
barChartProjekt.labels = newLabelsArray;
try this...
barChartProjekt.data.labels = newLabelsArray;

vue3 chart-js showing previous data on hover

vue3 chart-js showing previous data on hover.
I updated my chart data filed using API data but after updating the chat data, chart is showing previous API data on hover
i also tried using distory()
here are my codes
methods: {
barChart() {
var Chart = require("chart.js");
let options = new Chart(document.getElementById("bar-chart-grouped"), {
type: "bar",
data: {
labels: this.llabels,
datasets: [
{
label: "पुरुष जनसंख्या",
backgroundColor: "rgba(0, 143, 251, 0.8)",
data: this.mdata,
},
{
label: "महिला जनसंख्या",
backgroundColor: "rgba(0, 227, 150, 0.8)",
data: this.Fdata,
},
{
label: "पअन्य जनसंख्या",
backgroundColor: "rgb(254, 176, 25,0.8)",
data: this.Odata,
},
],
},
});
if (!this.chart) {
this.chart = options;
} else {
this.chart.options = options;
this.chart.update();
}
},
}
Instead of this.chart.options = options;, use this.chart.options = options.options;
The variable options is holding your chart, not only the options.
A better approach would be to only create the chart on the first run of that method (=> place the new Chart inside the if)

Highcharts Solid Gauge Dynamic Update Using JSON

Updated & Resolved, see below.
I have been working on this for several days, searching and reading many tutorials and I am still stuck. Ultimately I am working on a page that will contain multiple solid gauge charts with data supplied by JSON from an SQLITE3 database. The database is updated every minute and I would like to have the chart data update dynamically, not by refreshing the browser page.
For the purpose of my learning, I have reduced this down to one chart.
All current and future data will be arranged as such:
PHP
[{"name":"s1_id","data":[684172]},
{"name":"s1_time","data":[1483097398000]},
{"name":"s1_probe_id","data":["28-0000071cba01"]},
{"name":"s1_temp_c","data":[22.125]},
{"name":"s1_temp_f","data":[71.825]},
{"name":"s2_id","data":[684171]},
{"name":"s2_time","data":[1483097397000]},
{"name":"s2_probe_id","data":["28-0000071d7153"]},
{"name":"s2_temp_c","data":[22.062]},
{"name":"s2_temp_f","data":[71.7116]}]
This is the current layout of my java:
JS
$(function() {
var options = {
chart: {
type: 'solidgauge'
},
title: null,
pane: {
center: ['50%', '90%'],
size: '140%',
startAngle: -90,
endAngle: 90,
background: {
backgroundColor: (Highcharts.theme && Highcharts.theme.background2) || '#EEE',
innerRadius: '60%',
outerRadius: '100%',
shape: 'arc'
}
},
tooltip: {
enabled: false
},
// the value axis
yAxis: {
stops: [
[0.10, '#2b908f'],//Blue
[0.35, '#55BF3B'],//Green
[0.65, '#DDDF0D'],//Yellow
[0.90, '#DF5353']//Red
],
lineWidth: 0,
minorTickInterval: null,
tickPixelInterval: 1000,
tickWidth: 0,
title: {
y: -70
},
labels: {
y: 16
},
min: 0,
max: 1000000,
title: {
text: 'Degree C'
}
},
plotOptions: {
solidgauge: {
dataLabels: {
y: -10,
borderWidth: 0,
useHTML: true
}
}
},
series: []
};
var gauge1;
$.getJSON('sgt3.php', function(json){
options.chart.renderTo = 'chart1';
options.series.push(json[0]);
gauge1 = new Highcharts.Chart(options);
});
});
I was using information from this post but it leaves off the dynamic update aspect. As I mentioned before, I will have more charts rendering to div ids, all coming from the one JSON array, which is why I have referenced the following link:
Multiple dynamic Highcharts on one page with json
If anyone has an idea how to dynamically update this please let me know. I have tried several setInterval methods but all they seem to do is redraw the chart but no data is updated.
Update:
I spent a while doing some more iterations and resolved before coming back here. I changed each gauge to have their own function such as:
$('#gauge0').highcharts(Highcharts.merge(options, {
yAxis: {
min: 15,
max: 30,
tickPositions: [15, 20, 25, 30],
title: {
text: 'Table'
}
},
credits: {
enabled: false
},
series: [{
data: [30],
dataLabels: {
y: 20,
format: '<div style="text-align:center"><span style="font-size:48px;color:' +
((Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black') + '">{y:.3f}</span><br/>' +
'<span style="font-size:12px;color:silver">Degree C</span></div>'
},
tooltip: {
valueSuffix: 'Tooltip 1'
}
}]
}));
Then got the setInterval to work by assigning to each gauge respectively. I have added a lot more info than just the two I referenced but each var and setData can be added respectively.
// Bring life to the dials
setInterval(function() {
$.ajax({
url: 'data_temps.php',
success: function(json) {
var chart0 = $('#gauge0').highcharts();
var chart1 = $('#gauge1').highcharts();
// add the point
chart0.series[0].setData(json[3]['data'],true);
chart1.series[0].setData(json[8]['data'],true);
},
cache: false
})
}, 1000)
Hopefully this can help someone in the future. This may not be the most efficient way but its working great right now. Thanks again everyone for your suggestions.
You may try something like this:
change:
var gauge1;
$.getJSON('sgt3.php', function(json){
options.chart.renderTo = 'chart1';
options.series.push(json[0]);
gauge1 = new Highcharts.Chart(options);
});
to:
options.chart.renderTo = 'chart1';
var gauge1 = new Highcharts.Chart(options);
$.getJSON('sgt3.php', function(json){
gauge1.series[0].points.length = 0;
gauge1.series[0].points.push(json[0]);
});
That is, updating the existing series on a chart instead of re-creating it.
As I've mentioned in the comment before, highcharts provide an example of dynamically updated gauge:
http://jsfiddle.net/gh/get/jquery/3.1.1/highslide-software/highcharts.com/tree/master/samples/highcharts/demo/gauge-solid/

undefined is not an object (evaluating 'b.length') in Safari

I am using AmCharts to display a graph on my site.
it worked in Safari on Aug 29th. Havn't changed anything except some text underneath the graph since then. (checked my git history).
But somehow, when I checked it today, the graph was broken.
I am getting the following error:
TypeError: undefined is not an object (evaluating 'b[0].time')
from Serial.js line 24-25
Serial.js is a linechart library from AmCharts I am using. I havn't updated it or changed anything in it either.
I have no idea why I started getting this error. It seems to be related to the data given to the chart component.
when I comment out the AmCharts react component, it works fine.
Along with the error an AJAX call is made, which fetches chart data from an API also fails, somehow running the same Serial.js code.
I tried forcing it to not put the chart on the page until all of the data has been retrieved.
if (chartData[0]) {
if (chartData[0].market) {
console.log(chartData);
chart = (
<div id="result-chart" className={this.state.chartClass}>
{React.createElement(AmCharts.React, config)}
</div>
)
}
}
The error occours when the following AJAX call is running:
getAnnualData() {
$.ajax(`https://www.quandl.com/api/v1/datasets/YAHOO/INDEX_GSPC.json?trim_start=1970-01-01&collapse=monthly&column=4&auth_token=XXXXX`)
.then((r) => {
let fixedData = r.data.map((point) => {
return point[1].toFixed(0)
})
fixedData = fixedData.reverse()
let percent = null;
for(var e = 0; e < fixedData.length; e++) {
if(e < 1) {
percent = (25000 * 100) / fixedData[0] / 100;
}
fixedData[e] = Math.floor(percent * fixedData[e]);
}
this.set('annualData', fixedData)
})
.fail((e) => {
console.error('Failed fetching QUANDL DATA', e)
})
},
the fail 'Failed fetching QUANDL DATA' is running. The error passed a long here is the same as first mentioned.
Looking at the stack trace the error occours at the following event:
onDataUpdated — serial.js:25:412
For that code to even run, I'd assume that the data must be there. Otherwise it should never run any AmCharts code.
The weirdest thing however, is that this only happens on Safari... It works fine on Chrome, Edge & Firefox...
Debugging this has also proven extremely difficult, as none of my console logs shows up on this page on Safari.
However it is definitely connected to the Ajax call somehow. I have other AmCharts graphs on another page. ALl the ones that use data from that AJAX call is broken in Safari. However the linecharts not using that dataset works fine... (all of them works fine in other browsers however)
I also checked the actual data I am getting back and using from Quandl. and the data looks fine.
I tried manually setting the market keys on the chartData to nothing. And the code to put the graph on the page never runs and there's no errors. I then also tried setting it to a fixed number. Everytime. The code ran and I still got the error. So it's definitely not due to "bad" API data.
I am using Babel to compile the ES6 code.
Here's the code used to generate the chartData:
let basicData = store.plans.get('basic').get('annualData')
let premiumData = store.plans.get('premium').get('annualData')
let businessData = store.plans.get('business').get('annualData')
// let fundData = store.plans.get('fund').get('annualData')
let marketData = store.market.data.get('annualData')
let fixedData = basicData.map((point, i) => {
let premiumBalance = 0
let businessBalance = 0
// let fundBalance = 0
let marketBalance = 0
if (premiumData[i]) { premiumBalance = premiumData[i].balance }
if (businessData[i]) { businessBalance = businessData[i].balance }
// if (fundData[i]) { fundBalance = fundData[i].balance }
if (marketData[i]) { marketBalance = marketData[i] }
return {
basic: point.balance,
premium: premiumBalance,
business: businessBalance,
// fund: fundBalance,
market: marketBalance,
basicBalloon: formatPrice(point.balance),
premiumBalloon: formatPrice(premiumBalance),
businessBalloon: formatPrice(businessBalance),
// fundBalloon: formatPrice(fundBalance),
marketBalloon: formatPrice(marketBalance),
date: `${point.date.year}-${point.date.month}-${point.date.day}`
}
})
function formatPrice(value) {
while(/(\d+)(\d{3})/.test(value.toString())) {
value = value.toString().replace(/(\d+)(\d{3})/, '$1'+','+'$2');
}
let price = '$' + value;
return price;
}
let chartData = []
if (this.state.animate && this.state.fetched) {
chartData = fixedData;
}
In case it is relevant here's the config for the chart:
let config = {
type: "serial",
theme: "dark",
addClassNames: true,
dataProvider: chartData,
balloon: {
color: '#49494A',
fillAlpha: 1,
borderColor: '#FFFFFF',
borderThickness: 0,
},
graphs: [
{
id: "market",
lineColor: "#49494A",
bullet: "square",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
bulletSize: 5,
hideBulletsCount: 10,
lineThickness: 2,
useLineColorForBulletBorder: true,
valueField: "market",
"balloonText": "<div class=\"chart-balloon\"><span class=\"plan-name market-name\">S&P 500</span><span class=\"balloon-value\">[[marketBalloon]]</span></div>",
},
{
id: "basic",
lineColor: "#FFFFFF",
bullet: "square",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
bulletSize: 5,
hideBulletsCount: 10,
lineThickness: 2,
useLineColorForBulletBorder: true,
valueField: "basic",
balloonText: "<div class=\"chart-balloon\"><span class=\"plan-name\">Basic</span><span class=\"balloon-value\">[[basicBalloon]]</span></div>"
},
{
id: "premium",
lineColor: "#FFFFFF",
bullet: "square",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
bulletSize: 5,
hideBulletsCount: 10,
lineThickness: 2,
useLineColorForBulletBorder: true,
valueField: "premium",
balloonText: "<div class=\"chart-balloon\"><span class=\"plan-name\">Premium</span><span class=\"balloon-value\">[[premiumBalloon]]</span></div>"
},
{
id: "business",
lineColor: "#FFFFFF",
bullet: "square",
bulletBorderAlpha: 1,
bulletColor: "#FFFFFF",
bulletSize: 5,
hideBulletsCount: 10,
lineThickness: 2,
useLineColorForBulletBorder: true,
valueField: "business",
"balloonText": "<div class=\"chart-balloon\"><span class=\"plan-name\">Business</span><span class=\"balloon-value\">[[businessBalloon]]</span></div>",
}
],
valueAxes: [{
logarithmic: true,
unit: '$',
unitPosition: 'left',
gridAlpha: 0.15,
minorGridEnabled: true,
dashLength: 0,
inside: true,
}],
chartCursor: {
valueLineEnabled: true,
valueLineAlpha: 0.5,
fullWidth: true,
cursorAlpha: 0.5
},
categoryField: "date",
categoryAxis: {
parseDates: true,
equalSpacing: true,
},
};
if (store.session.browserType() === 'Safari') {
config.dataDateFormat = "YYYY-M-D",
config.categoryAxis = {
equalSpacing: true,
}
}
And yes, I have tried taking out the Safari specific code.
Any help would be greatly appreciated.
Github history for the file with the graph component: https://github.com/MarkLyck/Formula-Stocks/commits/master/app/scripts/components/home/TheResults.js

Categories

Resources