@orbcharts/plugins-basic 3.0.0-alpha.68 → 3.0.0-alpha.69

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