Using zoom/pan with ChartJS - reactjs

So I've been using react ChartJS to build some graphs, but eventually I ran into scroll problems that I couldn't solve with chartjs, so I found Apex that has Zoom built-in and I could make it scrollable.
But now since they are two completely different libs, so I can't copy it 100% the way it was.
I did not find anything on the docs talking about multiple labels in one value, so if anyone could help me with this one.
And if you see close enough there are some line running on the chart below, are those possible to do with Apex? (I'm using a customPlugin in chartJS to do it)

Ok, in the end I changed back to ChartJS and used the chartjs-plugin-zoom plugin to do the same thing I was doing with ApexChart, I'm placing my code in this answer in case that anyone faces the same problem.
React Bar Component:
import React from "react";
import { Bar, Chart } from "react-chartjs-2";
import ChartDataLabels from "chartjs-plugin-datalabels";
import Zoom from "chartjs-plugin-zoom";
const ChartBar = ({
data,
text,
noLabel,
stacked,
newPlugin,
labelPosition,
// test,
}) => {
Chart.register(Zoom);
return (
<div
className="graphics"
style={{
display: "flex",
alignItems: "center",
flexDirection: "column",
}}
>
{/* <h1>Gráficos</h1> */}
<div style={{ width: "65%", height: "350px" }}>
<Bar
data={data}
plugins={[
ChartDataLabels,
newPlugin ? newPlugin : "",
// test ? test : "",
]}
options={{
categoryPercentage: stacked ? 1.0 : 0.9,
barPercentage: 1.0,
// layout: {
// padding: {
// left: 0,
// right: 0,
// top: 60,
// bottom: 0,
// },
// },
responsive: true,
maintainAspectRatio: false,
plugins: {
zoom: {
// limits: { y: { min: "original", max: "original" } },
// pan: { enabled: true, mode: "xy", threshold: 10 },
// zoom: {
// wheel: {
// enabled: true,
// mode: "xy",
// },
// },
limits: { y: { min: "original", max: "original" } },
pan: { enabled: true, mode: "x", threshold: 10 },
zoom: {
mode: "x",
drag: {
enabled: true,
backgroundColor: "rgba(225,0,225,0.3)",
},
wheel: {
enabled: true,
modifierKey: "alt",
},
},
},
tooltip: { enabled: false },
legend: {
display: noLabel ? false : true,
position: labelPosition ? labelPosition : "bottom",
title: { padding: 40 },
},
title: { text: text, display: true, padding: 30 },
},
scales: {
// scaleLabel: { display: true },
x: {
stacked: stacked ? true : false,
// ticks: {
// display: false,
// autoSkip: true,
// maxTicksLimit: 10,
// beginAtZero: true,
// },
// gridLines: {
// display: false,
// },
},
y: { stacked: stacked ? true : false, ticks: { display: false } },
// xAxes: [{ scaleLabel: { display: true } }],
},
}}
/>
</div>
</div>
);
};
export default ChartBar;
One data object that is being used with this component(eg. the one in the main question):
{
customPlugin: {
id: "customValue",
afterDraw: (chart, args, opts) => {
const {
ctx,
data: { datasets },
_metasets,
} = chart;
datasets[1].data.forEach((dp, i) => {
let increasePercent =
(dp * 100) / datasets[0].data[i] >= 100
? Math.round(
((dp * 100) / datasets[0].data[i] - 100) * 100
) / 100
: (Math.round(
(100 - (dp * 100) / datasets[0].data[i]) * 100
) /
100) *
-1;
let barValue = `${increasePercent}%`;
const lineHeight = ctx.measureText("M").width;
const offset = opts.offset || 0;
const dash = opts.dash || [];
ctx.textAlign = "center";
ctx.fillText(
barValue,
_metasets[1].data[i].x,
_metasets[1].data[i].y - lineHeight * 1.5,
_metasets[1].data[i].width
);
if (_metasets[0].data[i].y >= _metasets[1].data[i].y) {
ctx.beginPath();
ctx.setLineDash(dash);
ctx.moveTo(_metasets[0].data[i].x, _metasets[0].data[i].y);
ctx.lineTo(
_metasets[0].data[i].x,
_metasets[1].data[i].y - offset
);
ctx.lineTo(
_metasets[1].data[i].x,
_metasets[1].data[i].y - offset
);
ctx.stroke();
} else {
ctx.beginPath();
ctx.setLineDash(dash);
ctx.moveTo(
_metasets[0].data[i].x,
_metasets[0].data[i].y - offset
);
ctx.lineTo(
_metasets[1].data[i].x,
_metasets[0].data[i].y - offset
);
ctx.lineTo(
_metasets[1].data[i].x,
_metasets[1].data[i].y - offset - lineHeight * 2
);
ctx.stroke();
}
});
},
},
text: "Evolução da Receita Líquida por Produto",
type: "bar",
// labels: values?.estimatedProducts?.map((v, i) => {
// return `Rec Líq - Prod ${++i}`;
// }),
labels: addNewArrayValue(values?.estimatedProducts, true),
datasets: [
{
type: "bar",
label: values?.monthsLabels?.mesBaseLabel,
data: values?.productsValues?.receitaLiquidaBase,
backgroundColor: ["rgba(42,62,176, 1)"],
datalabels: {
font: {
size: 10,
},
rotation: -90,
color: "white",
formatter: (value, context) => {
if (value !== 0) {
return value
?.toFixed()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
// ?.toFixed(0)
// .replace(/\d(?=(\d{3})+\.)/g, "$&,");
} else {
return 0;
}
},
},
},
{
type: "bar",
label: values?.monthsLabels?.mesOrcadoLabel,
data: values?.productsValues?.receitaLiquidaOrcado,
backgroundColor: "orange",
datalabels: {
font: {
size: 10,
},
rotation: -90,
color: "black",
formatter: (value, context) => {
if (value !== 0) {
return value
?.toFixed()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} else {
return 0;
}
},
},
},
],
},
Yeah, it's not the best code, but it's a starter, I've been struggling for 1h30m just because there's little to no actual examples/doc on this case.

Related

particlejs React typscript height width container

i want to do effect with particlejs i want to contain my particle in div with (width 250px height 250px) but i cant i dont know why the all particle are in all page
i tried to this contain the div in div and with position absolute/relative
import { useCallback } from "react";
import Particles from "react-tsparticles";
import type { Container, Engine } from "tsparticles-engine";
import { loadFull } from "tsparticles";
import '../style/Particules.css'
const Particule = () => {
const particlesInit = useCallback(async (engine: Engine) => {
console.log(engine);
// you can initialize the tsParticles instance (engine) here, adding custom shapes or presets
// this loads the tsparticles package bundle, it's the easiest method for getting everything ready
// starting from v2 you can add only the features you need reducing the bundle size
await loadFull(engine);
}, []);
const particlesLoaded = useCallback(async (container: Container | undefined) => {
await console.log(container);
}, []);
return (
<div className="container">
<div>
<h2>first</h2>
</div>
<div> <h2>second</h2>
<Particles
id="tsparticles"
init={particlesInit}
loaded={particlesLoaded}
options={{
background: {
color: {
value: "#0d47a1",
}
},
fullScreen: {
enable: true,
zIndex: -1,
},
fpsLimit: 60,
interactivity: {
events: {
onClick: {
enable: true,
mode: "push",
},
onHover: {
enable: true,
mode: "repulse",
},
resize: true,
},
modes: {
push: {
quantity: 1,
},
repulse: {
distance: 100,
duration: 0.04,
},
},
},
particles: {
color: {
value: "#fff",
},
links: {
color: "#ffffff",
distance: 15,
enable: true,
opacity: 0.1,
},
collisions: {
enable: false,
},
move: {
direction: "none",
enable: true,
outModes: {
default: "bounce",
},
random: false,
speed: 0.5,
straight: false,
},
number: {
density: {
enable: true,
area: 800,
},
value: 30,
},
opacity: {
value: 2,
},
style: {
position: "absolute"
},
shape: {
type: "images",
"images":
[{
"src": 'https://media.wuerth.com/stmedia/modyf/eshop/products/std.lang.all/resolutions/category/png-546x410px/56314216.png',
}, {
"src": "https://media.wuerth.com/stmedia/modyf/eshop/products/std.lang.all/resolutions/category/png-546x410px/56597539.png"
},
{
"src": "https://media.wuerth.com/stmedia/modyf/eshop/products/std.lang.all/resolutions/category/png-546x410px/4042942.png"
},
{
"src": ""
}
]
},
size: {
random: true,
value: 50,
},
},
detectRetina: true,
}}
/>
</div>
<div>
<h2>third</h2>
</div>
</div>
);
};
export default Particule;
css
.container{
display: flex;
position: absolute;
width: 100vw;
height : 100%;
top: 0;
left: 0;
}
.container > div {
position: relative;
width: 100%;
}
i have this result in the image your see me i want this
but me i want to contain my particle in the second div only [enter image description here](https://i.stack.imgur.com/Yxteq.png)
i try to add the style in the the position absolute , but nothing
<Particles
id="tsparticles"
init={particlesInit}
loaded={particlesLoaded}
options={{
background: {
color: {
value: "#0d47a1",
}
},
style:{
position:"absolute"
},
thanks for the help
i want to add the particle in the second div with heigth and width i dont want on the all page

How to make a realtime chart with Highcharts, Firebase, ReactJS with Typescript?

I need to create a graph in real time getting data from Firebase. I'm using ReactJs with typescript along with Highcharts.
I've already managed to simulate Highcharts in real time, but now I'm wondering how I can connect it to get the data that will be added to Firebase.
Global Graph Component:
import * as Highcharts from 'highcharts';
import HighchartsReact from 'highcharts-react-official';
import HighchartsMore from 'highcharts/highcharts-more';
import HighchartsAccessibility from 'highcharts/modules/accessibility';
import HighchartsData from 'highcharts/modules/data';
import HighchartsExporting from 'highcharts/modules/exporting';
import HighchartsHeatmap from 'highcharts/modules/heatmap';
import HighchartsTreeChart from 'highcharts/modules/treemap';
import { defaultTheme } from '../../../styles/themes';
HighchartsAccessibility(Highcharts);
HighchartsMore(Highcharts);
HighchartsData(Highcharts);
HighchartsHeatmap(Highcharts);
HighchartsTreeChart(Highcharts);
HighchartsExporting(Highcharts);
interface IChartProps {
options: Highcharts.Options;
}
export const labelsStyle = (
fontSize = 20,
color = defaultTheme.palette.grey[900]
) => ({
color,
fontSize: `${fontSize / 16}rem`,
fontFamily: 'Poppins',
margin: '0px',
fontWeight: 400,
lineHeight: `${(fontSize + 10) / 16}rem`,
});
export const Chart: React.FC<IChartProps> = ({ options }) => {
Highcharts.setOptions({
// rangeSelector: {
// enabled: false,
// },
// navigator: {
// enabled: false,
// },
xAxis: {
labels: {
style: labelsStyle(),
},
},
yAxis: {
labels: {
style: labelsStyle(18, defaultTheme.palette.grey[200]),
},
},
credits: {
enabled: false,
},
exporting: {
enabled: false,
},
});
return <HighchartsReact highcharts={Highcharts} options={options} />;
};
Realtime Graph Component:
import { TooltipFormatterContextObject } from 'highcharts';
import {
Chart,
labelsStyle,
} from '../../../../shared/components/DataDisplay/Chart';
import { defaultTheme } from '../../../../shared/styles/themes';
import { formatDate } from '../../../../shared/utils/date';
import { getRandomIntInclusive } from '../../../../shared/utils/math';
export const GraphHeartRateMonitor: React.FC = () => {
const analyticsDataState = {
dates: [],
data: [],
};
const options: Highcharts.Options = {
chart: {
borderWidth: 0,
height: '176px',
plotBackgroundColor: defaultTheme.palette.background.default,
type: 'line',
marginRight: 10,
events: {
load() {
const series = this.series[0];
setInterval(function () {
const x = new Date().getTime(), // current time
y = getRandomIntInclusive(80, 120);
series.addPoint([x, y], true, true);
}, 1000);
},
},
},
title: {
text: '',
},
xAxis: {
type: 'datetime',
tickPixelInterval: 300,
labels: {
enabled: false,
},
},
yAxis: {
title: {
text: '',
},
gridLineColor: defaultTheme.palette.background.default,
tickInterval: 10,
labels: {
enabled: false,
},
plotLines: [
{
value: 120,
color: defaultTheme.palette.red[300],
width: 2,
label: {
text: '120',
useHTML: true,
verticalAlign: 'middle',
align: 'left',
style: {
background: defaultTheme.palette.red[300],
borderRadius: '24px',
height: '18px',
padding: '2px 12px',
color: defaultTheme.palette.common.white,
fontFamily: 'Roboto Mono',
fontSize: '12px',
fontWeight: '400',
},
},
},
],
// lineColor: defaultTheme.palette.background.default,
},
legend: {
enabled: false,
},
exporting: {
enabled: false,
},
series: [
{
name: 'Random data',
color: defaultTheme.palette.grey[900],
data: (function () {
// generate an array of random data
let data = [],
time = new Date().getTime(),
i;
for (i = -19; i <= 0; i++) {
data.push({
x: time + i * 1000,
y: getRandomIntInclusive(80, 120),
});
}
return data;
})(),
},
],
tooltip: {
useHTML: true,
backgroundColor: defaultTheme.palette.background.paper,
borderRadius: 4,
shadow: {
color: defaultTheme.palette.common.black,
},
padding: 12,
style: labelsStyle(20, defaultTheme.palette.grey[300]),
formatter() {
const self: TooltipFormatterContextObject = this;
return `
<span> Time:</span>
<strong>${formatDate(new Date(self.x), 'HH:mm:ss')}</strong>
</br>
<span>Heart Rate:</span>
<strong >${self.y} bpm</strong>
`;
},
},
};
return <Chart options={options} />;
};
To call the component just call as a normal component <GraphHeartRateMonitor /> .

Is it possible to have a background linear gradient and alternating box annotations using react-charts-js2?

I am trying to accomplish the following graph in react-charts-js2.
ChartsJS2 chart in Figma
I have been able to accomplish the version with the linear gradient using this code:
<Line
data={createLineGraphData(
gpuMemoryUtilizationTimeSeries,
toMomentTimeFormat,
val => (val == null ? 0 : val * 100),
'Power Draw (W)',
)}
options={gpuExplorerGpuMemoryUtilizationOptions}
/>
const createLineGraphData = function(
data,
fnLabel,
fnValue,
label = '',
) {
// data.result.values
const labels =
data &&
data.map(i => {
if (i && i[0] && fnLabel(i[0])) {
return fnLabel(i[0])
} else {
return '-'
}
})
const graphdata =
data &&
data.map(i => {
if (i && i[0] && fnValue(i[1])) {
return fnValue(i[1])
} else {
return 0
}
})
return canvas => {
let ctx = canvas.getContext('2d')
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50)
gradientStroke.addColorStop(1, 'rgba(29,140,248,0.2)')
gradientStroke.addColorStop(0.4, 'rgba(29,140,248,0.0)')
gradientStroke.addColorStop(0, 'rgba(29,140,248,0)') //blue colors
return {
labels: labels,
datasets: [
{
label: label,
fill: true,
backgroundColor: gradientStroke,
borderColor: '#1f8ef1',
borderWidth: 2,
borderDash: [],
borderDashOffset: 0.0,
pointBackgroundColor: '#1f8ef1',
pointBorderColor: 'rgba(255,255,255,0)',
pointHoverBackgroundColor: '#1f8ef1',
pointBorderWidth: 20,
pointHoverRadius: 4,
pointHoverBorderWidth: 15,
pointRadius: 4,
data: graphdata,
},
],
}
}
}
However when adding these options: (Specifically when I add the box annotations) the linear gradient disappears. How can I achieve both? ..
export const getGpuExplorerPowerDrawOptions = (data, timestamps) => {
const xAxisTicks = [0]
const suggestedMaxNum = maxNum(data) === 0 ? 500 : maxNum(data)
let i = 0
while (i <= suggestedMaxNum) {
i += stepSize(data)
xAxisTicks.push(i)
}
return {
maintainAspectRatio: false,
legend: {
display: false,
},
tooltips: {
backgroundColor: '#f5f5f5',
titleFontColor: '#333',
bodyFontColor: '#666',
bodySpacing: 4,
xPadding: 12,
mode: 'nearest',
intersect: 0,
position: 'nearest',
},
responsive: true,
scales: {
yAxes: [
{
position: 'right',
barPercentage: 1.6,
gridLines: {
display: false,
drawBorder: false,
},
ticks: {
callback: function(value, index, ticks) {
return value + ' W'
},
min: 0,
beginAtZero: true,
suggestedMin: 0,
suggestedMax: maxNum(data) === 0 ? 500 : maxNum(data),
stepSize: stepSize(data),
padding: 20,
fontColor: '#9a9a9a',
},
},
],
xAxes: [
{
type: 'time',
time: {
unit: 'minute',
unitStepSize: 30,
displayFormats: {
minute: 'h:mm A',
millisecond: 'h:mm A',
},
},
barPercentage: 1.6,
gridLines: {
display: false,
drawBorder: false,
color: 'rgba(255,255,255, 0.1)',
zeroLineColor: 'transparent',
},
ticks: {
padding: 20,
fontColor: '#9a9a9a',
},
},
],
},
annotation: {
annotations: [
...timestamps.map((timestamp, idx, arr) => {
return {
adjustScaleRange: false,
type: 'box',
// optional drawTime to control layering, overrides global drawTime setting
drawTime: 'beforeDatasetsDraw',
// ID of the X scale to bind onto
xScaleID: 'x-axis-0',
// ID of the Y scale to bind onto
yScaleID: 'y-axis-0',
xMin: arr[idx],
xMax: idx + 4 > arr.length - 1 ? arr[arr.length - 1] : arr[idx + 4],
// Top edge of the box in units along the y axis
yMax: xAxisTicks[xAxisTicks.length - 1],
// Bottom edge of the box
yMin: 0, //maxNum(data),
backgroundColor:
idx % 2 === 0 ? thirdBackground : 'rgb(46, 51, 58)',
}
}),
...xAxisTicks.map(tick => {
return {
type: 'line',
mode: 'horizontal',
scaleID: 'y-axis-0',
value: tick,
borderColor: 'rgba(215, 215, 215, 0.1)',
borderWidth: 1,
label: {
enabled: false,
},
}
}),
],
},
}
}
Here is a picture of what the graph looks like before I add the box annotations:
Before adding box annotations options
After adding box annotations options (Notice how the linear gradient disappears)

Why does highcharts shrink on re-render?

I'm using highcharts-react-official to render a HighchartsReact component. It shows up and works appropriately until I re-render the component. By changing the state at all, the chart will shrink vertically.
I've experimented with setting reflow in the chart options as well as toggling allowChartUpdate and immutable flags on the component itself to no avail.
const ChartView = props => {
const { data } = props;
if(data.highstockData && data.startDate && data.endDate) {
const min = parseInt(moment(data.startDate, 'x').startOf('day').format('x'));
const max = parseInt(moment(data.endDate, 'x').startOf('day').format('x'));
const chartOptions = getChartConfig(data.highstockData, min, max);
return (
<HighchartsReact
highcharts={Highcharts}
options={chartOptions}
/>
)
}
return null;
};
And the parent Component's render return:
return (
<div className="vertical-margin">
{isFetching && !data && <LoadingView/>}
{hasError && !data && <ErrorView/>}
{
data &&
<React.Fragment>
{buttonRow}
<ChartView
data={data}
/>
</React.Fragment>
}
</div>
)
As I said re-rendering for any reason causes the highchart to shrink in height with each re-render. For testing, I call this line:
this.setState({});
I could post the chart config if needed, but it's nothing too fancy.
I haven't been able to find anyone else having this issue and have been pulling my hair out searching for an answer.
It turned out to indeed be a highchart option I was passing into the component. Apparently it was because this option:
scrollbar: {
enabled: true
},
Was not nested under the xAxis section of the options as it should be. It still created a scrollbar correctly but caused this weird, shrinking issue on component render.
chart: {
marginRight: 75,
ignoreHiddenSeries: false,
panning: false,
spacingTop: 10,
height: `${Constants.HIGHCHART_TABLE_HEIGHT}px`,
},
time: {
useUTC: false
},
credits: {
enabled: false
},
exporting: {
enabled: false
},
legend: {
align: 'left',
enabled: true,
itemMarginTop: 5,
itemStyle: {
"color": "#333333",
"cursor": "pointer",
"fontSize": "12px",
"fontWeight": "normal",
"width": "240px"
},
layout: 'vertical',
verticalAlign: 'top',
y: 0
},
navigator: {
enabled: false,
xAxis: {
tickPixelInterval: 100
}
},
plotOptions: {
line: {
marker: {
enabled: true,
fillColor: "#ffffff",
lineColor: null,
lineWidth: 1
}
}
},
rangeSelector: {
enabled: false
},
tooltip: {
formatter: function () {
const sortedPoints = this.points.sort((a, b) => {
return a.point.legendOrder - b.point.legendOrder
});
return [
'<b>',
moment(this.x, 'x').format('MM/DD/YYYY HH:mm'),
'</b><br/>',
sortedPoints.map((item) => {
return [
'<br/><span style="color:'+ item.series.color +';">\u258c</span> ',
'<span>' + item.series.name + '</span>: <b>' + item.y + '</b>',
item.comments ? '<p>(' + item.comments + ')</p>' : ''
].join('');
}).join('')
].join('');
},
shared: true,
crosshairs: {
color: '#ddd',
dashStyle: 'solid'
},
},
xAxis: {
type: 'datetime',
labels:{
formatter: function() {
const parsed = moment(this.value, 'x');
return parsed.format('HH:mm') + '<br/>' + parsed.format('MM/DD');
}
},
min,
max,
reversed: true,
scrollbar: {
enabled: true
},
},
yAxis: [{
alignTicks: false,
max: 60,
min: 0,
opposite: false,
tickInterval: 5,
title: {
text: ''
}
}, {
alignTicks: false,
max: 300,
min: 0,
opposite: true,
tickInterval: 25,
title: {
text: ''
}
}],
//The below properties are watched separately for changes.
series: data,
title: {
text: ''
},
loading: false,
};
Also here's the full options file for reference. It wasn't just that scrollbar option causing it. It was a specific combination of options as I discovered by trying to re-create the problem with a new chart.

Zoom and Pan in react-chartjs-2

I have recently implemented chart display using react-chartjs-2 (https://github.com/jerairrest/react-chartjs-2)
I want to enable zooming and panning feature so that it will be more user-friendly in touch based screens. To implement this features, I installed react-hammerjs and chartjs-plugin-zoom.
import {Chart, Line} from 'react-chartjs-2';
import Hammer from 'react-hammerjs';
import zoom from 'chartjs-plugin-zoom'
And I registered the plugin
componentWillMount(){
Chart.plugins.register(zoom)
}
And the render method goes as follows:
render(){
return <Line data={data} options={options} />
}
Pan and Zoom options:
pan:{
enabled=true,
mode:'x'
},
zoom:{
enabled:true,
drag:true,
mode:'xy'
}
I guess this is the correct method to implement. Unfortunately, the above implementation did not work. I will be really grateful if some of you guys already implemented Zooming and Panning using react-chartjs-2 plugin, please share if how you achieved these functionalities. Or you could point out the problem in my code above.
In order to add Zoom and Pan capabilities to your chart components based on react-chartjs-2, you can follow the steps as shown below:
Step 1: you need to install chartjs-plugin-zoom
$ npm install chartjs-plugin-zoom
Step 2: Import chartjs-plugin-zoom in your chart component
import 'chartjs-plugin-zoom';
Step 3: Enable zoom and pan in the ChartJS component options
zoom: {
enabled: true,
mode: 'x',
},
pan: {
enabled: true,
mode: 'x',
},
That's it. So now your chart component should look like this:
import React from 'react';
import { Line } from 'react-chartjs-2';
import 'chartjs-plugin-zoom';
export default function TimelineChart({ dailyDataSets }) {
const lineChart = dailyDataSets[0] ? (
<Line
data={{
labels: dailyDataSets.map(({ date }) => date),
datasets: [
{
data: dailyDataSets.map((data) => data.attr1),
label: 'First data set',
borderColor: 'red',
fill: true,
},
{
data: dailyDataSets.map((data) => data.attr2),
label: 'Second data set',
borderColor: 'green',
fill: true,
},
],
}}
options={{
title: { display: true, text: 'My Chart' },
zoom: {
enabled: true,
mode: 'x',
},
pan: {
enabled: true,
mode: 'x',
},
}}
/>
) : null;
return <div>{lineChart}</div>;
}
Notes:
You don't have to install hammerjs explicitly, as it will be automatically included by installing chartjs-plugin-zoom as its dependency, see below:
$ npm ls
...
├─┬ chartjs-plugin-zoom#0.7.7
│ └── hammerjs#2.0.8
...
One way to zoom as an example (at least for Mac), you can move your mouse pointer into the chart area, and then scroll your mouse down or up. Once zoomed in, you can keep your mouse clicked while dragging left or right.
There's a syntax error under pan object for enabled attribute.
You've mistakenly put = instead of :
Replace this:
pan:{
enabled=true,
...
},
With:
pan:{
enabled:true,
...
},
And also as #Jun Bin suggested:
Install hammerjs as:
npm install hammerjs --save
And in your component, import it as:
import Hammer from "hammerjs";
you imported the wrong hammer it should be from "hammerjs";
You need to add import 'chartjs-plugin-zoom'; and then add zoom options into options.plugins.zoom, like:
const options = {
plugins: {
zoom: {
pan: {
enabled: true,
mode: 'x',
},
zoom: {
enabled: true,
drag: true,
mode: 'xy'
}
}
}
};
I am trying to do this in a NextJS Project. But to no success so far.
I am using a timeseries plot with date-fns/locale for German and English and keep getting this error:
Cannot convert a Symbol value to a string
TypeError: Cannot convert a Symbol value to a string at TypedRegistry.register (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4802:50) at Registry._exec (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4927:21) at eval (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4919:16) at each (webpack-internal:///./node_modules/chart.js/dist/chunks/helpers.segment.js:233:10) at eval (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4917:70) at Array.forEach (<anonymous>) at Registry._each (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4912:15) at Registry.add (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:4870:10) at Function.value [as register] (webpack-internal:///./node_modules/chart.js/dist/chart.esm.js:6192:16) at eval (webpack-internal:///./components/Charts/PortfolioPriceLineDual.jsx:39:45) at Module../components/Charts/PortfolioPriceLineDual.jsx (https://dev.domain.de/_next/static/chunks/components_Charts_PortfolioPriceLineDual_jsx.js:7758:1) at Module.options.factory (https://dev.domain.de/_next/static/chunks/webpack.js?ts=1653499440538:655:31) at __webpack_require__ (https://dev.domain.de/_next/static/chunks/webpack.js?ts=1653499440538:37:33) at Function.fn (https://dev.domain.de/_next/static/chunks/webpack.js?ts=1653499440538:310:21)
My Component:
import { Line } from 'react-chartjs-2'
import 'chartjs-adapter-date-fns'
import { de, enGB, ja } from 'date-fns/locale'
import dynamic from 'next/dynamic'
import 'chart.js/auto'
import { useRouter } from 'next/router'
import { Chart } from 'chart.js'
// import zoomPlugin from 'chartjs-plugin-zoom';
const zoomPlugin = dynamic(() => import('chartjs-plugin-zoom'), {
ssr: false,
})
Chart.register(zoomPlugin);
const PortfolioPriceLineDual = ({
title,
data,
unit,
axesOptions,
showLegend = true,
}) => {
const totalDuration = 5000
const delayBetweenPoints = totalDuration / data.datasets[0].data.length
// const animation =
const { locale } = useRouter()
let format
switch (locale) {
case 'de-DE':
format = de
break
case 'en-US':
format = enGB
break
case 'ja-JP':
format = ja
break
default:
break
}
return (
<Line
data={data}
options={{
responsive: true,
// maintainAspectRatio: true,
// aspectRatio: 16 / 9,
resizeDelay: 5,
animation: {
x: {
type: 'number',
easing: 'linear',
duration: delayBetweenPoints,
from: NaN, // the point is initially skipped
delay: (ctx) => {
if (ctx.type !== 'data' || ctx.xStarted) {
return 0
}
ctx.xStarted = true
return ctx.index * delayBetweenPoints
},
},
y: {
type: 'number',
easing: 'linear',
duration: delayBetweenPoints,
from: (ctx) => {
return ctx.index === 0
? ctx.chart.scales.y.getPixelForValue(100)
: ctx.chart
.getDatasetMeta(ctx.datasetIndex)
.data[ctx.index - 1].getProps(['y'], true).y
},
delay: (ctx) => {
if (ctx.type !== 'data' || ctx.yStarted) {
return 0
}
ctx.yStarted = true
return ctx.index * delayBetweenPoints
},
},
y1: {
type: 'number',
easing: 'linear',
duration: delayBetweenPoints,
from: (ctx) => {
return ctx.index === 0
? ctx.chart.scales.y.getPixelForValue(100)
: ctx.chart
.getDatasetMeta(ctx.datasetIndex)
.data[ctx.index - 1].getProps(['y'], true).y
},
delay: (ctx) => {
if (ctx.type !== 'data' || ctx.yStarted) {
return 0
}
ctx.yStarted = true
return ctx.index * delayBetweenPoints
},
},
},
interaction: {
mode: 'index',
intersect: false,
},
scales: {
x: {
type: 'time',
time: {
unit: 'year',
displayFormats: {
quarter: 'yyyy',
},
tooltipFormat: 'MMMM yyyy',
},
adapters: {
date: {
locale: format,
},
},
ticks: {
align: 'start',
color: '#122a42',
font: {
size: 14,
weight: 'bold',
},
},
grid: {
display: true,
drawBorder: false,
drawOnChartArea: true,
drawTicks: true,
},
},
y: {
type: 'logarithmic',
grid: {
display: true,
drawBorder: false,
drawOnChartArea: true,
drawTicks: true,
},
ticks: {
color: '#122a42',
align: 'end',
font: {
size: 10,
weight: 'normal',
},
// Include a dollar sign in the ticks
// stepSize: 1000,
callback: function (value) {
// callback: function (value, index, ticks) {
return `${new Intl.NumberFormat(locale, axesOptions).format(
value
)}`
},
},
},
y1: {
type: 'linear',
display: true,
position: 'right',
// grid line settings
grid: {
drawOnChartArea: false, // only want the grid lines for one axis to show up
},
ticks: {
color: '#122a42',
align: 'end',
font: {
size: 10,
weight: 'normal',
},
// Include a dollar sign in the ticks
// stepSize: 1000,
callback: function (value) {
// callback: function (value, index, ticks) {
return `${new Intl.NumberFormat(locale, axesOptions).format(
value
)}`
},
},
},
},
zoom: {
enabled: true,
mode: 'x',
},
pan: {
enabled: true,
mode: 'x',
},
plugins: {
zoom: {
enabled: true,
mode: 'x',
},
pan: {
enabled: true,
mode: 'x',
},
// zoom: {
// zoom: {
// wheel: {
// enabled: true,
// },
// pinch: {
// enabled: true,
// },
// mode: 'x',
// },
// },
title: {
display: true,
color: '#151C30',
font: {
size: 26,
weight: 'bold',
style: 'normal',
},
padding: {
bottom: 10,
},
text: `${title}`,
},
tooltip: {
enabled: true,
backgroundColor: '#122a42',
itemSort: function (a, b) {
return b.raw - a.raw
},
callbacks: {
label: function (context) {
let label = context.dataset.label || ''
if (label) {
label += ': '
}
if (context.parsed.y !== null) {
label += `${new Intl.NumberFormat(locale, axesOptions).format(
context.parsed.y
)} ${unit}`
}
return label
},
},
},
legend: {
position: 'bottom',
labels: {
// This more specific font property overrides the global property
color: '#151C30',
font: {
size: 12,
weight: 'light',
},
},
},
},
}}
/>
)
}
export default PortfolioPriceLineDual

Resources