Why is my ChartJs grid not matching up with points? - reactjs

I am making a chart displaying stock data using ChartJs (react-chartjs-2). I have a datapoint for every minute from 9:30am to 3:59pm. When I plot it, my chart looks like the image below. The points don't even remotely align with the grid. The point in the image says 10:16 but the chart positions it between 9 and 10. Why is it like this? The points should also start halfway after the 9 tick since my data starts at 9:30. Does anyone know what I can do to resolve this?
My data is in the format like x = 2023-02-15 09:30:00-05:00 and y = 226.145. I have an option that truncates what is displayed on the tick so instead of displaying the entire datetime format, it only displays the hour.
Here is some of my code:
const data = {
labels: Object.keys(stockdata),
datasets: [
{
data: Object.values(stockdata),
borderColor: "rgba(75,192,192,1)",
},
],
};
const options = {
responsive: true,
plugins: {
legend: {
position: "top",
},
title: {
display: true,
text: "Chart.js Line Chart",
},
},
pointRadius: 0,
scales: {
x: {
ticks: {
callback: function (val) {
const time = parseInt(
this.getLabelForValue(val).split(" ")[1].split(":")[0] % 12
);
return time == 0 ? 12 : time;
},
maxTicksLimit: 7,
},
grid: {
color: "#303030",
},
},
y: {
grid: {
color: "#303030",
},
},
},
};
React component render:
<Line data={data} options={options} />

I assume the issue is, that your localtime has a date offset off -1 hours, but without seeing the data (and knowing the offset), I can't say for sure.
If this would be the case, you could/would have to convert your dataset values. Depending on your dataset and available libraries you could use functions like:
momentjs:
let localDate= moment.utc("2023-02-01 10:00:00")
.local()
.format("YYYY-MM-DD HH:mm:ss");
vanilajs:
let localDate = new Date(Date.UTC(2023, 1, 1, 10, 0, 0));
You would have to loop through all the x-values / dates entries, to fix this.

Related

react-chartjs-2: How to customize the onHover tooltip

In a react-chartjs-2 bar chart: How do I customize all the information shown inside the tooltip when hovering over a bar?
I would expect something like data.datasets[x].tooltip.callback to be available, but I can't find anything useful/working.
Note: I prefer a more generalized answer over a very specific answer to the concrete situation I'm describing below. This would probably be more useful for other readers.
Concrete Situation
I'm plotting data in a bar graph.
X values are given as timestamps (milliseconds). Y values are numbers representing a percentage. Example:
data = [
{ x: timestamp(2022, 1, 1, 1, 15), y: 1 },
{ x: timestamp(2022, 1, 1, 1, 30), y: 2 },
// ...
]
X-axis ticks are formatted as 1:15, 1:30, ...
Y-axis ticks are formatted as 0 %, 0.5 %, 1%, ....
Problem
When I hover over a bar, it will show:
The timestamp in milliseconds
The label of the data
I want the tooltip to show:
The formatted version of the timestamp (like 1:15).
The formatted version of the y-value (like 2%).
No label at all
Complete Code of React Component
function timestamp(year: number, month: number, day: number, hour: number, minute: number) {
const timestamp = new Date(year, month - 1, day, hour, minute).valueOf();
return timestamp;
}
export const TimeGraph: React.FC = () => {
const data = [
{ x: timestamp(2022, 1, 1, 1, 15), y: 1 },
{ x: timestamp(2022, 1, 1, 1, 30), y: 2 },
{ x: timestamp(2022, 1, 1, 1, 45), y: 3 },
{ x: timestamp(2022, 1, 1, 2, 0), y: 4 },
{ x: timestamp(2022, 1, 1, 2, 15), y: 2 },
];
const xValues = data.map((value: any) => {
return value.x;
});
const yValues = data.map((value: any) => {
return value.y;
});
const options = {
scales: {
x: {
grid: {
display: false,
},
ticks: {
callback: (index: any) => {
const date = new Date(xValues[index]);
return date.getHours() + ':' + date.getMinutes();
},
},
},
yAxes: {
grid: {
display: false,
},
ticks: {
callback: (value: any) => {
return value + ' %';
},
},
},
},
plugins: {
legend: {
position: 'top' as const,
},
title: {
display: false,
},
},
};
const chartData = {
labels: xValues,
datasets: [
{
label: 'TODO: remove',
data: yValues,
},
],
};
return (
<>
<Bar options={options} data={chartData} height={150} />
</>
);
};
Answer for chart.js 3 (which is used by react-chartjs-2)
You can customize the tooltip via options.plugins.tooltip.callbacks (Reference)
const options = {
plugins: {
tooltip: {
callbacks: {
title: (xDatapoint) => {return formatXValue(xDatapoint.raw)},
label: (yDatapoint) => {return formatYValue(yDatapoint.raw)},
}
}
},
}
Note:
This will customize all tooltips for all datasets.
Take a look here if you'd like to use a completely customized tool tip
Side note: Remove label on top of diagram
This was not part of the question, but it showed up in the screenshot:
options.plugins.legend.display: false disables the legend.

