I have the following component which I then import and use in a page. Unfortunately I am getting the error error - TypeError: Cannot read property 'labels' of undefined and data and options are underlined in the line where I pass them to ChartCard. I tried to follow the docs as close as I can but I guess I must be doing something wrong.
import faker from '#faker-js/faker'
import {
CategoryScale,
Chart as ChartJS,
Legend,
LinearScale,
LineElement,
PointElement,
Title,
Tooltip,
} from 'chart.js'
import { Line } from 'react-chartjs-2'
import BaseCard from './BaseCard'
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
)
export async function getStaticProps() {
const options = {
responsive: true,
plugins: {
legend: {
position: 'top' as const,
},
title: {
display: true,
text: 'Price',
},
},
}
const labels = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
]
const data = {
labels,
datasets: [
{
label: 'Dataset 1',
data: labels.map(() => faker.datatype.number({ min: 2000, max: 4000 })),
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.5)',
},
],
}
return {
props: { options, data },
}
}
export default function ChartCard({ options, data }) {
return (
<BaseCard>
<Line options={options} data={data} />
</BaseCard>
)
}
Edit: I've now refactored my code to have the getStaticProps in the index.js page as follows:
#index.tsx
import type { NextPage } from 'next'
import faker from '#faker-js/faker'
import ChartCard from '../components/ChartCard'
export async function getStaticProps() {
const options = {
responsive: true,
plugins: {
legend: {
position: 'top' as const,
},
title: {
display: true,
text: 'IndexCoin Price',
},
},
}
const labels = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
]
const data = {
labels,
datasets: [
{
label: 'Dataset 1',
data: labels.map(() => faker.datatype.number({ min: 2000, max: 4000 })),
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.5)',
},
],
}
return {
props: { options, data },
}
}
const Home: NextPage = (options, data) => {
console.log(options)
console.log(data)
console.log(data.datasets)
return(
<ChartCard options={options} data={data} />
)
}
The console output of this is:
options: { responsive: true, plugins: { legend: [Object], title: [Object] } },
data: {
labels: [
'January', 'February',
'March', 'April',
'May', 'June',
'July'
],
datasets: [ [Object] ]
}
}
{}
undefined
And this crashes because datasets is undefined. Why is this happening?
You can only use getStaticProps in a NextJS page (i.e. component under src/pages/MyPageComponent.js).
(https://nextjs.org/docs/basic-features/data-fetching/get-static-props)
This means that your ChartCard is being called with options=undefined and data=undefined as the getStaticProps never runs.
It certainly would be nice to be able to encapsulate the server-side data fetching and rendering in one component file, but unfortunately this isn't possible right now.
What you'll need to do is to add an api to e.g. src/api/MyApi.js and call that in your component.
E.g. a quick example to start from:
src/api/MyApi.js
export default function MyApi(req, res) {
//... do your data fetching
res.send({options, data})
}
src/components/MyComponent.js
const [result, setResult] = useState();
useEffect(async ()=>{
const res = await fetch('/api/MyApi');
const result = await res.json();
setResult(result);
}, []);
const {options, data} = result ?? {};
Note the above will perform client-side data fetching so will need a live server.
If you're expecting to build static pages at build time then your only option (IIRC) is to capture ALL the data needed in the src/pages/MyPage.js's getStaticProps and pass them into the page's components as needed.
if this code is exact copy of your project there is a typo in it:
position: 'top' as const
try this:
position: 'top as const'
and also use getStaticProps for page components
In my view point, props is an object, so change your code as follow;
const Home: NextPage = ({options, data}) => {
console.log(options)
console.log(data)
console.log(data.datasets)
return(
<ChartCard options={options} data={data} />
)
}
Related
First let me clarify, i'm new to vue.
What I am trying to do is, when I get data from the defineProps. I want that the data from the defineProps goes to my script. Only I cant get the data over there. It is only available in the <template> {{ graph }} </template>. Notice that i can access the data with {{ graph }} in there. I just cant find a way to use it in export default { }
So my script is looking like this
<script setup>
const props = defineProps(['graph']);
</script>
<template>
{{ graph }} // Works
</template>
<script>
console.log(props); // Doesnt work or
console.log(graph); // Doesnt work
export default {
}
</script>
So, how do I get the data from props(graph) into export default { }?
Why do I want do this?
I saw https://vue-chartjs.org/examples/ and I followed it.
export default {
name: 'BarChart',
components: { Bar },
data() {
return {
chartData: {
labels: [ 'January', 'February', 'March' ],
datasets: [ { data: [3, 20, 12] } ]
},
chartOptions: {
responsive: true
}
}
}
}
Notice the data: [3, 20, 12]. I want to edit the values with the data I get. For example data: [props.day1, props.day2, props.day3]
Like commented, use just script setup, try like following:
<template>
<Bar :data="chartData" :options="chartOptions" />
</template>
<script setup>
import { Bar } from 'vue-chartjs'
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js'
import { ref } from 'vue'
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
const chartData = ref({
labels: [ 'January', 'February', 'March'],
datasets: [
{
label: 'Data One',
backgroundColor: '#f87979',
data: [40, 20, 12]
}
]
})
const chartOptions = ref({
responsive: true,
maintainAspectRatio: false
})
</script>
demo HERE
I am currently trying to add a gradient background with a slight transparency to a radar graph inside a react component. I have only been able to find solutions for this problem by referencing a chartjs canvas within an html file, but no solutions via a react component in a tsx/jsx file.
Desired Result I am looking for, and the
Current Result
The code below is used to achieve the current result.
import { Chart } from 'react-chartjs-2';
import { Chart as ChartJS, LineController, LineElement, PointElement, LinearScale, Title, Filler, RadialLinearScale } from 'chart.js';
ChartJS.register(LineController, RadialLinearScale, LineElement, PointElement, LinearScale, Filler, Title);
const labels = ['Thing 1', 'Thing 2', 'Thing 3', 'Thing 4', 'Thing 5', 'Thing 6'];
const chartData = [5, 3, 4, 5, 2, 4];
const data = {
labels: labels,
datasets: [
{
data: chartData,
fill: true,
backgroundColor: 'pink',
borderColor: 'transparent',
pointBorderColor: 'transparent',
pointBackgroundColor: 'transparent',
},
],
};
const options = {
scales: {
r: {
grid: {
lineWidth: 2,
},
angleLines: {
lineWidth: 2,
},
gridLines: {
display: false
},
ticks: {
display: false,
maxTicksLimit: 1
},
}
}
}
const RadarGraph = () => {
return (
<Chart type="radar" data={data} options={options}/>
)
}
export default RadarGraph;
You will need to use a scriptable function for the backgroundColor which contains the chart which contains the context like so:
const data = {
labels: labels,
datasets: [
{
data: chartData,
fill: true,
backgroundColor: ({chart: {ctx}}) => {
const bg = ctx.createLinearGradient(paramters);
// More config for your gradient
return bg;
}
},
],
};
I am building a react app using ChartJs and customizing each point on the line graph with a custom image. The image is accessed through a web address as shown below.
Essentially the getTopPlayersImages function returns an array of Image objects containing the src of the image.
const getTopPlayersImages = async (labels) => {
let images = await Promise.all(
labels.map(async (year, index) => {
let playerId = await getPlayerId(graphData[3][index].player);
let img = new Image(82.11, 60);
img.src = `https://ak-static.cms.nba.com/wp-content/uploads/headshots/nba/latest/260x190/${playerId}.png`;
img.onerror = ({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src = require("../assets/logoman.png");
};
return img;
})
);
return images;
};
The issue is that sometimes when most or all of the images are found through the web address, the images are displayed on the line chart without issues. But if I select a category where the majority images are not found and have to use the local image ../assets/logoman.png, then I get the following error:
Uncaught DOMException: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state.
at drawPoint (http://localhost:3000/static/js/bundle.js:17504:11)
at PointElement.draw (http://localhost:3000/static/js/bundle.js:10609:67)
at LineController.draw (http://localhost:3000/static/js/bundle.js:2967:17)
at LineController.draw (http://localhost:3000/static/js/bundle.js:4509:11)
at Chart._drawDataset (http://localhost:3000/static/js/bundle.js:9363:21)
at Chart._drawDatasets (http://localhost:3000/static/js/bundle.js:9333:12)
at Chart.draw (http://localhost:3000/static/js/bundle.js:9294:10)
at http://localhost:3000/static/js/bundle.js:1860:15
at Map.forEach (<anonymous>)
at Animator._update (http://localhost:3000/static/js/bundle.js:1833:18)
The head-scratching part is that this error doesn't happen 100% of the time. Sometimes everything works as expected, but more often than not, this error completely causes the app to crash and the chart to be removed from the DOM.
I think what is happening is that the chart is trying to display the images before they are fully loaded. How can I prevent ChartJs from rendering the images on the graph before they have had a chance to load?
Here is how I am rendering the images in chartJs.
const data = {
labels,
datasets: [
{
fill: "-1",
backgroundColor: "rgba(10, 162, 235, 0.5)",
label: "100th percentile",
data: graphData[3]?.map((item) => item[props.statSelection]),
playerName: graphData[3]?.map((item) => item.player),
pointStyle: topPlayerImages,
},
],
};
I am using pointStyle and the topPlayerImages is a state within my component which gets updated in a useEffect hook like so:
useEffect(() => {
const updateImages = async () => {
setTopPlayerImages([]);
let images = await getTopPlayersImages(labels);
setTopPlayerImages(images);
};
updateImages();
}, [graphData]);
Here is the full code for this component if that makes it a little bit clearer.
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Filler,
Legend,
} from "chart.js";
import { Line } from "react-chartjs-2";
import { useEffect, useState } from "react";
import { main } from "../utils/statCalcs";
import { getPlayerId } from "../utils/getPlayerId";
const Chart = (props) => {
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Filler,
Legend
);
const getTopPlayersImages = async (labels) => {
let images = await Promise.all(
labels.map(async (year, index) => {
let playerId = await getPlayerId(graphData[3][index].player);
let img = new Image(82.11, 60);
img.src = `https://ak-static.cms.nba.com/wp-content/uploads/headshots/nba/latest/260x190/${playerId}.png`;
img.onerror = ({ currentTarget }) => {
currentTarget.onerror = null;
currentTarget.src = require("../assets/logoman.png");
};
return img;
})
);
return images;
};
const options = {
responsive: true,
plugins: {
tooltip: {
callbacks: {
label: function (context) {
let label = context.dataset.label || "";
if (context.dataset.label === "100th percentile") {
label += ": ";
label += context.dataset.playerName[context.dataIndex];
}
return label;
},
},
},
legend: {
position: "top",
},
title: {
display: true,
text: "Chart.js Line Chart",
},
},
};
const labels = props.playerData.map((item) => {
return item.Year;
});
let [graphData, setGraphData] = useState([]);
let [topPlayerImages, setTopPlayerImages] = useState([]);
useEffect(() => {
let getData = async (arrayOfPercentiles) => {
// Clear state on re-render
setGraphData([]);
// Loop through the percentiles and fetch the results and save in state.
let percentileDataArray = [];
for (let percentile of arrayOfPercentiles) {
let data = await Promise.all(
labels.map((year) => {
return main(year, props.statSelection, percentile);
})
);
percentileDataArray.push(data);
}
setGraphData(percentileDataArray);
};
getData([25, 50, 75, 100]);
}, [props.statSelection, props.playerData]);
useEffect(() => {
const updateImages = async () => {
setTopPlayerImages([]);
let images = await getTopPlayersImages(labels);
setTopPlayerImages(images);
};
updateImages();
}, [graphData]);
const data = {
labels,
datasets: [
{
label: "Dataset 1",
data: props.playerData.map((item) =>
parseFloat(item.Data[0][props.statSelection])
),
borderColor: "rgb(255, 99, 132)",
backgroundColor: "rgba(255, 99, 132, 0.5)",
},
{
fill: true,
backgroundColor: "rgba(53, 162, 235, 0.5)",
label: "25th percentile",
data: graphData[0]?.map((item) => item[props.statSelection]),
},
{
fill: "-1",
label: "50th percentile",
data: graphData[1]?.map((item) => item[props.statSelection]),
},
{
fill: "-1",
backgroundColor: "rgba(53, 162, 235, 0.5)",
label: "75th percentile",
data: graphData[2]?.map((item) => item[props.statSelection]),
},
{
fill: "-1",
backgroundColor: "rgba(10, 162, 235, 0.5)",
label: "100th percentile",
data: graphData[3]?.map((item) => item[props.statSelection]),
playerName: graphData[3]?.map((item) => item.player),
pointStyle: topPlayerImages,
},
],
};
return (
<div id="chart">
<Line options={options} data={data} />
</div>
);
};
export default Chart;
Any help is greatly appreciated!
I am trying to create a chart.js line chart that shows public traffic ticket data. I have a component that calls a helper datareader js function to load the data. The issue I am facing that I am not doing the async part correctly, and the data loads after the chart is rendered.
Here is the component
import React, { useState, useEffect } from 'react';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import { Line } from 'react-chartjs-2';
import faker from 'faker';
import {GetParkingTicketTimes} from '../helper/DataReader.js'
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
);
export const options = {
responsive: true,
plugins: {
legend: {
position: 'top'
},
title: {
display: true,
text: 'Chart.js Line Chart',
},
},
};
const labels = ['00:00', '01:00', '02:00', '03:00', '04:00', '05:00', '06:00', '07:00', '08:00', '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '18:00', '19:00', '20:00', '21:00', '22:00', '23:00'];
export const data = {
labels,
datasets: [
{
label: '2016',
data: labels.map(() => faker.datatype.number({ min: -1000, max: 1000 })),
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.5)',
},
{
label: 'Dataset 2',
data: labels.map(() => faker.datatype.number({ min: -1000, max: 1000 })),
borderColor: 'rgb(53, 162, 235)',
backgroundColor: 'rgba(53, 162, 235, 0.5)',
},
],
};
export function TimeOfDayTickets() {
useEffect(() => {
const fetchData = async () => {
const data = await GetParkingTicketTimes('2017');
console.log(data);
setChartData({
labels: labels,
datasets: [
{
label: "Traffic Tickets",
data: data,
borderColor: 'rgb(53, 162, 235)',
backgroundColor: 'rgba(53, 162, 235, 0.5)',
}
]
});
}
fetchData()
}, []);
const [chartData, setChartData] = useState({})
return <Line options={options} data={chartData} />;
}
export default TimeOfDayTickets
Here is the helper function
import { usePapaParse } from "react-papaparse";
import parkingTickets2017 from "../data/Parking_Tickets_2017.csv";
function getDataSetByYear(year) {
switch (year) {
case "2017":
return parkingTickets2017;
default:
}
}
export async function GetParkingTicketTimes(year) {
let countByHour = {};
const papaConfig = {
header: true,
complete: (results, file) => {
console.log("Parsing complete:", results, file);
// Getting just the times
let times = results.data.map((a) => a.ISSUETIME);
// Go through each time and put them in appropriate hour bucket
for (let i = 0; i < times.length; i++) {
if (times[i]) {
let h = times[i].substring(0, 2);
if (!countByHour[h]) {
countByHour[h] = 0;
}
countByHour[h]++;
}
}
return countByHour;
},
download: true,
error: (error, file) => {
console.log("Error while parsing:", error, file);
},
};
const { readString } = usePapaParse();
await readString(getDataSetByYear(year), papaConfig);
}
The return countByHour is returned too late. What is the right way to do this?
I am working on building a React app that contains a line chart representing the timeline of data recordings from a certain point in time to present day. I am using the JS library Chart js. Specifically the 'react-chartjs-2' plugin. I am using the '/historical/all' path of the following API:
[https://corona.lmao.ninja/docs/?urls.primaryName=version%202.0.0#/][1]
I am successfully retrieving a response from the Axios GET request. But the structuring of the JSON being sent back to me is confusing me when it comes to the implementation I have been following to build the application. I have a lot of the scaffolding in place, but I am struggling to get the data from GET request function to the line chart.
The data is being sent in the following structure:
{
"cases": {
"3/21/20": 304507,
"3/22/20": 336953,
"3/23/20": 378231,
"3/24/20": 418041,
"3/25/20": 467653,
"3/26/20": 529591,
"3/27/20": 593291,
"3/28/20": 660693,
"3/29/20": 720140,
"3/30/20": 782389,
"3/31/20": 857487,
"4/1/20": 932605,
"4/2/20": 1013466,
"4/3/20": 1095917,
"4/4/20": 1176060,
"4/5/20": 1249754,
"4/6/20": 1321481,
"4/7/20": 1396476,
"4/8/20": 1480202,
"4/9/20": 1565278,
"4/10/20": 1657526,
"4/11/20": 1735650,
"4/12/20": 1834721,
"4/13/20": 1904838,
"4/14/20": 1976191,
"4/15/20": 2056054,
"4/16/20": 2152437,
"4/17/20": 2240190,
"4/18/20": 2317758,
"4/19/20": 2401378
},
"deaths": {
"3/21/20": 12973,
"3/22/20": 14651,
"3/23/20": 16505,
"3/24/20": 18625,
"3/25/20": 21181,
"3/26/20": 23970,
"3/27/20": 27198,
"3/28/20": 30652,
"3/29/20": 33925,
"3/30/20": 37582,
"3/31/20": 42107,
"4/1/20": 47180,
"4/2/20": 52983,
"4/3/20": 58787,
"4/4/20": 64606,
"4/5/20": 69374,
"4/6/20": 74565,
"4/7/20": 81937,
"4/8/20": 88338,
"4/9/20": 95521,
"4/10/20": 102525,
"4/11/20": 108502,
"4/12/20": 114090,
"4/13/20": 119481,
"4/14/20": 125983,
"4/15/20": 134176,
"4/16/20": 143800,
"4/17/20": 153821,
"4/18/20": 159509,
"4/19/20": 165043
},
"recovered": {
"3/21/20": 91682,
"3/22/20": 97889,
"3/23/20": 98341,
"3/24/20": 107890,
"3/25/20": 113604,
"3/26/20": 121966,
"3/27/20": 130659,
"3/28/20": 138949,
"3/29/20": 148616,
"3/30/20": 164100,
"3/31/20": 176442,
"4/1/20": 191853,
"4/2/20": 208528,
"4/3/20": 223621,
"4/4/20": 243575,
"4/5/20": 257000,
"4/6/20": 273259,
"4/7/20": 296263,
"4/8/20": 324507,
"4/9/20": 348813,
"4/10/20": 370241,
"4/11/20": 395521,
"4/12/20": 414599,
"4/13/20": 440897,
"4/14/20": 466051,
"4/15/20": 502053,
"4/16/20": 532409,
"4/17/20": 557798,
"4/18/20": 581355,
"4/19/20": 612056
}
}
I would like for each category (cases, deaths, recovered) to represent a line on the chart showing the progression over time, but I'm unsure how to handle it. With the intention being that the dates would be the X-axis and the points would be plotted in a different colored line for each.
Would anyone be able to help with this at all? I would greatly appreciate some guidance. Below are the two primary files corresponding to the Chart and API handling:
index.js
export const fetchDailyData = async () => {
try {
const { data } = await axios.get(`${url}/historical/all`);
const labels = Object.keys(data.cases);
const cases = labels.map((name) => data.cases[name]);
const deaths = labels.map((name) => data.deaths[name]);
const recovered = labels.map((name) => data.recovered[name]);
return {labels, cases, deaths, recovered};
} catch (error) {
return error;
}
}
Chart.jsx
import React, { useState, useEffect } from 'react';
import { Line, Bar } from 'react-chartjs-2';
import { fetchDailyData } from '../../api';
import styles from './Chart.module.css';
const Chart = ({data: { labels, cases, deaths, recovered }}) => {
const [dailyData, setDailyData] = useState([]);
useEffect(() => {
const fetchAPI = async () => {
setDailyData(await fetchDailyData());
}
fetchAPI();
}, []);
const lineChart = (
<Line data = {
{
labels,
datasets: [{
data:cases,
label: 'Infected',
borderColor: '#3333ff',
fill: true,
}, {
data: deaths,
label: 'Deaths',
borderColor: 'red',
backgroundColor: 'rgba(255, 0, 0, 0.5)',
fill: true,
},
{
data: recovered,
label: 'Recovered',
borderColor: 'green',
backgroundColor: 'rgba(0, 255, 0, 0.5)',
fill: true,
}],
}
}
/>
);
return (
<div className = { styles.container }>
{lineChart}
</div>
);
};
export default Chart;
Here is a complete solution:
// Create the chartData state
const [chartData, setChartData] = useState();
// in your fetch function, convert the data to chartData
const labels = Object.keys(data.cases);
const cases = labels.map((name) => data.cases[name]);
const deaths = labels.map((name) => data.deaths[name]);
const recovered = labels.map((name) => data.recovered[name]);
setChartData({
labels,
datasets: [{
data: cases,
label: 'Infected',
borderColor: '#3333ff',
fill: true
}, {
data: deaths,
label: 'Deaths',
borderColor: 'red',
backgroundColor: 'rgba(255, 0, 0, 0.5)',
fill: true
}, {
data: recovered,
label: 'Recovered',
borderColor: 'green',
backgroundColor: 'rgba(0, 255, 0, 0.5)',
fill: true
}]
})
// Now render your chart (but make sure chartData exists)
return (
<div className = { styles.container }>
{chartData &&
<Line data={chartData} />
}
</div>
);
First extract the x labels:
const labels = Object.keys(dailyData.cases);
Then extract the y values for each serie:
const cases = Object.keys(dailyData.cases).map((name) => dailyData.cases[name]);
const deaths = Object.keys(dailyData.deaths).map((name) => dailyData.deaths[name]);
const recovered = Object.keys(dailyData.recovered).map((name) => dailyData.recovered[name]);
Then assemble everything into a chart js object:
<Line data = {
labels,
datasets: [{
data: cases,
label: 'Infected',
borderColor: '#3333ff',
fill: true
}, {
data: deaths,
label: 'Deaths',
borderColor: 'red',
backgroundColor: 'rgba(255, 0, 0, 0.5)',
fill: true
}, {
data: recovered,
label: 'Recovered',
borderColor: 'green',
backgroundColor: 'rgba(0, 255, 0, 0.5)',
fill: true
}]
}
/>