Skip to content

Commit 12598ab

Browse files
committed
graph chart commponent
1 parent ce74251 commit 12598ab

File tree

1 file changed

+319
-0
lines changed

1 file changed

+319
-0
lines changed
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
import {
2+
changeChildAction,
3+
changeValueAction,
4+
CompAction,
5+
CompActionTypes,
6+
wrapChildAction,
7+
} from "lowcoder-core";
8+
import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig";
9+
import { graphChartChildrenMap, ChartSize, getDataKeys } from "./graphChartConstants";
10+
import { graphChartPropertyView } from "./graphChartPropertyView";
11+
import _ from "lodash";
12+
import { useContext, useEffect, useMemo, useRef, useState } from "react";
13+
import ReactResizeDetector from "react-resize-detector";
14+
import ReactECharts from "../chartComp/reactEcharts";
15+
import {
16+
childrenToProps,
17+
depsConfig,
18+
genRandomKey,
19+
NameConfig,
20+
UICompBuilder,
21+
withDefault,
22+
withExposingConfigs,
23+
withViewFn,
24+
ThemeContext,
25+
chartColorPalette,
26+
getPromiseAfterDispatch,
27+
dropdownControl,
28+
JSONObject
29+
} from "lowcoder-sdk";
30+
import { getEchartsLocale, trans } from "i18n/comps";
31+
import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig";
32+
import {
33+
echartsConfigOmitChildren,
34+
getEchartsConfig,
35+
getSelectedPoints,
36+
} from "./graphChartUtils";
37+
import 'echarts-extension-gmap';
38+
import log from "loglevel";
39+
40+
let clickEventCallback = () => {};
41+
42+
const chartModeOptions = [
43+
{
44+
label: "ECharts JSON",
45+
value: "json",
46+
}
47+
] as const;
48+
49+
let GraphChartTmpComp = (function () {
50+
return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...graphChartChildrenMap}, () => null)
51+
.setPropertyViewFn(graphChartPropertyView)
52+
.build();
53+
})();
54+
55+
GraphChartTmpComp = withViewFn(GraphChartTmpComp, (comp) => {
56+
const mode = comp.children.mode.getView();
57+
const onUIEvent = comp.children.onUIEvent.getView();
58+
const onEvent = comp.children.onEvent.getView();
59+
const echartsCompRef = useRef<ReactECharts | null>();
60+
const [chartSize, setChartSize] = useState<ChartSize>();
61+
const firstResize = useRef(true);
62+
const theme = useContext(ThemeContext);
63+
const defaultChartTheme = {
64+
color: chartColorPalette,
65+
backgroundColor: "#fff",
66+
};
67+
68+
let themeConfig = defaultChartTheme;
69+
try {
70+
themeConfig = theme?.theme.chart ? JSON.parse(theme?.theme.chart) : defaultChartTheme;
71+
} catch (error) {
72+
log.error('theme chart error: ', error);
73+
}
74+
75+
const triggerClickEvent = async (dispatch: any, action: CompAction<JSONValue>) => {
76+
await getPromiseAfterDispatch(
77+
dispatch,
78+
action,
79+
{ autoHandleAfterReduce: true }
80+
);
81+
onEvent('click');
82+
}
83+
84+
useEffect(() => {
85+
const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance();
86+
if (!echartsCompInstance) {
87+
return _.noop;
88+
}
89+
echartsCompInstance?.on("click", (param: any) => {
90+
document.dispatchEvent(new CustomEvent("clickEvent", {
91+
bubbles: true,
92+
detail: {
93+
action: 'click',
94+
data: param.data,
95+
}
96+
}));
97+
triggerClickEvent(
98+
comp.dispatch,
99+
changeChildAction("lastInteractionData", param.data, false)
100+
);
101+
});
102+
return () => {
103+
echartsCompInstance?.off("click");
104+
document.removeEventListener('clickEvent', clickEventCallback)
105+
};
106+
}, []);
107+
108+
useEffect(() => {
109+
// bind events
110+
const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance();
111+
if (!echartsCompInstance) {
112+
return _.noop;
113+
}
114+
echartsCompInstance?.on("selectchanged", (param: any) => {
115+
const option: any = echartsCompInstance?.getOption();
116+
document.dispatchEvent(new CustomEvent("clickEvent", {
117+
bubbles: true,
118+
detail: {
119+
action: param.fromAction,
120+
data: getSelectedPoints(param, option)
121+
}
122+
}));
123+
124+
if (param.fromAction === "select") {
125+
comp.dispatch(changeChildAction("selectedPoints", getSelectedPoints(param, option), false));
126+
onUIEvent("select");
127+
} else if (param.fromAction === "unselect") {
128+
comp.dispatch(changeChildAction("selectedPoints", getSelectedPoints(param, option), false));
129+
onUIEvent("unselect");
130+
}
131+
132+
triggerClickEvent(
133+
comp.dispatch,
134+
changeChildAction("lastInteractionData", getSelectedPoints(param, option), false)
135+
);
136+
});
137+
// unbind
138+
return () => {
139+
echartsCompInstance?.off("selectchanged");
140+
document.removeEventListener('clickEvent', clickEventCallback)
141+
};
142+
}, [onUIEvent]);
143+
144+
const echartsConfigChildren = _.omit(comp.children, echartsConfigOmitChildren);
145+
const option = useMemo(() => {
146+
return getEchartsConfig(
147+
childrenToProps(echartsConfigChildren) as ToViewReturn<typeof echartsConfigChildren>,
148+
chartSize
149+
);
150+
}, [chartSize, ...Object.values(echartsConfigChildren)]);
151+
152+
useEffect(() => {
153+
comp.children.mapInstance.dispatch(changeValueAction(null, false))
154+
if(comp.children.mapInstance.value) return;
155+
}, [option])
156+
157+
return (
158+
<ReactResizeDetector
159+
onResize={(w, h) => {
160+
if (w && h) {
161+
setChartSize({ w: w, h: h });
162+
}
163+
if (!firstResize.current) {
164+
// ignore the first resize, which will impact the loading animation
165+
echartsCompRef.current?.getEchartsInstance().resize();
166+
} else {
167+
firstResize.current = false;
168+
}
169+
}}
170+
>
171+
<ReactECharts
172+
ref={(e) => (echartsCompRef.current = e)}
173+
style={{ height: "100%" }}
174+
notMerge
175+
lazyUpdate
176+
opts={{ locale: getEchartsLocale() }}
177+
option={option}
178+
theme={mode !== 'map' ? themeConfig : undefined}
179+
mode={mode}
180+
/>
181+
</ReactResizeDetector>
182+
);
183+
});
184+
185+
function getYAxisFormatContextValue(
186+
data: Array<JSONObject>,
187+
yAxisType: EchartsAxisType,
188+
yAxisName?: string
189+
) {
190+
const dataSample = yAxisName && data.length > 0 && data[0][yAxisName];
191+
let contextValue = dataSample;
192+
if (yAxisType === "time") {
193+
// to timestamp
194+
const time =
195+
typeof dataSample === "number" || typeof dataSample === "string"
196+
? new Date(dataSample).getTime()
197+
: null;
198+
if (time) contextValue = time;
199+
}
200+
return contextValue;
201+
}
202+
203+
GraphChartTmpComp = class extends GraphChartTmpComp {
204+
private lastYAxisFormatContextVal?: JSONValue;
205+
private lastColorContext?: JSONObject;
206+
207+
updateContext(comp: this) {
208+
// the context value of axis format
209+
let resultComp = comp;
210+
const data = comp.children.data.getView();
211+
const sampleSeries = comp.children.series.getView().find((s) => !s.getView().hide);
212+
const yAxisContextValue = getYAxisFormatContextValue(
213+
data,
214+
comp.children.yConfig.children.yAxisType.getView(),
215+
sampleSeries?.children.columnName.getView()
216+
);
217+
if (yAxisContextValue !== comp.lastYAxisFormatContextVal) {
218+
comp.lastYAxisFormatContextVal = yAxisContextValue;
219+
resultComp = comp.setChild(
220+
"yConfig",
221+
comp.children.yConfig.reduce(
222+
wrapChildAction(
223+
"formatter",
224+
AxisFormatterComp.changeContextDataAction({ value: yAxisContextValue })
225+
)
226+
)
227+
);
228+
}
229+
// item color context
230+
const colorContextVal = {
231+
seriesName: sampleSeries?.children.seriesName.getView(),
232+
value: yAxisContextValue,
233+
};
234+
if (
235+
comp.children.chartConfig.children.comp.children.hasOwnProperty("itemColor") &&
236+
!_.isEqual(colorContextVal, comp.lastColorContext)
237+
) {
238+
comp.lastColorContext = colorContextVal;
239+
resultComp = resultComp.setChild(
240+
"chartConfig",
241+
comp.children.chartConfig.reduce(
242+
wrapChildAction(
243+
"comp",
244+
wrapChildAction("itemColor", ItemColorComp.changeContextDataAction(colorContextVal))
245+
)
246+
)
247+
);
248+
}
249+
return resultComp;
250+
}
251+
252+
override reduce(action: CompAction): this {
253+
const comp = super.reduce(action);
254+
if (action.type === CompActionTypes.UPDATE_NODES_V2) {
255+
const newData = comp.children.data.getView();
256+
// data changes
257+
if (comp.children.data !== this.children.data) {
258+
setTimeout(() => {
259+
// update x-axis value
260+
const keys = getDataKeys(newData);
261+
if (keys.length > 0 && !keys.includes(comp.children.xAxisKey.getView())) {
262+
comp.children.xAxisKey.dispatch(changeValueAction(keys[0] || ""));
263+
}
264+
// pass to child series comp
265+
comp.children.series.dispatchDataChanged(newData);
266+
}, 0);
267+
}
268+
return this.updateContext(comp);
269+
}
270+
return comp;
271+
}
272+
273+
override autoHeight(): boolean {
274+
return false;
275+
}
276+
};
277+
278+
let GraphChartComp = withExposingConfigs(GraphChartTmpComp, [
279+
depsConfig({
280+
name: "selectedPoints",
281+
desc: trans("chart.selectedPointsDesc"),
282+
depKeys: ["selectedPoints"],
283+
func: (input) => {
284+
return input.selectedPoints;
285+
},
286+
}),
287+
depsConfig({
288+
name: "lastInteractionData",
289+
desc: trans("chart.lastInteractionDataDesc"),
290+
depKeys: ["lastInteractionData"],
291+
func: (input) => {
292+
return input.lastInteractionData;
293+
},
294+
}),
295+
depsConfig({
296+
name: "data",
297+
desc: trans("chart.dataDesc"),
298+
depKeys: ["data", "mode"],
299+
func: (input) =>[] ,
300+
}),
301+
new NameConfig("title", trans("chart.titleDesc")),
302+
]);
303+
304+
305+
export const GraphChartCompWithDefault = withDefault(GraphChartComp, {
306+
xAxisKey: "date",
307+
series: [
308+
{
309+
dataIndex: genRandomKey(),
310+
seriesName: trans("chart.spending"),
311+
columnName: "spending",
312+
},
313+
{
314+
dataIndex: genRandomKey(),
315+
seriesName: trans("chart.budget"),
316+
columnName: "budget",
317+
},
318+
],
319+
});

0 commit comments

Comments
 (0)