@orbcharts/core 3.0.7 → 4.0.0-alpha.0

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 (196) hide show
  1. package/dist/orbcharts-core.es.js +2826 -6582
  2. package/dist/orbcharts-core.umd.js +6 -6
  3. package/dist/src/OrbCharts.d.ts +18 -0
  4. package/dist/src/chart/createChart.d.ts +3 -0
  5. package/dist/src/chart/createGraphData.d.ts +3 -0
  6. package/dist/src/chart/createGridData.d.ts +3 -0
  7. package/dist/src/chart/createMultivariateData.d.ts +3 -0
  8. package/dist/src/chart/createSeriesData.d.ts +3 -0
  9. package/dist/src/chart/createTreeData.d.ts +3 -0
  10. package/dist/src/chart/defaults.d.ts +5 -0
  11. package/dist/src/defineCanvasLayer.d.ts +16 -0
  12. package/dist/src/defineCanvasPlugin.d.ts +22 -0
  13. package/dist/src/defineSVGLayer.d.ts +16 -0
  14. package/dist/src/defineSVGPlugin.d.ts +22 -0
  15. package/dist/src/index.d.ts +6 -14
  16. package/dist/src/layer/createLayer.d.ts +3 -0
  17. package/dist/src/plugin/createPlugin.d.ts +3 -0
  18. package/dist/src/test/createGraphData.test.d.ts +1 -0
  19. package/dist/src/test/createTreeData.test.d.ts +1 -0
  20. package/dist/src/test/simple-graph-test.d.ts +74 -0
  21. package/dist/src/test/simple-tree-test.d.ts +13 -0
  22. package/dist/src/types/Chart.d.ts +39 -0
  23. package/dist/src/types/ChartContext.d.ts +27 -0
  24. package/dist/src/types/Common.d.ts +3 -0
  25. package/dist/src/types/Encoding.d.ts +33 -0
  26. package/dist/src/types/Event.d.ts +12 -0
  27. package/dist/src/types/Layers.d.ts +55 -0
  28. package/dist/src/types/ModelData.d.ts +70 -0
  29. package/dist/src/types/Plugin.d.ts +39 -0
  30. package/dist/src/types/RawData.d.ts +18 -0
  31. package/dist/src/types/RenderData.d.ts +4 -0
  32. package/dist/src/types/Theme.d.ts +17 -0
  33. package/dist/src/types/Validator.d.ts +20 -0
  34. package/dist/src/types/index.d.ts +12 -0
  35. package/dist/src/utils/aggregateUtils.d.ts +37 -0
  36. package/dist/src/utils/colorUtils.d.ts +22 -0
  37. package/dist/src/utils/commonUtils.d.ts +3 -5
  38. package/dist/src/utils/dom-lifecycle.d.ts +37 -0
  39. package/dist/src/utils/dom.d.ts +6 -0
  40. package/dist/src/utils/index.d.ts +5 -1
  41. package/dist/src/utils/observables.d.ts +1 -25
  42. package/dist/src/utils/orbchartsUtils.d.ts +2 -53
  43. package/dist/src/utils/validator.d.ts +2 -2
  44. package/dist/test/aggregateTest.d.ts +1 -0
  45. package/package.json +24 -13
  46. package/src/OrbCharts.ts +35 -0
  47. package/src/chart/createChart.ts +997 -0
  48. package/src/chart/createGraphData.ts +391 -0
  49. package/src/chart/createGridData.ts +247 -0
  50. package/src/chart/createMultivariateData.ts +181 -0
  51. package/src/chart/createSeriesData.ts +297 -0
  52. package/src/chart/createTreeData.ts +344 -0
  53. package/src/chart/defaults.ts +120 -0
  54. package/src/defineCanvasLayer.ts +24 -0
  55. package/src/defineCanvasPlugin.ts +39 -0
  56. package/src/defineSVGLayer.ts +24 -0
  57. package/src/defineSVGPlugin.ts +39 -0
  58. package/src/index.ts +6 -18
  59. package/src/layer/createLayer.ts +138 -0
  60. package/src/plugin/createPlugin.ts +470 -0
  61. package/src/test/createGraphData.test.ts +103 -0
  62. package/src/test/createTreeData.test.ts +97 -0
  63. package/src/test/simple-graph-test.js +51 -0
  64. package/src/test/simple-tree-test.js +58 -0
  65. package/src/types/Chart.ts +62 -0
  66. package/src/types/ChartContext.ts +42 -0
  67. package/src/types/Common.ts +5 -0
  68. package/src/types/Encoding.ts +43 -0
  69. package/src/types/Event.ts +26 -0
  70. package/src/types/Layers.ts +93 -0
  71. package/src/types/ModelData.ts +95 -0
  72. package/src/types/Plugin.ts +98 -0
  73. package/src/types/RawData.ts +67 -0
  74. package/src/types/RenderData.ts +16 -0
  75. package/src/types/Theme.ts +21 -0
  76. package/src/types/Validator.ts +36 -0
  77. package/src/types/index.ts +12 -0
  78. package/src/utils/aggregateUtils.ts +99 -0
  79. package/src/utils/colorUtils.ts +63 -0
  80. package/src/utils/commonUtils.ts +12 -11
  81. package/src/utils/dom-lifecycle.ts +164 -0
  82. package/src/utils/dom.ts +55 -0
  83. package/src/utils/index.ts +6 -2
  84. package/src/utils/observables.ts +1 -292
  85. package/src/utils/orbchartsUtils.ts +6 -393
  86. package/src/utils/validator.ts +15 -14
  87. package/dist/lib/core-types.d.ts +0 -1
  88. package/dist/src/AbstractChart.d.ts +0 -19
  89. package/dist/src/GridChart.d.ts +0 -6
  90. package/dist/src/MultiGridChart.d.ts +0 -6
  91. package/dist/src/MultiValueChart.d.ts +0 -6
  92. package/dist/src/RelationshipChart.d.ts +0 -6
  93. package/dist/src/SeriesChart.d.ts +0 -6
  94. package/dist/src/TreeChart.d.ts +0 -6
  95. package/dist/src/base/createBaseChart.d.ts +0 -3
  96. package/dist/src/base/createBasePlugin.d.ts +0 -3
  97. package/dist/src/base/validators/chartOptionsValidator.d.ts +0 -3
  98. package/dist/src/base/validators/chartParamsValidator.d.ts +0 -3
  99. package/dist/src/base/validators/elementValidator.d.ts +0 -3
  100. package/dist/src/base/validators/pluginsValidator.d.ts +0 -3
  101. package/dist/src/defaults.d.ts +0 -25
  102. package/dist/src/defineGridPlugin.d.ts +0 -1
  103. package/dist/src/defineMultiGridPlugin.d.ts +0 -1
  104. package/dist/src/defineMultiValuePlugin.d.ts +0 -1
  105. package/dist/src/defineNoneDataPlugin.d.ts +0 -1
  106. package/dist/src/defineRelationshipPlugin.d.ts +0 -1
  107. package/dist/src/defineSeriesPlugin.d.ts +0 -1
  108. package/dist/src/defineTreePlugin.d.ts +0 -1
  109. package/dist/src/grid/computedDataFn.d.ts +0 -4
  110. package/dist/src/grid/contextObserverCallback.d.ts +0 -3
  111. package/dist/src/grid/dataFormatterValidator.d.ts +0 -3
  112. package/dist/src/grid/dataValidator.d.ts +0 -3
  113. package/dist/src/grid/gridObservables.d.ts +0 -64
  114. package/dist/src/multiGrid/computedDataFn.d.ts +0 -3
  115. package/dist/src/multiGrid/contextObserverCallback.d.ts +0 -3
  116. package/dist/src/multiGrid/dataFormatterValidator.d.ts +0 -3
  117. package/dist/src/multiGrid/dataValidator.d.ts +0 -3
  118. package/dist/src/multiGrid/multiGridObservables.d.ts +0 -16
  119. package/dist/src/multiValue/computedDataFn.d.ts +0 -3
  120. package/dist/src/multiValue/contextObserverCallback.d.ts +0 -3
  121. package/dist/src/multiValue/dataFormatterValidator.d.ts +0 -3
  122. package/dist/src/multiValue/dataValidator.d.ts +0 -3
  123. package/dist/src/multiValue/multiValueObservables.d.ts +0 -130
  124. package/dist/src/relationship/computedDataFn.d.ts +0 -3
  125. package/dist/src/relationship/contextObserverCallback.d.ts +0 -3
  126. package/dist/src/relationship/dataFormatterValidator.d.ts +0 -3
  127. package/dist/src/relationship/dataValidator.d.ts +0 -3
  128. package/dist/src/relationship/relationshipObservables.d.ts +0 -13
  129. package/dist/src/series/computedDataFn.d.ts +0 -3
  130. package/dist/src/series/contextObserverCallback.d.ts +0 -3
  131. package/dist/src/series/dataFormatterValidator.d.ts +0 -3
  132. package/dist/src/series/dataValidator.d.ts +0 -3
  133. package/dist/src/series/seriesObservables.d.ts +0 -37
  134. package/dist/src/tree/computedDataFn.d.ts +0 -3
  135. package/dist/src/tree/contextObserverCallback.d.ts +0 -3
  136. package/dist/src/tree/dataFormatterValidator.d.ts +0 -3
  137. package/dist/src/tree/dataValidator.d.ts +0 -3
  138. package/dist/src/tree/treeObservables.d.ts +0 -10
  139. package/dist/src/utils/d3Scale.d.ts +0 -28
  140. package/lib/core-types.ts +0 -7
  141. package/src/AbstractChart.ts +0 -57
  142. package/src/GridChart.ts +0 -25
  143. package/src/MultiGridChart.ts +0 -25
  144. package/src/MultiValueChart.ts +0 -25
  145. package/src/RelationshipChart.ts +0 -25
  146. package/src/SeriesChart.ts +0 -25
  147. package/src/TreeChart.ts +0 -25
  148. package/src/base/createBaseChart.ts +0 -524
  149. package/src/base/createBasePlugin.ts +0 -154
  150. package/src/base/validators/chartOptionsValidator.ts +0 -24
  151. package/src/base/validators/chartParamsValidator.ts +0 -134
  152. package/src/base/validators/elementValidator.ts +0 -14
  153. package/src/base/validators/pluginsValidator.ts +0 -15
  154. package/src/defaults.ts +0 -284
  155. package/src/defineGridPlugin.ts +0 -3
  156. package/src/defineMultiGridPlugin.ts +0 -3
  157. package/src/defineMultiValuePlugin.ts +0 -3
  158. package/src/defineNoneDataPlugin.ts +0 -4
  159. package/src/defineRelationshipPlugin.ts +0 -3
  160. package/src/defineSeriesPlugin.ts +0 -3
  161. package/src/defineTreePlugin.ts +0 -3
  162. package/src/grid/computedDataFn.ts +0 -129
  163. package/src/grid/contextObserverCallback.ts +0 -209
  164. package/src/grid/dataFormatterValidator.ts +0 -126
  165. package/src/grid/dataValidator.ts +0 -13
  166. package/src/grid/gridObservables.ts +0 -699
  167. package/src/multiGrid/computedDataFn.ts +0 -123
  168. package/src/multiGrid/contextObserverCallback.ts +0 -109
  169. package/src/multiGrid/dataFormatterValidator.ts +0 -121
  170. package/src/multiGrid/dataValidator.ts +0 -13
  171. package/src/multiGrid/multiGridObservables.ts +0 -367
  172. package/src/multiValue/computedDataFn.ts +0 -113
  173. package/src/multiValue/contextObserverCallback.ts +0 -328
  174. package/src/multiValue/dataFormatterValidator.ts +0 -95
  175. package/src/multiValue/dataValidator.ts +0 -13
  176. package/src/multiValue/multiValueObservables.ts +0 -865
  177. package/src/relationship/computedDataFn.ts +0 -159
  178. package/src/relationship/contextObserverCallback.ts +0 -80
  179. package/src/relationship/dataFormatterValidator.ts +0 -14
  180. package/src/relationship/dataValidator.ts +0 -14
  181. package/src/relationship/relationshipObservables.ts +0 -85
  182. package/src/series/computedDataFn.ts +0 -88
  183. package/src/series/contextObserverCallback.ts +0 -132
  184. package/src/series/dataFormatterValidator.ts +0 -47
  185. package/src/series/dataValidator.ts +0 -13
  186. package/src/series/seriesObservables.ts +0 -210
  187. package/src/tree/computedDataFn.ts +0 -129
  188. package/src/tree/contextObserverCallback.ts +0 -58
  189. package/src/tree/dataFormatterValidator.ts +0 -14
  190. package/src/tree/dataValidator.ts +0 -14
  191. package/src/tree/treeObservables.ts +0 -106
  192. package/src/utils/d3Scale.ts +0 -198
  193. package/tsconfig.base.json +0 -14
  194. package/tsconfig.json +0 -3
  195. package/vite-env.d.ts +0 -7
  196. package/vite.config.js +0 -23