Line Chart Js x-axis values all 0 on React

I'm trying to implement a line graph through chart js on react, however, whenever my function sets the x and y axis data, it keeps returning a line chart that is vertical with x-axis values at 0.
Here is my code below:
import React, { useEffect, useState } from 'react'
import './LineGraph.css'
import {Line} from "react-chartjs-2"
function LineGraph() {
const [ graphData, setGraphData ] = useState([])
const data = [{x:10, y:20}, {x:15, y:10}, {x:12, y:4}]
const createMockData = () => {
let data = [];
let value = 50;
for(var i = 1; i < 366; i++) {
let date = new Date()
date.setHours(0,0,0,0)
date.setDate(i)
value += Math.round((Math.random() < 0.5 ? 1 : 0) * Math.random() * 10)
data.push({x: value, y:value})
console.log(data)
}
setGraphData(data)
}
useEffect(()=> {
createMockData()
}, [])
return (
<div className="linegraph">
<Line
data={{
datasets: [
{
type: "line",
data: graphData,
backgroundColor: "black",
borderColor: "#5AC53B",
borderWidth: 2,
pointBorderColor: 'rgba(22, 22, 22, 0)',
pointBackgroundColor: 'rgba(0,0,0,0)',
pointHoverBackgroundColor: '#5AC53B',
pointHoverBorderColor: '#000000',
pointHoverBorderWidth: 4,
pointHoverRadius: 6,
}
]
}}
options={{
plugins:{
legend: {
display: false
},
tooltips: {
interaction: {
mode: "index",
intersect: false
}
}
},
scales: {
x: [
{
type: "time",
time: {
format: "MM/DD/YY",
tooltipFormat: "ll",
},
ticks: {
display: false,
}
},
],
y: {
ticks: {
display: false
}
}
}
}}
/>
</div>
)
}
export default LineGraph
I'm using react-charts-2, latest version 3.5.1. I think the main problem is with my x-axis because my y-coordinates all show up, which is why the line is vertical, however, all the x values are 0, so their is not diagonal or correlated uphill/downhill line. Please help me resolve this
You are defining the x axis scale as an array , this is v2 syntax. All scales have to be defined as an object. So at the moment chart.js sees your scale as a category scale instead of a time scale and it cant handle number inputs as labels.
To fix this you will need to change your scale like so:
scales: {
x: {
type: "time",
time: {
format: "MM/DD/YY",
tooltipFormat: "ll",
},
ticks: {
display: false,
}
},
,
}
For using the timescale you will also need a date adapter, since you are using js dates you can just use the normal datefns adapter like so:
npm install date-fns chartjs-adapter-date-fns --save
import {Line} from "react-chartjs-2"
import 'chartjs-adapter-date-fns';

ECharts: Force XAxis Labels to show all the values for type=time without any duplicate values

