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.
Related
I am currently trying to use the plugin chartjs-plugin-annotation in my react project.
Unfortunately, it is not working...
He is my implementation :
import React, { Component } from "react";
//import "./css/tideComponent.css";
import jsonData from "./ressources/tideOostende2023.json";
import "chart.js/auto";
import { Chart } from "react-chartjs-2";
import * as ChartAnnotation from "chartjs-plugin-annotation";
class Tide extends Component {
state = {
dayDate: new Date().toJSON().slice(5, 10),
highTide: "",
highTide2: "",
lowTide: "",
lowTide2: "",
};
async componentDidMount() {
const index = jsonData.findIndex(
(item) => item.date === this.state.dayDate
);
//TODO store tide in an array(using split method) & filter low to high to have a correct graph
this.setState({
highTide: jsonData[index].highTide,
highTide2: jsonData[index].highTide2,
lowTide: jsonData[index].lowTide,
lowTide2: jsonData[index].lowTide2,
});
}
timeToNumeric(tideTime) {
const tideTimeSplitted = tideTime.split(":");
return tideTimeSplitted[0] * 1 + tideTimeSplitted[1] / 60;
}
handleTideData() {
if (
this.timeToNumeric(this.state.highTide) <
this.timeToNumeric(this.state.lowTide)
)
return [
{ x: -2, y: 0.5 },
{ x: this.timeToNumeric(this.state.highTide), y: 1.5 },
{ x: this.timeToNumeric(this.state.lowTide), y: 0.5 },
{ x: this.timeToNumeric(this.state.highTide2), y: 1.5 },
{ x: this.timeToNumeric(this.state.lowTide2), y: 0.5 },
{ x: 26, y: 1.5 },
];
return [
{ x: -2, y: 1.5 },
{ x: this.timeToNumeric(this.state.lowTide), y: 0.5 },
{ x: this.timeToNumeric(this.state.highTide), y: 1.5 },
{ x: this.timeToNumeric(this.state.lowTide2), y: 0.5 },
{ x: this.timeToNumeric(this.state.highTide2), y: 1.5 },
{ x: 26, y: 0.5 },
];
}
render() {
const data = {
datasets: [
{
data: this.handleTideData(),
fill: false,
backgroundColor: "rgb(35, 71, 89, 0.88)",
borderColor: " rgb(35, 71, 79, 0.88)",
tension: 0.4,
},
],
};
const options = {
annotation: {
annotations: [
{
type: "line",
mode: "horizontal",
scaleID: "x",
value: 1,
borderColor: "white",
borderWidth: 2,
},
],
},
scales: {
x: { min: 0, max: 24, ticks: { stepSize: 1 } },
y: { min: 0, max: 2.2, display: false },
},
showLine: true,
pointStyle: false,
plugins: {
legend: { display: false },
},
};
return (
<div className="tideContainer">
<Chart
type="scatter"
data={data}
options={options}
plugins={ChartAnnotation}
/>
</div>
);
}
}
export default Tide;`
I tried different things but still not working. I also reviewed multiple question on SO but cannot find my solution. Chart Js is correctly working with my implementation, it is only the plugin that does not work.
Thank you in advance for your great help !!!
I think plugins property in react-chartjs-2 should be an array, I guess.
<Chart
type="scatter"
data={data}
options={options}
plugins={[ChartAnnotation]}
/>
The options config for annotation plugin is not in the right node.
It must be added in options.plugins node.
const options = {
plugins: { // <-- to add, was missing
annotation: {
annotations: [
{
type: "line",
mode: "horizontal",
scaleID: "x",
value: 1,
borderColor: "white",
borderWidth: 2,
},
],
},
}
I am trying to get a progressive line chart working using the docs as a starting point.
Link to example
While I now have the sample code working after removing the Utils.CHART_COLORS and importing the easingEffects. I am trying to get my data to show up, I don't get any errors, which just doesn't work.
import { easingEffects } from "chart.js/helpers";
# This is in a separate file, I import it.
export const DemoData = [
{
"id": 1,
"date":,
"price":,
"mess":
},
{
"id": 1,
"date":"11/01/2022",
"price":"100",
"bp": "95",
"mess": "Hi"
},
{
"id": 2,
"date":"11/05/2022",
"price":"95",
"bp": "90",
"mess":"Hola"
},
{
"id": 3,
"date":"11/10/2022",
"price":"97",
"bp": "99",
"mess": "Dang"
},
]
# I have tried a bunch of different things, latest iteration
let easing = easingEffects.easeOutQuad;
let restart = false;
const totalDuration = 5000;
// const duration = (ctx) => easing(ctx.index / data.length) * totalDuration / data.length;
const delay = (ctx) => easing(ctx.index / data.length) * totalDuration;
const previousY = (ctx) => ctx.index === 0 ? ctx.chart.scales.y.getPixelForValue(100) : ctx.chart.getDatasetMeta(ctx.datasetIndex).data[ctx.index - 1].getProps(['y'], true).y;
const animation = {
x: {
type: 'number',
easing: 'linear',
duration: 5000,
from: NaN, // the point is initially skipped
delay: 2500
},
y: {
type: 'number',
easing: 'linear',
duration: 5000,
}
}
export const config_progressive = {
type: 'line',
data: {
labels: DemoData.map((data) => data.date),
datasets: [{
label: 'Stock Price',
data: DemoData.map((data) => data.price),
},
{
label: 'Purchase Price',
data: DemoData.map((data) => data.bp),
}
]
},
options: {
animation,
interaction: {
intersect: false
},
plugins: {
legend: false,
title: {
display: true,
text: () => easing.name
}
},
scales: {
x: {
type: 'time',
time: {
unit: 'day',
parser: 'mm.dd.yyyy'
}
}
}
}
};
The same DemoData in a simple line chart works without a problem.
In the output of the above code. The Y axis seems to have the right numbers, the "X axis" doesn't show dates. And the data doesn't show up at all.
I wasn't able to find a resource to learn how to proceed/troubleshoot.
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.
I have created a histogram of some data, and would like to show a vertical line where the mean of this data is located. I am able to place a line at any of the categories on the bar chart, for example:
The code for doing this is (react-c3js):
<C3Chart
key={foo}
data={{ unload: true,
columns: data.columns,
type: 'bar',
color: (color, d) => someColor
}}
grid={{
x: {
lines: [
{ value: '1332', text: 'mean' },
{ value: d3.mean(dataForHistogram), text: 'median' },
]
},
y: {
lines: [
{ value: 100, text: 'oiweryoqeiuw' },
]
}
}}
axis={{
x: {
unload: true,
show: features ? true : false,
categories,
type: "category",
label: someLabel,
}
}} />
Note that 1332 is not the actual mean - the correct mean is 2092. But this is not a category value, so the line does not display when I use the mean as the value for the line.
How can I place a line representing the mean on such a bar chart?
Someone gave me the following solution, and it works for me. The solution was to change the type of x-axis from category to linear and to specify the x data:
const categories = [1, 2, 3, 4, 5];
const yValues = [10, 8, 6, 3, 7];
const xDataName = 'my-x-data';
const data = {
columns: [
[xDataName, ...categories],
[yData1, ...yValues],
],
};
return (<C3Chart
key={foo}
data={{
x: xDataName,
unload: true,
columns: data.columns,
type: 'bar',
color: (color, d) => someColor
}}
grid={{
lines: {
front: true
},
x: {
lines: [
{ value: d3.mean(yValues), text: 'mean' },
]
}
}}
axis={{
x: {
unload: true,
show: features ? true : false,
categories,
type: "linear",
label: someLabel,
}
}} />)
I would like to have an array object instead of the following json format;
[1409558400000, 7.45],[1409562000000, 5.71], [1409565600000, 7.50],
... .;
My purpose is to show all data on the graph based on their hh:mm parameter (which is done in the link already), and I ended up such json array;
[10, 7.45],[09, 5.71], [11, 7.50], ...
But I also would like to keep their timestamp in order to have more information about each data point so that I can provide the timestamp when user clicks on a point.
I simply need to have something like this [10, 7.45,1409562000000] ; hour value, age, and timestamp respectively.
How can I have such data array for flot chart ?
var d = [
[1409558400000, 7.45],
[1409562000000, 5.71],
[1409565600000, 7.50],
[1409569200000, 7.63],
[1409576400000, 3.14],
[1409644800000, 7.45],
[1409648400000, 5.71],
[1409652000000, 7.50],
[1409655600000, 7.63],
[1409662800000, 3.14],
[1409731200000, 7.45],
[1409734800000, 5.71],
[1409738400000, 7.50],
[1409742000000, 7.63],
[1409749200000, 3.14]
];
$.each(d, function (index, datapoint) {
datapoint[0] = (new Date(datapoint[0])).getHours();
});
$.plot("#placeholder", [d], {
series: {
lines: {
show: true
},
points: {
show: true
}
},
grid: {
hoverable: true,
clickable: true,
markings: [{
yaxis: {
from: 0,
to: 4
},
color: "#F2CDEA"
}, {
yaxis: {
from: 4,
to: 7
},
color: "#D7EEE1"
}, {
yaxis: {
from: 7,
to: 12
},
color: "#F2CDEA"
}]
},
xaxis: {
},
yaxis: {
min: 0,
max: 12
}
});
$("#placeholder").bind("plotclick", function(event, pos, item) {
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
if (item) {
//window.location="pagex.html";
alert("x: " + x);
//plot.highlight(item.series, item.datapoint);
}
});
<!-- basic time series flot chart -->
<h>Create a custom green range</h>
<div style="height: 400px; width: 600px;" id="placeholder"></div>
http://jsfiddle.net/shamaleyte/wzLaqzf5/1/
Basically I adapt the answer given in the following link for my scenario.
Link for Referenced answer
Link : displaying custom tooltip when hovering over a point in flot
My Code:
var data = [
[1409558400000, 7.45],
[1409562000033, 5.71],
];
$.each(data, function (index, datapoint) {
datapoint[2] = datapoint[0]; // copy the timestamp and paste it as the 3rd object
datapoint[0] = (new Date(datapoint[0])).getHours(); // to put hours on y axis
});
$.plot("#placeholder", [d2], {
series: {
lines: {
show: true
},
points: {
show: true
}
},
grid: {
hoverable: true,
clickable: true,
markings: [{
yaxis: {
from: 0,
to: 4
},
color: "#F2CDEA"
}, {
yaxis: {
from: 4,
to: 7
},
color: "#D7EEE1"
}, {
yaxis: {
from: 7,
to: 12
},
color: "#F2CDEA"
}]
},
xaxis: {
},
yaxis: {
min: 0,
max: 12
}
});
$("#placeholder").bind("plotclick", function(event, pos, item) {
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
var tooltip = item.series.data[item.dataIndex][2];
if (item) {
//window.location="pagex.html";
alert("x: " + tooltip);
//plot.highlight(item.series, item.datapoint);
}
});
My Fiddle : http://jsfiddle.net/shamaleyte/wzLaqzf5/2/