@orbcharts/core 4.0.0-pre-alpha.3 → 4.0.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.
@@ -1,6 +1,7 @@
1
1
  import type { RawData, RawDataColumn, Encoding, ModelDataGraph, ModelDatumGraphNode, ModelDatumGraphEdge, Theme } from '../types'
2
2
  import { aggregate } from '../utils/aggregateUtils'
3
3
  import { getColorByFrom } from '../utils/colorUtils'
4
+ import { resolveGroupKey } from '../utils/encodingUtils'
4
5
 
5
6
  export const createGraphData = (rawData: RawData, encoding: Encoding, theme: Theme): ModelDataGraph[] => {
6
7
  // 判斷是一維陣列還是二維陣列
@@ -38,7 +39,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
38
39
  (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
39
40
  datasetArray.forEach((d) => {
40
41
  if (!d.source || !d.target) {
41
- const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
42
+ const datasetKey = resolveGroupKey(d, encoding.dataset, `dataset-${datasetIndex}`)
42
43
  if (!datasetMap.has(datasetKey)) {
43
44
  datasetMap.set(datasetKey, [])
44
45
  }
@@ -48,7 +49,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
48
49
  })
49
50
  } else {
50
51
  nodeData.forEach((d) => {
51
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
52
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
52
53
  if (!datasetMap.has(datasetKey)) {
53
54
  datasetMap.set(datasetKey, [])
54
55
  }
@@ -67,7 +68,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
67
68
  (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
68
69
  datasetArray.forEach((d) => {
69
70
  if (!d.source || !d.target) {
70
- const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
71
+ const datasetKey = resolveGroupKey(d, encoding.dataset, `dataset-${datasetIndex}`)
71
72
  if (!datasetOrder.includes(datasetKey)) {
72
73
  datasetOrder.push(datasetKey)
73
74
  }
@@ -76,7 +77,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
76
77
  })
77
78
  } else {
78
79
  nodeData.forEach((d) => {
79
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
80
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
80
81
  if (!datasetOrder.includes(datasetKey)) {
81
82
  datasetOrder.push(datasetKey)
82
83
  }
@@ -98,7 +99,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
98
99
  // 依據 series 欄位將資料分組
99
100
  const seriesMap = new Map<string, RawDataColumn[]>()
100
101
  data.forEach((d) => {
101
- const seriesKey = (d as any)[encoding.series.from] || 'default'
102
+ const seriesKey = resolveGroupKey(d, encoding.series, '')
102
103
  if (!seriesMap.has(seriesKey)) {
103
104
  seriesMap.set(seriesKey, [])
104
105
  }
@@ -113,7 +114,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
113
114
  } else if (encoding.series.sort === 'original') {
114
115
  const seriesOrder: string[] = []
115
116
  data.forEach((d) => {
116
- const seriesKey = (d as any)[encoding.series.from] || 'default'
117
+ const seriesKey = resolveGroupKey(d, encoding.series, '')
117
118
  if (!seriesOrder.includes(seriesKey)) {
118
119
  seriesOrder.push(seriesKey)
119
120
  }
@@ -130,7 +131,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
130
131
  // 依據 category 欄位將 series 內的資料分組
131
132
  const categoryMap = new Map<string, RawDataColumn[]>()
132
133
  seriesItems.forEach((d) => {
133
- const categoryKey = (d as any)[encoding.category.from] || 'default'
134
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
134
135
  if (!categoryMap.has(categoryKey)) {
135
136
  categoryMap.set(categoryKey, [])
136
137
  }
@@ -145,7 +146,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
145
146
  } else if (encoding.category.sort === 'original') {
146
147
  const categoryOrder: string[] = []
147
148
  seriesItems.forEach((d) => {
148
- const categoryKey = (d as any)[encoding.category.from] || 'default'
149
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
149
150
  if (!categoryOrder.includes(categoryKey)) {
150
151
  categoryOrder.push(categoryKey)
151
152
  }
@@ -170,7 +171,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
170
171
  name: d.name || '',
171
172
  data: d.data,
172
173
  value: typeof value === 'number' ? value : null,
173
- color: getColorByFrom(encoding.color.from, {
174
+ color: getColorByFrom(encoding.color.by, {
174
175
  index: categoryIndex,
175
176
  seriesIndex,
176
177
  categoryIndex,
@@ -222,7 +223,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
222
223
  name: firstItem.name || categoryName,
223
224
  data: firstItem.data,
224
225
  value: aggregatedValue,
225
- color: getColorByFrom(encoding.color.from, {
226
+ color: getColorByFrom(encoding.color.by, {
226
227
  index: categoryIndex,
227
228
  seriesIndex,
228
229
  categoryIndex,
@@ -280,7 +281,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
280
281
 
281
282
  // 依據 dataset 分組處理 edges(這裡使用所有邊的資料,但只處理目前 dataset 的邊)
282
283
  const datasetEdges = edgeData.filter(d => {
283
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
284
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
284
285
  return datasetKey === datasetName
285
286
  })
286
287
 
@@ -289,8 +290,8 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
289
290
  datasetEdges.forEach((d) => {
290
291
  const source = d.source!
291
292
  const target = d.target!
292
- const series = (d as any)[encoding.series.from] || 'default'
293
- const category = (d as any)[encoding.category.from] || 'default'
293
+ const series = resolveGroupKey(d, encoding.series, '')
294
+ const category = resolveGroupKey(d, encoding.category, '')
294
295
  const groupKey = `${source}-${target}-${series}-${category}`
295
296
 
296
297
  if (!edgeGroupMap.has(groupKey)) {
@@ -312,8 +313,8 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
312
313
  // 不聚合,保持原始資料結構
313
314
  groupEdges.forEach((d, index) => {
314
315
  const value = (d as any)[encoding.value.from]
315
- const edgeSeries = (d as any)[encoding.series.from] || 'default'
316
- const edgeCategory = (d as any)[encoding.category.from] || 'default'
316
+ const edgeSeries = resolveGroupKey(d, encoding.series, '')
317
+ const edgeCategory = resolveGroupKey(d, encoding.category, '')
317
318
  const edgeSeriesIndex = getEdgeSeriesIndex(edgeSeries)
318
319
  const edgeCategoryIndex = getEdgeCategoryIndex(edgeCategory)
319
320
  const edge: ModelDatumGraphEdge = {
@@ -323,7 +324,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
323
324
  name: d.name || '',
324
325
  data: d.data,
325
326
  value: typeof value === 'number' ? value : null,
326
- color: getColorByFrom(encoding.color.from, {
327
+ color: getColorByFrom(encoding.color.by, {
327
328
  index: edges.length,
328
329
  seriesIndex: edgeSeriesIndex,
329
330
  categoryIndex: edgeCategoryIndex,
@@ -352,8 +353,8 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
352
353
 
353
354
  const aggregatedValue = aggregate(values, encoding.value.aggregate)
354
355
 
355
- const edgeSeries = (firstEdge as any)[encoding.series.from] || 'default'
356
- const edgeCategory = (firstEdge as any)[encoding.category.from] || 'default'
356
+ const edgeSeries = resolveGroupKey(firstEdge, encoding.series, '')
357
+ const edgeCategory = resolveGroupKey(firstEdge, encoding.category, '')
357
358
  const edgeSeriesIndex = getEdgeSeriesIndex(edgeSeries)
358
359
  const edgeCategoryIndex = getEdgeCategoryIndex(edgeCategory)
359
360
 
@@ -364,7 +365,7 @@ export const createGraphData = (rawData: RawData, encoding: Encoding, theme: The
364
365
  name: firstEdge.name || '',
365
366
  data: firstEdge.data,
366
367
  value: aggregatedValue,
367
- color: getColorByFrom(encoding.color.from, {
368
+ color: getColorByFrom(encoding.color.by, {
368
369
  index: edges.length,
369
370
  seriesIndex: edgeSeriesIndex,
370
371
  categoryIndex: edgeCategoryIndex,
@@ -1,6 +1,7 @@
1
1
  import type { RawData, RawDataColumn, Encoding, ModelDataGrid, ModelDatumGrid, Theme } from '../types'
2
2
  import { aggregate } from '../utils/aggregateUtils'
3
3
  import { getColorByFrom } from '../utils/colorUtils'
4
+ import { resolveGroupKey } from '../utils/encodingUtils'
4
5
 
5
6
  export const createGridData = (rawData: RawData, encoding: Encoding, theme: Theme): ModelDataGrid[] => {
6
7
  // 依據 dataset 欄位將資料分組
@@ -13,7 +14,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
13
14
  // 二維陣列:每個子陣列代表一個 dataset
14
15
  (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
15
16
  datasetArray.forEach((d) => {
16
- const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
17
+ const datasetKey = resolveGroupKey(d, encoding.dataset, `dataset-${datasetIndex}`)
17
18
  if (!datasetMap.has(datasetKey)) {
18
19
  datasetMap.set(datasetKey, [])
19
20
  }
@@ -23,7 +24,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
23
24
  } else {
24
25
  // 一維陣列:依據 dataset 欄位分組
25
26
  (rawData as RawDataColumn[]).forEach((d) => {
26
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
27
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
27
28
  if (!datasetMap.has(datasetKey)) {
28
29
  datasetMap.set(datasetKey, [])
29
30
  }
@@ -42,7 +43,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
42
43
  if (is2DArray) {
43
44
  (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
44
45
  datasetArray.forEach((d) => {
45
- const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
46
+ const datasetKey = resolveGroupKey(d, encoding.dataset, `dataset-${datasetIndex}`)
46
47
  if (!datasetOrder.includes(datasetKey)) {
47
48
  datasetOrder.push(datasetKey)
48
49
  }
@@ -50,7 +51,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
50
51
  })
51
52
  } else {
52
53
  (rawData as RawDataColumn[]).forEach((d) => {
53
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
54
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
54
55
  if (!datasetOrder.includes(datasetKey)) {
55
56
  datasetOrder.push(datasetKey)
56
57
  }
@@ -70,7 +71,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
70
71
  // 依據 series 欄位將資料分組
71
72
  const seriesMap = new Map<string, RawDataColumn[]>()
72
73
  data.forEach((d) => {
73
- const seriesKey = (d as any)[encoding.series.from] || 'default'
74
+ const seriesKey = resolveGroupKey(d, encoding.series, '')
74
75
  if (!seriesMap.has(seriesKey)) {
75
76
  seriesMap.set(seriesKey, [])
76
77
  }
@@ -86,7 +87,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
86
87
  // original 排序:依照原始資料中 series 名稱出現的順序
87
88
  const seriesOrder: string[] = []
88
89
  data.forEach((d) => {
89
- const seriesKey = (d as any)[encoding.series.from] || 'default'
90
+ const seriesKey = resolveGroupKey(d, encoding.series, '')
90
91
  if (!seriesOrder.includes(seriesKey)) {
91
92
  seriesOrder.push(seriesKey)
92
93
  }
@@ -100,7 +101,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
100
101
  // 建立全局的 category 排序(跨該 dataset 下所有 series)
101
102
  const globalCategoryOrder: string[] = []
102
103
  data.forEach((d) => {
103
- const categoryKey = (d as any)[encoding.category.from] || 'default'
104
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
104
105
  if (!globalCategoryOrder.includes(categoryKey)) {
105
106
  globalCategoryOrder.push(categoryKey)
106
107
  }
@@ -126,7 +127,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
126
127
  // 依據 category 欄位將 series 內的資料分組
127
128
  const categoryMap = new Map<string, RawDataColumn[]>()
128
129
  seriesItems.forEach((d) => {
129
- const categoryKey = (d as any)[encoding.category.from] || 'default'
130
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
130
131
  if (!categoryMap.has(categoryKey)) {
131
132
  categoryMap.set(categoryKey, [])
132
133
  }
@@ -147,7 +148,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
147
148
  name: categoryName,
148
149
  data: undefined,
149
150
  value: null,
150
- color: getColorByFrom(encoding.color.from, {
151
+ color: getColorByFrom(encoding.color.by, {
151
152
  index: categoryIndex,
152
153
  seriesIndex,
153
154
  categoryIndex,
@@ -169,7 +170,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
169
170
  name: d.name || '',
170
171
  data: d.data,
171
172
  value: typeof value === 'number' ? value : null,
172
- color: getColorByFrom(encoding.color.from, {
173
+ color: getColorByFrom(encoding.color.by, {
173
174
  index: categoryIndex,
174
175
  seriesIndex,
175
176
  categoryIndex,
@@ -222,7 +223,7 @@ export const createGridData = (rawData: RawData, encoding: Encoding, theme: Them
222
223
  name: firstItem.name || categoryName,
223
224
  data: firstItem.data,
224
225
  value: aggregatedValue,
225
- color: getColorByFrom(encoding.color.from, {
226
+ color: getColorByFrom(encoding.color.by, {
226
227
  index: categoryIndex,
227
228
  seriesIndex,
228
229
  categoryIndex,
@@ -1,5 +1,6 @@
1
1
  import type { RawData, RawDataColumn, Encoding, ModelDataMultivariate, ModelDatumMultivariate, Theme } from '../types'
2
2
  import { getColorByFrom } from '../utils/colorUtils'
3
+ import { resolveGroupKey } from '../utils/encodingUtils'
3
4
 
4
5
  export const createMultivariateData = (rawData: RawData, encoding: Encoding, theme: Theme): ModelDataMultivariate[] => {
5
6
  // 依據 dataset 欄位將資料分組
@@ -12,7 +13,7 @@ export const createMultivariateData = (rawData: RawData, encoding: Encoding, the
12
13
  // 二維陣列:每個子陣列代表一個 dataset
13
14
  (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
14
15
  datasetArray.forEach((d) => {
15
- const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
16
+ const datasetKey = resolveGroupKey(d, encoding.dataset, `dataset-${datasetIndex}`)
16
17
  if (!datasetMap.has(datasetKey)) {
17
18
  datasetMap.set(datasetKey, [])
18
19
  }
@@ -22,7 +23,7 @@ export const createMultivariateData = (rawData: RawData, encoding: Encoding, the
22
23
  } else {
23
24
  // 一維陣列:依據 dataset 欄位分組
24
25
  (rawData as RawDataColumn[]).forEach((d) => {
25
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
26
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
26
27
  if (!datasetMap.has(datasetKey)) {
27
28
  datasetMap.set(datasetKey, [])
28
29
  }
@@ -41,7 +42,7 @@ export const createMultivariateData = (rawData: RawData, encoding: Encoding, the
41
42
  if (is2DArray) {
42
43
  (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
43
44
  datasetArray.forEach((d) => {
44
- const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
45
+ const datasetKey = resolveGroupKey(d, encoding.dataset, `dataset-${datasetIndex}`)
45
46
  if (!datasetOrder.includes(datasetKey)) {
46
47
  datasetOrder.push(datasetKey)
47
48
  }
@@ -49,7 +50,7 @@ export const createMultivariateData = (rawData: RawData, encoding: Encoding, the
49
50
  })
50
51
  } else {
51
52
  (rawData as RawDataColumn[]).forEach((d) => {
52
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
53
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
53
54
  if (!datasetOrder.includes(datasetKey)) {
54
55
  datasetOrder.push(datasetKey)
55
56
  }
@@ -69,7 +70,7 @@ export const createMultivariateData = (rawData: RawData, encoding: Encoding, the
69
70
  // 依據 series 欄位將資料分組
70
71
  const seriesMap = new Map<string, RawDataColumn[]>()
71
72
  data.forEach((d) => {
72
- const seriesKey = (d as any)[encoding.series.from] || 'default'
73
+ const seriesKey = resolveGroupKey(d, encoding.series, '')
73
74
  if (!seriesMap.has(seriesKey)) {
74
75
  seriesMap.set(seriesKey, [])
75
76
  }
@@ -85,7 +86,7 @@ export const createMultivariateData = (rawData: RawData, encoding: Encoding, the
85
86
  // original 排序:依照原始資料中 series 名稱出現的順序
86
87
  const seriesOrder: string[] = []
87
88
  data.forEach((d) => {
88
- const seriesKey = (d as any)[encoding.series.from] || 'default'
89
+ const seriesKey = resolveGroupKey(d, encoding.series, '')
89
90
  if (!seriesOrder.includes(seriesKey)) {
90
91
  seriesOrder.push(seriesKey)
91
92
  }
@@ -104,7 +105,7 @@ export const createMultivariateData = (rawData: RawData, encoding: Encoding, the
104
105
  // 依據 category 欄位將 series 內的資料分組
105
106
  const categoryMap = new Map<string, RawDataColumn[]>()
106
107
  seriesItems.forEach((d) => {
107
- const categoryKey = (d as any)[encoding.category.from] || 'default'
108
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
108
109
  if (!categoryMap.has(categoryKey)) {
109
110
  categoryMap.set(categoryKey, [])
110
111
  }
@@ -120,7 +121,7 @@ export const createMultivariateData = (rawData: RawData, encoding: Encoding, the
120
121
  // original 排序:依照原始資料中 category 名稱出現的順序
121
122
  const categoryOrder: string[] = []
122
123
  seriesItems.forEach((d) => {
123
- const categoryKey = (d as any)[encoding.category.from] || 'default'
124
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
124
125
  if (!categoryOrder.includes(categoryKey)) {
125
126
  categoryOrder.push(categoryKey)
126
127
  }
@@ -155,7 +156,7 @@ export const createMultivariateData = (rawData: RawData, encoding: Encoding, the
155
156
  name: d.name || '',
156
157
  data: d.data,
157
158
  value: null, // MultivariateData 的主要值在 values 陣列中,這裡設為 null
158
- color: getColorByFrom(encoding.color.from, {
159
+ color: getColorByFrom(encoding.color.by, {
159
160
  index,
160
161
  seriesIndex,
161
162
  categoryIndex,
@@ -1,6 +1,7 @@
1
1
  import type { RawData, RawDataColumn, Encoding, ModelDataSeries, ModelDatumSeries, Theme } from '../types'
2
2
  import { aggregate } from '../utils/aggregateUtils'
3
3
  import { getColorByFrom } from '../utils/colorUtils'
4
+ import { resolveGroupKey } from '../utils/encodingUtils'
4
5
 
5
6
  export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Theme): ModelDataSeries[] => {
6
7
  // 依據 dataset 欄位將資料分組
@@ -13,7 +14,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
13
14
  // 二維陣列:每個子陣列代表一個 dataset
14
15
  (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
15
16
  datasetArray.forEach((d) => {
16
- const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
17
+ const datasetKey = resolveGroupKey(d, encoding.dataset, `dataset-${datasetIndex}`)
17
18
  if (!datasetMap.has(datasetKey)) {
18
19
  datasetMap.set(datasetKey, [])
19
20
  }
@@ -23,7 +24,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
23
24
  } else {
24
25
  // 一維陣列:依據 dataset 欄位分組
25
26
  (rawData as RawDataColumn[]).forEach((d) => {
26
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
27
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
27
28
  if (!datasetMap.has(datasetKey)) {
28
29
  datasetMap.set(datasetKey, [])
29
30
  }
@@ -42,7 +43,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
42
43
  if (is2DArray) {
43
44
  (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
44
45
  datasetArray.forEach((d) => {
45
- const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
46
+ const datasetKey = resolveGroupKey(d, encoding.dataset, `dataset-${datasetIndex}`)
46
47
  if (!datasetOrder.includes(datasetKey)) {
47
48
  datasetOrder.push(datasetKey)
48
49
  }
@@ -50,7 +51,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
50
51
  })
51
52
  } else {
52
53
  (rawData as RawDataColumn[]).forEach((d) => {
53
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
54
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
54
55
  if (!datasetOrder.includes(datasetKey)) {
55
56
  datasetOrder.push(datasetKey)
56
57
  }
@@ -70,7 +71,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
70
71
  // 依據 series 欄位將資料分組
71
72
  const seriesMap = new Map<string, RawDataColumn[]>()
72
73
  data.forEach((d) => {
73
- const seriesKey = (d as any)[encoding.series.from] || 'default'
74
+ const seriesKey = resolveGroupKey(d, encoding.series, '')
74
75
  if (!seriesMap.has(seriesKey)) {
75
76
  seriesMap.set(seriesKey, [])
76
77
  }
@@ -86,7 +87,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
86
87
  // original 排序:依照原始資料中 series 名稱出現的順序
87
88
  const seriesOrder: string[] = []
88
89
  data.forEach((d) => {
89
- const seriesKey = (d as any)[encoding.series.from] || 'default'
90
+ const seriesKey = resolveGroupKey(d, encoding.series, '')
90
91
  if (!seriesOrder.includes(seriesKey)) {
91
92
  seriesOrder.push(seriesKey)
92
93
  }
@@ -103,7 +104,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
103
104
  if (encoding.category.sort === 'original') {
104
105
  // original:依照原始資料中 category 名稱出現的全域順序,不進行排序
105
106
  data.forEach((d) => {
106
- const categoryKey = (d as any)[encoding.category.from] || 'default'
107
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
107
108
  if (!globalCategoryOrder.includes(categoryKey)) {
108
109
  globalCategoryOrder.push(categoryKey)
109
110
  }
@@ -112,14 +113,14 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
112
113
  // alphabetical:依照字母順序排序所有 category
113
114
  const allCategoryNames = new Set<string>()
114
115
  data.forEach((d) => {
115
- allCategoryNames.add((d as any)[encoding.category.from] || 'default')
116
+ allCategoryNames.add(resolveGroupKey(d, encoding.category, ''))
116
117
  })
117
118
  globalCategoryOrder.push(...Array.from(allCategoryNames).sort((a, b) => a.localeCompare(b, undefined, { numeric: true })))
118
119
  } else if (Array.isArray(encoding.category.sort)) {
119
120
  // 自訂順序:指定的 category 優先,其餘依原始順序補上
120
121
  const allCategoryNames: string[] = []
121
122
  data.forEach((d) => {
122
- const categoryKey = (d as any)[encoding.category.from] || 'default'
123
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
123
124
  if (!allCategoryNames.includes(categoryKey)) {
124
125
  allCategoryNames.push(categoryKey)
125
126
  }
@@ -145,7 +146,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
145
146
  // 依據 category 欄位將 series 內的資料分組
146
147
  const categoryMap = new Map<string, RawDataColumn[]>()
147
148
  seriesItems.forEach((d) => {
148
- const categoryKey = (d as any)[encoding.category.from] || 'default'
149
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
149
150
  if (!categoryMap.has(categoryKey)) {
150
151
  categoryMap.set(categoryKey, [])
151
152
  }
@@ -161,7 +162,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
161
162
  if (encoding.category.sort === 'original' && encoding.value.aggregate === 'none') {
162
163
  // sort=original + 不聚合:直接依照 seriesItems 的原始排列順序,不進行分組重排
163
164
  seriesItems.forEach((d, index) => {
164
- const categoryName = (d as any)[encoding.category.from] || 'default'
165
+ const categoryName = resolveGroupKey(d, encoding.category, '')
165
166
  const categoryIndex = globalCategoryIndexMap.get(categoryName)!
166
167
  const value = (d as any)[encoding.value.from]
167
168
  seriesData.push({
@@ -171,7 +172,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
171
172
  name: d.name || '',
172
173
  data: d.data,
173
174
  value: typeof value === 'number' ? value : null,
174
- color: getColorByFrom(encoding.color.from, {
175
+ color: getColorByFrom(encoding.color.by, {
175
176
  index: categoryIndex,
176
177
  seriesIndex,
177
178
  categoryIndex,
@@ -218,7 +219,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
218
219
  name: d.name || '',
219
220
  data: d.data,
220
221
  value: typeof value === 'number' ? value : null,
221
- color: getColorByFrom(encoding.color.from, {
222
+ color: getColorByFrom(encoding.color.by, {
222
223
  index: categoryIndex,
223
224
  seriesIndex,
224
225
  categoryIndex,
@@ -271,7 +272,7 @@ export const createSeriesData = (rawData: RawData, encoding: Encoding, theme: Th
271
272
  name: firstItem.name || categoryName,
272
273
  data: firstItem.data,
273
274
  value: aggregatedValue,
274
- color: getColorByFrom(encoding.color.from, {
275
+ color: getColorByFrom(encoding.color.by, {
275
276
  index: categoryIndex,
276
277
  seriesIndex,
277
278
  categoryIndex,
@@ -1,6 +1,7 @@
1
1
  import type { RawData, RawDataColumn, Encoding, ModelDataTree, ModelDatumTree, Theme } from '../types'
2
2
  import { aggregate } from '../utils/aggregateUtils'
3
3
  import { getColorByFrom } from '../utils/colorUtils'
4
+ import { resolveGroupKey } from '../utils/encodingUtils'
4
5
 
5
6
  export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Theme): ModelDataTree[] => {
6
7
  // 依據 dataset 欄位將資料分組
@@ -13,7 +14,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
13
14
  // 二維陣列:每個子陣列代表一個 dataset
14
15
  (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
15
16
  datasetArray.forEach((d) => {
16
- const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
17
+ const datasetKey = resolveGroupKey(d, encoding.dataset, `dataset-${datasetIndex}`)
17
18
  if (!datasetMap.has(datasetKey)) {
18
19
  datasetMap.set(datasetKey, [])
19
20
  }
@@ -23,7 +24,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
23
24
  } else {
24
25
  // 一維陣列:依據 dataset 欄位分組
25
26
  (rawData as RawDataColumn[]).forEach((d) => {
26
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
27
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
27
28
  if (!datasetMap.has(datasetKey)) {
28
29
  datasetMap.set(datasetKey, [])
29
30
  }
@@ -41,7 +42,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
41
42
  if (is2DArray) {
42
43
  (rawData as RawDataColumn[][]).forEach((datasetArray, datasetIndex) => {
43
44
  datasetArray.forEach((d) => {
44
- const datasetKey = (d as any)[encoding.dataset.from] || `dataset-${datasetIndex}`
45
+ const datasetKey = resolveGroupKey(d, encoding.dataset, `dataset-${datasetIndex}`)
45
46
  if (!datasetOrder.includes(datasetKey)) {
46
47
  datasetOrder.push(datasetKey)
47
48
  }
@@ -49,7 +50,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
49
50
  })
50
51
  } else {
51
52
  (rawData as RawDataColumn[]).forEach((d) => {
52
- const datasetKey = (d as any)[encoding.dataset.from] || 'default'
53
+ const datasetKey = resolveGroupKey(d, encoding.dataset, '')
53
54
  if (!datasetOrder.includes(datasetKey)) {
54
55
  datasetOrder.push(datasetKey)
55
56
  }
@@ -68,7 +69,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
68
69
  // 建立 dataset 內排序後的 series 名稱陣列(僅作為屬性與色彩來源,不做拆樹)
69
70
  const seriesMap = new Map<string, RawDataColumn[]>()
70
71
  data.forEach((d) => {
71
- const seriesKey = (d as any)[encoding.series.from] || 'default'
72
+ const seriesKey = resolveGroupKey(d, encoding.series, '')
72
73
  if (!seriesMap.has(seriesKey)) {
73
74
  seriesMap.set(seriesKey, [])
74
75
  }
@@ -81,7 +82,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
81
82
  } else if (encoding.series.sort === 'original') {
82
83
  const seriesOrder: string[] = []
83
84
  data.forEach((d) => {
84
- const seriesKey = (d as any)[encoding.series.from] || 'default'
85
+ const seriesKey = resolveGroupKey(d, encoding.series, '')
85
86
  if (!seriesOrder.includes(seriesKey)) {
86
87
  seriesOrder.push(seriesKey)
87
88
  }
@@ -94,7 +95,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
94
95
  // 建立 dataset 內排序後的 category 名稱陣列(僅作為屬性與色彩來源,不做拆樹)
95
96
  const categoryMap = new Map<string, RawDataColumn[]>()
96
97
  data.forEach((d) => {
97
- const categoryKey = (d as any)[encoding.category.from] || 'default'
98
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
98
99
  if (!categoryMap.has(categoryKey)) {
99
100
  categoryMap.set(categoryKey, [])
100
101
  }
@@ -107,7 +108,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
107
108
  } else if (encoding.category.sort === 'original') {
108
109
  const categoryOrder: string[] = []
109
110
  data.forEach((d) => {
110
- const categoryKey = (d as any)[encoding.category.from] || 'default'
111
+ const categoryKey = resolveGroupKey(d, encoding.category, '')
111
112
  if (!categoryOrder.includes(categoryKey)) {
112
113
  categoryOrder.push(categoryKey)
113
114
  }
@@ -128,7 +129,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
128
129
  })
129
130
 
130
131
  let globalNodeIndex = 0
131
- const defaultSeriesName = sortedSeriesNames[0] || 'default'
132
+ const defaultSeriesName = sortedSeriesNames[0] || ''
132
133
  const defaultSeriesIndex = sortedSeriesNames.indexOf(defaultSeriesName)
133
134
 
134
135
  // 為每個 id 建立 ModelDatumTree,並進行聚合
@@ -136,9 +137,9 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
136
137
 
137
138
  Array.from(nodeMap.entries()).forEach(([nodeId, nodeItems]) => {
138
139
  const firstItem = nodeItems[0]
139
- const seriesName = (firstItem as any)[encoding.series.from] || 'default'
140
+ const seriesName = resolveGroupKey(firstItem, encoding.series, '')
140
141
  const seriesIndex = sortedSeriesNames.indexOf(seriesName)
141
- const categoryName = (firstItem as any)[encoding.category.from] || 'default'
142
+ const categoryName = resolveGroupKey(firstItem, encoding.category, '')
142
143
  const categoryIndex = sortedCategoryNames.indexOf(categoryName)
143
144
 
144
145
  if (encoding.value.aggregate === 'none') {
@@ -151,7 +152,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
151
152
  name: firstItem.name || nodeId,
152
153
  data: firstItem.data,
153
154
  value: typeof value === 'number' ? value : null,
154
- color: getColorByFrom(encoding.color.from, {
155
+ color: getColorByFrom(encoding.color.by, {
155
156
  index: globalNodeIndex - 1,
156
157
  seriesIndex,
157
158
  categoryIndex,
@@ -187,7 +188,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
187
188
  name: firstItem.name || nodeId,
188
189
  data: firstItem.data,
189
190
  value: aggregatedValue,
190
- color: getColorByFrom(encoding.color.from, {
191
+ color: getColorByFrom(encoding.color.by, {
191
192
  index: globalNodeIndex - 1,
192
193
  seriesIndex,
193
194
  categoryIndex,
@@ -283,7 +284,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
283
284
  name: 'Empty Tree',
284
285
  data: {},
285
286
  value: null,
286
- color: getColorByFrom(encoding.color.from, {
287
+ color: getColorByFrom(encoding.color.by, {
287
288
  index: globalNodeIndex - 1,
288
289
  seriesIndex: defaultSeriesIndex < 0 ? 0 : defaultSeriesIndex,
289
290
  categoryIndex: 0,
@@ -296,7 +297,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
296
297
  children: [],
297
298
  series: defaultSeriesName,
298
299
  seriesIndex: defaultSeriesIndex < 0 ? 0 : defaultSeriesIndex,
299
- category: 'default',
300
+ category: '',
300
301
  categoryIndex: 0,
301
302
  })
302
303
  } else if (trees.length === 1) {
@@ -310,7 +311,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
310
311
  name: `Virtual Root (${datasetName})`,
311
312
  data: {},
312
313
  value: null,
313
- color: getColorByFrom(encoding.color.from, {
314
+ color: getColorByFrom(encoding.color.by, {
314
315
  index: globalNodeIndex - 1,
315
316
  seriesIndex: defaultSeriesIndex < 0 ? 0 : defaultSeriesIndex,
316
317
  categoryIndex: 0,
@@ -333,7 +334,7 @@ export const createTreeData = (rawData: RawData, encoding: Encoding, theme: Them
333
334
  }),
334
335
  series: defaultSeriesName,
335
336
  seriesIndex: defaultSeriesIndex < 0 ? 0 : defaultSeriesIndex,
336
- category: 'default',
337
+ category: '',
337
338
  categoryIndex: 0,
338
339
  }
339
340
  result.push(virtualRoot)