@orbcharts/plugins-basic 3.0.0-alpha.41 → 3.0.0-alpha.43

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-plugins-basic.es.js +5477 -5426
  3. package/dist/orbcharts-plugins-basic.umd.js +8 -8
  4. package/dist/src/base/BaseBarStack.d.ts +1 -1
  5. package/dist/src/base/BaseBars.d.ts +1 -1
  6. package/dist/src/base/BaseBarsTriangle.d.ts +1 -1
  7. package/dist/src/base/BaseDots.d.ts +1 -1
  8. package/dist/src/base/BaseLineAreas.d.ts +1 -1
  9. package/dist/src/base/BaseLines.d.ts +1 -1
  10. package/dist/src/multiGrid/defaults.d.ts +2 -1
  11. package/dist/src/multiGrid/index.d.ts +1 -0
  12. package/dist/src/multiGrid/plugins/MultiLineAreas.d.ts +1 -0
  13. package/dist/src/multiGrid/types.d.ts +4 -4
  14. package/package.json +42 -42
  15. package/src/base/BaseBarStack.ts +881 -879
  16. package/src/base/BaseBars.ts +750 -748
  17. package/src/base/BaseBarsTriangle.ts +659 -657
  18. package/src/base/BaseDots.ts +639 -637
  19. package/src/base/BaseGroupAxis.ts +496 -496
  20. package/src/base/BaseLegend.ts +636 -636
  21. package/src/base/BaseLineAreas.ts +621 -624
  22. package/src/base/BaseLines.ts +692 -695
  23. package/src/base/BaseValueAxis.ts +479 -479
  24. package/src/base/types.ts +2 -2
  25. package/src/grid/defaults.ts +121 -121
  26. package/src/grid/gridObservables.ts +263 -263
  27. package/src/grid/index.ts +15 -15
  28. package/src/grid/plugins/BarStack.ts +37 -37
  29. package/src/grid/plugins/Bars.ts +37 -37
  30. package/src/grid/plugins/BarsDiverging.ts +39 -39
  31. package/src/grid/plugins/BarsTriangle.ts +34 -34
  32. package/src/grid/plugins/Dots.ts +35 -35
  33. package/src/grid/plugins/GridLegend.ts +58 -58
  34. package/src/grid/plugins/GroupAux.ts +643 -643
  35. package/src/grid/plugins/GroupAxis.ts +30 -30
  36. package/src/grid/plugins/LineAreas.ts +36 -36
  37. package/src/grid/plugins/Lines.ts +35 -35
  38. package/src/grid/plugins/ScalingArea.ts +174 -174
  39. package/src/grid/plugins/ValueAxis.ts +31 -31
  40. package/src/grid/plugins/ValueStackAxis.ts +70 -70
  41. package/src/grid/types.ts +120 -120
  42. package/src/index.ts +9 -9
  43. package/src/multiGrid/defaults.ts +147 -140
  44. package/src/multiGrid/index.ts +11 -10
  45. package/src/multiGrid/multiGridObservables.ts +289 -278
  46. package/src/multiGrid/plugins/MultiBarStack.ts +60 -60
  47. package/src/multiGrid/plugins/MultiBars.ts +59 -59
  48. package/src/multiGrid/plugins/MultiBarsTriangle.ts +58 -58
  49. package/src/multiGrid/plugins/MultiDots.ts +58 -58
  50. package/src/multiGrid/plugins/MultiGridLegend.ts +88 -88
  51. package/src/multiGrid/plugins/MultiGroupAxis.ts +53 -53
  52. package/src/multiGrid/plugins/MultiLineAreas.ts +59 -0
  53. package/src/multiGrid/plugins/MultiLines.ts +58 -58
  54. package/src/multiGrid/plugins/MultiValueAxis.ts +53 -53
  55. package/src/multiGrid/plugins/OverlappingValueAxes.ts +164 -165
  56. package/src/multiGrid/types.ts +67 -67
  57. package/src/noneData/defaults.ts +61 -61
  58. package/src/noneData/index.ts +3 -3
  59. package/src/noneData/plugins/Container.ts +10 -10
  60. package/src/noneData/plugins/Tooltip.ts +304 -304
  61. package/src/noneData/types.ts +26 -26
  62. package/src/series/defaults.ts +99 -99
  63. package/src/series/index.ts +6 -6
  64. package/src/series/plugins/Bubbles.ts +551 -549
  65. package/src/series/plugins/Pie.ts +600 -598
  66. package/src/series/plugins/PieEventTexts.ts +194 -194
  67. package/src/series/plugins/PieLabels.ts +288 -285
  68. package/src/series/plugins/SeriesLegend.ts +58 -58
  69. package/src/series/seriesUtils.ts +50 -50
  70. package/src/series/types.ts +67 -67
  71. package/src/tree/defaults.ts +22 -22
  72. package/src/tree/index.ts +3 -3
  73. package/src/tree/plugins/TreeLegend.ts +58 -58
  74. package/src/tree/plugins/TreeMap.ts +302 -300
  75. package/src/tree/types.ts +23 -23
  76. package/src/utils/commonUtils.ts +21 -21
  77. package/src/utils/d3Graphics.ts +124 -124
  78. package/src/utils/d3Utils.ts +73 -73
  79. package/src/utils/observables.ts +14 -14
  80. package/src/utils/orbchartsUtils.ts +100 -100
  81. package/tsconfig.dev.json +16 -16
  82. package/tsconfig.json +13 -13
  83. package/tsconfig.prod.json +13 -13
  84. package/vite.config.js +49 -49