I am facing some issues with Echarts-Line Charts while trying to render the label data on the X-Axis.
Scenario 1 – Plotting XAxis Labels when only single quarter information is available
Expected Output: Only a single label should appear on XAxis.
Expected Output
Actual Output [Echarts] – Duplicate xAxis labels
Echart Output
I want to have one to one mapping between data and xAxis label.
Scenario 2 – Plotting XAxis labels when there is a huge dataset
Expected Output :- The labels on xAxis has all the quarter information.
Expected Output when dataset is large
Actual Output [Echarts]- Some XAxis labels are skipped automatically by Echarts library, I have tried rotation but it still skips the datapoints.
Actual: xAxis Labels got skipped
I want to force Echart to show all the labels irrespective of the overlapping of values.
Sample Code:
import React from 'react';
import ReactEcharts from 'echarts-for-react';
const LineChart : React.FC = (props) => {
var option2={
title: {
text: ''
},
tooltip: {
trigger: 'item'
},
legend: {
x: 'left',
padding: [0, 0, 0, 40],
data: ['Plot this']
},
// Impacts the grid lines, LRBT are kind of a padding which moves the chart
grid: {
show: true,
left: '3%',
containLabel: true,
borderColor: 'white'
},
xAxis: {
name: "Category",
nameLocation: "middle",
nameTextStyle: {
padding: [10, 0, 0, 0]
},
type: 'time',
boundaryGap: false,
splitNumber: 0,
axisLabel: {
interval:0,
showMinLabel: true,
showMaxLabel:true,
formatter: (function (value:any, index:any) {
const date = new Date(value * 1);
const month = (date.getMonth() + 1);
// to ignore duplicate values
if (!([1,4,7,10].includes(month))){
return null;
}
const quarter = `Q${Math.ceil(month / 3)}`
const year = date.getFullYear()
const xAxisData = `${quarter}-${year}`
return xAxisData
})
// formatter:'Q{Q}-{yyyy}'
}
},
yAxis: {
name: "",
nameLocation: "middle",
nameTextStyle: {
padding: [0, 0, 30, 0]
},
splitArea:{
show:true,
areaStyle:{
color:["white", "#FAFBFC"]
}
},
type: 'value'
},
series: [
{
name: 'Plot This',
type: 'line',
symbol: 'emptyCircle',
color: '#00A3E0',
symbolSize: 10,
lineStyle: {
type: 'solid'
},
emphasis: {
focus: 'series',
blurScope: 'coordinateSystem'
},
data: [
[1506816000000, 509680038.04382974],
[1514764800000, 791155276.2344121],
[1522540800000, 799123227.7082155],
[1530403200000, 802979755.202323],
[1538352000000, 808190497.8038454],
[1546300800000, 948760339.9516863],
[1554076800000, 1042540676.5278728],
[1561939200000, 875160118.2571102],
[1569888000000, 712878628.868768],
[1577836800000, 735685154.726105],
[1585699200000, 820177866.0564957],
[1593561600000, 818982140.8503832],
[1601510400000, 815904376.9750341],
[1609459200000, 836625579.7082175],
[1617235200000, 832549982.9206431],
[1625097600000, 828002503.802811],
[1633046400000, 821488484.2030047],
[1640995200000, 823540791.742887],
[1648771200000, 818621496.9663928],
[1656633600000, 813346336.6927732],
[1664582400000, 808353924.8521348],
[1672531200000, 804944324.4562442],
[1680307200000, 799921655.5844442]
]
},
]
}
return (
<div>
<p> </p>
<ReactEcharts
option = {option2}
/>
</div>
);
}
export default LineChart;
Output of the above code:
XAxis with Missing Quarters
Replace the data of the series with below to get the Scenario 1:
data: [
[1506816000000, 509680038.04382974],
]
Output:
Duplicate Quarters
Thanks in advance.

How to show Legend of Apex Charts (ReactJS) even if there is no series data available

I am using line-apex Charts in ReactJs. If there is no data available then legend for Y-Axis is invisible. And if data is available they will show up. I want legend to be displayed even if there is no data in series.
Alternatives I tried out: If there is no data in DB, send seriesData with 0 Values, it will show the point on the graph with 0 value and legend will be available. But, I want to get rid of that point.
getOptions() {
return {
chart: {
height: 200,
type: "line",
stacked: false,
},
colors: this.themes[this.props.theme],
dataLabels: {
enabled: false,
},
stroke: {
width: [3.5, 3.5],
},
xaxis: {
categories: this.props.xCategories,
labels: {
style: {
color: "#263238",
},
},
},
yaxis: [
{
axisTicks: {
show: true,
},
axisBorder: {
show: false,
},
labels: {
style: {
colors: this.themes[this.props.theme][0],
fontWeight: "bold",
},
},
tooltip: {
enabled: false,
}
},
{
seriesName: "Revenue",
opposite: false,
axisTicks: {
show: true,
},
axisBorder: {
show: false,
},
labels: {
style: {
colors: this.themes[this.props.theme][1],
fontWeight: "bold",
},
},
},
],
legend: {
position: "top",
horizontalAlign: "left",
offsetX: -15,
fontWeight: "bold",
}
}
}
And below is the render method:
render() {
return (
<div>
<Chart
options={this.getOptions()}
series={this.props.seriesData}
type='line'
/>
</div>
);
}
There are a few properties that determine whether the legend should be shown in different circumstances. There's information available in the ApexCharts documentation, but here's an overview:
showForSingleSeries
default: false
This will determine if the legend should be shown even if there is just one series being displayed on the graph.
showForNullSeries
default: true
This will determine whether series that contain only null values are hidden.
showForZeroSeries
default: true
This will determine whether series that contain only zero values are hidden.
In your case, you'll want to make sure first of all that the showForSingleSeries is set to true to ensure that the legend is always shown. Secondly, rather than returning a series that contains a zero value, you can return either an empty array or an array containing null elements.
Finally, in order for the legend to show up when using your code, I had to add a series property to the options object. This is because the legend won't display unless it has any series names to show - it doesn't know the names of the series you'll be providing it with unless you specify it.
var options = {
...
series: [
{
name: "Revenue",
data: []
}
]
...
}

