From cd83ec31d67fa6a00fb9c33a19ac3def01679fe4 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Tue, 23 Apr 2024 22:10:46 +0500 Subject: [PATCH 001/118] map component --- .../src/comps/mapComp/mapComp.tsx | 450 ++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/mapComp/mapComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/mapComp/mapComp.tsx b/client/packages/lowcoder-comps/src/comps/mapComp/mapComp.tsx new file mode 100644 index 000000000..43c44bf34 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/mapComp/mapComp.tsx @@ -0,0 +1,450 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { chartChildrenMap, ChartSize, getDataKeys } from "../chartComp/chartConstants"; +import { chartPropertyView } from "../chartComp/chartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../chartComp/reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + JSONObject, + JSONValue, + NameConfig, + ToViewReturn, + UICompBuilder, + withDefault, + withExposingConfigs, + withMethodExposing, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, + loadGoogleMapsScript, +} from "comps/chartComp/chartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "Map", + value: "map", + } +] as const; + +let MapTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'map'),...chartChildrenMap}, () => null) + .setPropertyViewFn(chartPropertyView) + .build(); +})(); + +MapTmpComp = withViewFn(MapTmpComp, (comp) => { + const apiKey = comp.children.mapApiKey.getView(); + const mode = comp.children.mode.getView(); + const mapCenterPosition = { + lng: comp.children.mapCenterLng.getView(), + lat: comp.children.mapCenterLat.getView(), + } + const mapZoomlevel = comp.children.mapZoomLevel.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onMapEvent = comp.children.onMapEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + const [mapScriptLoaded, setMapScriptLoaded] = useState(false); + 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) => { + await getPromiseAfterDispatch( + dispatch, + action, + { autoHandleAfterReduce: true } + ); + onEvent('click'); + } + + useEffect(() => { + // click events for JSON/Map mode + if (mode === 'ui') return; + + 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) + }; + }, [mode, mapScriptLoaded]); + + useEffect(() => { + // click events for UI mode + if(mode !== 'ui') return; + + // bind events + const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); + if (!echartsCompInstance) { + return _.noop; + } + echartsCompInstance?.on("selectchanged", (param: any) => { + const option: any = echartsCompInstance?.getOption(); + //log.log("chart select change", param); + // trigger click event listener + + 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) + }; + }, [mode, onUIEvent]); + + const echartsConfigChildren = _.omit(comp.children, echartsConfigOmitChildren); + const option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + const isMapScriptLoaded = useMemo(() => { + return mapScriptLoaded || window?.google; + }, [mapScriptLoaded]) + + const loadGoogleMapData = () => { + const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); + if (!echartsCompInstance) { + return _.noop; + } + + comp.children.mapInstance.dispatch(changeValueAction(echartsCompInstance)) + onMapEvent('mapReady') + } + + const handleOnMapScriptLoad = () => { + setMapScriptLoaded(true); + setTimeout(() => { + loadGoogleMapData(); + }) + } + + useEffect(() => { + if( mode !== 'map') { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + return; + } + + if(comp.children.mapInstance.value) return; + + const gMapScript = loadGoogleMapsScript(apiKey); + if(isMapScriptLoaded) { + handleOnMapScriptLoad(); + return; + } + gMapScript.addEventListener('load', handleOnMapScriptLoad); + return () => { + gMapScript.removeEventListener('load', handleOnMapScriptLoad); + } + }, [mode, apiKey, option]) + + useEffect(() => { + if(mode !== 'map') return; + onMapEvent('centerPositionChange'); + }, [mode, mapCenterPosition.lat, mapCenterPosition.lng]) + + useEffect(() => { + if(mode !== 'map') return; + onMapEvent('zoomLevelChange'); + }, [mode, mapZoomlevel]) + + return ( + { + 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; + } + }} + > + {(mode !== 'map' || (mode === 'map' && isMapScriptLoaded)) && ( + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + )} + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +MapTmpComp = class extends MapTmpComp { + 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 MapComp = withExposingConfigs(MapTmpComp, [ + 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) => { + if (input.mode === "ui") { + return input.data; + } else { + // no data in json mode + return []; + } + }, + }), + new NameConfig("title", trans("chart.titleDesc")), +]); + +MapComp = withMethodExposing(MapComp, [ + { + method: { + name: "getMapInstance", + }, + execute: (comp) => { + return new Promise(resolve => { + let intervalCount = 0; + const mapInstanceInterval = setInterval(() => { + const instance = comp.children.mapInstance.getView(); + const mapInstance = instance?.getModel()?.getComponent("gmap")?.getGoogleMap() + if(mapInstance || intervalCount === 10) { + clearInterval(mapInstanceInterval) + resolve(mapInstance) + } + intervalCount++; + }, 1000); + }) + } + }, + { + method: { + name: "getMapZoomLevel", + }, + execute: (comp) => { + return comp.children.mapZoomLevel.getView(); + } + }, + { + method: { + name: "getMapCenterPosition", + }, + execute: (comp) => { + return Promise.resolve({ + lng: comp.children.mapCenterLng.getView(), + lat: comp.children.mapCenterLat.getView(), + }); + } + }, + { + method: { + name: "onClick", + params: [ + { + name: "callback", + type: "function", + }, + ], + }, + execute: (comp, params) => { + clickEventCallback = params[0]; + document.addEventListener('clickEvent', clickEventCallback); + } + }, +]) + +export const MapCompWithDefault = withDefault(MapComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); From 9d78a157e0308d457a860a4004c083bb74b89e9b Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Tue, 23 Apr 2024 22:10:58 +0500 Subject: [PATCH 002/118] echarts comp --- .../src/comps/eChartsComp/echartsComp.tsx | 450 ++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx b/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx new file mode 100644 index 000000000..c8099901e --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx @@ -0,0 +1,450 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { chartChildrenMap, ChartSize, getDataKeys } from "../chartComp/chartConstants"; +import { chartPropertyView } from "../chartComp/chartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../chartComp/reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + JSONObject, + JSONValue, + NameConfig, + ToViewReturn, + UICompBuilder, + withDefault, + withExposingConfigs, + withMethodExposing, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, + loadGoogleMapsScript, +} from "comps/chartComp/chartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let EChartsTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...chartChildrenMap}, () => null) + .setPropertyViewFn(chartPropertyView) + .build(); +})(); + +EChartsTmpComp = withViewFn(EChartsTmpComp, (comp) => { + const apiKey = comp.children.mapApiKey.getView(); + const mode = comp.children.mode.getView(); + const mapCenterPosition = { + lng: comp.children.mapCenterLng.getView(), + lat: comp.children.mapCenterLat.getView(), + } + const mapZoomlevel = comp.children.mapZoomLevel.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onMapEvent = comp.children.onMapEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + const [mapScriptLoaded, setMapScriptLoaded] = useState(false); + 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) => { + await getPromiseAfterDispatch( + dispatch, + action, + { autoHandleAfterReduce: true } + ); + onEvent('click'); + } + + useEffect(() => { + // click events for JSON/Map mode + if (mode === 'ui') return; + + 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) + }; + }, [mode, mapScriptLoaded]); + + useEffect(() => { + // click events for UI mode + if(mode !== 'ui') return; + + // bind events + const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); + if (!echartsCompInstance) { + return _.noop; + } + echartsCompInstance?.on("selectchanged", (param: any) => { + const option: any = echartsCompInstance?.getOption(); + //log.log("chart select change", param); + // trigger click event listener + + 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) + }; + }, [mode, onUIEvent]); + + const echartsConfigChildren = _.omit(comp.children, echartsConfigOmitChildren); + const option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + const isMapScriptLoaded = useMemo(() => { + return mapScriptLoaded || window?.google; + }, [mapScriptLoaded]) + + const loadGoogleMapData = () => { + const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); + if (!echartsCompInstance) { + return _.noop; + } + + comp.children.mapInstance.dispatch(changeValueAction(echartsCompInstance)) + onMapEvent('mapReady') + } + + const handleOnMapScriptLoad = () => { + setMapScriptLoaded(true); + setTimeout(() => { + loadGoogleMapData(); + }) + } + + useEffect(() => { + if( mode !== 'map') { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + return; + } + + if(comp.children.mapInstance.value) return; + + const gMapScript = loadGoogleMapsScript(apiKey); + if(isMapScriptLoaded) { + handleOnMapScriptLoad(); + return; + } + gMapScript.addEventListener('load', handleOnMapScriptLoad); + return () => { + gMapScript.removeEventListener('load', handleOnMapScriptLoad); + } + }, [mode, apiKey, option]) + + useEffect(() => { + if(mode !== 'map') return; + onMapEvent('centerPositionChange'); + }, [mode, mapCenterPosition.lat, mapCenterPosition.lng]) + + useEffect(() => { + if(mode !== 'map') return; + onMapEvent('zoomLevelChange'); + }, [mode, mapZoomlevel]) + + return ( + { + 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; + } + }} + > + {(mode !== 'map' || (mode === 'map' && isMapScriptLoaded)) && ( + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + )} + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +EChartsTmpComp = class extends EChartsTmpComp { + 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 EChartsComp = withExposingConfigs(EChartsTmpComp, [ + 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) => { + if (input.mode === "ui") { + return input.data; + } else { + // no data in json mode + return []; + } + }, + }), + new NameConfig("title", trans("chart.titleDesc")), +]); + +EChartsComp = withMethodExposing(EChartsComp, [ + { + method: { + name: "getMapInstance", + }, + execute: (comp) => { + return new Promise(resolve => { + let intervalCount = 0; + const mapInstanceInterval = setInterval(() => { + const instance = comp.children.mapInstance.getView(); + const mapInstance = instance?.getModel()?.getComponent("gmap")?.getGoogleMap() + if(mapInstance || intervalCount === 10) { + clearInterval(mapInstanceInterval) + resolve(mapInstance) + } + intervalCount++; + }, 1000); + }) + } + }, + { + method: { + name: "getMapZoomLevel", + }, + execute: (comp) => { + return comp.children.mapZoomLevel.getView(); + } + }, + { + method: { + name: "getMapCenterPosition", + }, + execute: (comp) => { + return Promise.resolve({ + lng: comp.children.mapCenterLng.getView(), + lat: comp.children.mapCenterLat.getView(), + }); + } + }, + { + method: { + name: "onClick", + params: [ + { + name: "callback", + type: "function", + }, + ], + }, + execute: (comp, params) => { + clickEventCallback = params[0]; + document.addEventListener('clickEvent', clickEventCallback); + } + }, +]) + +export const EChartsCompWithDefault = withDefault(EChartsComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); From 87546be76016708b186efc0e266f7862ab33450b Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Tue, 23 Apr 2024 22:11:12 +0500 Subject: [PATCH 003/118] charts constants updated --- .../src/comps/chartComp/chartConstants.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx index 4242a5090..431c5d2ce 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx @@ -62,6 +62,20 @@ const chartModeOptions = [ }, ] as const; +const mapModeOptions = [ + { + label: "Map", + value: "map", + }, +] as const; + +const eChartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + }, +] as const; + export const UIEventOptions = [ { label: trans("chart.select"), @@ -285,7 +299,6 @@ export type NonUIChartDataType = { } export const chartChildrenMap = { - mode: dropdownControl(chartModeOptions, "ui"), selectedPoints: stateComp>([]), lastInteractionData: stateComp | NonUIChartDataType>({}), onEvent: eventHandlerControl([clickEvent] as const), From 59247a9a4bf42485ce4e5d251393d00c47be911f Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Tue, 23 Apr 2024 22:11:24 +0500 Subject: [PATCH 004/118] chart comp --- .../lowcoder-comps/src/comps/chartComp/chartComp.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx index 53873b2b5..9afd69833 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx @@ -28,6 +28,7 @@ import { ThemeContext, chartColorPalette, getPromiseAfterDispatch, + dropdownControl } from "lowcoder-sdk"; import { getEchartsLocale, trans } from "i18n/comps"; import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; @@ -42,8 +43,15 @@ import log from "loglevel"; let clickEventCallback = () => {}; +const chartModeOptions = [ + { + label: trans("chart.UIMode"), + value: "ui", + } +] as const; + let ChartTmpComp = (function () { - return new UICompBuilder(chartChildrenMap, () => null) + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'ui'),...chartChildrenMap}, () => null) .setPropertyViewFn(chartPropertyView) .build(); })(); From f7e3a074d4d84ac67a67fc1c68687e0852945dd4 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Tue, 23 Apr 2024 22:11:45 +0500 Subject: [PATCH 005/118] echarts and map integrated --- client/packages/lowcoder-comps/package.json | 16 ++++++++++++++++ client/packages/lowcoder-comps/src/index.ts | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index c77e3beb1..632eb77c8 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -63,6 +63,22 @@ "w": 15, "h": 40 } + }, + "eCharts": { + "name": "ECharts", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, + "map": { + "name": "Map", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } } } }, diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 065393b52..76de0ea94 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -2,9 +2,13 @@ import { ChartCompWithDefault } from "./comps/chartComp/chartComp"; import { ImageEditorComp } from "./comps/imageEditorComp/index"; import { CalendarComp } from "./comps/calendarComp/calendarComp"; import { MermaidComp } from "comps/mermaidComp"; +import { EChartsCompWithDefault } from "comps/eChartsComp/echartsComp"; +import { MapCompWithDefault } from "comps/mapComp/mapComp"; export default { chart: ChartCompWithDefault, + map: MapCompWithDefault, + eCharts: EChartsCompWithDefault, imageEditor: ImageEditorComp, calendar: CalendarComp, mermaid: MermaidComp, From eb882b039d1ec72e5fd55638134dda9b304adcec Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 26 Apr 2024 17:32:18 +0500 Subject: [PATCH 006/118] removed unused code --- .../src/comps/chartComp/chartComp.tsx | 167 +----------------- .../src/comps/chartComp/chartPropertyView.tsx | 4 +- .../src/comps/eChartsComp/echartsComp.tsx | 4 +- .../src/comps/mapComp/mapComp.tsx | 78 +------- 4 files changed, 16 insertions(+), 237 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx index 9afd69833..8b56ec0c1 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartComp.tsx @@ -16,10 +16,7 @@ import { childrenToProps, depsConfig, genRandomKey, - JSONObject, - JSONValue, NameConfig, - ToViewReturn, UICompBuilder, withDefault, withExposingConfigs, @@ -36,7 +33,6 @@ import { echartsConfigOmitChildren, getEchartsConfig, getSelectedPoints, - loadGoogleMapsScript, } from "comps/chartComp/chartUtils"; import 'echarts-extension-gmap'; import log from "loglevel"; @@ -57,20 +53,12 @@ let ChartTmpComp = (function () { })(); ChartTmpComp = withViewFn(ChartTmpComp, (comp) => { - const apiKey = comp.children.mapApiKey.getView(); const mode = comp.children.mode.getView(); - const mapCenterPosition = { - lng: comp.children.mapCenterLng.getView(), - lat: comp.children.mapCenterLat.getView(), - } - const mapZoomlevel = comp.children.mapZoomLevel.getView(); const onUIEvent = comp.children.onUIEvent.getView(); - const onMapEvent = comp.children.onMapEvent.getView(); const onEvent = comp.children.onEvent.getView(); const echartsCompRef = useRef(); const [chartSize, setChartSize] = useState(); - const [mapScriptLoaded, setMapScriptLoaded] = useState(false); const firstResize = useRef(true); const theme = useContext(ThemeContext); const defaultChartTheme = { @@ -95,36 +83,6 @@ ChartTmpComp = withViewFn(ChartTmpComp, (comp) => { } useEffect(() => { - // click events for JSON/Map mode - if (mode === 'ui') return; - - 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) - }; - }, [mode, mapScriptLoaded]); - - useEffect(() => { - // click events for UI mode - if(mode !== 'ui') return; - // bind events const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); if (!echartsCompInstance) { @@ -132,8 +90,6 @@ ChartTmpComp = withViewFn(ChartTmpComp, (comp) => { } echartsCompInstance?.on("selectchanged", (param: any) => { const option: any = echartsCompInstance?.getOption(); - //log.log("chart select change", param); - // trigger click event listener document.dispatchEvent(new CustomEvent("clickEvent", { bubbles: true, @@ -161,7 +117,7 @@ ChartTmpComp = withViewFn(ChartTmpComp, (comp) => { echartsCompInstance?.off("selectchanged"); document.removeEventListener('clickEvent', clickEventCallback) }; - }, [mode, onUIEvent]); + }, [onUIEvent]); const echartsConfigChildren = _.omit(comp.children, echartsConfigOmitChildren); const option = useMemo(() => { @@ -171,55 +127,9 @@ ChartTmpComp = withViewFn(ChartTmpComp, (comp) => { ); }, [chartSize, ...Object.values(echartsConfigChildren)]); - const isMapScriptLoaded = useMemo(() => { - return mapScriptLoaded || window?.google; - }, [mapScriptLoaded]) - - const loadGoogleMapData = () => { - const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); - if (!echartsCompInstance) { - return _.noop; - } - - comp.children.mapInstance.dispatch(changeValueAction(echartsCompInstance)) - onMapEvent('mapReady') - } - - const handleOnMapScriptLoad = () => { - setMapScriptLoaded(true); - setTimeout(() => { - loadGoogleMapData(); - }) - } - - useEffect(() => { - if( mode !== 'map') { - comp.children.mapInstance.dispatch(changeValueAction(null, false)) - return; - } - - if(comp.children.mapInstance.value) return; - - const gMapScript = loadGoogleMapsScript(apiKey); - if(isMapScriptLoaded) { - handleOnMapScriptLoad(); - return; - } - gMapScript.addEventListener('load', handleOnMapScriptLoad); - return () => { - gMapScript.removeEventListener('load', handleOnMapScriptLoad); - } - }, [mode, apiKey, option]) - - useEffect(() => { - if(mode !== 'map') return; - onMapEvent('centerPositionChange'); - }, [mode, mapCenterPosition.lat, mapCenterPosition.lng]) - useEffect(() => { - if(mode !== 'map') return; - onMapEvent('zoomLevelChange'); - }, [mode, mapZoomlevel]) + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + }, [option]) return ( { } }} > - {(mode !== 'map' || (mode === 'map' && isMapScriptLoaded)) && ( - (echartsCompRef.current = e)} style={{ height: "100%" }} notMerge lazyUpdate opts={{ locale: getEchartsLocale() }} option={option} - theme={mode !== 'map' ? themeConfig : undefined} + theme={themeConfig} mode={mode} /> - )} ); }); @@ -365,74 +273,11 @@ let ChartComp = withExposingConfigs(ChartTmpComp, [ name: "data", desc: trans("chart.dataDesc"), depKeys: ["data", "mode"], - func: (input) => { - if (input.mode === "ui") { - return input.data; - } else { - // no data in json mode - return []; - } - }, + func: (input) => input.data, }), new NameConfig("title", trans("chart.titleDesc")), ]); -ChartComp = withMethodExposing(ChartComp, [ - { - method: { - name: "getMapInstance", - }, - execute: (comp) => { - return new Promise(resolve => { - let intervalCount = 0; - const mapInstanceInterval = setInterval(() => { - const instance = comp.children.mapInstance.getView(); - const mapInstance = instance?.getModel()?.getComponent("gmap")?.getGoogleMap() - if(mapInstance || intervalCount === 10) { - clearInterval(mapInstanceInterval) - resolve(mapInstance) - } - intervalCount++; - }, 1000); - }) - } - }, - { - method: { - name: "getMapZoomLevel", - }, - execute: (comp) => { - return comp.children.mapZoomLevel.getView(); - } - }, - { - method: { - name: "getMapCenterPosition", - }, - execute: (comp) => { - return Promise.resolve({ - lng: comp.children.mapCenterLng.getView(), - lat: comp.children.mapCenterLat.getView(), - }); - } - }, - { - method: { - name: "onClick", - params: [ - { - name: "callback", - type: "function", - }, - ], - }, - execute: (comp, params) => { - clickEventCallback = params[0]; - document.addEventListener('clickEvent', clickEventCallback); - } - }, -]) - export const ChartCompWithDefault = withDefault(ChartComp, { xAxisKey: "date", series: [ diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx index 3b183d235..acecdd902 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx @@ -223,12 +223,12 @@ export function chartPropertyView( } return ( <> -
+ {/*
{children.mode.propertyView({ label: "", radioButton: true, })} -
+
*/} {getChatConfigByMode(children.mode.getView())} ); diff --git a/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx b/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx index c8099901e..dbf01aeeb 100644 --- a/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx @@ -235,8 +235,7 @@ EChartsTmpComp = withViewFn(EChartsTmpComp, (comp) => { } }} > - {(mode !== 'map' || (mode === 'map' && isMapScriptLoaded)) && ( - (echartsCompRef.current = e)} style={{ height: "100%" }} notMerge @@ -246,7 +245,6 @@ EChartsTmpComp = withViewFn(EChartsTmpComp, (comp) => { theme={mode !== 'map' ? themeConfig : undefined} mode={mode} /> - )} ); }); diff --git a/client/packages/lowcoder-comps/src/comps/mapComp/mapComp.tsx b/client/packages/lowcoder-comps/src/comps/mapComp/mapComp.tsx index 43c44bf34..5c0e2769b 100644 --- a/client/packages/lowcoder-comps/src/comps/mapComp/mapComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/mapComp/mapComp.tsx @@ -16,10 +16,7 @@ import { childrenToProps, depsConfig, genRandomKey, - JSONObject, - JSONValue, NameConfig, - ToViewReturn, UICompBuilder, withDefault, withExposingConfigs, @@ -95,9 +92,6 @@ MapTmpComp = withViewFn(MapTmpComp, (comp) => { } useEffect(() => { - // click events for JSON/Map mode - if (mode === 'ui') return; - const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); if (!echartsCompInstance) { return _.noop; @@ -119,49 +113,7 @@ MapTmpComp = withViewFn(MapTmpComp, (comp) => { echartsCompInstance?.off("click"); document.removeEventListener('clickEvent', clickEventCallback) }; - }, [mode, mapScriptLoaded]); - - useEffect(() => { - // click events for UI mode - if(mode !== 'ui') return; - - // bind events - const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); - if (!echartsCompInstance) { - return _.noop; - } - echartsCompInstance?.on("selectchanged", (param: any) => { - const option: any = echartsCompInstance?.getOption(); - //log.log("chart select change", param); - // trigger click event listener - - 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) - }; - }, [mode, onUIEvent]); + }, [mapScriptLoaded]); const echartsConfigChildren = _.omit(comp.children, echartsConfigOmitChildren); const option = useMemo(() => { @@ -193,11 +145,6 @@ MapTmpComp = withViewFn(MapTmpComp, (comp) => { } useEffect(() => { - if( mode !== 'map') { - comp.children.mapInstance.dispatch(changeValueAction(null, false)) - return; - } - if(comp.children.mapInstance.value) return; const gMapScript = loadGoogleMapsScript(apiKey); @@ -209,17 +156,15 @@ MapTmpComp = withViewFn(MapTmpComp, (comp) => { return () => { gMapScript.removeEventListener('load', handleOnMapScriptLoad); } - }, [mode, apiKey, option]) + }, [apiKey, option]) useEffect(() => { - if(mode !== 'map') return; onMapEvent('centerPositionChange'); - }, [mode, mapCenterPosition.lat, mapCenterPosition.lng]) + }, [mapCenterPosition.lat, mapCenterPosition.lng]) useEffect(() => { - if(mode !== 'map') return; onMapEvent('zoomLevelChange'); - }, [mode, mapZoomlevel]) + }, [mapZoomlevel]) return ( { } }} > - {(mode !== 'map' || (mode === 'map' && isMapScriptLoaded)) && ( - (echartsCompRef.current = e)} style={{ height: "100%" }} notMerge lazyUpdate opts={{ locale: getEchartsLocale() }} option={option} - theme={mode !== 'map' ? themeConfig : undefined} + theme={undefined} mode={mode} /> - )} ); }); @@ -365,14 +308,7 @@ let MapComp = withExposingConfigs(MapTmpComp, [ name: "data", desc: trans("chart.dataDesc"), depKeys: ["data", "mode"], - func: (input) => { - if (input.mode === "ui") { - return input.data; - } else { - // no data in json mode - return []; - } - }, + func: (input) =>[], }), new NameConfig("title", trans("chart.titleDesc")), ]); From f88e8a739d83d35d99488e5e100a6f929e23b3ed Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 26 Apr 2024 17:40:47 +0500 Subject: [PATCH 007/118] echarts removed unused code --- .../src/comps/eChartsComp/echartsComp.tsx | 117 +----------------- 1 file changed, 4 insertions(+), 113 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx b/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx index dbf01aeeb..e6d932b22 100644 --- a/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx @@ -63,14 +63,11 @@ EChartsTmpComp = withViewFn(EChartsTmpComp, (comp) => { lng: comp.children.mapCenterLng.getView(), lat: comp.children.mapCenterLat.getView(), } - const mapZoomlevel = comp.children.mapZoomLevel.getView(); const onUIEvent = comp.children.onUIEvent.getView(); - const onMapEvent = comp.children.onMapEvent.getView(); const onEvent = comp.children.onEvent.getView(); const echartsCompRef = useRef(); const [chartSize, setChartSize] = useState(); - const [mapScriptLoaded, setMapScriptLoaded] = useState(false); const firstResize = useRef(true); const theme = useContext(ThemeContext); const defaultChartTheme = { @@ -95,9 +92,6 @@ EChartsTmpComp = withViewFn(EChartsTmpComp, (comp) => { } useEffect(() => { - // click events for JSON/Map mode - if (mode === 'ui') return; - const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); if (!echartsCompInstance) { return _.noop; @@ -119,12 +113,9 @@ EChartsTmpComp = withViewFn(EChartsTmpComp, (comp) => { echartsCompInstance?.off("click"); document.removeEventListener('clickEvent', clickEventCallback) }; - }, [mode, mapScriptLoaded]); + }, []); useEffect(() => { - // click events for UI mode - if(mode !== 'ui') return; - // bind events const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); if (!echartsCompInstance) { @@ -161,7 +152,7 @@ EChartsTmpComp = withViewFn(EChartsTmpComp, (comp) => { echartsCompInstance?.off("selectchanged"); document.removeEventListener('clickEvent', clickEventCallback) }; - }, [mode, onUIEvent]); + }, [onUIEvent]); const echartsConfigChildren = _.omit(comp.children, echartsConfigOmitChildren); const option = useMemo(() => { @@ -171,55 +162,10 @@ EChartsTmpComp = withViewFn(EChartsTmpComp, (comp) => { ); }, [chartSize, ...Object.values(echartsConfigChildren)]); - const isMapScriptLoaded = useMemo(() => { - return mapScriptLoaded || window?.google; - }, [mapScriptLoaded]) - - const loadGoogleMapData = () => { - const echartsCompInstance = echartsCompRef?.current?.getEchartsInstance(); - if (!echartsCompInstance) { - return _.noop; - } - - comp.children.mapInstance.dispatch(changeValueAction(echartsCompInstance)) - onMapEvent('mapReady') - } - - const handleOnMapScriptLoad = () => { - setMapScriptLoaded(true); - setTimeout(() => { - loadGoogleMapData(); - }) - } - useEffect(() => { - if( mode !== 'map') { - comp.children.mapInstance.dispatch(changeValueAction(null, false)) - return; - } - + comp.children.mapInstance.dispatch(changeValueAction(null, false)) if(comp.children.mapInstance.value) return; - - const gMapScript = loadGoogleMapsScript(apiKey); - if(isMapScriptLoaded) { - handleOnMapScriptLoad(); - return; - } - gMapScript.addEventListener('load', handleOnMapScriptLoad); - return () => { - gMapScript.removeEventListener('load', handleOnMapScriptLoad); - } - }, [mode, apiKey, option]) - - useEffect(() => { - if(mode !== 'map') return; - onMapEvent('centerPositionChange'); - }, [mode, mapCenterPosition.lat, mapCenterPosition.lng]) - - useEffect(() => { - if(mode !== 'map') return; - onMapEvent('zoomLevelChange'); - }, [mode, mapZoomlevel]) + }, [option]) return ( { - return new Promise(resolve => { - let intervalCount = 0; - const mapInstanceInterval = setInterval(() => { - const instance = comp.children.mapInstance.getView(); - const mapInstance = instance?.getModel()?.getComponent("gmap")?.getGoogleMap() - if(mapInstance || intervalCount === 10) { - clearInterval(mapInstanceInterval) - resolve(mapInstance) - } - intervalCount++; - }, 1000); - }) - } - }, - { - method: { - name: "getMapZoomLevel", - }, - execute: (comp) => { - return comp.children.mapZoomLevel.getView(); - } - }, - { - method: { - name: "getMapCenterPosition", - }, - execute: (comp) => { - return Promise.resolve({ - lng: comp.children.mapCenterLng.getView(), - lat: comp.children.mapCenterLat.getView(), - }); - } - }, - { - method: { - name: "onClick", - params: [ - { - name: "callback", - type: "function", - }, - ], - }, - execute: (comp, params) => { - clickEventCallback = params[0]; - document.addEventListener('clickEvent', clickEventCallback); - } - }, -]) export const EChartsCompWithDefault = withDefault(EChartsComp, { xAxisKey: "date", From c113ea6b0deccc0dc5147cbe97505ef8239405f9 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 26 Apr 2024 17:41:38 +0500 Subject: [PATCH 008/118] echarts removed unused code -1 --- .../lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx b/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx index e6d932b22..5e1852d95 100644 --- a/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx @@ -309,14 +309,7 @@ let EChartsComp = withExposingConfigs(EChartsTmpComp, [ name: "data", desc: trans("chart.dataDesc"), depKeys: ["data", "mode"], - func: (input) => { - if (input.mode === "ui") { - return input.data; - } else { - // no data in json mode - return []; - } - }, + func: (input) =>[] , }), new NameConfig("title", trans("chart.titleDesc")), ]); From b93d0cb3305508d612df0b2d2bd2f38899344c79 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Wed, 1 May 2024 19:39:08 +0500 Subject: [PATCH 009/118] echart styles --- .../src/comps/controls/styleControlConstants.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx index d9876fa55..841108265 100644 --- a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx +++ b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx @@ -1314,6 +1314,15 @@ export const DrawerStyle = [getBackground()] as const export const JsonEditorStyle = [LABEL] as const; +export const EchartsStyle = [ + getBackground("primarySurface"), + { + name: "color", + label: trans("color"), + color: "#4C64D9", + }, +] as const; + export const CalendarStyle = [ getBackground("primarySurface"), { From 4d27c67977ba5a2aab50439a764861f8e885dbc6 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Wed, 1 May 2024 19:39:36 +0500 Subject: [PATCH 010/118] funnel chart config --- .../chartConfigs/funnelChartConfig.tsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/funnelChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/funnelChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/funnelChartConfig.tsx new file mode 100644 index 000000000..e5ac00c23 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/funnelChartConfig.tsx @@ -0,0 +1,35 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { FunnelSeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const FunnelChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): FunnelSeriesOption => { + const config: FunnelSeriesOption = { + type: "funnel", + label: { + show: props.showLabel, + position: "top", + }, + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("echarts.funnelType"), + radioButton: true, + })} + + )) + .build(); +})(); From 0c73194d089f16174481d5f65140304d4feea5fb Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Wed, 1 May 2024 19:39:47 +0500 Subject: [PATCH 011/118] gauge chart config --- .../chartConfigs/gaugeChartConfig.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/gaugeChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/gaugeChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/gaugeChartConfig.tsx new file mode 100644 index 000000000..1323d74ae --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/gaugeChartConfig.tsx @@ -0,0 +1,31 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { GaugeSeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const GaugeChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): GaugeSeriesOption => { + const config: GaugeSeriesOption = { + type: "gauge", + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("echarts.gaugeType"), + radioButton: true, + })} + + )) + .build(); +})(); From 0e5ad339bebb99168e0f4e07b2f7f79bbc53c4ae Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Wed, 1 May 2024 19:40:02 +0500 Subject: [PATCH 012/118] new values added --- .../lowcoder-comps/src/i18n/comps/locales/en.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index 53b106826..dd47097e5 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -1,5 +1,19 @@ export const en = { + echarts: { + title: 'Title', + defaultTitle: 'Funnel Chart', + backgroundColor: 'Background Color', + defaultBackgroundColor: '#fff', + color: 'Color', + defaultColor: '#4C64D9', + funnelType:'Funnel Chart Type', + gaugeType: 'Gauge Chart Type', + legendPosition: "Legend Position", + labelPosition: "Label Position", + }, chart: { + funnel: 'Funnel', + gauge:'Gauge', delete: "Delete", data: "Data", mode: "Mode", @@ -14,6 +28,7 @@ export const en = { seriesName: "Series Name", dataColumns: "Data Columns", title: "Title", + tooltip:'Tooltip', xAxisDirection: "X-axis Direction", xAxisName: "X-axis Name", xAxisType: "X-axis Type", From f0774bdaefe62f6741a35f7b8adc5c4f29a0f19d Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Wed, 1 May 2024 19:40:22 +0500 Subject: [PATCH 013/118] extra data removed --- .../src/i18n/comps/locales/enObj.tsx | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index 6f82ef845..3eda6649b 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -154,35 +154,8 @@ export const enObj: I18nObjects = { ], defaultEchartsJsonOption: { - title: { - text: "Funnel Chart", - left: "center", - }, - backgroundColor: "#ffffff", - color: chartColorPalette, - tooltip: { - trigger: "item", - formatter: "{a}
{b} : {c}%", - }, - legend: { - data: ["Show", "Click", "Visit", "Query", "Buy"], - top: "bottom", - }, series: [ { - name: "Funnel", - type: "funnel", - left: "10%", - top: 60, - bottom: 60, - width: "80%", - min: 0, - max: 100, - gap: 2, - label: { - show: true, - position: "inside", - }, data: [ { value: 100, name: "Show" }, { value: 80, name: "Click" }, From e32c71bc352346ad05abb6b22590944067903d33 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Wed, 1 May 2024 19:40:39 +0500 Subject: [PATCH 014/118] legend config --- .../chartConfigs/echartsLegendConfig.tsx | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsLegendConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsLegendConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsLegendConfig.tsx new file mode 100644 index 000000000..41fb166fb --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsLegendConfig.tsx @@ -0,0 +1,44 @@ +import { + AlignBottom, + AlignTop, + dropdownControl, + MultiCompBuilder, +} from "lowcoder-sdk"; +import { LegendComponentOption } from "echarts"; +import { trans } from "i18n/comps"; + +const LegendPositionOptions = [ + { + label: , + value: "bottom", + }, + { + label: , + value: "top", + }, +] as const; + +export const EchartsLegendConfig = (function () { + return new MultiCompBuilder( + { + position: dropdownControl(LegendPositionOptions, "bottom"), + }, + (props): LegendComponentOption => { + const config: LegendComponentOption = { + top: "bottom", + type: "scroll", + }; + config.top = props.position + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {children.position.propertyView({ + label: trans("echarts.legendPosition"), + radioButton: true, + })} + + )) + .build(); +})(); From 92930f7e97e312d8b3cef5d39d2432d7e04fd352 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Wed, 1 May 2024 19:40:54 +0500 Subject: [PATCH 015/118] label config --- .../chartConfigs/echartsLabelConfig.tsx | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsLabelConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsLabelConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsLabelConfig.tsx new file mode 100644 index 000000000..64b808e01 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsLabelConfig.tsx @@ -0,0 +1,49 @@ +import { + AlignClose, + AlignRight, + AlignLeft, + dropdownControl, + MultiCompBuilder, +} from "lowcoder-sdk"; +import { LegendComponentOption } from "echarts"; +import { trans } from "i18n/comps"; + +const LabelPositionOptions = [ + { + label: , + value: "inside", + }, + { + label: , + value: "right", + }, + { + label: , + value: "left", + }, +] as const; + +export const EchartsLabelConfig = (function () { + return new MultiCompBuilder( + { + position: dropdownControl(LabelPositionOptions, "inside"), + }, + (props): LegendComponentOption => { + const config: LegendComponentOption = { + top: "inside", + type: "scroll", + }; + config.top = props.position + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {children.position.propertyView({ + label: trans("echarts.labelPosition"), + radioButton: true, + })} + + )) + .build(); +})(); From 5fdd5f18dca46111f90e9558c41dba77de2439aa Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Wed, 1 May 2024 19:41:44 +0500 Subject: [PATCH 016/118] echarts configurations updated --- .../src/comps/chartComp/chartConstants.tsx | 46 +++++++++---------- .../src/comps/chartComp/chartPropertyView.tsx | 33 ++++++++++++- .../src/comps/chartComp/chartUtils.ts | 44 +++++++++++++++++- 3 files changed, 97 insertions(+), 26 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx index 431c5d2ce..c1c8f167f 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx @@ -13,20 +13,25 @@ import { eventHandlerControl, valueComp, withType, - ValueFromOption, uiChildren, clickEvent, + styleControl, + EchartsStyle } from "lowcoder-sdk"; import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; import { BarChartConfig } from "./chartConfigs/barChartConfig"; import { XAxisConfig, YAxisConfig } from "./chartConfigs/cartesianAxisConfig"; import { LegendConfig } from "./chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "./chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "./chartConfigs/echartsLabelConfig"; import { LineChartConfig } from "./chartConfigs/lineChartConfig"; import { PieChartConfig } from "./chartConfigs/pieChartConfig"; import { ScatterChartConfig } from "./chartConfigs/scatterChartConfig"; import { SeriesListComp } from "./seriesComp"; import { EChartsOption } from "echarts"; import { i18nObjs, trans } from "i18n/comps"; +import { GaugeChartConfig } from "./chartConfigs/gaugeChartConfig"; +import { FunnelChartConfig } from "./chartConfigs/funnelChartConfig"; export const ChartTypeOptions = [ { @@ -47,32 +52,14 @@ export const ChartTypeOptions = [ }, ] as const; -const chartModeOptions = [ +export const EchartsTypeOptions = [ { - label: trans("chart.UIMode"), - value: "ui", + label: trans("chart.funnel"), + value: "funnel", }, { - label: "ECharts JSON", - value: "json", - }, - { - label: "Map", - value: "map", - }, -] as const; - -const mapModeOptions = [ - { - label: "Map", - value: "map", - }, -] as const; - -const eChartModeOptions = [ - { - label: "ECharts JSON", - value: "json", + label: trans("chart.gauge"), + value: "gauge", }, ] as const; @@ -251,7 +238,13 @@ const ChartOptionMap = { scatter: ScatterChartConfig, }; +const EchartsOptionMap = { + funnel: FunnelChartConfig, + gauge: GaugeChartConfig, +}; + const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "funnel"); export type CharOptionCompType = keyof typeof ChartOptionMap; export const chartUiModeChildren = { @@ -269,6 +262,11 @@ export const chartUiModeChildren = { const chartJsonModeChildren = { echartsOption: jsonControl(toObject, i18nObjs.defaultEchartsJsonOption), + echartsTitle: withDefault(StringControl, trans("echarts.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), } const chartMapModeChildren = { diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx index acecdd902..94b53d98f 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx @@ -1,5 +1,5 @@ import { changeChildAction, CompAction } from "lowcoder-core"; -import { ChartCompChildrenType, ChartTypeOptions, getDataKeys } from "./chartConstants"; +import { ChartCompChildrenType, ChartTypeOptions, EchartsTypeOptions,getDataKeys } from "./chartConstants"; import { newSeries } from "./seriesComp"; import { CustomModal, @@ -150,10 +150,41 @@ export function chartPropertyView( ), })} + { + // keep the previous value + if (children.echartsConfig.children.comp.children.hasOwnProperty("showLabel")) { + children.echartsConfig.dispatchChangeValueAction({ + compType: value as any, + comp: { + showLabel: ( + children.echartsConfig.children.comp.children as any + ).showLabel.toJsonValue(), + }, + }); + } else { + children.echartsConfig.dispatchChangeValueAction({ + compType: value, + }); + } + }} + /> + {children.echartsConfig.children.compType.getView() === "funnel" && + <> + {children.echartsLegendConfig.getPropertyView()} + {children.echartsLabelConfig.getPropertyView()} + } + {children.echartsTitle.propertyView({ label: trans("echarts.title") })}
{children.onEvent.propertyView()}
+
+ {children.style.getPropertyView()} +
{hiddenPropertyView(children)}
); diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts b/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts index 8a1d912fe..b0b90d7cd 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts @@ -130,8 +130,50 @@ export function getSeriesConfig(props: EchartsConfigProps) { // https://echarts.apache.org/en/option.html export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { if (props.mode === "json") { - return props.echartsOption ? props.echartsOption : {}; + let opt={ + "title": { + "text": props.echartsTitle, + "left": "center" + }, + "backgroundColor": props?.style?.background, + "color":props?.style?.color, + "tooltip": { + "trigger": "item", + "formatter": "{a}
{b} : {c}%" + }, + "legend": { + "data": [ + "Show", + "Click", + "Visit", + "Query", + "Buy" + ], + "top": props.echartsLegendConfig.top, + }, + "series": [ + { + "name": props.echartsConfig.type, + "type": props.echartsConfig.type, + "left": "10%", + "top": 60, + "bottom": 60, + "width": "80%", + "min": 0, + "max": 100, + "gap": 2, + "label": { + "show": true, + "position": props.echartsLabelConfig.top + }, + "data": props.echartsOption.series[0].data + } + ] +} + return props.echartsOption ? opt : {}; + } + if(props.mode === "map") { const { mapZoomLevel, From 98cfff37c082f8c4b0ffbb2948b75bec6a4f7265 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Thu, 2 May 2024 02:10:06 +0500 Subject: [PATCH 017/118] new values added --- client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index dd47097e5..44aea9e04 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -10,6 +10,8 @@ export const en = { gaugeType: 'Gauge Chart Type', legendPosition: "Legend Position", labelPosition: "Label Position", + tooltip: 'Tooltip', + legendVisibility:'Legend Visibility' }, chart: { funnel: 'Funnel', From 3421f8c8cc37c43d36dc6c232ed45b2a691df6e6 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Thu, 2 May 2024 02:10:47 +0500 Subject: [PATCH 018/118] legend and title position and tooltip visibility --- .../lowcoder-comps/src/comps/chartComp/chartConstants.tsx | 2 ++ .../src/comps/chartComp/chartPropertyView.tsx | 2 ++ .../lowcoder-comps/src/comps/chartComp/chartUtils.ts | 7 ++++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx index c1c8f167f..4279c82e5 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx @@ -267,6 +267,8 @@ const chartJsonModeChildren = { echartsLabelConfig: EchartsLabelConfig, echartsConfig: EchartsOptionComp, style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), } const chartMapModeChildren = { diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx index 94b53d98f..469b719aa 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx @@ -178,6 +178,8 @@ export function chartPropertyView( {children.echartsLabelConfig.getPropertyView()} } {children.echartsTitle.propertyView({ label: trans("echarts.title") })} + {children.tooltip.propertyView({label: trans("echarts.tooltip")})} + {children.legendVisibility.propertyView({label: trans("echarts.legendVisibility")})}
{children.onEvent.propertyView()} diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts b/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts index b0b90d7cd..71917e267 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts @@ -133,15 +133,16 @@ export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSiz let opt={ "title": { "text": props.echartsTitle, - "left": "center" + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" }, "backgroundColor": props?.style?.background, "color":props?.style?.color, - "tooltip": { + "tooltip": props.tooltip&&{ "trigger": "item", "formatter": "{a}
{b} : {c}%" }, - "legend": { + "legend":props.legendVisibility&& { "data": [ "Show", "Click", From 8735112a06cf078d85068941aa002d657dc2a127 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Thu, 2 May 2024 02:29:43 +0500 Subject: [PATCH 019/118] color and data updated --- .../src/comps/chartComp/chartUtils.ts | 12 +++--------- .../src/i18n/comps/locales/enObj.tsx | 16 ++++++---------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts b/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts index 71917e267..57b908be5 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartUtils.ts @@ -137,19 +137,13 @@ export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSiz "left":"center" }, "backgroundColor": props?.style?.background, - "color":props?.style?.color, + "color": props.echartsOption.data?.map(data => data.color), "tooltip": props.tooltip&&{ "trigger": "item", "formatter": "{a}
{b} : {c}%" }, "legend":props.legendVisibility&& { - "data": [ - "Show", - "Click", - "Visit", - "Query", - "Buy" - ], + "data": props.echartsOption.data?.map(data=>data.name), "top": props.echartsLegendConfig.top, }, "series": [ @@ -167,7 +161,7 @@ export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSiz "show": true, "position": props.echartsLabelConfig.top }, - "data": props.echartsOption.series[0].data + "data": props.echartsOption.data } ] } diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index 3eda6649b..03e9acc14 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -154,17 +154,13 @@ export const enObj: I18nObjects = { ], defaultEchartsJsonOption: { - series: [ - { - data: [ - { value: 100, name: "Show" }, - { value: 80, name: "Click" }, - { value: 60, name: "Visit" }, - { value: 40, name: "Query" }, - { value: 20, name: "Buy" }, + data: [ + { value: 100, name: "Show",color:'#fc8452' }, + { value: 80, name: "Click" ,color:'#9a60b4'}, + { value: 60, name: "Visit" ,color:'#fac858'}, + { value: 40, name: "Query" ,color:'#ee6666'}, + { value: 20, name: "Buy" ,color:'#3ba272'}, ], - }, - ], }, defaultMapJsonOption: defaultMapData, From 359de598adf84a9397bd7dc40589c5600c4b9f69 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Thu, 2 May 2024 23:59:32 +0500 Subject: [PATCH 020/118] echart styles updated --- .../lowcoder/src/comps/controls/styleControlConstants.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx index 841108265..bbe4e7900 100644 --- a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx +++ b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx @@ -1316,11 +1316,6 @@ export const JsonEditorStyle = [LABEL] as const; export const EchartsStyle = [ getBackground("primarySurface"), - { - name: "color", - label: trans("color"), - color: "#4C64D9", - }, ] as const; export const CalendarStyle = [ From 5d7138a386869d75e0fb62f1056ebe136762eb42 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 00:00:01 +0500 Subject: [PATCH 021/118] new data added --- .../src/i18n/comps/locales/enObj.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index 03e9acc14..7f185c438 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -162,6 +162,35 @@ export const enObj: I18nObjects = { { value: 20, name: "Buy" ,color:'#3ba272'}, ], }, + defaultFunnelChartOption: { + data: [ + { value: 100, name: "Show",color:'#fc8452' }, + { value: 80, name: "Click" ,color:'#9a60b4'}, + { value: 60, name: "Visit" ,color:'#fac858'}, + { value: 40, name: "Query" ,color:'#ee6666'}, + { value: 20, name: "Buy" ,color:'#3ba272'}, + ], + }, + defaultGaugeChartOption: { + data: [ + { value: 60, name: "Completed",color:'#fc8452' } + ] + }, + defaultSankeyChartOption: { + data: [ + {name: "Show"}, + {name: "Click"}, + {name: "Visit"}, + {name: "Query"}, + {name: "Buy"} + ], + links: [ + {source: "Show", target: "Click", value: 80}, + {source: "Click", target: "Visit", value: 60}, + {source: "Visit", target: "Query", value: 40}, + {source: "Query", target: "Buy", value: 20} + ] + }, defaultMapJsonOption: defaultMapData, }; From 8b48b9145bf960cdfa71ac23dec203bc83f56658 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 00:00:11 +0500 Subject: [PATCH 022/118] new types added --- .../packages/lowcoder-comps/src/i18n/comps/locales/types.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx index 92f3cc4b6..f428c4f84 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx @@ -4,6 +4,9 @@ import { XAXisComponentOption } from "echarts"; export type I18nObjects = { defaultDataSource: JSONObject[]; defaultEchartsJsonOption: Record; + defaultGaugeChartOption: Record; + defaultFunnelChartOption: Record; + defaultSankeyChartOption: Record; defaultMapJsonOption: Record; timeXAxisLabel?: XAXisComponentOption["axisLabel"]; imageEditorLocale?: Record; From c102b5998256ea3fea0408291505b1344a73ca90 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 00:00:25 +0500 Subject: [PATCH 023/118] new translations added --- .../src/i18n/comps/locales/en.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index 44aea9e04..d61e85f45 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -1,21 +1,28 @@ export const en = { - echarts: { + sankeyChart: { + sankeyType: 'Sankey Chart Type', + title: 'Title', + defaultTitle: 'Sankey Chart', + tooltip: 'Tooltip', + }, + funnelChart: { title: 'Title', defaultTitle: 'Funnel Chart', - backgroundColor: 'Background Color', - defaultBackgroundColor: '#fff', - color: 'Color', - defaultColor: '#4C64D9', funnelType:'Funnel Chart Type', + tooltip: 'Tooltip', + legendVisibility:'Legend Visibility' + }, + gaugeChart: { + title: 'Title', + defaultTitle: 'Gauge Chart', gaugeType: 'Gauge Chart Type', + tooltip: 'Tooltip', + }, + echarts: { legendPosition: "Legend Position", labelPosition: "Label Position", - tooltip: 'Tooltip', - legendVisibility:'Legend Visibility' }, chart: { - funnel: 'Funnel', - gauge:'Gauge', delete: "Delete", data: "Data", mode: "Mode", From 433cf67be86892e47b37ab28f7646dd7fb791a32 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 00:01:03 +0500 Subject: [PATCH 024/118] unused code removed --- .../src/comps/chartComp/chartConstants.tsx | 11 ---- .../src/comps/chartComp/chartPropertyView.tsx | 56 +------------------ 2 files changed, 1 insertion(+), 66 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx index 4279c82e5..d43134234 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConstants.tsx @@ -52,17 +52,6 @@ export const ChartTypeOptions = [ }, ] as const; -export const EchartsTypeOptions = [ - { - label: trans("chart.funnel"), - value: "funnel", - }, - { - label: trans("chart.gauge"), - value: "gauge", - }, -] as const; - export const UIEventOptions = [ { label: trans("chart.select"), diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx index 469b719aa..cfda76b14 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartPropertyView.tsx @@ -1,5 +1,5 @@ import { changeChildAction, CompAction } from "lowcoder-core"; -import { ChartCompChildrenType, ChartTypeOptions, EchartsTypeOptions,getDataKeys } from "./chartConstants"; +import { ChartCompChildrenType, ChartTypeOptions,getDataKeys } from "./chartConstants"; import { newSeries } from "./seriesComp"; import { CustomModal, @@ -134,60 +134,6 @@ export function chartPropertyView( const jsonModePropertyView = ( <> -
- {children.echartsOption.propertyView({ - label: trans("chart.echartsOptionLabel"), - styleName: "higher", - tooltip: ( - - ), - })} - { - // keep the previous value - if (children.echartsConfig.children.comp.children.hasOwnProperty("showLabel")) { - children.echartsConfig.dispatchChangeValueAction({ - compType: value as any, - comp: { - showLabel: ( - children.echartsConfig.children.comp.children as any - ).showLabel.toJsonValue(), - }, - }); - } else { - children.echartsConfig.dispatchChangeValueAction({ - compType: value, - }); - } - }} - /> - {children.echartsConfig.children.compType.getView() === "funnel" && - <> - {children.echartsLegendConfig.getPropertyView()} - {children.echartsLabelConfig.getPropertyView()} - } - {children.echartsTitle.propertyView({ label: trans("echarts.title") })} - {children.tooltip.propertyView({label: trans("echarts.tooltip")})} - {children.legendVisibility.propertyView({label: trans("echarts.legendVisibility")})} -
-
- {children.onEvent.propertyView()} -
-
- {children.style.getPropertyView()} -
-
{hiddenPropertyView(children)}
); From 33ebd94dfce8b320d6578d20a8fc520b980e951d Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 00:01:46 +0500 Subject: [PATCH 025/118] configs updated --- .../src/comps/chartComp/chartConfigs/funnelChartConfig.tsx | 2 +- .../src/comps/chartComp/chartConfigs/gaugeChartConfig.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/funnelChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/funnelChartConfig.tsx index e5ac00c23..84d3f4691 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/funnelChartConfig.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/funnelChartConfig.tsx @@ -26,7 +26,7 @@ export const FunnelChartConfig = (function () { <> {showLabelPropertyView(children)} {children.type.propertyView({ - label: trans("echarts.funnelType"), + label: trans("funnelChart.funnelType"), radioButton: true, })} diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/gaugeChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/gaugeChartConfig.tsx index 1323d74ae..a72c96cbb 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/gaugeChartConfig.tsx +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/gaugeChartConfig.tsx @@ -22,7 +22,7 @@ export const GaugeChartConfig = (function () { <> {showLabelPropertyView(children)} {children.type.propertyView({ - label: trans("echarts.gaugeType"), + label: trans("gaugeChart.gaugeType"), radioButton: true, })} From 187df89edc86b978da5931750281179bde22d3a6 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 00:02:06 +0500 Subject: [PATCH 026/118] sankey chart config --- .../chartConfigs/sankeyChartConfig.tsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/sankeyChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/sankeyChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/sankeyChartConfig.tsx new file mode 100644 index 000000000..60c646c0b --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/sankeyChartConfig.tsx @@ -0,0 +1,35 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { SankeySeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const SankeyChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): SankeySeriesOption => { + const config: SankeySeriesOption = { + type: "sankey", + label: { + show: props.showLabel, + position: "top", + }, + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("sankeyChart.sankeyType"), + radioButton: true, + })} + + )) + .build(); +})(); From 846eb3542ef65200f5eba4f46a3430acb1ca3fb4 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 00:02:55 +0500 Subject: [PATCH 027/118] funnel chart separated --- .../funnelChartComp/funnelChartComp.tsx | 318 +++++++++++++++++ .../funnelChartComp/funnelChartConstants.tsx | 299 ++++++++++++++++ .../funnelChartPropertyView.tsx | 57 +++ .../funnelChartComp/funnelChartUtils.ts | 328 ++++++++++++++++++ 4 files changed, 1002 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartComp.tsx create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartConstants.tsx create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartPropertyView.tsx create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartComp.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartComp.tsx new file mode 100644 index 000000000..4fc0408b2 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartComp.tsx @@ -0,0 +1,318 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartConfigs/cartesianAxisConfig"; +import { funnelChartChildrenMap, ChartSize, getDataKeys } from "./funnelChartConstants"; +import { funnelChartPropertyView } from "./funnelChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + NameConfig, + UICompBuilder, + withDefault, + withExposingConfigs, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "./funnelChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let FunnelChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...funnelChartChildrenMap}, () => null) + .setPropertyViewFn(funnelChartPropertyView) + .build(); +})(); + +FunnelChartTmpComp = withViewFn(FunnelChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +FunnelChartTmpComp = class extends FunnelChartTmpComp { + 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 FunnelChartComp = withExposingConfigs(FunnelChartTmpComp, [ + 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 FunnelChartCompWithDefault = withDefault(FunnelChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartConstants.tsx new file mode 100644 index 000000000..8f509d604 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { FunnelChartConfig } from "../chartConfigs/funnelChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + funnel: FunnelChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "funnel"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultFunnelChartOption), + echartsTitle: withDefault(StringControl, trans("funnelChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const funnelChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(funnelChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartPropertyView.tsx new file mode 100644 index 000000000..8905eb2c3 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartPropertyView.tsx @@ -0,0 +1,57 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./funnelChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartConfigs/chartUrls"; + +export function funnelChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsLegendConfig.getPropertyView()} + {children.echartsLabelConfig.getPropertyView()} + {children.echartsTitle.propertyView({ label: trans("funnelChart.title") })} + {children.tooltip.propertyView({label: trans("funnelChart.tooltip")})} + {children.legendVisibility.propertyView({label: trans("funnelChart.legendVisibility")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartUtils.ts b/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartUtils.ts new file mode 100644 index 000000000..3200980a8 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartUtils.ts @@ -0,0 +1,328 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props?.style?.background, + "color": props.echartsOption.data?.map(data => data.color), + "tooltip": props.tooltip&&{ + "trigger": "item", + "formatter": "{a}
{b} : {c}%" + }, + "legend":props.legendVisibility&& { + "data": props.echartsOption.data?.map(data=>data.name), + "top": props.echartsLegendConfig.top, + }, + "series": [ + { + "name": props.echartsConfig.type, + "type": props.echartsConfig.type, + "left": "10%", + "top": 60, + "bottom": 60, + "width": "80%", + "min": 0, + "max": 100, + "gap": 2, + "label": { + "show": true, + "position": props.echartsLabelConfig.top + }, + "data": props.echartsOption.data + } + ] +} + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From c8799f7b790bdb666298c1fac588d839b943c196 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 00:03:16 +0500 Subject: [PATCH 028/118] gauge chart separated --- .../gaugeChartComp/gaugeChartComp.tsx | 319 +++++++++++++++++ .../gaugeChartComp/gaugeChartConstants.tsx | 299 ++++++++++++++++ .../gaugeChartComp/gaugeChartPropertyView.tsx | 54 +++ .../gaugeChartComp/gaugeChartUtils.ts | 327 ++++++++++++++++++ 4 files changed, 999 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartComp.tsx create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartConstants.tsx create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartPropertyView.tsx create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartComp.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartComp.tsx new file mode 100644 index 000000000..428b7b23f --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartComp.tsx @@ -0,0 +1,319 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartConfigs/cartesianAxisConfig"; +import { gaugeChartChildrenMap, ChartSize, getDataKeys } from "./gaugeChartConstants"; +import { gaugeChartPropertyView } from "./gaugeChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../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/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "./gaugeChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let GaugeChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...gaugeChartChildrenMap}, () => null) + .setPropertyViewFn(gaugeChartPropertyView) + .build(); +})(); + +GaugeChartTmpComp = withViewFn(GaugeChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +GaugeChartTmpComp = class extends GaugeChartTmpComp { + 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 GaugeChartComp = withExposingConfigs(GaugeChartTmpComp, [ + 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 GaugeChartCompWithDefault = withDefault(GaugeChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartConstants.tsx new file mode 100644 index 000000000..9db11568e --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { GaugeChartConfig } from "../chartConfigs/gaugeChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + gauge: GaugeChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "gauge"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultGaugeChartOption), + echartsTitle: withDefault(StringControl, trans("gaugeChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const gaugeChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(gaugeChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartPropertyView.tsx new file mode 100644 index 000000000..106b2d331 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartPropertyView.tsx @@ -0,0 +1,54 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./gaugeChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartConfigs/chartUrls"; + +export function gaugeChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsTitle.propertyView({ label: trans("gaugeChart.title") })} + {children.tooltip.propertyView({label: trans("gaugeChart.tooltip")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartUtils.ts b/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartUtils.ts new file mode 100644 index 000000000..92d67d40a --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartUtils.ts @@ -0,0 +1,327 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props?.style?.background, + "color": props.echartsOption.data?.map(data => data.color), + "tooltip": props.tooltip&&{ + "trigger": "item", + "formatter": "{a}
{b} : {c}%" + }, + "series": [ + { + "name": props.echartsConfig.type, + "type": props.echartsConfig.type, + "left": "10%", + "top": 60, + "bottom": 60, + "width": "80%", + "min": 0, + "max": 100, + "gap": 2, + "label": { + "show": true, + "position": props.echartsLabelConfig.top + }, + "detail": { + "formatter": "{value}%" + }, + "data": props.echartsOption.data + } + ] +} + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From 519c024e8da317e3a4e16a1b1e3b8844be0cc7d3 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 00:03:36 +0500 Subject: [PATCH 029/118] sankey chart created --- .../sankeyChartComp/sankeyChartComp.tsx | 318 +++++++++++++++++ .../sankeyChartComp/sankeyChartConstants.tsx | 299 ++++++++++++++++ .../sankeyChartPropertyView.tsx | 55 +++ .../sankeyChartComp/sankeyChartUtils.ts | 325 ++++++++++++++++++ 4 files changed, 997 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartComp.tsx create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartConstants.tsx create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartPropertyView.tsx create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartComp.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartComp.tsx new file mode 100644 index 000000000..45539438c --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartComp.tsx @@ -0,0 +1,318 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartConfigs/cartesianAxisConfig"; +import { sankeyChartChildrenMap, ChartSize, getDataKeys } from "./sankeyChartConstants"; +import { sankeyChartPropertyView } from "./sankeyChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + NameConfig, + UICompBuilder, + withDefault, + withExposingConfigs, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "./sankeyChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let SankeyChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...sankeyChartChildrenMap}, () => null) + .setPropertyViewFn(sankeyChartPropertyView) + .build(); +})(); + +SankeyChartTmpComp = withViewFn(SankeyChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +SankeyChartTmpComp = class extends SankeyChartTmpComp { + 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 SankeyChartComp = withExposingConfigs(SankeyChartTmpComp, [ + 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 SankeyChartCompWithDefault = withDefault(SankeyChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartConstants.tsx new file mode 100644 index 000000000..203f53e7c --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { SankeyChartConfig } from "../chartConfigs/sankeyChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + sankey: SankeyChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "sankey"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultSankeyChartOption), + echartsTitle: withDefault(StringControl, trans("sankeyChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const sankeyChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(sankeyChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartPropertyView.tsx new file mode 100644 index 000000000..f4f6703e0 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartPropertyView.tsx @@ -0,0 +1,55 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./sankeyChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartConfigs/chartUrls"; + +export function sankeyChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsLabelConfig.getPropertyView()} + {children.echartsTitle.propertyView({ label: trans("sankeyChart.title") })} + {children.tooltip.propertyView({label: trans("sankeyChart.tooltip")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartUtils.ts b/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartUtils.ts new file mode 100644 index 000000000..82f195b55 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartUtils.ts @@ -0,0 +1,325 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props?.style?.background, + "color": props.echartsOption.data?.map(data => data.color), + "tooltip": props.tooltip&&{ + "trigger": "item", + "formatter": "{a}
{b} : {c}%" + }, + "series": [ + { + "name": props.echartsConfig.type, + "type": props.echartsConfig.type, + "left": "10%", + "top": 60, + "bottom": 60, + "width": "80%", + "min": 0, + "max": 100, + "gap": 2, + "label": { + "show": true, + "position": props.echartsLabelConfig.top + }, + "data": props.echartsOption.data, + "links":props.echartsOption.links + } + ] +} + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From cdb09995e8fc0dda7c6689e77219b86857d52147 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 00:03:59 +0500 Subject: [PATCH 030/118] charts displayed --- client/packages/lowcoder-comps/package.json | 20 ++++++++++++++++++-- client/packages/lowcoder-comps/src/index.ts | 8 ++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index 632eb77c8..cd0ef05c3 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -64,8 +64,24 @@ "h": 40 } }, - "eCharts": { - "name": "ECharts", + "funnelChart": { + "name": "Funnel Chart", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, + "gaugeChart": { + "name": "Gauge Chart", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, + "sankeyChart": { + "name": "Sankey Chart", "icon": "./icons/icon-chart.svg", "layoutInfo": { "w": 15, diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 76de0ea94..20a22d328 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -2,13 +2,17 @@ import { ChartCompWithDefault } from "./comps/chartComp/chartComp"; import { ImageEditorComp } from "./comps/imageEditorComp/index"; import { CalendarComp } from "./comps/calendarComp/calendarComp"; import { MermaidComp } from "comps/mermaidComp"; -import { EChartsCompWithDefault } from "comps/eChartsComp/echartsComp"; import { MapCompWithDefault } from "comps/mapComp/mapComp"; +import { FunnelChartCompWithDefault } from "comps/chartComp/funnelChartComp/funnelChartComp"; +import { GaugeChartCompWithDefault } from "comps/chartComp/gaugeChartComp/gaugeChartComp"; +import { SankeyChartCompWithDefault } from "comps/chartComp/sankeyChartComp/sankeyChartComp"; export default { chart: ChartCompWithDefault, map: MapCompWithDefault, - eCharts: EChartsCompWithDefault, + funnelChart: FunnelChartCompWithDefault, + gaugeChart: GaugeChartCompWithDefault, + sankeyChart: SankeyChartCompWithDefault, imageEditor: ImageEditorComp, calendar: CalendarComp, mermaid: MermaidComp, From 36b216fe1fdcceda39ac8dce7fd8c316259e11c1 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 15:07:21 +0500 Subject: [PATCH 031/118] candlestick type added --- client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx index f428c4f84..392d51ef9 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx @@ -7,6 +7,7 @@ export type I18nObjects = { defaultGaugeChartOption: Record; defaultFunnelChartOption: Record; defaultSankeyChartOption: Record; + defaultCandleStickChartOption: Record; defaultMapJsonOption: Record; timeXAxisLabel?: XAXisComponentOption["axisLabel"]; imageEditorLocale?: Record; From 573be0be012f4f3a5808f0112e9435876f81e548 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 15:07:54 +0500 Subject: [PATCH 032/118] default candlestick data --- .../lowcoder-comps/src/i18n/comps/locales/enObj.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index 7f185c438..748a0b2cd 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -191,6 +191,18 @@ export const enObj: I18nObjects = { {source: "Query", target: "Buy", value: 20} ] }, + defaultCandleStickChartOption: { + xAxis: { + data: ["Day 1", "Day 2", "Day 3", "Day 4", "Day 5"] + }, + data:[ + [100, 200, 50, 150], + [120, 220, 80, 180], + [80, 150, 60, 130], + [130, 230, 110, 190], + [90, 180, 70, 160] + ] + }, defaultMapJsonOption: defaultMapData, }; From fccf962cea96ce2c4c7f8faff0635460465516b4 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 15:08:19 +0500 Subject: [PATCH 033/118] candlestick translation --- client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index d61e85f45..c1b32b94c 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -1,4 +1,10 @@ export const en = { + candleStickChart: { + candleStickType: 'CandleStick Chart Type', + title: 'Title', + defaultTitle: 'CandleStick Chart', + tooltip: 'Tooltip', + }, sankeyChart: { sankeyType: 'Sankey Chart Type', title: 'Title', From 5b315c9e3529cca1f4429681a4b358f5a502e51b Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 15:13:26 +0500 Subject: [PATCH 034/118] candlestick constants --- .../candleStickChartConstants.tsx | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartConstants.tsx new file mode 100644 index 000000000..512a0ddda --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { CandleStickChartConfig } from "../chartConfigs/candleStickChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + candleStick: CandleStickChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "candleStick"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultCandleStickChartOption), + echartsTitle: withDefault(StringControl, trans("candleStickChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const candleStickChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(candleStickChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; From 22f9550262d763022ae829792a747cecbc6d6f00 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 15:13:44 +0500 Subject: [PATCH 035/118] candlestick chart config --- .../chartConfigs/candleStickChartConfig.tsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/candleStickChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/candleStickChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/candleStickChartConfig.tsx new file mode 100644 index 000000000..7b7b5b103 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/candleStickChartConfig.tsx @@ -0,0 +1,35 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { CandlestickSeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const CandleStickChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): CandlestickSeriesOption => { + const config: CandlestickSeriesOption = { + type: "candlestick", + label: { + show: props.showLabel, + position: "top", + }, + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("candleStickChart.candleStickType"), + radioButton: true, + })} + + )) + .build(); +})(); From b334ffc03b0bf3fbf3353290ff1e6a9cc2c11f90 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 15:13:56 +0500 Subject: [PATCH 036/118] candlestick chart utils --- .../candleStickChartUtils.ts | 340 ++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartUtils.ts b/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartUtils.ts new file mode 100644 index 000000000..3132b257f --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartUtils.ts @@ -0,0 +1,340 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + console.log("🚀 ~ getEchartsConfig ~ props:", props) + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props?.style?.background, + "color": props.echartsOption.data?.map(data => data.color), + "tooltip": props.tooltip&&{ + "trigger": "axis", + "axisPointer": { + "type": "cross" + } + }, + "grid": { + "left": "10%", + "right": "10%", + "bottom": "10%", + }, + "xAxis": { + "type": "category", + "data": props.echartsOption.xAxis.data + }, + "yAxis": { + "type": "value", + "scale": true + }, + "series": [ + { + "name": props.echartsConfig.type, + "type": props.echartsConfig.type, + "left": "10%", + "top": 60, + "bottom": 60, + "width": "80%", + "min": 0, + "max": 100, + "gap": 2, + "label": { + "show": true, + "position": props.echartsLabelConfig.top + }, + "data": props.echartsOption.data, + } + ] +} + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From 185da1dd27784cf8420f96ba0c6d02ead4cc872f Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 15:14:10 +0500 Subject: [PATCH 037/118] candlestick chart property view --- .../candleStickChartPropertyView.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartPropertyView.tsx new file mode 100644 index 000000000..0f4110605 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartPropertyView.tsx @@ -0,0 +1,54 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./candleStickChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartConfigs/chartUrls"; + +export function candleStickChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsTitle.propertyView({ label: trans("candleStickChart.title") })} + {children.tooltip.propertyView({label: trans("candleStickChart.tooltip")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} From 558470b1ff7a6d3ec12d3f5401cfeba3554f6d42 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 15:14:25 +0500 Subject: [PATCH 038/118] candlestick chart component --- .../candleStickChartComp.tsx | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartComp.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartComp.tsx new file mode 100644 index 000000000..d25c8a84e --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartComp.tsx @@ -0,0 +1,318 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartConfigs/cartesianAxisConfig"; +import { candleStickChartChildrenMap, ChartSize, getDataKeys } from "./candleStickChartConstants"; +import { candleStickChartPropertyView } from "./candleStickChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + NameConfig, + UICompBuilder, + withDefault, + withExposingConfigs, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "./candleStickChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let CandleStickChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...candleStickChartChildrenMap}, () => null) + .setPropertyViewFn(candleStickChartPropertyView) + .build(); +})(); + +CandleStickChartTmpComp = withViewFn(CandleStickChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +CandleStickChartTmpComp = class extends CandleStickChartTmpComp { + 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 CandleStickChartComp = withExposingConfigs(CandleStickChartTmpComp, [ + 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 CandleStickChartCompWithDefault = withDefault(CandleStickChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); From d4a549a0ed5922a704a02f795599cda8fd81227e Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 15:15:23 +0500 Subject: [PATCH 039/118] candlestick chart rendered --- client/packages/lowcoder-comps/package.json | 8 ++++++++ client/packages/lowcoder-comps/src/index.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index cd0ef05c3..af3cc5692 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -88,6 +88,14 @@ "h": 40 } }, + "candleStickChart": { + "name": "CandleStick Chart", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, "map": { "name": "Map", "icon": "./icons/icon-chart.svg", diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 20a22d328..5ebabf7e8 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -6,6 +6,7 @@ import { MapCompWithDefault } from "comps/mapComp/mapComp"; import { FunnelChartCompWithDefault } from "comps/chartComp/funnelChartComp/funnelChartComp"; import { GaugeChartCompWithDefault } from "comps/chartComp/gaugeChartComp/gaugeChartComp"; import { SankeyChartCompWithDefault } from "comps/chartComp/sankeyChartComp/sankeyChartComp"; +import { CandleStickChartCompWithDefault } from "comps/chartComp/candleStickChartComp/candleStickChartComp"; export default { chart: ChartCompWithDefault, @@ -13,6 +14,7 @@ export default { funnelChart: FunnelChartCompWithDefault, gaugeChart: GaugeChartCompWithDefault, sankeyChart: SankeyChartCompWithDefault, + candleStickChart: CandleStickChartCompWithDefault, imageEditor: ImageEditorComp, calendar: CalendarComp, mermaid: MermaidComp, From 27d5bd5a86be6850056a1ba12401011548f9f397 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 15:20:21 +0500 Subject: [PATCH 040/118] filed changed --- .../candleStickChartComp.tsx | 4 +- .../candleStickChartConstants.tsx | 20 +- .../candleStickChartPropertyView.tsx | 2 +- .../candleStickChartUtils.ts | 4 +- .../src/comps/eChartsComp/echartsComp.tsx | 332 ------------------ .../funnelChartComp/funnelChartComp.tsx | 0 .../funnelChartComp/funnelChartConstants.tsx | 0 .../funnelChartPropertyView.tsx | 0 .../funnelChartComp/funnelChartUtils.ts | 0 .../gaugeChartComp/gaugeChartComp.tsx | 0 .../gaugeChartComp/gaugeChartConstants.tsx | 0 .../gaugeChartComp/gaugeChartPropertyView.tsx | 0 .../gaugeChartComp/gaugeChartUtils.ts | 0 .../sankeyChartComp/sankeyChartComp.tsx | 0 .../sankeyChartComp/sankeyChartConstants.tsx | 0 .../sankeyChartPropertyView.tsx | 0 .../sankeyChartComp/sankeyChartUtils.ts | 0 client/packages/lowcoder-comps/src/index.ts | 8 +- 18 files changed, 19 insertions(+), 351 deletions(-) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/candleStickChartComp/candleStickChartComp.tsx (98%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/candleStickChartComp/candleStickChartConstants.tsx (89%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/candleStickChartComp/candleStickChartPropertyView.tsx (95%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/candleStickChartComp/candleStickChartUtils.ts (98%) delete mode 100644 client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx rename client/packages/lowcoder-comps/src/comps/{chartComp => }/funnelChartComp/funnelChartComp.tsx (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/funnelChartComp/funnelChartConstants.tsx (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/funnelChartComp/funnelChartPropertyView.tsx (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/funnelChartComp/funnelChartUtils.ts (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/gaugeChartComp/gaugeChartComp.tsx (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/gaugeChartComp/gaugeChartConstants.tsx (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/gaugeChartComp/gaugeChartPropertyView.tsx (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/gaugeChartComp/gaugeChartUtils.ts (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/sankeyChartComp/sankeyChartComp.tsx (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/sankeyChartComp/sankeyChartConstants.tsx (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/sankeyChartComp/sankeyChartPropertyView.tsx (100%) rename client/packages/lowcoder-comps/src/comps/{chartComp => }/sankeyChartComp/sankeyChartUtils.ts (100%) diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartComp.tsx b/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartComp.tsx similarity index 98% rename from client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartComp.tsx rename to client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartComp.tsx index d25c8a84e..3d1750d9c 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartComp.tsx @@ -5,13 +5,13 @@ import { CompActionTypes, wrapChildAction, } from "lowcoder-core"; -import { AxisFormatterComp, EchartsAxisType } from "../chartConfigs/cartesianAxisConfig"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; import { candleStickChartChildrenMap, ChartSize, getDataKeys } from "./candleStickChartConstants"; import { candleStickChartPropertyView } from "./candleStickChartPropertyView"; import _ from "lodash"; import { useContext, useEffect, useMemo, useRef, useState } from "react"; import ReactResizeDetector from "react-resize-detector"; -import ReactECharts from "../reactEcharts"; +import ReactECharts from "../chartComp/reactEcharts"; import { childrenToProps, depsConfig, diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartConstants.tsx similarity index 89% rename from client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartConstants.tsx rename to client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartConstants.tsx index 512a0ddda..1de9ffdb3 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartConstants.tsx @@ -19,18 +19,18 @@ import { EchartsStyle } from "lowcoder-sdk"; import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; -import { BarChartConfig } from "../chartConfigs/barChartConfig"; -import { XAxisConfig, YAxisConfig } from "../chartConfigs/cartesianAxisConfig"; -import { LegendConfig } from "../chartConfigs/legendConfig"; -import { EchartsLegendConfig } from "../chartConfigs/echartsLegendConfig"; -import { EchartsLabelConfig } from "../chartConfigs/echartsLabelConfig"; -import { LineChartConfig } from "../chartConfigs/lineChartConfig"; -import { PieChartConfig } from "../chartConfigs/pieChartConfig"; -import { ScatterChartConfig } from "../chartConfigs/scatterChartConfig"; -import { SeriesListComp } from "../seriesComp"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; import { EChartsOption } from "echarts"; import { i18nObjs, trans } from "i18n/comps"; -import { CandleStickChartConfig } from "../chartConfigs/candleStickChartConfig"; +import { CandleStickChartConfig } from "../chartComp/chartConfigs/candleStickChartConfig"; export const ChartTypeOptions = [ { diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartPropertyView.tsx similarity index 95% rename from client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartPropertyView.tsx rename to client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartPropertyView.tsx index 0f4110605..9fa9060e3 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartPropertyView.tsx @@ -6,7 +6,7 @@ import { sectionNames, } from "lowcoder-sdk"; import { trans } from "i18n/comps"; -import { examplesUrl,optionUrl } from "../chartConfigs/chartUrls"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; export function candleStickChartPropertyView( children: ChartCompChildrenType, diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartUtils.ts b/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartUtils.ts similarity index 98% rename from client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartUtils.ts rename to client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartUtils.ts index 3132b257f..b8b1812f5 100644 --- a/client/packages/lowcoder-comps/src/comps/chartComp/candleStickChartComp/candleStickChartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartUtils.ts @@ -6,12 +6,12 @@ import { noDataPieChartConfig, } from "comps/chartComp/chartConstants"; import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; -import { EChartsOptionWithMap } from "../reactEcharts/types"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; import _ from "lodash"; import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; import Big from "big.js"; -import { googleMapsApiUrl } from "../chartConfigs/chartUrls"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; export function transformData( originData: JSONObject[], diff --git a/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx b/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx deleted file mode 100644 index 5e1852d95..000000000 --- a/client/packages/lowcoder-comps/src/comps/eChartsComp/echartsComp.tsx +++ /dev/null @@ -1,332 +0,0 @@ -import { - changeChildAction, - changeValueAction, - CompAction, - CompActionTypes, - wrapChildAction, -} from "lowcoder-core"; -import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; -import { chartChildrenMap, ChartSize, getDataKeys } from "../chartComp/chartConstants"; -import { chartPropertyView } from "../chartComp/chartPropertyView"; -import _ from "lodash"; -import { useContext, useEffect, useMemo, useRef, useState } from "react"; -import ReactResizeDetector from "react-resize-detector"; -import ReactECharts from "../chartComp/reactEcharts"; -import { - childrenToProps, - depsConfig, - genRandomKey, - JSONObject, - JSONValue, - NameConfig, - ToViewReturn, - UICompBuilder, - withDefault, - withExposingConfigs, - withMethodExposing, - withViewFn, - ThemeContext, - chartColorPalette, - getPromiseAfterDispatch, - dropdownControl -} from "lowcoder-sdk"; -import { getEchartsLocale, trans } from "i18n/comps"; -import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; -import { - echartsConfigOmitChildren, - getEchartsConfig, - getSelectedPoints, - loadGoogleMapsScript, -} from "comps/chartComp/chartUtils"; -import 'echarts-extension-gmap'; -import log from "loglevel"; - -let clickEventCallback = () => {}; - -const chartModeOptions = [ - { - label: "ECharts JSON", - value: "json", - } -] as const; - -let EChartsTmpComp = (function () { - return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...chartChildrenMap}, () => null) - .setPropertyViewFn(chartPropertyView) - .build(); -})(); - -EChartsTmpComp = withViewFn(EChartsTmpComp, (comp) => { - const apiKey = comp.children.mapApiKey.getView(); - const mode = comp.children.mode.getView(); - const mapCenterPosition = { - lng: comp.children.mapCenterLng.getView(), - lat: comp.children.mapCenterLat.getView(), - } - const onUIEvent = comp.children.onUIEvent.getView(); - const onEvent = comp.children.onEvent.getView(); - - const echartsCompRef = useRef(); - const [chartSize, setChartSize] = useState(); - 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) => { - 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(); - //log.log("chart select change", param); - // trigger click event listener - - 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 option = useMemo(() => { - return getEchartsConfig( - childrenToProps(echartsConfigChildren) as ToViewReturn, - chartSize - ); - }, [chartSize, ...Object.values(echartsConfigChildren)]); - - useEffect(() => { - comp.children.mapInstance.dispatch(changeValueAction(null, false)) - if(comp.children.mapInstance.value) return; - }, [option]) - - return ( - { - 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; - } - }} - > - (echartsCompRef.current = e)} - style={{ height: "100%" }} - notMerge - lazyUpdate - opts={{ locale: getEchartsLocale() }} - option={option} - theme={mode !== 'map' ? themeConfig : undefined} - mode={mode} - /> - - ); -}); - -function getYAxisFormatContextValue( - data: Array, - 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; -} - -EChartsTmpComp = class extends EChartsTmpComp { - 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 EChartsComp = withExposingConfigs(EChartsTmpComp, [ - 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 EChartsCompWithDefault = withDefault(EChartsComp, { - xAxisKey: "date", - series: [ - { - dataIndex: genRandomKey(), - seriesName: trans("chart.spending"), - columnName: "spending", - }, - { - dataIndex: genRandomKey(), - seriesName: trans("chart.budget"), - columnName: "budget", - }, - ], -}); diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartComp.tsx b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartComp.tsx similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartComp.tsx rename to client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartConstants.tsx similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartConstants.tsx rename to client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartPropertyView.tsx similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartPropertyView.tsx rename to client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartUtils.ts b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartUtils.ts similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/funnelChartComp/funnelChartUtils.ts rename to client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartComp.tsx b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartComp.tsx similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartComp.tsx rename to client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartConstants.tsx similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartConstants.tsx rename to client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartPropertyView.tsx similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartPropertyView.tsx rename to client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartUtils.ts b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartUtils.ts similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/gaugeChartComp/gaugeChartUtils.ts rename to client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartComp.tsx b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartComp.tsx similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartComp.tsx rename to client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartConstants.tsx similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartConstants.tsx rename to client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartPropertyView.tsx similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartPropertyView.tsx rename to client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartUtils.ts b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartUtils.ts similarity index 100% rename from client/packages/lowcoder-comps/src/comps/chartComp/sankeyChartComp/sankeyChartUtils.ts rename to client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 5ebabf7e8..207bd99f4 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -3,10 +3,10 @@ import { ImageEditorComp } from "./comps/imageEditorComp/index"; import { CalendarComp } from "./comps/calendarComp/calendarComp"; import { MermaidComp } from "comps/mermaidComp"; import { MapCompWithDefault } from "comps/mapComp/mapComp"; -import { FunnelChartCompWithDefault } from "comps/chartComp/funnelChartComp/funnelChartComp"; -import { GaugeChartCompWithDefault } from "comps/chartComp/gaugeChartComp/gaugeChartComp"; -import { SankeyChartCompWithDefault } from "comps/chartComp/sankeyChartComp/sankeyChartComp"; -import { CandleStickChartCompWithDefault } from "comps/chartComp/candleStickChartComp/candleStickChartComp"; +import { FunnelChartCompWithDefault } from "comps/funnelChartComp/funnelChartComp"; +import { GaugeChartCompWithDefault } from "comps/gaugeChartComp/gaugeChartComp"; +import { SankeyChartCompWithDefault } from "comps/sankeyChartComp/sankeyChartComp"; +import { CandleStickChartCompWithDefault } from "comps/candleStickChartComp/candleStickChartComp"; export default { chart: ChartCompWithDefault, From 39b163aaf356cbce5616cd80efde19da92157e6f Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 18:55:26 +0500 Subject: [PATCH 041/118] console removed --- .../src/comps/candleStickChartComp/candleStickChartUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartUtils.ts b/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartUtils.ts index b8b1812f5..00dcb0d2d 100644 --- a/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/candleStickChartComp/candleStickChartUtils.ts @@ -129,7 +129,6 @@ export function getSeriesConfig(props: EchartsConfigProps) { // https://echarts.apache.org/en/option.html export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { - console.log("🚀 ~ getEchartsConfig ~ props:", props) if (props.mode === "json") { let opt={ "title": { From 50622f03aed950074cc7cb7f16c62c7dfc8348f9 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 18:56:45 +0500 Subject: [PATCH 042/118] imports changed --- .../comps/funnelChartComp/funnelChartComp.tsx | 4 ++-- .../funnelChartComp/funnelChartConstants.tsx | 20 +++++++++---------- .../funnelChartPropertyView.tsx | 2 +- .../comps/funnelChartComp/funnelChartUtils.ts | 4 ++-- .../comps/gaugeChartComp/gaugeChartComp.tsx | 4 ++-- .../gaugeChartComp/gaugeChartConstants.tsx | 20 +++++++++---------- .../gaugeChartComp/gaugeChartPropertyView.tsx | 2 +- .../comps/gaugeChartComp/gaugeChartUtils.ts | 4 ++-- .../comps/sankeyChartComp/sankeyChartComp.tsx | 4 ++-- .../sankeyChartComp/sankeyChartConstants.tsx | 20 +++++++++---------- .../sankeyChartPropertyView.tsx | 2 +- .../comps/sankeyChartComp/sankeyChartUtils.ts | 4 ++-- 12 files changed, 45 insertions(+), 45 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartComp.tsx b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartComp.tsx index 4fc0408b2..f26078335 100644 --- a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartComp.tsx @@ -5,13 +5,13 @@ import { CompActionTypes, wrapChildAction, } from "lowcoder-core"; -import { AxisFormatterComp, EchartsAxisType } from "../chartConfigs/cartesianAxisConfig"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; import { funnelChartChildrenMap, ChartSize, getDataKeys } from "./funnelChartConstants"; import { funnelChartPropertyView } from "./funnelChartPropertyView"; import _ from "lodash"; import { useContext, useEffect, useMemo, useRef, useState } from "react"; import ReactResizeDetector from "react-resize-detector"; -import ReactECharts from "../reactEcharts"; +import ReactECharts from "../chartComp/reactEcharts"; import { childrenToProps, depsConfig, diff --git a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartConstants.tsx index 8f509d604..97e622a28 100644 --- a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartConstants.tsx @@ -19,18 +19,18 @@ import { EchartsStyle } from "lowcoder-sdk"; import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; -import { BarChartConfig } from "../chartConfigs/barChartConfig"; -import { XAxisConfig, YAxisConfig } from "../chartConfigs/cartesianAxisConfig"; -import { LegendConfig } from "../chartConfigs/legendConfig"; -import { EchartsLegendConfig } from "../chartConfigs/echartsLegendConfig"; -import { EchartsLabelConfig } from "../chartConfigs/echartsLabelConfig"; -import { LineChartConfig } from "../chartConfigs/lineChartConfig"; -import { PieChartConfig } from "../chartConfigs/pieChartConfig"; -import { ScatterChartConfig } from "../chartConfigs/scatterChartConfig"; -import { SeriesListComp } from "../seriesComp"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; import { EChartsOption } from "echarts"; import { i18nObjs, trans } from "i18n/comps"; -import { FunnelChartConfig } from "../chartConfigs/funnelChartConfig"; +import { FunnelChartConfig } from "../chartComp/chartConfigs/funnelChartConfig"; export const ChartTypeOptions = [ { diff --git a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartPropertyView.tsx index 8905eb2c3..0481ccb80 100644 --- a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartPropertyView.tsx @@ -6,7 +6,7 @@ import { sectionNames, } from "lowcoder-sdk"; import { trans } from "i18n/comps"; -import { examplesUrl,optionUrl } from "../chartConfigs/chartUrls"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; export function funnelChartPropertyView( children: ChartCompChildrenType, diff --git a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartUtils.ts b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartUtils.ts index 3200980a8..64a2f9797 100644 --- a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartUtils.ts @@ -6,12 +6,12 @@ import { noDataPieChartConfig, } from "comps/chartComp/chartConstants"; import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; -import { EChartsOptionWithMap } from "../reactEcharts/types"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; import _ from "lodash"; import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; import Big from "big.js"; -import { googleMapsApiUrl } from "../chartConfigs/chartUrls"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; export function transformData( originData: JSONObject[], diff --git a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartComp.tsx b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartComp.tsx index 428b7b23f..3823dd888 100644 --- a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartComp.tsx @@ -5,13 +5,13 @@ import { CompActionTypes, wrapChildAction, } from "lowcoder-core"; -import { AxisFormatterComp, EchartsAxisType } from "../chartConfigs/cartesianAxisConfig"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; import { gaugeChartChildrenMap, ChartSize, getDataKeys } from "./gaugeChartConstants"; import { gaugeChartPropertyView } from "./gaugeChartPropertyView"; import _ from "lodash"; import { useContext, useEffect, useMemo, useRef, useState } from "react"; import ReactResizeDetector from "react-resize-detector"; -import ReactECharts from "../reactEcharts"; +import ReactECharts from "../chartComp/reactEcharts"; import { childrenToProps, depsConfig, diff --git a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartConstants.tsx index 9db11568e..a0f1d5e2a 100644 --- a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartConstants.tsx @@ -19,18 +19,18 @@ import { EchartsStyle } from "lowcoder-sdk"; import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; -import { BarChartConfig } from "../chartConfigs/barChartConfig"; -import { XAxisConfig, YAxisConfig } from "../chartConfigs/cartesianAxisConfig"; -import { LegendConfig } from "../chartConfigs/legendConfig"; -import { EchartsLegendConfig } from "../chartConfigs/echartsLegendConfig"; -import { EchartsLabelConfig } from "../chartConfigs/echartsLabelConfig"; -import { LineChartConfig } from "../chartConfigs/lineChartConfig"; -import { PieChartConfig } from "../chartConfigs/pieChartConfig"; -import { ScatterChartConfig } from "../chartConfigs/scatterChartConfig"; -import { SeriesListComp } from "../seriesComp"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; import { EChartsOption } from "echarts"; import { i18nObjs, trans } from "i18n/comps"; -import { GaugeChartConfig } from "../chartConfigs/gaugeChartConfig"; +import { GaugeChartConfig } from "../chartComp/chartConfigs/gaugeChartConfig"; export const ChartTypeOptions = [ { diff --git a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartPropertyView.tsx index 106b2d331..ac044baf0 100644 --- a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartPropertyView.tsx @@ -6,7 +6,7 @@ import { sectionNames, } from "lowcoder-sdk"; import { trans } from "i18n/comps"; -import { examplesUrl,optionUrl } from "../chartConfigs/chartUrls"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; export function gaugeChartPropertyView( children: ChartCompChildrenType, diff --git a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartUtils.ts b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartUtils.ts index 92d67d40a..24e072cc1 100644 --- a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartUtils.ts @@ -6,12 +6,12 @@ import { noDataPieChartConfig, } from "comps/chartComp/chartConstants"; import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; -import { EChartsOptionWithMap } from "../reactEcharts/types"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; import _ from "lodash"; import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; import Big from "big.js"; -import { googleMapsApiUrl } from "../chartConfigs/chartUrls"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; export function transformData( originData: JSONObject[], diff --git a/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartComp.tsx b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartComp.tsx index 45539438c..680f47771 100644 --- a/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartComp.tsx @@ -5,13 +5,13 @@ import { CompActionTypes, wrapChildAction, } from "lowcoder-core"; -import { AxisFormatterComp, EchartsAxisType } from "../chartConfigs/cartesianAxisConfig"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; import { sankeyChartChildrenMap, ChartSize, getDataKeys } from "./sankeyChartConstants"; import { sankeyChartPropertyView } from "./sankeyChartPropertyView"; import _ from "lodash"; import { useContext, useEffect, useMemo, useRef, useState } from "react"; import ReactResizeDetector from "react-resize-detector"; -import ReactECharts from "../reactEcharts"; +import ReactECharts from "../chartComp/reactEcharts"; import { childrenToProps, depsConfig, diff --git a/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartConstants.tsx index 203f53e7c..bc106cebe 100644 --- a/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartConstants.tsx @@ -19,18 +19,18 @@ import { EchartsStyle } from "lowcoder-sdk"; import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; -import { BarChartConfig } from "../chartConfigs/barChartConfig"; -import { XAxisConfig, YAxisConfig } from "../chartConfigs/cartesianAxisConfig"; -import { LegendConfig } from "../chartConfigs/legendConfig"; -import { EchartsLegendConfig } from "../chartConfigs/echartsLegendConfig"; -import { EchartsLabelConfig } from "../chartConfigs/echartsLabelConfig"; -import { LineChartConfig } from "../chartConfigs/lineChartConfig"; -import { PieChartConfig } from "../chartConfigs/pieChartConfig"; -import { ScatterChartConfig } from "../chartConfigs/scatterChartConfig"; -import { SeriesListComp } from "../seriesComp"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; import { EChartsOption } from "echarts"; import { i18nObjs, trans } from "i18n/comps"; -import { SankeyChartConfig } from "../chartConfigs/sankeyChartConfig"; +import { SankeyChartConfig } from "../chartComp/chartConfigs/sankeyChartConfig"; export const ChartTypeOptions = [ { diff --git a/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartPropertyView.tsx index f4f6703e0..da18ef2a4 100644 --- a/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartPropertyView.tsx @@ -6,7 +6,7 @@ import { sectionNames, } from "lowcoder-sdk"; import { trans } from "i18n/comps"; -import { examplesUrl,optionUrl } from "../chartConfigs/chartUrls"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; export function sankeyChartPropertyView( children: ChartCompChildrenType, diff --git a/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartUtils.ts b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartUtils.ts index 82f195b55..819fa4e67 100644 --- a/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/sankeyChartComp/sankeyChartUtils.ts @@ -6,12 +6,12 @@ import { noDataPieChartConfig, } from "comps/chartComp/chartConstants"; import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; -import { EChartsOptionWithMap } from "../reactEcharts/types"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; import _ from "lodash"; import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; import Big from "big.js"; -import { googleMapsApiUrl } from "../chartConfigs/chartUrls"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; export function transformData( originData: JSONObject[], From 34290f3690871bb9ac89f1b2c577b7a02a8791b5 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 19:44:43 +0500 Subject: [PATCH 043/118] radar options added --- .../src/i18n/comps/locales/enObj.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index 748a0b2cd..beb381eef 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -203,6 +203,35 @@ export const enObj: I18nObjects = { [90, 180, 70, 160] ] }, + defaultRadarChartOption: { + indicator: [ + { name: "Indicator 1", max: 100 }, + { name: "Indicator 2", max: 100 }, + { name: "Indicator 3", max: 100 }, + { name: "Indicator 4", max: 100 }, + { name: "Indicator 5", max: 100 } + ], + series: [ + { + "name": "Data 1", + "data": [ + { + "value": [90, 80, 70, 60, 50], + "name": "Data 1" + } + ] + }, + { + "name": "Data 2", + "data": [ + { + "value": [70, 60, 50, 40, 30], + "name": "Data 2" + } + ] + } + ] + }, defaultMapJsonOption: defaultMapData, }; From e3e0fce485e1ac1a3d956be3007c1f96e0bdee83 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 19:44:59 +0500 Subject: [PATCH 044/118] radar type added --- client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx index 392d51ef9..3cc3d7c71 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx @@ -8,6 +8,7 @@ export type I18nObjects = { defaultFunnelChartOption: Record; defaultSankeyChartOption: Record; defaultCandleStickChartOption: Record; + defaultRadarChartOption: Record; defaultMapJsonOption: Record; timeXAxisLabel?: XAXisComponentOption["axisLabel"]; imageEditorLocale?: Record; From 4f0f807cdc635ba6990c1756bfc9ade170485809 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 19:45:14 +0500 Subject: [PATCH 045/118] radar translations added --- client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index c1b32b94c..2bd840abe 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -1,4 +1,10 @@ export const en = { + radarChart: { + radarType: 'Radar Chart Type', + title: 'Title', + defaultTitle: 'Radar Chart', + tooltip: 'Tooltip', + }, candleStickChart: { candleStickType: 'CandleStick Chart Type', title: 'Title', From f6acf9e7ea49b63f9022ba10ff6437ba65ce5925 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 19:45:30 +0500 Subject: [PATCH 046/118] radar utils --- .../comps/radarChartComp/radarChartUtils.ts | 321 ++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartUtils.ts b/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartUtils.ts new file mode 100644 index 000000000..378e405fd --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartUtils.ts @@ -0,0 +1,321 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + console.log("🚀 ~ getEchartsConfig ~ props:", props) + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props?.style?.background, + "color": props.echartsOption.data?.map(data => data.color), + "tooltip": { + "trigger": "axis", + "formatter": function(params) { + let tooltipText = params[0].name + '
'; + params.forEach(function(item) { + tooltipText += item.seriesName + ': ' + item.value + '
'; + }); + return tooltipText; + } + }, + "radar": [ + { + "indicator": props.echartsOption.indicator, + "center": ["50%", "50%"], + "radius": "60%" + } + ], + "series": props.echartsOption.series.map(option=>{return {...option,type:'radar'}}) +} + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From f2a3645b50750d2fa0cbe8379f2a85d3774eb234 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 19:45:43 +0500 Subject: [PATCH 047/118] radar property view --- .../radarChartComp/radarChartPropertyView.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartPropertyView.tsx new file mode 100644 index 000000000..f6ea20b14 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartPropertyView.tsx @@ -0,0 +1,54 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./radarChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function radarChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsTitle.propertyView({ label: trans("radarChart.title") })} + {children.tooltip.propertyView({label: trans("radarChart.tooltip")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} From bf1bad08b69ada61f583a4a30aac2f278247e1f3 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 19:45:55 +0500 Subject: [PATCH 048/118] radar constants --- .../radarChartComp/radarChartConstants.tsx | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartConstants.tsx new file mode 100644 index 000000000..a96f7a36d --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { RadarChartConfig } from "comps/chartComp/chartConfigs/radarChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + radar: RadarChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "radar"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultRadarChartOption), + echartsTitle: withDefault(StringControl, trans("radarChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const radarChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(radarChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; From b9b30a8516e160f56d2c9a522e4ce3d9227a2621 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 19:46:05 +0500 Subject: [PATCH 049/118] radar config --- .../chartConfigs/radarChartConfig.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/radarChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/radarChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/radarChartConfig.tsx new file mode 100644 index 000000000..5615c2d73 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/radarChartConfig.tsx @@ -0,0 +1,31 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { RadarSeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const RadarChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): RadarSeriesOption => { + const config: RadarSeriesOption = { + type: "radar", + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("radarChart.radarType"), + radioButton: true, + })} + + )) + .build(); +})(); From 9d5645e913dad251a7c8a7f671e89fb4909ffa39 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 19:46:17 +0500 Subject: [PATCH 050/118] radar component --- .../comps/radarChartComp/radarChartComp.tsx | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartComp.tsx b/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartComp.tsx new file mode 100644 index 000000000..b9a9af5be --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartComp.tsx @@ -0,0 +1,318 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { radarChartChildrenMap, ChartSize, getDataKeys } from "./radarChartConstants"; +import { radarChartPropertyView } from "./radarChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../chartComp/reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + NameConfig, + UICompBuilder, + withDefault, + withExposingConfigs, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "./radarChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let RadarChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...radarChartChildrenMap}, () => null) + .setPropertyViewFn(radarChartPropertyView) + .build(); +})(); + +RadarChartTmpComp = withViewFn(RadarChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +RadarChartTmpComp = class extends RadarChartTmpComp { + 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 RadarChartComp = withExposingConfigs(RadarChartTmpComp, [ + 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 RadarChartCompWithDefault = withDefault(RadarChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); From 1766f740c4c0919b80d002c720b0f8e28be71b27 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 19:46:32 +0500 Subject: [PATCH 051/118] radar chart rendered --- client/packages/lowcoder-comps/package.json | 8 ++++++++ client/packages/lowcoder-comps/src/index.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index af3cc5692..12a25e012 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -96,6 +96,14 @@ "h": 40 } }, + "radarChart": { + "name": "Radar Chart", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, "map": { "name": "Map", "icon": "./icons/icon-chart.svg", diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 207bd99f4..0ef0e042f 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -7,6 +7,7 @@ import { FunnelChartCompWithDefault } from "comps/funnelChartComp/funnelChartCom import { GaugeChartCompWithDefault } from "comps/gaugeChartComp/gaugeChartComp"; import { SankeyChartCompWithDefault } from "comps/sankeyChartComp/sankeyChartComp"; import { CandleStickChartCompWithDefault } from "comps/candleStickChartComp/candleStickChartComp"; +import { RadarChartCompWithDefault } from "comps/radarChartComp/radarChartComp"; export default { chart: ChartCompWithDefault, @@ -15,6 +16,7 @@ export default { gaugeChart: GaugeChartCompWithDefault, sankeyChart: SankeyChartCompWithDefault, candleStickChart: CandleStickChartCompWithDefault, + radarChart: RadarChartCompWithDefault, imageEditor: ImageEditorComp, calendar: CalendarComp, mermaid: MermaidComp, From 52ac64d39ad6b9e80796eb02efc61f7cde203132 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 19:59:53 +0500 Subject: [PATCH 052/118] console removed --- .../lowcoder-comps/src/comps/radarChartComp/radarChartUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartUtils.ts b/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartUtils.ts index 378e405fd..3ce2d0f7b 100644 --- a/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/radarChartComp/radarChartUtils.ts @@ -129,7 +129,6 @@ export function getSeriesConfig(props: EchartsConfigProps) { // https://echarts.apache.org/en/option.html export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { - console.log("🚀 ~ getEchartsConfig ~ props:", props) if (props.mode === "json") { let opt={ "title": { From 3d0c32cf863dc4808d5e8e9ec7c9e9b394d2c390 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 22:45:49 +0500 Subject: [PATCH 053/118] heatmap type added --- client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx index 3cc3d7c71..3c1b80341 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx @@ -9,6 +9,7 @@ export type I18nObjects = { defaultSankeyChartOption: Record; defaultCandleStickChartOption: Record; defaultRadarChartOption: Record; + defaultHeatmapChartOption: Record; defaultMapJsonOption: Record; timeXAxisLabel?: XAXisComponentOption["axisLabel"]; imageEditorLocale?: Record; From dd83ab141bc15e4e82a287604c3a7ceb2552aa44 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 22:45:58 +0500 Subject: [PATCH 054/118] heatmap data added --- .../src/i18n/comps/locales/enObj.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index beb381eef..4fd0d363c 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -232,6 +232,37 @@ export const enObj: I18nObjects = { } ] }, + defaultHeatmapChartOption: { + xAxis: { + "data": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + }, + yAxis: { + "data": ["Morning", "Afternoon", "Evening"] + }, + data: [ + [0, 0, 10], + [0, 1, 20], + [0, 2, 30], + [1, 0, 40], + [1, 1, 50], + [1, 2, 60], + [2, 0, 70], + [2, 1, 80], + [2, 2, 90], + [3, 0, 100], + [3, 1, 90], + [3, 2, 80], + [4, 0, 70], + [4, 1, 60], + [4, 2, 50], + [5, 0, 40], + [5, 1, 30], + [5, 2, 20], + [6, 0, 10], + [6, 1, 0], + [6, 2, 10] + ] + }, defaultMapJsonOption: defaultMapData, }; From d62f76f53f354dbadf11bdfc0641f299305d7061 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 22:46:11 +0500 Subject: [PATCH 055/118] heatmap translation added --- client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index 2bd840abe..4a1cb7e63 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -1,4 +1,10 @@ export const en = { + heatmapChart: { + heatmapType: 'Heatmap Chart Type', + title: 'Title', + defaultTitle: 'Heatmap Chart', + tooltip: 'Tooltip', + }, radarChart: { radarType: 'Radar Chart Type', title: 'Title', From 0f327feb8759c7e2451d11ba77358814a8dc84c0 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 22:46:31 +0500 Subject: [PATCH 056/118] heatmap chart property view --- .../heatmapChartPropertyView.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartPropertyView.tsx new file mode 100644 index 000000000..8f990546e --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartPropertyView.tsx @@ -0,0 +1,54 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./heatmapChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function heatmapChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsTitle.propertyView({ label: trans("heatmapChart.title") })} + {children.tooltip.propertyView({label: trans("heatmapChart.tooltip")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} From c61ac6cec9dd80854a5d8e5b1ad32152a9c8f98d Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 22:46:45 +0500 Subject: [PATCH 057/118] heatmap chart constants --- .../heatmapChartConstants.tsx | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartConstants.tsx new file mode 100644 index 000000000..d4a8a9a29 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { HeatmapChartConfig } from "comps/chartComp/chartConfigs/heatmapChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + heatmap: HeatmapChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "heatmap"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultHeatmapChartOption), + echartsTitle: withDefault(StringControl, trans("heatmapChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const heatmapChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(heatmapChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; From 132b731ee66ff686e4189406c042a9c3114e1ad8 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 22:46:57 +0500 Subject: [PATCH 058/118] heatmap chart config --- .../chartConfigs/heatmapChartConfig.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/heatmapChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/heatmapChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/heatmapChartConfig.tsx new file mode 100644 index 000000000..cbebb6410 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/heatmapChartConfig.tsx @@ -0,0 +1,31 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { HeatmapSeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const HeatmapChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): HeatmapSeriesOption => { + const config: HeatmapSeriesOption = { + type: "heatmap", + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("heatmapChart.heatmapType"), + radioButton: true, + })} + + )) + .build(); +})(); From fc1300412674c3656b1f14731f260325828a9b26 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 22:47:08 +0500 Subject: [PATCH 059/118] heatmap chart utils --- .../heatmapChartComp/heatmapChartUtils.ts | 336 ++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartUtils.ts b/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartUtils.ts new file mode 100644 index 000000000..6d9c0bef9 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartUtils.ts @@ -0,0 +1,336 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props?.style?.background, + "color": props.echartsOption.data?.map(data => data.color), + "tooltip": props.tooltip&&{ + "position": "top" + }, + "grid": { + "height": "50%", + "top": "10%" + }, + "visualMap": { + "min": 0, + "max": 100, + "calculable": true, + "orient": "horizontal", + "left": "center", + "bottom": "15%" + }, + "legend": { + "data": ["Heatmap"], + "left": "left" + }, + 'xAxis': { + "type": "category", + 'data':props.echartsOption.xAxis.data + }, + 'yAxis': { + "type": "category", + data: props.echartsOption.yAxis.data + }, + 'series': [ + { + 'name': 'Heatmap', + 'type': 'heatmap', + 'data':props.echartsOption.data + } + ] +} + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From 044bf700df56bff539b3bae347bb50501178a4f6 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 22:47:45 +0500 Subject: [PATCH 060/118] heatmap chart component --- .../heatmapChartComp/heatmapChartComp.tsx | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartComp.tsx b/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartComp.tsx new file mode 100644 index 000000000..760b79dca --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/heatmapChartComp/heatmapChartComp.tsx @@ -0,0 +1,318 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { heatmapChartChildrenMap, ChartSize, getDataKeys } from "./heatmapChartConstants"; +import { heatmapChartPropertyView } from "./heatmapChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../chartComp/reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + NameConfig, + UICompBuilder, + withDefault, + withExposingConfigs, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "./heatmapChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let HeatmapChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...heatmapChartChildrenMap}, () => null) + .setPropertyViewFn(heatmapChartPropertyView) + .build(); +})(); + +HeatmapChartTmpComp = withViewFn(HeatmapChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +HeatmapChartTmpComp = class extends HeatmapChartTmpComp { + 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 HeatmapChartComp = withExposingConfigs(HeatmapChartTmpComp, [ + 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 HeatmapChartCompWithDefault = withDefault(HeatmapChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); From 5fcea6d4e6f96969e7a7f39507bf83876afcd2ca Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 22:47:58 +0500 Subject: [PATCH 061/118] heatmap chart rendered --- client/packages/lowcoder-comps/package.json | 8 ++++++++ client/packages/lowcoder-comps/src/index.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index 12a25e012..73e586269 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -104,6 +104,14 @@ "h": 40 } }, + "heatmapChart": { + "name": "Heatmap Chart", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, "map": { "name": "Map", "icon": "./icons/icon-chart.svg", diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 0ef0e042f..76c1f8428 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -8,6 +8,7 @@ import { GaugeChartCompWithDefault } from "comps/gaugeChartComp/gaugeChartComp"; import { SankeyChartCompWithDefault } from "comps/sankeyChartComp/sankeyChartComp"; import { CandleStickChartCompWithDefault } from "comps/candleStickChartComp/candleStickChartComp"; import { RadarChartCompWithDefault } from "comps/radarChartComp/radarChartComp"; +import { HeatmapChartCompWithDefault } from "comps/heatmapChartComp/heatmapChartComp"; export default { chart: ChartCompWithDefault, @@ -17,6 +18,7 @@ export default { sankeyChart: SankeyChartCompWithDefault, candleStickChart: CandleStickChartCompWithDefault, radarChart: RadarChartCompWithDefault, + heatmapChart: HeatmapChartCompWithDefault, imageEditor: ImageEditorComp, calendar: CalendarComp, mermaid: MermaidComp, From ad79dda62cc19675905a38236c6f9a3d8b93e2fd Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:21:14 +0500 Subject: [PATCH 062/118] graph chart type added --- client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx index 3c1b80341..e36a8361c 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx @@ -10,6 +10,7 @@ export type I18nObjects = { defaultCandleStickChartOption: Record; defaultRadarChartOption: Record; defaultHeatmapChartOption: Record; + defaultGraphChartOption: Record; defaultMapJsonOption: Record; timeXAxisLabel?: XAXisComponentOption["axisLabel"]; imageEditorLocale?: Record; From ddcb3e9783bb25101d0e05f3610257c25d4276da Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:21:28 +0500 Subject: [PATCH 063/118] graph chart data added --- .../src/i18n/comps/locales/enObj.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index 4fd0d363c..3ce3bacc6 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -263,6 +263,21 @@ export const enObj: I18nObjects = { [6, 2, 10] ] }, + defaultGraphChartOption: { + categories: [ + {name: "Nodes"}, + {name: "Edges"} + ], + nodes: [ + {name: "Node 1", category: 0}, + {name: "Node 2", category: 0}, + {name: "Node 3", category: 0} + ], + links: [ + {source: "Node 1", target: "Node 2", category: 1}, + {source: "Node 2", target: "Node 3", category: 1} + ] + }, defaultMapJsonOption: defaultMapData, }; From 082c86f65dc4e34ae64db61f768a710c8b720941 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:22:03 +0500 Subject: [PATCH 064/118] graph chart translation added --- client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index 4a1cb7e63..39a8d6d4a 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -1,4 +1,10 @@ export const en = { + graphChart: { + graphType: 'Graph Chart Type', + title: 'Title', + defaultTitle: 'Graph Chart', + tooltip: 'Tooltip', + }, heatmapChart: { heatmapType: 'Heatmap Chart Type', title: 'Title', From 7642467148457ad8e6571b9028f1e952e6d4278f Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:23:17 +0500 Subject: [PATCH 065/118] graph chart constants --- .../graphChartComp/graphChartConstants.tsx | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartConstants.tsx new file mode 100644 index 000000000..a3c5c0095 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { GraphChartConfig } from "../chartComp/chartConfigs/graphChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + graph: GraphChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "graph"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultGraphChartOption), + echartsTitle: withDefault(StringControl, trans("graphChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const graphChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(graphChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; From fed85682accca91bec5ccd5581d126fba57d0faf Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:23:32 +0500 Subject: [PATCH 066/118] graph chart property view --- .../graphChartComp/graphChartPropertyView.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartPropertyView.tsx new file mode 100644 index 000000000..3ad76fb1e --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartPropertyView.tsx @@ -0,0 +1,54 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./graphChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function graphChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsTitle.propertyView({ label: trans("graphChart.title") })} + {children.tooltip.propertyView({label: trans("graphChart.tooltip")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} From 4972d9ceb729735c364637554ae652144ade2cea Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:23:49 +0500 Subject: [PATCH 067/118] graph chart config --- .../chartConfigs/graphChartConfig.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/graphChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/graphChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/graphChartConfig.tsx new file mode 100644 index 000000000..dbc23403e --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/graphChartConfig.tsx @@ -0,0 +1,31 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { GraphSeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const GraphChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): GraphSeriesOption => { + const config: GraphSeriesOption = { + type: "graph", + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("graphChart.graphType"), + radioButton: true, + })} + + )) + .build(); +})(); From ce74251a13ec07ec86719df18fc5543ef6350748 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:23:57 +0500 Subject: [PATCH 068/118] graph chart utils --- .../comps/graphChartComp/graphChartUtils.ts | 319 ++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartUtils.ts b/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartUtils.ts new file mode 100644 index 000000000..df231bd0d --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartUtils.ts @@ -0,0 +1,319 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props?.style?.background, + "color": props.echartsOption.data?.map(data => data.color), + "tooltip": props.tooltip&& { + "trigger": "item" + }, + 'series': [ + { + "type": "graph", + "layout": "force", + "force": { + "repulsion": 100, + "gravity": 0.1, + "edgeLength": 100 + }, + 'categories': props.echartsOption.categories, + 'links': props.echartsOption.links, + 'nodes': props.echartsOption.nodes, + } + ] +} + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From 12598ab231a89a9eda1de26d19e590cd61d7b8cb Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:24:08 +0500 Subject: [PATCH 069/118] graph chart commponent --- .../comps/graphChartComp/graphChartComp.tsx | 319 ++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartComp.tsx b/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartComp.tsx new file mode 100644 index 000000000..7b395901c --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/graphChartComp/graphChartComp.tsx @@ -0,0 +1,319 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { graphChartChildrenMap, ChartSize, getDataKeys } from "./graphChartConstants"; +import { graphChartPropertyView } from "./graphChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../chartComp/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/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "./graphChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let GraphChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...graphChartChildrenMap}, () => null) + .setPropertyViewFn(graphChartPropertyView) + .build(); +})(); + +GraphChartTmpComp = withViewFn(GraphChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +GraphChartTmpComp = class extends GraphChartTmpComp { + 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 GraphChartComp = withExposingConfigs(GraphChartTmpComp, [ + 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 GraphChartCompWithDefault = withDefault(GraphChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); From 388a5b64abd16e675762f7da4dcff8c454f3a029 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:24:22 +0500 Subject: [PATCH 070/118] graph chart rendered --- client/packages/lowcoder-comps/package.json | 8 ++++++++ client/packages/lowcoder-comps/src/index.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index 73e586269..de38f1873 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -112,6 +112,14 @@ "h": 40 } }, + "graphChart": { + "name": "Graph Chart", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, "map": { "name": "Map", "icon": "./icons/icon-chart.svg", diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 76c1f8428..11169667d 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -9,6 +9,7 @@ import { SankeyChartCompWithDefault } from "comps/sankeyChartComp/sankeyChartCom import { CandleStickChartCompWithDefault } from "comps/candleStickChartComp/candleStickChartComp"; import { RadarChartCompWithDefault } from "comps/radarChartComp/radarChartComp"; import { HeatmapChartCompWithDefault } from "comps/heatmapChartComp/heatmapChartComp"; +import { GraphChartCompWithDefault } from "comps/graphChartComp/graphChartComp"; export default { chart: ChartCompWithDefault, @@ -19,6 +20,7 @@ export default { candleStickChart: CandleStickChartCompWithDefault, radarChart: RadarChartCompWithDefault, heatmapChart: HeatmapChartCompWithDefault, + graphChart: GraphChartCompWithDefault, imageEditor: ImageEditorComp, calendar: CalendarComp, mermaid: MermaidComp, From 3c71c2cdedf3c3c70698b0ddebcee5983515490e Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:44:28 +0500 Subject: [PATCH 071/118] tree chart type added --- client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx index e36a8361c..77e70f218 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx @@ -11,6 +11,7 @@ export type I18nObjects = { defaultRadarChartOption: Record; defaultHeatmapChartOption: Record; defaultGraphChartOption: Record; + defaultTreeChartOption: Record; defaultMapJsonOption: Record; timeXAxisLabel?: XAXisComponentOption["axisLabel"]; imageEditorLocale?: Record; From fd8a34f510dcbbd5143522ce328450f36e1cdf85 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:44:40 +0500 Subject: [PATCH 072/118] tree chart data added --- .../src/i18n/comps/locales/enObj.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index 3ce3bacc6..4c81ba7d0 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -278,6 +278,27 @@ export const enObj: I18nObjects = { {source: "Node 2", target: "Node 3", category: 1} ] }, + defaultTreeChartOption: { + data: [{ + name: "Parent", + children: [ + { + name: "Child 1", + children: [ + { name: "Child 1-1" }, + { name: "Child 1-2" } + ] + }, + { + name: "Child 2", + children: [ + { name: "Child 2-1" }, + { name: "Child 2-2" } + ] + } + ] + }] + }, defaultMapJsonOption: defaultMapData, }; From b51a34b320488280c89c82b6cb9cabd1cfd71b9f Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:44:50 +0500 Subject: [PATCH 073/118] tree chart translation added --- client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index 39a8d6d4a..89b2f536d 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -1,4 +1,10 @@ export const en = { + treeChart: { + treeType: 'Tree Chart Type', + title: 'Title', + defaultTitle: 'Tree Chart', + tooltip: 'Tooltip', + }, graphChart: { graphType: 'Graph Chart Type', title: 'Title', From cbf997372b330b7c5b67f5b0e053d49a9dc9ebba Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:45:02 +0500 Subject: [PATCH 074/118] tree chart utils --- .../src/comps/treeChartComp/treeChartUtils.ts | 330 ++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartUtils.ts b/client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartUtils.ts new file mode 100644 index 000000000..c7719fdb7 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartUtils.ts @@ -0,0 +1,330 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props?.style?.background, + "color": props.echartsOption.data?.map(data => data.color), + "tooltip": props.tooltip&&{ + "trigger": "item", + "triggerOn": "mousemove" + }, + "series": [ + { + "name": props.echartsConfig.type, + "type": props.echartsConfig.type, + "top": "10%", + "left": "10%", + "bottom": "10%", + "right": "10%", + "symbolSize": 7, + 'data': props.echartsOption.data, + "label": { + "position": "top", + "verticalAlign": "middle", + "align": "right" + }, + "leaves": { + "label": { + "position": "bottom", + "verticalAlign": "middle", + "align": "left" + } + } + } + ] +} + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From 5b25beade2077721a5d8c52dfa0681c4fd6f646d Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:45:15 +0500 Subject: [PATCH 075/118] tree chart property view --- .../treeChartComp/treeChartPropertyView.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartPropertyView.tsx new file mode 100644 index 000000000..920062913 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartPropertyView.tsx @@ -0,0 +1,54 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./treeChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function treeChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsTitle.propertyView({ label: trans("treeChart.title") })} + {children.tooltip.propertyView({label: trans("treeChart.tooltip")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} From e8b54bd2cc80e91fcf62b1915baaf6c8726ad07e Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:45:24 +0500 Subject: [PATCH 076/118] tree chart constants --- .../treeChartComp/treeChartConstants.tsx | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartConstants.tsx new file mode 100644 index 000000000..573cc03db --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/treeChartComp/treeChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { TreeChartConfig } from "comps/chartComp/chartConfigs/treeChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + tree: TreeChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "tree"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultTreeChartOption), + echartsTitle: withDefault(StringControl, trans("treeChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const treeChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(treeChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; From 96e24dcd86c3d116cbb77033f3181e89c9860ff8 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:45:33 +0500 Subject: [PATCH 077/118] tree chart config --- .../chartConfigs/treeChartConfig.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/treeChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/treeChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/treeChartConfig.tsx new file mode 100644 index 000000000..3f824008d --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/treeChartConfig.tsx @@ -0,0 +1,31 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { TreeSeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const TreeChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): TreeSeriesOption => { + const config: TreeSeriesOption = { + type: "tree", + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("treeChart.treeType"), + radioButton: true, + })} + + )) + .build(); +})(); From 840159076664020fefc53050097478ef6eefb3b2 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:45:43 +0500 Subject: [PATCH 078/118] tree chart component --- .../src/comps/treeChartComp/treechartComp.tsx | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/treeChartComp/treechartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/treeChartComp/treechartComp.tsx b/client/packages/lowcoder-comps/src/comps/treeChartComp/treechartComp.tsx new file mode 100644 index 000000000..91bb602e8 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/treeChartComp/treechartComp.tsx @@ -0,0 +1,318 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { treeChartChildrenMap, ChartSize, getDataKeys } from "./treeChartConstants"; +import { treeChartPropertyView } from "./treeChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../chartComp/reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + NameConfig, + UICompBuilder, + withDefault, + withExposingConfigs, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "./treeChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let TreeChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...treeChartChildrenMap}, () => null) + .setPropertyViewFn(treeChartPropertyView) + .build(); +})(); + +TreeChartTmpComp = withViewFn(TreeChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +TreeChartTmpComp = class extends TreeChartTmpComp { + 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 TreeChartComp = withExposingConfigs(TreeChartTmpComp, [ + 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 TreeChartCompWithDefault = withDefault(TreeChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); From d930685ef182d0963152d1f9dc6483d6846e8f08 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Fri, 3 May 2024 23:45:56 +0500 Subject: [PATCH 079/118] tree chart rendered --- client/packages/lowcoder-comps/package.json | 8 ++++++++ client/packages/lowcoder-comps/src/index.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index de38f1873..41f1548c3 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -120,6 +120,14 @@ "h": 40 } }, + "treeChart": { + "name": "Tree Chart", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, "map": { "name": "Map", "icon": "./icons/icon-chart.svg", diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 11169667d..6d08f2e28 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -10,6 +10,7 @@ import { CandleStickChartCompWithDefault } from "comps/candleStickChartComp/cand import { RadarChartCompWithDefault } from "comps/radarChartComp/radarChartComp"; import { HeatmapChartCompWithDefault } from "comps/heatmapChartComp/heatmapChartComp"; import { GraphChartCompWithDefault } from "comps/graphChartComp/graphChartComp"; +import { TreeChartCompWithDefault } from "comps/treeChartComp/treechartComp"; export default { chart: ChartCompWithDefault, @@ -21,6 +22,7 @@ export default { radarChart: RadarChartCompWithDefault, heatmapChart: HeatmapChartCompWithDefault, graphChart: GraphChartCompWithDefault, + treeChart: TreeChartCompWithDefault, imageEditor: ImageEditorComp, calendar: CalendarComp, mermaid: MermaidComp, From 6d2da937a8ecfd14b12a83ac7f52aa51cb92c74e Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:25:27 +0500 Subject: [PATCH 080/118] treemap chart type added --- client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx index 77e70f218..43f068d52 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx @@ -12,6 +12,7 @@ export type I18nObjects = { defaultHeatmapChartOption: Record; defaultGraphChartOption: Record; defaultTreeChartOption: Record; + defaultTreemapChartOption: Record; defaultMapJsonOption: Record; timeXAxisLabel?: XAXisComponentOption["axisLabel"]; imageEditorLocale?: Record; From 46f8b8d35eb9e91650678633d03042f23daab4b0 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:26:55 +0500 Subject: [PATCH 081/118] treemap chart data added --- .../lowcoder-comps/src/i18n/comps/locales/enObj.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index 4c81ba7d0..fe81082de 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -299,6 +299,17 @@ export const enObj: I18nObjects = { ] }] }, + defaultTreemapChartOption: { + data: [ + { + name: "Parent", + children: [ + {name: "Child 1", value: 10}, + {name: "Child 2", value: 20} + ] + } + ] + }, defaultMapJsonOption: defaultMapData, }; From 45dd772de0e748781f5f0b277cd4768c0be608b2 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:27:06 +0500 Subject: [PATCH 082/118] treemap chart translation added --- client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index 89b2f536d..ee908fcb5 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -1,4 +1,10 @@ export const en = { + treemapChart: { + treemapType: 'Treemap Chart Type', + title: 'Title', + defaultTitle: 'Treemap Chart', + tooltip: 'Tooltip', + }, treeChart: { treeType: 'Tree Chart Type', title: 'Title', From 5dd9125502e5a306e203e2bd4dd469551c055766 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:27:22 +0500 Subject: [PATCH 083/118] treemap chart constants --- .../treemapChartConstants.tsx | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartConstants.tsx new file mode 100644 index 000000000..a5c302b2b --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { TreemapChartConfig } from "comps/chartComp/chartConfigs/treemapChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + treemap: TreemapChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "treemap"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultTreemapChartOption), + echartsTitle: withDefault(StringControl, trans("treemapChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const treemapChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(treemapChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; From 9af7ace1952a6d0265422f40555e74d0e7a3c8d9 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:27:43 +0500 Subject: [PATCH 084/118] treemap chart property view --- .../treemapChartPropertyView.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartPropertyView.tsx new file mode 100644 index 000000000..4d5c543bb --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartPropertyView.tsx @@ -0,0 +1,54 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./treemapChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function treeChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsTitle.propertyView({ label: trans("treemapChart.title") })} + {children.tooltip.propertyView({label: trans("treemapChart.tooltip")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} From 91d551c6fc49fcb2d0bd872db20aa294e0e03ae4 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:28:07 +0500 Subject: [PATCH 085/118] treemap chart config --- .../chartConfigs/treemapChartConfig.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/treemapChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/treemapChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/treemapChartConfig.tsx new file mode 100644 index 000000000..a201337ef --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/treemapChartConfig.tsx @@ -0,0 +1,31 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { TreemapSeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const TreemapChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): TreemapSeriesOption => { + const config: TreemapSeriesOption = { + type: "treemap", + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("treemapChart.treemapType"), + radioButton: true, + })} + + )) + .build(); +})(); From 741b5586c4e0ad45543c3c4b6daa30e1d8a76cba Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:28:15 +0500 Subject: [PATCH 086/118] treemap chart utils --- .../treemapChartComp/treemapChartUtils.ts | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartUtils.ts b/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartUtils.ts new file mode 100644 index 000000000..d84d9faa5 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartUtils.ts @@ -0,0 +1,318 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props?.style?.background, + "color": props.echartsOption.data?.map(data => data.color), + "tooltip": props.tooltip&&{ + "trigger": "item", + "triggerOn": "mousemove" + }, + "series": [ + { + "name": props.echartsConfig.type, + "type": props.echartsConfig.type, + "top": "10%", + "left": "10%", + "bottom": "10%", + "right": "10%", + "symbolSize": 7, + 'data': props.echartsOption.data, + } + ] +} + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From 7d0d9a02f9349a3a05d2ee9133fe1c609ab7e110 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:28:38 +0500 Subject: [PATCH 087/118] treemap chart component --- .../treemapChartComp/treemapChartComp.tsx | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartComp.tsx b/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartComp.tsx new file mode 100644 index 000000000..fa0d6f078 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartComp.tsx @@ -0,0 +1,318 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { treemapChartChildrenMap, ChartSize, getDataKeys } from "./treemapChartConstants"; +import { treeChartPropertyView } from "./treemapChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../chartComp/reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + NameConfig, + UICompBuilder, + withDefault, + withExposingConfigs, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "./treemapChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let TreemapChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...treemapChartChildrenMap}, () => null) + .setPropertyViewFn(treeChartPropertyView) + .build(); +})(); + +TreemapChartTmpComp = withViewFn(TreemapChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +TreemapChartTmpComp = class extends TreemapChartTmpComp { + 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 TreemapChartComp = withExposingConfigs(TreemapChartTmpComp, [ + 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 TreemapChartCompWithDefault = withDefault(TreemapChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); From 772a1ef4b4f5c8f14e6c1bf9104a5741aedc4240 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:30:11 +0500 Subject: [PATCH 088/118] treemap chart rendered --- client/packages/lowcoder-comps/package.json | 8 ++++++++ client/packages/lowcoder-comps/src/index.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index 41f1548c3..8e7ecfb1b 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -128,6 +128,14 @@ "h": 40 } }, + "treemapChart": { + "name": "Treemap Chart", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, "map": { "name": "Map", "icon": "./icons/icon-chart.svg", diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 6d08f2e28..f94adad7f 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -11,6 +11,7 @@ import { RadarChartCompWithDefault } from "comps/radarChartComp/radarChartComp"; import { HeatmapChartCompWithDefault } from "comps/heatmapChartComp/heatmapChartComp"; import { GraphChartCompWithDefault } from "comps/graphChartComp/graphChartComp"; import { TreeChartCompWithDefault } from "comps/treeChartComp/treechartComp"; +import { TreemapChartCompWithDefault } from "comps/treemapChartComp/treemapChartComp"; export default { chart: ChartCompWithDefault, @@ -23,6 +24,7 @@ export default { heatmapChart: HeatmapChartCompWithDefault, graphChart: GraphChartCompWithDefault, treeChart: TreeChartCompWithDefault, + treemapChart: TreemapChartCompWithDefault, imageEditor: ImageEditorComp, calendar: CalendarComp, mermaid: MermaidComp, From 4feac07633e143a859439c65429cfc6ea7a8f34d Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:44:52 +0500 Subject: [PATCH 089/118] sunburst chart type added --- client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx index 43f068d52..1d1986703 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx @@ -13,6 +13,7 @@ export type I18nObjects = { defaultGraphChartOption: Record; defaultTreeChartOption: Record; defaultTreemapChartOption: Record; + defaultSunburstChartOption: Record; defaultMapJsonOption: Record; timeXAxisLabel?: XAXisComponentOption["axisLabel"]; imageEditorLocale?: Record; From 3bd86d74840e0e10c409bfefcb767dd3cac27eeb Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:45:01 +0500 Subject: [PATCH 090/118] sunburst chart data added --- .../src/i18n/comps/locales/enObj.tsx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index fe81082de..adf54456b 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -310,6 +310,29 @@ export const enObj: I18nObjects = { } ] }, + defaultSunburstChartOption: { + data: [ + { + name: "Grandparent", + children: [ + { + name: "Parent A", + children: [ + {name: "Child A1", value: 10}, + {name: "Child A2", value: 20} + ] + }, + { + name: "Parent B", + children: [ + {name: "Child B1", value: 15}, + {name: "Child B2", value: 25} + ] + } + ] + } + ] + }, defaultMapJsonOption: defaultMapData, }; From 7e06a81aeb24ad7fccfb38ac10c8d718b852fbd2 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:45:33 +0500 Subject: [PATCH 091/118] sunburst chart translation added --- client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index ee908fcb5..35e282e58 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -1,4 +1,10 @@ export const en = { + sunburstChart: { + sunburstType: 'Sunburst Chart Type', + title: 'Title', + defaultTitle: 'Sunburst Chart', + tooltip: 'Tooltip', + }, treemapChart: { treemapType: 'Treemap Chart Type', title: 'Title', From 3d3e6f6a911df86f71e6bf6187993c597d789674 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:45:48 +0500 Subject: [PATCH 092/118] sunburst chart config --- .../chartConfigs/sunburstChartConfig.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/sunburstChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/sunburstChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/sunburstChartConfig.tsx new file mode 100644 index 000000000..8306911a3 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/sunburstChartConfig.tsx @@ -0,0 +1,31 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { SunburstSeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const SunburstChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): SunburstSeriesOption => { + const config: SunburstSeriesOption = { + type: "sunburst", + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("sunburstChart.sunburstType"), + radioButton: true, + })} + + )) + .build(); +})(); From 26a09a8623d7314a1f0bc20aa0606656c1f95e17 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:45:57 +0500 Subject: [PATCH 093/118] sunburst chart constants --- .../sunburstChartConstants.tsx | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartConstants.tsx new file mode 100644 index 000000000..c4e7aa539 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { SunburstChartConfig } from "comps/chartComp/chartConfigs/sunburstChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + sunburst: SunburstChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "sunburst"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultSunburstChartOption), + echartsTitle: withDefault(StringControl, trans("sunburstChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const sunburstChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(sunburstChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; From 22a139b1baec342bdf6e46c57c0f4ed0c2fac749 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:46:09 +0500 Subject: [PATCH 094/118] sunburst chart property view --- .../sunburstChartPropertyView.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartPropertyView.tsx new file mode 100644 index 000000000..f93c2562d --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartPropertyView.tsx @@ -0,0 +1,54 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./sunburstChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function sunburstChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsTitle.propertyView({ label: trans("treeChart.title") })} + {children.tooltip.propertyView({label: trans("treeChart.tooltip")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} From 64ec59770f7f6dc837b6c646288bb55518b20f53 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:46:19 +0500 Subject: [PATCH 095/118] sunburst chart utils --- .../sunburstChartComp/sunburstChartUtils.ts | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartUtils.ts b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartUtils.ts new file mode 100644 index 000000000..85bbb9676 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartUtils.ts @@ -0,0 +1,318 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props?.style?.background, + "color": props.echartsOption.data?.map(data => data.color), + "tooltip": props.tooltip&&{ + "trigger": "item", + "formatter": "{b}: {c}" + }, + "series": [ + { + "name": props.echartsConfig.type, + "type": props.echartsConfig.type, + "top": "10%", + "left": "10%", + "bottom": "10%", + "right": "10%", + "symbolSize": 7, + 'data': props.echartsOption.data, + } + ] +} + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From 729dedf7591eed2bb6dd5011a37ee0192776885d Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:46:28 +0500 Subject: [PATCH 096/118] sunburst chart component --- .../sunburstChartComp/sunburstChartComp.tsx | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartComp.tsx b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartComp.tsx new file mode 100644 index 000000000..be9ead730 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartComp.tsx @@ -0,0 +1,318 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { sunburstChartChildrenMap, ChartSize, getDataKeys } from "./sunburstChartConstants"; +import { sunburstChartPropertyView } from "./sunburstChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../chartComp/reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + NameConfig, + UICompBuilder, + withDefault, + withExposingConfigs, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "../treeChartComp/treeChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let TreeChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...sunburstChartChildrenMap}, () => null) + .setPropertyViewFn(sunburstChartPropertyView) + .build(); +})(); + +TreeChartTmpComp = withViewFn(TreeChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +TreeChartTmpComp = class extends TreeChartTmpComp { + 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 TreeChartComp = withExposingConfigs(TreeChartTmpComp, [ + 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 SunburstChartCompWithDefault = withDefault(TreeChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); From 9a880e87c9c5f4c517d6eef00c0ab9ae36efb3ee Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 00:46:38 +0500 Subject: [PATCH 097/118] sunburst chart rendered --- client/packages/lowcoder-comps/package.json | 8 ++++++++ client/packages/lowcoder-comps/src/index.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index 8e7ecfb1b..7750b3ee4 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -136,6 +136,14 @@ "h": 40 } }, + "sunburstChart": { + "name": "Sunburst Chart", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, "map": { "name": "Map", "icon": "./icons/icon-chart.svg", diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index f94adad7f..9ab908e59 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -12,6 +12,7 @@ import { HeatmapChartCompWithDefault } from "comps/heatmapChartComp/heatmapChart import { GraphChartCompWithDefault } from "comps/graphChartComp/graphChartComp"; import { TreeChartCompWithDefault } from "comps/treeChartComp/treechartComp"; import { TreemapChartCompWithDefault } from "comps/treemapChartComp/treemapChartComp"; +import { SunburstChartCompWithDefault } from "comps/sunburstChartComp/sunburstChartComp"; export default { chart: ChartCompWithDefault, @@ -25,6 +26,7 @@ export default { graphChart: GraphChartCompWithDefault, treeChart: TreeChartCompWithDefault, treemapChart: TreemapChartCompWithDefault, + sunburstChart: SunburstChartCompWithDefault, imageEditor: ImageEditorComp, calendar: CalendarComp, mermaid: MermaidComp, From caca39da25b1f3f1a893d09196a1af6a6f0b616c Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 22:02:25 +0500 Subject: [PATCH 098/118] translation updated --- .../src/i18n/comps/locales/en.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index 35e282e58..0544f8b86 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -1,4 +1,10 @@ export const en = { + calendarChart: { + calendarType: 'Calendar Chart Type', + title: 'Title', + defaultTitle: 'Calendar Chart', + tooltip: 'Tooltip', + }, sunburstChart: { sunburstType: 'Sunburst Chart Type', title: 'Title', @@ -52,7 +58,22 @@ export const en = { defaultTitle: 'Funnel Chart', funnelType:'Funnel Chart Type', tooltip: 'Tooltip', - legendVisibility:'Legend Visibility' + legendVisibility: 'Legend Visibility', + left: 'Left', + defaultLeft:'35', + top: 'Top', + defaultTop:'60', + bottom: 'Bottom', + defaultBottom:'60', + width: 'Width', + defaultWidth:'80', + min: 'Min', + defaultMin:'0', + max: 'Max', + defaultMax:'100', + gap: 'Gap', + defaultGap: '2', + label:'Label', }, gaugeChart: { title: 'Title', @@ -63,6 +84,7 @@ export const en = { echarts: { legendPosition: "Legend Position", labelPosition: "Label Position", + titlePosition: "Title Position", }, chart: { delete: "Delete", From 5ac3aa4562280f65bab03fe38dc45f018d840e0c Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 22:02:56 +0500 Subject: [PATCH 099/118] funnel chart utils updated --- .../comps/funnelChartComp/funnelChartUtils.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartUtils.ts b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartUtils.ts index 64a2f9797..2f2b33505 100644 --- a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartUtils.ts @@ -134,7 +134,7 @@ export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSiz "title": { "text": props.echartsTitle, 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', - "left":"center" + "left":props.echartsTitleConfig.top }, "backgroundColor": props?.style?.background, "color": props.echartsOption.data?.map(data => data.color), @@ -150,15 +150,15 @@ export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSiz { "name": props.echartsConfig.type, "type": props.echartsConfig.type, - "left": "10%", - "top": 60, - "bottom": 60, - "width": "80%", - "min": 0, - "max": 100, - "gap": 2, + "left": `${props.left}%`, + "top": props.top, + "bottom": props.bottom, + "width": `${props.left}%`, + "min": props.min, + "max": props.max, + "gap": props.gap, "label": { - "show": true, + "show": props.label, "position": props.echartsLabelConfig.top }, "data": props.echartsOption.data From b96deeb1797099d910ccf6530fc2c50a792139d8 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 22:03:11 +0500 Subject: [PATCH 100/118] funnel chart property view updated --- .../funnelChartComp/funnelChartPropertyView.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartPropertyView.tsx index 0481ccb80..4f90f9ac9 100644 --- a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartPropertyView.tsx @@ -31,10 +31,19 @@ export function funnelChartPropertyView( ), })} - {children.echartsLegendConfig.getPropertyView()} - {children.echartsLabelConfig.getPropertyView()} + {children.legendVisibility.getView()&& children.echartsLegendConfig.getPropertyView()} + {children.label.getView()&& children.echartsLabelConfig.getPropertyView()} + {children.echartsTitleConfig.getPropertyView()} + {children.left.propertyView({ label: trans("funnelChart.left") })} + {children.top.propertyView({ label: trans("funnelChart.top") })} + {children.bottom.propertyView({ label: trans("funnelChart.bottom") })} + {children.width.propertyView({ label: trans("funnelChart.width") })} + {children.min.propertyView({ label: trans("funnelChart.min") })} + {children.max.propertyView({ label: trans("funnelChart.max") })} + {children.gap.propertyView({ label: trans("funnelChart.gap") })} {children.echartsTitle.propertyView({ label: trans("funnelChart.title") })} {children.tooltip.propertyView({label: trans("funnelChart.tooltip")})} + {children.label.propertyView({label: trans("funnelChart.label")})} {children.legendVisibility.propertyView({label: trans("funnelChart.legendVisibility")})}
From 26ec31356094638e1a470b4b7769633443b109bf Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Sun, 5 May 2024 22:03:33 +0500 Subject: [PATCH 101/118] funnel chart constants updated --- .../src/comps/funnelChartComp/funnelChartConstants.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartConstants.tsx index 97e622a28..36e55cf52 100644 --- a/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/funnelChartComp/funnelChartConstants.tsx @@ -31,6 +31,7 @@ import { SeriesListComp } from "../chartComp/seriesComp"; import { EChartsOption } from "echarts"; import { i18nObjs, trans } from "i18n/comps"; import { FunnelChartConfig } from "../chartComp/chartConfigs/funnelChartConfig"; +import { EchartsTitleConfig } from "comps/chartComp/chartConfigs/echartsTitleConfig"; export const ChartTypeOptions = [ { @@ -253,9 +254,18 @@ const chartJsonModeChildren = { echartsLegendConfig: EchartsLegendConfig, echartsLabelConfig: EchartsLabelConfig, echartsConfig: EchartsOptionComp, + echartsTitleConfig:EchartsTitleConfig, style: styleControl(EchartsStyle), tooltip: withDefault(BoolControl, true), + label: withDefault(BoolControl, true), legendVisibility: withDefault(BoolControl, true), + left:withDefault(NumberControl,trans('funnelChart.defaultLeft')), + top:withDefault(NumberControl,trans('funnelChart.defaultTop')), + bottom:withDefault(NumberControl,trans('funnelChart.defaultBottom')), + width:withDefault(NumberControl,trans('funnelChart.defaultWidth')), + min:withDefault(NumberControl,trans('funnelChart.defaultMin')), + max:withDefault(NumberControl,trans('funnelChart.defaultMax')), + gap:withDefault(NumberControl,trans('funnelChart.defaultGap')) } const chartMapModeChildren = { From 3c3806fe790866a8533cd83b31d0d9928746d2f2 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 01:51:20 +0500 Subject: [PATCH 102/118] title config --- .../chartConfigs/echartsTitleConfig.tsx | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsTitleConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsTitleConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsTitleConfig.tsx new file mode 100644 index 000000000..a9305de25 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/echartsTitleConfig.tsx @@ -0,0 +1,49 @@ +import { + AlignClose, + AlignRight, + AlignLeft, + dropdownControl, + MultiCompBuilder, +} from "lowcoder-sdk"; +import { LegendComponentOption } from "echarts"; +import { trans } from "i18n/comps"; + +const TitlePositionOptions = [ + { + label: , + value: "center", + }, + { + label: , + value: "right", + }, + { + label: , + value: "left", + }, +] as const; + +export const EchartsTitleConfig = (function () { + return new MultiCompBuilder( + { + position: dropdownControl(TitlePositionOptions, "center"), + }, + (props): LegendComponentOption => { + const config: LegendComponentOption = { + top: "center", + type: "scroll", + }; + config.top = props.position + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {children.position.propertyView({ + label: trans("echarts.titlePosition"), + radioButton: true, + })} + + )) + .build(); +})(); From d261d3e4610339ae8bfd0373c26a365aa84ab24f Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:01:06 +0500 Subject: [PATCH 103/118] gauge chart utils updated --- .../comps/gaugeChartComp/gaugeChartUtils.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartUtils.ts b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartUtils.ts index 24e072cc1..3327823ce 100644 --- a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartUtils.ts @@ -134,7 +134,7 @@ export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSiz "title": { "text": props.echartsTitle, 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', - "left":"center" + "left":props.echartsTitleConfig.top }, "backgroundColor": props?.style?.background, "color": props.echartsOption.data?.map(data => data.color), @@ -146,15 +146,18 @@ export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSiz { "name": props.echartsConfig.type, "type": props.echartsConfig.type, - "left": "10%", - "top": 60, - "bottom": 60, - "width": "80%", - "min": 0, - "max": 100, - "gap": 2, + "left": `${props.left}%`, + "top": props.top, + "bottom": props.bottom, + "width": `${props.left}%`, + "min": props.min, + "max": props.max, + "gap": props.gap, + 'detail': { + "backgroundColor": props?.style?.background, + }, "label": { - "show": true, + "show": props.label, "position": props.echartsLabelConfig.top }, "detail": { From d33b29cbfb7de2fa4dde20761e29d411576bbc1c Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:01:23 +0500 Subject: [PATCH 104/118] gauge chart property view updated --- .../comps/gaugeChartComp/gaugeChartPropertyView.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartPropertyView.tsx index ac044baf0..b1ac4bad3 100644 --- a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartPropertyView.tsx @@ -31,8 +31,16 @@ export function gaugeChartPropertyView( ), })} + {children.echartsTitleConfig.getPropertyView()} {children.echartsTitle.propertyView({ label: trans("gaugeChart.title") })} - {children.tooltip.propertyView({label: trans("gaugeChart.tooltip")})} + {/* {children.left.propertyView({ label: trans("gaugeChart.left") })} + {children.top.propertyView({ label: trans("gaugeChart.top") })} + {children.bottom.propertyView({ label: trans("gaugeChart.bottom") })} + {children.width.propertyView({ label: trans("gaugeChart.width") })} */} + {children.min.propertyView({ label: trans("gaugeChart.min") })} + {children.max.propertyView({ label: trans("gaugeChart.max") })} + {/* {children.gap.propertyView({ label: trans("gaugeChart.gap") })} */} + {children.tooltip.propertyView({ label: trans("gaugeChart.tooltip") })}
{children.onEvent.propertyView()} From 5352fa2de9ab8c10135ab1df50a8b37210407a9f Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:01:40 +0500 Subject: [PATCH 105/118] gauge chart constants updated --- .../src/comps/gaugeChartComp/gaugeChartConstants.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartConstants.tsx index a0f1d5e2a..5f128faec 100644 --- a/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartConstants.tsx +++ b/client/packages/lowcoder-comps/src/comps/gaugeChartComp/gaugeChartConstants.tsx @@ -31,6 +31,7 @@ import { SeriesListComp } from "../chartComp/seriesComp"; import { EChartsOption } from "echarts"; import { i18nObjs, trans } from "i18n/comps"; import { GaugeChartConfig } from "../chartComp/chartConfigs/gaugeChartConfig"; +import { EchartsTitleConfig } from "comps/chartComp/chartConfigs/echartsTitleConfig"; export const ChartTypeOptions = [ { @@ -253,9 +254,18 @@ const chartJsonModeChildren = { echartsLegendConfig: EchartsLegendConfig, echartsLabelConfig: EchartsLabelConfig, echartsConfig: EchartsOptionComp, + echartsTitleConfig:EchartsTitleConfig, style: styleControl(EchartsStyle), tooltip: withDefault(BoolControl, true), legendVisibility: withDefault(BoolControl, true), + label: withDefault(BoolControl, true), + left:withDefault(NumberControl,trans('gaugeChart.defaultLeft')), + top:withDefault(NumberControl,trans('gaugeChart.defaultTop')), + bottom:withDefault(NumberControl,trans('gaugeChart.defaultBottom')), + width:withDefault(NumberControl,trans('gaugeChart.defaultWidth')), + min:withDefault(NumberControl,trans('gaugeChart.defaultMin')), + max:withDefault(NumberControl,trans('gaugeChart.defaultMax')), + gap:withDefault(NumberControl,trans('gaugeChart.defaultGap')) } const chartMapModeChildren = { From 208bc5e1761b68746e1238ccc90284736c220b3c Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:07:48 +0500 Subject: [PATCH 106/118] variables updated --- .../comps/sunburstChartComp/sunburstChartComp.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartComp.tsx b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartComp.tsx index be9ead730..a136b12b6 100644 --- a/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartComp.tsx +++ b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartComp.tsx @@ -32,7 +32,7 @@ import { echartsConfigOmitChildren, getEchartsConfig, getSelectedPoints, -} from "../treeChartComp/treeChartUtils"; +} from "./sunburstChartUtils"; import 'echarts-extension-gmap'; import log from "loglevel"; @@ -45,13 +45,13 @@ const chartModeOptions = [ } ] as const; -let TreeChartTmpComp = (function () { +let SunburstChartTmpComp = (function () { return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...sunburstChartChildrenMap}, () => null) .setPropertyViewFn(sunburstChartPropertyView) .build(); })(); -TreeChartTmpComp = withViewFn(TreeChartTmpComp, (comp) => { +SunburstChartTmpComp = withViewFn(SunburstChartTmpComp, (comp) => { const mode = comp.children.mode.getView(); const onUIEvent = comp.children.onUIEvent.getView(); const onEvent = comp.children.onEvent.getView(); @@ -199,7 +199,7 @@ function getYAxisFormatContextValue( return contextValue; } -TreeChartTmpComp = class extends TreeChartTmpComp { +SunburstChartTmpComp = class extends SunburstChartTmpComp { private lastYAxisFormatContextVal?: JSONValue; private lastColorContext?: JSONObject; @@ -274,7 +274,7 @@ TreeChartTmpComp = class extends TreeChartTmpComp { } }; -let TreeChartComp = withExposingConfigs(TreeChartTmpComp, [ +let SunburstChartComp = withExposingConfigs(SunburstChartTmpComp, [ depsConfig({ name: "selectedPoints", desc: trans("chart.selectedPointsDesc"), @@ -301,7 +301,7 @@ let TreeChartComp = withExposingConfigs(TreeChartTmpComp, [ ]); -export const SunburstChartCompWithDefault = withDefault(TreeChartComp, { +export const SunburstChartCompWithDefault = withDefault(SunburstChartComp, { xAxisKey: "date", series: [ { From e2afd0fa43e51c0d90e5445ea1a114bc43d161d8 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:38:45 +0500 Subject: [PATCH 107/118] themeriver type added --- client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx index 1d1986703..4f227e3bc 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/types.tsx @@ -14,6 +14,8 @@ export type I18nObjects = { defaultTreeChartOption: Record; defaultTreemapChartOption: Record; defaultSunburstChartOption: Record; + defaultCalendarChartOption: Record; + defaultThemeriverChartOption: Record; defaultMapJsonOption: Record; timeXAxisLabel?: XAXisComponentOption["axisLabel"]; imageEditorLocale?: Record; From fe6325b63e5a8268f7ff163b3ac55f558c2b0eda Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:39:00 +0500 Subject: [PATCH 108/118] themeriver translations added --- .../src/i18n/comps/locales/en.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts index 0544f8b86..c5b3a8f3b 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/en.ts @@ -5,6 +5,12 @@ export const en = { defaultTitle: 'Calendar Chart', tooltip: 'Tooltip', }, + themeriverChart: { + themeriverType: 'Themeriver Chart Type', + title: 'Title', + defaultTitle: 'Themeriver Chart', + tooltip: 'Tooltip', + }, sunburstChart: { sunburstType: 'Sunburst Chart Type', title: 'Title', @@ -80,6 +86,21 @@ export const en = { defaultTitle: 'Gauge Chart', gaugeType: 'Gauge Chart Type', tooltip: 'Tooltip', + left: 'Left', + defaultLeft:'35', + top: 'Top', + defaultTop:'60', + bottom: 'Bottom', + defaultBottom:'60', + width: 'Width', + defaultWidth:'80', + min: 'Min', + defaultMin:'0', + max: 'Max', + defaultMax:'100', + gap: 'Gap', + defaultGap: '2', + label:'Label', }, echarts: { legendPosition: "Legend Position", From 6ce567ef19b6d39bc5bc908e2f97925e28f61b21 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:39:22 +0500 Subject: [PATCH 109/118] themeriver data added --- .../src/i18n/comps/locales/enObj.tsx | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index adf54456b..f48377d12 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -333,6 +333,45 @@ export const enObj: I18nObjects = { } ] }, + defaultCalendarChartOption: { + data:[ + ["2022-01-01", 10], + ["2022-02-05", 30], + ["2022-03-15", 50], + ["2022-04-20", 70], + ["2022-05-25", 90], + ["2022-06-30", 100], + ["2022-07-10", 80], + ["2022-08-20", 60], + ["2022-09-25", 40], + ["2022-10-30", 20], + ["2022-11-05", 5] + ] + }, + defaultThemeriverChartOption: { + data: [ + ["2024-01-01", 10, "Category A"], + ["2024-01-02", 15, "Category A"], + ["2024-01-03", 20, "Category A"], + ["2024-01-04", 25, "Category A"], + ["2024-01-05", 30, "Category A"], + ["2024-01-06", 35, "Category A"], + ["2024-01-07", 40, "Category A"], + ["2024-01-08", 45, "Category A"], + ["2024-01-09", 50, "Category A"], + ["2024-01-10", 55, "Category A"], + ["2024-01-01", 15, "Category B"], + ["2024-01-02", 20, "Category B"], + ["2024-01-03", 25, "Category B"], + ["2024-01-04", 30, "Category B"], + ["2024-01-05", 35, "Category B"], + ["2024-01-06", 40, "Category B"], + ["2024-01-07", 45, "Category B"], + ["2024-01-08", 50, "Category B"], + ["2024-01-09", 55, "Category B"], + ["2024-01-10", 60, "Category B"] + ] + }, defaultMapJsonOption: defaultMapData, }; From fe2000b939e751ed0d14c61a64d5e48f6508856e Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:39:45 +0500 Subject: [PATCH 110/118] themeriver chart utils --- .../themeriverChartUtils.ts | 345 ++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartUtils.ts diff --git a/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartUtils.ts b/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartUtils.ts new file mode 100644 index 000000000..5d9195e3c --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartUtils.ts @@ -0,0 +1,345 @@ +import { + CharOptionCompType, + ChartCompPropsType, + ChartSize, + noDataAxisConfig, + noDataPieChartConfig, +} from "comps/chartComp/chartConstants"; +import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig"; +import { EChartsOptionWithMap } from "../chartComp/reactEcharts/types"; +import _ from "lodash"; +import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk"; +import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig"; +import Big from "big.js"; +import { googleMapsApiUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function transformData( + originData: JSONObject[], + xAxis: string, + seriesColumnNames: string[] +) { + // aggregate data by x-axis + const transformedData: JSONObject[] = []; + originData.reduce((prev, cur) => { + if (cur === null || cur === undefined) { + return prev; + } + const groupValue = cur[xAxis] as string; + if (!prev[groupValue]) { + // init as 0 + const initValue: any = {}; + seriesColumnNames.forEach((name) => { + initValue[name] = 0; + }); + prev[groupValue] = initValue; + transformedData.push(prev[groupValue]); + } + // remain the x-axis data + prev[groupValue][xAxis] = groupValue; + seriesColumnNames.forEach((key) => { + if (key === xAxis) { + return; + } else if (isNumeric(cur[key])) { + const bigNum = Big(cur[key]); + prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber(); + } else { + prev[groupValue][key] += 1; + } + }); + return prev; + }, {} as any); + return transformedData; +} + +const notAxisChartSet: Set = new Set(["pie"] as const); +export const echartsConfigOmitChildren = [ + "hidden", + "selectedPoints", + "onUIEvent", + "mapInstance" +] as const; +type EchartsConfigProps = Omit; + +export function isAxisChart(type: CharOptionCompType) { + return !notAxisChartSet.has(type); +} + +export function getSeriesConfig(props: EchartsConfigProps) { + const visibleSeries = props.series.filter((s) => !s.getView().hide); + const seriesLength = visibleSeries.length; + return visibleSeries.map((s, index) => { + if (isAxisChart(props.chartConfig.type)) { + let encodeX: string, encodeY: string; + const horizontalX = props.xAxisDirection === "horizontal"; + let itemStyle = props.chartConfig.itemStyle; + // FIXME: need refactor... chartConfig returns a function with paramters + if (props.chartConfig.type === "bar") { + // barChart's border radius, depend on x-axis direction and stack state + const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0]; + if (props.chartConfig.stack && index === visibleSeries.length - 1) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } else if (!props.chartConfig.stack) { + itemStyle = { ...itemStyle, borderRadius: borderRadius }; + } + } + if (horizontalX) { + encodeX = props.xAxisKey; + encodeY = s.getView().columnName; + } else { + encodeX = s.getView().columnName; + encodeY = props.xAxisKey; + } + return { + name: s.getView().seriesName, + selectedMode: "single", + select: { + itemStyle: { + borderColor: "#000", + }, + }, + encode: { + x: encodeX, + y: encodeY, + }, + // each type of chart's config + ...props.chartConfig, + itemStyle: itemStyle, + label: { + ...props.chartConfig.label, + ...(!horizontalX && { position: "outside" }), + }, + }; + } else { + // pie + const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig); + return { + ...props.chartConfig, + radius: radiusAndCenter.radius, + center: radiusAndCenter.center, + name: s.getView().seriesName, + selectedMode: "single", + encode: { + itemName: props.xAxisKey, + value: s.getView().columnName, + }, + }; + } + }); +} + +// https://echarts.apache.org/en/option.html +export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap { + if (props.mode === "json") { + let opt={ + "title": { + "text": props.echartsTitle, + 'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom', + "left":"center" + }, + "backgroundColor": props.style.background, + "tooltip": props.tooltip&&{ + "trigger": "axis", + "axisPointer": { + "type": "line", + "lineStyle": { + "color": "rgba(0,0,0,0.2)", + "width": 2, + "type": "solid" + } + } + }, + "singleAxis": { + "type": "time", + "bottom": 50, + "axisTick": {}, + "axisLabel": {}, + "splitLine": {}, + "axisPointer": { + "animation": true, + "label": { + "show": true, + "color": "#fff" + } + }, + "splitNumber": 30 + }, + "series": [ + { + "type": props.echartsConfig.type, + "data": props.echartsOption.data, + "label": { + "show": true, + "position": "top", + "fontSize": 12 + }, + "emphasis": { + "itemStyle": { + "shadowBlur": 20, + "shadowColor": "rgba(0, 0, 0, 0.8)" + } + } + } + ] +} + + return props.echartsOption ? opt : {}; + + } + + if(props.mode === "map") { + const { + mapZoomLevel, + mapCenterLat, + mapCenterLng, + mapOptions, + showCharts, + } = props; + + const echartsOption = mapOptions && showCharts ? mapOptions : {}; + return { + gmap: { + center: [mapCenterLng, mapCenterLat], + zoom: mapZoomLevel, + renderOnMoving: true, + echartsLayerZIndex: showCharts ? 2019 : 0, + roam: true + }, + ...echartsOption, + } + } + // axisChart + const axisChart = isAxisChart(props.chartConfig.type); + const gridPos = { + left: 20, + right: props.legendConfig.left === "right" ? "10%" : 20, + top: 50, + bottom: 35, + }; + let config: EChartsOptionWithMap = { + title: { text: props.title, left: "center" }, + tooltip: { + confine: true, + trigger: axisChart ? "axis" : "item", + }, + legend: props.legendConfig, + grid: { + ...gridPos, + containLabel: true, + }, + }; + if (props.data.length <= 0) { + // no data + return { + ...config, + ...(axisChart ? noDataAxisConfig : noDataPieChartConfig), + }; + } + const yAxisConfig = props.yConfig(); + const seriesColumnNames = props.series + .filter((s) => !s.getView().hide) + .map((s) => s.getView().columnName); + // y-axis is category and time, data doesn't need to aggregate + const transformedData = + yAxisConfig.type === "category" || yAxisConfig.type === "time" + ? props.data + : transformData(props.data, props.xAxisKey, seriesColumnNames); + config = { + ...config, + dataset: [ + { + source: transformedData, + sourceHeader: false, + }, + ], + series: getSeriesConfig(props), + }; + if (axisChart) { + // pure chart's size except the margin around + let chartRealSize; + if (chartSize) { + const rightSize = + typeof gridPos.right === "number" + ? gridPos.right + : (chartSize.w * parseFloat(gridPos.right)) / 100.0; + chartRealSize = { + // actually it's self-adaptive with the x-axis label on the left, not that accurate but work + w: chartSize.w - gridPos.left - rightSize, + // also self-adaptive on the bottom + h: chartSize.h - gridPos.top - gridPos.bottom, + right: rightSize, + }; + } + const finalXyConfig = calcXYConfig( + props.xConfig, + yAxisConfig, + props.xAxisDirection, + transformedData.map((d) => d[props.xAxisKey]), + chartRealSize + ); + config = { + ...config, + // @ts-ignore + xAxis: finalXyConfig.xConfig, + // @ts-ignore + yAxis: finalXyConfig.yConfig, + }; + } + // log.log("Echarts transformedData and config", transformedData, config); + return config; +} + +export function getSelectedPoints(param: any, option: any) { + const series = option.series; + const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source; + if (series && dataSource) { + return param.selected.flatMap((selectInfo: any) => { + const seriesInfo = series[selectInfo.seriesIndex]; + if (!seriesInfo || !seriesInfo.encode) { + return []; + } + return selectInfo.dataIndex.map((index: any) => { + const commonResult = { + seriesName: seriesInfo.name, + }; + if (seriesInfo.encode.itemName && seriesInfo.encode.value) { + return { + ...commonResult, + itemName: dataSource[index][seriesInfo.encode.itemName], + value: dataSource[index][seriesInfo.encode.value], + }; + } else { + return { + ...commonResult, + x: dataSource[index][seriesInfo.encode.x], + y: dataSource[index][seriesInfo.encode.y], + }; + } + }); + }); + } + return []; +} + +export function loadGoogleMapsScript(apiKey: string) { + const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`; + const scripts = document.getElementsByTagName('script'); + // is script already loaded + let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl)); + if(scriptIndex > -1) { + return scripts[scriptIndex]; + } + // is script loaded with diff api_key, remove the script and load again + scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl)); + if(scriptIndex > -1) { + scripts[scriptIndex].remove(); + } + + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = mapsUrl; + script.async = true; + script.defer = true; + window.document.body.appendChild(script); + + return script; +} From 53d007ed30fdd972b625bcdfff4c78cc9435d850 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:39:58 +0500 Subject: [PATCH 111/118] themeriver chart property view --- .../themeriverChartPropertyView.tsx | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartPropertyView.tsx diff --git a/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartPropertyView.tsx new file mode 100644 index 000000000..529dbcc57 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartPropertyView.tsx @@ -0,0 +1,54 @@ +import { CompAction } from "lowcoder-core"; +import { ChartCompChildrenType } from "./themeriverChartConstants"; +import { + hiddenPropertyView, + Section, + sectionNames, +} from "lowcoder-sdk"; +import { trans } from "i18n/comps"; +import { examplesUrl,optionUrl } from "../chartComp/chartConfigs/chartUrls"; + +export function themeriverChartPropertyView( + children: ChartCompChildrenType, + dispatch: (action: CompAction) => void +) { + + const jsonModePropertyView = ( + <> +
+ {children.echartsOption.propertyView({ + label: trans("chart.echartsOptionLabel"), + styleName: "higher", + tooltip: ( + + ), + })} + {children.echartsTitle.propertyView({ label: trans("themeriverChart.title") })} + {children.tooltip.propertyView({label: trans("themeriverChart.tooltip")})} +
+
+ {children.onEvent.propertyView()} +
+
+ {children.style.getPropertyView()} +
+
{hiddenPropertyView(children)}
+ + ); + + const getChatConfigByMode = (mode: string) => { + switch(mode) { + case "json": + return jsonModePropertyView; + } + } + return getChatConfigByMode(children.mode.getView()) +} From 045a90f7d8caaa97a05ddb3dfd0c044843171684 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:40:23 +0500 Subject: [PATCH 112/118] themeriver chart constants --- .../themeriverChartConstants.tsx | 299 ++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartConstants.tsx diff --git a/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartConstants.tsx b/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartConstants.tsx new file mode 100644 index 000000000..589634148 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartConstants.tsx @@ -0,0 +1,299 @@ +import { + jsonControl, + JSONObject, + stateComp, + toJSONObjectArray, + toObject, + BoolControl, + withDefault, + StringControl, + NumberControl, + FunctionControl, + dropdownControl, + eventHandlerControl, + valueComp, + withType, + uiChildren, + clickEvent, + styleControl, + EchartsStyle +} from "lowcoder-sdk"; +import { RecordConstructorToComp, RecordConstructorToView } from "lowcoder-core"; +import { BarChartConfig } from "../chartComp/chartConfigs/barChartConfig"; +import { XAxisConfig, YAxisConfig } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { LegendConfig } from "../chartComp/chartConfigs/legendConfig"; +import { EchartsLegendConfig } from "../chartComp/chartConfigs/echartsLegendConfig"; +import { EchartsLabelConfig } from "../chartComp/chartConfigs/echartsLabelConfig"; +import { LineChartConfig } from "../chartComp/chartConfigs/lineChartConfig"; +import { PieChartConfig } from "../chartComp/chartConfigs/pieChartConfig"; +import { ScatterChartConfig } from "../chartComp/chartConfigs/scatterChartConfig"; +import { SeriesListComp } from "../chartComp/seriesComp"; +import { EChartsOption } from "echarts"; +import { i18nObjs, trans } from "i18n/comps"; +import { ThemeriverChartConfig } from "comps/chartComp/chartConfigs/themeriverChartConfig"; + +export const ChartTypeOptions = [ + { + label: trans("chart.bar"), + value: "bar", + }, + { + label: trans("chart.line"), + value: "line", + }, + { + label: trans("chart.scatter"), + value: "scatter", + }, + { + label: trans("chart.pie"), + value: "pie", + }, +] as const; + +export const UIEventOptions = [ + { + label: trans("chart.select"), + value: "select", + description: trans("chart.selectDesc"), + }, + { + label: trans("chart.unSelect"), + value: "unselect", + description: trans("chart.unselectDesc"), + }, +] as const; + +export const MapEventOptions = [ + { + label: trans("chart.mapReady"), + value: "mapReady", + description: trans("chart.mapReadyDesc"), + }, + { + label: trans("chart.zoomLevelChange"), + value: "zoomLevelChange", + description: trans("chart.zoomLevelChangeDesc"), + }, + { + label: trans("chart.centerPositionChange"), + value: "centerPositionChange", + description: trans("chart.centerPositionChangeDesc"), + }, +] as const; + +export const XAxisDirectionOptions = [ + { + label: trans("chart.horizontal"), + value: "horizontal", + }, + { + label: trans("chart.vertical"), + value: "vertical", + }, +] as const; + +export type XAxisDirectionType = ValueFromOption; + +export const noDataAxisConfig = { + animation: false, + xAxis: { + type: "category", + name: trans("chart.noData"), + nameLocation: "middle", + data: [], + axisLine: { + lineStyle: { + color: "#8B8FA3", + }, + }, + }, + yAxis: { + type: "value", + axisLabel: { + color: "#8B8FA3", + }, + splitLine: { + lineStyle: { + color: "#F0F0F0", + }, + }, + }, + tooltip: { + show: false, + }, + series: [ + { + data: [700], + type: "line", + itemStyle: { + opacity: 0, + }, + }, + ], +} as EChartsOption; + +export const noDataPieChartConfig = { + animation: false, + tooltip: { + show: false, + }, + legend: { + formatter: trans("chart.unknown"), + top: "bottom", + selectedMode: false, + }, + color: ["#B8BBCC", "#CED0D9", "#DCDEE6", "#E6E6EB"], + series: [ + { + type: "pie", + radius: "35%", + center: ["25%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + { + type: "pie", + radius: "35%", + center: ["75%", "50%"], + silent: true, + label: { + show: false, + }, + data: [ + { + name: "1", + value: 70, + }, + { + name: "2", + value: 68, + }, + { + name: "3", + value: 48, + }, + { + name: "4", + value: 40, + }, + ], + }, + ], +} as EChartsOption; + +export type ChartSize = { w: number; h: number }; + +export const getDataKeys = (data: Array) => { + if (!data) { + return []; + } + const dataKeys: Array = []; + data.slice(0, 50).forEach((d) => { + Object.keys(d).forEach((key) => { + if (!dataKeys.includes(key)) { + dataKeys.push(key); + } + }); + }); + return dataKeys; +}; + +const ChartOptionMap = { + bar: BarChartConfig, + line: LineChartConfig, + pie: PieChartConfig, + scatter: ScatterChartConfig, +}; + +const EchartsOptionMap = { + themeriver: ThemeriverChartConfig, +}; + +const ChartOptionComp = withType(ChartOptionMap, "bar"); +const EchartsOptionComp = withType(EchartsOptionMap, "themeriver"); +export type CharOptionCompType = keyof typeof ChartOptionMap; + +export const chartUiModeChildren = { + title: StringControl, + data: jsonControl(toJSONObjectArray, i18nObjs.defaultDataSource), + xAxisKey: valueComp(""), // x-axis, key from data + xAxisDirection: dropdownControl(XAxisDirectionOptions, "horizontal"), + series: SeriesListComp, + xConfig: XAxisConfig, + yConfig: YAxisConfig, + legendConfig: LegendConfig, + chartConfig: ChartOptionComp, + onUIEvent: eventHandlerControl(UIEventOptions), +}; + +const chartJsonModeChildren = { + echartsOption: jsonControl(toObject, i18nObjs.defaultThemeriverChartOption), + echartsTitle: withDefault(StringControl, trans("themeriverChart.defaultTitle")), + echartsLegendConfig: EchartsLegendConfig, + echartsLabelConfig: EchartsLabelConfig, + echartsConfig: EchartsOptionComp, + style: styleControl(EchartsStyle), + tooltip: withDefault(BoolControl, true), + legendVisibility: withDefault(BoolControl, true), +} + +const chartMapModeChildren = { + mapInstance: stateComp(), + getMapInstance: FunctionControl, + mapApiKey: withDefault(StringControl, ''), + mapZoomLevel: withDefault(NumberControl, 3), + mapCenterLng: withDefault(NumberControl, 15.932644), + mapCenterLat: withDefault(NumberControl, 50.942063), + mapOptions: jsonControl(toObject, i18nObjs.defaultMapJsonOption), + onMapEvent: eventHandlerControl(MapEventOptions), + showCharts: withDefault(BoolControl, true), +} + +export type UIChartDataType = { + seriesName: string; + // coordinate chart + x?: any; + y?: any; + // pie or funnel + itemName?: any; + value?: any; +}; + +export type NonUIChartDataType = { + name: string; + value: any; +} + +export const themeriverChartChildrenMap = { + selectedPoints: stateComp>([]), + lastInteractionData: stateComp | NonUIChartDataType>({}), + onEvent: eventHandlerControl([clickEvent] as const), + ...chartUiModeChildren, + ...chartJsonModeChildren, + ...chartMapModeChildren, +}; + +const chartUiChildrenMap = uiChildren(themeriverChartChildrenMap); +export type ChartCompPropsType = RecordConstructorToView; +export type ChartCompChildrenType = RecordConstructorToComp; From d16656a45951af748c122493c72b503594c85709 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:40:42 +0500 Subject: [PATCH 113/118] themeriver chart component --- .../themeriverChartComp.tsx | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartComp.tsx diff --git a/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartComp.tsx b/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartComp.tsx new file mode 100644 index 000000000..225c65cc6 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/themeriverChartComp/themeriverChartComp.tsx @@ -0,0 +1,318 @@ +import { + changeChildAction, + changeValueAction, + CompAction, + CompActionTypes, + wrapChildAction, +} from "lowcoder-core"; +import { AxisFormatterComp, EchartsAxisType } from "../chartComp/chartConfigs/cartesianAxisConfig"; +import { themeriverChartChildrenMap, ChartSize, getDataKeys } from "./themeriverChartConstants"; +import { themeriverChartPropertyView } from "./themeriverChartPropertyView"; +import _ from "lodash"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import ReactResizeDetector from "react-resize-detector"; +import ReactECharts from "../chartComp/reactEcharts"; +import { + childrenToProps, + depsConfig, + genRandomKey, + NameConfig, + UICompBuilder, + withDefault, + withExposingConfigs, + withViewFn, + ThemeContext, + chartColorPalette, + getPromiseAfterDispatch, + dropdownControl +} from "lowcoder-sdk"; +import { getEchartsLocale, trans } from "i18n/comps"; +import { ItemColorComp } from "comps/chartComp/chartConfigs/lineChartConfig"; +import { + echartsConfigOmitChildren, + getEchartsConfig, + getSelectedPoints, +} from "./themeriverChartUtils"; +import 'echarts-extension-gmap'; +import log from "loglevel"; + +let clickEventCallback = () => {}; + +const chartModeOptions = [ + { + label: "ECharts JSON", + value: "json", + } +] as const; + +let ThemeriverChartTmpComp = (function () { + return new UICompBuilder({mode:dropdownControl(chartModeOptions,'json'),...themeriverChartChildrenMap}, () => null) + .setPropertyViewFn(themeriverChartPropertyView) + .build(); +})(); + +ThemeriverChartTmpComp = withViewFn(ThemeriverChartTmpComp, (comp) => { + const mode = comp.children.mode.getView(); + const onUIEvent = comp.children.onUIEvent.getView(); + const onEvent = comp.children.onEvent.getView(); + const echartsCompRef = useRef(); + const [chartSize, setChartSize] = useState(); + 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) => { + 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 option = useMemo(() => { + return getEchartsConfig( + childrenToProps(echartsConfigChildren) as ToViewReturn, + chartSize + ); + }, [chartSize, ...Object.values(echartsConfigChildren)]); + + useEffect(() => { + comp.children.mapInstance.dispatch(changeValueAction(null, false)) + if(comp.children.mapInstance.value) return; + }, [option]) + + return ( + { + 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; + } + }} + > + (echartsCompRef.current = e)} + style={{ height: "100%" }} + notMerge + lazyUpdate + opts={{ locale: getEchartsLocale() }} + option={option} + theme={mode !== 'map' ? themeConfig : undefined} + mode={mode} + /> + + ); +}); + +function getYAxisFormatContextValue( + data: Array, + 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; +} + +ThemeriverChartTmpComp = class extends ThemeriverChartTmpComp { + 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 ThemeriverChartComp = withExposingConfigs(ThemeriverChartTmpComp, [ + 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 ThemeriverChartCompWithDefault = withDefault(ThemeriverChartComp, { + xAxisKey: "date", + series: [ + { + dataIndex: genRandomKey(), + seriesName: trans("chart.spending"), + columnName: "spending", + }, + { + dataIndex: genRandomKey(), + seriesName: trans("chart.budget"), + columnName: "budget", + }, + ], +}); From af7f08da789bd8608c78f26ecb844f5dc53b3e20 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:41:36 +0500 Subject: [PATCH 114/118] themeriver chart rendered --- client/packages/lowcoder-comps/package.json | 8 ++++++++ client/packages/lowcoder-comps/src/index.ts | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/client/packages/lowcoder-comps/package.json b/client/packages/lowcoder-comps/package.json index 7750b3ee4..a77e38666 100644 --- a/client/packages/lowcoder-comps/package.json +++ b/client/packages/lowcoder-comps/package.json @@ -144,6 +144,14 @@ "h": 40 } }, + "themeriverChart": { + "name": "Themeriver Chart", + "icon": "./icons/icon-chart.svg", + "layoutInfo": { + "w": 15, + "h": 40 + } + }, "map": { "name": "Map", "icon": "./icons/icon-chart.svg", diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 9ab908e59..0f8422701 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -13,6 +13,8 @@ import { GraphChartCompWithDefault } from "comps/graphChartComp/graphChartComp"; import { TreeChartCompWithDefault } from "comps/treeChartComp/treechartComp"; import { TreemapChartCompWithDefault } from "comps/treemapChartComp/treemapChartComp"; import { SunburstChartCompWithDefault } from "comps/sunburstChartComp/sunburstChartComp"; +import { CalendarChartCompWithDefault } from "comps/calendarChartComp/chlendarChartComp"; +import { ThemeriverChartCompWithDefault } from "comps/themeriverChartComp/themeriverChartComp"; export default { chart: ChartCompWithDefault, @@ -27,7 +29,9 @@ export default { treeChart: TreeChartCompWithDefault, treemapChart: TreemapChartCompWithDefault, sunburstChart: SunburstChartCompWithDefault, + calendarChart: CalendarChartCompWithDefault, + themeriverChart: ThemeriverChartCompWithDefault, imageEditor: ImageEditorComp, - calendar: CalendarComp, + // calendar: CalendarComp, mermaid: MermaidComp, }; From 9b5c600630a2ef90bed0596d5d45f85dc5e36b67 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:41:46 +0500 Subject: [PATCH 115/118] themeriver chart config --- .../chartConfigs/themeriverChartConfig.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/themeriverChartConfig.tsx diff --git a/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/themeriverChartConfig.tsx b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/themeriverChartConfig.tsx new file mode 100644 index 000000000..ae639eb22 --- /dev/null +++ b/client/packages/lowcoder-comps/src/comps/chartComp/chartConfigs/themeriverChartConfig.tsx @@ -0,0 +1,31 @@ +import { + BoolControl, + MultiCompBuilder, + showLabelPropertyView, +} from "lowcoder-sdk"; +import { ThemeRiverSeriesOption } from "echarts"; +import { trans } from "i18n/comps"; + +export const ThemeriverChartConfig = (function () { + return new MultiCompBuilder( + { + showLabel: BoolControl, + }, + (props): ThemeRiverSeriesOption => { + const config: ThemeRiverSeriesOption = { + type: "themeRiver", + }; + return config; + } + ) + .setPropertyViewFn((children) => ( + <> + {showLabelPropertyView(children)} + {children.type.propertyView({ + label: trans("themeriverChart.themeriverType"), + radioButton: true, + })} + + )) + .build(); +})(); From 4912e3fbb66cb6d8b0d1cb57ed498afc92b81ee3 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:42:05 +0500 Subject: [PATCH 116/118] variables updated --- .../src/comps/sunburstChartComp/sunburstChartPropertyView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartPropertyView.tsx b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartPropertyView.tsx index f93c2562d..c8e631be8 100644 --- a/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartPropertyView.tsx +++ b/client/packages/lowcoder-comps/src/comps/sunburstChartComp/sunburstChartPropertyView.tsx @@ -31,8 +31,8 @@ export function sunburstChartPropertyView( ), })} - {children.echartsTitle.propertyView({ label: trans("treeChart.title") })} - {children.tooltip.propertyView({label: trans("treeChart.tooltip")})} + {children.echartsTitle.propertyView({ label: trans("sunburstChart.title") })} + {children.tooltip.propertyView({label: trans("sunburstChart.tooltip")})}
{children.onEvent.propertyView()} From e07179099a502b762d1a6bcc76056138083eb7ce Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 03:44:56 +0500 Subject: [PATCH 117/118] unused code removed --- client/packages/lowcoder-comps/src/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/packages/lowcoder-comps/src/index.ts b/client/packages/lowcoder-comps/src/index.ts index 0f8422701..f58c68328 100644 --- a/client/packages/lowcoder-comps/src/index.ts +++ b/client/packages/lowcoder-comps/src/index.ts @@ -13,7 +13,6 @@ import { GraphChartCompWithDefault } from "comps/graphChartComp/graphChartComp"; import { TreeChartCompWithDefault } from "comps/treeChartComp/treechartComp"; import { TreemapChartCompWithDefault } from "comps/treemapChartComp/treemapChartComp"; import { SunburstChartCompWithDefault } from "comps/sunburstChartComp/sunburstChartComp"; -import { CalendarChartCompWithDefault } from "comps/calendarChartComp/chlendarChartComp"; import { ThemeriverChartCompWithDefault } from "comps/themeriverChartComp/themeriverChartComp"; export default { @@ -29,9 +28,8 @@ export default { treeChart: TreeChartCompWithDefault, treemapChart: TreemapChartCompWithDefault, sunburstChart: SunburstChartCompWithDefault, - calendarChart: CalendarChartCompWithDefault, themeriverChart: ThemeriverChartCompWithDefault, imageEditor: ImageEditorComp, - // calendar: CalendarComp, + calendar: CalendarComp, mermaid: MermaidComp, }; From 913401d80184949d63a9eae0f3bce92c10e4ccc2 Mon Sep 17 00:00:00 2001 From: MenaamAfzal Date: Mon, 6 May 2024 04:19:18 +0500 Subject: [PATCH 118/118] treemap data color updated --- .../treemapChartComp/treemapChartUtils.ts | 17 ++++++----- .../src/i18n/comps/locales/enObj.tsx | 29 +++++++++++++++++-- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartUtils.ts b/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartUtils.ts index d84d9faa5..81f033597 100644 --- a/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartUtils.ts +++ b/client/packages/lowcoder-comps/src/comps/treemapChartComp/treemapChartUtils.ts @@ -137,24 +137,25 @@ export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSiz "left":"center" }, "backgroundColor": props?.style?.background, - "color": props.echartsOption.data?.map(data => data.color), + "color": [], "tooltip": props.tooltip&&{ "trigger": "item", - "triggerOn": "mousemove" + "formatter": "{b}: {c}", + "axisPointer": { + "type": "shadow" + }, }, "series": [ { "name": props.echartsConfig.type, "type": props.echartsConfig.type, - "top": "10%", - "left": "10%", - "bottom": "10%", - "right": "10%", - "symbolSize": 7, 'data': props.echartsOption.data, + "breadcrumb": { + "show": true + } } ] -} + } return props.echartsOption ? opt : {}; } diff --git a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx index f48377d12..2e1b9b874 100644 --- a/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx +++ b/client/packages/lowcoder-comps/src/i18n/comps/locales/enObj.tsx @@ -302,10 +302,33 @@ export const enObj: I18nObjects = { defaultTreemapChartOption: { data: [ { - name: "Parent", + name: 'nodeA', + value: 10, children: [ - {name: "Child 1", value: 10}, - {name: "Child 2", value: 20} + { + name: 'nodeAa', + value: 4, + }, + { + name: 'nodeAb', + value: 6 + } + ] + }, + { + name: 'nodeB', + value: 20, + children: [ + { + name: 'nodeBa', + value: 20, + children: [ + { + name: 'nodeBa1', + value: 20 + } + ] + } ] } ]