lowcoder-map-component 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/icons/demo-icon.svg +10 -0
- package/icons/hills.svg +17 -0
- package/index.css +27 -0
- package/index.html +35 -0
- package/index.tsx +20 -0
- package/loader.mjs +11 -0
- package/package.json +175 -0
- package/src/README.md +35 -0
- package/src/components/ChartPropertyView.tsx +961 -0
- package/src/components/SeriesComp.tsx +368 -0
- package/src/components/TabPropertyView.tsx +127 -0
- package/src/comps/Barcharts/comp.tsx +338 -0
- package/src/comps/Barcharts/constants.tsx +77 -0
- package/src/comps/Linecharts/comp.tsx +350 -0
- package/src/comps/Linecharts/constants.tsx +53 -0
- package/src/comps/Linechartsv2/comp.tsx +350 -0
- package/src/comps/Linechartsv2/constants.tsx +68 -0
- package/src/comps/Mapcharts/comp.tsx +381 -0
- package/src/comps/Mapcharts/constants.tsx +312 -0
- package/src/comps/Mapchartsv2/comp.tsx +393 -0
- package/src/comps/Mapchartsv2/constants.tsx +340 -0
- package/src/comps/MixedLineBarCharts/comp.tsx +353 -0
- package/src/comps/MixedLineBarCharts/constants.tsx +60 -0
- package/src/comps/MultiLineCharts/comp.tsx +362 -0
- package/src/comps/MultiLineCharts/constants.tsx +96 -0
- package/src/comps/PercentageCharts/comp.tsx +359 -0
- package/src/comps/PercentageCharts/constants.tsx +98 -0
- package/src/comps/Piecharts/comp.tsx +334 -0
- package/src/comps/Piecharts/constants.tsx +48 -0
- package/src/comps/Tablecharts/comp.tsx +429 -0
- package/src/comps/Tablecharts/constants.tsx +97 -0
- package/src/comps/Totalcharts/comp.tsx +463 -0
- package/src/comps/Totalcharts/constants.tsx +66 -0
- package/src/comps/TwoLineCharts/comp.tsx +350 -0
- package/src/comps/TwoLineCharts/constants.tsx +82 -0
- package/src/comps/mapComponent/comp.tsx +338 -0
- package/src/comps/mapComponent/constants.tsx +2149 -0
- package/src/comps/tab/comp.tsx +283 -0
- package/src/comps/tab/constants.tsx +79 -0
- package/src/configs/barChartConfig.tsx +153 -0
- package/src/configs/baseConfig.tsx +66 -0
- package/src/configs/candleStickChartConfig.tsx +35 -0
- package/src/configs/cartesianAxisConfig.tsx +314 -0
- package/src/configs/chartUrls.tsx +9 -0
- package/src/configs/echartConfig.tsx +260 -0
- package/src/configs/echartsLabelConfig.tsx +47 -0
- package/src/configs/echartsLegendConfig.tsx +29 -0
- package/src/configs/echartsTitleConfig.tsx +49 -0
- package/src/configs/echartsTitleVerticalConfig.tsx +50 -0
- package/src/configs/funnelChartConfig.tsx +35 -0
- package/src/configs/gaugeChartConfig.tsx +31 -0
- package/src/configs/graphChartConfig.tsx +31 -0
- package/src/configs/heatmapChartConfig.tsx +31 -0
- package/src/configs/legendConfig.tsx +55 -0
- package/src/configs/lineChartConfig.tsx +246 -0
- package/src/configs/lineChartConfigv2.tsx +246 -0
- package/src/configs/mapChartConfig.tsx +106 -0
- package/src/configs/mapChartConfigv2.tsx +106 -0
- package/src/configs/mixedChartConfig.tsx +21 -0
- package/src/configs/pieChartConfig.tsx +156 -0
- package/src/configs/radarChartConfig.tsx +31 -0
- package/src/configs/sankeyChartConfig.tsx +35 -0
- package/src/configs/scatterChartConfig.tsx +152 -0
- package/src/configs/sunburstChartConfig.tsx +31 -0
- package/src/configs/tabConfig.tsx +0 -0
- package/src/configs/tableChartConfig.tsx +81 -0
- package/src/configs/themeriverChartConfig.tsx +31 -0
- package/src/configs/totalChartConfig.tsx +670 -0
- package/src/configs/treeChartConfig.tsx +31 -0
- package/src/configs/treemapChartConfig.tsx +31 -0
- package/src/controls/AnimationsControls.tsx +3 -0
- package/src/controls/AutoHeightControl.tsx +2 -0
- package/src/controls/AxisControls.tsx +146 -0
- package/src/controls/AxisVisibilityControl.tsx +14 -0
- package/src/controls/ChartTypeControl.tsx +15 -0
- package/src/controls/ColorScaleControl.tsx +221 -0
- package/src/controls/ColumnControl.tsx +204 -0
- package/src/controls/ConfigControl.tsx +37 -0
- package/src/controls/DirectionControls.tsx +20 -0
- package/src/controls/IconControl.tsx +88 -0
- package/src/controls/LegendControl.tsx +8 -0
- package/src/controls/RowLimitControl.tsx +91 -0
- package/src/controls/StyleControls.tsx +22 -0
- package/src/controls/TimeControls.tsx +41 -0
- package/src/controls/TrendlineControl.tsx +89 -0
- package/src/controls/UIEventControl.tsx +21 -0
- package/src/controls/index.ts +16 -0
- package/src/controls/radioControl.tsx +88 -0
- package/src/exposing/index.ts +3 -0
- package/src/exposing/setPoint.ts +19 -0
- package/src/geo/vn.geo.json +369897 -0
- package/src/geo/world.geo.json +32127 -0
- package/src/i18n/comps/index.tsx +39 -0
- package/src/i18n/comps/locales/en.ts +558 -0
- package/src/i18n/comps/locales/enObj.tsx +7186 -0
- package/src/i18n/comps/locales/index.ts +7 -0
- package/src/i18n/comps/locales/pt.ts +37 -0
- package/src/i18n/comps/locales/ptObj.tsx +40 -0
- package/src/i18n/comps/locales/types.tsx +622 -0
- package/src/i18n/comps/locales/zh.ts +3 -0
- package/src/i18n/comps/locales/zhObj.tsx +4 -0
- package/src/index.ts +31 -0
- package/src/types/global.d.ts +9 -0
- package/src/types/lowcoder-sdk.d.ts +578 -0
- package/src/utils/chartStyle.util.ts +121 -0
- package/src/utils/columnExtractor.util.ts +41 -0
- package/src/utils/dataTransform.util.ts +37 -0
- package/src/utils/deepEqual.util.ts +29 -0
- package/src/utils/echarts.util.tsx +822 -0
- package/src/utils/getDataKey.util.ts +115 -0
- package/src/utils/getLinearRegression.util.ts +46 -0
- package/src/utils/googleMaps.util.ts +28 -0
- package/src/utils/isEmpty.util.ts +10 -0
- package/src/utils/move.util.ts +6 -0
- package/src/utils/selection.util.ts +73 -0
- package/src/utils/style.util.ts +315 -0
- package/src/utils/time.util.ts +221 -0
- package/src/utils/timeFormatter.util.ts +104 -0
- package/src/utils/timeProcessing.util.ts +38 -0
- package/src/utils/trendline.util.ts +342 -0
- package/tsconfig.json +25 -0
- package/vite.config.js +19 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { timeFormat } from "@/configs/cartesianAxisConfig";
|
|
2
|
+
import dayjs, { Dayjs } from "dayjs";
|
|
3
|
+
import isBetween from "dayjs/plugin/isBetween";
|
|
4
|
+
import quarterOfYear from "dayjs/plugin/quarterOfYear";
|
|
5
|
+
import timezone from "dayjs/plugin/timezone";
|
|
6
|
+
import utc from "dayjs/plugin/utc";
|
|
7
|
+
import weekOfYear from "dayjs/plugin/weekOfYear";
|
|
8
|
+
import { JSONObject } from "lowcoder-sdk";
|
|
9
|
+
|
|
10
|
+
dayjs.extend(isBetween);
|
|
11
|
+
dayjs.extend(utc);
|
|
12
|
+
dayjs.extend(timezone);
|
|
13
|
+
dayjs.extend(weekOfYear);
|
|
14
|
+
dayjs.extend(quarterOfYear);
|
|
15
|
+
|
|
16
|
+
export interface TimeFilterParams {
|
|
17
|
+
timeColumn: string;
|
|
18
|
+
timeGranularity: string;
|
|
19
|
+
timeRange: string;
|
|
20
|
+
startTime?: string;
|
|
21
|
+
endTime?: string;
|
|
22
|
+
timeFormat?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function groupDataByTimeGranularity(
|
|
26
|
+
data: JSONObject[],
|
|
27
|
+
timeColumn: string,
|
|
28
|
+
timeGranularity: string,
|
|
29
|
+
seriesColumns: string[],
|
|
30
|
+
timeFormat?: string
|
|
31
|
+
): JSONObject[] {
|
|
32
|
+
if (!timeColumn || !timeGranularity) {
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const groupedData: Record<string, JSONObject> = {};
|
|
37
|
+
|
|
38
|
+
data.forEach((item) => {
|
|
39
|
+
const rawValue = item[timeColumn];
|
|
40
|
+
if (!rawValue || typeof rawValue !== "string") return;
|
|
41
|
+
|
|
42
|
+
let timestamp = parseDateWithFormat(rawValue, timeFormat);
|
|
43
|
+
|
|
44
|
+
if (!timestamp || !timestamp.isValid()) {
|
|
45
|
+
timestamp = parseDateWithFormat(rawValue);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!timestamp || !timestamp.isValid()) {
|
|
49
|
+
console.warn("Invalid date:", rawValue);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let groupKey: string | number;
|
|
54
|
+
|
|
55
|
+
switch (timeGranularity) {
|
|
56
|
+
case "second":
|
|
57
|
+
groupKey = timestamp.format("ss");
|
|
58
|
+
break;
|
|
59
|
+
case "minute":
|
|
60
|
+
groupKey = timestamp.format("mm:ss");
|
|
61
|
+
break;
|
|
62
|
+
case "hour":
|
|
63
|
+
groupKey = timestamp.format("HH:mm:ss");
|
|
64
|
+
break;
|
|
65
|
+
case "day":
|
|
66
|
+
groupKey = timestamp.format("DD");
|
|
67
|
+
break;
|
|
68
|
+
case "week":
|
|
69
|
+
groupKey = `W${timestamp.week()} ${timestamp.format("YYYY")}`;
|
|
70
|
+
break;
|
|
71
|
+
case "month":
|
|
72
|
+
groupKey = timestamp.format("MM-YYYY");
|
|
73
|
+
break;
|
|
74
|
+
case "quarter":
|
|
75
|
+
groupKey = timestamp.format("[Q]Q-YYYY");
|
|
76
|
+
break;
|
|
77
|
+
case "year":
|
|
78
|
+
groupKey = timestamp.format("YYYY");
|
|
79
|
+
break;
|
|
80
|
+
default:
|
|
81
|
+
groupKey = timestamp.format("DD-MM-YYYY");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!groupedData[groupKey]) {
|
|
85
|
+
groupedData[groupKey] = {
|
|
86
|
+
[timeColumn]: groupKey,
|
|
87
|
+
...seriesColumns.reduce((acc, col) => ({ ...acc, [col]: 0 }), {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
seriesColumns.forEach((col) => {
|
|
92
|
+
if (item[col] !== undefined && item[col] !== null) {
|
|
93
|
+
const currentValue = Number(groupedData[groupKey][col]) || 0;
|
|
94
|
+
const newValue = currentValue + Number(item[col]);
|
|
95
|
+
groupedData[groupKey][col] = newValue;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const result = Object.values(groupedData).sort(
|
|
101
|
+
(a, b) =>
|
|
102
|
+
dayjs(a[timeColumn] as string).valueOf() -
|
|
103
|
+
dayjs(b[timeColumn] as string).valueOf()
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function parseDateWithFormat(
|
|
110
|
+
dateString: string,
|
|
111
|
+
format?: string
|
|
112
|
+
): Dayjs | null {
|
|
113
|
+
if (!dateString) return null;
|
|
114
|
+
|
|
115
|
+
if (format) {
|
|
116
|
+
const parsed = dayjs(dateString, format, true);
|
|
117
|
+
if (parsed.isValid()) return parsed;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (/^\d{2}-\d{4}$/.test(dateString)) {
|
|
121
|
+
const [month, year] = dateString.split("-");
|
|
122
|
+
return dayjs(`${year}-${month.padStart(2, "0")}-01`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const fmt of timeFormat) {
|
|
126
|
+
const parsed = dayjs(dateString, fmt, true);
|
|
127
|
+
if (parsed.isValid()) return parsed;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function filterDataByTimeRange(
|
|
134
|
+
data: JSONObject[],
|
|
135
|
+
params: TimeFilterParams
|
|
136
|
+
): JSONObject[] {
|
|
137
|
+
const { timeColumn, timeRange, startTime, endTime, timeFormat } = params;
|
|
138
|
+
|
|
139
|
+
if (!timeColumn || timeRange === "noFilter") {
|
|
140
|
+
return data;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const now = dayjs();
|
|
144
|
+
let startDate: Dayjs;
|
|
145
|
+
let endDate: Dayjs = now;
|
|
146
|
+
|
|
147
|
+
switch (timeRange) {
|
|
148
|
+
case "lastDay":
|
|
149
|
+
startDate = now.subtract(1, "day");
|
|
150
|
+
break;
|
|
151
|
+
case "lastWeek":
|
|
152
|
+
startDate = now.subtract(1, "week");
|
|
153
|
+
break;
|
|
154
|
+
case "lastMonth":
|
|
155
|
+
startDate = now.subtract(1, "month");
|
|
156
|
+
break;
|
|
157
|
+
case "lastQuarter":
|
|
158
|
+
startDate = now.subtract(1, "quarter");
|
|
159
|
+
break;
|
|
160
|
+
case "lastYear":
|
|
161
|
+
startDate = now.subtract(1, "year");
|
|
162
|
+
break;
|
|
163
|
+
case "currentDay":
|
|
164
|
+
startDate = now.startOf("day");
|
|
165
|
+
break;
|
|
166
|
+
case "custom":
|
|
167
|
+
startDate = startTime
|
|
168
|
+
? parseDateWithFormat(startTime, timeFormat) || now.subtract(1, "day")
|
|
169
|
+
: now.subtract(1, "day");
|
|
170
|
+
if (endTime) {
|
|
171
|
+
endDate = parseDateWithFormat(endTime, timeFormat) || now;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
return data;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return data.filter((item) => {
|
|
180
|
+
const t = dayjs(item[timeColumn] as string);
|
|
181
|
+
return t.isValid() && t.isBetween(startDate, endDate, null, "[]");
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function validateAndParseTime(
|
|
186
|
+
timeString: string,
|
|
187
|
+
format: string = "DD-MM-YYYY HH:mm"
|
|
188
|
+
): Dayjs | null {
|
|
189
|
+
if (!timeString) return null;
|
|
190
|
+
|
|
191
|
+
const parsed = dayjs(timeString, format);
|
|
192
|
+
return parsed.isValid() ? parsed : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function formatTimeByGranularity(
|
|
196
|
+
time: Dayjs,
|
|
197
|
+
granularity: string
|
|
198
|
+
): string {
|
|
199
|
+
switch (granularity) {
|
|
200
|
+
case "second":
|
|
201
|
+
return time.format("DD-MM-YYYY HH:mm:ss");
|
|
202
|
+
case "minute":
|
|
203
|
+
return time.format("DD-MM-YYYY HH:mm");
|
|
204
|
+
case "hour":
|
|
205
|
+
return time.format("DD-MM-YYYY HH:00");
|
|
206
|
+
case "day":
|
|
207
|
+
return time.format("DD-MM-YYYY");
|
|
208
|
+
case "week":
|
|
209
|
+
return `W${time.week()} ${time.format("YYYY")}`;
|
|
210
|
+
case "month":
|
|
211
|
+
return time.format("MM-YYYY");
|
|
212
|
+
case "quarter":
|
|
213
|
+
return time.format("YYYY-[Q]Q");
|
|
214
|
+
case "year":
|
|
215
|
+
return time.format("YYYY");
|
|
216
|
+
default:
|
|
217
|
+
return time.format("DD-MM-YYYY");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export { filterDataByTimeRange, groupDataByTimeGranularity };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
|
|
3
|
+
export function getTimeBasedTooltipFormatter(props: any) {
|
|
4
|
+
return (params: any) => {
|
|
5
|
+
if (!Array.isArray(params)) {
|
|
6
|
+
const timeParam = params;
|
|
7
|
+
const timeValue = timeParam.axisValue;
|
|
8
|
+
let timeDisplay = timeValue;
|
|
9
|
+
|
|
10
|
+
if (dayjs(timeValue).isValid()) {
|
|
11
|
+
const date = dayjs(timeValue);
|
|
12
|
+
const formats: Record<string, string> = {
|
|
13
|
+
second: "YYYY-MM-DD HH:mm:ss",
|
|
14
|
+
minute: "YYYY-MM-DD HH:mm",
|
|
15
|
+
hour: "YYYY-MM-DD HH:00",
|
|
16
|
+
day: "YYYY-MM-DD",
|
|
17
|
+
week: `YYYY [Week] W`,
|
|
18
|
+
month: "YYYY-MM",
|
|
19
|
+
quarter: `YYYY [Q]Q`,
|
|
20
|
+
year: "YYYY",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
timeDisplay = date.format(
|
|
24
|
+
formats[props.timeGranularity] || "YYYY-MM-DD"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const value = timeParam.value[timeParam.encode.y[0]];
|
|
29
|
+
return `
|
|
30
|
+
<div style="font-weight: bold; margin-bottom: 5px;">${timeDisplay}</div>
|
|
31
|
+
<div style="display: flex; align-items: center; margin: 3px 0;">
|
|
32
|
+
<span style="display: inline-block; width: 10px; height: 10px; background: ${timeParam.color}; margin-right: 5px; border-radius: 2px;"></span>
|
|
33
|
+
<span>${timeParam.seriesName}: ${value}</span>
|
|
34
|
+
</div>
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Handle array of params (multiple series)
|
|
39
|
+
const timeParam = params[0];
|
|
40
|
+
const timeValue = timeParam.axisValue;
|
|
41
|
+
let timeDisplay = timeValue;
|
|
42
|
+
|
|
43
|
+
if (dayjs(timeValue).isValid()) {
|
|
44
|
+
const date = dayjs(timeValue);
|
|
45
|
+
const formats: Record<string, string> = {
|
|
46
|
+
second: "YYYY-MM-DD HH:mm:ss",
|
|
47
|
+
minute: "YYYY-MM-DD HH:mm",
|
|
48
|
+
hour: "YYYY-MM-DD HH:00",
|
|
49
|
+
day: "YYYY-MM-DD",
|
|
50
|
+
week: `YYYY [Week] W`,
|
|
51
|
+
month: "YYYY-MM",
|
|
52
|
+
quarter: `YYYY [Q]Q`,
|
|
53
|
+
year: "YYYY",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
timeDisplay = date.format(formats[props.timeGranularity] || "YYYY-MM-DD");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let result = `<div style="font-weight: bold; margin-bottom: 5px;">${timeDisplay}</div>`;
|
|
60
|
+
|
|
61
|
+
params.forEach((param: any) => {
|
|
62
|
+
const yField = Array.isArray(param.encode.y)
|
|
63
|
+
? param.encode.y[0]
|
|
64
|
+
: param.encode.y;
|
|
65
|
+
const value = param.value[yField];
|
|
66
|
+
result += `
|
|
67
|
+
<div style="display: flex; align-items: center; margin: 3px 0;">
|
|
68
|
+
<span style="display: inline-block; width: 10px; height: 10px; background: ${param.color}; margin-right: 5px; border-radius: 2px;"></span>
|
|
69
|
+
<span>${param.seriesName}: ${value}</span>
|
|
70
|
+
</div>
|
|
71
|
+
`;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return result;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getTimeAxisFormatter(timeGranularity: string) {
|
|
79
|
+
return (value: any) => {
|
|
80
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const date = dayjs(value);
|
|
86
|
+
if (!date.isValid()) return String(value);
|
|
87
|
+
|
|
88
|
+
const formats: Record<string, string> = {
|
|
89
|
+
second: "HH:mm:ss",
|
|
90
|
+
minute: "HH:mm",
|
|
91
|
+
hour: "HH:00\nMM-DD",
|
|
92
|
+
day: "MM-DD",
|
|
93
|
+
week: "MM-DD\n'W'W",
|
|
94
|
+
month: "YYYY-MM",
|
|
95
|
+
quarter: "YYYY-'Q'Q",
|
|
96
|
+
year: "YYYY",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return date.format(formats[timeGranularity] || "MM-DD");
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return String(value);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { JSONObject } from "lowcoder-sdk";
|
|
2
|
+
import { EChartPropsType } from "./echarts.util";
|
|
3
|
+
import { filterDataByTimeRange, groupDataByTimeGranularity } from "./time.util";
|
|
4
|
+
|
|
5
|
+
export function processTimeData(
|
|
6
|
+
props: EChartPropsType,
|
|
7
|
+
data: JSONObject[],
|
|
8
|
+
seriesColumnNames: string[]
|
|
9
|
+
) {
|
|
10
|
+
let processedData = data;
|
|
11
|
+
|
|
12
|
+
if (props.timeColumn && props.timeRange !== "noFilter") {
|
|
13
|
+
processedData = filterDataByTimeRange(processedData, {
|
|
14
|
+
timeColumn: props.timeColumn,
|
|
15
|
+
timeGranularity: props.timeGranularity,
|
|
16
|
+
timeRange: props.timeRange,
|
|
17
|
+
startTime: props.startTime,
|
|
18
|
+
endTime: props.endTime,
|
|
19
|
+
timeFormat: props.timeFormat,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (
|
|
24
|
+
props.timeColumn &&
|
|
25
|
+
props.timeGranularity &&
|
|
26
|
+
props.timeColumn === props.xAxisKey
|
|
27
|
+
) {
|
|
28
|
+
processedData = groupDataByTimeGranularity(
|
|
29
|
+
processedData,
|
|
30
|
+
props.timeColumn,
|
|
31
|
+
props.timeGranularity,
|
|
32
|
+
seriesColumnNames,
|
|
33
|
+
props.timeFormat
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return processedData;
|
|
38
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { UIChartDataType } from "@/comps/Linecharts/constants";
|
|
2
|
+
import { JSONObject } from "lowcoder-sdk";
|
|
3
|
+
|
|
4
|
+
export interface TrendlineConfig {
|
|
5
|
+
type: string;
|
|
6
|
+
name: string;
|
|
7
|
+
order?: number;
|
|
8
|
+
period?: number;
|
|
9
|
+
color?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const calculateTrendline = (
|
|
13
|
+
data: UIChartDataType[],
|
|
14
|
+
trendlineConfig: TrendlineConfig,
|
|
15
|
+
xAxisKey: string,
|
|
16
|
+
yAxisKey: string
|
|
17
|
+
): number[] => {
|
|
18
|
+
const { type, order = 2, period = 2 } = trendlineConfig;
|
|
19
|
+
|
|
20
|
+
const preparedData = data.map((item) => ({
|
|
21
|
+
x: item[xAxisKey as keyof UIChartDataType] || 0,
|
|
22
|
+
y: item[yAxisKey as keyof UIChartDataType] || 0,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
switch (type) {
|
|
26
|
+
case "linear":
|
|
27
|
+
return calculateLinearTrendline(preparedData);
|
|
28
|
+
|
|
29
|
+
case "polynomial":
|
|
30
|
+
return calculatePolynomialTrendline(preparedData, order);
|
|
31
|
+
|
|
32
|
+
case "exponential":
|
|
33
|
+
return calculateExponentialTrendline(preparedData);
|
|
34
|
+
|
|
35
|
+
case "logarithmic":
|
|
36
|
+
return calculateLogarithmicTrendline(preparedData);
|
|
37
|
+
|
|
38
|
+
case "power":
|
|
39
|
+
return calculatePowerTrendline(preparedData);
|
|
40
|
+
|
|
41
|
+
case "movingAverage":
|
|
42
|
+
return calculateMovingAverage(preparedData, period);
|
|
43
|
+
|
|
44
|
+
default:
|
|
45
|
+
return calculateLinearTrendline(preparedData);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Linear trendline (y = mx + b)
|
|
50
|
+
const calculateLinearTrendline = (
|
|
51
|
+
data: Array<{ x: number; y: number }>
|
|
52
|
+
): number[] => {
|
|
53
|
+
const n = data.length;
|
|
54
|
+
let sumX = 0,
|
|
55
|
+
sumY = 0,
|
|
56
|
+
sumXY = 0,
|
|
57
|
+
sumXX = 0;
|
|
58
|
+
|
|
59
|
+
data.forEach((point) => {
|
|
60
|
+
sumX += point.x;
|
|
61
|
+
sumY += point.y;
|
|
62
|
+
sumXY += point.x * point.y;
|
|
63
|
+
sumXX += point.x * point.x;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
|
|
67
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
68
|
+
|
|
69
|
+
return data.map((point) => slope * point.x + intercept);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Polynomial trendline (y = a₀ + a₁x + a₂x² + ... + aₙxⁿ)
|
|
73
|
+
const calculatePolynomialTrendline = (
|
|
74
|
+
data: Array<{ x: number; y: number }>,
|
|
75
|
+
order: number
|
|
76
|
+
): number[] => {
|
|
77
|
+
const n = data.length;
|
|
78
|
+
|
|
79
|
+
if (order === 2) {
|
|
80
|
+
let sumX = 0,
|
|
81
|
+
sumY = 0,
|
|
82
|
+
sumXX = 0,
|
|
83
|
+
sumXXX = 0,
|
|
84
|
+
sumXXXX = 0,
|
|
85
|
+
sumXY = 0,
|
|
86
|
+
sumXXY = 0;
|
|
87
|
+
|
|
88
|
+
data.forEach((point) => {
|
|
89
|
+
const x = point.x;
|
|
90
|
+
const y = point.y;
|
|
91
|
+
const xx = x * x;
|
|
92
|
+
|
|
93
|
+
sumX += x;
|
|
94
|
+
sumY += y;
|
|
95
|
+
sumXX += xx;
|
|
96
|
+
sumXXX += xx * x;
|
|
97
|
+
sumXXXX += xx * xx;
|
|
98
|
+
sumXY += x * y;
|
|
99
|
+
sumXXY += xx * y;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const Sxx = sumXX - (sumX * sumX) / n;
|
|
103
|
+
const Sxy = sumXY - (sumX * sumY) / n;
|
|
104
|
+
const Sxx2 = sumXXX - (sumX * sumXX) / n;
|
|
105
|
+
const Sx2y = sumXXY - (sumXX * sumY) / n;
|
|
106
|
+
const Sx2x2 = sumXXXX - (sumXX * sumXX) / n;
|
|
107
|
+
|
|
108
|
+
const denominator = Sxx * Sx2x2 - Sxx2 * Sxx2;
|
|
109
|
+
const a = (Sxy * Sx2x2 - Sx2y * Sxx2) / denominator;
|
|
110
|
+
const b = (Sx2y * Sxx - Sxy * Sxx2) / denominator;
|
|
111
|
+
const c = (sumY - a * sumX - b * sumXX) / n;
|
|
112
|
+
|
|
113
|
+
return data.map((point) => a * point.x + b * point.x * point.x + c);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Fallback to linear for higher orders
|
|
117
|
+
return calculateLinearTrendline(data);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Exponential trendline (y = ae^(bx))
|
|
121
|
+
const calculateExponentialTrendline = (
|
|
122
|
+
data: Array<{ x: number; y: number }>
|
|
123
|
+
): number[] => {
|
|
124
|
+
// Filter out non-positive values for log transformation
|
|
125
|
+
const validData = data.filter((p) => p.y > 0);
|
|
126
|
+
|
|
127
|
+
if (validData.length === 0) return new Array(data.length).fill(0);
|
|
128
|
+
|
|
129
|
+
// Transform to linear: ln(y) = ln(a) + bx
|
|
130
|
+
const transformedData = validData.map((p) => ({
|
|
131
|
+
x: p.x,
|
|
132
|
+
y: Math.log(p.y),
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
const linearResult = calculateLinearTrendline(transformedData);
|
|
136
|
+
|
|
137
|
+
// Transform back: y = e^(ln(y))
|
|
138
|
+
return data.map((p, index) => {
|
|
139
|
+
if (index < validData.length) {
|
|
140
|
+
return Math.exp(linearResult[index]);
|
|
141
|
+
}
|
|
142
|
+
return 0;
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Logarithmic trendline (y = a + b*ln(x))
|
|
147
|
+
const calculateLogarithmicTrendline = (
|
|
148
|
+
data: Array<{ x: number; y: number }>
|
|
149
|
+
): number[] => {
|
|
150
|
+
// Filter out non-positive x values
|
|
151
|
+
const validData = data.filter((p) => p.x > 0);
|
|
152
|
+
|
|
153
|
+
if (validData.length === 0) return new Array(data.length).fill(0);
|
|
154
|
+
|
|
155
|
+
// Transform to linear: y = a + b*ln(x)
|
|
156
|
+
const transformedData = validData.map((p) => ({
|
|
157
|
+
x: Math.log(p.x),
|
|
158
|
+
y: p.y,
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
const linearResult = calculateLinearTrendline(transformedData);
|
|
162
|
+
|
|
163
|
+
return data.map((p, index) => {
|
|
164
|
+
if (index < validData.length && p.x > 0) {
|
|
165
|
+
return linearResult[index];
|
|
166
|
+
}
|
|
167
|
+
return 0;
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Power trendline (y = ax^b)
|
|
172
|
+
const calculatePowerTrendline = (
|
|
173
|
+
data: Array<{ x: number; y: number }>
|
|
174
|
+
): number[] => {
|
|
175
|
+
// Filter out non-positive values
|
|
176
|
+
const validData = data.filter((p) => p.x > 0 && p.y > 0);
|
|
177
|
+
|
|
178
|
+
if (validData.length === 0) return new Array(data.length).fill(0);
|
|
179
|
+
|
|
180
|
+
// Transform to linear: ln(y) = ln(a) + b*ln(x)
|
|
181
|
+
const transformedData = validData.map((p) => ({
|
|
182
|
+
x: Math.log(p.x),
|
|
183
|
+
y: Math.log(p.y),
|
|
184
|
+
}));
|
|
185
|
+
|
|
186
|
+
const linearResult = calculateLinearTrendline(transformedData);
|
|
187
|
+
|
|
188
|
+
// Transform back: y = e^(ln(y))
|
|
189
|
+
return data.map((p, index) => {
|
|
190
|
+
if (index < validData.length && p.x > 0 && p.y > 0) {
|
|
191
|
+
return Math.exp(linearResult[index]);
|
|
192
|
+
}
|
|
193
|
+
return 0;
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Moving Average trendline
|
|
198
|
+
const calculateMovingAverage = (
|
|
199
|
+
data: Array<{ x: number; y: number }>,
|
|
200
|
+
period: number
|
|
201
|
+
): number[] => {
|
|
202
|
+
const result: number[] = [];
|
|
203
|
+
|
|
204
|
+
for (let i = 0; i < data.length; i++) {
|
|
205
|
+
if (i < period - 1) {
|
|
206
|
+
// Not enough data points for full period
|
|
207
|
+
result.push(data[i].y);
|
|
208
|
+
} else {
|
|
209
|
+
// Calculate moving average
|
|
210
|
+
let sum = 0;
|
|
211
|
+
for (let j = 0; j < period; j++) {
|
|
212
|
+
sum += data[i - j].y;
|
|
213
|
+
}
|
|
214
|
+
result.push(sum / period);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return result;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const createTrendlineSeries = (
|
|
222
|
+
trendlineConfig: TrendlineConfig,
|
|
223
|
+
trendlineData: number[],
|
|
224
|
+
xAxisData: any[],
|
|
225
|
+
index: number
|
|
226
|
+
) => {
|
|
227
|
+
const { type, name, color } = trendlineConfig;
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
name: name || `Trendline ${index + 1}`,
|
|
231
|
+
type: "line",
|
|
232
|
+
data: trendlineData.map((value, idx) => [xAxisData[idx], value]),
|
|
233
|
+
smooth: false,
|
|
234
|
+
lineStyle: {
|
|
235
|
+
width: 2,
|
|
236
|
+
type: getLineStyleType(type),
|
|
237
|
+
color: color || getDefaultColor(index),
|
|
238
|
+
},
|
|
239
|
+
symbol: "none",
|
|
240
|
+
tooltip: {
|
|
241
|
+
show: true,
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const getLineStyleType = (type: string): string => {
|
|
247
|
+
switch (type) {
|
|
248
|
+
case "linear":
|
|
249
|
+
return "solid";
|
|
250
|
+
case "polynomial":
|
|
251
|
+
return "dashed";
|
|
252
|
+
case "exponential":
|
|
253
|
+
return "dotted";
|
|
254
|
+
case "logarithmic":
|
|
255
|
+
return "dashdot";
|
|
256
|
+
case "power":
|
|
257
|
+
return "solid";
|
|
258
|
+
case "movingAverage":
|
|
259
|
+
return "solid";
|
|
260
|
+
default:
|
|
261
|
+
return "solid";
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const getDefaultColor = (index: number): string => {
|
|
266
|
+
const colors = [
|
|
267
|
+
"#ff0000",
|
|
268
|
+
"#00ff00",
|
|
269
|
+
"#0000ff",
|
|
270
|
+
"#ffff00",
|
|
271
|
+
"#ff00ff",
|
|
272
|
+
"#00ffff",
|
|
273
|
+
];
|
|
274
|
+
return colors[index % colors.length];
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
export const addTrendlinesToChartConfig = (
|
|
278
|
+
chartConfig: any,
|
|
279
|
+
data: UIChartDataType[],
|
|
280
|
+
trendlines: any[],
|
|
281
|
+
xAxisKey: string,
|
|
282
|
+
yAxisKey: string
|
|
283
|
+
) => {
|
|
284
|
+
if (!trendlines || trendlines.length === 0) {
|
|
285
|
+
return chartConfig;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const xAxisData = data.map((item) => item[xAxisKey as keyof UIChartDataType]);
|
|
289
|
+
|
|
290
|
+
const trendlineSeries = trendlines.map((trendline, index) => {
|
|
291
|
+
const trendlineConfig = trendline.getView();
|
|
292
|
+
const trendlineData = calculateTrendline(
|
|
293
|
+
data,
|
|
294
|
+
trendlineConfig,
|
|
295
|
+
xAxisKey,
|
|
296
|
+
yAxisKey
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
return createTrendlineSeries(
|
|
300
|
+
trendlineConfig,
|
|
301
|
+
trendlineData,
|
|
302
|
+
xAxisData,
|
|
303
|
+
index
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
...chartConfig,
|
|
309
|
+
series: [...(chartConfig.series || []), ...trendlineSeries],
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export interface DataPoint {
|
|
314
|
+
x: number;
|
|
315
|
+
y: number;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function convertToDataPoints(
|
|
319
|
+
data: JSONObject[],
|
|
320
|
+
xAxisKey: string,
|
|
321
|
+
yAxisKey: string
|
|
322
|
+
): DataPoint[] {
|
|
323
|
+
const points: DataPoint[] = [];
|
|
324
|
+
|
|
325
|
+
data.forEach((item) => {
|
|
326
|
+
const xVal = item[xAxisKey];
|
|
327
|
+
const yVal = item[yAxisKey];
|
|
328
|
+
|
|
329
|
+
if (typeof xVal === "number" && typeof yVal === "number") {
|
|
330
|
+
points.push({ x: xVal, y: yVal });
|
|
331
|
+
} else if (
|
|
332
|
+
typeof xVal === "string" &&
|
|
333
|
+
!isNaN(Number(xVal)) &&
|
|
334
|
+
typeof yVal === "string" &&
|
|
335
|
+
!isNaN(Number(yVal))
|
|
336
|
+
) {
|
|
337
|
+
points.push({ x: Number(xVal), y: Number(yVal) });
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
return points.sort((a, b) => a.x - b.x);
|
|
342
|
+
}
|