@orbcharts/core 3.0.6 → 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/LICENSE +200 -200
- 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 +59 -46
- 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 +8 -20
- 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 +56 -55
- package/src/utils/dom-lifecycle.ts +164 -0
- package/src/utils/dom.ts +55 -0
- package/src/utils/errorMessage.ts +40 -40
- package/src/utils/index.ts +8 -4
- package/src/utils/observables.ts +16 -308
- package/src/utils/orbchartsUtils.ts +9 -396
- package/src/utils/validator.ts +127 -126
- 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,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
|
+
}
|
|
@@ -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
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
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
|
+
export * from './validator'
|
package/src/utils/observables.ts
CHANGED
|
@@ -1,309 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
ComputedDatumTypeMap,
|
|
18
|
-
ContainerPositionScaled,
|
|
19
|
-
DataFormatterContainer,
|
|
20
|
-
DataFormatterTypeMap,
|
|
21
|
-
EventTypeMap,
|
|
22
|
-
HighlightTarget,
|
|
23
|
-
Layout,
|
|
24
|
-
TransformData } from '../../lib/core-types'
|
|
25
|
-
|
|
26
|
-
// interface DatumUnknown {
|
|
27
|
-
// value: number | null
|
|
28
|
-
// id: string
|
|
29
|
-
// // label: string
|
|
30
|
-
// seriesLabel?: string // 要符合每一種computedData所以不一定會有seriesLabel
|
|
31
|
-
// groupLabel?: string // 要符合每一種computedData所以不一定會有groupLabel
|
|
32
|
-
// }
|
|
33
|
-
|
|
34
|
-
export function resizeObservable(elem: HTMLElement | Element): Observable<DOMRectReadOnly> {
|
|
35
|
-
return new Observable(subscriber => {
|
|
36
|
-
const ro = new ResizeObserver(entries => {
|
|
37
|
-
const entry = entries[0]
|
|
38
|
-
if (entry && entry.contentRect) {
|
|
39
|
-
subscriber.next(entry.contentRect)
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
ro.observe(elem)
|
|
44
|
-
return function unsubscribe() {
|
|
45
|
-
ro.unobserve(elem)
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface HighlightTargetValue {
|
|
51
|
-
id: string | null
|
|
52
|
-
label: string | null
|
|
53
|
-
seriesLabel: string | null
|
|
54
|
-
groupLabel: string | null
|
|
55
|
-
categoryLabel: string | null
|
|
56
|
-
highlightDefault: string | null
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 通用 highlight Observable
|
|
60
|
-
export const highlightObservable = <T extends ChartType, D>({ datumList$, fullChartParams$, event$ }: {
|
|
61
|
-
datumList$: Observable<D[]>
|
|
62
|
-
fullChartParams$: Observable<ChartParams>
|
|
63
|
-
event$: Subject<EventTypeMap<T>>
|
|
64
|
-
}): Observable<D[]> => {
|
|
65
|
-
const destroy$ = new Subject()
|
|
66
|
-
|
|
67
|
-
// 預設的highlight
|
|
68
|
-
const highlightDefault$: Observable<HighlightTargetValue> = fullChartParams$.pipe(
|
|
69
|
-
takeUntil(destroy$),
|
|
70
|
-
map(d => d.highlightDefault),
|
|
71
|
-
distinctUntilChanged(),
|
|
72
|
-
map(highlightDefault => {
|
|
73
|
-
return {
|
|
74
|
-
id: null,
|
|
75
|
-
label: null,
|
|
76
|
-
seriesLabel: null,
|
|
77
|
-
groupLabel: null,
|
|
78
|
-
categoryLabel: null,
|
|
79
|
-
highlightDefault
|
|
80
|
-
} as HighlightTargetValue
|
|
81
|
-
}),
|
|
82
|
-
shareReplay(1)
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
const highlightTarget$: Observable<HighlightTarget> = fullChartParams$.pipe(
|
|
86
|
-
takeUntil(destroy$),
|
|
87
|
-
map(d => d.highlightTarget),
|
|
88
|
-
distinctUntilChanged(),
|
|
89
|
-
shareReplay(1)
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
// 事件觸發的highlight
|
|
93
|
-
const highlightMouseover$: Observable<HighlightTargetValue> = highlightTarget$.pipe(
|
|
94
|
-
switchMap(highlightTarget => {
|
|
95
|
-
return event$.pipe(
|
|
96
|
-
takeUntil(destroy$),
|
|
97
|
-
// filter(d => d.eventName === 'mouseover' || d.eventName === 'mousemove'),
|
|
98
|
-
filter(d => d.eventName === 'mouseover'),
|
|
99
|
-
// distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
|
|
100
|
-
map(_d => {
|
|
101
|
-
const d = _d as any
|
|
102
|
-
return d.datum
|
|
103
|
-
? {
|
|
104
|
-
id: d.datum.id,
|
|
105
|
-
label: null, // label有可能重覆所以不做判斷
|
|
106
|
-
seriesLabel: highlightTarget === 'series' ? d.datum.seriesLabel : null,
|
|
107
|
-
groupLabel: highlightTarget === 'group' ? d.datum.groupLabel : null,
|
|
108
|
-
categoryLabel: highlightTarget === 'category' ? d.datum.categoryLabel : null,
|
|
109
|
-
highlightDefault: null
|
|
110
|
-
} as HighlightTargetValue
|
|
111
|
-
: {
|
|
112
|
-
id: null,
|
|
113
|
-
label: null,
|
|
114
|
-
seriesLabel: null,
|
|
115
|
-
groupLabel: null,
|
|
116
|
-
categoryLabel: null,
|
|
117
|
-
highlightDefault: null
|
|
118
|
-
} as HighlightTargetValue
|
|
119
|
-
})
|
|
120
|
-
)
|
|
121
|
-
})
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
const highlightMouseout$ = event$.pipe(
|
|
125
|
-
takeUntil(destroy$),
|
|
126
|
-
filter(d => d.eventName === 'mouseout'),
|
|
127
|
-
// distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
|
|
128
|
-
// map(d => {
|
|
129
|
-
// return { id: '', label: '' }
|
|
130
|
-
// })
|
|
131
|
-
switchMap(d => highlightDefault$)
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
// function getDatumIds (datumList: ComputedDatumTypeMap<T>[], id: string | null) {
|
|
135
|
-
// const datum = datumList.find(d => (d as ComputedDatumBase).id === id)
|
|
136
|
-
// return datum ? [datum] : []
|
|
137
|
-
// }
|
|
138
|
-
function getDatumIds (datumList: ComputedDatumTypeMap<T>[], id: string | null, label: string | null) {
|
|
139
|
-
return id == null && label == null
|
|
140
|
-
? []
|
|
141
|
-
: datumList.filter(d => (d as ComputedDatumBase).id === id || (d as ComputedDatumBase).label === label)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function getSeriesIds (datumList: ComputedDatumTypeMap<T>[], seriesLabel: string | null) {
|
|
145
|
-
return seriesLabel == null
|
|
146
|
-
? []
|
|
147
|
-
: datumList.filter(d => (d as ComputedDatumTypeMap<"series">).seriesLabel === seriesLabel)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function getGroupIds (datumList: ComputedDatumTypeMap<T>[], groupLabel: string | null) {
|
|
151
|
-
return groupLabel == null
|
|
152
|
-
? []
|
|
153
|
-
: datumList.filter(d => (d as ComputedDatumTypeMap<"grid">).groupLabel === groupLabel)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function getCategoryIds (datumList: ComputedDatumTypeMap<T>[], categoryLabel: string | null) {
|
|
157
|
-
return categoryLabel == null
|
|
158
|
-
? []
|
|
159
|
-
: datumList.filter(d => (d as ComputedDatumTypeMap<"multiValue" | "relationship" | "tree">).categoryLabel === categoryLabel)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return new Observable<D[]>(subscriber => {
|
|
163
|
-
combineLatest({
|
|
164
|
-
target: merge(highlightMouseover$, highlightMouseout$, highlightDefault$),
|
|
165
|
-
datumList: datumList$,
|
|
166
|
-
fullChartParams: fullChartParams$,
|
|
167
|
-
}).pipe(
|
|
168
|
-
takeUntil(destroy$),
|
|
169
|
-
switchMap(async d => d)
|
|
170
|
-
).subscribe(data => {
|
|
171
|
-
let datumList: ComputedDatumTypeMap<T>[] = []
|
|
172
|
-
if (data.fullChartParams.highlightTarget === 'datum') {
|
|
173
|
-
datumList = getDatumIds(data.datumList as ComputedDatumTypeMap<T>[], data.target.id, data.target.label)
|
|
174
|
-
} else if (data.fullChartParams.highlightTarget === 'series') {
|
|
175
|
-
datumList = getSeriesIds(data.datumList as ComputedDatumTypeMap<T>[], data.target.seriesLabel)
|
|
176
|
-
} else if (data.fullChartParams.highlightTarget === 'group') {
|
|
177
|
-
datumList = getGroupIds(data.datumList as ComputedDatumTypeMap<T>[], data.target.groupLabel)
|
|
178
|
-
} else if (data.fullChartParams.highlightTarget === 'category') {
|
|
179
|
-
datumList = getCategoryIds(data.datumList as ComputedDatumTypeMap<T>[], data.target.categoryLabel)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
subscriber.next(datumList as D[])
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
return function unsubscribe () {
|
|
186
|
-
destroy$.next(undefined)
|
|
187
|
-
}
|
|
188
|
-
})
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export const seriesDataMapObservable = <DatumType extends ComputedDatumTypeMap<'series' | 'grid'>>({ datumList$ }: { datumList$: Observable<DatumType[]> }) => {
|
|
192
|
-
return datumList$.pipe(
|
|
193
|
-
map(data => {
|
|
194
|
-
const SeriesDataMap: Map<string, DatumType[]> = new Map()
|
|
195
|
-
data.forEach(d => {
|
|
196
|
-
const seriesData = SeriesDataMap.get(d.seriesLabel) ?? []
|
|
197
|
-
seriesData.push(d)
|
|
198
|
-
SeriesDataMap.set(d.seriesLabel, seriesData)
|
|
199
|
-
})
|
|
200
|
-
return SeriesDataMap
|
|
201
|
-
})
|
|
202
|
-
)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export const groupDataMapObservable = <DatumType extends ComputedDatumTypeMap<'grid'>> ({ datumList$ }: { datumList$: Observable<DatumType[]> }) => {
|
|
206
|
-
return datumList$.pipe(
|
|
207
|
-
map(data => {
|
|
208
|
-
const GroupDataMap: Map<string, DatumType[]> = new Map()
|
|
209
|
-
data.forEach(d => {
|
|
210
|
-
const groupData = GroupDataMap.get(d.groupLabel) ?? []
|
|
211
|
-
groupData.push(d)
|
|
212
|
-
GroupDataMap.set(d.groupLabel, groupData)
|
|
213
|
-
})
|
|
214
|
-
return GroupDataMap
|
|
215
|
-
})
|
|
216
|
-
)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export const categoryDataMapObservable = <DatumType extends ComputedDatumTypeMap<'multiValue' | 'relationship' | 'tree'>> ({ datumList$ }: { datumList$: Observable<DatumType[]> }) => {
|
|
220
|
-
return datumList$.pipe(
|
|
221
|
-
map(data => {
|
|
222
|
-
const GroupDataMap: Map<string, DatumType[]> = new Map()
|
|
223
|
-
data
|
|
224
|
-
.filter(d => d.categoryLabel != null)
|
|
225
|
-
.forEach(d => {
|
|
226
|
-
const groupData = GroupDataMap.get(d.categoryLabel) ?? []
|
|
227
|
-
groupData.push(d)
|
|
228
|
-
GroupDataMap.set(d.categoryLabel, groupData)
|
|
229
|
-
})
|
|
230
|
-
return GroupDataMap
|
|
231
|
-
})
|
|
232
|
-
)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export const textSizePxObservable = (chartParams$: Observable<ChartParams>) => {
|
|
236
|
-
return chartParams$.pipe(
|
|
237
|
-
map(d => d.styles.textSize),
|
|
238
|
-
distinctUntilChanged(),
|
|
239
|
-
map(data => {
|
|
240
|
-
let value = NaN
|
|
241
|
-
if (typeof data === 'string') {
|
|
242
|
-
if (data.includes('rem')) {
|
|
243
|
-
const rootFontSizePx = parseFloat(getComputedStyle(document.documentElement).fontSize)
|
|
244
|
-
const num = parseFloat(data)
|
|
245
|
-
value = num * rootFontSizePx
|
|
246
|
-
} else if (data.includes('px')) {
|
|
247
|
-
value = parseFloat(data)
|
|
248
|
-
}
|
|
249
|
-
} else if (typeof data === 'number') {
|
|
250
|
-
return data
|
|
251
|
-
}
|
|
252
|
-
return value ? value : 14 // default
|
|
253
|
-
})
|
|
254
|
-
)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export const containerSizeObservable = ({ layout$, containerPosition$, container$ }: {
|
|
258
|
-
layout$: Observable<Layout>
|
|
259
|
-
containerPosition$: Observable<ContainerPositionScaled[]>
|
|
260
|
-
container$: Observable<DataFormatterContainer>
|
|
261
|
-
}) => {
|
|
262
|
-
const rowAmount$ = containerPosition$.pipe(
|
|
263
|
-
map(containerPosition => {
|
|
264
|
-
const maxRowIndex = containerPosition.reduce((acc, current) => {
|
|
265
|
-
return current.rowIndex > acc ? current.rowIndex : acc
|
|
266
|
-
}, 0)
|
|
267
|
-
return maxRowIndex + 1
|
|
268
|
-
}),
|
|
269
|
-
distinctUntilChanged(),
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
const columnAmount$ = containerPosition$.pipe(
|
|
273
|
-
map(containerPosition => {
|
|
274
|
-
const maxColumnIndex = containerPosition.reduce((acc, current) => {
|
|
275
|
-
return current.columnIndex > acc ? current.columnIndex : acc
|
|
276
|
-
}, 0)
|
|
277
|
-
return maxColumnIndex + 1
|
|
278
|
-
}),
|
|
279
|
-
distinctUntilChanged()
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
return combineLatest({
|
|
283
|
-
layout: layout$,
|
|
284
|
-
rowAmount: rowAmount$,
|
|
285
|
-
columnAmount: columnAmount$,
|
|
286
|
-
container: container$
|
|
287
|
-
}).pipe(
|
|
288
|
-
switchMap(async (d) => d),
|
|
289
|
-
map(data => {
|
|
290
|
-
// const width = (data.layout.rootWidth / data.columnAmount) - (data.layout.left + data.layout.right)
|
|
291
|
-
// const height = (data.layout.rootHeight / data.rowAmount) - (data.layout.top + data.layout.bottom)
|
|
292
|
-
const columnGap = data.container.columnGap === 'auto'
|
|
293
|
-
? data.layout.left + data.layout.right
|
|
294
|
-
: data.container.columnGap
|
|
295
|
-
const rowGap = data.container.rowGap === 'auto'
|
|
296
|
-
? data.layout.top + data.layout.bottom
|
|
297
|
-
: data.container.rowGap
|
|
298
|
-
const width = (data.layout.rootWidth - data.layout.left - data.layout.right - (columnGap * (data.columnAmount - 1))) / data.columnAmount
|
|
299
|
-
const height = (data.layout.rootHeight - data.layout.top - data.layout.bottom - (rowGap * (data.rowAmount - 1))) / data.rowAmount
|
|
300
|
-
|
|
301
|
-
return {
|
|
302
|
-
width,
|
|
303
|
-
height
|
|
304
|
-
}
|
|
305
|
-
}),
|
|
306
|
-
distinctUntilChanged((a, b) => a.width === b.width && a.height === b.height),
|
|
307
|
-
// shareReplay(1)
|
|
308
|
-
)
|
|
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
|
+
})
|
|
309
17
|
}
|