Skip to content

[WIP]: Implemented Echart variations #1539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 44 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
62be602
waterfall, simplebar chart done
Imiss-U1025 Feb 6, 2025
52bfbbb
waterfall done
Imiss-U1025 Feb 6, 2025
82e3b47
polar chart
Imiss-U1025 Feb 6, 2025
ed7d742
add barchart comp package info
Imiss-U1025 Feb 7, 2025
ec054bb
tangent chart
Imiss-U1025 Feb 10, 2025
5da87e0
Polar end angle
Imiss-U1025 Feb 10, 2025
0580fe4
race chart
Imiss-U1025 Feb 10, 2025
d92c816
Fixed stack bar error
Imiss-U1025 Feb 10, 2025
7f6ba4b
default axis label
Imiss-U1025 Feb 10, 2025
fcf074e
hide legend in waterfall chart
Imiss-U1025 Feb 10, 2025
bfec22d
linechart
Imiss-U1025 Feb 10, 2025
fad5306
Mark Line
Imiss-U1025 Feb 12, 2025
20963c7
Added mark areas to config
Imiss-U1025 Feb 12, 2025
1627cd7
mark area label position when orientation changes
Imiss-U1025 Feb 12, 2025
aad0007
area pieces
Imiss-U1025 Feb 13, 2025
38bdda7
check for area pieces length
Imiss-U1025 Feb 13, 2025
a60dad0
end label
Imiss-U1025 Feb 13, 2025
cece2a4
animation duration & symbol
Imiss-U1025 Feb 13, 2025
48c10a0
step line
Imiss-U1025 Feb 14, 2025
407deca
polar line chart
Imiss-U1025 Feb 14, 2025
dca89d2
itemStyle
Imiss-U1025 Feb 16, 2025
be9ed2f
3 Initial Pie Chart
Imiss-U1025 Feb 16, 2025
8d2427a
show legends
Imiss-U1025 Feb 17, 2025
d7ab34f
start/end angle
Imiss-U1025 Feb 17, 2025
af0b599
Rose Type
Imiss-U1025 Feb 17, 2025
bedbbc1
label style
Imiss-U1025 Feb 17, 2025
7644006
label line property
Imiss-U1025 Feb 17, 2025
63fa7f0
show calendar labels
Imiss-U1025 Feb 19, 2025
baf77bb
item style
Imiss-U1025 Feb 19, 2025
71c01ba
pie bg image
Imiss-U1025 Feb 20, 2025
d41fc20
geoMap finished
Imiss-U1025 Feb 20, 2025
c6324b4
scatter chart simple
Imiss-U1025 Feb 20, 2025
703a322
mark line
Imiss-U1025 Feb 21, 2025
aabc0ef
singleAxis chart
Imiss-U1025 Feb 24, 2025
9e1f789
align label
Imiss-U1025 Feb 24, 2025
611eb60
punch card
Imiss-U1025 Feb 24, 2025
b31ed15
visual Map
Imiss-U1025 Feb 24, 2025
916af14
Merge branch 'dev' into feature/echarts
FalkWolsky Feb 24, 2025
4aa934b
Polar chart
Imiss-U1025 Feb 24, 2025
6aeba33
Merge branch 'feature/echarts' of https://github.com/lowcoder-org/low…
Imiss-U1025 Feb 24, 2025
048b326
heatmap
Imiss-U1025 Feb 24, 2025
5d8ebe8
Merge branch 'dev' into feature/echarts
FalkWolsky Feb 24, 2025
6b519d5
Fixing BusinessEventPublisher
Feb 24, 2025
437ea26
Fixing BusinessEventPublisher
Feb 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions client/packages/lowcoder-comps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,38 @@
"h": 40
}
},
"barChart": {
"name": "Bar Chart",
"icon": "./icons/icon-chart.svg",
"layoutInfo": {
"w": 12,
"h": 40
}
},
"lineChart": {
"name": "Line Chart",
"icon": "./icons/icon-chart.svg",
"layoutInfo": {
"w": 12,
"h": 40
}
},
"pieChart": {
"name": "Pie Chart",
"icon": "./icons/icon-chart.svg",
"layoutInfo": {
"w": 12,
"h": 40
}
},
"scatterChart": {
"name": "Scatter Chart",
"icon": "./icons/icon-chart.svg",
"layoutInfo": {
"w": 12,
"h": 40
}
},
"imageEditor": {
"name": "Image Editor",
"icon": "./icons/icon-chart.svg",
Expand Down
320 changes: 320 additions & 0 deletions client/packages/lowcoder-comps/src/comps/barChartComp/barChartComp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
import {
changeChildAction,
changeValueAction,
CompAction,
CompActionTypes,
wrapChildAction,
} from "lowcoder-core";
import { AxisFormatterComp, EchartsAxisType } from "../basicChartComp/chartConfigs/cartesianAxisConfig";
import { barChartChildrenMap, ChartSize, getDataKeys } from "./barChartConstants";
import { barChartPropertyView } from "./barChartPropertyView";
import _ from "lodash";
import { useContext, useEffect, useMemo, useRef, useState } from "react";
import ReactResizeDetector from "react-resize-detector";
import ReactECharts from "../basicChartComp/reactEcharts";
import {
childrenToProps,
depsConfig,
genRandomKey,
NameConfig,
UICompBuilder,
withDefault,
withExposingConfigs,
withViewFn,
ThemeContext,
chartColorPalette,
getPromiseAfterDispatch,
dropdownControl,
JSONObject,
} from "lowcoder-sdk";
import { getEchartsLocale, trans } from "i18n/comps";
import { ItemColorComp } from "comps/basicChartComp/chartConfigs/lineChartConfig";
import {
echartsConfigOmitChildren,
getEchartsConfig,
getSelectedPoints,
} from "./barChartUtils";
import 'echarts-extension-gmap';
import log from "loglevel";

let clickEventCallback = () => {};

const chartModeOptions = [
{
label: "ECharts JSON",
value: "json",
}
] as const;

let BarChartTmpComp = (function () {
return new UICompBuilder({mode:dropdownControl(chartModeOptions,'ui'),...barChartChildrenMap}, () => null)
.setPropertyViewFn(barChartPropertyView)
.build();
})();

BarChartTmpComp = withViewFn(BarChartTmpComp, (comp) => {
const mode = comp.children.mode.getView();
const onUIEvent = comp.children.onUIEvent.getView();
const onEvent = comp.children.onEvent.getView();
const echartsCompRef = useRef<ReactECharts | null>();
const [chartSize, setChartSize] = useState<ChartSize>();
const firstResize = useRef(true);
const theme = useContext(ThemeContext);
const defaultChartTheme = {
color: chartColorPalette,
backgroundColor: "#fff",
};

let themeConfig = defaultChartTheme;
try {
themeConfig = theme?.theme.chart ? JSON.parse(theme?.theme.chart) : defaultChartTheme;
} catch (error) {
log.error('theme chart error: ', error);
}

const triggerClickEvent = async (dispatch: any, action: CompAction<JSONValue>) => {
await getPromiseAfterDispatch(
dispatch,
action,
{ autoHandleAfterReduce: true }
);
onEvent('click');
}

useEffect(() => {
const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance();
if (!echartsCompInstance) {
return _.noop;
}
echartsCompInstance?.on("click", (param: any) => {
document.dispatchEvent(new CustomEvent("clickEvent", {
bubbles: true,
detail: {
action: 'click',
data: param.data,
}
}));
triggerClickEvent(
comp.dispatch,
changeChildAction("lastInteractionData", param.data, false)
);
});
return () => {
echartsCompInstance?.off("click");
document.removeEventListener('clickEvent', clickEventCallback)
};
}, []);

useEffect(() => {
// bind events
const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance();
if (!echartsCompInstance) {
return _.noop;
}
echartsCompInstance?.on("selectchanged", (param: any) => {
const option: any = echartsCompInstance?.getOption();
document.dispatchEvent(new CustomEvent("clickEvent", {
bubbles: true,
detail: {
action: param.fromAction,
data: getSelectedPoints(param, option)
}
}));

if (param.fromAction === "select") {
comp.dispatch(changeChildAction("selectedPoints", getSelectedPoints(param, option), false));
onUIEvent("select");
} else if (param.fromAction === "unselect") {
comp.dispatch(changeChildAction("selectedPoints", getSelectedPoints(param, option), false));
onUIEvent("unselect");
}

triggerClickEvent(
comp.dispatch,
changeChildAction("lastInteractionData", getSelectedPoints(param, option), false)
);
});
// unbind
return () => {
echartsCompInstance?.off("selectchanged");
document.removeEventListener('clickEvent', clickEventCallback)
};
}, [onUIEvent]);

const echartsConfigChildren = _.omit(comp.children, echartsConfigOmitChildren);
const childrenProps = childrenToProps(echartsConfigChildren);
const option = useMemo(() => {
return getEchartsConfig(
childrenProps as ToViewReturn<typeof echartsConfigChildren>,
chartSize,
themeConfig
);
}, [theme, childrenProps, chartSize, ...Object.values(echartsConfigChildren)]);

useEffect(() => {
comp.children.mapInstance.dispatch(changeValueAction(null, false))
if(comp.children.mapInstance.value) return;
}, [option])

return (
<ReactResizeDetector
onResize={(w, h) => {
if (w && h) {
setChartSize({ w: w, h: h });
}
if (!firstResize.current) {
// ignore the first resize, which will impact the loading animation
echartsCompRef.current?.getEchartsInstance().resize();
} else {
firstResize.current = false;
}
}}
>
<ReactECharts
ref={(e) => (echartsCompRef.current = e)}
style={{ height: "100%" }}
notMerge
lazyUpdate
opts={{ locale: getEchartsLocale() }}
option={option}
mode={mode}
/>
</ReactResizeDetector>
);
});

function getYAxisFormatContextValue(
data: Array<JSONObject>,
yAxisType: EchartsAxisType,
yAxisName?: string
) {
const dataSample = yAxisName && data.length > 0 && data[0][yAxisName];
let contextValue = dataSample;
if (yAxisType === "time") {
// to timestamp
const time =
typeof dataSample === "number" || typeof dataSample === "string"
? new Date(dataSample).getTime()
: null;
if (time) contextValue = time;
}
return contextValue;
}

BarChartTmpComp = class extends BarChartTmpComp {
private lastYAxisFormatContextVal?: JSONValue;
private lastColorContext?: JSONObject;

updateContext(comp: this) {
// the context value of axis format
let resultComp = comp;
const data = comp.children.data.getView();
const sampleSeries = comp.children.series.getView().find((s) => !s.getView().hide);
const yAxisContextValue = getYAxisFormatContextValue(
data,
comp.children.yConfig.children.yAxisType.getView(),
sampleSeries?.children.columnName.getView()
);
if (yAxisContextValue !== comp.lastYAxisFormatContextVal) {
comp.lastYAxisFormatContextVal = yAxisContextValue;
resultComp = comp.setChild(
"yConfig",
comp.children.yConfig.reduce(
wrapChildAction(
"formatter",
AxisFormatterComp.changeContextDataAction({ value: yAxisContextValue })
)
)
);
}
// item color context
const colorContextVal = {
seriesName: sampleSeries?.children.seriesName.getView(),
value: yAxisContextValue,
};
if (
comp.children.chartConfig.children.comp.children.hasOwnProperty("itemColor") &&
!_.isEqual(colorContextVal, comp.lastColorContext)
) {
comp.lastColorContext = colorContextVal;
resultComp = resultComp.setChild(
"chartConfig",
comp.children.chartConfig.reduce(
wrapChildAction(
"comp",
wrapChildAction("itemColor", ItemColorComp.changeContextDataAction(colorContextVal))
)
)
);
}
return resultComp;
}

override reduce(action: CompAction): this {
const comp = super.reduce(action);
if (action.type === CompActionTypes.UPDATE_NODES_V2) {
const newData = comp.children.data.getView();
// data changes
if (comp.children.data !== this.children.data) {
setTimeout(() => {
// update x-axis value
const keys = getDataKeys(newData);
if (keys.length > 0 && !keys.includes(comp.children.xAxisKey.getView())) {
comp.children.xAxisKey.dispatch(changeValueAction(keys[0] || ""));
}
// pass to child series comp
comp.children.series.dispatchDataChanged(newData);
}, 0);
}
return this.updateContext(comp);
}
return comp;
}

override autoHeight(): boolean {
return false;
}
};

let BarChartComp = withExposingConfigs(BarChartTmpComp, [
depsConfig({
name: "selectedPoints",
desc: trans("chart.selectedPointsDesc"),
depKeys: ["selectedPoints"],
func: (input) => {
return input.selectedPoints;
},
}),
depsConfig({
name: "lastInteractionData",
desc: trans("chart.lastInteractionDataDesc"),
depKeys: ["lastInteractionData"],
func: (input) => {
return input.lastInteractionData;
},
}),
depsConfig({
name: "data",
desc: trans("chart.dataDesc"),
depKeys: ["data", "mode"],
func: (input) =>[] ,
}),
new NameConfig("title", trans("chart.titleDesc")),
]);


export const BarChartCompWithDefault = withDefault(BarChartComp, {
xAxisKey: "date",
series: [
{
dataIndex: genRandomKey(),
seriesName: trans("chart.spending"),
columnName: "spending",
},
{
dataIndex: genRandomKey(),
seriesName: trans("chart.budget"),
columnName: "budget",
},
],
});
Loading
Loading