@orbcharts/core 3.0.0-beta.5 → 3.0.0-beta.7
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 +1481 -1412
- package/dist/orbcharts-core.umd.js +4 -4
- package/dist/src/defaults.d.ts +22 -22
- package/dist/src/utils/orbchartsUtils.d.ts +7 -7
- package/dist/src/utils/relationshipObservables.d.ts +13 -0
- package/lib/core-types.ts +7 -7
- package/package.json +42 -42
- package/src/AbstractChart.ts +57 -57
- package/src/GridChart.ts +24 -24
- package/src/MultiGridChart.ts +24 -24
- package/src/MultiValueChart.ts +24 -24
- package/src/RelationshipChart.ts +24 -24
- package/src/SeriesChart.ts +24 -24
- package/src/TreeChart.ts +24 -24
- package/src/base/createBaseChart.ts +505 -505
- package/src/base/createBasePlugin.ts +153 -153
- package/src/base/validators/chartOptionsValidator.ts +23 -23
- package/src/base/validators/chartParamsValidator.ts +133 -133
- package/src/base/validators/elementValidator.ts +13 -13
- package/src/base/validators/pluginsValidator.ts +14 -14
- package/src/defaults.ts +235 -235
- package/src/defineGridPlugin.ts +3 -3
- package/src/defineMultiGridPlugin.ts +3 -3
- package/src/defineMultiValuePlugin.ts +3 -3
- package/src/defineNoneDataPlugin.ts +4 -4
- package/src/defineRelationshipPlugin.ts +3 -3
- package/src/defineSeriesPlugin.ts +3 -3
- package/src/defineTreePlugin.ts +3 -3
- package/src/grid/computedDataFn.ts +129 -129
- package/src/grid/contextObserverCallback.ts +176 -176
- package/src/grid/dataFormatterValidator.ts +101 -101
- package/src/grid/dataValidator.ts +12 -12
- package/src/index.ts +20 -20
- package/src/multiGrid/computedDataFn.ts +123 -123
- package/src/multiGrid/contextObserverCallback.ts +41 -41
- package/src/multiGrid/dataFormatterValidator.ts +115 -115
- package/src/multiGrid/dataValidator.ts +12 -12
- package/src/multiValue/computedDataFn.ts +110 -110
- package/src/multiValue/contextObserverCallback.ts +160 -160
- package/src/multiValue/dataFormatterValidator.ts +9 -9
- package/src/multiValue/dataValidator.ts +9 -9
- package/src/relationship/computedDataFn.ts +144 -125
- package/src/relationship/contextObserverCallback.ts +80 -12
- package/src/relationship/dataFormatterValidator.ts +9 -9
- package/src/relationship/dataValidator.ts +9 -9
- package/src/series/computedDataFn.ts +88 -88
- package/src/series/contextObserverCallback.ts +100 -100
- package/src/series/dataFormatterValidator.ts +41 -41
- package/src/series/dataValidator.ts +12 -12
- package/src/tree/computedDataFn.ts +129 -129
- package/src/tree/contextObserverCallback.ts +58 -58
- package/src/tree/dataFormatterValidator.ts +13 -13
- package/src/tree/dataValidator.ts +13 -13
- package/src/utils/commonUtils.ts +55 -55
- package/src/utils/d3Scale.ts +198 -198
- package/src/utils/errorMessage.ts +42 -42
- package/src/utils/gridObservables.ts +683 -683
- package/src/utils/index.ts +9 -9
- package/src/utils/multiGridObservables.ts +392 -392
- package/src/utils/multiValueObservables.ts +661 -661
- package/src/utils/observables.ts +219 -219
- package/src/utils/orbchartsUtils.ts +377 -377
- package/src/utils/relationshipObservables.ts +85 -0
- package/src/utils/seriesObservables.ts +175 -175
- package/src/utils/treeObservables.ts +105 -105
- package/src/utils/validator.ts +126 -126
- package/tsconfig.base.json +13 -13
- package/tsconfig.json +2 -2
- package/vite-env.d.ts +6 -6
- package/vite.config.js +22 -22
@@ -1,505 +1,505 @@
|
|
1
|
-
import * as d3 from 'd3'
|
2
|
-
import {
|
3
|
-
combineLatest,
|
4
|
-
iif,
|
5
|
-
of,
|
6
|
-
EMPTY,
|
7
|
-
Subject,
|
8
|
-
BehaviorSubject,
|
9
|
-
Observable,
|
10
|
-
first,
|
11
|
-
takeUntil,
|
12
|
-
catchError,
|
13
|
-
throwError } from 'rxjs'
|
14
|
-
import {
|
15
|
-
map,
|
16
|
-
mergeWith,
|
17
|
-
concatMap,
|
18
|
-
switchMap,
|
19
|
-
switchAll,
|
20
|
-
throttleTime,
|
21
|
-
debounceTime,
|
22
|
-
distinctUntilChanged,
|
23
|
-
share,
|
24
|
-
shareReplay,
|
25
|
-
filter,
|
26
|
-
take,
|
27
|
-
startWith,
|
28
|
-
scan,
|
29
|
-
} from 'rxjs/operators'
|
30
|
-
import type {
|
31
|
-
CreateBaseChart,
|
32
|
-
CreateChart,
|
33
|
-
ComputedDataFn,
|
34
|
-
ChartEntity,
|
35
|
-
ChartType,
|
36
|
-
ChartParams,
|
37
|
-
ChartParamsPartial,
|
38
|
-
ContextSubject,
|
39
|
-
ComputedDataTypeMap,
|
40
|
-
ContextObserverCallback,
|
41
|
-
ChartOptionsPartial,
|
42
|
-
DataTypeMap,
|
43
|
-
DataFormatterTypeMap,
|
44
|
-
DataFormatterPartialTypeMap,
|
45
|
-
DataFormatterBase,
|
46
|
-
DataFormatterContext,
|
47
|
-
DataFormatterValidator,
|
48
|
-
DataValidator,
|
49
|
-
Layout,
|
50
|
-
PluginEntity,
|
51
|
-
PluginContext,
|
52
|
-
Preset,
|
53
|
-
PresetPartial,
|
54
|
-
ContextObserverTypeMap,
|
55
|
-
ValidatorResult,
|
56
|
-
} from '../../lib/core-types'
|
57
|
-
import { mergeOptionsWithDefault, resizeObservable } from '../utils'
|
58
|
-
import { createValidatorErrorMessage, createValidatorWarningMessage, createOrbChartsErrorMessage } from '../utils/errorMessage'
|
59
|
-
import { chartOptionsValidator } from './validators/chartOptionsValidator'
|
60
|
-
import { elementValidator } from './validators/elementValidator'
|
61
|
-
import { chartParamsValidator } from './validators/chartParamsValidator'
|
62
|
-
import { pluginsValidator } from './validators/pluginsValidator'
|
63
|
-
import {
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
// 判斷dataFormatter是否需要size參數
|
71
|
-
// const isAxesTypeMap: {[key in ChartType]: Boolean} = {
|
72
|
-
// series: false,
|
73
|
-
// grid: true,
|
74
|
-
// multiGrid: true,
|
75
|
-
// multiValue: true,
|
76
|
-
// tree: false,
|
77
|
-
// relationship: false
|
78
|
-
// }
|
79
|
-
|
80
|
-
|
81
|
-
function mergeDataFormatter <T>(dataFormatter: any, defaultDataFormatter: T, chartType: ChartType): T {
|
82
|
-
const mergedData = mergeOptionsWithDefault(dataFormatter, defaultDataFormatter)
|
83
|
-
|
84
|
-
if (chartType === 'multiGrid' && (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList != null) {
|
85
|
-
// multiGrid欄位為陣列,需要各別來merge預設值
|
86
|
-
(mergedData as DataFormatterTypeMap<'multiGrid'>).gridList = (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList.map((d, i) => {
|
87
|
-
const defaultGrid = (defaultDataFormatter as DataFormatterTypeMap<'multiGrid'>).gridList[i] || (defaultDataFormatter as DataFormatterTypeMap<'multiGrid'>).gridList[0]
|
88
|
-
return mergeOptionsWithDefault(d, defaultGrid)
|
89
|
-
})
|
90
|
-
}
|
91
|
-
return mergedData
|
92
|
-
}
|
93
|
-
|
94
|
-
export const createBaseChart: CreateBaseChart = <T extends ChartType>({
|
95
|
-
defaultDataFormatter,
|
96
|
-
dataFormatterValidator,
|
97
|
-
computedDataFn,
|
98
|
-
dataValidator,
|
99
|
-
contextObserverCallback
|
100
|
-
}: {
|
101
|
-
defaultDataFormatter: DataFormatterTypeMap<T>
|
102
|
-
dataFormatterValidator: DataFormatterValidator<T>
|
103
|
-
computedDataFn: ComputedDataFn<T>
|
104
|
-
dataValidator: DataValidator<T>
|
105
|
-
contextObserverCallback: ContextObserverCallback<T>
|
106
|
-
}): CreateChart<T> => {
|
107
|
-
const destroy$ = new Subject()
|
108
|
-
|
109
|
-
const chartType: ChartType = (defaultDataFormatter as unknown as DataFormatterBase<any>).type
|
110
|
-
|
111
|
-
// 建立chart實例
|
112
|
-
return function createChart (element: HTMLElement | Element, options?: ChartOptionsPartial<T>): ChartEntity<T> {
|
113
|
-
try {
|
114
|
-
const { status, columnName, expectToBe } = chartOptionsValidator(options)
|
115
|
-
if (status === 'error') {
|
116
|
-
throw new Error(createValidatorErrorMessage({
|
117
|
-
columnName,
|
118
|
-
expectToBe,
|
119
|
-
from: 'Chart.constructor'
|
120
|
-
}))
|
121
|
-
} else if (status === 'warning') {
|
122
|
-
console.warn(createValidatorWarningMessage({
|
123
|
-
columnName,
|
124
|
-
expectToBe,
|
125
|
-
from: 'Chart.constructor'
|
126
|
-
}))
|
127
|
-
} else {
|
128
|
-
const { status, columnName, expectToBe } = elementValidator(element)
|
129
|
-
if (status === 'error') {
|
130
|
-
throw new Error(createValidatorErrorMessage({
|
131
|
-
columnName,
|
132
|
-
expectToBe,
|
133
|
-
from: 'Chart.constructor'
|
134
|
-
}))
|
135
|
-
} else if (status === 'warning') {
|
136
|
-
console.warn(createValidatorWarningMessage({
|
137
|
-
columnName,
|
138
|
-
expectToBe,
|
139
|
-
from: 'Chart.constructor'
|
140
|
-
}))
|
141
|
-
}
|
142
|
-
}
|
143
|
-
} catch (e) {
|
144
|
-
throw new Error(e)
|
145
|
-
}
|
146
|
-
|
147
|
-
|
148
|
-
// -- selections --
|
149
|
-
// svg selection
|
150
|
-
d3.select(element).selectAll('svg').remove()
|
151
|
-
const svgSelection = d3.select(element).append('svg')
|
152
|
-
svgSelection
|
153
|
-
.attr('xmlns:xlink', 'http://www.w3.org/1999/xlink')
|
154
|
-
.attr('xmls', 'http://www.w3.org/2000/svg')
|
155
|
-
.attr('version', '1.1')
|
156
|
-
.style('position', 'absolute')
|
157
|
-
// .style('width', '100%')
|
158
|
-
// .style('height', '100%')
|
159
|
-
.classed('orbcharts__root', true)
|
160
|
-
// 傳入操作的 selection
|
161
|
-
const selectionLayout = svgSelection.append('g')
|
162
|
-
selectionLayout.classed('orbcharts__layout', true)
|
163
|
-
const selectionPlugins = selectionLayout.append('g')
|
164
|
-
selectionPlugins.classed('orbcharts__plugins', true)
|
165
|
-
|
166
|
-
// chartSubject
|
167
|
-
const chartSubject: ContextSubject<T> = {
|
168
|
-
data$: new Subject(),
|
169
|
-
dataFormatter$: new Subject(),
|
170
|
-
plugins$: new Subject(),
|
171
|
-
chartParams$: new Subject(),
|
172
|
-
event$: new Subject()
|
173
|
-
}
|
174
|
-
|
175
|
-
// options
|
176
|
-
const mergedPresetWithDefault: Preset<T, any> = ((options) => {
|
177
|
-
const _options = options ? options :
|
178
|
-
const preset = _options.preset ? _options.preset : {} as PresetPartial<T, any>
|
179
|
-
|
180
|
-
return {
|
181
|
-
name: preset.name ?? '',
|
182
|
-
description: preset.description ?? '',
|
183
|
-
chartParams: preset.chartParams
|
184
|
-
? mergeOptionsWithDefault(preset.chartParams,
|
185
|
-
:
|
186
|
-
dataFormatter: preset.dataFormatter
|
187
|
-
// ? mergeOptionsWithDefault(preset.dataFormatter, defaultDataFormatter)
|
188
|
-
? mergeDataFormatter(preset.dataFormatter, defaultDataFormatter, chartType)
|
189
|
-
: defaultDataFormatter,
|
190
|
-
allPluginParams: preset.allPluginParams
|
191
|
-
? preset.allPluginParams
|
192
|
-
: {}
|
193
|
-
}
|
194
|
-
})(options)
|
195
|
-
|
196
|
-
const sharedData$ = chartSubject.data$.pipe(shareReplay(1))
|
197
|
-
const shareAndMergedDataFormatter$ = chartSubject.dataFormatter$
|
198
|
-
.pipe(
|
199
|
-
takeUntil(destroy$),
|
200
|
-
startWith({} as DataFormatterPartialTypeMap<T>),
|
201
|
-
map((dataFormatter) => {
|
202
|
-
try {
|
203
|
-
// 檢查 dataFormatter$ 資料格式是否正確
|
204
|
-
const { status, columnName, expectToBe } = dataFormatterValidator(dataFormatter)
|
205
|
-
if (status === 'error') {
|
206
|
-
throw new Error(createValidatorErrorMessage({
|
207
|
-
columnName,
|
208
|
-
expectToBe,
|
209
|
-
from: 'Chart.dataFormatter$'
|
210
|
-
}))
|
211
|
-
} else if (status === 'warning') {
|
212
|
-
console.warn(createValidatorWarningMessage({
|
213
|
-
columnName,
|
214
|
-
expectToBe,
|
215
|
-
from: 'Chart.dataFormatter$'
|
216
|
-
}))
|
217
|
-
}
|
218
|
-
} catch (e) {
|
219
|
-
// throw new Error(e)
|
220
|
-
// 驗證失敗仍繼續執行,才不會把 Observable 資料流給中斷掉
|
221
|
-
console.error(createOrbChartsErrorMessage(e))
|
222
|
-
}
|
223
|
-
return mergeDataFormatter(dataFormatter, mergedPresetWithDefault.dataFormatter, chartType)
|
224
|
-
}),
|
225
|
-
// catchError((e) => {
|
226
|
-
// console.error(createOrbChartsErrorMessage(e))
|
227
|
-
// return EMPTY
|
228
|
-
// }),
|
229
|
-
shareReplay(1)
|
230
|
-
)
|
231
|
-
const shareAndMergedChartParams$ = chartSubject.chartParams$
|
232
|
-
.pipe(
|
233
|
-
takeUntil(destroy$),
|
234
|
-
startWith({}),
|
235
|
-
map((d) => {
|
236
|
-
try {
|
237
|
-
// 檢查 chartParams$ 資料格式是否正確
|
238
|
-
const { status, columnName, expectToBe } = chartParamsValidator(chartType, d)
|
239
|
-
if (status === 'error') {
|
240
|
-
throw new Error(createValidatorErrorMessage({
|
241
|
-
columnName,
|
242
|
-
expectToBe,
|
243
|
-
from: 'Chart.chartParams$'
|
244
|
-
}))
|
245
|
-
} else if (status === 'warning') {
|
246
|
-
console.warn(createValidatorWarningMessage({
|
247
|
-
columnName,
|
248
|
-
expectToBe,
|
249
|
-
from: 'Chart.chartParams$'
|
250
|
-
}))
|
251
|
-
}
|
252
|
-
} catch (e) {
|
253
|
-
// throw new Error(e)
|
254
|
-
// 驗證失敗仍繼續執行,才不會把 Observable 資料流給中斷掉
|
255
|
-
console.error(createOrbChartsErrorMessage(e))
|
256
|
-
}
|
257
|
-
return mergeOptionsWithDefault(d, mergedPresetWithDefault.chartParams)
|
258
|
-
}),
|
259
|
-
// catchError((e) => {
|
260
|
-
// console.error(createOrbChartsErrorMessage(e))
|
261
|
-
// return EMPTY
|
262
|
-
// }),
|
263
|
-
shareReplay(1)
|
264
|
-
)
|
265
|
-
|
266
|
-
// -- size --
|
267
|
-
// padding
|
268
|
-
const mergedPadding$ = shareAndMergedChartParams$
|
269
|
-
.pipe(
|
270
|
-
takeUntil(destroy$),
|
271
|
-
startWith({}),
|
272
|
-
map((d: any) => {
|
273
|
-
return mergeOptionsWithDefault(d.padding ?? {},
|
274
|
-
})
|
275
|
-
)
|
276
|
-
mergedPadding$
|
277
|
-
.pipe(
|
278
|
-
takeUntil(destroy$),
|
279
|
-
first()
|
280
|
-
)
|
281
|
-
.subscribe(d => {
|
282
|
-
selectionLayout
|
283
|
-
.attr('transform', `translate(${d.left}, ${d.top})`)
|
284
|
-
})
|
285
|
-
mergedPadding$.subscribe(size => {
|
286
|
-
selectionLayout
|
287
|
-
.transition()
|
288
|
-
.attr('transform', `translate(${size.left}, ${size.top})`)
|
289
|
-
})
|
290
|
-
|
291
|
-
// 監聽外層的element尺寸
|
292
|
-
const rootSize$: Observable<{ width: number; height: number }> = of({
|
293
|
-
width: options?.width ??
|
294
|
-
height: options?.height ??
|
295
|
-
}).pipe(
|
296
|
-
switchMap(size => {
|
297
|
-
return iif(
|
298
|
-
() => size.width === 'auto' || size.height === 'auto',
|
299
|
-
// 有 'auto' 的話就監聽element的尺寸
|
300
|
-
resizeObservable(element).pipe(
|
301
|
-
map((d) => {
|
302
|
-
return {
|
303
|
-
width: size.width === 'auto' ? d.width : size.width,
|
304
|
-
height: size.height === 'auto' ? d.height : size.height
|
305
|
-
}
|
306
|
-
})
|
307
|
-
),
|
308
|
-
of(size as { width: number; height: number })
|
309
|
-
)
|
310
|
-
}),
|
311
|
-
takeUntil(destroy$),
|
312
|
-
share()
|
313
|
-
)
|
314
|
-
const rootSizeFiltered$ = of().pipe(
|
315
|
-
mergeWith(
|
316
|
-
rootSize$.pipe(
|
317
|
-
debounceTime(250)
|
318
|
-
),
|
319
|
-
rootSize$.pipe(
|
320
|
-
throttleTime(250)
|
321
|
-
)
|
322
|
-
),
|
323
|
-
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),
|
324
|
-
share()
|
325
|
-
)
|
326
|
-
const rootSizeSubscription = rootSizeFiltered$.subscribe()
|
327
|
-
|
328
|
-
// layout
|
329
|
-
const layout$: Observable<Layout> = combineLatest({
|
330
|
-
rootSize: rootSizeFiltered$,
|
331
|
-
mergedPadding: mergedPadding$
|
332
|
-
}).pipe(
|
333
|
-
takeUntil(destroy$),
|
334
|
-
switchMap(async (d) => {
|
335
|
-
const rootWidth = d.rootSize.width > 0
|
336
|
-
? d.rootSize.width
|
337
|
-
:
|
338
|
-
const rootHeight = d.rootSize.height > 0
|
339
|
-
? d.rootSize.height
|
340
|
-
:
|
341
|
-
return {
|
342
|
-
width: rootWidth - d.mergedPadding.left - d.mergedPadding.right,
|
343
|
-
height: rootHeight - d.mergedPadding.top - d.mergedPadding.bottom,
|
344
|
-
top: d.mergedPadding.top,
|
345
|
-
right: d.mergedPadding.right,
|
346
|
-
bottom: d.mergedPadding.bottom,
|
347
|
-
left: d.mergedPadding.left,
|
348
|
-
rootWidth,
|
349
|
-
rootHeight
|
350
|
-
}
|
351
|
-
}),
|
352
|
-
shareReplay(1)
|
353
|
-
)
|
354
|
-
layout$.subscribe(d => {
|
355
|
-
svgSelection
|
356
|
-
.attr('width', d.rootWidth)
|
357
|
-
.attr('height', d.rootHeight)
|
358
|
-
})
|
359
|
-
|
360
|
-
// -- computedData --
|
361
|
-
const computedData$: Observable<ComputedDataTypeMap<T>> = combineLatest({
|
362
|
-
data: sharedData$,
|
363
|
-
dataFormatter: shareAndMergedDataFormatter$,
|
364
|
-
chartParams: shareAndMergedChartParams$,
|
365
|
-
// layout: iif(() => isAxesTypeMap[chartType] === true, layout$, of(undefined))
|
366
|
-
}).pipe(
|
367
|
-
takeUntil(destroy$),
|
368
|
-
switchMap(async d => d),
|
369
|
-
switchMap((d) => {
|
370
|
-
return of(d)
|
371
|
-
.pipe(
|
372
|
-
map(_d => {
|
373
|
-
try {
|
374
|
-
// 檢查 data$ 資料格式是否正確
|
375
|
-
const { status, columnName, expectToBe } = dataValidator(_d.data)
|
376
|
-
if (status === 'error') {
|
377
|
-
throw new Error(createValidatorErrorMessage({
|
378
|
-
columnName,
|
379
|
-
expectToBe,
|
380
|
-
from: 'Chart.data$'
|
381
|
-
}))
|
382
|
-
} else if (status === 'warning') {
|
383
|
-
console.warn(createValidatorWarningMessage({
|
384
|
-
columnName,
|
385
|
-
expectToBe,
|
386
|
-
from: 'Chart.data$'
|
387
|
-
}))
|
388
|
-
}
|
389
|
-
} catch (e) {
|
390
|
-
// throw new Error(e)
|
391
|
-
// 驗證失敗仍繼續執行,才不會把 Observable 資料流給中斷掉
|
392
|
-
console.error(createOrbChartsErrorMessage(e))
|
393
|
-
}
|
394
|
-
return computedDataFn({ data: _d.data, dataFormatter: _d.dataFormatter, chartParams: _d.chartParams })
|
395
|
-
}),
|
396
|
-
// catchError((e) => {
|
397
|
-
// console.error(createOrbChartsErrorMessage(e))
|
398
|
-
// return EMPTY
|
399
|
-
// })
|
400
|
-
)
|
401
|
-
}),
|
402
|
-
shareReplay(1)
|
403
|
-
)
|
404
|
-
|
405
|
-
// subscribe - computedData組合了所有的chart參數,所以訂閱computedData可以一次訂閱所有的資料流
|
406
|
-
computedData$.subscribe()
|
407
|
-
|
408
|
-
// -- plugins --
|
409
|
-
const pluginEntityMap: any = {} // 用於destroy
|
410
|
-
chartSubject.plugins$.subscribe(plugins => {
|
411
|
-
try {
|
412
|
-
// 檢查 plugins$ 資料格式是否正確
|
413
|
-
const { status, columnName, expectToBe } = pluginsValidator(chartType, plugins)
|
414
|
-
if (status === 'error') {
|
415
|
-
throw new Error(createValidatorErrorMessage({
|
416
|
-
columnName,
|
417
|
-
expectToBe,
|
418
|
-
from: 'Chart.plugins$'
|
419
|
-
}))
|
420
|
-
} else if (status === 'warning') {
|
421
|
-
console.warn(createValidatorWarningMessage({
|
422
|
-
columnName,
|
423
|
-
expectToBe,
|
424
|
-
from: 'Chart.plugins$'
|
425
|
-
}))
|
426
|
-
}
|
427
|
-
} catch (e) {
|
428
|
-
console.error(createOrbChartsErrorMessage(e))
|
429
|
-
return
|
430
|
-
// throw new Error(e)
|
431
|
-
}
|
432
|
-
|
433
|
-
selectionPlugins
|
434
|
-
.selectAll<SVGGElement, PluginEntity<T, any, any>>('g.orbcharts__plugin')
|
435
|
-
.data(plugins, d => d.name as string)
|
436
|
-
.join(
|
437
|
-
enter => {
|
438
|
-
return enter
|
439
|
-
.append('g')
|
440
|
-
.attr('class', plugin => {
|
441
|
-
return `orbcharts__plugin orbcharts__${plugin.name}`
|
442
|
-
})
|
443
|
-
.each((plugin, i, n) => {
|
444
|
-
const _pluginObserverBase = {
|
445
|
-
fullParams$: new Observable(),
|
446
|
-
fullChartParams$: shareAndMergedChartParams$,
|
447
|
-
fullDataFormatter$: shareAndMergedDataFormatter$,
|
448
|
-
computedData$,
|
449
|
-
layout$
|
450
|
-
}
|
451
|
-
const pluginObserver: ContextObserverTypeMap<T, typeof plugin.defaultParams> = contextObserverCallback({
|
452
|
-
observer: _pluginObserverBase,
|
453
|
-
subject: chartSubject
|
454
|
-
})
|
455
|
-
|
456
|
-
// -- createPlugin(plugin) --
|
457
|
-
const pluginSelection = d3.select(n[i])
|
458
|
-
const pluginContext: PluginContext<T, typeof plugin.name, typeof plugin.defaultParams> = {
|
459
|
-
selection: pluginSelection,
|
460
|
-
rootSelection: svgSelection,
|
461
|
-
name: plugin.name,
|
462
|
-
chartType,
|
463
|
-
subject: chartSubject,
|
464
|
-
observer: pluginObserver
|
465
|
-
}
|
466
|
-
|
467
|
-
plugin.setPresetParams(mergedPresetWithDefault.allPluginParams[plugin.name] ?? {})
|
468
|
-
// 傳入context
|
469
|
-
plugin.setContext(pluginContext)
|
470
|
-
|
471
|
-
// 紀錄起來
|
472
|
-
pluginEntityMap[pluginContext.name as string] = plugin
|
473
|
-
|
474
|
-
// init plugin
|
475
|
-
plugin.init()
|
476
|
-
|
477
|
-
})
|
478
|
-
},
|
479
|
-
update => update,
|
480
|
-
exit => {
|
481
|
-
return exit
|
482
|
-
.each((plugin: PluginEntity<T, unknown, unknown>, i, n) => {
|
483
|
-
if (pluginEntityMap[plugin.name as string]) {
|
484
|
-
pluginEntityMap[plugin.name as string].destroy()
|
485
|
-
pluginEntityMap[plugin.name as string] = undefined
|
486
|
-
}
|
487
|
-
})
|
488
|
-
.remove()
|
489
|
-
}
|
490
|
-
)
|
491
|
-
.sort((a, b) => a.layerIndex - b.layerIndex)
|
492
|
-
|
493
|
-
})
|
494
|
-
|
495
|
-
return {
|
496
|
-
...chartSubject,
|
497
|
-
selection: svgSelection,
|
498
|
-
destroy () {
|
499
|
-
d3.select(element).selectAll('svg').remove()
|
500
|
-
destroy$.next(undefined)
|
501
|
-
rootSizeSubscription.unsubscribe()
|
502
|
-
}
|
503
|
-
}
|
504
|
-
}
|
505
|
-
}
|
1
|
+
import * as d3 from 'd3'
|
2
|
+
import {
|
3
|
+
combineLatest,
|
4
|
+
iif,
|
5
|
+
of,
|
6
|
+
EMPTY,
|
7
|
+
Subject,
|
8
|
+
BehaviorSubject,
|
9
|
+
Observable,
|
10
|
+
first,
|
11
|
+
takeUntil,
|
12
|
+
catchError,
|
13
|
+
throwError } from 'rxjs'
|
14
|
+
import {
|
15
|
+
map,
|
16
|
+
mergeWith,
|
17
|
+
concatMap,
|
18
|
+
switchMap,
|
19
|
+
switchAll,
|
20
|
+
throttleTime,
|
21
|
+
debounceTime,
|
22
|
+
distinctUntilChanged,
|
23
|
+
share,
|
24
|
+
shareReplay,
|
25
|
+
filter,
|
26
|
+
take,
|
27
|
+
startWith,
|
28
|
+
scan,
|
29
|
+
} from 'rxjs/operators'
|
30
|
+
import type {
|
31
|
+
CreateBaseChart,
|
32
|
+
CreateChart,
|
33
|
+
ComputedDataFn,
|
34
|
+
ChartEntity,
|
35
|
+
ChartType,
|
36
|
+
ChartParams,
|
37
|
+
ChartParamsPartial,
|
38
|
+
ContextSubject,
|
39
|
+
ComputedDataTypeMap,
|
40
|
+
ContextObserverCallback,
|
41
|
+
ChartOptionsPartial,
|
42
|
+
DataTypeMap,
|
43
|
+
DataFormatterTypeMap,
|
44
|
+
DataFormatterPartialTypeMap,
|
45
|
+
DataFormatterBase,
|
46
|
+
DataFormatterContext,
|
47
|
+
DataFormatterValidator,
|
48
|
+
DataValidator,
|
49
|
+
Layout,
|
50
|
+
PluginEntity,
|
51
|
+
PluginContext,
|
52
|
+
Preset,
|
53
|
+
PresetPartial,
|
54
|
+
ContextObserverTypeMap,
|
55
|
+
ValidatorResult,
|
56
|
+
} from '../../lib/core-types'
|
57
|
+
import { mergeOptionsWithDefault, resizeObservable } from '../utils'
|
58
|
+
import { createValidatorErrorMessage, createValidatorWarningMessage, createOrbChartsErrorMessage } from '../utils/errorMessage'
|
59
|
+
import { chartOptionsValidator } from './validators/chartOptionsValidator'
|
60
|
+
import { elementValidator } from './validators/elementValidator'
|
61
|
+
import { chartParamsValidator } from './validators/chartParamsValidator'
|
62
|
+
import { pluginsValidator } from './validators/pluginsValidator'
|
63
|
+
import {
|
64
|
+
DEFAULT_CHART_OPTIONS,
|
65
|
+
DEFAULT_PADDING,
|
66
|
+
DEFAULT_CHART_PARAMS,
|
67
|
+
DEFAULT_CHART_WIDTH,
|
68
|
+
DEFAULT_CHART_HEIGHT } from '../defaults'
|
69
|
+
|
70
|
+
// 判斷dataFormatter是否需要size參數
|
71
|
+
// const isAxesTypeMap: {[key in ChartType]: Boolean} = {
|
72
|
+
// series: false,
|
73
|
+
// grid: true,
|
74
|
+
// multiGrid: true,
|
75
|
+
// multiValue: true,
|
76
|
+
// tree: false,
|
77
|
+
// relationship: false
|
78
|
+
// }
|
79
|
+
|
80
|
+
|
81
|
+
function mergeDataFormatter <T>(dataFormatter: any, defaultDataFormatter: T, chartType: ChartType): T {
|
82
|
+
const mergedData = mergeOptionsWithDefault(dataFormatter, defaultDataFormatter)
|
83
|
+
|
84
|
+
if (chartType === 'multiGrid' && (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList != null) {
|
85
|
+
// multiGrid欄位為陣列,需要各別來merge預設值
|
86
|
+
(mergedData as DataFormatterTypeMap<'multiGrid'>).gridList = (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList.map((d, i) => {
|
87
|
+
const defaultGrid = (defaultDataFormatter as DataFormatterTypeMap<'multiGrid'>).gridList[i] || (defaultDataFormatter as DataFormatterTypeMap<'multiGrid'>).gridList[0]
|
88
|
+
return mergeOptionsWithDefault(d, defaultGrid)
|
89
|
+
})
|
90
|
+
}
|
91
|
+
return mergedData
|
92
|
+
}
|
93
|
+
|
94
|
+
export const createBaseChart: CreateBaseChart = <T extends ChartType>({
|
95
|
+
defaultDataFormatter,
|
96
|
+
dataFormatterValidator,
|
97
|
+
computedDataFn,
|
98
|
+
dataValidator,
|
99
|
+
contextObserverCallback
|
100
|
+
}: {
|
101
|
+
defaultDataFormatter: DataFormatterTypeMap<T>
|
102
|
+
dataFormatterValidator: DataFormatterValidator<T>
|
103
|
+
computedDataFn: ComputedDataFn<T>
|
104
|
+
dataValidator: DataValidator<T>
|
105
|
+
contextObserverCallback: ContextObserverCallback<T>
|
106
|
+
}): CreateChart<T> => {
|
107
|
+
const destroy$ = new Subject()
|
108
|
+
|
109
|
+
const chartType: ChartType = (defaultDataFormatter as unknown as DataFormatterBase<any>).type
|
110
|
+
|
111
|
+
// 建立chart實例
|
112
|
+
return function createChart (element: HTMLElement | Element, options?: ChartOptionsPartial<T>): ChartEntity<T> {
|
113
|
+
try {
|
114
|
+
const { status, columnName, expectToBe } = chartOptionsValidator(options)
|
115
|
+
if (status === 'error') {
|
116
|
+
throw new Error(createValidatorErrorMessage({
|
117
|
+
columnName,
|
118
|
+
expectToBe,
|
119
|
+
from: 'Chart.constructor'
|
120
|
+
}))
|
121
|
+
} else if (status === 'warning') {
|
122
|
+
console.warn(createValidatorWarningMessage({
|
123
|
+
columnName,
|
124
|
+
expectToBe,
|
125
|
+
from: 'Chart.constructor'
|
126
|
+
}))
|
127
|
+
} else {
|
128
|
+
const { status, columnName, expectToBe } = elementValidator(element)
|
129
|
+
if (status === 'error') {
|
130
|
+
throw new Error(createValidatorErrorMessage({
|
131
|
+
columnName,
|
132
|
+
expectToBe,
|
133
|
+
from: 'Chart.constructor'
|
134
|
+
}))
|
135
|
+
} else if (status === 'warning') {
|
136
|
+
console.warn(createValidatorWarningMessage({
|
137
|
+
columnName,
|
138
|
+
expectToBe,
|
139
|
+
from: 'Chart.constructor'
|
140
|
+
}))
|
141
|
+
}
|
142
|
+
}
|
143
|
+
} catch (e) {
|
144
|
+
throw new Error(e)
|
145
|
+
}
|
146
|
+
|
147
|
+
|
148
|
+
// -- selections --
|
149
|
+
// svg selection
|
150
|
+
d3.select(element).selectAll('svg').remove()
|
151
|
+
const svgSelection = d3.select(element).append('svg')
|
152
|
+
svgSelection
|
153
|
+
.attr('xmlns:xlink', 'http://www.w3.org/1999/xlink')
|
154
|
+
.attr('xmls', 'http://www.w3.org/2000/svg')
|
155
|
+
.attr('version', '1.1')
|
156
|
+
.style('position', 'absolute')
|
157
|
+
// .style('width', '100%')
|
158
|
+
// .style('height', '100%')
|
159
|
+
.classed('orbcharts__root', true)
|
160
|
+
// 傳入操作的 selection
|
161
|
+
const selectionLayout = svgSelection.append('g')
|
162
|
+
selectionLayout.classed('orbcharts__layout', true)
|
163
|
+
const selectionPlugins = selectionLayout.append('g')
|
164
|
+
selectionPlugins.classed('orbcharts__plugins', true)
|
165
|
+
|
166
|
+
// chartSubject
|
167
|
+
const chartSubject: ContextSubject<T> = {
|
168
|
+
data$: new Subject(),
|
169
|
+
dataFormatter$: new Subject(),
|
170
|
+
plugins$: new Subject(),
|
171
|
+
chartParams$: new Subject(),
|
172
|
+
event$: new Subject()
|
173
|
+
}
|
174
|
+
|
175
|
+
// options
|
176
|
+
const mergedPresetWithDefault: Preset<T, any> = ((options) => {
|
177
|
+
const _options = options ? options : DEFAULT_CHART_OPTIONS as ChartOptionsPartial<T>
|
178
|
+
const preset = _options.preset ? _options.preset : {} as PresetPartial<T, any>
|
179
|
+
|
180
|
+
return {
|
181
|
+
name: preset.name ?? '',
|
182
|
+
description: preset.description ?? '',
|
183
|
+
chartParams: preset.chartParams
|
184
|
+
? mergeOptionsWithDefault(preset.chartParams, DEFAULT_CHART_PARAMS)
|
185
|
+
: DEFAULT_CHART_PARAMS,
|
186
|
+
dataFormatter: preset.dataFormatter
|
187
|
+
// ? mergeOptionsWithDefault(preset.dataFormatter, defaultDataFormatter)
|
188
|
+
? mergeDataFormatter(preset.dataFormatter, defaultDataFormatter, chartType)
|
189
|
+
: defaultDataFormatter,
|
190
|
+
allPluginParams: preset.allPluginParams
|
191
|
+
? preset.allPluginParams
|
192
|
+
: {}
|
193
|
+
}
|
194
|
+
})(options)
|
195
|
+
|
196
|
+
const sharedData$ = chartSubject.data$.pipe(shareReplay(1))
|
197
|
+
const shareAndMergedDataFormatter$ = chartSubject.dataFormatter$
|
198
|
+
.pipe(
|
199
|
+
takeUntil(destroy$),
|
200
|
+
startWith({} as DataFormatterPartialTypeMap<T>),
|
201
|
+
map((dataFormatter) => {
|
202
|
+
try {
|
203
|
+
// 檢查 dataFormatter$ 資料格式是否正確
|
204
|
+
const { status, columnName, expectToBe } = dataFormatterValidator(dataFormatter)
|
205
|
+
if (status === 'error') {
|
206
|
+
throw new Error(createValidatorErrorMessage({
|
207
|
+
columnName,
|
208
|
+
expectToBe,
|
209
|
+
from: 'Chart.dataFormatter$'
|
210
|
+
}))
|
211
|
+
} else if (status === 'warning') {
|
212
|
+
console.warn(createValidatorWarningMessage({
|
213
|
+
columnName,
|
214
|
+
expectToBe,
|
215
|
+
from: 'Chart.dataFormatter$'
|
216
|
+
}))
|
217
|
+
}
|
218
|
+
} catch (e) {
|
219
|
+
// throw new Error(e)
|
220
|
+
// 驗證失敗仍繼續執行,才不會把 Observable 資料流給中斷掉
|
221
|
+
console.error(createOrbChartsErrorMessage(e))
|
222
|
+
}
|
223
|
+
return mergeDataFormatter(dataFormatter, mergedPresetWithDefault.dataFormatter, chartType)
|
224
|
+
}),
|
225
|
+
// catchError((e) => {
|
226
|
+
// console.error(createOrbChartsErrorMessage(e))
|
227
|
+
// return EMPTY
|
228
|
+
// }),
|
229
|
+
shareReplay(1)
|
230
|
+
)
|
231
|
+
const shareAndMergedChartParams$ = chartSubject.chartParams$
|
232
|
+
.pipe(
|
233
|
+
takeUntil(destroy$),
|
234
|
+
startWith({}),
|
235
|
+
map((d) => {
|
236
|
+
try {
|
237
|
+
// 檢查 chartParams$ 資料格式是否正確
|
238
|
+
const { status, columnName, expectToBe } = chartParamsValidator(chartType, d)
|
239
|
+
if (status === 'error') {
|
240
|
+
throw new Error(createValidatorErrorMessage({
|
241
|
+
columnName,
|
242
|
+
expectToBe,
|
243
|
+
from: 'Chart.chartParams$'
|
244
|
+
}))
|
245
|
+
} else if (status === 'warning') {
|
246
|
+
console.warn(createValidatorWarningMessage({
|
247
|
+
columnName,
|
248
|
+
expectToBe,
|
249
|
+
from: 'Chart.chartParams$'
|
250
|
+
}))
|
251
|
+
}
|
252
|
+
} catch (e) {
|
253
|
+
// throw new Error(e)
|
254
|
+
// 驗證失敗仍繼續執行,才不會把 Observable 資料流給中斷掉
|
255
|
+
console.error(createOrbChartsErrorMessage(e))
|
256
|
+
}
|
257
|
+
return mergeOptionsWithDefault(d, mergedPresetWithDefault.chartParams)
|
258
|
+
}),
|
259
|
+
// catchError((e) => {
|
260
|
+
// console.error(createOrbChartsErrorMessage(e))
|
261
|
+
// return EMPTY
|
262
|
+
// }),
|
263
|
+
shareReplay(1)
|
264
|
+
)
|
265
|
+
|
266
|
+
// -- size --
|
267
|
+
// padding
|
268
|
+
const mergedPadding$ = shareAndMergedChartParams$
|
269
|
+
.pipe(
|
270
|
+
takeUntil(destroy$),
|
271
|
+
startWith({}),
|
272
|
+
map((d: any) => {
|
273
|
+
return mergeOptionsWithDefault(d.padding ?? {}, DEFAULT_PADDING)
|
274
|
+
})
|
275
|
+
)
|
276
|
+
mergedPadding$
|
277
|
+
.pipe(
|
278
|
+
takeUntil(destroy$),
|
279
|
+
first()
|
280
|
+
)
|
281
|
+
.subscribe(d => {
|
282
|
+
selectionLayout
|
283
|
+
.attr('transform', `translate(${d.left}, ${d.top})`)
|
284
|
+
})
|
285
|
+
mergedPadding$.subscribe(size => {
|
286
|
+
selectionLayout
|
287
|
+
.transition()
|
288
|
+
.attr('transform', `translate(${size.left}, ${size.top})`)
|
289
|
+
})
|
290
|
+
|
291
|
+
// 監聽外層的element尺寸
|
292
|
+
const rootSize$: Observable<{ width: number; height: number }> = of({
|
293
|
+
width: options?.width ?? DEFAULT_CHART_OPTIONS.width,
|
294
|
+
height: options?.height ?? DEFAULT_CHART_OPTIONS.height
|
295
|
+
}).pipe(
|
296
|
+
switchMap(size => {
|
297
|
+
return iif(
|
298
|
+
() => size.width === 'auto' || size.height === 'auto',
|
299
|
+
// 有 'auto' 的話就監聽element的尺寸
|
300
|
+
resizeObservable(element).pipe(
|
301
|
+
map((d) => {
|
302
|
+
return {
|
303
|
+
width: size.width === 'auto' ? d.width : size.width,
|
304
|
+
height: size.height === 'auto' ? d.height : size.height
|
305
|
+
}
|
306
|
+
})
|
307
|
+
),
|
308
|
+
of(size as { width: number; height: number })
|
309
|
+
)
|
310
|
+
}),
|
311
|
+
takeUntil(destroy$),
|
312
|
+
share()
|
313
|
+
)
|
314
|
+
const rootSizeFiltered$ = of().pipe(
|
315
|
+
mergeWith(
|
316
|
+
rootSize$.pipe(
|
317
|
+
debounceTime(250)
|
318
|
+
),
|
319
|
+
rootSize$.pipe(
|
320
|
+
throttleTime(250)
|
321
|
+
)
|
322
|
+
),
|
323
|
+
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),
|
324
|
+
share()
|
325
|
+
)
|
326
|
+
const rootSizeSubscription = rootSizeFiltered$.subscribe()
|
327
|
+
|
328
|
+
// layout
|
329
|
+
const layout$: Observable<Layout> = combineLatest({
|
330
|
+
rootSize: rootSizeFiltered$,
|
331
|
+
mergedPadding: mergedPadding$
|
332
|
+
}).pipe(
|
333
|
+
takeUntil(destroy$),
|
334
|
+
switchMap(async (d) => {
|
335
|
+
const rootWidth = d.rootSize.width > 0
|
336
|
+
? d.rootSize.width
|
337
|
+
: DEFAULT_CHART_WIDTH
|
338
|
+
const rootHeight = d.rootSize.height > 0
|
339
|
+
? d.rootSize.height
|
340
|
+
: DEFAULT_CHART_HEIGHT
|
341
|
+
return {
|
342
|
+
width: rootWidth - d.mergedPadding.left - d.mergedPadding.right,
|
343
|
+
height: rootHeight - d.mergedPadding.top - d.mergedPadding.bottom,
|
344
|
+
top: d.mergedPadding.top,
|
345
|
+
right: d.mergedPadding.right,
|
346
|
+
bottom: d.mergedPadding.bottom,
|
347
|
+
left: d.mergedPadding.left,
|
348
|
+
rootWidth,
|
349
|
+
rootHeight
|
350
|
+
}
|
351
|
+
}),
|
352
|
+
shareReplay(1)
|
353
|
+
)
|
354
|
+
layout$.subscribe(d => {
|
355
|
+
svgSelection
|
356
|
+
.attr('width', d.rootWidth)
|
357
|
+
.attr('height', d.rootHeight)
|
358
|
+
})
|
359
|
+
|
360
|
+
// -- computedData --
|
361
|
+
const computedData$: Observable<ComputedDataTypeMap<T>> = combineLatest({
|
362
|
+
data: sharedData$,
|
363
|
+
dataFormatter: shareAndMergedDataFormatter$,
|
364
|
+
chartParams: shareAndMergedChartParams$,
|
365
|
+
// layout: iif(() => isAxesTypeMap[chartType] === true, layout$, of(undefined))
|
366
|
+
}).pipe(
|
367
|
+
takeUntil(destroy$),
|
368
|
+
switchMap(async d => d),
|
369
|
+
switchMap((d) => {
|
370
|
+
return of(d)
|
371
|
+
.pipe(
|
372
|
+
map(_d => {
|
373
|
+
try {
|
374
|
+
// 檢查 data$ 資料格式是否正確
|
375
|
+
const { status, columnName, expectToBe } = dataValidator(_d.data)
|
376
|
+
if (status === 'error') {
|
377
|
+
throw new Error(createValidatorErrorMessage({
|
378
|
+
columnName,
|
379
|
+
expectToBe,
|
380
|
+
from: 'Chart.data$'
|
381
|
+
}))
|
382
|
+
} else if (status === 'warning') {
|
383
|
+
console.warn(createValidatorWarningMessage({
|
384
|
+
columnName,
|
385
|
+
expectToBe,
|
386
|
+
from: 'Chart.data$'
|
387
|
+
}))
|
388
|
+
}
|
389
|
+
} catch (e) {
|
390
|
+
// throw new Error(e)
|
391
|
+
// 驗證失敗仍繼續執行,才不會把 Observable 資料流給中斷掉
|
392
|
+
console.error(createOrbChartsErrorMessage(e))
|
393
|
+
}
|
394
|
+
return computedDataFn({ data: _d.data, dataFormatter: _d.dataFormatter, chartParams: _d.chartParams })
|
395
|
+
}),
|
396
|
+
// catchError((e) => {
|
397
|
+
// console.error(createOrbChartsErrorMessage(e))
|
398
|
+
// return EMPTY
|
399
|
+
// })
|
400
|
+
)
|
401
|
+
}),
|
402
|
+
shareReplay(1)
|
403
|
+
)
|
404
|
+
|
405
|
+
// subscribe - computedData組合了所有的chart參數,所以訂閱computedData可以一次訂閱所有的資料流
|
406
|
+
computedData$.subscribe()
|
407
|
+
|
408
|
+
// -- plugins --
|
409
|
+
const pluginEntityMap: any = {} // 用於destroy
|
410
|
+
chartSubject.plugins$.subscribe(plugins => {
|
411
|
+
try {
|
412
|
+
// 檢查 plugins$ 資料格式是否正確
|
413
|
+
const { status, columnName, expectToBe } = pluginsValidator(chartType, plugins)
|
414
|
+
if (status === 'error') {
|
415
|
+
throw new Error(createValidatorErrorMessage({
|
416
|
+
columnName,
|
417
|
+
expectToBe,
|
418
|
+
from: 'Chart.plugins$'
|
419
|
+
}))
|
420
|
+
} else if (status === 'warning') {
|
421
|
+
console.warn(createValidatorWarningMessage({
|
422
|
+
columnName,
|
423
|
+
expectToBe,
|
424
|
+
from: 'Chart.plugins$'
|
425
|
+
}))
|
426
|
+
}
|
427
|
+
} catch (e) {
|
428
|
+
console.error(createOrbChartsErrorMessage(e))
|
429
|
+
return
|
430
|
+
// throw new Error(e)
|
431
|
+
}
|
432
|
+
|
433
|
+
selectionPlugins
|
434
|
+
.selectAll<SVGGElement, PluginEntity<T, any, any>>('g.orbcharts__plugin')
|
435
|
+
.data(plugins, d => d.name as string)
|
436
|
+
.join(
|
437
|
+
enter => {
|
438
|
+
return enter
|
439
|
+
.append('g')
|
440
|
+
.attr('class', plugin => {
|
441
|
+
return `orbcharts__plugin orbcharts__${plugin.name}`
|
442
|
+
})
|
443
|
+
.each((plugin, i, n) => {
|
444
|
+
const _pluginObserverBase = {
|
445
|
+
fullParams$: new Observable(),
|
446
|
+
fullChartParams$: shareAndMergedChartParams$,
|
447
|
+
fullDataFormatter$: shareAndMergedDataFormatter$,
|
448
|
+
computedData$,
|
449
|
+
layout$
|
450
|
+
}
|
451
|
+
const pluginObserver: ContextObserverTypeMap<T, typeof plugin.defaultParams> = contextObserverCallback({
|
452
|
+
observer: _pluginObserverBase,
|
453
|
+
subject: chartSubject
|
454
|
+
})
|
455
|
+
|
456
|
+
// -- createPlugin(plugin) --
|
457
|
+
const pluginSelection = d3.select(n[i])
|
458
|
+
const pluginContext: PluginContext<T, typeof plugin.name, typeof plugin.defaultParams> = {
|
459
|
+
selection: pluginSelection,
|
460
|
+
rootSelection: svgSelection,
|
461
|
+
name: plugin.name,
|
462
|
+
chartType,
|
463
|
+
subject: chartSubject,
|
464
|
+
observer: pluginObserver
|
465
|
+
}
|
466
|
+
|
467
|
+
plugin.setPresetParams(mergedPresetWithDefault.allPluginParams[plugin.name] ?? {})
|
468
|
+
// 傳入context
|
469
|
+
plugin.setContext(pluginContext)
|
470
|
+
|
471
|
+
// 紀錄起來
|
472
|
+
pluginEntityMap[pluginContext.name as string] = plugin
|
473
|
+
|
474
|
+
// init plugin
|
475
|
+
plugin.init()
|
476
|
+
|
477
|
+
})
|
478
|
+
},
|
479
|
+
update => update,
|
480
|
+
exit => {
|
481
|
+
return exit
|
482
|
+
.each((plugin: PluginEntity<T, unknown, unknown>, i, n) => {
|
483
|
+
if (pluginEntityMap[plugin.name as string]) {
|
484
|
+
pluginEntityMap[plugin.name as string].destroy()
|
485
|
+
pluginEntityMap[plugin.name as string] = undefined
|
486
|
+
}
|
487
|
+
})
|
488
|
+
.remove()
|
489
|
+
}
|
490
|
+
)
|
491
|
+
.sort((a, b) => a.layerIndex - b.layerIndex)
|
492
|
+
|
493
|
+
})
|
494
|
+
|
495
|
+
return {
|
496
|
+
...chartSubject,
|
497
|
+
selection: svgSelection,
|
498
|
+
destroy () {
|
499
|
+
d3.select(element).selectAll('svg').remove()
|
500
|
+
destroy$.next(undefined)
|
501
|
+
rootSizeSubscription.unsubscribe()
|
502
|
+
}
|
503
|
+
}
|
504
|
+
}
|
505
|
+
}
|