Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit dd58106

Browse files
Merge pull request #1548 from lowcoder-org/feature/echarts
boxplot/parallel/line3d chart
2 parents aa27381 + c40b5e0 commit dd58106

26 files changed

+3946
-10
lines changed

‎client/packages/lowcoder-comps/package.json‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"agora-rtm-sdk": "^1.5.1",
2424
"big.js": "^6.2.1",
2525
"echarts-extension-gmap": "^1.6.0",
26+
"echarts-gl": "^2.0.9",
2627
"echarts-wordcloud": "^2.1.0",
2728
"lowcoder-cli": "workspace:^",
2829
"lowcoder-sdk": "workspace:^",
@@ -90,6 +91,30 @@
9091
"h": 40
9192
}
9293
},
94+
"boxplotChart": {
95+
"name": "Boxplot Chart",
96+
"icon": "./icons/icon-chart.svg",
97+
"layoutInfo": {
98+
"w": 12,
99+
"h": 40
100+
}
101+
},
102+
"parallelChart": {
103+
"name": "Parallel Chart",
104+
"icon": "./icons/icon-chart.svg",
105+
"layoutInfo": {
106+
"w": 12,
107+
"h": 40
108+
}
109+
},
110+
"line3dChart": {
111+
"name": "Line3D Chart",
112+
"icon": "./icons/icon-chart.svg",
113+
"layoutInfo": {
114+
"w": 12,
115+
"h": 40
116+
}
117+
},
93118
"imageEditor": {
94119
"name": "Image Editor",
95120
"icon": "./icons/icon-chart.svg",

‎client/packages/lowcoder-comps/src/comps/basicChartComp/reactEcharts/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as echarts from "echarts";
2+
import "echarts-gl";
23
import "echarts-wordcloud";
34
import { EChartsReactProps, EChartsInstance, EChartsOptionWithMap } from "./types";
45
import EChartsReactCore from "./core";
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
import {
2+
changeChildAction,
3+
changeValueAction,
4+
CompAction,
5+
CompActionTypes,
6+
wrapChildAction,
7+
} from "lowcoder-core";
8+
import { AxisFormatterComp, EchartsAxisType } from "../basicChartComp/chartConfigs/cartesianAxisConfig";
9+
import { boxplotChartChildrenMap, ChartSize, getDataKeys } from "./boxplotChartConstants";
10+
import { boxplotChartPropertyView } from "./boxplotChartPropertyView";
11+
import _ from "lodash";
12+
import { useContext, useEffect, useMemo, useRef, useState } from "react";
13+
import ReactResizeDetector from "react-resize-detector";
14+
import ReactECharts from "../basicChartComp/reactEcharts";
15+
import * as echarts from "echarts";
16+
import {
17+
childrenToProps,
18+
depsConfig,
19+
genRandomKey,
20+
NameConfig,
21+
UICompBuilder,
22+
withDefault,
23+
withExposingConfigs,
24+
withViewFn,
25+
ThemeContext,
26+
chartColorPalette,
27+
getPromiseAfterDispatch,
28+
dropdownControl,
29+
} from "lowcoder-sdk";
30+
import { getEchartsLocale, i18nObjs, trans } from "i18n/comps";
31+
import {
32+
echartsConfigOmitChildren,
33+
getEchartsConfig,
34+
getSelectedPoints,
35+
} from "./boxplotChartUtils";
36+
import 'echarts-extension-gmap';
37+
import log from "loglevel";
38+
39+
let clickEventCallback = () => {};
40+
41+
const chartModeOptions = [
42+
{
43+
label: "UI",
44+
value: "ui",
45+
}
46+
] as const;
47+
48+
let BoxplotChartTmpComp = (function () {
49+
return new UICompBuilder({mode:dropdownControl(chartModeOptions,'ui'),...boxplotChartChildrenMap}, () => null)
50+
.setPropertyViewFn(boxplotChartPropertyView)
51+
.build();
52+
})();
53+
54+
BoxplotChartTmpComp = withViewFn(BoxplotChartTmpComp, (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 childrenProps = childrenToProps(echartsConfigChildren);
145+
146+
const option = useMemo(() => {
147+
return getEchartsConfig(
148+
childrenProps as ToViewReturn<typeof echartsConfigChildren>,
149+
chartSize,
150+
themeConfig
151+
);
152+
}, [theme, childrenProps, chartSize, ...Object.values(echartsConfigChildren)]);
153+
154+
return (
155+
<ReactResizeDetector
156+
onResize={(w, h) => {
157+
if (w && h) {
158+
setChartSize({ w: w, h: h });
159+
}
160+
if (!firstResize.current) {
161+
// ignore the first resize, which will impact the loading animation
162+
echartsCompRef.current?.getEchartsInstance().resize();
163+
} else {
164+
firstResize.current = false;
165+
}
166+
}}
167+
>
168+
<ReactECharts
169+
ref={(e) => (echartsCompRef.current = e)}
170+
style={{ height: "100%" }}
171+
notMerge
172+
lazyUpdate
173+
opts={{ locale: getEchartsLocale() }}
174+
option={option}
175+
mode={mode}
176+
/>
177+
</ReactResizeDetector>
178+
);
179+
});
180+
181+
function getYAxisFormatContextValue(
182+
data: Array<JSONObject>,
183+
yAxisType: EchartsAxisType,
184+
yAxisName?: string
185+
) {
186+
const dataSample = yAxisName && data.length > 0 && data[0][yAxisName];
187+
let contextValue = dataSample;
188+
if (yAxisType === "time") {
189+
// to timestamp
190+
const time =
191+
typeof dataSample === "number" || typeof dataSample === "string"
192+
? new Date(dataSample).getTime()
193+
: null;
194+
if (time) contextValue = time;
195+
}
196+
return contextValue;
197+
}
198+
199+
BoxplotChartTmpComp = class extends BoxplotChartTmpComp {
200+
private lastYAxisFormatContextVal?: JSONValue;
201+
private lastColorContext?: JSONObject;
202+
203+
updateContext(comp: this) {
204+
// the context value of axis format
205+
let resultComp = comp;
206+
const data = comp.children.data.getView();
207+
const yAxisContextValue = getYAxisFormatContextValue(
208+
data,
209+
comp.children.yConfig.children.yAxisType.getView(),
210+
);
211+
if (yAxisContextValue !== comp.lastYAxisFormatContextVal) {
212+
comp.lastYAxisFormatContextVal = yAxisContextValue;
213+
resultComp = comp.setChild(
214+
"yConfig",
215+
comp.children.yConfig.reduce(
216+
wrapChildAction(
217+
"formatter",
218+
AxisFormatterComp.changeContextDataAction({ value: yAxisContextValue })
219+
)
220+
)
221+
);
222+
}
223+
return resultComp;
224+
}
225+
226+
override reduce(action: CompAction): this {
227+
const comp = super.reduce(action);
228+
if (action.type === CompActionTypes.UPDATE_NODES_V2) {
229+
const newData = comp.children.data.getView();
230+
// data changes
231+
if (comp.children.data !== this.children.data) {
232+
setTimeout(() => {
233+
// update x-axis value
234+
const keys = getDataKeys(newData);
235+
if (keys.length > 0 && !keys.includes(comp.children.xAxisKey.getView())) {
236+
comp.children.xAxisKey.dispatch(changeValueAction(keys[0] || ""));
237+
}
238+
if (keys.length > 0 && !keys.includes(comp.children.yAxisKey.getView())) {
239+
comp.children.yAxisKey.dispatch(changeValueAction(keys[1] || ""));
240+
}
241+
}, 0);
242+
}
243+
return this.updateContext(comp);
244+
}
245+
return comp;
246+
}
247+
248+
override autoHeight(): boolean {
249+
return false;
250+
}
251+
};
252+
253+
let BoxplotChartComp = withExposingConfigs(BoxplotChartTmpComp, [
254+
depsConfig({
255+
name: "selectedPoints",
256+
desc: trans("chart.selectedPointsDesc"),
257+
depKeys: ["selectedPoints"],
258+
func: (input) => {
259+
return input.selectedPoints;
260+
},
261+
}),
262+
depsConfig({
263+
name: "lastInteractionData",
264+
desc: trans("chart.lastInteractionDataDesc"),
265+
depKeys: ["lastInteractionData"],
266+
func: (input) => {
267+
return input.lastInteractionData;
268+
},
269+
}),
270+
depsConfig({
271+
name: "data",
272+
desc: trans("chart.dataDesc"),
273+
depKeys: ["data", "mode"],
274+
func: (input) =>[] ,
275+
}),
276+
new NameConfig("title", trans("chart.titleDesc")),
277+
]);
278+
279+
280+
export const BoxplotChartCompWithDefault = withDefault(BoxplotChartComp, {
281+
xAxisKey: "date",
282+
});

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /