@orbcharts/core 4.0.0-alpha.0 → 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-core.es.js +876 -865
  3. package/dist/orbcharts-core.umd.js +3 -3
  4. package/dist/src/types/Plugin.d.ts +1 -1
  5. package/package.json +1 -1
  6. package/src/OrbCharts.ts +34 -34
  7. package/src/chart/createChart.ts +1013 -996
  8. package/src/chart/createGraphData.ts +391 -391
  9. package/src/chart/createGridData.ts +247 -247
  10. package/src/chart/createMultivariateData.ts +181 -181
  11. package/src/chart/createSeriesData.ts +297 -297
  12. package/src/chart/createTreeData.ts +344 -344
  13. package/src/chart/defaults.ts +119 -119
  14. package/src/defineCanvasLayer.ts +23 -23
  15. package/src/defineCanvasPlugin.ts +38 -38
  16. package/src/defineSVGLayer.ts +23 -23
  17. package/src/defineSVGPlugin.ts +38 -38
  18. package/src/index.ts +8 -8
  19. package/src/layer/createLayer.ts +137 -137
  20. package/src/plugin/createPlugin.ts +487 -469
  21. package/src/test/createGraphData.test.ts +103 -103
  22. package/src/test/createTreeData.test.ts +97 -97
  23. package/src/test/simple-graph-test.js +51 -51
  24. package/src/test/simple-tree-test.js +58 -58
  25. package/src/types/Chart.ts +62 -62
  26. package/src/types/ChartContext.ts +41 -41
  27. package/src/types/Common.ts +4 -4
  28. package/src/types/Encoding.ts +42 -42
  29. package/src/types/Event.ts +25 -25
  30. package/src/types/Layers.ts +92 -92
  31. package/src/types/ModelData.ts +94 -94
  32. package/src/types/Plugin.ts +101 -98
  33. package/src/types/RawData.ts +67 -67
  34. package/src/types/RenderData.ts +15 -15
  35. package/src/types/Theme.ts +20 -20
  36. package/src/types/Validator.ts +35 -35
  37. package/src/types/index.ts +12 -12
  38. package/src/utils/aggregateUtils.ts +99 -99
  39. package/src/utils/colorUtils.ts +63 -63
  40. package/src/utils/commonUtils.ts +56 -56
  41. package/src/utils/dom-lifecycle.ts +164 -164
  42. package/src/utils/dom.ts +54 -54
  43. package/src/utils/errorMessage.ts +40 -40
  44. package/src/utils/index.ts +7 -7
  45. package/src/utils/observables.ts +16 -16
  46. package/src/utils/orbchartsUtils.ts +8 -8
  47. package/src/utils/validator.ts +127 -127
@@ -1,164 +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
- // }
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 CHANGED
@@ -1,55 +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
- }
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
55
  }
