Closed toduyemi closed 3 months ago
Please add a reproducable sample as required by the issue template, because it seems to work fine with 0 values: https://jsfiddle.net/2gcorjx5/
Here's a deployment of project: https://toduyemi.github.io/weather-app/.
I've included the afterDatasetUpdate plugin from your sandbox in the deployed code. I have also set the data:
callback to log every value before it returns it.
It strange because I included a new dataset array of just 0s and it failed to return NaN.
Steps to reproduce:
Yet the problem remains persistent for that dataset. I'd appreciate any insight you may have.
//chart 1 ===================>
let chart1: Chart, chart2: Chart;
export async function renderChart(forecast: ForecastObj[]) {
const getMaxValueWithPadding = () => {
return (
Math.max(...forecast.map((row) => (row.rain ?? 0) + (row.snow ?? 0))) *
1.1
);
};
const chartCtr = document.querySelector('#temp-chart1') as HTMLCanvasElement;
if (chart1) chart1.destroy();
if (chart2) chart2.destroy();
chart1 = new Chart(chartCtr, {
type: 'line',
plugins: [
ChartDataLabels,
{
afterDatasetUpdate: (chart, args) => {
console.log(
args.meta.data.map((d) => d.y),
args.index,
);
},
},
],
options: {
layout: {
padding: {
bottom: 47.15,
},
},
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: false,
},
},
scales: {
x: {
adapters: {
date: {
locale: enUS,
},
},
grid: {
display: false,
},
type: 'time',
ticks: {
stepSize: 3,
major: {
enabled: true,
},
},
time: {
unit: 'hour',
tooltipFormat: 'HH:mm',
},
position: 'top',
},
yTemp: {
ticks: {
display: false,
},
grid: {
display: false,
},
border: {
display: false,
},
},
yPop: {
display: false,
max: getMaxValueWithPadding(),
},
yLev: {
display: false,
},
},
},
data: {
labels: forecast.map((row) => row.date),
datasets: [
//test data for NaN
{
label: '# of Points',
data: new Array(40).fill(0),
borderWidth: 1,
},
{
type: 'line',
label: 'temp every 3 hrs',
data: forecast.map((row) => row.temp),
yAxisID: 'yTemp',
datalabels: {
display: false,
},
},
{
label: '3h rain level',
data: forecast.map((row) => {
const value = (row.rain ?? 0) + (row.snow ?? 0);
console.log(value);
return value;
// return 0;
}),
yAxisID: 'yPop',
type: 'bar',
datalabels: {
labels: {
description: {
anchor: 'start',
align: 'start',
font: {
size: 8.5,
},
formatter: (value, context) => {
const bar = forecast[context.dataIndex];
const words = bar.weather.description.split(' ');
return [...words];
},
},
precipitation: {
anchor: 'end',
align: 'end',
offset: 15,
font: {
size: 8.3,
weight: 'bold',
},
formatter: (value, context) => {
if (value) return `${value} mm/h`;
else return '';
},
textAlign: 'center',
},
probability: {
anchor: 'end',
align: 'end',
font: {
size: 8.3,
},
formatter: (value, context) => {
const bar = forecast[context.dataIndex];
return `${(bar.pop * 100).toFixed()}%`;
},
textAlign: 'center',
},
},
},
},
],
},
});
// Chart2 ===================>
const chartCtr2 = document.querySelector('#temp-chart2') as HTMLCanvasElement;
chart2 = new Chart(chartCtr2, {
type: 'line',
options: {
maintainAspectRatio: false,
layout: {
padding: {
top: 30,
bottom: 41,
},
},
animation: false,
plugins: {
legend: {
display: false,
},
},
scales: {
x: {
ticks: {
display: false,
},
},
y: {
afterFit: (ctx) => {
ctx.width = 35;
},
ticks: {
callback: (value) => `${value} C`,
},
},
},
},
data: {
labels: forecast.map((row) => row.date),
datasets: [
{
label: 'temp every 3 hrs',
data: forecast.map((row) => row.temp),
},
],
},
});
}
Figured out the source of the problem! It was due to my max
configuration being based on the highest value! Fixed it with an OR coercion.
Expected behavior
-Data-labels (from data-labels plugin) should appear at configured position in relation to bars. If there are no bars, they should appear above the x-axis.
-When all values are zero (thus no bars rendered) Metadata for BarElements should have
y
andbase
properties assigned as they would if there are was a non-zero value.(note that most of the y values are the same indicating that the relevant value for those are 0)
Current behavior
Using the data-labels plugin to set additional information, if every value for my dataset returns as 0 and thus no bars rendered, none of the corresponding data labels render.
The values for the bar graph correspond to the mm label (seen above in expected behaviour). The description at the bottom plus percentages are extra information that do not render, if every bar graph value is 0.
When you look at the top right of the defect sc, you can see a bunch of text smashed into the corner. These are the missing labels. It seems with no bars, the plugin doesn't know how to anchor.
Investigating via the call stack, I found:
If no bars are rendered, the properties are recieved in the plugin ,
y: NaN and base: NaN
of the BarElementswhich cascasdes into breaking the computation of positioning the label in the plugin
I've confirmed that even when the data-value is 0, the properties
y
&base
are assigned a non-falsy value as long as there is one non-zero value in the data-set (see expected behaviour images).Looking in the local scope of
_updateDataSet()
within the Chart class, the y property for each Bar Element has been assigned as NaN.Optional extra steps/info to reproduce
Edge case: Dataset has to return 0 for every
value
Is there way for me to create one dummy value point, that isn't displayed or rendered on the chart? one that will go through the calculation process and allow the property calculations to behave as specified?
Possible solution
-Falsy values such as NaN for coordinate related properties should be type guarded against. -Falsy values such as NaN should be coerced to the appropriate value that would result from a 0 value in the dataset.
Context
My chart calls to an api so my data is dynamic. It does not render as specified for valid data.
chart.js version
v4.4.2
Browser name and version
Chrome
Link to your project
https://github.com/toduyemi/weather-app/tree/OpenWeatherApi