Skip to content

Commit

Permalink
feat(react-components): calculate min/max and store value in store fo…
Browse files Browse the repository at this point in the history
…r chart to consume
  • Loading branch information
ssjagad authored and chejimmy committed Feb 28, 2024
1 parent 548351a commit 41b8551
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import isEqual from 'lodash.isequal';
import { useChartStore } from '../store';

export const useDataStreamMaxMin = () => {
const dataStreamMaxes = useChartStore(
(state) => state.dataStreamMaxes,
isEqual
);

const dataStreamMins = useChartStore(
(state) => state.dataStreamMins,
isEqual
);

return {
dataStreamMaxes,
dataStreamMins,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { StateCreator } from 'zustand';

export type MinMaxMap = { [key in string]: number | undefined };

export interface MinMaxData {
dataStreamMaxes: MinMaxMap;
dataStreamMins: MinMaxMap;
}

export interface MinMaxState extends MinMaxData {
setMaxes: (datastreamMaxes: MinMaxMap) => void;
setMins: (datastreamMins: MinMaxMap) => void;
}

export const createMinMaxSlice: StateCreator<MinMaxState> = (set) => ({
dataStreamMaxes: {},
dataStreamMins: {},
setMaxes: (datastreamMaxes) =>
set(() => ({
dataStreamMaxes: {
...datastreamMaxes,
},
})),
setMins: (datastreamMins) =>
set(() => ({
dataStreamMins: {
...datastreamMins,
},
})),
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { createMultiYAxisSlice, MultiYAxisState } from './multiYAxis';
import { createDataStreamsSlice, DataStreamsState } from './contextDataStreams';
import { createMinMaxSlice, MinMaxState } from './dataStreamMinMaxStore';

export type StateData = DataStreamsState &
MultiYAxisState & { chartId: string };
MultiYAxisState &
MinMaxState & { chartId: string };

export const createChartStore = (
initProps: Partial<StateData> & Required<Pick<Partial<StateData>, 'chartId'>>
Expand All @@ -16,6 +18,7 @@ export const createChartStore = (
...initProps,
...createMultiYAxisSlice(...args),
...createDataStreamsSlice(...args),
...createMinMaxSlice(...args),
}),
{ name: `chart-store-${initProps?.chartId}` }
)
Expand Down
7 changes: 6 additions & 1 deletion packages/react-components/src/echarts/configure.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import * as echartsCore from 'echarts/core';

// Custom Extensions
import { yAxisSyncExtension, trendCursorsExtension } from './extensions';
import {
yAxisSyncExtension,
trendCursorsExtension,
dataStreamMinMaxSyncExtension,
} from './extensions';

export const configureEchartsPlugins = () => {
echartsCore.use(yAxisSyncExtension);
echartsCore.use(trendCursorsExtension);
echartsCore.use(dataStreamMinMaxSyncExtension);
};
1 change: 1 addition & 0 deletions packages/react-components/src/echarts/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './yAxisSync';
export * from './trendCursors';
export * from './minMaxDataStreamValues';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './updateDataStreamMinMax';
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import SeriesModel from 'echarts/types/src/model/Series';
import { SeriesOption, DefaultStatesMixin } from 'echarts/types/src/util/types';

export const findMinMax = (
series: SeriesModel<SeriesOption<unknown, DefaultStatesMixin>>,
start: number,
end: number
) => {
let max: number | undefined = undefined;
let min: number | undefined = undefined;
series.getData().each((dims) => {
const dataPoint = series.getData().getValues(dims) as number[];
if (contains(start, end, dataPoint[0])) {
if (max === undefined || dataPoint[1] > max) {
max = dataPoint[1];
}
if (min === undefined || dataPoint[1] < min) {
min = dataPoint[1];
}
}
});
return { max, min };
};

/* We are checking if the data is within the x-axis of the viewport
Currently, the dataZoom also filters the points so that the series
only contains the points seen in the viewport, but this decouples
the functinality in case of changes to useDataZoom in the future.
*/
const contains = (start: number, end: number, value: number) => {
return value > start && value < end;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { EChartsExtensionInstallRegisters } from 'echarts/types/src/extension';
import { findMinMax } from './minMaxDataStreamSync';
import useDataStore from '../../../store';
import throttle from 'lodash.throttle';
import Grid from 'echarts/types/src/coord/cartesian/Grid';
import { LifecycleEvents } from 'echarts/types/src/core/lifecycle';

const THROTTLE_RATE = 2000;

// Echarts core use type does not map correctly to the echarts extension type so exporting as any
// eslint-disable-next-line
export const dataStreamMinMaxSyncExtension: any = (registers: EChartsExtensionInstallRegisters) => {
const dataStreamMinMaxCallback = (
...args: LifecycleEvents['series:afterupdate']
) => {
const [ecModel, api, params] = args;
const appKitChartId = ecModel.option.appKitChartId as string; // widget-id
const grid = api
?.getCoordinateSystems()
.find((a) => a.model?.type === 'grid') as Grid;

if (!grid) return; // grid not found, return early

const axis = grid?.getAxis('x'); //get X axis object from echarts
if (!axis) return; // axis not found, return early

const startDateXCoordinateValue = axis?.coordToData(
axis.toLocalCoord(axis?.getExtent()[0])
); // start date coordinate value for x as number
const endDateXCoordinateValue = axis?.coordToData(
axis.toLocalCoord(axis?.getExtent()[1])
); // end date coordinate value for x as number

if (
!params.updatedSeries ||
startDateXCoordinateValue === undefined ||
endDateXCoordinateValue === undefined ||
startDateXCoordinateValue > endDateXCoordinateValue
)
return;

const minValues: Record<string, number | undefined> = {}; // each value will be {dsId: min}
const maxValues: Record<string, number | undefined> = {}; // each value will be {dsId: max}

params.updatedSeries?.forEach((series) => {
const id = `${series.option.id}`; // datastream-id
const { max, min } = findMinMax(
series,
startDateXCoordinateValue,
endDateXCoordinateValue
);
minValues[id] = min;
maxValues[id] = max;
});
// add min/max values to the appropriate chart store
const storeState = useDataStore.getState();
const stores = storeState.chartStores;
const store = stores[appKitChartId]; //get store for widget
if (!store) return;
const state = store.getState();
state.setMaxes(maxValues);
state.setMins(minValues);
};

registers.registerUpdateLifecycle(
'series:afterupdate',
throttle(dataStreamMinMaxCallback, THROTTLE_RATE)
);
};

0 comments on commit 41b8551

Please sign in to comment.