@@ -1,41 +1,41 @@
1
- // message的prefix - error: 有中斷,warning: 無中斷
2
- export function createMessagePrefix (status: 'warning' | 'error'): string {
3
- return `[OrbCharts ${status}]:`
4
- }
5
-
6
- // throw到最外層的錯誤訊息
7
- export function createOrbChartsErrorMessage (e: Error): string {
8
- return `${createMessagePrefix('error')} ${e.message}`
9
- }
10
-
11
-
12
- // 未預期的錯誤
13
- export function createUnexpectedErrorMessage ({ from, systemMessage }: {
14
- from: string //
15
- systemMessage: string // catch 給的的原生錯誤訊息
16
- }): string {
17
- return `unexpected error from '${from}':
18
- ${systemMessage}`
19
- }
20
-
21
- // validator 的 error 訊息
22
- export function createValidatorErrorMessage ({ columnName, expectToBe, from }: {
23
- columnName: string // e.g. 'seriesLabels'
24
- expectToBe: string // e.g. 'string[]'
25
- from: string // e.g. Chart.chartParams$, Pie.params$
26
- }): string {
27
- return `Invalid value: '${columnName}' must be '${expectToBe}'
28
-
29
- ----> find in '${from}'`
30
- }
31
-
32
- // validator 的 warning 訊息
33
- export function createValidatorWarningMessage ({ columnName, expectToBe, from }: {
34
- columnName: string // e.g. 'seriesLabels'
35
- expectToBe: string // e.g. 'string[]'
36
- from: string // e.g. Chart.chartParams$, Pie.params$
37
- }): string {
38
- return `${createMessagePrefix('warning')} Value is not correct: '${columnName}' suppose to be '${expectToBe}', it may cause unexpected errors.'
39
-
40
- ----> find in '${from}'`
1
+ // message的prefix - error: 有中斷,warning: 無中斷
2
+ export function createMessagePrefix (status: 'warning' | 'error'): string {
3
+ return `[OrbCharts ${status}]:`
4
+ }
5
+
6
+ // throw到最外層的錯誤訊息
7
+ export function createOrbChartsErrorMessage (e: Error): string {
8
+ return `${createMessagePrefix('error')} ${e.message}`
9
+ }
10
+
11
+
12
+ // 未預期的錯誤
13
+ export function createUnexpectedErrorMessage ({ from, systemMessage }: {
14
+ from: string //
15
+ systemMessage: string // catch 給的的原生錯誤訊息
16
+ }): string {
17
+ return `unexpected error from '${from}':
18
+ ${systemMessage}`
19
+ }
20
+
21
+ // validator 的 error 訊息
22
+ export function createValidatorErrorMessage ({ columnName, expectToBe, from }: {
23
+ columnName: string // e.g. 'seriesLabels'
24
+ expectToBe: string // e.g. 'string[]'
25
+ from: string // e.g. Chart.chartParams$, Pie.params$
26
+ }): string {
27
+ return `Invalid value: '${columnName}' must be '${expectToBe}'
28
+
29
+ ----> find in '${from}'`
30
+ }
31
+
32
+ // validator 的 warning 訊息
33
+ export function createValidatorWarningMessage ({ columnName, expectToBe, from }: {
34
+ columnName: string // e.g. 'seriesLabels'
35
+ expectToBe: string // e.g. 'string[]'
36
+ from: string // e.g. Chart.chartParams$, Pie.params$
37
+ }): string {
38
+ return `${createMessagePrefix('warning')} Value is not correct: '${columnName}' suppose to be '${expectToBe}', it may cause unexpected errors.'
39
+
40
+ ----> find in '${from}'`
41
41
  }
@@ -1,8 +1,8 @@
1
- export * from './aggregateUtils'
2
- export * from './colorUtils'
3
- export * from './commonUtils'
4
- export * from './dom'
5
- export * from './errorMessage'
6
- export * from './observables'
7
- export * from './orbchartsUtils'
1
+ export * from './aggregateUtils'
2
+ export * from './colorUtils'
3
+ export * from './commonUtils'
4
+ export * from './dom'
5
+ export * from './errorMessage'
6
+ export * from './observables'
7
+ export * from './orbchartsUtils'
8
8
  export * from './validator'
@@ -1,17 +1,17 @@
1
- import { Observable, BehaviorSubject } from 'rxjs'
2
-
3
- export function resizeObservable(elem: HTMLElement | Element): Observable<DOMRectReadOnly> {
4
- return new Observable(subscriber => {
5
- const ro = new ResizeObserver(entries => {
6
- const entry = entries[0]
7
- if (entry && entry.contentRect) {
8
- subscriber.next(entry.contentRect)
9
- }
10
- })
11
-
12
- ro.observe(elem)
13
- return function unsubscribe() {
14
- ro.unobserve(elem)
15
- }
16
- })
1
+ import { Observable, BehaviorSubject } from 'rxjs'
2
+
3
+ export function resizeObservable(elem: HTMLElement | Element): Observable<DOMRectReadOnly> {
4
+ return new Observable(subscriber => {
5
+ const ro = new ResizeObserver(entries => {
6
+ const entry = entries[0]
7
+ if (entry && entry.contentRect) {
8
+ subscriber.next(entry.contentRect)
9
+ }
10
+ })
11
+
12
+ ro.observe(elem)
13
+ return function unsubscribe() {
14
+ ro.unobserve(elem)
15
+ }
16
+ })
17
17
  }
@@ -1,9 +1,9 @@
1
-
2
- export function createPluginClassName (pluginName: string) {
3
- return `orbcharts-${pluginName}`
4
- }
5
-
6
- export function createLayerClassName (pluginName: string, layerName: string) {
7
- // orbcharts-pluginName-layerName
8
- return `${createPluginClassName(pluginName)}-${layerName}`
1
+
2
+ export function createPluginClassName (pluginName: string) {
3
+ return `orbcharts-${pluginName}`
4
+ }
5
+
6
+ export function createLayerClassName (pluginName: string, layerName: string) {
7
+ // orbcharts-pluginName-layerName
8
+ return `${createPluginClassName(pluginName)}-${layerName}`
9
9
  }