@@ -1,599 +1,601 @@
1
- import * as d3 from 'd3'
2
- import {
3
- of,
4
- combineLatest,
5
- map,
6
- merge,
7
- take,
8
- filter,
9
- switchMap,
10
- first,
11
- takeUntil,
12
- distinctUntilChanged,
13
- BehaviorSubject,
14
- Subject,
15
- Observable } from 'rxjs'
16
- import {
17
- defineSeriesPlugin } from '@orbcharts/core'
18
- import type {
19
- ChartParams } from '@orbcharts/core'
20
- import type { PieParams } from '../types'
21
- import type { PieDatum } from '../seriesUtils'
22
- import { DEFAULT_PIE_PARAMS } from '../defaults'
23
- import { makePieData } from '../seriesUtils'
24
- import { getD3TransitionEase, makeD3Arc } from '../../utils/d3Utils'
25
- import { getClassName } from '../../utils/orbchartsUtils'
26
-
27
- const pluginName = 'Pie'
28
- const pathClassName = getClassName(pluginName, 'path')
29
-
30
- function makeTweenPieRenderDataFn ({ enter, exit, data, lastData, fullParams }: {
31
- enter: d3.Selection<d3.EnterElement, PieDatum, any, any>
32
- exit: d3.Selection<SVGPathElement, unknown, any, any>
33
- data: PieDatum[]
34
- lastData: PieDatum[]
35
- fullParams: PieParams
36
- }): (t: number) => PieDatum[] {
37
- // 無更新資料項目則只計算資料變化 (新資料 * t + 舊資料 * (1 - t))
38
- if (!enter.size() && !exit.size()) {
39
- return (t: number) => {
40
- const tweenData: PieDatum[] = data.map((_d, _i) => {
41
- const lastDatum = lastData[_i] ?? {
42
- startAngle: 0,
43
- endAngle: 0,
44
- value: 0
45
- }
46
- return {
47
- ..._d,
48
- startAngle: (_d.startAngle * t) + (lastDatum.startAngle * (1 - t)),
49
- endAngle: (_d.endAngle * t) + (lastDatum.endAngle * (1 - t)),
50
- value: (_d.value * t) + (lastDatum.value * (1 - t))
51
- }
52
- })
53
-
54
- return makePieRenderData(
55
- tweenData,
56
- fullParams.startAngle!,
57
- fullParams.endAngle!,
58
- 1
59
- )
60
- }
61
- // 有更新資料則重新繪圖
62
- } else {
63
- return (t: number) => {
64
- return makePieRenderData(
65
- data,
66
- fullParams.startAngle!,
67
- fullParams.endAngle!,
68
- t
69
- )
70
- }
71
- }
72
- }
73
-
74
- function makePieRenderData (data: PieDatum[], startAngle: number, endAngle: number, t: number): PieDatum[] {
75
- return data.map((d, i) => {
76
- const _startAngle = startAngle + (d.startAngle - startAngle) * t
77
- const _endAngle = _startAngle + (d.endAngle - d.startAngle) * t
78
- return {
79
- ...d,
80
- startAngle: _startAngle,
81
- endAngle: _endAngle
82
- }
83
- })
84
- }
85
-
86
- function renderPie ({ selection, renderData, arc }: {
87
- selection: d3.Selection<SVGGElement, unknown, any, unknown>
88
- renderData: PieDatum[]
89
- arc: d3.Arc<any, d3.DefaultArcObject>
90
- }): d3.Selection<SVGPathElement, PieDatum, any, any> {
91
- let update: d3.Selection<SVGPathElement, PieDatum, any, any> = selection
92
- .selectAll<SVGPathElement, PieDatum>('path')
93
- .data(renderData, d => d.id)
94
- let enter = update.enter()
95
- .append<SVGPathElement>('path')
96
- .classed(pathClassName, true)
97
- let exit = update.exit()
98
-
99
- enter
100
- .append('path')
101
-
102
- const pathSelection = update.merge(enter)
103
- pathSelection
104
- .style('cursor', 'pointer')
105
- .attr('fill', (d, i) => d.data.color)
106
- // .transition()
107
- .attr('d', (d, i) => {
108
- return arc!(d as any)
109
- })
110
- exit.remove()
111
-
112
- return pathSelection
113
- }
114
-
115
- function highlight ({ pathSelection, ids, fullChartParams, arc, arcMouseover }: {
116
- pathSelection: d3.Selection<SVGPathElement, PieDatum, any, any>
117
- ids: string[]
118
- fullChartParams: ChartParams
119
- arc: d3.Arc<any, d3.DefaultArcObject>
120
- arcMouseover: d3.Arc<any, d3.DefaultArcObject>
121
- }) {
122
- pathSelection.interrupt('highlight')
123
-
124
- if (!ids.length) {
125
- // 取消放大
126
- pathSelection
127
- .transition('highlight')
128
- .style('opacity', 1)
129
- .attr('d', (d) => {
130
- return arc!(d as any)
131
- })
132
- return
133
- }
134
-
135
- pathSelection.each((d, i, n) => {
136
- const segment = d3.select(n[i])
137
-
138
- if (ids.includes(d.data.id)) {
139
- segment
140
- .style('opacity', 1)
141
- .transition('highlight')
142
- .ease(d3.easeElastic)
143
- .duration(500)
144
- .attr('d', (d: any) => {
145
- return arcMouseover!(d)
146
- })
147
- // .on('interrupt', () => {
148
- // // this.pathSelection!.select('path').attr('d', (d) => {
149
- // // return this.arc!(d as any)
150
- // // })
151
- // this.initHighlight()
152
- // })
153
- } else {
154
- // 取消放大
155
- segment
156
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
157
- .transition('highlight')
158
- .attr('d', (d) => {
159
- return arc!(d as any)
160
- })
161
- }
162
- })
163
- }
164
-
165
-
166
- export const Pie = defineSeriesPlugin(pluginName, DEFAULT_PIE_PARAMS)(({ selection, name, observer, subject }) => {
167
-
168
- const destroy$ = new Subject()
169
-
170
- const graphicGSelection: d3.Selection<SVGGElement, any, any, any> = selection.append('g')
171
- // let pathSelection: d3.Selection<SVGPathElement, PieDatum, any, any> | undefined
172
- const pathSelection$: Subject<d3.Selection<SVGPathElement, PieDatum, any, any>> = new Subject()
173
- let lastData: PieDatum[] = []
174
- let renderData: PieDatum[] = []
175
- // let originHighlight: Highlight | null = null
176
-
177
- observer.layout$
178
- .pipe(
179
- first()
180
- )
181
- .subscribe(size => {
182
- selection
183
- .attr('transform', `translate(${size.width / 2}, ${size.height / 2})`)
184
- observer.layout$
185
- .pipe(
186
- takeUntil(destroy$)
187
- )
188
- .subscribe(size => {
189
- selection
190
- .transition()
191
- .attr('transform', `translate(${size.width / 2}, ${size.height / 2})`)
192
- })
193
- })
194
-
195
- const shorterSideWith$ = observer.layout$.pipe(
196
- takeUntil(destroy$),
197
- map(d => d.width < d.height ? d.width : d.height)
198
- )
199
-
200
- const pieData$: Observable<PieDatum[]> = new Observable(subscriber => {
201
- combineLatest({
202
- computedData: observer.computedData$,
203
- fullParams: observer.fullParams$,
204
- }).pipe(
205
- takeUntil(destroy$),
206
- // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
207
- switchMap(async (d) => d),
208
- ).subscribe(data => {
209
- const pieData: PieDatum[] = makePieData({
210
- computedDataSeries: data.computedData,
211
- startAngle: data.fullParams.startAngle,
212
- endAngle: data.fullParams.endAngle
213
- })
214
- subscriber.next(pieData)
215
- })
216
- })
217
-
218
- // const SeriesDataMap$ = observer.computedData$.pipe(
219
- // takeUntil(destroy$),
220
- // map(d => makeSeriesDataMap(d))
221
- // )
222
-
223
- const arc$: Observable<d3.Arc<any, d3.DefaultArcObject>> = new Observable(subscriber => {
224
- combineLatest({
225
- shorterSideWith: shorterSideWith$,
226
- fullParams: observer.fullParams$,
227
- }).pipe(
228
- takeUntil(destroy$),
229
- // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
230
- switchMap(async (d) => d),
231
- ).subscribe(data => {
232
- const arc = makeD3Arc({
233
- axisWidth: data.shorterSideWith,
234
- innerRadius: data.fullParams.innerRadius,
235
- outerRadius: data.fullParams.outerRadius,
236
- padAngle: data.fullParams.padAngle,
237
- cornerRadius: data.fullParams.cornerRadius
238
- })
239
- subscriber.next(arc)
240
- })
241
- })
242
-
243
- const arcMouseover$: Observable<d3.Arc<any, d3.DefaultArcObject>> = new Observable(subscriber => {
244
- combineLatest({
245
- shorterSideWith: shorterSideWith$,
246
- fullParams: observer.fullParams$,
247
- }).pipe(
248
- takeUntil(destroy$),
249
- // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
250
- switchMap(async (d) => d),
251
- ).subscribe(data => {
252
- const arcMouseover = makeD3Arc({
253
- axisWidth: data.shorterSideWith,
254
- innerRadius: data.fullParams.innerRadius,
255
- outerRadius: data.fullParams.outerMouseoverRadius, // 外半徑變化
256
- padAngle: data.fullParams.padAngle,
257
- cornerRadius: data.fullParams.cornerRadius
258
- })
259
- subscriber.next(arcMouseover)
260
- })
261
- })
262
-
263
- // combineLatest({
264
- // pieData: pieData$,
265
- // SeriesDataMap: SeriesDataMap$,
266
- // arc: arc$,
267
- // arcMouseover: arcMouseover$,
268
- // computedData: computedData$,
269
- // fullParams: fullParams$,
270
- // // fullChartParams: fullChartParams$
271
- // }).pipe(
272
- // takeUntil(destroy$),
273
- // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
274
- // switchMap(d => combineLatest({
275
- // pieData: pieData$,
276
- // SeriesDataMap: SeriesDataMap$,
277
- // arc: arc$,
278
- // arcMouseover: arcMouseover$,
279
- // computedData: computedData$,
280
- // fullParams: fullParams$,
281
- // fullChartParams: fullChartParams$
282
- // })),
283
- // take(1)
284
- const highlightTarget$ = observer.fullChartParams$.pipe(
285
- takeUntil(destroy$),
286
- map(d => d.highlightTarget),
287
- distinctUntilChanged()
288
- )
289
-
290
- combineLatest({
291
- pieData: pieData$,
292
- SeriesDataMap: observer.SeriesDataMap$,
293
- arc: arc$,
294
- arcMouseover: arcMouseover$,
295
- computedData: observer.computedData$,
296
- fullParams: observer.fullParams$,
297
- fullChartParams: observer.fullChartParams$,
298
- highlightTarget: highlightTarget$
299
- }).pipe(
300
- takeUntil(destroy$),
301
- switchMap(async d => d)
302
- ).subscribe(data => {
303
- graphicGSelection.interrupt('graphicMove')
304
- // console.log('graphic', data)
305
- let update: d3.Selection<SVGPathElement, PieDatum, any, any> = selection
306
- .selectAll<SVGPathElement, PieDatum>('path')
307
- .data(data.pieData, d => d.data.id)
308
- let enter = update.enter()
309
- let exit = update.exit()
310
-
311
- const makeTweenPieRenderData = makeTweenPieRenderDataFn({
312
- enter,
313
- exit,
314
- data: data.pieData,
315
- lastData,
316
- fullParams: data.fullParams
317
- })
318
-
319
- graphicGSelection
320
- .transition('graphicMove')
321
- .duration(data.fullChartParams.transitionDuration)
322
- .ease(getD3TransitionEase(data.fullChartParams.transitionEase))
323
- .tween('move', (self, t) => {
324
- return (t) => {
325
- renderData = makeTweenPieRenderData(t)
326
-
327
- const pathSelection = renderPie({ selection: graphicGSelection, renderData, arc: data.arc })
328
-
329
- subject.event$.next({
330
- type: 'series',
331
- pluginName: name,
332
- eventName: 'transitionMove',
333
- event: undefined,
334
- highlightTarget: data.highlightTarget,
335
- datum: null,
336
- series: [],
337
- seriesIndex: -1,
338
- seriesLabel: '',
339
- data: data.computedData
340
- })
341
- // const callbackData = makeEnterDurationCallbackData(data.computedData, )
342
- // enterDurationCallback(callbackData, t)
343
- }
344
- })
345
- .on('end', (self, t) => {
346
- renderData = makePieRenderData(
347
- data.pieData,
348
- data.fullParams.startAngle,
349
- data.fullParams.endAngle,
350
- 1
351
- )
352
- // console.log('renderData', renderData)
353
- const pathSelection = renderPie({ selection: graphicGSelection, renderData, arc: data.arc })
354
-
355
- // if (data.fullParams.highlightTarget && data.fullParams.highlightTarget != 'none') {
356
- // if (data.fullChartParams.highlightTarget && data.fullChartParams.highlightTarget != 'none') {
357
- // pathSelection!.style('cursor', 'pointer')
358
- // }
359
-
360
- pathSelection$.next(pathSelection)
361
-
362
- // pathSelection && setPathEvent({
363
- // pathSelection,
364
- // pluginName: name,
365
- // data: data.computedData,
366
- // fullChartParams: data.fullChartParams,
367
- // arc: data.arc,
368
- // arcMouseover: data.arcMouseover,
369
- // SeriesDataMap: data.SeriesDataMap,
370
- // event$: store.event$
371
- // })
372
-
373
- // 渲染完後紀錄為前次的資料
374
- lastData = Object.assign([], data.pieData)
375
-
376
- subject.event$.next({
377
- type: 'series',
378
- pluginName: name,
379
- eventName: 'transitionEnd',
380
- event: undefined,
381
- highlightTarget: data.highlightTarget,
382
- datum: null,
383
- series: [],
384
- seriesIndex: -1,
385
- seriesLabel: '',
386
- data: data.computedData
387
- })
388
-
389
- pathSelection!
390
- .on('mouseover', (event, pieDatum) => {
391
- event.stopPropagation()
392
-
393
- subject.event$.next({
394
- type: 'series',
395
- eventName: 'mouseover',
396
- pluginName: name,
397
- highlightTarget: data.highlightTarget,
398
- datum: pieDatum.data,
399
- series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
400
- seriesIndex: pieDatum.data.seriesIndex,
401
- seriesLabel: pieDatum.data.seriesLabel,
402
- event,
403
- data: data.computedData
404
- })
405
- })
406
- .on('mousemove', (event, pieDatum) => {
407
- event.stopPropagation()
408
-
409
- subject.event$.next({
410
- type: 'series',
411
- eventName: 'mousemove',
412
- pluginName: name,
413
- highlightTarget: data.highlightTarget,
414
- datum: pieDatum.data,
415
- series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
416
- seriesIndex: pieDatum.data.seriesIndex,
417
- seriesLabel: pieDatum.data.seriesLabel,
418
- event,
419
- data: data.computedData,
420
- })
421
- })
422
- .on('mouseout', (event, pieDatum) => {
423
- event.stopPropagation()
424
-
425
- subject.event$.next({
426
- type: 'series',
427
- eventName: 'mouseout',
428
- pluginName: name,
429
- highlightTarget: data.highlightTarget,
430
- datum: pieDatum.data,
431
- series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
432
- seriesIndex: pieDatum.data.seriesIndex,
433
- seriesLabel: pieDatum.data.seriesLabel,
434
- event,
435
- data: data.computedData,
436
- })
437
- })
438
- .on('click', (event, pieDatum) => {
439
- event.stopPropagation()
440
-
441
- subject.event$.next({
442
- type: 'series',
443
- eventName: 'click',
444
- pluginName: name,
445
- highlightTarget: data.highlightTarget,
446
- datum: pieDatum.data,
447
- series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
448
- seriesIndex: pieDatum.data.seriesIndex,
449
- seriesLabel: pieDatum.data.seriesLabel,
450
- event,
451
- data: data.computedData,
452
- })
453
- })
454
- })
455
- })
456
-
457
- // 事件觸發的highlight
458
- // const highlightMouseover$ = store.event$.pipe(
459
- // takeUntil(destroy$),
460
- // filter(d => d.eventName === 'mouseover'),
461
- // // distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
462
- // map(d => {
463
- // return d.datum
464
- // ? { id: d.datum?.id, label: d.datum.label }
465
- // : { id: '', label: '' }
466
- // })
467
- // )
468
- // const highlightMouseout$ = store.event$.pipe(
469
- // takeUntil(destroy$),
470
- // filter(d => d.eventName === 'mouseout'),
471
- // // distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
472
- // map(d => {
473
- // return { id: '', label: '' }
474
- // })
475
- // )
476
-
477
- // // 預設的highlight
478
- // const highlightDefault$ = fullChartParams$.pipe(
479
- // takeUntil(destroy$),
480
- // map(d => {
481
- // return { id: d.highlightDefault, label: d.highlightDefault }
482
- // })
483
- // )
484
-
485
- // combineLatest({
486
- // target: merge(highlightMouseover$, highlightMouseout$, highlightDefault$),
487
- // pathSelection: pathSelection$,
488
- // computedData: computedData$,
489
- // fullChartParams: fullChartParams$,
490
- // arc: arc$,
491
- // arcMouseover: arcMouseover$
492
- // }).pipe(
493
- // takeUntil(destroy$)
494
- // ).subscribe(data => {
495
- // // console.log('target', data.target)
496
- // const ids = getSeriesHighlightIds({
497
- // id: data.target.id,
498
- // label: data.target.label,
499
- // trigger: data.fullChartParams.highlightDefault.trigger,
500
- // data: data.computedData
501
- // })
502
- // // console.log('ids', ids)
503
- // highlight({
504
- // pathSelection: data.pathSelection,
505
- // ids: ids,
506
- // fullChartParams: data.fullChartParams,
507
- // arc: data.arc,
508
- // arcMouseover: data.arcMouseover
509
- // })
510
- // })
511
-
512
- combineLatest({
513
- pathSelection: pathSelection$,
514
- highlight: observer.seriesHighlight$,
515
- fullChartParams: observer.fullChartParams$,
516
- arc: arc$,
517
- arcMouseover: arcMouseover$
518
- }).pipe(
519
- takeUntil(destroy$),
520
- switchMap(async d => d)
521
- ).subscribe(data => {
522
- highlight({
523
- pathSelection: data.pathSelection,
524
- ids: data.highlight,
525
- fullChartParams: data.fullChartParams,
526
- arc: data.arc,
527
- arcMouseover: data.arcMouseover
528
- })
529
- })
530
-
531
- // d.fullParams
532
-
533
- // console.log('selection', selection)
534
-
535
- // fullChartParams$
536
- // .subscribe(d => {
537
- // console.log('fullChartParams', d)
538
- // })
539
-
540
- // computedData$
541
- // .subscribe(d => {
542
- // console.log('computedData', d)
543
- // })
544
- // console.log('-- defineSeriesPlugin --')
545
- // console.log('selector', selector)
546
- // // data$.subscribe(d => {
547
- // // console.log('data$', d)
548
- // // })
549
-
550
- // store.dataFormatter$.subscribe(d => {
551
- // console.log('store.dataFormatter$', d)
552
- // })
553
-
554
- // computedData$.subscribe(d => {
555
- // console.log('computedData$', d)
556
- // })
557
-
558
- // event$.subscribe(d => {
559
- // console.log('event$', d)
560
- // })
561
-
562
- // fullParams$.subscribe(d => {
563
- // console.log('fullParams$', d)
564
- // })
565
-
566
- // store.data$.subscribe(d => {
567
- // console.log('store.data$', d)
568
- // })
569
-
570
- // store.dataFormatter$.subscribe(d => {
571
- // console.log('store.dataFormatter$', d)
572
- // })
573
-
574
- // store.event$.subscribe(d => {
575
- // console.log('store.event$', d)
576
- // })
577
-
578
- // store.fullParams$.subscribe(d => {
579
- // console.log('store.fullParams$', d)
580
- // })
581
-
582
- // layout$.subscribe(d => {
583
- // console.log('layout$', d)
584
- // })
585
-
586
- // console.log('-- end --')
587
- // // const newData = data.map(d => d.map(_d => {
588
- // // return {
589
- // // ..._d as any,
590
- // // value: (_d as any).value + 10
591
- // // }
592
- // // }))
593
-
594
- // // data$.next(newData)
595
-
596
- return () => {
597
- destroy$.next(undefined)
598
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ of,
4
+ combineLatest,
5
+ map,
6
+ merge,
7
+ take,
8
+ filter,
9
+ switchMap,
10
+ first,
11
+ takeUntil,
12
+ distinctUntilChanged,
13
+ BehaviorSubject,
14
+ Subject,
15
+ Observable } from 'rxjs'
16
+ import {
17
+ defineSeriesPlugin } from '@orbcharts/core'
18
+ import type {
19
+ ChartParams } from '@orbcharts/core'
20
+ import type { PieParams } from '../types'
21
+ import type { PieDatum } from '../seriesUtils'
22
+ import { DEFAULT_PIE_PARAMS } from '../defaults'
23
+ import { makePieData } from '../seriesUtils'
24
+ import { getD3TransitionEase, makeD3Arc } from '../../utils/d3Utils'
25
+ import { getClassName } from '../../utils/orbchartsUtils'
26
+
27
+ const pluginName = 'Pie'
28
+ const pathClassName = getClassName(pluginName, 'path')
29
+
30
+ function makeTweenPieRenderDataFn ({ enter, exit, data, lastData, fullParams }: {
31
+ enter: d3.Selection<d3.EnterElement, PieDatum, any, any>
32
+ exit: d3.Selection<SVGPathElement, unknown, any, any>
33
+ data: PieDatum[]
34
+ lastData: PieDatum[]
35
+ fullParams: PieParams
36
+ }): (t: number) => PieDatum[] {
37
+ // 無更新資料項目則只計算資料變化 (新資料 * t + 舊資料 * (1 - t))
38
+ if (!enter.size() && !exit.size()) {
39
+ return (t: number) => {
40
+ const tweenData: PieDatum[] = data.map((_d, _i) => {
41
+ const lastDatum = lastData[_i] ?? {
42
+ startAngle: 0,
43
+ endAngle: 0,
44
+ value: 0
45
+ }
46
+ return {
47
+ ..._d,
48
+ startAngle: (_d.startAngle * t) + (lastDatum.startAngle * (1 - t)),
49
+ endAngle: (_d.endAngle * t) + (lastDatum.endAngle * (1 - t)),
50
+ value: (_d.value * t) + (lastDatum.value * (1 - t))
51
+ }
52
+ })
53
+
54
+ return makePieRenderData(
55
+ tweenData,
56
+ fullParams.startAngle!,
57
+ fullParams.endAngle!,
58
+ 1
59
+ )
60
+ }
61
+ // 有更新資料則重新繪圖
62
+ } else {
63
+ return (t: number) => {
64
+ return makePieRenderData(
65
+ data,
66
+ fullParams.startAngle!,
67
+ fullParams.endAngle!,
68
+ t
69
+ )
70
+ }
71
+ }
72
+ }
73
+
74
+ function makePieRenderData (data: PieDatum[], startAngle: number, endAngle: number, t: number): PieDatum[] {
75
+ return data.map((d, i) => {
76
+ const _startAngle = startAngle + (d.startAngle - startAngle) * t
77
+ const _endAngle = _startAngle + (d.endAngle - d.startAngle) * t
78
+ return {
79
+ ...d,
80
+ startAngle: _startAngle,
81
+ endAngle: _endAngle
82
+ }
83
+ })
84
+ }
85
+
86
+ function renderPie ({ selection, renderData, arc }: {
87
+ selection: d3.Selection<SVGGElement, unknown, any, unknown>
88
+ renderData: PieDatum[]
89
+ arc: d3.Arc<any, d3.DefaultArcObject>
90
+ }): d3.Selection<SVGPathElement, PieDatum, any, any> {
91
+ let update: d3.Selection<SVGPathElement, PieDatum, any, any> = selection
92
+ .selectAll<SVGPathElement, PieDatum>('path')
93
+ .data(renderData, d => d.id)
94
+ let enter = update.enter()
95
+ .append<SVGPathElement>('path')
96
+ .classed(pathClassName, true)
97
+ let exit = update.exit()
98
+
99
+ enter
100
+ .append('path')
101
+
102
+ const pathSelection = update.merge(enter)
103
+ pathSelection
104
+ .style('cursor', 'pointer')
105
+ .attr('fill', (d, i) => d.data.color)
106
+ // .transition()
107
+ .attr('d', (d, i) => {
108
+ return arc!(d as any)
109
+ })
110
+ exit.remove()
111
+
112
+ return pathSelection
113
+ }
114
+
115
+ function highlight ({ pathSelection, ids, fullChartParams, arc, arcMouseover }: {
116
+ pathSelection: d3.Selection<SVGPathElement, PieDatum, any, any>
117
+ ids: string[]
118
+ fullChartParams: ChartParams
119
+ arc: d3.Arc<any, d3.DefaultArcObject>
120
+ arcMouseover: d3.Arc<any, d3.DefaultArcObject>
121
+ }) {
122
+ pathSelection.interrupt('highlight')
123
+
124
+ if (!ids.length) {
125
+ // 取消放大
126
+ pathSelection
127
+ .transition('highlight')
128
+ .style('opacity', 1)
129
+ .attr('d', (d) => {
130
+ return arc!(d as any)
131
+ })
132
+ return
133
+ }
134
+
135
+ pathSelection.each((d, i, n) => {
136
+ const segment = d3.select(n[i])
137
+
138
+ if (ids.includes(d.data.id)) {
139
+ segment
140
+ .style('opacity', 1)
141
+ .transition('highlight')
142
+ .ease(d3.easeElastic)
143
+ .duration(500)
144
+ .attr('d', (d: any) => {
145
+ return arcMouseover!(d)
146
+ })
147
+ // .on('interrupt', () => {
148
+ // // this.pathSelection!.select('path').attr('d', (d) => {
149
+ // // return this.arc!(d as any)
150
+ // // })
151
+ // this.initHighlight()
152
+ // })
153
+ } else {
154
+ // 取消放大
155
+ segment
156
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
157
+ .transition('highlight')
158
+ .attr('d', (d) => {
159
+ return arc!(d as any)
160
+ })
161
+ }
162
+ })
163
+ }
164
+
165
+
166
+ export const Pie = defineSeriesPlugin(pluginName, DEFAULT_PIE_PARAMS)(({ selection, name, observer, subject }) => {
167
+
168
+ const destroy$ = new Subject()
169
+
170
+ const graphicGSelection: d3.Selection<SVGGElement, any, any, any> = selection.append('g')
171
+ // let pathSelection: d3.Selection<SVGPathElement, PieDatum, any, any> | undefined
172
+ const pathSelection$: Subject<d3.Selection<SVGPathElement, PieDatum, any, any>> = new Subject()
173
+ let lastData: PieDatum[] = []
174
+ let renderData: PieDatum[] = []
175
+ // let originHighlight: Highlight | null = null
176
+
177
+ observer.layout$
178
+ .pipe(
179
+ first()
180
+ )
181
+ .subscribe(size => {
182
+ selection
183
+ .attr('transform', `translate(${size.width / 2}, ${size.height / 2})`)
184
+ observer.layout$
185
+ .pipe(
186
+ takeUntil(destroy$)
187
+ )
188
+ .subscribe(size => {
189
+ selection
190
+ .transition()
191
+ .attr('transform', `translate(${size.width / 2}, ${size.height / 2})`)
192
+ })
193
+ })
194
+
195
+ const shorterSideWith$ = observer.layout$.pipe(
196
+ takeUntil(destroy$),
197
+ map(d => d.width < d.height ? d.width : d.height)
198
+ )
199
+
200
+ const pieData$: Observable<PieDatum[]> = new Observable(subscriber => {
201
+ combineLatest({
202
+ computedData: observer.computedData$,
203
+ fullParams: observer.fullParams$,
204
+ }).pipe(
205
+ takeUntil(destroy$),
206
+ // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
207
+ switchMap(async (d) => d),
208
+ ).subscribe(data => {
209
+ const pieData: PieDatum[] = makePieData({
210
+ computedDataSeries: data.computedData,
211
+ startAngle: data.fullParams.startAngle,
212
+ endAngle: data.fullParams.endAngle
213
+ })
214
+ subscriber.next(pieData)
215
+ })
216
+ })
217
+
218
+ // const SeriesDataMap$ = observer.computedData$.pipe(
219
+ // takeUntil(destroy$),
220
+ // map(d => makeSeriesDataMap(d))
221
+ // )
222
+
223
+ const arc$: Observable<d3.Arc<any, d3.DefaultArcObject>> = new Observable(subscriber => {
224
+ combineLatest({
225
+ shorterSideWith: shorterSideWith$,
226
+ fullParams: observer.fullParams$,
227
+ }).pipe(
228
+ takeUntil(destroy$),
229
+ // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
230
+ switchMap(async (d) => d),
231
+ ).subscribe(data => {
232
+ const arc = makeD3Arc({
233
+ axisWidth: data.shorterSideWith,
234
+ innerRadius: data.fullParams.innerRadius,
235
+ outerRadius: data.fullParams.outerRadius,
236
+ padAngle: data.fullParams.padAngle,
237
+ cornerRadius: data.fullParams.cornerRadius
238
+ })
239
+ subscriber.next(arc)
240
+ })
241
+ })
242
+
243
+ const arcMouseover$: Observable<d3.Arc<any, d3.DefaultArcObject>> = new Observable(subscriber => {
244
+ combineLatest({
245
+ shorterSideWith: shorterSideWith$,
246
+ fullParams: observer.fullParams$,
247
+ }).pipe(
248
+ takeUntil(destroy$),
249
+ // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
250
+ switchMap(async (d) => d),
251
+ ).subscribe(data => {
252
+ const arcMouseover = makeD3Arc({
253
+ axisWidth: data.shorterSideWith,
254
+ innerRadius: data.fullParams.innerRadius,
255
+ outerRadius: data.fullParams.outerMouseoverRadius, // 外半徑變化
256
+ padAngle: data.fullParams.padAngle,
257
+ cornerRadius: data.fullParams.cornerRadius
258
+ })
259
+ subscriber.next(arcMouseover)
260
+ })
261
+ })
262
+
263
+ // combineLatest({
264
+ // pieData: pieData$,
265
+ // SeriesDataMap: SeriesDataMap$,
266
+ // arc: arc$,
267
+ // arcMouseover: arcMouseover$,
268
+ // computedData: computedData$,
269
+ // fullParams: fullParams$,
270
+ // // fullChartParams: fullChartParams$
271
+ // }).pipe(
272
+ // takeUntil(destroy$),
273
+ // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
274
+ // switchMap(d => combineLatest({
275
+ // pieData: pieData$,
276
+ // SeriesDataMap: SeriesDataMap$,
277
+ // arc: arc$,
278
+ // arcMouseover: arcMouseover$,
279
+ // computedData: computedData$,
280
+ // fullParams: fullParams$,
281
+ // fullChartParams: fullChartParams$
282
+ // })),
283
+ // take(1)
284
+ const highlightTarget$ = observer.fullChartParams$.pipe(
285
+ takeUntil(destroy$),
286
+ map(d => d.highlightTarget),
287
+ distinctUntilChanged()
288
+ )
289
+
290
+ combineLatest({
291
+ pieData: pieData$,
292
+ SeriesDataMap: observer.SeriesDataMap$,
293
+ arc: arc$,
294
+ arcMouseover: arcMouseover$,
295
+ computedData: observer.computedData$,
296
+ fullParams: observer.fullParams$,
297
+ fullChartParams: observer.fullChartParams$,
298
+ highlightTarget: highlightTarget$
299
+ }).pipe(
300
+ takeUntil(destroy$),
301
+ switchMap(async d => d)
302
+ ).subscribe(data => {
303
+ graphicGSelection.interrupt('graphicMove')
304
+ // console.log('graphic', data)
305
+ let update: d3.Selection<SVGPathElement, PieDatum, any, any> = selection
306
+ .selectAll<SVGPathElement, PieDatum>('path')
307
+ .data(data.pieData, d => d.data.id)
308
+ let enter = update.enter()
309
+ let exit = update.exit()
310
+
311
+ const makeTweenPieRenderData = makeTweenPieRenderDataFn({
312
+ enter,
313
+ exit,
314
+ data: data.pieData,
315
+ lastData,
316
+ fullParams: data.fullParams
317
+ })
318
+
319
+ graphicGSelection
320
+ .transition('graphicMove')
321
+ .duration(data.fullChartParams.transitionDuration)
322
+ .ease(getD3TransitionEase(data.fullChartParams.transitionEase))
323
+ .tween('move', (self, t) => {
324
+ return (t) => {
325
+ renderData = makeTweenPieRenderData(t)
326
+
327
+ const pathSelection = renderPie({ selection: graphicGSelection, renderData, arc: data.arc })
328
+
329
+ subject.event$.next({
330
+ type: 'series',
331
+ pluginName: name,
332
+ eventName: 'transitionMove',
333
+ event: undefined,
334
+ highlightTarget: data.highlightTarget,
335
+ datum: null,
336
+ series: [],
337
+ seriesIndex: -1,
338
+ seriesLabel: '',
339
+ data: data.computedData
340
+ })
341
+ // const callbackData = makeEnterDurationCallbackData(data.computedData, )
342
+ // enterDurationCallback(callbackData, t)
343
+ }
344
+ })
345
+ .on('end', (self, t) => {
346
+ renderData = makePieRenderData(
347
+ data.pieData,
348
+ data.fullParams.startAngle,
349
+ data.fullParams.endAngle,
350
+ 1
351
+ )
352
+ // console.log('renderData', renderData)
353
+ const pathSelection = renderPie({ selection: graphicGSelection, renderData, arc: data.arc })
354
+
355
+ // if (data.fullParams.highlightTarget && data.fullParams.highlightTarget != 'none') {
356
+ // if (data.fullChartParams.highlightTarget && data.fullChartParams.highlightTarget != 'none') {
357
+ // pathSelection!.style('cursor', 'pointer')
358
+ // }
359
+
360
+ pathSelection$.next(pathSelection)
361
+
362
+ // pathSelection && setPathEvent({
363
+ // pathSelection,
364
+ // pluginName: name,
365
+ // data: data.computedData,
366
+ // fullChartParams: data.fullChartParams,
367
+ // arc: data.arc,
368
+ // arcMouseover: data.arcMouseover,
369
+ // SeriesDataMap: data.SeriesDataMap,
370
+ // event$: store.event$
371
+ // })
372
+
373
+ // 渲染完後紀錄為前次的資料
374
+ lastData = Object.assign([], data.pieData)
375
+
376
+ subject.event$.next({
377
+ type: 'series',
378
+ pluginName: name,
379
+ eventName: 'transitionEnd',
380
+ event: undefined,
381
+ highlightTarget: data.highlightTarget,
382
+ datum: null,
383
+ series: [],
384
+ seriesIndex: -1,
385
+ seriesLabel: '',
386
+ data: data.computedData
387
+ })
388
+
389
+ pathSelection!
390
+ .on('mouseover', (event, pieDatum) => {
391
+ event.stopPropagation()
392
+
393
+ subject.event$.next({
394
+ type: 'series',
395
+ eventName: 'mouseover',
396
+ pluginName: name,
397
+ highlightTarget: data.highlightTarget,
398
+ datum: pieDatum.data,
399
+ series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
400
+ seriesIndex: pieDatum.data.seriesIndex,
401
+ seriesLabel: pieDatum.data.seriesLabel,
402
+ event,
403
+ data: data.computedData
404
+ })
405
+ })
406
+ .on('mousemove', (event, pieDatum) => {
407
+ event.stopPropagation()
408
+
409
+ subject.event$.next({
410
+ type: 'series',
411
+ eventName: 'mousemove',
412
+ pluginName: name,
413
+ highlightTarget: data.highlightTarget,
414
+ datum: pieDatum.data,
415
+ series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
416
+ seriesIndex: pieDatum.data.seriesIndex,
417
+ seriesLabel: pieDatum.data.seriesLabel,
418
+ event,
419
+ data: data.computedData,
420
+ })
421
+ })
422
+ .on('mouseout', (event, pieDatum) => {
423
+ event.stopPropagation()
424
+
425
+ subject.event$.next({
426
+ type: 'series',
427
+ eventName: 'mouseout',
428
+ pluginName: name,
429
+ highlightTarget: data.highlightTarget,
430
+ datum: pieDatum.data,
431
+ series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
432
+ seriesIndex: pieDatum.data.seriesIndex,
433
+ seriesLabel: pieDatum.data.seriesLabel,
434
+ event,
435
+ data: data.computedData,
436
+ })
437
+ })
438
+ .on('click', (event, pieDatum) => {
439
+ event.stopPropagation()
440
+
441
+ subject.event$.next({
442
+ type: 'series',
443
+ eventName: 'click',
444
+ pluginName: name,
445
+ highlightTarget: data.highlightTarget,
446
+ datum: pieDatum.data,
447
+ series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
448
+ seriesIndex: pieDatum.data.seriesIndex,
449
+ seriesLabel: pieDatum.data.seriesLabel,
450
+ event,
451
+ data: data.computedData,
452
+ })
453
+ })
454
+ })
455
+ })
456
+
457
+ // 事件觸發的highlight
458
+ // const highlightMouseover$ = store.event$.pipe(
459
+ // takeUntil(destroy$),
460
+ // filter(d => d.eventName === 'mouseover'),
461
+ // // distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
462
+ // map(d => {
463
+ // return d.datum
464
+ // ? { id: d.datum?.id, label: d.datum.label }
465
+ // : { id: '', label: '' }
466
+ // })
467
+ // )
468
+ // const highlightMouseout$ = store.event$.pipe(
469
+ // takeUntil(destroy$),
470
+ // filter(d => d.eventName === 'mouseout'),
471
+ // // distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
472
+ // map(d => {
473
+ // return { id: '', label: '' }
474
+ // })
475
+ // )
476
+
477
+ // // 預設的highlight
478
+ // const highlightDefault$ = fullChartParams$.pipe(
479
+ // takeUntil(destroy$),
480
+ // map(d => {
481
+ // return { id: d.highlightDefault, label: d.highlightDefault }
482
+ // })
483
+ // )
484
+
485
+ // combineLatest({
486
+ // target: merge(highlightMouseover$, highlightMouseout$, highlightDefault$),
487
+ // pathSelection: pathSelection$,
488
+ // computedData: computedData$,
489
+ // fullChartParams: fullChartParams$,
490
+ // arc: arc$,
491
+ // arcMouseover: arcMouseover$
492
+ // }).pipe(
493
+ // takeUntil(destroy$)
494
+ // ).subscribe(data => {
495
+ // // console.log('target', data.target)
496
+ // const ids = getSeriesHighlightIds({
497
+ // id: data.target.id,
498
+ // label: data.target.label,
499
+ // trigger: data.fullChartParams.highlightDefault.trigger,
500
+ // data: data.computedData
501
+ // })
502
+ // // console.log('ids', ids)
503
+ // highlight({
504
+ // pathSelection: data.pathSelection,
505
+ // ids: ids,
506
+ // fullChartParams: data.fullChartParams,
507
+ // arc: data.arc,
508
+ // arcMouseover: data.arcMouseover
509
+ // })
510
+ // })
511
+
512
+ combineLatest({
513
+ pathSelection: pathSelection$,
514
+ highlight: observer.seriesHighlight$.pipe(
515
+ map(data => data.map(d => d.id))
516
+ ),
517
+ fullChartParams: observer.fullChartParams$,
518
+ arc: arc$,
519
+ arcMouseover: arcMouseover$
520
+ }).pipe(
521
+ takeUntil(destroy$),
522
+ switchMap(async d => d)
523
+ ).subscribe(data => {
524
+ highlight({
525
+ pathSelection: data.pathSelection,
526
+ ids: data.highlight,
527
+ fullChartParams: data.fullChartParams,
528
+ arc: data.arc,
529
+ arcMouseover: data.arcMouseover
530
+ })
531
+ })
532
+
533
+ // d.fullParams
534
+
535
+ // console.log('selection', selection)
536
+
537
+ // fullChartParams$
538
+ // .subscribe(d => {
539
+ // console.log('fullChartParams', d)
540
+ // })
541
+
542
+ // computedData$
543
+ // .subscribe(d => {
544
+ // console.log('computedData', d)
545
+ // })
546
+ // console.log('-- defineSeriesPlugin --')
547
+ // console.log('selector', selector)
548
+ // // data$.subscribe(d => {
549
+ // // console.log('data$', d)
550
+ // // })
551
+
552
+ // store.dataFormatter$.subscribe(d => {
553
+ // console.log('store.dataFormatter$', d)
554
+ // })
555
+
556
+ // computedData$.subscribe(d => {
557
+ // console.log('computedData$', d)
558
+ // })
559
+
560
+ // event$.subscribe(d => {
561
+ // console.log('event$', d)
562
+ // })
563
+
564
+ // fullParams$.subscribe(d => {
565
+ // console.log('fullParams$', d)
566
+ // })
567
+
568
+ // store.data$.subscribe(d => {
569
+ // console.log('store.data$', d)
570
+ // })
571
+
572
+ // store.dataFormatter$.subscribe(d => {
573
+ // console.log('store.dataFormatter$', d)
574
+ // })
575
+
576
+ // store.event$.subscribe(d => {
577
+ // console.log('store.event$', d)
578
+ // })
579
+
580
+ // store.fullParams$.subscribe(d => {
581
+ // console.log('store.fullParams$', d)
582
+ // })
583
+
584
+ // layout$.subscribe(d => {
585
+ // console.log('layout$', d)
586
+ // })
587
+
588
+ // console.log('-- end --')
589
+ // // const newData = data.map(d => d.map(_d => {
590
+ // // return {
591
+ // // ..._d as any,
592
+ // // value: (_d as any).value + 10
593
+ // // }
594
+ // // }))
595
+
596
+ // // data$.next(newData)
597
+
598
+ return () => {
599
+ destroy$.next(undefined)
600
+ }
599
601
  })