Skip to content

Commit d16656a

Browse files
committed
themeriver chart component
1 parent 045a90f commit d16656a

File tree

1 file changed

+318
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)