@orbcharts/core 3.0.7 → 4.0.0-pre-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.
- package/dist/orbcharts-core.es.js +2795 -6591
- package/dist/orbcharts-core.umd.js +6 -6
- package/dist/src/OrbCharts.d.ts +18 -0
- package/dist/src/chart/createChart.d.ts +3 -0
- package/dist/src/chart/createGraphData.d.ts +3 -0
- package/dist/src/chart/createGridData.d.ts +3 -0
- package/dist/src/chart/createMultivariateData.d.ts +3 -0
- package/dist/src/chart/createSeriesData.d.ts +3 -0
- package/dist/src/chart/createTreeData.d.ts +3 -0
- package/dist/src/chart/defaults.d.ts +5 -0
- package/dist/src/defineCanvasLayer.d.ts +16 -0
- package/dist/src/defineCanvasPlugin.d.ts +22 -0
- package/dist/src/defineSVGLayer.d.ts +16 -0
- package/dist/src/defineSVGPlugin.d.ts +22 -0
- package/dist/src/index.d.ts +6 -14
- package/dist/src/layer/createLayer.d.ts +3 -0
- package/dist/src/plugin/createPlugin.d.ts +3 -0
- package/dist/src/test/createGraphData.test.d.ts +1 -0
- package/dist/src/test/createTreeData.test.d.ts +1 -0
- package/dist/src/test/simple-graph-test.d.ts +74 -0
- package/dist/src/test/simple-tree-test.d.ts +13 -0
- package/dist/src/types/Chart.d.ts +39 -0
- package/dist/src/types/ChartContext.d.ts +27 -0
- package/dist/src/types/Common.d.ts +3 -0
- package/dist/src/types/Encoding.d.ts +33 -0
- package/dist/src/types/Event.d.ts +12 -0
- package/dist/src/types/Layers.d.ts +55 -0
- package/dist/src/types/ModelData.d.ts +70 -0
- package/dist/src/types/Plugin.d.ts +39 -0
- package/dist/src/types/RawData.d.ts +18 -0
- package/dist/src/types/RenderData.d.ts +4 -0
- package/dist/src/types/Theme.d.ts +17 -0
- package/dist/src/types/Validator.d.ts +20 -0
- package/dist/src/types/index.d.ts +12 -0
- package/dist/src/utils/aggregateUtils.d.ts +37 -0
- package/dist/src/utils/colorUtils.d.ts +22 -0
- package/dist/src/utils/commonUtils.d.ts +3 -5
- package/dist/src/utils/dom-lifecycle.d.ts +37 -0
- package/dist/src/utils/dom.d.ts +6 -0
- package/dist/src/utils/index.d.ts +5 -1
- package/dist/src/utils/observables.d.ts +1 -25
- package/dist/src/utils/orbchartsUtils.d.ts +2 -53
- package/dist/src/utils/validator.d.ts +2 -2
- package/dist/test/aggregateTest.d.ts +1 -0
- package/package.json +21 -8
- package/src/OrbCharts.ts +35 -0
- package/src/chart/createChart.ts +997 -0
- package/src/chart/createGraphData.ts +391 -0
- package/src/chart/createGridData.ts +247 -0
- package/src/chart/createMultivariateData.ts +181 -0
- package/src/chart/createSeriesData.ts +297 -0
- package/src/chart/createTreeData.ts +344 -0
- package/src/chart/defaults.ts +100 -0
- package/src/defineCanvasLayer.ts +24 -0
- package/src/defineCanvasPlugin.ts +39 -0
- package/src/defineSVGLayer.ts +24 -0
- package/src/defineSVGPlugin.ts +39 -0
- package/src/index.ts +6 -18
- package/src/layer/createLayer.ts +138 -0
- package/src/plugin/createPlugin.ts +470 -0
- package/src/test/createGraphData.test.ts +103 -0
- package/src/test/createTreeData.test.ts +97 -0
- package/src/test/simple-graph-test.js +51 -0
- package/src/test/simple-tree-test.js +58 -0
- package/src/types/Chart.ts +62 -0
- package/src/types/ChartContext.ts +42 -0
- package/src/types/Common.ts +5 -0
- package/src/types/Encoding.ts +43 -0
- package/src/types/Event.ts +26 -0
- package/src/types/Layers.ts +93 -0
- package/src/types/ModelData.ts +95 -0
- package/src/types/Plugin.ts +98 -0
- package/src/types/RawData.ts +67 -0
- package/src/types/RenderData.ts +16 -0
- package/src/types/Theme.ts +21 -0
- package/src/types/Validator.ts +36 -0
- package/src/types/index.ts +12 -0
- package/src/utils/aggregateUtils.ts +99 -0
- package/src/utils/colorUtils.ts +63 -0
- package/src/utils/commonUtils.ts +12 -11
- package/src/utils/dom-lifecycle.ts +164 -0
- package/src/utils/dom.ts +55 -0
- package/src/utils/index.ts +6 -2
- package/src/utils/observables.ts +1 -292
- package/src/utils/orbchartsUtils.ts +6 -393
- package/src/utils/validator.ts +15 -14
- package/dist/lib/core-types.d.ts +0 -1
- package/dist/src/AbstractChart.d.ts +0 -19
- package/dist/src/GridChart.d.ts +0 -6
- package/dist/src/MultiGridChart.d.ts +0 -6
- package/dist/src/MultiValueChart.d.ts +0 -6
- package/dist/src/RelationshipChart.d.ts +0 -6
- package/dist/src/SeriesChart.d.ts +0 -6
- package/dist/src/TreeChart.d.ts +0 -6
- package/dist/src/base/createBaseChart.d.ts +0 -3
- package/dist/src/base/createBasePlugin.d.ts +0 -3
- package/dist/src/base/validators/chartOptionsValidator.d.ts +0 -3
- package/dist/src/base/validators/chartParamsValidator.d.ts +0 -3
- package/dist/src/base/validators/elementValidator.d.ts +0 -3
- package/dist/src/base/validators/pluginsValidator.d.ts +0 -3
- package/dist/src/defaults.d.ts +0 -25
- package/dist/src/defineGridPlugin.d.ts +0 -1
- package/dist/src/defineMultiGridPlugin.d.ts +0 -1
- package/dist/src/defineMultiValuePlugin.d.ts +0 -1
- package/dist/src/defineNoneDataPlugin.d.ts +0 -1
- package/dist/src/defineRelationshipPlugin.d.ts +0 -1
- package/dist/src/defineSeriesPlugin.d.ts +0 -1
- package/dist/src/defineTreePlugin.d.ts +0 -1
- package/dist/src/grid/computedDataFn.d.ts +0 -4
- package/dist/src/grid/contextObserverCallback.d.ts +0 -3
- package/dist/src/grid/dataFormatterValidator.d.ts +0 -3
- package/dist/src/grid/dataValidator.d.ts +0 -3
- package/dist/src/grid/gridObservables.d.ts +0 -64
- package/dist/src/multiGrid/computedDataFn.d.ts +0 -3
- package/dist/src/multiGrid/contextObserverCallback.d.ts +0 -3
- package/dist/src/multiGrid/dataFormatterValidator.d.ts +0 -3
- package/dist/src/multiGrid/dataValidator.d.ts +0 -3
- package/dist/src/multiGrid/multiGridObservables.d.ts +0 -16
- package/dist/src/multiValue/computedDataFn.d.ts +0 -3
- package/dist/src/multiValue/contextObserverCallback.d.ts +0 -3
- package/dist/src/multiValue/dataFormatterValidator.d.ts +0 -3
- package/dist/src/multiValue/dataValidator.d.ts +0 -3
- package/dist/src/multiValue/multiValueObservables.d.ts +0 -130
- package/dist/src/relationship/computedDataFn.d.ts +0 -3
- package/dist/src/relationship/contextObserverCallback.d.ts +0 -3
- package/dist/src/relationship/dataFormatterValidator.d.ts +0 -3
- package/dist/src/relationship/dataValidator.d.ts +0 -3
- package/dist/src/relationship/relationshipObservables.d.ts +0 -13
- package/dist/src/series/computedDataFn.d.ts +0 -3
- package/dist/src/series/contextObserverCallback.d.ts +0 -3
- package/dist/src/series/dataFormatterValidator.d.ts +0 -3
- package/dist/src/series/dataValidator.d.ts +0 -3
- package/dist/src/series/seriesObservables.d.ts +0 -37
- package/dist/src/tree/computedDataFn.d.ts +0 -3
- package/dist/src/tree/contextObserverCallback.d.ts +0 -3
- package/dist/src/tree/dataFormatterValidator.d.ts +0 -3
- package/dist/src/tree/dataValidator.d.ts +0 -3
- package/dist/src/tree/treeObservables.d.ts +0 -10
- package/dist/src/utils/d3Scale.d.ts +0 -28
- package/lib/core-types.ts +0 -7
- package/src/AbstractChart.ts +0 -57
- package/src/GridChart.ts +0 -25
- package/src/MultiGridChart.ts +0 -25
- package/src/MultiValueChart.ts +0 -25
- package/src/RelationshipChart.ts +0 -25
- package/src/SeriesChart.ts +0 -25
- package/src/TreeChart.ts +0 -25
- package/src/base/createBaseChart.ts +0 -524
- package/src/base/createBasePlugin.ts +0 -154
- package/src/base/validators/chartOptionsValidator.ts +0 -24
- package/src/base/validators/chartParamsValidator.ts +0 -134
- package/src/base/validators/elementValidator.ts +0 -14
- package/src/base/validators/pluginsValidator.ts +0 -15
- package/src/defaults.ts +0 -284
- package/src/defineGridPlugin.ts +0 -3
- package/src/defineMultiGridPlugin.ts +0 -3
- package/src/defineMultiValuePlugin.ts +0 -3
- package/src/defineNoneDataPlugin.ts +0 -4
- package/src/defineRelationshipPlugin.ts +0 -3
- package/src/defineSeriesPlugin.ts +0 -3
- package/src/defineTreePlugin.ts +0 -3
- package/src/grid/computedDataFn.ts +0 -129
- package/src/grid/contextObserverCallback.ts +0 -209
- package/src/grid/dataFormatterValidator.ts +0 -126
- package/src/grid/dataValidator.ts +0 -13
- package/src/grid/gridObservables.ts +0 -699
- package/src/multiGrid/computedDataFn.ts +0 -123
- package/src/multiGrid/contextObserverCallback.ts +0 -109
- package/src/multiGrid/dataFormatterValidator.ts +0 -121
- package/src/multiGrid/dataValidator.ts +0 -13
- package/src/multiGrid/multiGridObservables.ts +0 -367
- package/src/multiValue/computedDataFn.ts +0 -113
- package/src/multiValue/contextObserverCallback.ts +0 -328
- package/src/multiValue/dataFormatterValidator.ts +0 -95
- package/src/multiValue/dataValidator.ts +0 -13
- package/src/multiValue/multiValueObservables.ts +0 -865
- package/src/relationship/computedDataFn.ts +0 -159
- package/src/relationship/contextObserverCallback.ts +0 -80
- package/src/relationship/dataFormatterValidator.ts +0 -14
- package/src/relationship/dataValidator.ts +0 -14
- package/src/relationship/relationshipObservables.ts +0 -85
- package/src/series/computedDataFn.ts +0 -88
- package/src/series/contextObserverCallback.ts +0 -132
- package/src/series/dataFormatterValidator.ts +0 -47
- package/src/series/dataValidator.ts +0 -13
- package/src/series/seriesObservables.ts +0 -210
- package/src/tree/computedDataFn.ts +0 -129
- package/src/tree/contextObserverCallback.ts +0 -58
- package/src/tree/dataFormatterValidator.ts +0 -14
- package/src/tree/dataValidator.ts +0 -14
- package/src/tree/treeObservables.ts +0 -106
- package/src/utils/d3Scale.ts +0 -198
- package/tsconfig.base.json +0 -14
- package/tsconfig.json +0 -3
- package/vite-env.d.ts +0 -7
- package/vite.config.js +0 -23
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Observable, Subject } from 'rxjs'
|
|
2
|
+
import type {
|
|
3
|
+
DeepPartial,
|
|
4
|
+
LayerEntity,
|
|
5
|
+
ChartContext,
|
|
6
|
+
ExtendableContext
|
|
7
|
+
} from './index'
|
|
8
|
+
import { ValidatorResult } from '../types/Validator'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export interface PluginInfo {
|
|
12
|
+
id: string
|
|
13
|
+
name: string
|
|
14
|
+
elementType: 'svg' | 'canvas'
|
|
15
|
+
shownLayers: string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// export interface PluginSetupProps<ExtendContext extends ExtendableContext, PluginParams extends Record<string, any>> {
|
|
19
|
+
// context: ChartContext<ExtendContext>
|
|
20
|
+
// svg: SVGElement
|
|
21
|
+
// canvas: HTMLCanvasElement
|
|
22
|
+
// pluginParams$: Observable<PluginParams>
|
|
23
|
+
// }
|
|
24
|
+
|
|
25
|
+
// export interface SVGPluginSetupProps<ExtendContext extends ExtendableContext, PluginParams extends Record<string, any>> {
|
|
26
|
+
// context: ChartContext<ExtendContext>
|
|
27
|
+
// svgG: SVGGElement
|
|
28
|
+
// pluginParams$: Observable<PluginParams>
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
// export interface CanvasPluginSetupProps<ExtendContext extends ExtendableContext, PluginParams extends Record<string, any>> {
|
|
32
|
+
// context: ChartContext<ExtendContext>
|
|
33
|
+
// canvas: HTMLCanvasElement
|
|
34
|
+
// pluginParams$: Observable<PluginParams>
|
|
35
|
+
// }
|
|
36
|
+
|
|
37
|
+
// export type PluginSetupProps<ElementType extends 'svg' | 'canvas', ExtendContext extends ExtendableContext, PluginParams extends Record<string, any>> =
|
|
38
|
+
// ElementType extends 'svg' ? SVGPluginSetupProps<ExtendContext, PluginParams> :
|
|
39
|
+
// ElementType extends 'canvas' ? CanvasPluginSetupProps<ExtendContext, PluginParams> :
|
|
40
|
+
// never
|
|
41
|
+
|
|
42
|
+
export interface PluginSetupProps<ExtendContext extends ExtendableContext, PluginParams extends Record<string, any>> {
|
|
43
|
+
context: ChartContext<ExtendContext>
|
|
44
|
+
pluginParams$: Observable<PluginParams>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DefinePluginConfig<ExtendContext extends ExtendableContext, PluginParams extends Record<string, any>, AllLayerParams extends Record<string, any>>{
|
|
48
|
+
name: string
|
|
49
|
+
defaultParams?: PluginParams
|
|
50
|
+
validator?: (params: DeepPartial<AllLayerParams | PluginParams>) => ValidatorResult
|
|
51
|
+
// { valid: boolean; errors?: string[] }
|
|
52
|
+
layers?: LayerEntity<ExtendContext, PluginParams, AllLayerParams[keyof AllLayerParams]>[]
|
|
53
|
+
// extendContext?: (context: Readonly<ChartContext>) => ExtendContext
|
|
54
|
+
setup?: (props: PluginSetupProps<ExtendContext, PluginParams>) => () => void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// export interface CreatePlugin<PluginParams> {
|
|
58
|
+
// (config: DefinePluginConfig<PluginParams>): PluginEntity<PluginParams>
|
|
59
|
+
// }
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
export interface PluginEntity<ElementType extends 'svg' | 'canvas', PluginParams extends Record<string, any>, AllLayerParams extends Record<string, any>> {
|
|
63
|
+
readonly _name: string
|
|
64
|
+
readonly _elementType: ElementType
|
|
65
|
+
_getId: () => string
|
|
66
|
+
_setId: (id: string) => void
|
|
67
|
+
_injectContext(context: ChartContext<{}>): void
|
|
68
|
+
// layer visibility controls
|
|
69
|
+
show(names: (keyof AllLayerParams) | (keyof AllLayerParams)[]): void
|
|
70
|
+
showOnly(names: (keyof AllLayerParams) | (keyof AllLayerParams)[]): void
|
|
71
|
+
showAll(): void
|
|
72
|
+
hide(names: (keyof AllLayerParams) | (keyof AllLayerParams)[]): void
|
|
73
|
+
hideAll(): void
|
|
74
|
+
toggle(names: (keyof AllLayerParams) | (keyof AllLayerParams)[]): void
|
|
75
|
+
getShownLayerNames(): (keyof AllLayerParams)[]
|
|
76
|
+
// layer params
|
|
77
|
+
// setLayers(partial: DeepPartial<PluginParams>): void // deep-merge with default
|
|
78
|
+
updateParams(patch: DeepPartial<PluginParams | AllLayerParams>): void // deep-merge with previous
|
|
79
|
+
forceReplaceParams(full: PluginParams | AllLayerParams): void // replace(特殊需求,可節省效能)
|
|
80
|
+
getParams(): Readonly<PluginParams | AllLayerParams>
|
|
81
|
+
// layer<LayerName extends keyof PluginParams>(name: LayerName): {
|
|
82
|
+
// // set(partial: DeepPartial<PluginParams[LayerName]>): void // deep-merge with default 該 layer 的 params
|
|
83
|
+
// update(patch: DeepPartial<PluginParams[LayerName]>): void // deep-merge with previous 該 layer 的 params
|
|
84
|
+
// replace(full: PluginParams[LayerName]): void // replace(特殊需求,可節省效能)
|
|
85
|
+
// show(): void
|
|
86
|
+
// hide(): void
|
|
87
|
+
// toggle(): void
|
|
88
|
+
// }
|
|
89
|
+
|
|
90
|
+
destroy(): void
|
|
91
|
+
|
|
92
|
+
// outputs (observables)
|
|
93
|
+
// layers$: Observable<LayersConfig> // 各 layer 的有效參數(合併後)
|
|
94
|
+
// visibleLayerNames$: Observable<string[]> // 目前可見的 layer 清單(原 show$)
|
|
95
|
+
// event$: Observable<{ data: EventData; event: Event }> // 互動事件
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
// RawData -> ModelData (DataEndoding & ModelType) -> RenderData (Plugin)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export type RawData = RawDataColumn[] | RawDataColumn[][] // 二維陣列會轉化為多個dataset
|
|
8
|
+
|
|
9
|
+
// 可透過 DataEncoding 更改欄位名稱的預設欄位
|
|
10
|
+
export type DynamicDefaultRawDataFields = {
|
|
11
|
+
value?: number | null
|
|
12
|
+
x?: number | null // multivariate
|
|
13
|
+
y?: number | null // multivariate
|
|
14
|
+
z?: number | null // multivariate
|
|
15
|
+
dataset?: string
|
|
16
|
+
series?: string
|
|
17
|
+
category?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type RawDataColumn<DynamicFields extends Record<string, any> = DynamicDefaultRawDataFields> = {
|
|
21
|
+
// 不可變更的基本欄位
|
|
22
|
+
id?: string
|
|
23
|
+
name?: string
|
|
24
|
+
source?: string
|
|
25
|
+
target?: string
|
|
26
|
+
parent?: string
|
|
27
|
+
data?: any
|
|
28
|
+
} & DynamicFields // 可變更的欄位
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// export type DynamicRawDataColumn<
|
|
33
|
+
// ValueField extends string = 'value',
|
|
34
|
+
// DatasetField extends string = 'dataset',
|
|
35
|
+
// SeriesField extends string = 'series',
|
|
36
|
+
// CategoryField extends string = 'category'
|
|
37
|
+
// > = {
|
|
38
|
+
// id?: string
|
|
39
|
+
// name?: string
|
|
40
|
+
// source?: string
|
|
41
|
+
// target?: string
|
|
42
|
+
// parent?: string
|
|
43
|
+
// data?: any
|
|
44
|
+
// } & {
|
|
45
|
+
// [K in ValueField]: number | null
|
|
46
|
+
// } & {
|
|
47
|
+
// [K in DatasetField]?: string
|
|
48
|
+
// } & {
|
|
49
|
+
// [K in SeriesField]?: string
|
|
50
|
+
// } & {
|
|
51
|
+
// [K in CategoryField]?: string
|
|
52
|
+
// }
|
|
53
|
+
|
|
54
|
+
// export type FlexibleRawDataColumn<T extends Record<string, any> = {}> = {
|
|
55
|
+
// id?: string
|
|
56
|
+
// value?: number | null
|
|
57
|
+
// name?: string
|
|
58
|
+
// dataset?: string
|
|
59
|
+
// series?: string
|
|
60
|
+
// category?: string
|
|
61
|
+
// source?: string
|
|
62
|
+
// target?: string
|
|
63
|
+
// parent?: string
|
|
64
|
+
// data?: any
|
|
65
|
+
// } & T
|
|
66
|
+
|
|
67
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModelType,
|
|
3
|
+
ModelDatum,
|
|
4
|
+
ModelDatumGraphEdge
|
|
5
|
+
} from './index'
|
|
6
|
+
|
|
7
|
+
// 基礎型別,其他欄位留給 Plugin 擴充
|
|
8
|
+
// export interface RenderDatumBase<T extends ModelType> {
|
|
9
|
+
// modelDatum: ModelDatum<T>
|
|
10
|
+
// }
|
|
11
|
+
export type RenderDatumBase<
|
|
12
|
+
T extends ModelType,
|
|
13
|
+
ExtendTypes extends Record<string, any> = {}
|
|
14
|
+
> = ModelDatum<T> & ExtendTypes
|
|
15
|
+
|
|
16
|
+
export type RenderDatumGraphEdge<ExtendTypes extends Record<string, any> = {}> = ModelDatumGraphEdge & ExtendTypes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
export interface Colors {
|
|
3
|
+
data: string[],
|
|
4
|
+
primary: string,
|
|
5
|
+
secondary: string,
|
|
6
|
+
dataContrast: string[],
|
|
7
|
+
background: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type ColorScheme = 'light' | 'dark'
|
|
11
|
+
|
|
12
|
+
export interface Theme {
|
|
13
|
+
colorScheme: ColorScheme | 'auto'
|
|
14
|
+
colors: {
|
|
15
|
+
light: Colors,
|
|
16
|
+
dark: Colors
|
|
17
|
+
},
|
|
18
|
+
fontSize: string | number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ColorType = 'none' | keyof Colors
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type ToBeTypes = 'string' | 'number' | 'boolean' | 'object' | 'object[]' | 'string[]' | 'number[]' | 'Function' | 'null' | 'undefined'
|
|
2
|
+
|
|
3
|
+
export type ToBeOption = 'ColorType'
|
|
4
|
+
|
|
5
|
+
// 有使用定義好的型別則不需寫 validate
|
|
6
|
+
export interface ValidatorRuleToBeTypes {
|
|
7
|
+
toBeTypes: ToBeTypes[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 自訂規則
|
|
11
|
+
export interface ValidatorRuleToBe {
|
|
12
|
+
toBe: string
|
|
13
|
+
test: (value: any) => boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 選項資料型別
|
|
17
|
+
export interface ValidatorRuleToBeOption {
|
|
18
|
+
toBeOption: ToBeOption
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type ValidatorRule<T> = {[key in keyof T]: ValidatorRuleToBeTypes | ValidatorRuleToBe | ValidatorRuleToBeOption}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// export type ValidateObject<T> = (data: T, rules: ValidatorRule<T>) => ValidatorResult
|
|
25
|
+
|
|
26
|
+
// export interface ValidatorUtils {
|
|
27
|
+
// validateObject: typeof validateObject // 我發現要這樣寫才能夠透過 data 型別自動推斷出 T,不曉得有沒有更好的寫法
|
|
28
|
+
// }
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
export interface ValidatorResult {
|
|
32
|
+
status: 'success' | 'warning' | 'error'
|
|
33
|
+
// message: string // warning or error message
|
|
34
|
+
columnName: string,
|
|
35
|
+
expectToBe: string,
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './Chart'
|
|
2
|
+
export * from './ChartContext'
|
|
3
|
+
export * from './Encoding'
|
|
4
|
+
export * from './Event'
|
|
5
|
+
export * from './Layers'
|
|
6
|
+
export * from './ModelData'
|
|
7
|
+
export * from './Plugin'
|
|
8
|
+
export * from './RawData'
|
|
9
|
+
export * from './RenderData'
|
|
10
|
+
export * from './Theme'
|
|
11
|
+
export * from './Validator'
|
|
12
|
+
export * from './Common'
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 聚合函數工具集
|
|
3
|
+
* 用於處理數值陣列的各種聚合運算
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type AggregateType = 'sum' | 'mean' | 'median' | 'min' | 'max' | 'count' | 'none'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 計算數值陣列的總和
|
|
10
|
+
*/
|
|
11
|
+
export function sum(values: (number | null)[]): number | null {
|
|
12
|
+
const validValues = values.filter((v): v is number => v !== null && !isNaN(v))
|
|
13
|
+
if (validValues.length === 0) return null
|
|
14
|
+
return validValues.reduce((acc, val) => acc + val, 0)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 計算數值陣列的平均值
|
|
19
|
+
*/
|
|
20
|
+
export function mean(values: (number | null)[]): number | null {
|
|
21
|
+
const validValues = values.filter((v): v is number => v !== null && !isNaN(v))
|
|
22
|
+
if (validValues.length === 0) return null
|
|
23
|
+
const total = validValues.reduce((acc, val) => acc + val, 0)
|
|
24
|
+
return total / validValues.length
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 計算數值陣列的中位數
|
|
29
|
+
*/
|
|
30
|
+
export function median(values: (number | null)[]): number | null {
|
|
31
|
+
const validValues = values.filter((v): v is number => v !== null && !isNaN(v))
|
|
32
|
+
if (validValues.length === 0) return null
|
|
33
|
+
|
|
34
|
+
const sorted = [...validValues].sort((a, b) => a - b)
|
|
35
|
+
const mid = Math.floor(sorted.length / 2)
|
|
36
|
+
|
|
37
|
+
if (sorted.length % 2 === 0) {
|
|
38
|
+
return (sorted[mid - 1] + sorted[mid]) / 2
|
|
39
|
+
} else {
|
|
40
|
+
return sorted[mid]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 計算數值陣列的最小值
|
|
46
|
+
*/
|
|
47
|
+
export function min(values: (number | null)[]): number | null {
|
|
48
|
+
const validValues = values.filter((v): v is number => v !== null && !isNaN(v))
|
|
49
|
+
if (validValues.length === 0) return null
|
|
50
|
+
return Math.min(...validValues)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 計算數值陣列的最大值
|
|
55
|
+
*/
|
|
56
|
+
export function max(values: (number | null)[]): number | null {
|
|
57
|
+
const validValues = values.filter((v): v is number => v !== null && !isNaN(v))
|
|
58
|
+
if (validValues.length === 0) return null
|
|
59
|
+
return Math.max(...validValues)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 計算數值陣列的有效數量
|
|
64
|
+
*/
|
|
65
|
+
export function count(values: (number | null)[]): number {
|
|
66
|
+
return values.filter((v): v is number => v !== null && !isNaN(v)).length
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 不進行聚合,返回第一個有效值
|
|
71
|
+
*/
|
|
72
|
+
export function none(values: (number | null)[]): number | null {
|
|
73
|
+
const validValues = values.filter((v): v is number => v !== null && !isNaN(v))
|
|
74
|
+
return validValues.length > 0 ? validValues[0] : null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 根據聚合類型執行對應的聚合函數
|
|
79
|
+
*/
|
|
80
|
+
export function aggregate(values: (number | null)[], type: AggregateType): number | null {
|
|
81
|
+
switch (type) {
|
|
82
|
+
case 'sum':
|
|
83
|
+
return sum(values)
|
|
84
|
+
case 'mean':
|
|
85
|
+
return mean(values)
|
|
86
|
+
case 'median':
|
|
87
|
+
return median(values)
|
|
88
|
+
case 'min':
|
|
89
|
+
return min(values)
|
|
90
|
+
case 'max':
|
|
91
|
+
return max(values)
|
|
92
|
+
case 'count':
|
|
93
|
+
return count(values)
|
|
94
|
+
case 'none':
|
|
95
|
+
return none(values)
|
|
96
|
+
default:
|
|
97
|
+
throw new Error(`Unknown aggregate type: ${type}`)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Theme } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 根據索引和主題計算對應的顏色
|
|
5
|
+
* @param index 索引值
|
|
6
|
+
* @param theme 主題設定
|
|
7
|
+
* @returns 對應的顏色(hex 格式)
|
|
8
|
+
*/
|
|
9
|
+
export function getColorByIndex(index: number, theme: Theme): string {
|
|
10
|
+
// 根據 colorScheme 決定使用哪個色彩方案
|
|
11
|
+
let colorScheme: 'light' | 'dark'
|
|
12
|
+
if (theme.colorScheme === 'auto') {
|
|
13
|
+
// 可以根據系統設定或其他邏輯來決定,這裡預設使用 light
|
|
14
|
+
colorScheme = 'light'
|
|
15
|
+
} else {
|
|
16
|
+
colorScheme = theme.colorScheme
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const dataColors = theme.colors[colorScheme].data
|
|
20
|
+
|
|
21
|
+
// 使用模運算來循環使用色票
|
|
22
|
+
const colorIndex = index % dataColors.length
|
|
23
|
+
return dataColors[colorIndex]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 根據不同的 encoding.color.from 計算顏色
|
|
28
|
+
* @param colorFrom 顏色來源類型
|
|
29
|
+
* @param options 包含所有可能索引的選項物件
|
|
30
|
+
* @param theme 主題設定
|
|
31
|
+
* @returns 對應的顏色(hex 格式)
|
|
32
|
+
*/
|
|
33
|
+
export function getColorByFrom(
|
|
34
|
+
colorFrom: 'index' | 'series' | 'category' | 'dataset',
|
|
35
|
+
options: {
|
|
36
|
+
index?: number
|
|
37
|
+
seriesIndex?: number
|
|
38
|
+
categoryIndex?: number
|
|
39
|
+
datasetIndex?: number
|
|
40
|
+
},
|
|
41
|
+
theme: Theme
|
|
42
|
+
): string {
|
|
43
|
+
let targetIndex: number
|
|
44
|
+
|
|
45
|
+
switch (colorFrom) {
|
|
46
|
+
case 'index':
|
|
47
|
+
targetIndex = options.index ?? 0
|
|
48
|
+
break
|
|
49
|
+
case 'series':
|
|
50
|
+
targetIndex = options.seriesIndex ?? 0
|
|
51
|
+
break
|
|
52
|
+
case 'category':
|
|
53
|
+
targetIndex = options.categoryIndex ?? 0
|
|
54
|
+
break
|
|
55
|
+
case 'dataset':
|
|
56
|
+
targetIndex = options.datasetIndex ?? 0
|
|
57
|
+
break
|
|
58
|
+
default:
|
|
59
|
+
targetIndex = 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return getColorByIndex(targetIndex, theme)
|
|
63
|
+
}
|
package/src/utils/commonUtils.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DeepPartial } from "../types/Common";
|
|
1
2
|
|
|
2
3
|
// 是否為原始物件
|
|
3
4
|
export function isPlainObject(variable: any) {
|
|
@@ -16,31 +17,31 @@ export function isDom(obj: any) {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
// 將可選的參數和預設值合併
|
|
19
|
-
export function
|
|
20
|
-
if (isPlainObject(options) === false || isPlainObject(
|
|
21
|
-
return Object.assign({},
|
|
20
|
+
export function deepOverwrite<DeepRecord extends Record<string, any>>(full: DeepRecord, options: DeepPartial<DeepRecord>): DeepRecord {
|
|
21
|
+
if (isPlainObject(options) === false || isPlainObject(full) === false) {
|
|
22
|
+
return Object.assign({}, full)
|
|
22
23
|
}
|
|
23
|
-
const
|
|
24
|
-
const obj:
|
|
24
|
+
const mergeObject = (_full: DeepRecord, _options: DeepPartial<DeepRecord>) => {
|
|
25
|
+
const obj: DeepRecord = (Object.assign({}, _full) as any)
|
|
25
26
|
for (let key of Object.keys(_options)) {
|
|
26
|
-
if ((key in
|
|
27
|
+
if ((key in _full) == false) {
|
|
27
28
|
continue
|
|
28
29
|
}
|
|
29
30
|
let objValue: any = undefined
|
|
30
31
|
// 下一層的plain object
|
|
31
|
-
if (isPlainObject(_options[key]) && isPlainObject(
|
|
32
|
-
objValue =
|
|
33
|
-
obj[key as keyof
|
|
32
|
+
if (isPlainObject(_options[key]) && isPlainObject(_full[key])) {
|
|
33
|
+
objValue = mergeObject(_full[key], _options[key])
|
|
34
|
+
obj[key as keyof DeepRecord] = objValue
|
|
34
35
|
}
|
|
35
36
|
// 不是plain object直接賦值
|
|
36
37
|
else {
|
|
37
|
-
obj[key as keyof
|
|
38
|
+
obj[key as keyof DeepRecord] = _options[key] as DeepRecord[typeof key]
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
return obj
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
return
|
|
44
|
+
return mergeObject(full, options)
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
// 加上千分位 ,
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM 元素生命週期管理工具
|
|
3
|
+
* 提供類似 D3.js 的 enter/update/exit 模式
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 比較兩個數組是否相等
|
|
8
|
+
*/
|
|
9
|
+
export function arraysEqual<T>(a: T[], b: T[]): boolean {
|
|
10
|
+
if (a.length !== b.length) return false
|
|
11
|
+
return a.every((val, i) => val === b[i])
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 高效的元素重新排序函數,使用最少的 DOM 操作
|
|
16
|
+
*/
|
|
17
|
+
export function reorderElements<T extends Element>(
|
|
18
|
+
parent: Element,
|
|
19
|
+
targetOrder: string[],
|
|
20
|
+
elementsRef: Record<string, T>
|
|
21
|
+
): void {
|
|
22
|
+
// 獲取當前所有相關的子元素
|
|
23
|
+
const currentElements = targetOrder
|
|
24
|
+
.map(name => elementsRef[name])
|
|
25
|
+
.filter(el => el && el.parentNode === parent)
|
|
26
|
+
|
|
27
|
+
if (currentElements.length === 0) return
|
|
28
|
+
|
|
29
|
+
// 檢查當前順序是否已經正確
|
|
30
|
+
let isCorrectOrder = true
|
|
31
|
+
let previousElement: Element | null = null
|
|
32
|
+
|
|
33
|
+
for (const element of currentElements) {
|
|
34
|
+
if (previousElement && previousElement.nextElementSibling !== element) {
|
|
35
|
+
isCorrectOrder = false
|
|
36
|
+
break
|
|
37
|
+
}
|
|
38
|
+
previousElement = element
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 如果順序已經正確,直接返回
|
|
42
|
+
if (isCorrectOrder) return
|
|
43
|
+
|
|
44
|
+
// 使用 DocumentFragment 來批量操作,減少 reflow
|
|
45
|
+
const fragment = document.createDocumentFragment()
|
|
46
|
+
|
|
47
|
+
// 按照目標順序將元素添加到 fragment
|
|
48
|
+
targetOrder.forEach(name => {
|
|
49
|
+
const element = elementsRef[name]
|
|
50
|
+
if (element && element.parentNode === parent) {
|
|
51
|
+
fragment.appendChild(element)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// 一次性將所有元素插入回父元素
|
|
56
|
+
parent.appendChild(fragment)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 元素工廠函數類型
|
|
61
|
+
*/
|
|
62
|
+
export type ElementFactory<T extends Element> = (id: string) => T
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 生命週期鉤子函數類型
|
|
66
|
+
*/
|
|
67
|
+
export interface LifecycleHooks<T extends Element> {
|
|
68
|
+
/** 元素進入時的回調 */
|
|
69
|
+
onEnter?: (element: T, id: string) => void
|
|
70
|
+
/** 元素更新時的回調 */
|
|
71
|
+
onUpdate?: (element: T, id: string) => void
|
|
72
|
+
/** 元素退出前的回調 */
|
|
73
|
+
onExit?: (element: T, id: string) => void
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* D3.js 風格的元素生命週期管理:處理 enter/update/exit
|
|
78
|
+
*
|
|
79
|
+
* @param parent 父容器元素
|
|
80
|
+
* @param targetIds 目標元素 ID 列表(按順序)
|
|
81
|
+
* @param elementsRef 元素引用字典
|
|
82
|
+
* @param createElement 元素創建工廠函數
|
|
83
|
+
* @param hooks 生命週期鉤子函數(可選)
|
|
84
|
+
*/
|
|
85
|
+
export function handleElementLifecycle<T extends Element>(
|
|
86
|
+
parent: Element,
|
|
87
|
+
targetIds: string[],
|
|
88
|
+
elementsRef: Record<string, T>,
|
|
89
|
+
createElement: ElementFactory<T>,
|
|
90
|
+
hooks?: LifecycleHooks<T>
|
|
91
|
+
): void {
|
|
92
|
+
// Exit: 移除不再需要的元素
|
|
93
|
+
const currentIds = Object.keys(elementsRef)
|
|
94
|
+
const toRemove = currentIds.filter(id => !targetIds.includes(id))
|
|
95
|
+
|
|
96
|
+
toRemove.forEach(id => {
|
|
97
|
+
const element = elementsRef[id]
|
|
98
|
+
if (element && element.parentNode === parent) {
|
|
99
|
+
// 執行退出回調
|
|
100
|
+
hooks?.onExit?.(element, id)
|
|
101
|
+
|
|
102
|
+
// 從 DOM 移除
|
|
103
|
+
parent.removeChild(element)
|
|
104
|
+
}
|
|
105
|
+
delete elementsRef[id]
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// Enter: 創建新元素
|
|
109
|
+
const toAdd = targetIds.filter(id => !elementsRef[id])
|
|
110
|
+
|
|
111
|
+
toAdd.forEach(id => {
|
|
112
|
+
const element = createElement(id)
|
|
113
|
+
elementsRef[id] = element
|
|
114
|
+
parent.appendChild(element)
|
|
115
|
+
|
|
116
|
+
// 執行進入回調
|
|
117
|
+
hooks?.onEnter?.(element, id)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Update: 處理已存在的元素
|
|
121
|
+
const existing = targetIds.filter(id => elementsRef[id] && !toAdd.includes(id))
|
|
122
|
+
existing.forEach(id => {
|
|
123
|
+
const element = elementsRef[id]
|
|
124
|
+
hooks?.onUpdate?.(element, id)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// 重新排序所有存在的元素
|
|
128
|
+
if (targetIds.length > 0) {
|
|
129
|
+
reorderElements(parent, targetIds, elementsRef)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// /**
|
|
134
|
+
// * 創建 SVG group 元素的工廠函數
|
|
135
|
+
// */
|
|
136
|
+
// export function createSVGGroup(className?: string): ElementFactory<SVGGElement> {
|
|
137
|
+
// return (id: string) => {
|
|
138
|
+
// const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
|
139
|
+
// g.classList.add(className ? `${className}--${id}` : `layer--${id}`)
|
|
140
|
+
// return g
|
|
141
|
+
// }
|
|
142
|
+
// }
|
|
143
|
+
|
|
144
|
+
// /**
|
|
145
|
+
// * 創建 Canvas 元素的工廠函數
|
|
146
|
+
// */
|
|
147
|
+
// export function createCanvas(className?: string): ElementFactory<HTMLCanvasElement> {
|
|
148
|
+
// return (id: string) => {
|
|
149
|
+
// const canvas = document.createElement('canvas')
|
|
150
|
+
// canvas.classList.add(className ? `${className}--${id}` : `layer--${id}`)
|
|
151
|
+
// return canvas
|
|
152
|
+
// }
|
|
153
|
+
// }
|
|
154
|
+
|
|
155
|
+
// /**
|
|
156
|
+
// * 創建通用 DIV 元素的工廠函數
|
|
157
|
+
// */
|
|
158
|
+
// export function createDiv(className?: string): ElementFactory<HTMLDivElement> {
|
|
159
|
+
// return (id: string) => {
|
|
160
|
+
// const div = document.createElement('div')
|
|
161
|
+
// div.classList.add(className ? `${className}--${id}` : `layer--${id}`)
|
|
162
|
+
// return div
|
|
163
|
+
// }
|
|
164
|
+
// }
|
package/src/utils/dom.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { isDom } from "./commonUtils";
|
|
2
|
+
|
|
3
|
+
export function createSVG (className?: string): SVGSVGElement {
|
|
4
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
5
|
+
svg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink')
|
|
6
|
+
svg.setAttribute('xmls', 'http://www.w3.org/2000/svg')
|
|
7
|
+
svg.setAttribute('version', '1.1')
|
|
8
|
+
svg.style.position = 'absolute'
|
|
9
|
+
if (className) {
|
|
10
|
+
svg.classList.add(className)
|
|
11
|
+
}
|
|
12
|
+
return svg
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createCanvasElement (className?: string): HTMLCanvasElement {
|
|
16
|
+
const canvas = document.createElement('canvas')
|
|
17
|
+
canvas.style.position = 'absolute'
|
|
18
|
+
if (className) {
|
|
19
|
+
canvas.classList.add(className)
|
|
20
|
+
}
|
|
21
|
+
return canvas
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createSVGGroup(className?: string): SVGGElement {
|
|
25
|
+
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
|
26
|
+
if (className) {
|
|
27
|
+
g.classList.add(className)
|
|
28
|
+
}
|
|
29
|
+
return g
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createCanvas(className?: string): HTMLCanvasElement {
|
|
33
|
+
const canvas = document.createElement('canvas')
|
|
34
|
+
if (className) {
|
|
35
|
+
canvas.classList.add(className)
|
|
36
|
+
}
|
|
37
|
+
return canvas
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createDiv(className?: string): HTMLDivElement {
|
|
41
|
+
const div = document.createElement('div')
|
|
42
|
+
if (className) {
|
|
43
|
+
div.classList.add(className)
|
|
44
|
+
}
|
|
45
|
+
return div
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 清空 element 底下所有元素
|
|
49
|
+
export function removeElementChildren(el: HTMLElement | Element) {
|
|
50
|
+
if (isDom(el)) {
|
|
51
|
+
while (el.firstChild) {
|
|
52
|
+
el.removeChild(el.firstChild)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|