-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hover/Legend not working when updating data asyncronously (React) #57
Comments
Looks like the layout issue is caused by the chart being initialized multiple times: React.useEffect(() => {
(async () => {
const { sciChartSurface, wasmContext } = await drawExample(chartData);
sciChartSurfaceRef.current = sciChartSurface;
wasmContextRef.current = wasmContext;
})();
return () => {
sciChartSurfaceRef.current?.delete();
};
}, [sciChartSurfaceRef, chartData]); I suggest initializing it once, and then, on incoming data updates, Please check the snippet below. import React from "react";
import { MouseWheelZoomModifier } from 'scichart/Charting/ChartModifiers/MouseWheelZoomModifier';
import { ZoomExtentsModifier } from 'scichart/Charting/ChartModifiers/ZoomExtentsModifier';
import { ZoomPanModifier } from 'scichart/Charting/ChartModifiers/ZoomPanModifier';
import { XyDataSeries } from 'scichart/Charting/Model/XyDataSeries';
import { NumericAxis } from 'scichart/Charting/Visuals/Axis/NumericAxis';
import { FastLineRenderableSeries } from 'scichart/Charting/Visuals/RenderableSeries/FastLineRenderableSeries';
import { SciChartSurface } from 'scichart/Charting/Visuals/SciChartSurface';
import { LegendModifier } from 'scichart/Charting/ChartModifiers/LegendModifier';
import { ELegendOrientation, ELegendPlacement } from 'scichart/Charting/Visuals/Legend/SciChartLegendBase';
import { EllipsePointMarker } from 'scichart/Charting/Visuals/PointMarkers/EllipsePointMarker';
import { RolloverModifier } from 'scichart/Charting/ChartModifiers/RolloverModifier';
import { TSciChart } from 'scichart/types/TSciChart';
const DIVID = "CHARTID"
SciChartSurface.setRuntimeLicenseKey('REDACTED');
const streamSelectors = {
56: {
uiLabel: 'First Stream',
color: "#cecece"
},
65: {
uiLabel: "Second Stream",
color: "#000000"
}
}
type ChartData = Record<number, Array<{ timestamp: number, value: number, label: string }>>
// this function is supposed to add new data series to the chart or update the existing ones
const addOrUpdateSeries = (
dataSeriesMap: Map<keyof typeof streamSelectors, XyDataSeries>,
sciChartSurface: SciChartSurface,
wasmContext: TSciChart,
chartData: ChartData,
) => {
const streamIds = Object.keys(chartData);
streamIds.forEach((s) => {
const streamId = Number(s) as keyof ChartData;
const streamSelectorId = streamId as keyof typeof streamSelectors;
if (dataSeriesMap.has(streamSelectorId)) {
const xyDataSeries = dataSeriesMap.get(streamSelectorId);
chartData[streamId].forEach((value) => {
xyDataSeries.append(value.timestamp, value.value);
});
} else {
// create new data series for the specific stream
const xyDataSeries = new XyDataSeries(wasmContext);
dataSeriesMap.set(streamSelectorId, xyDataSeries);
xyDataSeries.dataSeriesName = streamSelectors[streamSelectorId].uiLabel;
const lineColor = streamSelectors[streamSelectorId].color;
chartData[streamId].forEach((value) => {
xyDataSeries.append(value.timestamp, value.value);
});
const lineSeries = new FastLineRenderableSeries(wasmContext, {
stroke: lineColor,
strokeThickness: 2,
dataSeries: xyDataSeries,
pointMarker: new EllipsePointMarker(wasmContext, {
width: 4,
height: 4,
strokeThickness: 1,
fill: lineColor,
}),
});
lineSeries.rolloverModifierProps.tooltipLabelX = 'Date';
lineSeries.rolloverModifierProps.tooltipLabelY = '';
lineSeries.rolloverModifierProps.tooltipColor = lineColor;
sciChartSurface.renderableSeries.add(lineSeries);
}
});
};
// drawExample will initialize the chart and modifiers
const drawExample = async () => {
// Create a SciChartSurface
const { sciChartSurface, wasmContext } = await SciChartSurface.create(DIVID);
// Create the X,Y Axis
const xAxis = new NumericAxis(wasmContext);
xAxis.labelProvider.formatLabel = (unixTimestamp: number) => {
return new Date(unixTimestamp * 1000).toLocaleDateString('en-us', {
month: 'numeric',
year: 'numeric',
day: 'numeric',
});
};
const yAxis = new NumericAxis(wasmContext);
sciChartSurface.yAxes.add(yAxis);
sciChartSurface.xAxes.add(xAxis);
sciChartSurface.chartModifiers.add(
new LegendModifier({
placement: ELegendPlacement.TopLeft,
orientation: ELegendOrientation.Vertical,
showLegend: true,
showCheckboxes: true,
showSeriesMarkers: true,
})
);
sciChartSurface.chartModifiers.add(new RolloverModifier({}));
sciChartSurface.chartModifiers.add(new ZoomPanModifier());
sciChartSurface.chartModifiers.add(new ZoomExtentsModifier());
sciChartSurface.chartModifiers.add(new MouseWheelZoomModifier());
sciChartSurface.zoomExtents();
return { sciChartSurface, wasmContext };
}
export const Chart = () => {
const chartRef = React.useRef<HTMLDivElement>(null);
const [sciChartSurface, setSciChartSurface] = React.useState<SciChartSurface>();
const [wasmContext, setWasmContext] = React.useState<TSciChart>();
// a structure to hold references of dataSeries corresponding to the specific streams
const dataSeriesMapRef = React.useRef<Map<keyof typeof streamSelectors, XyDataSeries>>();
const [chartData, setChartData] = React.useState<ChartData>({} as ChartData);
React.useEffect(() => {
(async () => {
const { sciChartSurface, wasmContext } = await drawExample();
setSciChartSurface(sciChartSurface);
setWasmContext(wasmContext);
})();
dataSeriesMapRef.current = new Map<keyof typeof streamSelectors, XyDataSeries>();
return () => {
sciChartSurface?.delete();
};
}, []);// make sure the chart is initialized only once
React.useEffect(() => {
if (dataSeriesMapRef.current && sciChartSurface && wasmContext) {
addOrUpdateSeries(
dataSeriesMapRef.current,
sciChartSurface,
wasmContext,
chartData
);
}
}, [chartData])
React.useEffect(() => {
// make sure the chart is initialized before passing new data
if (!sciChartSurface) {
return;
}
const data: ChartData = {
56: [{
timestamp: new Date("2021-01-01").getTime() / 1000,
value: 5,
label: "Jan 2021"
},
{
timestamp: new Date("2021-02-01").getTime() / 1000,
value: 10,
label: "Feb 2021"
},
{
timestamp: new Date("2021-03-01").getTime() / 1000,
value: 15,
label: "March 2021"
},
],
65: [{
timestamp: new Date("2021-01-01").getTime() / 1000,
value: 15,
label: "Jan 2021"
},
{
timestamp: new Date("2021-02-01").getTime() / 1000,
value: 20,
label: "Feb 2021"
},
{
timestamp: new Date("2021-03-01").getTime() / 1000,
value: 25,
label: "March 2021"
}]
}
const nextData: ChartData = {
56: [{
timestamp: new Date("2021-04-01").getTime() / 1000,
value: 5,
label: "April 2021"
},
{
timestamp: new Date("2021-05-01").getTime() / 1000,
value: 10,
label: "May 2021"
},
{
timestamp: new Date("2021-06-01").getTime() / 1000,
value: 15,
label: "June 2021"
},
],
65: [{
timestamp: new Date("2021-04-01").getTime() / 1000,
value: 15,
label: "April 2021"
},
{
timestamp: new Date("2021-05-01").getTime() / 1000,
value: 20,
label: "May 2021"
},
{
timestamp: new Date("2021-06-01").getTime() / 1000,
value: 25,
label: "June 2021"
}]
};
setChartData(data)
// simulate new data updates
setTimeout(() => {
setChartData(nextData)
}, 5000)
}, [sciChartSurface])
return <>
<div ref={chartRef} id={DIVID} style={{ width: 800, height: 800 }} />
</>
} |
@observerjnr I don't hate the idea; being able to simply add/change data on an existing wasm instance seems an optimal way to go. What I might suggest is a way to clear it out and rebuild it easily. For example, in react, you might make changes on a form and get data from API pretty frequently, so instead of me keeping track of what's been on the chart, a usual pattern is to simply give the data and it gets redrawn. That's why I tried the Could you add a way to clear out the series? that way I'd be able to easy say, "OK my data changed, here you go, rerender" |
Hi Jesse
The function call sciChartSurface.invalidateElement() schedules a redraw,
however it may not draw immediately as we internally throttle/schedule
draws.
Is that what you were looking for?
Best regards,
Andrew
…On Wed, Mar 10, 2021 at 3:59 PM Jesse Wolgamott ***@***.***> wrote:
@observerjnr <https://github.com/observerjnr> I don't hate the idea;
being able to simply add/change data on an existing wasm instance seems an
optimal way to go.
What I might suggest is a way to clear it out and rebuild it easily. For
example, in react, you might make changes on a form and get data from API
pretty frequently, so instead of me keeping track of what's been on the
chart, a usual pattern is to simply give the data and it gets redrawn.
That's why I tried the sciChartSurface?.delete(); at the start of the
effect.
Could you add a way to clear out the series? that way I'd be able to easy
say, "OK my data changed, here you go, rerender"
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#57 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADLEDVNY76K7YRE3DEBMI4LTC6CMTANCNFSM4YZL4MJQ>
.
|
@andyb1979 that seems reasonable, but I found The Zoom on the chart is not quite where I'd want it to be.. any suggestions to force the surface to re-figure out the zoom based on the new series in the chart? import React from "react";
import { MouseWheelZoomModifier } from 'scichart/Charting/ChartModifiers/MouseWheelZoomModifier';
import { ZoomExtentsModifier } from 'scichart/Charting/ChartModifiers/ZoomExtentsModifier';
import { ZoomPanModifier } from 'scichart/Charting/ChartModifiers/ZoomPanModifier';
import { XyDataSeries } from 'scichart/Charting/Model/XyDataSeries';
import { NumericAxis } from 'scichart/Charting/Visuals/Axis/NumericAxis';
import { FastLineRenderableSeries } from 'scichart/Charting/Visuals/RenderableSeries/FastLineRenderableSeries';
import { SciChartSurface } from 'scichart/Charting/Visuals/SciChartSurface';
import { LegendModifier } from 'scichart/Charting/ChartModifiers/LegendModifier';
import { ELegendOrientation, ELegendPlacement } from 'scichart/Charting/Visuals/Legend/SciChartLegendBase';
import { EllipsePointMarker } from 'scichart/Charting/Visuals/PointMarkers/EllipsePointMarker';
import { RolloverModifier } from 'scichart/Charting/ChartModifiers/RolloverModifier';
import { TSciChart } from 'scichart/types/TSciChart';
const DIVID = "CHARTID"
SciChartSurface.setRuntimeLicenseKey('');
const streamSelectors = {
56: {
uiLabel: 'First Stream',
color: "#cecece"
},
65: {
uiLabel: "Second Stream",
color: "#000000"
}
}
type ChartData = Record<number, Array<{ timestamp: number, value: number, label: string }>>
const updateChartWithData = (chartData: ChartData,
wasmContext: TSciChart,
sciChartSurface: SciChartSurface) => {
sciChartSurface.renderableSeries.clear();
const streamIds = Object.keys(chartData);
streamIds.forEach((s) => {
const streamId = Number(s) as keyof ChartData;
const streamSelectorId = streamId as keyof typeof streamSelectors;
const xyDataSeries = new XyDataSeries(wasmContext);
xyDataSeries.dataSeriesName = streamSelectors[streamSelectorId].uiLabel;
chartData[streamId].forEach((value) => {
xyDataSeries.append(value.timestamp, value.value);
});
const lineColor = streamSelectors[streamSelectorId].color;
const lineSeries = new FastLineRenderableSeries(wasmContext, {
stroke: lineColor,
strokeThickness: 2,
dataSeries: xyDataSeries,
pointMarker: new EllipsePointMarker(wasmContext, {
width: 4,
height: 4,
strokeThickness: 1,
fill: lineColor,
// stroke: 'LightSteelBlue',
}),
});
lineSeries.rolloverModifierProps.tooltipLabelX = 'Date';
lineSeries.rolloverModifierProps.tooltipLabelY = '';
// lineSeries.rolloverModifierProps.tooltipTextColor = theme.palette.getContrastText(lineColor);
lineSeries.rolloverModifierProps.tooltipColor = lineColor;
sciChartSurface.renderableSeries.add(lineSeries);
sciChartSurface.zoomExtents(); // added after next comment
});
}
const drawExample = async () => {
// Create a SciChartSurface
const { sciChartSurface, wasmContext } = await SciChartSurface.create(DIVID);
// Create the X,Y Axis
const xAxis = new NumericAxis(wasmContext);
xAxis.labelProvider.formatLabel = (unixTimestamp: number) => {
return new Date(unixTimestamp * 1000).toLocaleDateString('en-us', {
month: 'numeric',
year: 'numeric',
day: 'numeric',
});
};
const yAxis = new NumericAxis(wasmContext); // ;, { growBy: new NumberRange(0.05, 0.05) });
sciChartSurface.yAxes.add(yAxis);
sciChartSurface.xAxes.add(xAxis);
sciChartSurface.chartModifiers.add(
new LegendModifier({
placement: ELegendPlacement.TopLeft,
orientation: ELegendOrientation.Vertical,
showLegend: true,
showCheckboxes: true,
showSeriesMarkers: true,
})
);
sciChartSurface.chartModifiers.add(new RolloverModifier({}));
sciChartSurface.chartModifiers.add(new ZoomPanModifier());
sciChartSurface.chartModifiers.add(new ZoomExtentsModifier());
sciChartSurface.chartModifiers.add(new MouseWheelZoomModifier());
sciChartSurface.zoomExtents();
return { sciChartSurface, wasmContext };
}
export const Chart = () => {
const chartRef = React.useRef<HTMLDivElement>(null);
const sciChartSurfaceRef = React.useRef<SciChartSurface>();
const wasmContextRef = React.useRef<TSciChart>();
const [chartData, setChartData] = React.useState<ChartData>({} as ChartData);
React.useEffect(() => {
console.log('RUN React.useEffect');
(async () => {
const { sciChartSurface, wasmContext } = await drawExample();
sciChartSurfaceRef.current = sciChartSurface;
wasmContextRef.current = wasmContext;
})();
// Deleting sciChartSurface to prevent memory leak
return () => {
sciChartSurfaceRef.current?.delete();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sciChartSurfaceRef]);
React.useEffect(() => {
if (wasmContextRef.current && sciChartSurfaceRef.current) {
updateChartWithData(chartData, wasmContextRef.current, sciChartSurfaceRef.current)
}
}, [chartData])
React.useEffect(() => {
const data: ChartData = {
56: [{
timestamp: new Date("2021-01-01").getTime() / 1000,
value: 5,
label: "Jan 2021"
},
{
timestamp: new Date("2021-02-01").getTime() / 1000,
value: 10,
label: "Feb 2021"
},
{
timestamp: new Date("2021-03-01").getTime() / 1000,
value: 15,
label: "March 2021"
},
],
65: [{
timestamp: new Date("2021-01-01").getTime() / 1000,
value: 15,
label: "Jan 2021"
},
{
timestamp: new Date("2021-02-01").getTime() / 1000,
value: 20,
label: "Feb 2021"
},
{
timestamp: new Date("2021-03-01").getTime() / 1000,
value: 25,
label: "March 2021"
}]
}
setTimeout(() => {
setChartData(data)
}, 250)
}, [])
return <>
<div ref={chartRef} id={DIVID} style={{ width: 800, height: 800 }} />
</>
} |
hi @jwo try to call |
@klishevich that worked! Ok, so yes I think with all these things, no code change needed. I would recommend that y’all add a documentation with async data that uses the techniques above. Feel free to use my example, but you probably have better ones. Thanks for working this through with me! feel free to close if you like, I’m all good here |
Hi there! Using the https://demo.scichart.com/javascript-line-chart if I move the data to update asyncronously (like from an API), the legend and the hover bar don't seem to work. They'll work on the next render, but not right away.
Below the green hover bar only appears if I move my mouse between the graph and the incorrectly positioned legend.
Expectations
drawExample
is runNotes:
sciChartSurfaceRef.current?.delete();
to the start of the Effect to force a new wasm, but it did not seem to change the outcomeCode
The text was updated successfully, but these errors were encountered: