@orbcharts/core 4.0.0-pre-alpha.4 → 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.
@@ -13,15 +13,18 @@ export const DEFAULT_SIZE_CONFIG: SizeConfig = {
13
13
  export const DEFAULT_DATA_ENCODING: Encoding = {
14
14
  dataset: {
15
15
  from: 'dataset',
16
- sort: 'original'
16
+ sort: 'original',
17
+ ignore: false
17
18
  },
18
19
  series: {
19
20
  from: 'series',
20
- sort: 'original'
21
+ sort: 'original',
22
+ ignore: false
21
23
  },
22
24
  category: {
23
25
  from: 'category',
24
- sort: 'original'
26
+ sort: 'original',
27
+ ignore: false
25
28
  },
26
29
  value: {
27
30
  from: 'value',
@@ -34,7 +37,7 @@ export const DEFAULT_DATA_ENCODING: Encoding = {
34
37
  { from: 'z', name: 'z' },
35
38
  ],
36
39
  color: {
37
- from: 'series',
40
+ by: 'series',
38
41
  }
39
42
  }
40
43
 
@@ -61,16 +64,26 @@ export const DEFAULT_THEME: Theme = {
61
64
  colors: {
62
65
  light: {
63
66
  data: [
64
- "#0088FF",
65
- "#FF3232",
66
- "#38BEA8",
67
- "#6F3BD5",
68
- "#314285",
69
- "#42C724",
70
- "#D52580",
71
- "#F4721B",
72
- "#D117EA",
73
- "#7E7D7D"
67
+ // "#0088FF",
68
+ // "#FF3232",
69
+ // "#38BEA8",
70
+ // "#6F3BD5",
71
+ // "#314285",
72
+ // "#42C724",
73
+ // "#D52580",
74
+ // "#F4721B",
75
+ // "#D117EA",
76
+ // "#7E7D7D"
77
+ '#4E79A7', // blue
78
+ '#F28E2B', // orange
79
+ '#E15759', // red
80
+ '#76B7B2', // teal
81
+ '#59A14F', // green
82
+ '#EDC948', // yellow (已調過不刺眼)
83
+ '#B07AA1', // purple
84
+ '#FF9DA7', // pink
85
+ '#9C755F', // brown
86
+ '#BAB0AC', // gray
74
87
  ],
75
88
  primary: '#000000',
76
89
  secondary: '#e0e0e0',
@@ -79,16 +92,26 @@ export const DEFAULT_THEME: Theme = {
79
92
  },
80
93
  dark: {
81
94
  data: [
82
- "#4BABFF",
83
- "#FF6C6C",
84
- "#7DD3C4",
85
- "#8E6BC9",
86
- "#5366AC",
87
- "#86DC72",
88
- "#FF72BB",
89
- "#F9B052",
90
- "#EF76FF",
91
- "#C4C4C4"
95
+ // "#4BABFF",
96
+ // "#FF6C6C",
97
+ // "#7DD3C4",
98
+ // "#8E6BC9",
99
+ // "#5366AC",
100
+ // "#86DC72",
101
+ // "#FF72BB",
102
+ // "#F9B052",
103
+ // "#EF76FF",
104
+ // "#C4C4C4"
105
+ '#7EA6E0', // blue (亮化)
106
+ '#FFB86B', // orange
107
+ '#FF7A7A', // red
108
+ '#6ED0CC', // teal
109
+ '#7ED97A', // green
110
+ '#FFE06E', // yellow
111
+ '#D4A5D8', // purple
112
+ '#FFB3BA', // pink
113
+ '#C89F7A', // brown
114
+ '#D3D3D3', // gray
92
115
  ],
93
116
  primary: '#FFFFFF',
94
117
  secondary: '#e0e0e0',
@@ -43,15 +43,21 @@ export const createPlugin = <
43
43
  // const layerSVGElementsRef: Record<string, SVGElement> = {}
44
44
  // const layerCanvasElementsRef: Record<string, HTMLCanvasElement> = {}
45
45
 
46
+ // 每個 plugin 實例建立「自己的」layer 實體。
47
+ // Layer 內部狀態(enableProps$、setup 訂閱等)一次只能服務一個 plugin 實例,
48
+ // 若跨實例共用(例如 module 層級的單例),同頁多個 plugin 會互搶 layer,
49
+ // 造成部分圖表的 layer 不繪製或被銷毀。
50
+ const layers = config.layers.map((Layer) => new Layer())
51
+
46
52
  // 全部的layers
47
53
  const allLayerInstances = {
48
54
  // 將陣列轉成字典
49
- ...config.layers.reduce((acc, layer) => {
55
+ ...layers.reduce((acc, layer) => {
50
56
  acc[layer._name as keyof AllLayerParams] = layer
51
57
  return acc
52
- }, {} as Record<keyof AllLayerParams, typeof config.layers[number]>)
58
+ }, {} as Record<keyof AllLayerParams, typeof layers[number]>)
53
59
  }
54
- const getAllLayerNamesSet = () => new Set(config.layers.map(l => l._name as keyof AllLayerParams))
60
+ const getAllLayerNamesSet = () => new Set(layers.map(l => l._name as keyof AllLayerParams))
55
61
  // const getParamsKeySet = (params: DeepPartial<PluginParams | AllLayerParams>): Set<keyof AllLayerParams> => {
56
62
  // // 取得有設定 params 的 layer name
57
63
  // if (params) {
@@ -72,7 +78,7 @@ export const createPlugin = <
72
78
  }
73
79
 
74
80
  // config 設定顯示的 layers
75
- const systemInitLayerNames = config.layers.reduce((prev, current) => {
81
+ const systemInitLayerNames = layers.reduce((prev, current) => {
76
82
  if (current._initShow) {
77
83
  prev.push(current._name as keyof AllLayerParams)
78
84
  }
@@ -107,7 +113,7 @@ export const createPlugin = <
107
113
  const shownLayerNamesSeq: string[] = Array.from(ShownLayerNameSet)
108
114
  .map(name => [
109
115
  name,
110
- config.layers.find(l => l._name === name)?._layerIndex ?? -1
116
+ layers.find(l => l._name === name)?._layerIndex ?? -1
111
117
  ])
112
118
  .filter(([, index]) => index !== -1)
113
119
  .sort((a, b) => (a[1] as number) - (b[1] as number))
@@ -138,6 +144,14 @@ export const createPlugin = <
138
144
  // })
139
145
  // )
140
146
 
147
+ // 目前生效的 plugin 本身 params(預設值 + 歷次 patch 累積合併)。
148
+ // deepOverwrite 會忽略 defaults 中不存在的 key,因此建構子參數內的
149
+ // layer keys(如 { Bar: {} })會被自然濾除,不會混入 plugin-level params。
150
+ let currentPluginParams: PluginParams = deepOverwrite(
151
+ (config.defaultParams ?? {}) as PluginParams,
152
+ (initPluginParams ?? {}) as DeepPartial<PluginParams>
153
+ )
154
+
141
155
  // update plugin params
142
156
  const patchPluginParams$ = new BehaviorSubject<DeepPartial<AllLayerParams | PluginParams> | undefined>(
143
157
  initPluginParams ?? {}
@@ -171,9 +185,8 @@ export const createPlugin = <
171
185
  console.error(createOrbChartsErrorMessage(e))
172
186
  }
173
187
 
174
- const newParams = deepOverwrite(config.defaultParams, patch as DeepPartial<PluginParams> ?? {})
175
- // console.log('newParams', newParams)
176
- subscriber.next(newParams)
188
+ // currentPluginParams 已於 updateParams 中累積合併(歷次 patch 不互相覆蓋)
189
+ subscriber.next(currentPluginParams)
177
190
  })
178
191
 
179
192
  // -- force replace --
@@ -279,7 +292,7 @@ export const createPlugin = <
279
292
  // }
280
293
 
281
294
  // init layers
282
- config.layers.forEach((layer) => {
295
+ layers.forEach((layer) => {
283
296
  if (ShownLayerNameSet.has(layer._name as keyof AllLayerParams)) {
284
297
  const layerEnableProps: LayerEnableProps<'svg' | 'canvas', ExtendContext, PluginParams, unknown> =
285
298
  elementType === 'svg' ?
@@ -397,7 +410,8 @@ export const createPlugin = <
397
410
  // params = { ...params, ...partial }
398
411
  // },
399
412
  updateParams: (patch: DeepPartial<PluginParams | AllLayerParams>) => {
400
- // plugin params
413
+ // plugin params(累積合併至目前值,使連續多次 updateParams 不互相覆蓋)
414
+ currentPluginParams = deepOverwrite(currentPluginParams, patch as DeepPartial<PluginParams>)
401
415
  patchPluginParams$.next(patch)
402
416
  // layer params
403
417
  Object.keys(patch).forEach((key) => {
@@ -411,6 +425,7 @@ export const createPlugin = <
411
425
  },
412
426
  forceReplaceParams: (full: PluginParams | AllLayerParams) => {
413
427
  // plugin params
428
+ currentPluginParams = full as PluginParams
414
429
  forceReplacePluginParams$.next(full)
415
430
  // layer params
416
431
  Object.keys(full).forEach((key) => {
@@ -422,10 +437,12 @@ export const createPlugin = <
422
437
  // InitLayerNameSet$.next(getParamsKeySet(full))
423
438
  },
424
439
  getParams: () => {
425
- return config.layers.reduce((acc, layer) => {
426
- acc[layer._name] = layer._getParams()
427
- return acc
428
- }, {} as Record<string, any>) as PluginParams | AllLayerParams
440
+ // plugin 本身的 params 在前、各 layer params 在後
441
+ const result: Record<string, any> = Object.assign({}, currentPluginParams)
442
+ layers.forEach((layer) => {
443
+ result[layer._name] = layer._getParams()
444
+ })
445
+ return result as PluginParams | AllLayerParams
429
446
  },
430
447
  // layer: <LayerName extends keyof PluginParams>(name: LayerName) => ({
431
448
  // // set: (partial: DeepPartial<PluginParams[LayerName]>) => {
@@ -458,7 +475,8 @@ export const createPlugin = <
458
475
  // IsShowLayerNameSet$.complete()
459
476
  ShownLayerNameSet$.complete()
460
477
 
461
- config.layers.forEach((layer) => {
478
+ // layers 為本實例專屬,destroy 不影響其他 plugin 實例
479
+ layers.forEach((layer) => {
462
480
  layer._destroy()
463
481
  })
464
482
  },
@@ -21,7 +21,7 @@ const testEncoding: Encoding = {
21
21
  category: { from: 'category', sort: 'original' },
22
22
  value: { from: 'value', sort: 'original', aggregate: 'none' },
23
23
  multivariate: [],
24
- color: { from: 'category' }
24
+ color: { by: 'category' }
25
25
  }
26
26
 
27
27
  const testTheme: Theme = {
@@ -27,7 +27,7 @@ const testEncoding: Encoding = {
27
27
  category: { from: 'category', sort: 'original' },
28
28
  value: { from: 'value', sort: 'original', aggregate: 'none' },
29
29
  multivariate: [],
30
- color: { from: 'category' }
30
+ color: { by: 'category' }
31
31
  }
32
32
 
33
33
  const testTheme: Theme = {
@@ -20,7 +20,7 @@ const testEncoding = {
20
20
  category: { from: 'category', sort: 'original' },
21
21
  value: { from: 'value', sort: 'original', aggregate: 'none' },
22
22
  multivariate: [],
23
- color: { from: 'category' }
23
+ color: { by: 'category' }
24
24
  }
25
25
 
26
26
  const testTheme = {
@@ -4,16 +4,19 @@
4
4
  export interface EncodingDataset {
5
5
  from: string
6
6
  sort: 'original' | 'alphabetical' | string[]
7
+ ignore?: boolean
7
8
  }
8
9
 
9
10
  export interface EncodingSeries {
10
11
  from: string
11
12
  sort: 'original' | 'alphabetical' | string[]
13
+ ignore?: boolean
12
14
  }
13
15
 
14
16
  export interface EncodingCategory {
15
17
  from: string
16
18
  sort: 'original' | 'alphabetical' | string[]
19
+ ignore?: boolean
17
20
  }
18
21
 
19
22
  export interface EncodingValue {
@@ -30,7 +33,7 @@ export interface EncodingMultivariateItem {
30
33
  export type EncodingMultivariate = EncodingMultivariateItem[]
31
34
 
32
35
  export interface EncodingColor {
33
- from: 'index' | 'series' | 'category' | 'dataset'
36
+ by: 'index' | 'series' | 'category' | 'dataset'
34
37
  }
35
38
 
36
39
  export interface Encoding {
@@ -49,7 +49,10 @@ export interface DefinePluginConfig<ExtendContext extends ExtendableContext, Plu
49
49
  defaultParams?: PluginParams
50
50
  validator?: (params: DeepPartial<AllLayerParams | PluginParams>) => ValidatorResult
51
51
  // { valid: boolean; errors?: string[] }
52
- layers?: LayerEntity<ExtendContext, PluginParams, AllLayerParams[keyof AllLayerParams]>[]
52
+ // Layer 建構子陣列——由 createPlugin 在「每個 plugin 實例」建立時各自實例化。
53
+ // (Layer 內部狀態(enableProps$ 等)不可跨 plugin 實例共用,否則同頁多個
54
+ // plugin 實例會互搶 layer,造成部分圖表不繪製)
55
+ layers?: (new () => LayerEntity<ExtendContext, PluginParams, AllLayerParams[keyof AllLayerParams]>)[]
53
56
  // extendContext?: (context: Readonly<ChartContext>) => ExtendContext
54
57
  setup?: (props: PluginSetupProps<ExtendContext, PluginParams>) => () => void
55
58
  }
@@ -24,7 +24,7 @@ export function getColorByIndex(index: number, theme: Theme): string {
24
24
  }
25
25
 
26
26
  /**
27
- * 根據不同的 encoding.color.from 計算顏色
27
+ * 根據不同的 encoding.color.by 計算顏色
28
28
  * @param colorFrom 顏色來源類型
29
29
  * @param options 包含所有可能索引的選項物件
30
30
  * @param theme 主題設定
@@ -0,0 +1,18 @@
1
+ import type { RawDataColumn } from '../types'
2
+
3
+ /**
4
+ * 解析單筆資料在某個 Encoding 維度(dataset / series / category)上的分組 key。
5
+ * 若該維度設定 ignore: true,所有資料一律回傳空字串,等於忽略此維度、全部視為同一組。
6
+ * fallback 預設為空字串,避免內部佔位值外洩成使用者看得到的顯示文字(例如 name)。
7
+ */
8
+ export function resolveGroupKey(
9
+ datum: RawDataColumn,
10
+ dim: { from: string; ignore?: boolean },
11
+ fallback: string = ''
12
+ ): string {
13
+ if (dim.ignore) {
14
+ return ''
15
+ }
16
+ const key = (datum as any)[dim.from]
17
+ return key || fallback
18
+ }
@@ -2,6 +2,7 @@ export * from './aggregateUtils'
2
2
  export * from './colorUtils'
3
3
  export * from './commonUtils'
4
4
  export * from './dom'
5
+ export * from './encodingUtils'
5
6
  export * from './errorMessage'
6
7
  export * from './observables'
7
8
  export * from './orbchartsUtils'
@@ -57,6 +57,16 @@ function getInvalidProperty<T> (data: T, rules: Partial<ValidatorRule<T>>) {
57
57
  if (rules[columnName as keyof T]) {
58
58
  const rule: ValidatorRuleToBeTypes | ValidatorRuleToBe | ValidatorRuleToBeOption = rules[columnName as keyof T]
59
59
  const value = data[columnName as keyof T]
60
+ // 明確傳入 undefined 視同「未提供該欄位」,不做驗證。
61
+ // 常見於呼叫端用 spread/條件式建構 options(例如
62
+ // `{ ...base, encoding: hasEncoding ? {...} : undefined }`),
63
+ // Object.keys() 仍會列出該屬性名稱,若不特別排除,這裡會誤判成
64
+ // "型別錯誤" 而不是 "沒有提供"(目前程式碼裡沒有任何規則真的需要
65
+ // 用 toBeTypes: ['undefined'] 去要求欄位必須是 undefined,所以一律
66
+ // 跳過是安全的)。
67
+ if (value === undefined) {
68
+ return false
69
+ }
60
70
  // 測試 "toBeTypes"
61
71
  if ((rule as ValidatorRuleToBeTypes).toBeTypes) {
62
72
  const toBeTypes = (rule as ValidatorRuleToBeTypes).toBeTypes