@orbcharts/core 3.0.0-alpha.38 → 3.0.0-alpha.40

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-core.es.js +5 -4
  3. package/dist/orbcharts-core.umd.js +1 -1
  4. package/dist/src/types/ContextObserverGrid.d.ts +1 -1
  5. package/dist/src/types/ContextObserverSeries.d.ts +1 -1
  6. package/dist/src/types/ContextObserverTree.d.ts +1 -1
  7. package/dist/src/utils/observables.d.ts +5 -5
  8. package/package.json +41 -41
  9. package/src/AbstractChart.ts +48 -48
  10. package/src/GridChart.ts +20 -20
  11. package/src/MultiGridChart.ts +20 -20
  12. package/src/MultiValueChart.ts +20 -20
  13. package/src/RelationshipChart.ts +20 -20
  14. package/src/SeriesChart.ts +20 -20
  15. package/src/TreeChart.ts +20 -20
  16. package/src/base/createBaseChart.ts +367 -367
  17. package/src/base/createBasePlugin.ts +89 -89
  18. package/src/defaults.ts +247 -247
  19. package/src/defineGridPlugin.ts +3 -3
  20. package/src/defineMultiGridPlugin.ts +3 -3
  21. package/src/defineMultiValuePlugin.ts +3 -3
  22. package/src/defineNoneDataPlugin.ts +4 -4
  23. package/src/defineRelationshipPlugin.ts +3 -3
  24. package/src/defineSeriesPlugin.ts +3 -3
  25. package/src/defineTreePlugin.ts +3 -3
  26. package/src/grid/computeGridData.ts +205 -205
  27. package/src/grid/createGridContextObserver.ts +124 -124
  28. package/src/grid/gridObservables.ts +486 -486
  29. package/src/index.ts +21 -21
  30. package/src/multiGrid/computeMultiGridData.ts +173 -173
  31. package/src/multiGrid/createMultiGridContextObserver.ts +34 -34
  32. package/src/multiGrid/multiGridObservables.ts +285 -285
  33. package/src/multiValue/computeMultiValueData.ts +136 -136
  34. package/src/multiValue/createMultiValueContextObserver.ts +12 -12
  35. package/src/relationship/computeRelationshipData.ts +106 -106
  36. package/src/relationship/createRelationshipContextObserver.ts +12 -12
  37. package/src/series/computeSeriesData.ts +153 -153
  38. package/src/series/createSeriesContextObserver.ts +33 -33
  39. package/src/series/seriesObservables.ts +23 -23
  40. package/src/tree/computeTreeData.ts +128 -128
  41. package/src/tree/createTreeContextObserver.ts +56 -56
  42. package/src/tree/treeObservables.ts +94 -94
  43. package/src/types/Chart.ts +48 -48
  44. package/src/types/ChartParams.ts +51 -51
  45. package/src/types/ComputedData.ts +82 -82
  46. package/src/types/ComputedDataGrid.ts +13 -13
  47. package/src/types/ComputedDataMultiGrid.ts +2 -2
  48. package/src/types/ComputedDataMultiValue.ts +9 -9
  49. package/src/types/ComputedDataRelationship.ts +19 -19
  50. package/src/types/ComputedDataSeries.ts +7 -7
  51. package/src/types/ComputedDataTree.ts +19 -19
  52. package/src/types/ContextObserver.ts +38 -38
  53. package/src/types/ContextObserverGrid.ts +33 -33
  54. package/src/types/ContextObserverMultiGrid.ts +27 -27
  55. package/src/types/ContextObserverMultiValue.ts +4 -4
  56. package/src/types/ContextObserverRelationship.ts +4 -4
  57. package/src/types/ContextObserverSeries.ts +7 -7
  58. package/src/types/ContextObserverTree.ts +10 -10
  59. package/src/types/ContextSubject.ts +18 -18
  60. package/src/types/Data.ts +45 -45
  61. package/src/types/DataFormatter.ts +95 -95
  62. package/src/types/DataFormatterGrid.ts +55 -55
  63. package/src/types/DataFormatterMultiGrid.ts +42 -42
  64. package/src/types/DataFormatterMultiValue.ts +20 -20
  65. package/src/types/DataFormatterRelationship.ts +22 -22
  66. package/src/types/DataFormatterSeries.ts +29 -29
  67. package/src/types/DataFormatterTree.ts +12 -12
  68. package/src/types/DataGrid.ts +11 -11
  69. package/src/types/DataMultiGrid.ts +6 -6
  70. package/src/types/DataMultiValue.ts +12 -12
  71. package/src/types/DataRelationship.ts +27 -27
  72. package/src/types/DataSeries.ts +11 -11
  73. package/src/types/DataTree.ts +20 -20
  74. package/src/types/Event.ts +153 -153
  75. package/src/types/Layout.ts +11 -11
  76. package/src/types/Padding.ts +5 -5
  77. package/src/types/Plugin.ts +60 -60
  78. package/src/types/TransformData.ts +7 -7
  79. package/src/types/index.ts +37 -37
  80. package/src/utils/commonUtils.ts +50 -50
  81. package/src/utils/d3Utils.ts +89 -89
  82. package/src/utils/index.ts +4 -4
  83. package/src/utils/observables.ts +181 -181
  84. package/src/utils/orbchartsUtils.ts +253 -253
  85. package/tsconfig.json +13 -13
  86. package/vite.config.js +44 -44
