Skip to content

Commit 447ecf3

Browse files
authored
Merge pull request #856 from raheeliftikhar5/echarts-compatible-with-older-version
fix echarts compatiblity with older versions
2 parents 3f3911f + 0c5f620 commit 447ecf3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2958
-91
lines changed

client/packages/lowcoder-comps/package.json

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

0 commit comments

Comments
 (0)