@orbcharts/core 3.0.0-alpha.52 → 3.0.0-alpha.53

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-core.es.js +709 -703
  3. package/dist/orbcharts-core.umd.js +2 -2
  4. package/dist/src/types/ContextObserverMultiGrid.d.ts +2 -1
  5. package/dist/vite.config.d.ts +1 -1
  6. package/package.json +41 -41
  7. package/src/AbstractChart.ts +48 -48
  8. package/src/GridChart.ts +20 -20
  9. package/src/MultiGridChart.ts +20 -20
  10. package/src/MultiValueChart.ts +20 -20
  11. package/src/RelationshipChart.ts +20 -20
  12. package/src/SeriesChart.ts +20 -20
  13. package/src/TreeChart.ts +20 -20
  14. package/src/base/createBaseChart.ts +369 -369
  15. package/src/base/createBasePlugin.ts +95 -95
  16. package/src/defaults.ts +226 -226
  17. package/src/defineGridPlugin.ts +3 -3
  18. package/src/defineMultiGridPlugin.ts +3 -3
  19. package/src/defineMultiValuePlugin.ts +3 -3
  20. package/src/defineNoneDataPlugin.ts +4 -4
  21. package/src/defineRelationshipPlugin.ts +3 -3
  22. package/src/defineSeriesPlugin.ts +3 -3
  23. package/src/defineTreePlugin.ts +3 -3
  24. package/src/grid/computeGridData.ts +134 -134
  25. package/src/grid/createGridContextObserver.ts +155 -155
  26. package/src/grid/gridObservables.ts +607 -607
  27. package/src/index.ts +21 -21
  28. package/src/multiGrid/computeMultiGridData.ts +130 -130
  29. package/src/multiGrid/createMultiGridContextObserver.ts +41 -40
  30. package/src/multiGrid/multiGridObservables.ts +365 -364
  31. package/src/multiValue/computeMultiValueData.ts +143 -143
  32. package/src/multiValue/createMultiValueContextObserver.ts +12 -12
  33. package/src/relationship/computeRelationshipData.ts +118 -118
  34. package/src/relationship/createRelationshipContextObserver.ts +12 -12
  35. package/src/series/computeSeriesData.ts +90 -90
  36. package/src/series/createSeriesContextObserver.ts +93 -93
  37. package/src/series/seriesObservables.ts +175 -175
  38. package/src/tree/computeTreeData.ts +132 -132
  39. package/src/tree/createTreeContextObserver.ts +61 -61
  40. package/src/tree/treeObservables.ts +94 -94
  41. package/src/types/Chart.ts +50 -50
  42. package/src/types/ChartParams.ts +51 -51
  43. package/src/types/ComputedData.ts +83 -83
  44. package/src/types/ComputedDataGrid.ts +13 -13
  45. package/src/types/ComputedDataMultiGrid.ts +2 -2
  46. package/src/types/ComputedDataMultiValue.ts +9 -9
  47. package/src/types/ComputedDataRelationship.ts +19 -19
  48. package/src/types/ComputedDataSeries.ts +7 -7
  49. package/src/types/ComputedDataTree.ts +19 -19
  50. package/src/types/ContextObserver.ts +38 -38
  51. package/src/types/ContextObserverGrid.ts +42 -42
  52. package/src/types/ContextObserverMultiGrid.ts +16 -15
  53. package/src/types/ContextObserverMultiValue.ts +4 -4
  54. package/src/types/ContextObserverRelationship.ts +4 -4
  55. package/src/types/ContextObserverSeries.ts +29 -29
  56. package/src/types/ContextObserverTree.ts +11 -11
  57. package/src/types/ContextSubject.ts +18 -18
  58. package/src/types/Data.ts +45 -45
  59. package/src/types/DataFormatter.ts +74 -74
  60. package/src/types/DataFormatterGrid.ts +67 -67
  61. package/src/types/DataFormatterMultiGrid.ts +44 -44
  62. package/src/types/DataFormatterMultiValue.ts +23 -23
  63. package/src/types/DataFormatterRelationship.ts +25 -25
  64. package/src/types/DataFormatterSeries.ts +20 -20
  65. package/src/types/DataFormatterTree.ts +12 -12
  66. package/src/types/DataGrid.ts +11 -11
  67. package/src/types/DataMultiGrid.ts +6 -6
  68. package/src/types/DataMultiValue.ts +12 -12
  69. package/src/types/DataRelationship.ts +27 -27
  70. package/src/types/DataSeries.ts +11 -11
  71. package/src/types/DataTree.ts +20 -20
  72. package/src/types/Event.ts +153 -153
  73. package/src/types/Layout.ts +11 -11
  74. package/src/types/Padding.ts +5 -5
  75. package/src/types/Plugin.ts +60 -60
  76. package/src/types/TransformData.ts +7 -7
  77. package/src/types/index.ts +37 -37
  78. package/src/utils/commonUtils.ts +50 -50
  79. package/src/utils/d3Utils.ts +89 -89
  80. package/src/utils/index.ts +4 -4
  81. package/src/utils/observables.ts +201 -201
  82. package/src/utils/orbchartsUtils.ts +349 -349
  83. package/tsconfig.json +13 -13
  84. package/vite.config.js +22 -44
