@orbcharts/plugins-basic 3.0.0-alpha.50 → 3.0.0-alpha.52

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