@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.
- package/dist/orbcharts-core.es.js +1662 -1589
- package/dist/orbcharts-core.umd.js +6 -6
- package/dist/src/test/simple-graph-test.d.ts +1 -2
- package/dist/src/types/Encoding.d.ts +4 -1
- package/dist/src/types/Plugin.d.ts +1 -1
- package/dist/src/utils/colorUtils.d.ts +1 -1
- package/dist/src/utils/encodingUtils.d.ts +11 -0
- package/dist/src/utils/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/chart/createChart.ts +47 -4
- package/src/chart/createGraphData.ts +20 -19
- package/src/chart/createGridData.ts +12 -11
- package/src/chart/createMultivariateData.ts +10 -9
- package/src/chart/createSeriesData.ts +15 -14
- package/src/chart/createTreeData.ts +18 -17
- package/src/chart/defaults.ts +47 -24
- package/src/plugin/createPlugin.ts +33 -15
- package/src/test/createGraphData.test.ts +1 -1
- package/src/test/createTreeData.test.ts +1 -1
- package/src/test/simple-graph-test.js +1 -1
- package/src/types/Encoding.ts +4 -1
- package/src/types/Plugin.ts +4 -1
- package/src/utils/colorUtils.ts +1 -1
- package/src/utils/encodingUtils.ts +18 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/validator.ts +10 -0
package/src/chart/defaults.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
...
|
|
55
|
+
...layers.reduce((acc, layer) => {
|
|
50
56
|
acc[layer._name as keyof AllLayerParams] = layer
|
|
51
57
|
return acc
|
|
52
|
-
}, {} as Record<keyof AllLayerParams, typeof
|
|
58
|
+
}, {} as Record<keyof AllLayerParams, typeof layers[number]>)
|
|
53
59
|
}
|
|
54
|
-
const getAllLayerNamesSet = () => new Set(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
-
|
|
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: {
|
|
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: {
|
|
30
|
+
color: { by: 'category' }
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const testTheme: Theme = {
|
package/src/types/Encoding.ts
CHANGED
|
@@ -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
|
-
|
|
36
|
+
by: 'index' | 'series' | 'category' | 'dataset'
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
export interface Encoding {
|
package/src/types/Plugin.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/src/utils/colorUtils.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/utils/index.ts
CHANGED
package/src/utils/validator.ts
CHANGED
|
@@ -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
|