@orbcharts/core 3.0.0-alpha.47 → 3.0.0-alpha.49

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