@@ -1,367 +1,367 @@
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, layout: _d.layout })
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
- }
317
- })
318
-
319
- enter.each((plugin, i, n) => {
320
- const _pluginObserverBase = {
321
- fullParams$: new Observable(),
322
- fullChartParams$: shareAndMergedChartParams$,
323
- fullDataFormatter$: shareAndMergedDataFormatter$,
324
- computedData$,
325
- layout$
326
- }
327
- const pluginObserver: ContextObserverTypeMap<T, typeof plugin.defaultParams> = contextObserverFn({
328
- observer: _pluginObserverBase,
329
- subject: chartSubject
330
- })
331
-
332
- // -- createPlugin(plugin) --
333
- const pluginSelection = d3.select(n[i])
334
- const pluginContext: PluginContext<T, typeof plugin.name, typeof plugin.defaultParams> = {
335
- selection: pluginSelection,
336
- rootSelection: svgSelection,
337
- name: plugin.name,
338
- chartType,
339
- subject: chartSubject,
340
- observer: pluginObserver
341
- }
342
-
343
- plugin.setPresetParams(mergedPresetWithDefault.allPluginParams[plugin.name] ?? {})
344
- // 傳入context
345
- plugin.setContext(pluginContext)
346
-
347
- // 紀錄起來
348
- pluginEntityMap[pluginContext.name as string] = plugin
349
-
350
- // init plugin
351
- plugin.init()
352
-
353
- })
354
-
355
- })
356
-
357
- return {
358
- ...chartSubject,
359
- selection: svgSelection,
360
- destroy () {
361
- d3.select(element).selectAll('svg').remove()
362
- destroy$.next(undefined)
363
- rootSizeSubscription.unsubscribe()
364
- }
365
- }
366
- }
367
- }
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, layout: _d.layout })
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
+ }
317
+ })
318
+
319
+ enter.each((plugin, i, n) => {
320
+ const _pluginObserverBase = {
321
+ fullParams$: new Observable(),
322
+ fullChartParams$: shareAndMergedChartParams$,
323
+ fullDataFormatter$: shareAndMergedDataFormatter$,
324
+ computedData$,
325
+ layout$
326
+ }
327
+ const pluginObserver: ContextObserverTypeMap<T, typeof plugin.defaultParams> = contextObserverFn({
328
+ observer: _pluginObserverBase,
329
+ subject: chartSubject
330
+ })
331
+
332
+ // -- createPlugin(plugin) --
333
+ const pluginSelection = d3.select(n[i])
334
+ const pluginContext: PluginContext<T, typeof plugin.name, typeof plugin.defaultParams> = {
335
+ selection: pluginSelection,
336
+ rootSelection: svgSelection,
337
+ name: plugin.name,
338
+ chartType,
339
+ subject: chartSubject,
340
+ observer: pluginObserver
341
+ }
342
+
343
+ plugin.setPresetParams(mergedPresetWithDefault.allPluginParams[plugin.name] ?? {})
344
+ // 傳入context
345
+ plugin.setContext(pluginContext)
346
+
347
+ // 紀錄起來
348
+ pluginEntityMap[pluginContext.name as string] = plugin
349
+
350
+ // init plugin
351
+ plugin.init()
352
+
353
+ })
354
+
355
+ })
356
+
357
+ return {
358
+ ...chartSubject,
359
+ selection: svgSelection,
360
+ destroy () {
361
+ d3.select(element).selectAll('svg').remove()
362
+ destroy$.next(undefined)
363
+ rootSizeSubscription.unsubscribe()
364
+ }
365
+ }
366
+ }
367
+ }