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.
Files changed (123) hide show
  1. package/README.md +37 -0
  2. package/icons/demo-icon.svg +10 -0
  3. package/icons/hills.svg +17 -0
  4. package/index.css +27 -0
  5. package/index.html +35 -0
  6. package/index.tsx +20 -0
  7. package/loader.mjs +11 -0
  8. package/package.json +175 -0
  9. package/src/README.md +35 -0
  10. package/src/components/ChartPropertyView.tsx +961 -0
  11. package/src/components/SeriesComp.tsx +368 -0
  12. package/src/components/TabPropertyView.tsx +127 -0
  13. package/src/comps/Barcharts/comp.tsx +338 -0
  14. package/src/comps/Barcharts/constants.tsx +77 -0
  15. package/src/comps/Linecharts/comp.tsx +350 -0
  16. package/src/comps/Linecharts/constants.tsx +53 -0
  17. package/src/comps/Linechartsv2/comp.tsx +350 -0
  18. package/src/comps/Linechartsv2/constants.tsx +68 -0
  19. package/src/comps/Mapcharts/comp.tsx +381 -0
  20. package/src/comps/Mapcharts/constants.tsx +312 -0
  21. package/src/comps/Mapchartsv2/comp.tsx +393 -0
  22. package/src/comps/Mapchartsv2/constants.tsx +340 -0
  23. package/src/comps/MixedLineBarCharts/comp.tsx +353 -0
  24. package/src/comps/MixedLineBarCharts/constants.tsx +60 -0
  25. package/src/comps/MultiLineCharts/comp.tsx +362 -0
  26. package/src/comps/MultiLineCharts/constants.tsx +96 -0
  27. package/src/comps/PercentageCharts/comp.tsx +359 -0
  28. package/src/comps/PercentageCharts/constants.tsx +98 -0
  29. package/src/comps/Piecharts/comp.tsx +334 -0
  30. package/src/comps/Piecharts/constants.tsx +48 -0
  31. package/src/comps/Tablecharts/comp.tsx +429 -0
  32. package/src/comps/Tablecharts/constants.tsx +97 -0
  33. package/src/comps/Totalcharts/comp.tsx +463 -0
  34. package/src/comps/Totalcharts/constants.tsx +66 -0
  35. package/src/comps/TwoLineCharts/comp.tsx +350 -0
  36. package/src/comps/TwoLineCharts/constants.tsx +82 -0
  37. package/src/comps/mapComponent/comp.tsx +338 -0
  38. package/src/comps/mapComponent/constants.tsx +2149 -0
  39. package/src/comps/tab/comp.tsx +283 -0
  40. package/src/comps/tab/constants.tsx +79 -0
  41. package/src/configs/barChartConfig.tsx +153 -0
  42. package/src/configs/baseConfig.tsx +66 -0
  43. package/src/configs/candleStickChartConfig.tsx +35 -0
  44. package/src/configs/cartesianAxisConfig.tsx +314 -0
  45. package/src/configs/chartUrls.tsx +9 -0
  46. package/src/configs/echartConfig.tsx +260 -0
  47. package/src/configs/echartsLabelConfig.tsx +47 -0
  48. package/src/configs/echartsLegendConfig.tsx +29 -0
  49. package/src/configs/echartsTitleConfig.tsx +49 -0
  50. package/src/configs/echartsTitleVerticalConfig.tsx +50 -0
  51. package/src/configs/funnelChartConfig.tsx +35 -0
  52. package/src/configs/gaugeChartConfig.tsx +31 -0
  53. package/src/configs/graphChartConfig.tsx +31 -0
  54. package/src/configs/heatmapChartConfig.tsx +31 -0
  55. package/src/configs/legendConfig.tsx +55 -0
  56. package/src/configs/lineChartConfig.tsx +246 -0
  57. package/src/configs/lineChartConfigv2.tsx +246 -0
  58. package/src/configs/mapChartConfig.tsx +106 -0
  59. package/src/configs/mapChartConfigv2.tsx +106 -0
  60. package/src/configs/mixedChartConfig.tsx +21 -0
  61. package/src/configs/pieChartConfig.tsx +156 -0
  62. package/src/configs/radarChartConfig.tsx +31 -0
  63. package/src/configs/sankeyChartConfig.tsx +35 -0
  64. package/src/configs/scatterChartConfig.tsx +152 -0
  65. package/src/configs/sunburstChartConfig.tsx +31 -0
  66. package/src/configs/tabConfig.tsx +0 -0
  67. package/src/configs/tableChartConfig.tsx +81 -0
  68. package/src/configs/themeriverChartConfig.tsx +31 -0
  69. package/src/configs/totalChartConfig.tsx +670 -0
  70. package/src/configs/treeChartConfig.tsx +31 -0
  71. package/src/configs/treemapChartConfig.tsx +31 -0
  72. package/src/controls/AnimationsControls.tsx +3 -0
  73. package/src/controls/AutoHeightControl.tsx +2 -0
  74. package/src/controls/AxisControls.tsx +146 -0
  75. package/src/controls/AxisVisibilityControl.tsx +14 -0
  76. package/src/controls/ChartTypeControl.tsx +15 -0
  77. package/src/controls/ColorScaleControl.tsx +221 -0
  78. package/src/controls/ColumnControl.tsx +204 -0
  79. package/src/controls/ConfigControl.tsx +37 -0
  80. package/src/controls/DirectionControls.tsx +20 -0
  81. package/src/controls/IconControl.tsx +88 -0
  82. package/src/controls/LegendControl.tsx +8 -0
  83. package/src/controls/RowLimitControl.tsx +91 -0
  84. package/src/controls/StyleControls.tsx +22 -0
  85. package/src/controls/TimeControls.tsx +41 -0
  86. package/src/controls/TrendlineControl.tsx +89 -0
  87. package/src/controls/UIEventControl.tsx +21 -0
  88. package/src/controls/index.ts +16 -0
  89. package/src/controls/radioControl.tsx +88 -0
  90. package/src/exposing/index.ts +3 -0
  91. package/src/exposing/setPoint.ts +19 -0
  92. package/src/geo/vn.geo.json +369897 -0
  93. package/src/geo/world.geo.json +32127 -0
  94. package/src/i18n/comps/index.tsx +39 -0
  95. package/src/i18n/comps/locales/en.ts +558 -0
  96. package/src/i18n/comps/locales/enObj.tsx +7186 -0
  97. package/src/i18n/comps/locales/index.ts +7 -0
  98. package/src/i18n/comps/locales/pt.ts +37 -0
  99. package/src/i18n/comps/locales/ptObj.tsx +40 -0
  100. package/src/i18n/comps/locales/types.tsx +622 -0
  101. package/src/i18n/comps/locales/zh.ts +3 -0
  102. package/src/i18n/comps/locales/zhObj.tsx +4 -0
  103. package/src/index.ts +31 -0
  104. package/src/types/global.d.ts +9 -0
  105. package/src/types/lowcoder-sdk.d.ts +578 -0
  106. package/src/utils/chartStyle.util.ts +121 -0
  107. package/src/utils/columnExtractor.util.ts +41 -0
  108. package/src/utils/dataTransform.util.ts +37 -0
  109. package/src/utils/deepEqual.util.ts +29 -0
  110. package/src/utils/echarts.util.tsx +822 -0
  111. package/src/utils/getDataKey.util.ts +115 -0
  112. package/src/utils/getLinearRegression.util.ts +46 -0
  113. package/src/utils/googleMaps.util.ts +28 -0
  114. package/src/utils/isEmpty.util.ts +10 -0
  115. package/src/utils/move.util.ts +6 -0
  116. package/src/utils/selection.util.ts +73 -0
  117. package/src/utils/style.util.ts +315 -0
  118. package/src/utils/time.util.ts +221 -0
  119. package/src/utils/timeFormatter.util.ts +104 -0
  120. package/src/utils/timeProcessing.util.ts +38 -0
  121. package/src/utils/trendline.util.ts +342 -0
  122. package/tsconfig.json +25 -0
  123. 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
+ }