I am using chart.js in a React app to chart live data. I want the user to be able to pan and zoom on the graph, so I've also included the chart.js zoom plugin. My relevant code is below. Before I've either zoomed or panned, the chart continually shifts to show new data. However, after zooming of panning, the chart no longer automatically shifts to show new data—I need to manually zoom out or pan right to show the new data. I'd like it to be so that if the user is zoomed/panned on the far right side of the chart (i.e., where the newest data is added), the chart will automatically shift as it does before I initially zoom/pan. Is this possible? Or is there another way I can go about doing this if it does not work with chart.js? Thanks!
// graph.js
import React from "react";
import Chart from "./chart";
import * as zoom from 'chartjs-plugin-zoom';
let updateInterval = 1000;
let typeData = "Live";
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {
meta: {
ticks: props.ticks,
lab: [],
dat: []
},
lineChartData: {
labels: [],
datasets: [
{
type: "line",
label: typeData,
backgroundColor: "rgba(0, 0, 0, 0)",
borderColor: this.props.theme.palette.primary.main,
pointBackgroundColor: this.props.theme.palette.secondary.main,
pointBorderColor: this.props.theme.palette.secondary.main,
borderWidth: "2",
lineTension: 0.45,
data: []
}
]
},
lineChartOptions: {
responsive: true,
maintainAspectRatio: false,
tooltips: {
enabled: true
},
scales: {
xAxes: [
{
ticks: {
autoSkip: true,
suggestedMax: 100
}
}
]
},
plugins: {
zoom: {
pan: {
enabled: true,
mode: 'x',
speed: 100,
},
zoom: {
enabled: true,
mode: 'x',
speed: 0.75,
}
}
}
}
};
this.updateChart = this.updateChart.bind(this);
}
componentDidMount() {
setInterval( this.updateChart,
updateInterval)
}
updateChart() {
console.log(this.state.lineChartOptions.scales.xAxes.suggestedMax);
const newDat = this.state.meta.dat;
const newNum = Math.round(Math.random()*100);
newDat.push(newNum);
const newLab = [...Array(newDat.length).keys()];
const newMeta = {ticks: this.props.ticks, lab: newLab, dat: newDat}
this.setState({meta: newMeta})
const oldDataSet = this.state.lineChartData.datasets[0];
let newDataSet = { ...oldDataSet };
newDataSet.data.push(newNum);
const possLabs = this.state.meta.lab;
const newChartData = {
...this.state.lineChartData,
datasets: [newDataSet],
labels: possLabs
};
this.setState({ lineChartData: newChartData });
}
render() {
return (
<div style={{height: 400}}>
<Chart
data={this.state.lineChartData}
options={this.state.lineChartOptions}
/>
</div>
);
}
}
export default Graph;
// chart.js
import React from "react";
import { Line } from "react-chartjs-2";
const Chart = props => <Line data={props.data} options={props.options} />;
export default Chart;
// App.js
import React from "react";
import Graph from "./graph"
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
}
};
render() {
return (
<div>
<Graph/>
</div>
);
}
}
export default App;
Related
I'm attempting make a chart with data that is being fetched from an API, but a problem i'm having is that i've been unable to properly render the chart due to an asynchronous call im making for the data. I believe the chart is executing before the data can be filled because upon loading the window nothing renders. However when I tried hard coding values in place of 'timestamps' and 'prices' in the data variable, the chart immediately renders. Does anyone know how I can format the rest of the code so that the chart renders only after the timestamp and price arrays have been filled?
import { useEffect } from 'react';
import { Line } from 'react-chartjs-2';
const MarketAPI = require('market-api');
const Client = new MarketAPI();
function GRAPH(){
const timestamps = [];
const prices = [];
var getData = async() => {
const fromTS = (Date.now() / 1000 | 0 ) - 86400; // 1D from current timestamp
const toTS = Date.now() / 1000 | 0; // current timestamp
let get = await Client.assets.fetchAssetHist('MSFT', {
from: fromTS,
to: toTS,
});
for(let i = 0; i < get.data.prices.length; i++){
timestamps.push(get.data.prices[i].[0]);
prices.push(get.data.prices[i].[1]);
}
console.log(timestamps);
console.log(prices);
}
const data = {
labels: timestamps,
datasets: [
{
data: prices,
fill: true,
backgroundColor: 'rgba(243, 230, 200, 0.2)',
borderColor: 'rgba(243, 210, 18)',
},
],
};
const options = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
grid: {
display: false
},
display:true
},
y: {
grid: {
display: false
},
display: false
}
},
elements: {
point:{
radius: 0
}
},
};
useEffect(()=>{
getData()
},[]);
return(
<div>
<Line data={data} options={options}/>
</div>
);
}
function App() {
return (
<GRAPH/>
);
}
Use the useState Hook to store your data instead.
import { useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2';
const MarketAPI = require('market-api');
const Client = new MarketAPI();
function GRAPH(){
const timestamps = [];
const prices = [];
const [data, setData] = useState({})
var getData = async() => {
const fromTS = (Date.now() / 1000 | 0 ) - 86400; // 1D from current timestamp
const toTS = Date.now() / 1000 | 0; // current timestamp
let get = await Client.assets.fetchAssetHist('MSFT', {
from: fromTS,
to: toTS,
});
for(let i = 0; i < get.data.prices.length; i++){
timestamps.push(get.data.prices[i].[0]);
prices.push(get.data.prices[i].[1]);
}
console.log(timestamps);
console.log(prices);
}
setData({
labels: timestamps,
datasets: [
{
data: prices,
fill: true,
backgroundColor: 'rgba(243, 230, 200, 0.2)',
borderColor: 'rgba(243, 210, 18)',
},
],
})
const options = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
grid: {
display: false
},
display:true
},
y: {
grid: {
display: false
},
display: false
}
},
elements: {
point:{
radius: 0
}
},
};
useEffect(()=>{
getData()
},[]);
return(
<div>
<Line data={data} options={options}/>
</div>
);
}
function App() {
return (
<GRAPH/>
);
}
I am trying to hide the legend of my chart created with Chart.js.
According to the official documentation (https://www.chartjs.org/docs/latest/configuration/legend.html), to hide the legend, the display property of the options.display object must be set to false.
I have tried to do it in the following way:
const options = {
legend: {
display: false,
}
};
But it doesn't work, my legend is still there. I even tried this other way, but unfortunately, without success.
const options = {
legend: {
display: false,
labels: {
display: false
}
}
}
};
This is my full code.
import React, { useEffect, useState } from 'react';
import { Line } from "react-chartjs-2";
import numeral from 'numeral';
const options = {
legend: {
display: false,
},
elements: {
point: {
radius: 1,
},
},
maintainAspectRatio: false,
tooltips: {
mode: "index",
intersect: false,
callbacks: {
label: function (tooltipItem, data) {
return numeral(tooltipItem.value).format("+0,000");
},
},
},
scales: {
xAxes: [
{
type: "time",
time: {
format: "DD/MM/YY",
tooltipFormat: "ll",
},
},
],
yAxes: [
{
gridLines: {
display: false,
},
ticks: {
callback: function(value, index, values) {
return numeral(value).format("0a");
},
},
},
],
},
};
const buildChartData = (data, casesType = "cases") => {
let chartData = [];
let lastDataPoint;
for(let date in data.cases) {
if (lastDataPoint) {
let newDataPoint = {
x: date,
y: data[casesType][date] - lastDataPoint
}
chartData.push(newDataPoint);
}
lastDataPoint = data[casesType][date];
}
return chartData;
};
function LineGraph({ casesType }) {
const [data, setData] = useState({});
useEffect(() => {
const fetchData = async() => {
await fetch("https://disease.sh/v3/covid-19/historical/all?lastdays=120")
.then ((response) => {
return response.json();
})
.then((data) => {
let chartData = buildChartData(data, casesType);
setData(chartData);
});
};
fetchData();
}, [casesType]);
return (
<div>
{data?.length > 0 && (
<Line
data={{
datasets: [
{
backgroundColor: "rgba(204, 16, 52, 0.5)",
borderColor: "#CC1034",
data: data
},
],
}}
options={options}
/>
)}
</div>
);
}
export default LineGraph;
Could someone help me? Thank you in advance!
PD: Maybe is useful to try to find a solution, but I get 'undefined' in the text of my legend and when I try to change the text like this, the text legend still appearing as 'Undefindex'.
const options = {
legend: {
display: true,
text: 'Hello!'
}
};
As described in the documentation you linked the namespace where the legend is configured is: options.plugins.legend, if you put it there it will work:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'pink'
}
]
},
options: {
plugins: {
legend: {
display: false
}
}
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.0/chart.js"></script>
</body>
On another note, a big part of your options object is wrong, its in V2 syntax while you are using v3, please take a look at the migration guide
Reason why you get undefined as text in your legend is, is because you dont supply any label argument in your dataset.
in the newest versions this code works fine
const options = {
plugins: {
legend: {
display: false,
},
},
};
return <Doughnut data={data} options={options} />;
Import your options value inside the charts component like so:
const options = {
legend: {
display: false
}
};
<Line data={data} options={options} />
I'm using react-Chartjs-2 pie chart for my dashboard. as per the requirement I have to show both label with data in the legend. the below component which I'm using in my application
import React, { Component } from "react";
import { Doughnut } from "react-chartjs-2";
class DoughnutChart extends Component {
constructor(props) {
super(props);
}
render() {
const chartdata = {
labels: ["Newly Added", "Edited", "Deleted"],
datasets: [
{
label: "Markets Monitored",
backgroundColor: [
"#83ce83",
"#959595",
"#f96a5d",
"#00A6B4",
"#6800B4",
],
data: [9, 5, 3],
},
],
};
return (
<Doughnut
data={chartdata}
options={{
legend: { display: true, position: "right" },
datalabels: {
display: true,
color: "white",
},
tooltips: {
backgroundColor: "#5a6e7f",
},
}}
/>
);
}
}
export default DoughnutChart;
now I'm getting chart like given below
my output
my requirement is adding values in legend(customizing chart legend). example image expected output
One way to do it would be to define data and labels before creating the chart. Then you can add the data to labels using .map method.
import React, { Component } from "react";
import { Doughnut } from "react-chartjs-2";
class DoughnutChart extends Component {
constructor(props) {
super(props);
}
render() {
let data = [9, 5, 3]
let labels = ["Newly Added", "Edited", "Deleted"]
let customLabels = labels.map((label,index) =>`${label}: ${data[index]}`)
const chartdata = {
labels: customLabels,
datasets: [
{
label: "Markets Monitored",
backgroundColor: [
"#83ce83",
"#959595",
"#f96a5d",
"#00A6B4",
"#6800B4",
],
data: data,
},
],
};
return (
<Doughnut
data={chartdata}
options={{
legend: { display: true, position: "right" },
datalabels: {
display: true,
color: "white",
},
tooltips: {
backgroundColor: "#5a6e7f",
},
}}
/>
);
}
}
export default DoughnutChart;
I am creating a cryptocurrency price tracker with React. I am using axios to make API requests and get information about the price history of different cryptocurrencies. This works fine, but when I am creating the chart with ChartJS, the chart only shows one value, even though I have thousands. Anyone know how to fix this? Big thanks in advance.
Here is my code for the chart component:
import Chartjs from "chart.js";
import axios from "axios";
import { historyOptions } from "../../chartConfigs/chartConfigs";
const HistoryChart = ({ coinId }) => {
const [timeLength, setTimeLength] = useState(30);
const chartRef = useRef();
const [timeSeriesData, setTimeSeriesData] = useState([]);
useEffect(() => {
const timeSeriesDataArray = [];
axios.get(`https://api.coingecko.com/api/v3/coins/${coinId}/market_chart?vs_currency=usd&days=${30}`)
.then(response => {
response.data.prices.forEach(element => {
timeSeriesDataArray.push(
{
t: element[0],
y: element[2].toFixed(2)
}
)
})
setTimeSeriesData(timeSeriesDataArray)
})
.catch(error => console.log(error))
}, [coinId, timeLength])
useEffect(() => {
if (chartRef && chartRef.current) {
const chartInstance = new Chartjs(chartRef.current,{
type: 'line',
data: {
datasets: [{
label: "Price",
data: timeSeriesData,
backgroundColor: "rgba(174, 374, 194, 0.5)",
borderColor: "rgba(174, 374, 194, 0.4)",
pointRadius: 0,
borderWidth: 1
}]
},
options: historyOptions
})
}
}, [timeSeriesData])
return (
<div className="history-chart-container">
<canvas ref={chartRef} id="history-chart" width={1200} height={500}></canvas>
</div>
)
}
export default HistoryChart
Here is my code for the chart options (historyOptions):
lineHeighAnnotation: {
always: true,
hover: false,
lineHeight: 1.5
},
animation: {
duration: 2000
},
maintainAspectRatio: false,
responsive: true,
scales: {
xAxes: {
type: "time",
distribution: "linear",
}
}
}```
[1]: https://i.stack.imgur.com/e7g4P.png
[2]: https://i.stack.imgur.com/t4YIa.png
This could be fixed by changing your line chart into a scatter chart.
const chartInstance = new Chartjs(chartRef.current,{
type: 'scatter',
...
To still get the lines drawn between the data points, you need to define the option showLine: true on the dataset.
datasets: [{
label: "Price",
showLine: true,
...
}]
UPDATE
You should also make sure to update the chart when new data is added and the chart already exists. Not knowing much about react.js, you could make chartInstance a global variable and proceed as follows:
if (chartRef && chartRef.current) {
chartInstance = new Chartjs(chartRef.current,{
type: 'line',
...
} else {
chartInstance.update();
}
I want to have three LineCharts below each other. They are sharing same x-axis (time). I was able to create this:
However, it is very useless to have x-axis on the top and middle chart. I would prefer not to display the x-axis for the top and middle. When I tried it, I got this:
But as you can see, the last grid for 12AM is not visible for the top and middle chart, and the grids are not aligned (it looks weird). Each chart is rendered as a separate component using React.
Here are my TimelinePlot component which is rendering each LineChart (sorry for the mess, but I did not yet refactor it):
import React from 'react';
import { Line } from 'react-chartjs-2';
import { GetColors } from '../../utils/PlotDescriptionHelper';
class TimelinePlot extends React.Component {
MapPropsToData(props) {
const colors = GetColors(props.series.length);
return props.series.map((dataset, index) => {
return {
label: dataset.name,
data: dataset.data,
borderWidth: 1,
fill: false,
borderColor: colors[index],
//pointBackgroundColor: 'rgb(255,255,255)',
};
});
}
MapPropsToOptions(props) {
return {
elements: {
point: {
radius: 0,
},
},
legend: {
// display: props.showLegend,
position: 'top',
align: 'end',
},
scales: {
yAxes: [
{
ticks: props.yAxisTicks,
scaleLabel: {
display: true,
labelString: props.yAxisName + props.yAxisUnit,
},
},
],
xAxes: [
{
type: 'time',
// position: props.xAxisPosition,
time: {
parser: 'YYYY-MM-DD HH:mm',
tooltipFormat: 'll HH:mm',
},
// scaleLabel: {
// display: true,
// //labelString: 'Date',
// },
ticks: {
min: props.xAxisStart,
max: props.xAxisEnd,
//display: true,
display: props.xAxisDisplay,
},
},
],
},
};
}
render() {
const dataset = { datasets: this.MapPropsToData(this.props) };
return (
<div className='measurement-row'>
<Line
data={dataset}
options={this.MapPropsToOptions(this.props)}
position='absolute'
height='15%'
width='80%'
/>
</div>
);
}
}
And here is the render method of the parent using TimelinePlot component:
render() {
var plots = Object.keys(this.state.timeSeries).map((key, index) => {
return (
<TimelinePlot
key={key + index}
series={this.state.timeSeries[key]}
yAxisName={FirstLetterToUpper(key)}
yAxisUnit={MapKeyToUnit(key)}
xAxisDisplay={index === Object.keys(this.state.timeSeries).length - 1}
xAxisPosition={index === 0 ? 'top' : 'bottom'}
xAxisStart={this.state.startTime}
xAxisEnd={this.state.endTime}
showLegend={index === 0}
yAxisTicks={MapYAxisTicks(key)}
/>
);
});
return (
<div className='width-90'>
<TimelineDashboardHeader />
<div className='dashboard__column'>{plots}</div>
</div>
);
}