@@ -1,369 +1,369 @@
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
- ContextSubject,
38
- ComputedDataTypeMap,
39
- ContextObserverFn,
40
- ChartOptionsPartial,
41
- DataTypeMap,
42
- DataFormatterTypeMap,
43
- DataFormatterPartialTypeMap,
44
- DataFormatterBase,
45
- DataFormatterContext,
46
- Layout,
47
- PluginEntity,
48
- PluginContext,
49
- Preset,
50
- PresetPartial,
51
- ContextObserverTypeMap } from '../types'
52
- // import type { EventTypeMap } from './types/Event'
53
- import { mergeOptionsWithDefault } from '../utils'
54
- import {
55
- CHART_OPTIONS_DEFAULT,
56
- PADDING_DEFAULT,
57
- CHART_PARAMS_DEFAULT,
58
- CHART_WIDTH_DEFAULT,
59
- CHART_HEIGHT_DEFAULT } from '../defaults'
60
-
61
- // 判斷dataFormatter是否需要size參數
62
- // const isAxesTypeMap: {[key in ChartType]: Boolean} = {
63
- // series: false,
64
- // grid: true,
65
- // multiGrid: true,
66
- // multiValue: true,
67
- // tree: false,
68
- // relationship: false
69
- // }
70
-
71
- function resizeObservable(elem: HTMLElement | Element): Observable<DOMRectReadOnly> {
72
- return new Observable(subscriber => {
73
- const ro = new ResizeObserver(entries => {
74
- const entry = entries[0]
75
- if (entry && entry.contentRect) {
76
- subscriber.next(entry.contentRect)
77
- }
78
- })
79
-
80
- ro.observe(elem)
81
- return function unsubscribe() {
82
- ro.unobserve(elem)
83
- }
84
- })
85
- }
86
-
87
- function mergeDataFormatter <T>(dataFormatter: any, defaultDataFormatter: T, chartType: ChartType): T {
88
- const mergedData = mergeOptionsWithDefault(dataFormatter, defaultDataFormatter)
89
-
90
- if (chartType === 'multiGrid' && (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList != null) {
91
- // multiGrid欄位為陣列,需要各別來merge預設值
92
- (mergedData as DataFormatterTypeMap<'multiGrid'>).gridList = (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList.map((d, i) => {
93
- const defaultGrid = (defaultDataFormatter as DataFormatterTypeMap<'multiGrid'>).gridList[i] || (defaultDataFormatter as DataFormatterTypeMap<'multiGrid'>).gridList[0]
94
- return mergeOptionsWithDefault(d, defaultGrid)
95
- })
96
- }
97
- return mergedData
98
- }
99
-
100
- export const createBaseChart: CreateBaseChart = <T extends ChartType>({ defaultDataFormatter, computedDataFn, contextObserverFn }: {
101
- defaultDataFormatter: DataFormatterTypeMap<T>
102
- computedDataFn: ComputedDataFn<T>
103
- contextObserverFn: ContextObserverFn<T>
104
- }): CreateChart<T> => {
105
- const destroy$ = new Subject()
106
-
107
- const chartType: ChartType = (defaultDataFormatter as unknown as DataFormatterBase<any>).type
108
-
109
- // 建立chart實例
110
- return function createChart (element: HTMLElement | Element, options?: ChartOptionsPartial<T>): ChartEntity<T> {
111
-
112
- // -- selections --
113
- // svg selection
114
- d3.select(element).selectAll('svg').remove()
115
- const svgSelection = d3.select(element).append('svg')
116
- svgSelection
117
- .attr('xmlns:xlink', 'http://www.w3.org/1999/xlink')
118
- .attr('xmls', 'http://www.w3.org/2000/svg')
119
- .attr('version', '1.1')
120
- .style('position', 'absolute')
121
- // .style('width', '100%')
122
- // .style('height', '100%')
123
- .classed('orbcharts__root', true)
124
- // 傳入操作的 selection
125
- const selectionLayout = svgSelection.append('g')
126
- selectionLayout.classed('orbcharts__layout', true)
127
- const selectionPlugins = selectionLayout.append('g')
128
- selectionPlugins.classed('orbcharts__plugins', true)
129
-
130
- // chartSubject
131
- const chartSubject: ContextSubject<T> = {
132
- data$: new Subject(),
133
- dataFormatter$: new Subject(),
134
- plugins$: new Subject(),
135
- chartParams$: new Subject(),
136
- event$: new Subject()
137
- }
138
-
139
- // options
140
- const mergedPresetWithDefault: Preset<T, any> = ((options) => {
141
- const _options = options ? options : CHART_OPTIONS_DEFAULT as ChartOptionsPartial<T>
142
- const preset = _options.preset ? _options.preset : {} as PresetPartial<T, any>
143
-
144
- return {
145
- name: preset.name ?? '',
146
- description: preset.description ?? '',
147
- chartParams: preset.chartParams
148
- ? mergeOptionsWithDefault(preset.chartParams, CHART_PARAMS_DEFAULT)
149
- : CHART_PARAMS_DEFAULT,
150
- dataFormatter: preset.dataFormatter
151
- // ? mergeOptionsWithDefault(preset.dataFormatter, defaultDataFormatter)
152
- ? mergeDataFormatter(preset.dataFormatter, defaultDataFormatter, chartType)
153
- : defaultDataFormatter,
154
- allPluginParams: preset.allPluginParams
155
- ? preset.allPluginParams
156
- : {}
157
- }
158
- })(options)
159
-
160
- const sharedData$ = chartSubject.data$.pipe(shareReplay(1))
161
- const shareAndMergedDataFormatter$ = chartSubject.dataFormatter$
162
- .pipe(
163
- takeUntil(destroy$),
164
- startWith({}),
165
- map((dataFormatter) => {
166
- // const mergedData = mergeOptionsWithDefault(dataFormatter, mergedPresetWithDefault.dataFormatter)
167
-
168
- // if (chartType === 'multiGrid' && (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList != null) {
169
- // // multiGrid欄位為陣列,需要各別來merge預設值
170
- // (mergedData as DataFormatterTypeMap<'multiGrid'>).gridList = (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList.map(d => {
171
- // return mergeOptionsWithDefault(d, (mergedPresetWithDefault.dataFormatter as DataFormatterTypeMap<'multiGrid'>).gridList[0])
172
- // })
173
- // }
174
- // return mergedData
175
- return mergeDataFormatter(dataFormatter, mergedPresetWithDefault.dataFormatter, chartType)
176
- }),
177
- shareReplay(1)
178
- )
179
- const shareAndMergedChartParams$ = chartSubject.chartParams$
180
- .pipe(
181
- takeUntil(destroy$),
182
- startWith({}),
183
- map((d) => {
184
- return mergeOptionsWithDefault(d, mergedPresetWithDefault.chartParams)
185
- }),
186
- shareReplay(1)
187
- )
188
-
189
- // -- size --
190
- // padding
191
- const mergedPadding$ = shareAndMergedChartParams$
192
- .pipe(
193
- takeUntil(destroy$),
194
- startWith({}),
195
- map((d: any) => {
196
- return mergeOptionsWithDefault(d.padding ?? {}, PADDING_DEFAULT)
197
- })
198
- )
199
- mergedPadding$
200
- .pipe(
201
- takeUntil(destroy$),
202
- first()
203
- )
204
- .subscribe(d => {
205
- selectionLayout
206
- .attr('transform', `translate(${d.left}, ${d.top})`)
207
- })
208
- mergedPadding$.subscribe(size => {
209
- selectionLayout
210
- .transition()
211
- .attr('transform', `translate(${size.left}, ${size.top})`)
212
- })
213
-
214
- // 監聽外層的element尺寸
215
- const rootSize$ = resizeObservable(element)
216
- .pipe(
217
- takeUntil(destroy$),
218
- share()
219
- )
220
- const rootSizeFiltered$ = of().pipe(
221
- mergeWith(
222
- rootSize$.pipe(
223
- debounceTime(250)
224
- ),
225
- rootSize$.pipe(
226
- throttleTime(250)
227
- )
228
- ),
229
- distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),
230
- share()
231
- )
232
- const rootSizeSubscription = rootSizeFiltered$.subscribe()
233
-
234
- // layout
235
- const layout$: Observable<Layout> = combineLatest({
236
- rootSize: rootSizeFiltered$,
237
- mergedPadding: mergedPadding$
238
- }).pipe(
239
- takeUntil(destroy$),
240
- switchMap(async (d) => {
241
- const rootWidth = d.rootSize.width > 0
242
- ? d.rootSize.width
243
- : CHART_WIDTH_DEFAULT
244
- const rootHeight = d.rootSize.height > 0
245
- ? d.rootSize.height
246
- : CHART_HEIGHT_DEFAULT
247
- return {
248
- width: rootWidth - d.mergedPadding.left - d.mergedPadding.right,
249
- height: rootHeight - d.mergedPadding.top - d.mergedPadding.bottom,
250
- top: d.mergedPadding.top,
251
- right: d.mergedPadding.right,
252
- bottom: d.mergedPadding.bottom,
253
- left: d.mergedPadding.left,
254
- rootWidth,
255
- rootHeight
256
- }
257
- }),
258
- shareReplay(1)
259
- )
260
- layout$.subscribe(d => {
261
- svgSelection
262
- .attr('width', d.rootWidth)
263
- .attr('height', d.rootHeight)
264
- })
265
-
266
- // -- computedData --
267
- const computedData$: Observable<ComputedDataTypeMap<T>> = combineLatest({
268
- data: sharedData$,
269
- dataFormatter: shareAndMergedDataFormatter$,
270
- chartParams: shareAndMergedChartParams$,
271
- // layout: iif(() => isAxesTypeMap[chartType] === true, layout$, of(undefined))
272
- }).pipe(
273
- takeUntil(destroy$),
274
- switchMap(async d => d),
275
- switchMap((d) => {
276
- return of(d)
277
- .pipe(
278
- map(_d => {
279
- try {
280
- return computedDataFn({ data: _d.data, dataFormatter: _d.dataFormatter, chartParams: _d.chartParams })
281
- } catch (e) {
282
- console.error(e)
283
- throw new Error(e)
284
- }
285
- }),
286
- catchError(() => EMPTY)
287
- )
288
- }),
289
- shareReplay(1)
290
- )
291
-
292
- // subscribe - computedData組合了所有的chart參數,所以訂閱computedData可以一次訂閱所有的資料流
293
- computedData$.subscribe()
294
-
295
- // -- plugins --
296
- const pluginEntityMap: any = {} // 用於destroy
297
- chartSubject.plugins$.subscribe(plugins => {
298
- if (!plugins) {
299
- return
300
- }
301
- // 建立<g>
302
- const update = selectionPlugins
303
- .selectAll<SVGGElement, PluginEntity<T, any, any>>('g.orbcharts__plugin')
304
- .data(plugins, d => d.name as string)
305
- const enter = update.enter()
306
- .append('g')
307
- .attr('class', plugin => {
308
- return `orbcharts__plugin orbcharts__${plugin.name}`
309
- })
310
- const exit = update.exit()
311
- .remove()
312
-
313
- // destroy entity
314
- exit.each((plugin: PluginEntity<T, unknown, unknown>, i, n) => {
315
- if (pluginEntityMap[plugin.name as string]) {
316
- pluginEntityMap[plugin.name as string].destroy()
317
- pluginEntityMap[plugin.name as string] = undefined
318
- }
319
- })
320
-
321
- enter.each((plugin, i, n) => {
322
- const _pluginObserverBase = {
323
- fullParams$: new Observable(),
324
- fullChartParams$: shareAndMergedChartParams$,
325
- fullDataFormatter$: shareAndMergedDataFormatter$,
326
- computedData$,
327
- layout$
328
- }
329
- const pluginObserver: ContextObserverTypeMap<T, typeof plugin.defaultParams> = contextObserverFn({
330
- observer: _pluginObserverBase,
331
- subject: chartSubject
332
- })
333
-
334
- // -- createPlugin(plugin) --
335
- const pluginSelection = d3.select(n[i])
336
- const pluginContext: PluginContext<T, typeof plugin.name, typeof plugin.defaultParams> = {
337
- selection: pluginSelection,
338
- rootSelection: svgSelection,
339
- name: plugin.name,
340
- chartType,
341
- subject: chartSubject,
342
- observer: pluginObserver
343
- }
344
-
345
- plugin.setPresetParams(mergedPresetWithDefault.allPluginParams[plugin.name] ?? {})
346
- // 傳入context
347
- plugin.setContext(pluginContext)
348
-
349
- // 紀錄起來
350
- pluginEntityMap[pluginContext.name as string] = plugin
351
-
352
- // init plugin
353
- plugin.init()
354
-
355
- })
356
-
357
- })
358
-
359
- return {
360
- ...chartSubject,
361
- selection: svgSelection,
362
- destroy () {
363
- d3.select(element).selectAll('svg').remove()
364
- destroy$.next(undefined)
365
- rootSizeSubscription.unsubscribe()
366
- }
367
- }
368
- }
369
- }
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
+ ContextSubject,
38
+ ComputedDataTypeMap,
39
+ ContextObserverFn,
40
+ ChartOptionsPartial,
41
+ DataTypeMap,
42
+ DataFormatterTypeMap,
43
+ DataFormatterPartialTypeMap,
44
+ DataFormatterBase,
45
+ DataFormatterContext,
46
+ Layout,
47
+ PluginEntity,
48
+ PluginContext,
49
+ Preset,
50
+ PresetPartial,
51
+ ContextObserverTypeMap } from '../types'
52
+ // import type { EventTypeMap } from './types/Event'
53
+ import { mergeOptionsWithDefault } from '../utils'
54
+ import {
55
+ CHART_OPTIONS_DEFAULT,
56
+ PADDING_DEFAULT,
57
+ CHART_PARAMS_DEFAULT,
58
+ CHART_WIDTH_DEFAULT,
59
+ CHART_HEIGHT_DEFAULT } from '../defaults'
60
+
61
+ // 判斷dataFormatter是否需要size參數
62
+ // const isAxesTypeMap: {[key in ChartType]: Boolean} = {
63
+ // series: false,
64
+ // grid: true,
65
+ // multiGrid: true,
66
+ // multiValue: true,
67
+ // tree: false,
68
+ // relationship: false
69
+ // }
70
+
71
+ function resizeObservable(elem: HTMLElement | Element): Observable<DOMRectReadOnly> {
72
+ return new Observable(subscriber => {
73
+ const ro = new ResizeObserver(entries => {
74
+ const entry = entries[0]
75
+ if (entry && entry.contentRect) {
76
+ subscriber.next(entry.contentRect)
77
+ }
78
+ })
79
+
80
+ ro.observe(elem)
81
+ return function unsubscribe() {
82
+ ro.unobserve(elem)
83
+ }
84
+ })
85
+ }
86
+
87
+ function mergeDataFormatter <T>(dataFormatter: any, defaultDataFormatter: T, chartType: ChartType): T {
88
+ const mergedData = mergeOptionsWithDefault(dataFormatter, defaultDataFormatter)
89
+
90
+ if (chartType === 'multiGrid' && (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList != null) {
91
+ // multiGrid欄位為陣列,需要各別來merge預設值
92
+ (mergedData as DataFormatterTypeMap<'multiGrid'>).gridList = (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList.map((d, i) => {
93
+ const defaultGrid = (defaultDataFormatter as DataFormatterTypeMap<'multiGrid'>).gridList[i] || (defaultDataFormatter as DataFormatterTypeMap<'multiGrid'>).gridList[0]
94
+ return mergeOptionsWithDefault(d, defaultGrid)
95
+ })
96
+ }
97
+ return mergedData
98
+ }
99
+
100
+ export const createBaseChart: CreateBaseChart = <T extends ChartType>({ defaultDataFormatter, computedDataFn, contextObserverFn }: {
101
+ defaultDataFormatter: DataFormatterTypeMap<T>
102
+ computedDataFn: ComputedDataFn<T>
103
+ contextObserverFn: ContextObserverFn<T>
104
+ }): CreateChart<T> => {
105
+ const destroy$ = new Subject()
106
+
107
+ const chartType: ChartType = (defaultDataFormatter as unknown as DataFormatterBase<any>).type
108
+
109
+ // 建立chart實例
110
+ return function createChart (element: HTMLElement | Element, options?: ChartOptionsPartial<T>): ChartEntity<T> {
111
+
112
+ // -- selections --
113
+ // svg selection
114
+ d3.select(element).selectAll('svg').remove()
115
+ const svgSelection = d3.select(element).append('svg')
116
+ svgSelection
117
+ .attr('xmlns:xlink', 'http://www.w3.org/1999/xlink')
118
+ .attr('xmls', 'http://www.w3.org/2000/svg')
119
+ .attr('version', '1.1')
120
+ .style('position', 'absolute')
121
+ // .style('width', '100%')
122
+ // .style('height', '100%')
123
+ .classed('orbcharts__root', true)
124
+ // 傳入操作的 selection
125
+ const selectionLayout = svgSelection.append('g')
126
+ selectionLayout.classed('orbcharts__layout', true)
127
+ const selectionPlugins = selectionLayout.append('g')
128
+ selectionPlugins.classed('orbcharts__plugins', true)
129
+
130
+ // chartSubject
131
+ const chartSubject: ContextSubject<T> = {
132
+ data$: new Subject(),
133
+ dataFormatter$: new Subject(),
134
+ plugins$: new Subject(),
135
+ chartParams$: new Subject(),
136
+ event$: new Subject()
137
+ }
138
+
139
+ // options
140
+ const mergedPresetWithDefault: Preset<T, any> = ((options) => {
141
+ const _options = options ? options : CHART_OPTIONS_DEFAULT as ChartOptionsPartial<T>
142
+ const preset = _options.preset ? _options.preset : {} as PresetPartial<T, any>
143
+
144
+ return {
145
+ name: preset.name ?? '',
146
+ description: preset.description ?? '',
147
+ chartParams: preset.chartParams
148
+ ? mergeOptionsWithDefault(preset.chartParams, CHART_PARAMS_DEFAULT)
149
+ : CHART_PARAMS_DEFAULT,
150
+ dataFormatter: preset.dataFormatter
151
+ // ? mergeOptionsWithDefault(preset.dataFormatter, defaultDataFormatter)
152
+ ? mergeDataFormatter(preset.dataFormatter, defaultDataFormatter, chartType)
153
+ : defaultDataFormatter,
154
+ allPluginParams: preset.allPluginParams
155
+ ? preset.allPluginParams
156
+ : {}
157
+ }
158
+ })(options)
159
+
160
+ const sharedData$ = chartSubject.data$.pipe(shareReplay(1))
161
+ const shareAndMergedDataFormatter$ = chartSubject.dataFormatter$
162
+ .pipe(
163
+ takeUntil(destroy$),
164
+ startWith({}),
165
+ map((dataFormatter) => {
166
+ // const mergedData = mergeOptionsWithDefault(dataFormatter, mergedPresetWithDefault.dataFormatter)
167
+
168
+ // if (chartType === 'multiGrid' && (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList != null) {
169
+ // // multiGrid欄位為陣列,需要各別來merge預設值
170
+ // (mergedData as DataFormatterTypeMap<'multiGrid'>).gridList = (dataFormatter as DataFormatterPartialTypeMap<'multiGrid'>).gridList.map(d => {
171
+ // return mergeOptionsWithDefault(d, (mergedPresetWithDefault.dataFormatter as DataFormatterTypeMap<'multiGrid'>).gridList[0])
172
+ // })
173
+ // }
174
+ // return mergedData
175
+ return mergeDataFormatter(dataFormatter, mergedPresetWithDefault.dataFormatter, chartType)
176
+ }),
177
+ shareReplay(1)
178
+ )
179
+ const shareAndMergedChartParams$ = chartSubject.chartParams$
180
+ .pipe(
181
+ takeUntil(destroy$),
182
+ startWith({}),
183
+ map((d) => {
184
+ return mergeOptionsWithDefault(d, mergedPresetWithDefault.chartParams)
185
+ }),
186
+ shareReplay(1)
187
+ )
188
+
189
+ // -- size --
190
+ // padding
191
+ const mergedPadding$ = shareAndMergedChartParams$
192
+ .pipe(
193
+ takeUntil(destroy$),
194
+ startWith({}),
195
+ map((d: any) => {
196
+ return mergeOptionsWithDefault(d.padding ?? {}, PADDING_DEFAULT)
197
+ })
198
+ )
199
+ mergedPadding$
200
+ .pipe(
201
+ takeUntil(destroy$),
202
+ first()
203
+ )
204
+ .subscribe(d => {
205
+ selectionLayout
206
+ .attr('transform', `translate(${d.left}, ${d.top})`)
207
+ })
208
+ mergedPadding$.subscribe(size => {
209
+ selectionLayout
210
+ .transition()
211
+ .attr('transform', `translate(${size.left}, ${size.top})`)
212
+ })
213
+
214
+ // 監聽外層的element尺寸
215
+ const rootSize$ = resizeObservable(element)
216
+ .pipe(
217
+ takeUntil(destroy$),
218
+ share()
219
+ )
220
+ const rootSizeFiltered$ = of().pipe(
221
+ mergeWith(
222
+ rootSize$.pipe(
223
+ debounceTime(250)
224
+ ),
225
+ rootSize$.pipe(
226
+ throttleTime(250)
227
+ )
228
+ ),
229
+ distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),
230
+ share()
231
+ )
232
+ const rootSizeSubscription = rootSizeFiltered$.subscribe()
233
+
234
+ // layout
235
+ const layout$: Observable<Layout> = combineLatest({
236
+ rootSize: rootSizeFiltered$,
237
+ mergedPadding: mergedPadding$
238
+ }).pipe(
239
+ takeUntil(destroy$),
240
+ switchMap(async (d) => {
241
+ const rootWidth = d.rootSize.width > 0
242
+ ? d.rootSize.width
243
+ : CHART_WIDTH_DEFAULT
244
+ const rootHeight = d.rootSize.height > 0
245
+ ? d.rootSize.height
246
+ : CHART_HEIGHT_DEFAULT
247
+ return {
248
+ width: rootWidth - d.mergedPadding.left - d.mergedPadding.right,
249
+ height: rootHeight - d.mergedPadding.top - d.mergedPadding.bottom,
250
+ top: d.mergedPadding.top,
251
+ right: d.mergedPadding.right,
252
+ bottom: d.mergedPadding.bottom,
253
+ left: d.mergedPadding.left,
254
+ rootWidth,
255
+ rootHeight
256
+ }
257
+ }),
258
+ shareReplay(1)
259
+ )
260
+ layout$.subscribe(d => {
261
+ svgSelection
262
+ .attr('width', d.rootWidth)
263
+ .attr('height', d.rootHeight)
264
+ })
265
+
266
+ // -- computedData --
267
+ const computedData$: Observable<ComputedDataTypeMap<T>> = combineLatest({
268
+ data: sharedData$,
269
+ dataFormatter: shareAndMergedDataFormatter$,
270
+ chartParams: shareAndMergedChartParams$,
271
+ // layout: iif(() => isAxesTypeMap[chartType] === true, layout$, of(undefined))
272
+ }).pipe(
273
+ takeUntil(destroy$),
274
+ switchMap(async d => d),
275
+ switchMap((d) => {
276
+ return of(d)
277
+ .pipe(
278
+ map(_d => {
279
+ try {
280
+ return computedDataFn({ data: _d.data, dataFormatter: _d.dataFormatter, chartParams: _d.chartParams })
281
+ } catch (e) {
282
+ console.error(e)
283
+ throw new Error(e)
284
+ }
285
+ }),
286
+ catchError(() => EMPTY)
287
+ )
288
+ }),
289
+ shareReplay(1)
290
+ )
291
+
292
+ // subscribe - computedData組合了所有的chart參數,所以訂閱computedData可以一次訂閱所有的資料流
293
+ computedData$.subscribe()
294
+
295
+ // -- plugins --
296
+ const pluginEntityMap: any = {} // 用於destroy
297
+ chartSubject.plugins$.subscribe(plugins => {
298
+ if (!plugins) {
299
+ return
300
+ }
301
+ // 建立<g>
302
+ const update = selectionPlugins
303
+ .selectAll<SVGGElement, PluginEntity<T, any, any>>('g.orbcharts__plugin')
304
+ .data(plugins, d => d.name as string)
305
+ const enter = update.enter()
306
+ .append('g')
307
+ .attr('class', plugin => {
308
+ return `orbcharts__plugin orbcharts__${plugin.name}`
309
+ })
310
+ const exit = update.exit()
311
+ .remove()
312
+
313
+ // destroy entity
314
+ exit.each((plugin: PluginEntity<T, unknown, unknown>, i, n) => {
315
+ if (pluginEntityMap[plugin.name as string]) {
316
+ pluginEntityMap[plugin.name as string].destroy()
317
+ pluginEntityMap[plugin.name as string] = undefined
318
+ }
319
+ })
320
+
321
+ enter.each((plugin, i, n) => {
322
+ const _pluginObserverBase = {
323
+ fullParams$: new Observable(),
324
+ fullChartParams$: shareAndMergedChartParams$,
325
+ fullDataFormatter$: shareAndMergedDataFormatter$,
326
+ computedData$,
327
+ layout$
328
+ }
329
+ const pluginObserver: ContextObserverTypeMap<T, typeof plugin.defaultParams> = contextObserverFn({
330
+ observer: _pluginObserverBase,
331
+ subject: chartSubject
332
+ })
333
+
334
+ // -- createPlugin(plugin) --
335
+ const pluginSelection = d3.select(n[i])
336
+ const pluginContext: PluginContext<T, typeof plugin.name, typeof plugin.defaultParams> = {
337
+ selection: pluginSelection,
338
+ rootSelection: svgSelection,
339
+ name: plugin.name,
340
+ chartType,
341
+ subject: chartSubject,
342
+ observer: pluginObserver
343
+ }
344
+
345
+ plugin.setPresetParams(mergedPresetWithDefault.allPluginParams[plugin.name] ?? {})
346
+ // 傳入context
347
+ plugin.setContext(pluginContext)
348
+
349
+ // 紀錄起來
350
+ pluginEntityMap[pluginContext.name as string] = plugin
351
+
352
+ // init plugin
353
+ plugin.init()
354
+
355
+ })
356
+
357
+ })
358
+
359
+ return {
360
+ ...chartSubject,
361
+ selection: svgSelection,
362
+ destroy () {
363
+ d3.select(element).selectAll('svg').remove()
364
+ destroy$.next(undefined)
365
+ rootSizeSubscription.unsubscribe()
366
+ }
367
+ }
368
+ }
369
+ }