@@ -0,0 +1,181 @@
1
+ import type { RawData, RawDataColumn, Encoding, ModelDataMultivariate, ModelDatumMultivariate, Theme } from '../types'
2
+ import { getColorByFrom } from '../utils/colorUtils'
3
+
4
+ export const createMultivariateData = (rawData: RawData, encoding: Encoding, theme: Theme): ModelDataMultivariate[] => {
5
+ // 依據 dataset 欄位將資料分組
6
+ const datasetMap = new Map<string, RawDataColumn[]>()
7
+
8
+ // 判斷是一維陣列還是二維陣列
9
+ const is2DArray = Array.isArray(rawData[0])
10
+
11
+ if (is2DArray) {
12
+ // 二維陣列:每個子陣列代表一個 dataset
13
+ (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
14
+ datasetArray.forEach((d) => {
15
+ const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
16
+ if (!datasetMap.has(datasetKey)) {
17
+ datasetMap.set(datasetKey, [])
18
+ }
19
+ datasetMap.get(datasetKey)!.push(d)
20
+ })
21
+ })
22
+ } else {
23
+ // 一維陣列:依據 dataset 欄位分組
24
+ (rawData as RawDataColumn[]).forEach((d) => {
25
+ const datasetKey = (d as any)[encoding.dataset.from] || 'default'
26
+ if (!datasetMap.has(datasetKey)) {
27
+ datasetMap.set(datasetKey, [])
28
+ }
29
+ datasetMap.get(datasetKey)!.push(d)
30
+ })
31
+ }
32
+
33
+ // 建立排序後的 dataset 名稱陣列
34
+ let sortedDatasetNames: string[] = Array.from(datasetMap.keys())
35
+ if (Array.isArray(encoding.dataset.sort)) {
36
+ sortedDatasetNames = encoding.dataset.sort.filter(name => datasetMap.has(name))
37
+ .concat(sortedDatasetNames.filter(name => !encoding.dataset.sort.includes(name)))
38
+ } else if (encoding.dataset.sort === 'original') {
39
+ // original 排序:依照原始資料中 dataset 名稱出現的順序
40
+ const datasetOrder: string[] = []
41
+ if (is2DArray) {
42
+ (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
43
+ datasetArray.forEach((d) => {
44
+ const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
45
+ if (!datasetOrder.includes(datasetKey)) {
46
+ datasetOrder.push(datasetKey)
47
+ }
48
+ })
49
+ })
50
+ } else {
51
+ (rawData as RawDataColumn[]).forEach((d) => {
52
+ const datasetKey = (d as any)[encoding.dataset.from] || 'default'
53
+ if (!datasetOrder.includes(datasetKey)) {
54
+ datasetOrder.push(datasetKey)
55
+ }
56
+ })
57
+ }
58
+ sortedDatasetNames = datasetOrder
59
+ } else if (encoding.dataset.sort === 'alphabetical') {
60
+ // alphabetical 排序:依照字母順序
61
+ sortedDatasetNames.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
62
+ }
63
+
64
+ // 對每個 dataset 進行 multivariate 資料的處理
65
+ const result: ModelDataMultivariate[] = []
66
+ sortedDatasetNames.forEach((datasetName, datasetIndex) => {
67
+ const data = datasetMap.get(datasetName)!
68
+
69
+ // 依據 series 欄位將資料分組
70
+ const seriesMap = new Map<string, RawDataColumn[]>()
71
+ data.forEach((d) => {
72
+ const seriesKey = (d as any)[encoding.series.from] || 'default'
73
+ if (!seriesMap.has(seriesKey)) {
74
+ seriesMap.set(seriesKey, [])
75
+ }
76
+ seriesMap.get(seriesKey)!.push(d)
77
+ })
78
+
79
+ // 建立排序後的系列名稱陣列
80
+ let sortedSeriesNames: string[] = Array.from(seriesMap.keys())
81
+ if (Array.isArray(encoding.series.sort)) {
82
+ sortedSeriesNames = encoding.series.sort.filter(name => seriesMap.has(name))
83
+ .concat(sortedSeriesNames.filter(name => !encoding.series.sort.includes(name)))
84
+ } else if (encoding.series.sort === 'original') {
85
+ // original 排序:依照原始資料中 series 名稱出現的順序
86
+ const seriesOrder: string[] = []
87
+ data.forEach((d) => {
88
+ const seriesKey = (d as any)[encoding.series.from] || 'default'
89
+ if (!seriesOrder.includes(seriesKey)) {
90
+ seriesOrder.push(seriesKey)
91
+ }
92
+ })
93
+ sortedSeriesNames = seriesOrder
94
+ } else if (encoding.series.sort === 'alphabetical') {
95
+ // alphabetical 排序:依照字母順序
96
+ sortedSeriesNames.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
97
+ }
98
+
99
+ // 依據排序後的系列名稱來建立最終的資料結構
100
+ const multivariateData: ModelDatumMultivariate[][] = []
101
+ sortedSeriesNames.forEach((seriesName, seriesIndex) => {
102
+ const seriesItems = seriesMap.get(seriesName)!
103
+
104
+ // 依據 category 欄位將 series 內的資料分組
105
+ const categoryMap = new Map<string, RawDataColumn[]>()
106
+ seriesItems.forEach((d) => {
107
+ const categoryKey = (d as any)[encoding.category.from] || 'default'
108
+ if (!categoryMap.has(categoryKey)) {
109
+ categoryMap.set(categoryKey, [])
110
+ }
111
+ categoryMap.get(categoryKey)!.push(d)
112
+ })
113
+
114
+ // 建立排序後的類別名稱陣列
115
+ let sortedCategoryNames: string[] = Array.from(categoryMap.keys())
116
+ if (Array.isArray(encoding.category.sort)) {
117
+ sortedCategoryNames = encoding.category.sort.filter(name => categoryMap.has(name))
118
+ .concat(sortedCategoryNames.filter(name => !encoding.category.sort.includes(name)))
119
+ } else if (encoding.category.sort === 'original') {
120
+ // original 排序:依照原始資料中 category 名稱出現的順序
121
+ const categoryOrder: string[] = []
122
+ seriesItems.forEach((d) => {
123
+ const categoryKey = (d as any)[encoding.category.from] || 'default'
124
+ if (!categoryOrder.includes(categoryKey)) {
125
+ categoryOrder.push(categoryKey)
126
+ }
127
+ })
128
+ sortedCategoryNames = categoryOrder
129
+ } else if (encoding.category.sort === 'alphabetical') {
130
+ // alphabetical 排序:依照字母順序
131
+ sortedCategoryNames.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
132
+ }
133
+
134
+ // 處理每個 category
135
+ const seriesData: ModelDatumMultivariate[] = []
136
+ sortedCategoryNames.forEach((categoryName, categoryIndex) => {
137
+ const categoryItems = categoryMap.get(categoryName)!
138
+
139
+ // MultivariateData 不進行聚合,直接處理每筆資料
140
+ categoryItems.forEach((d, index) => {
141
+ // 處理 multivariate 欄位,將多個維度的值收集起來
142
+ const multivariate = encoding.multivariate.map((multiVariateDef, multiVariateIndex) => ({
143
+ index: multiVariateIndex,
144
+ name: multiVariateDef.name,
145
+ value: (() => {
146
+ const rawValue = (d as any)[multiVariateDef.from]
147
+ return typeof rawValue === 'number' ? rawValue : null
148
+ })()
149
+ }))
150
+
151
+ const modelData: ModelDatumMultivariate = {
152
+ id: d.id || `${datasetName}-${seriesName}-${categoryName}-${index}`,
153
+ index,
154
+ modelType: 'multivariate',
155
+ name: d.name || '',
156
+ data: d.data,
157
+ value: null, // MultivariateData 的主要值在 values 陣列中,這裡設為 null
158
+ color: getColorByFrom(encoding.color.from, {
159
+ index,
160
+ seriesIndex,
161
+ categoryIndex,
162
+ datasetIndex
163
+ }, theme),
164
+ multivariate,
165
+ series: seriesName,
166
+ seriesIndex: seriesIndex,
167
+ category: categoryName,
168
+ categoryIndex: categoryIndex
169
+ }
170
+ seriesData.push(modelData)
171
+ })
172
+ })
173
+
174
+ multivariateData.push(seriesData)
175
+ })
176
+
177
+ result.push(multivariateData)
178
+ })
179
+
180
+ return result
181
+ }
@@ -0,0 +1,297 @@
1
+ import type { RawData, RawDataColumn, Encoding, ModelDataSeries, ModelDatumSeries, Theme } from '../types'
2
+ import { aggregate } from '../utils/aggregateUtils'
3
+ import { getColorByFrom } from '../utils/colorUtils'
4
+
5
+ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Theme): ModelDataSeries[] => {
6
+ // 依據 dataset 欄位將資料分組
7
+ const datasetMap = new Map<string, RawDataColumn[]>()
8
+
9
+ // 判斷是一維陣列還是二維陣列
10
+ const is2DArray = Array.isArray(rawData[0])
11
+
12
+ if (is2DArray) {
13
+ // 二維陣列:每個子陣列代表一個 dataset
14
+ (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
15
+ datasetArray.forEach((d) => {
16
+ const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
17
+ if (!datasetMap.has(datasetKey)) {
18
+ datasetMap.set(datasetKey, [])
19
+ }
20
+ datasetMap.get(datasetKey)!.push(d)
21
+ })
22
+ })
23
+ } else {
24
+ // 一維陣列:依據 dataset 欄位分組
25
+ (rawData as RawDataColumn[]).forEach((d) => {
26
+ const datasetKey = (d as any)[encoding.dataset.from] || 'default'
27
+ if (!datasetMap.has(datasetKey)) {
28
+ datasetMap.set(datasetKey, [])
29
+ }
30
+ datasetMap.get(datasetKey)!.push(d)
31
+ })
32
+ }
33
+
34
+ // 建立排序後的 dataset 名稱陣列
35
+ let sortedDatasetNames: string[] = Array.from(datasetMap.keys())
36
+ if (Array.isArray(encoding.dataset.sort)) {
37
+ sortedDatasetNames = encoding.dataset.sort.filter(name => datasetMap.has(name))
38
+ .concat(sortedDatasetNames.filter(name => !encoding.dataset.sort.includes(name)))
39
+ } else if (encoding.dataset.sort === 'original') {
40
+ // original 排序:依照原始資料中 dataset 名稱出現的順序
41
+ const datasetOrder: string[] = []
42
+ if (is2DArray) {
43
+ (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
44
+ datasetArray.forEach((d) => {
45
+ const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
46
+ if (!datasetOrder.includes(datasetKey)) {
47
+ datasetOrder.push(datasetKey)
48
+ }
49
+ })
50
+ })
51
+ } else {
52
+ (rawData as RawDataColumn[]).forEach((d) => {
53
+ const datasetKey = (d as any)[encoding.dataset.from] || 'default'
54
+ if (!datasetOrder.includes(datasetKey)) {
55
+ datasetOrder.push(datasetKey)
56
+ }
57
+ })
58
+ }
59
+ sortedDatasetNames = datasetOrder
60
+ } else if (encoding.dataset.sort === 'alphabetical') {
61
+ // alphabetical 排序:依照字母順序
62
+ sortedDatasetNames.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
63
+ }
64
+
65
+ // 對每個 dataset 進行 grid 資料的處理
66
+ const result: ModelDataSeries[] = []
67
+ sortedDatasetNames.forEach((datasetName, datasetIndex) => {
68
+ const data = datasetMap.get(datasetName)!
69
+
70
+ // 依據 series 欄位將資料分組
71
+ const seriesMap = new Map<string, RawDataColumn[]>()
72
+ data.forEach((d) => {
73
+ const seriesKey = (d as any)[encoding.series.from] || 'default'
74
+ if (!seriesMap.has(seriesKey)) {
75
+ seriesMap.set(seriesKey, [])
76
+ }
77
+ seriesMap.get(seriesKey)!.push(d)
78
+ })
79
+
80
+ // 建立排序後的系列名稱陣列
81
+ let sortedSeriesNames: string[] = Array.from(seriesMap.keys())
82
+ if (Array.isArray(encoding.series.sort)) {
83
+ sortedSeriesNames = encoding.series.sort.filter(name => seriesMap.has(name))
84
+ .concat(sortedSeriesNames.filter(name => !encoding.series.sort.includes(name)))
85
+ } else if (encoding.series.sort === 'original') {
86
+ // original 排序:依照原始資料中 series 名稱出現的順序
87
+ const seriesOrder: string[] = []
88
+ data.forEach((d) => {
89
+ const seriesKey = (d as any)[encoding.series.from] || 'default'
90
+ if (!seriesOrder.includes(seriesKey)) {
91
+ seriesOrder.push(seriesKey)
92
+ }
93
+ })
94
+ sortedSeriesNames = seriesOrder
95
+ } else if (encoding.series.sort === 'alphabetical') {
96
+ // alphabetical 排序:依照字母順序
97
+ sortedSeriesNames.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
98
+ }
99
+
100
+ // 在處理各 series 之前,先建立跨所有 series 的全域 category 順序與索引對應表
101
+ // 這樣可確保相同的 category 在不同 series 中擁有相同的 categoryIndex
102
+ const globalCategoryOrder: string[] = []
103
+ if (encoding.category.sort === 'original') {
104
+ // original:依照原始資料中 category 名稱出現的全域順序,不進行排序
105
+ data.forEach((d) => {
106
+ const categoryKey = (d as any)[encoding.category.from] || 'default'
107
+ if (!globalCategoryOrder.includes(categoryKey)) {
108
+ globalCategoryOrder.push(categoryKey)
109
+ }
110
+ })
111
+ } else if (encoding.category.sort === 'alphabetical') {
112
+ // alphabetical:依照字母順序排序所有 category
113
+ const allCategoryNames = new Set<string>()
114
+ data.forEach((d) => {
115
+ allCategoryNames.add((d as any)[encoding.category.from] || 'default')
116
+ })
117
+ globalCategoryOrder.push(...Array.from(allCategoryNames).sort((a, b) => a.localeCompare(b, undefined, { numeric: true })))
118
+ } else if (Array.isArray(encoding.category.sort)) {
119
+ // 自訂順序:指定的 category 優先,其餘依原始順序補上
120
+ const allCategoryNames: string[] = []
121
+ data.forEach((d) => {
122
+ const categoryKey = (d as any)[encoding.category.from] || 'default'
123
+ if (!allCategoryNames.includes(categoryKey)) {
124
+ allCategoryNames.push(categoryKey)
125
+ }
126
+ })
127
+ const sortArray = encoding.category.sort as string[]
128
+ globalCategoryOrder.push(
129
+ ...sortArray.filter(name => allCategoryNames.includes(name)),
130
+ ...allCategoryNames.filter(name => !sortArray.includes(name))
131
+ )
132
+ }
133
+
134
+ // 建立全域 category → categoryIndex 對應表
135
+ const globalCategoryIndexMap = new Map<string, number>()
136
+ globalCategoryOrder.forEach((name, index) => {
137
+ globalCategoryIndexMap.set(name, index)
138
+ })
139
+
140
+ // 依據排序後的系列名稱來建立最終的資料結構
141
+ const gridData: ModelDatumSeries[][] = []
142
+ sortedSeriesNames.forEach((seriesName, seriesIndex) => {
143
+ const seriesItems = seriesMap.get(seriesName)!
144
+
145
+ // 依據 category 欄位將 series 內的資料分組
146
+ const categoryMap = new Map<string, RawDataColumn[]>()
147
+ seriesItems.forEach((d) => {
148
+ const categoryKey = (d as any)[encoding.category.from] || 'default'
149
+ if (!categoryMap.has(categoryKey)) {
150
+ categoryMap.set(categoryKey, [])
151
+ }
152
+ categoryMap.get(categoryKey)!.push(d)
153
+ })
154
+
155
+ // 依據全域 category 順序篩選出此 series 含有的 category(保持全域順序)
156
+ const sortedCategoryNames: string[] = globalCategoryOrder.filter(name => categoryMap.has(name))
157
+
158
+ // 處理每個 category
159
+ const seriesData: ModelDatumSeries[] = []
160
+
161
+ if (encoding.category.sort === 'original' && encoding.value.aggregate === 'none') {
162
+ // sort=original + 不聚合:直接依照 seriesItems 的原始排列順序,不進行分組重排
163
+ seriesItems.forEach((d, index) => {
164
+ const categoryName = (d as any)[encoding.category.from] || 'default'
165
+ const categoryIndex = globalCategoryIndexMap.get(categoryName)!
166
+ const value = (d as any)[encoding.value.from]
167
+ seriesData.push({
168
+ id: d.id || `${datasetName}-${seriesName}-${categoryName}-${index}`,
169
+ index: categoryIndex, // Series 模式下 index 對應 categoryIndex
170
+ modelType: 'series',
171
+ name: d.name || '',
172
+ data: d.data,
173
+ value: typeof value === 'number' ? value : null,
174
+ color: getColorByFrom(encoding.color.from, {
175
+ index: categoryIndex,
176
+ seriesIndex,
177
+ categoryIndex,
178
+ datasetIndex
179
+ }, theme),
180
+ series: seriesName,
181
+ seriesIndex,
182
+ category: categoryName,
183
+ categoryIndex,
184
+ })
185
+ })
186
+
187
+ // 根據 value.sort 對整個 seriesData 進行排序
188
+ if (encoding.value.sort === 'asc') {
189
+ seriesData.sort((a, b) => {
190
+ if (a.value === null && b.value === null) return 0
191
+ if (a.value === null) return 1
192
+ if (b.value === null) return -1
193
+ return a.value - b.value
194
+ })
195
+ } else if (encoding.value.sort === 'desc') {
196
+ seriesData.sort((a, b) => {
197
+ if (a.value === null && b.value === null) return 0
198
+ if (a.value === null) return 1
199
+ if (b.value === null) return -1
200
+ return b.value - a.value
201
+ })
202
+ }
203
+ // 'original' 不需要額外排序,保持原始順序
204
+ } else {
205
+ sortedCategoryNames.forEach((categoryName) => {
206
+ // 使用全域 categoryIndex,確保不同 series 中相同 category 有相同的索引
207
+ const categoryIndex = globalCategoryIndexMap.get(categoryName)!
208
+ const categoryItems = categoryMap.get(categoryName)!
209
+
210
+ if (encoding.value.aggregate === 'none') {
211
+ // 不聚合,保持原始資料結構
212
+ let modelData: ModelDatumSeries[] = categoryItems.map((d, index) => {
213
+ const value = (d as any)[encoding.value.from]
214
+ return {
215
+ id: d.id || `${datasetName}-${seriesName}-${categoryName}-${index}`,
216
+ index: categoryIndex, // Series 模式下 index 對應 categoryIndex
217
+ modelType: 'series',
218
+ name: d.name || '',
219
+ data: d.data,
220
+ value: typeof value === 'number' ? value : null,
221
+ color: getColorByFrom(encoding.color.from, {
222
+ index: categoryIndex,
223
+ seriesIndex,
224
+ categoryIndex,
225
+ datasetIndex
226
+ }, theme),
227
+ series: seriesName,
228
+ seriesIndex,
229
+ category: categoryName,
230
+ categoryIndex,
231
+ }
232
+ })
233
+
234
+ // 根據 value.sort 進行排序
235
+ if (encoding.value.sort === 'asc') {
236
+ modelData.sort((a, b) => {
237
+ if (a.value === null && b.value === null) return 0
238
+ if (a.value === null) return 1
239
+ if (b.value === null) return -1
240
+ return a.value - b.value
241
+ })
242
+ } else if (encoding.value.sort === 'desc') {
243
+ modelData.sort((a, b) => {
244
+ if (a.value === null && b.value === null) return 0
245
+ if (a.value === null) return 1
246
+ if (b.value === null) return -1
247
+ return b.value - a.value
248
+ })
249
+ }
250
+ // 'original' 不需要額外排序,保持原始順序
251
+
252
+ seriesData.push(...modelData)
253
+ } else {
254
+ // 進行聚合,將相同 dataset, series, category 的資料合併為一筆
255
+ const values: (number | null)[] = categoryItems.map(d => {
256
+ if (encoding.value.aggregate === 'count') {
257
+ return 1 // count 聚合時每筆資料計為 1
258
+ }
259
+ const value = (d as any)[encoding.value.from]
260
+ return typeof value === 'number' ? value : null
261
+ })
262
+
263
+ const aggregatedValue = aggregate(values, encoding.value.aggregate)
264
+
265
+ // 合併其他欄位(使用第一筆資料的值)
266
+ const firstItem = categoryItems[0]
267
+ const modelData: ModelDatumSeries = {
268
+ id: firstItem.id || `${datasetName}-${seriesName}-${categoryName}-aggregated`,
269
+ index: categoryIndex, // Series 模式下 index 對應 categoryIndex
270
+ modelType: 'series',
271
+ name: firstItem.name || categoryName,
272
+ data: firstItem.data,
273
+ value: aggregatedValue,
274
+ color: getColorByFrom(encoding.color.from, {
275
+ index: categoryIndex,
276
+ seriesIndex,
277
+ categoryIndex,
278
+ datasetIndex
279
+ }, theme),
280
+ series: seriesName,
281
+ seriesIndex,
282
+ category: categoryName,
283
+ categoryIndex,
284
+ }
285
+ seriesData.push(modelData)
286
+ }
287
+ })
288
+ }
289
+
290
+ gridData.push(seriesData)
291
+ })
292
+
293
+ result.push(gridData)
294
+ })
295
+
296
+ return result
297
+ }