Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b334ffc

Browse files
committed
candlestick chart utils
1 parent 22f9550 commit b334ffc

File tree

1 file changed

+340
-0
lines changed

1 file changed

+340
-0
lines changed
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
import {
2+
CharOptionCompType,
3+
ChartCompPropsType,
4+
ChartSize,
5+
noDataAxisConfig,
6+
noDataPieChartConfig,
7+
} from "comps/chartComp/chartConstants";
8+
import { getPieRadiusAndCenter } from "comps/chartComp/chartConfigs/pieChartConfig";
9+
import { EChartsOptionWithMap } from "../reactEcharts/types";
10+
import _ from "lodash";
11+
import { chartColorPalette, isNumeric, JSONObject, loadScript } from "lowcoder-sdk";
12+
import { calcXYConfig } from "comps/chartComp/chartConfigs/cartesianAxisConfig";
13+
import Big from "big.js";
14+
import { googleMapsApiUrl } from "../chartConfigs/chartUrls";
15+
16+
export function transformData(
17+
originData: JSONObject[],
18+
xAxis: string,
19+
seriesColumnNames: string[]
20+
) {
21+
// aggregate data by x-axis
22+
const transformedData: JSONObject[] = [];
23+
originData.reduce((prev, cur) => {
24+
if (cur === null || cur === undefined) {
25+
return prev;
26+
}
27+
const groupValue = cur[xAxis] as string;
28+
if (!prev[groupValue]) {
29+
// init as 0
30+
const initValue: any = {};
31+
seriesColumnNames.forEach((name) => {
32+
initValue[name] = 0;
33+
});
34+
prev[groupValue] = initValue;
35+
transformedData.push(prev[groupValue]);
36+
}
37+
// remain the x-axis data
38+
prev[groupValue][xAxis] = groupValue;
39+
seriesColumnNames.forEach((key) => {
40+
if (key === xAxis) {
41+
return;
42+
} else if (isNumeric(cur[key])) {
43+
const bigNum = Big(cur[key]);
44+
prev[groupValue][key] = bigNum.add(prev[groupValue][key]).toNumber();
45+
} else {
46+
prev[groupValue][key] += 1;
47+
}
48+
});
49+
return prev;
50+
}, {} as any);
51+
return transformedData;
52+
}
53+
54+
const notAxisChartSet: Set<CharOptionCompType> = new Set(["pie"] as const);
55+
export const echartsConfigOmitChildren = [
56+
"hidden",
57+
"selectedPoints",
58+
"onUIEvent",
59+
"mapInstance"
60+
] as const;
61+
type EchartsConfigProps = Omit<ChartCompPropsType, typeof echartsConfigOmitChildren[number]>;
62+
63+
export function isAxisChart(type: CharOptionCompType) {
64+
return !notAxisChartSet.has(type);
65+
}
66+
67+
export function getSeriesConfig(props: EchartsConfigProps) {
68+
const visibleSeries = props.series.filter((s) => !s.getView().hide);
69+
const seriesLength = visibleSeries.length;
70+
return visibleSeries.map((s, index) => {
71+
if (isAxisChart(props.chartConfig.type)) {
72+
let encodeX: string, encodeY: string;
73+
const horizontalX = props.xAxisDirection === "horizontal";
74+
let itemStyle = props.chartConfig.itemStyle;
75+
// FIXME: need refactor... chartConfig returns a function with paramters
76+
if (props.chartConfig.type === "bar") {
77+
// barChart's border radius, depend on x-axis direction and stack state
78+
const borderRadius = horizontalX ? [2, 2, 0, 0] : [0, 2, 2, 0];
79+
if (props.chartConfig.stack && index === visibleSeries.length - 1) {
80+
itemStyle = { ...itemStyle, borderRadius: borderRadius };
81+
} else if (!props.chartConfig.stack) {
82+
itemStyle = { ...itemStyle, borderRadius: borderRadius };
83+
}
84+
}
85+
if (horizontalX) {
86+
encodeX = props.xAxisKey;
87+
encodeY = s.getView().columnName;
88+
} else {
89+
encodeX = s.getView().columnName;
90+
encodeY = props.xAxisKey;
91+
}
92+
return {
93+
name: s.getView().seriesName,
94+
selectedMode: "single",
95+
select: {
96+
itemStyle: {
97+
borderColor: "#000",
98+
},
99+
},
100+
encode: {
101+
x: encodeX,
102+
y: encodeY,
103+
},
104+
// each type of chart's config
105+
...props.chartConfig,
106+
itemStyle: itemStyle,
107+
label: {
108+
...props.chartConfig.label,
109+
...(!horizontalX && { position: "outside" }),
110+
},
111+
};
112+
} else {
113+
// pie
114+
const radiusAndCenter = getPieRadiusAndCenter(seriesLength, index, props.chartConfig);
115+
return {
116+
...props.chartConfig,
117+
radius: radiusAndCenter.radius,
118+
center: radiusAndCenter.center,
119+
name: s.getView().seriesName,
120+
selectedMode: "single",
121+
encode: {
122+
itemName: props.xAxisKey,
123+
value: s.getView().columnName,
124+
},
125+
};
126+
}
127+
});
128+
}
129+
130+
// https://echarts.apache.org/en/option.html
131+
export function getEchartsConfig(props: EchartsConfigProps, chartSize?: ChartSize): EChartsOptionWithMap {
132+
console.log("🚀 ~ getEchartsConfig ~ props:", props)
133+
if (props.mode === "json") {
134+
let opt={
135+
"title": {
136+
"text": props.echartsTitle,
137+
'top': props.echartsLegendConfig.top === 'bottom' ?'top':'bottom',
138+
"left":"center"
139+
},
140+
"backgroundColor": props?.style?.background,
141+
"color": props.echartsOption.data?.map(data => data.color),
142+
"tooltip": props.tooltip&&{
143+
"trigger": "axis",
144+
"axisPointer": {
145+
"type": "cross"
146+
}
147+
},
148+
"grid": {
149+
"left": "10%",
150+
"right": "10%",
151+
"bottom": "10%",
152+
},
153+
"xAxis": {
154+
"type": "category",
155+
"data": props.echartsOption.xAxis.data
156+
},
157+
"yAxis": {
158+
"type": "value",
159+
"scale": true
160+
},
161+
"series": [
162+
{
163+
"name": props.echartsConfig.type,
164+
"type": props.echartsConfig.type,
165+
"left": "10%",
166+
"top": 60,
167+
"bottom": 60,
168+
"width": "80%",
169+
"min": 0,
170+
"max": 100,
171+
"gap": 2,
172+
"label": {
173+
"show": true,
174+
"position": props.echartsLabelConfig.top
175+
},
176+
"data": props.echartsOption.data,
177+
}
178+
]
179+
}
180+
return props.echartsOption ? opt : {};
181+
182+
}
183+
184+
if(props.mode === "map") {
185+
const {
186+
mapZoomLevel,
187+
mapCenterLat,
188+
mapCenterLng,
189+
mapOptions,
190+
showCharts,
191+
} = props;
192+
193+
const echartsOption = mapOptions && showCharts ? mapOptions : {};
194+
return {
195+
gmap: {
196+
center: [mapCenterLng, mapCenterLat],
197+
zoom: mapZoomLevel,
198+
renderOnMoving: true,
199+
echartsLayerZIndex: showCharts ? 2019 : 0,
200+
roam: true
201+
},
202+
...echartsOption,
203+
}
204+
}
205+
// axisChart
206+
const axisChart = isAxisChart(props.chartConfig.type);
207+
const gridPos = {
208+
left: 20,
209+
right: props.legendConfig.left === "right" ? "10%" : 20,
210+
top: 50,
211+
bottom: 35,
212+
};
213+
let config: EChartsOptionWithMap = {
214+
title: { text: props.title, left: "center" },
215+
tooltip: {
216+
confine: true,
217+
trigger: axisChart ? "axis" : "item",
218+
},
219+
legend: props.legendConfig,
220+
grid: {
221+
...gridPos,
222+
containLabel: true,
223+
},
224+
};
225+
if (props.data.length <= 0) {
226+
// no data
227+
return {
228+
...config,
229+
...(axisChart ? noDataAxisConfig : noDataPieChartConfig),
230+
};
231+
}
232+
const yAxisConfig = props.yConfig();
233+
const seriesColumnNames = props.series
234+
.filter((s) => !s.getView().hide)
235+
.map((s) => s.getView().columnName);
236+
// y-axis is category and time, data doesn't need to aggregate
237+
const transformedData =
238+
yAxisConfig.type === "category" || yAxisConfig.type === "time"
239+
? props.data
240+
: transformData(props.data, props.xAxisKey, seriesColumnNames);
241+
config = {
242+
...config,
243+
dataset: [
244+
{
245+
source: transformedData,
246+
sourceHeader: false,
247+
},
248+
],
249+
series: getSeriesConfig(props),
250+
};
251+
if (axisChart) {
252+
// pure chart's size except the margin around
253+
let chartRealSize;
254+
if (chartSize) {
255+
const rightSize =
256+
typeof gridPos.right === "number"
257+
? gridPos.right
258+
: (chartSize.w * parseFloat(gridPos.right)) / 100.0;
259+
chartRealSize = {
260+
// actually it's self-adaptive with the x-axis label on the left, not that accurate but work
261+
w: chartSize.w - gridPos.left - rightSize,
262+
// also self-adaptive on the bottom
263+
h: chartSize.h - gridPos.top - gridPos.bottom,
264+
right: rightSize,
265+
};
266+
}
267+
const finalXyConfig = calcXYConfig(
268+
props.xConfig,
269+
yAxisConfig,
270+
props.xAxisDirection,
271+
transformedData.map((d) => d[props.xAxisKey]),
272+
chartRealSize
273+
);
274+
config = {
275+
...config,
276+
// @ts-ignore
277+
xAxis: finalXyConfig.xConfig,
278+
// @ts-ignore
279+
yAxis: finalXyConfig.yConfig,
280+
};
281+
}
282+
// log.log("Echarts transformedData and config", transformedData, config);
283+
return config;
284+
}
285+
286+
export function getSelectedPoints(param: any, option: any) {
287+
const series = option.series;
288+
const dataSource = _.isArray(option.dataset) && option.dataset[0]?.source;
289+
if (series && dataSource) {
290+
return param.selected.flatMap((selectInfo: any) => {
291+
const seriesInfo = series[selectInfo.seriesIndex];
292+
if (!seriesInfo || !seriesInfo.encode) {
293+
return [];
294+
}
295+
return selectInfo.dataIndex.map((index: any) => {
296+
const commonResult = {
297+
seriesName: seriesInfo.name,
298+
};
299+
if (seriesInfo.encode.itemName && seriesInfo.encode.value) {
300+
return {
301+
...commonResult,
302+
itemName: dataSource[index][seriesInfo.encode.itemName],
303+
value: dataSource[index][seriesInfo.encode.value],
304+
};
305+
} else {
306+
return {
307+
...commonResult,
308+
x: dataSource[index][seriesInfo.encode.x],
309+
y: dataSource[index][seriesInfo.encode.y],
310+
};
311+
}
312+
});
313+
});
314+
}
315+
return [];
316+
}
317+
318+
export function loadGoogleMapsScript(apiKey: string) {
319+
const mapsUrl = `${googleMapsApiUrl}?key=${apiKey}`;
320+
const scripts = document.getElementsByTagName('script');
321+
// is script already loaded
322+
let scriptIndex = _.findIndex(scripts, (script) => script.src.endsWith(mapsUrl));
323+
if(scriptIndex > -1) {
324+
return scripts[scriptIndex];
325+
}
326+
// is script loaded with diff api_key, remove the script and load again
327+
scriptIndex = _.findIndex(scripts, (script) => script.src.startsWith(googleMapsApiUrl));
328+
if(scriptIndex > -1) {
329+
scripts[scriptIndex].remove();
330+
}
331+
332+
const script = document.createElement("script");
333+
script.type = "text/javascript";
334+
script.src = mapsUrl;
335+
script.async = true;
336+
script.defer = true;
337+
window.document.body.appendChild(script);
338+
339+
return script;
340+
}

0 commit comments

Comments
 (0)