Chartist Timeseries Fixed Axis Time Labels

I'm trying to create a fairly simple time series chart for payments made at X time for Y amount. X-axis labels are fixed at every 12 hours like this:
new Date("2018-08-28 00:00"),
new Date("2018-08-28 12:00"),
new Date("2018-08-29 00:00"),
new Date("2018-08-29 12:00")
.............................
When I hard-code array of amount/time I get decent graph but without X-axis labels: https://jsfiddle.net/tolyan/69z2wepo/281812/. When I try to use some real data and map it to array, labels are generated instead of my fixed labels: https://jsfiddle.net/tolyan/69z2wepo/281806/. Also, how do I configure chartist to have some padding or gap for the graph start - for better readability. Thanks in advance for any tips!
I think there's nothing wrong with your mapping of the real data, I think is more of knowing your data and putting the correct "ticks" in the x axis, for example in your data you have stamps in terms of seconds, for example:
{amount: 22, timestamp: "2018-08-29T06:01:54.007"},
{amount: 1, timestamp: "2018-08-29T06:01:55.29"},
{amount: 11, timestamp: "2018-08-29T06:01:56.66"},
{amount: 9, timestamp: "2018-08-29T06:01:58.063"},
So of course if you are going to have your ticks set from the 28th at 00:00 till the 31st at 23:59, then it won't work and it won't show the labels because the graph needs to create that space in between for it to show the ticks, that's why it shows the dot at the beginning and at the end. So adjusting your example in the fiddle, try to put lower values to your ticks, something like this:
axisX: {
type: Chartist.FixedScaleAxis,
divisor: 4,
ticks: [
new Date("2018-08-29 06:01:52"),
new Date("2018-08-29 06:01:53"),
new Date("2018-08-29 06:01:55"),
new Date("2018-08-29 06:01:59")
],
So the full example should be something like:
var trans = [
{amount: 22, timestamp: "2018-08-29T06:01:54.007"},
{amount: 1, timestamp: "2018-08-29T06:01:55.29"},
{amount: 11, timestamp: "2018-08-29T06:01:56.66"},
{amount: 9, timestamp: "2018-08-29T06:01:58.063"},
{amount: 6, timestamp: "2018-08-29T06:02:02.203"},
{amount: 1, timestamp: "2018-08-29 06:02:03.413"}
];
var data = {
series: [
{
name: 'series-times',
data: trans.map((prop, key) => {
return {
x: new Date(prop["timestamp"]),
y: prop["amount"]
};
})
}
]
};
var options = {
lineSmooth: Chartist.Interpolation.cardinal({
tension: 0
}),
axisY: {
type: Chartist.FixedScaleAxis,
divisor: 8,
ticks: [0, 10, 20, 30, 40, 50, 60],
labelInterpolationFnc: function(value) {
return "$" + value;
}
},
axisX: {
type: Chartist.FixedScaleAxis,
divisor: 4,
ticks: [
new Date("2018-08-29 06:01:52"),
new Date("2018-08-29 06:01:53"),
new Date("2018-08-29 06:01:55"),
new Date("2018-08-29 06:01:59")
],
labelInterpolationFnc: function(value) {
return moment(value).format('MM/DD/YY HH:mm:ss');
}
},
// low: 0,
// high: 100,
showPoint: true,
height: "300px",
animation: {
draw: function(data) {
if (data.type === "line" || data.type === "area") {
data.element.animate({
d: {
begin: 600,
dur: 700,
from: data.path
.clone()
.scale(1, 0)
.translate(0, data.chartRect.height())
.stringify(),
to: data.path.clone().stringify(),
easing: Chartist.Svg.Easing.easeOutQuint
}
});
} else if (data.type === "point") {
data.element.animate({
opacity: {
begin: (data.index + 1) * delays,
dur: durations,
from: 0,
to: 1,
easing: "ease"
}
});
}
}
}
}
/* var options = {
seriesBarDistance: 100
}; */
new Chartist.Line('.ct-chart', data, options);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<link href="https://cdn.jsdelivr.net/chartist.js/latest/chartist.min.css" rel="stylesheet"/>
<div class="ct-chart ct-square"></div>
<script src="https://cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>
For changing the padding of the labels, just target them with CSS, you can just check them out which is the label class and adjust it to your needs. Or also another more cleaner solution is to change the JavaScript options of the tick, or the labels, more info in:
https://gionkunz.github.io/chartist-js/api-documentation.html
Hope it helps, let me know if you have questions.
Leo.

Resources