Skip to content

Commit dd58106

Browse files
authored
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy