@orbcharts/plugins-basic 3.0.0-alpha.61 → 3.0.0-alpha.62

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +1 -1
  3. package/dist/orbcharts-plugins-basic.umd.js +1 -1
  4. package/package.json +42 -42
  5. package/src/base/BaseBarStack.ts +778 -778
  6. package/src/base/BaseBars.ts +764 -764
  7. package/src/base/BaseBarsTriangle.ts +672 -672
  8. package/src/base/BaseDots.ts +513 -513
  9. package/src/base/BaseGroupAxis.ts +558 -558
  10. package/src/base/BaseLegend.ts +641 -641
  11. package/src/base/BaseLineAreas.ts +628 -628
  12. package/src/base/BaseLines.ts +704 -704
  13. package/src/base/BaseValueAxis.ts +480 -478
  14. package/src/base/types.ts +2 -2
  15. package/src/grid/defaults.ts +128 -128
  16. package/src/grid/gridObservables.ts +541 -541
  17. package/src/grid/index.ts +15 -15
  18. package/src/grid/plugins/BarStack.ts +43 -43
  19. package/src/grid/plugins/Bars.ts +44 -44
  20. package/src/grid/plugins/BarsPN.ts +41 -41
  21. package/src/grid/plugins/BarsTriangle.ts +42 -42
  22. package/src/grid/plugins/Dots.ts +37 -37
  23. package/src/grid/plugins/GridLegend.ts +59 -59
  24. package/src/grid/plugins/GroupAux.ts +976 -976
  25. package/src/grid/plugins/GroupAxis.ts +35 -35
  26. package/src/grid/plugins/LineAreas.ts +40 -40
  27. package/src/grid/plugins/Lines.ts +40 -40
  28. package/src/grid/plugins/ScalingArea.ts +173 -173
  29. package/src/grid/plugins/ValueAxis.ts +36 -36
  30. package/src/grid/plugins/ValueStackAxis.ts +38 -38
  31. package/src/grid/types.ts +123 -123
  32. package/src/index.ts +9 -9
  33. package/src/multiGrid/defaults.ts +158 -158
  34. package/src/multiGrid/index.ts +13 -13
  35. package/src/multiGrid/multiGridObservables.ts +49 -49
  36. package/src/multiGrid/plugins/MultiBarStack.ts +78 -78
  37. package/src/multiGrid/plugins/MultiBars.ts +77 -77
  38. package/src/multiGrid/plugins/MultiBarsTriangle.ts +77 -77
  39. package/src/multiGrid/plugins/MultiDots.ts +65 -65
  40. package/src/multiGrid/plugins/MultiGridLegend.ts +89 -89
  41. package/src/multiGrid/plugins/MultiGroupAxis.ts +69 -69
  42. package/src/multiGrid/plugins/MultiLineAreas.ts +77 -77
  43. package/src/multiGrid/plugins/MultiLines.ts +77 -77
  44. package/src/multiGrid/plugins/MultiValueAxis.ts +69 -69
  45. package/src/multiGrid/plugins/MultiValueStackAxis.ts +69 -69
  46. package/src/multiGrid/plugins/OverlappingValueAxes.ts +167 -167
  47. package/src/multiGrid/plugins/OverlappingValueStackAxes.ts +168 -168
  48. package/src/multiGrid/types.ts +72 -72
  49. package/src/noneData/defaults.ts +102 -102
  50. package/src/noneData/index.ts +3 -3
  51. package/src/noneData/plugins/Container.ts +10 -10
  52. package/src/noneData/plugins/Tooltip.ts +310 -310
  53. package/src/noneData/types.ts +26 -26
  54. package/src/series/defaults.ts +144 -144
  55. package/src/series/index.ts +9 -9
  56. package/src/series/plugins/Bubbles.ts +545 -545
  57. package/src/series/plugins/Pie.ts +576 -576
  58. package/src/series/plugins/PieEventTexts.ts +262 -262
  59. package/src/series/plugins/PieLabels.ts +304 -304
  60. package/src/series/plugins/Rose.ts +472 -472
  61. package/src/series/plugins/RoseLabels.ts +362 -362
  62. package/src/series/plugins/SeriesLegend.ts +59 -59
  63. package/src/series/seriesObservables.ts +145 -145
  64. package/src/series/seriesUtils.ts +51 -51
  65. package/src/series/types.ts +83 -83
  66. package/src/tree/defaults.ts +23 -23
  67. package/src/tree/index.ts +3 -3
  68. package/src/tree/plugins/TreeLegend.ts +59 -59
  69. package/src/tree/plugins/TreeMap.ts +305 -305
  70. package/src/tree/types.ts +23 -23
  71. package/src/utils/commonUtils.ts +21 -21
  72. package/src/utils/d3Graphics.ts +124 -124
  73. package/src/utils/d3Utils.ts +73 -73
  74. package/src/utils/observables.ts +14 -14
  75. package/src/utils/orbchartsUtils.ts +100 -100
  76. package/tsconfig.base.json +13 -13
  77. package/tsconfig.json +2 -2
  78. package/vite.config.js +22 -22
@@ -1,473 +1,473 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- map,
5
- filter,
6
- switchMap,
7
- takeUntil,
8
- distinctUntilChanged,
9
- shareReplay,
10
- Observable,
11
- Subject,
12
- BehaviorSubject } from 'rxjs'
13
- import type {
14
- ComputedDataSeries,
15
- ComputedDatumSeries,
16
- SeriesContainerPosition,
17
- ChartParams,
18
- EventSeries,
19
- Layout } from '@orbcharts/core'
20
- import type { D3PieDatum } from '../seriesUtils'
21
- import type { RoseParams } from '../types'
22
- import {
23
- defineSeriesPlugin } from '@orbcharts/core'
24
- import { DEFAULT_ROSE_PARAMS } from '../defaults'
25
- // import { makePieData } from '../seriesUtils'
26
- // import { getD3TransitionEase, makeD3Arc } from '../../utils/d3Utils'
27
- import { getClassName } from '../../utils/orbchartsUtils'
28
- import { seriesCenterSelectionObservable } from '../seriesObservables'
29
-
30
- // @Q@ 暫時先寫在這裡,之後pie一起重構後再放到seriesUtils
31
- export interface PieDatum extends D3PieDatum {
32
- data: ComputedDatumSeries
33
- id: string
34
- prevValue: number // 補間動畫用的(前次資料的value)
35
- }
36
-
37
- const pluginName = 'Rose'
38
-
39
- const roseInnerRadius = 0
40
- const roseStartAngle = 0
41
- const roseEndAngle = Math.PI * 2
42
- const rosePadAngle = 0
43
-
44
- function makeTweenArcFn ({ cornerRadius, outerRadius, axisWidth, maxValue, arcScaleType }: {
45
- // interpolateRadius: (t: number) => number
46
- outerRadius: number
47
- cornerRadius: number
48
- axisWidth: number
49
- maxValue: number
50
- arcScaleType: 'radius' | 'area'
51
- }): (d: PieDatum) => (t: number) => string {
52
-
53
- const outerRadiusWidth = (axisWidth / 2) * outerRadius
54
-
55
- // const arcScale = d3.scaleLinear()
56
- // .domain([0, maxValue])
57
- // .range([0, outerRadiusWidth])
58
-
59
- const exponent = arcScaleType === 'area'
60
- ? 0.5 // 比例映射面積(0.5為取平方根)
61
- : 1 // 比例映射半徑
62
-
63
- const arcScale = d3.scalePow()
64
- .domain([0, maxValue])
65
- .range([0, outerRadiusWidth])
66
- .exponent(exponent)
67
-
68
- return (d: PieDatum) => {
69
- const prevEachOuterRadius = arcScale(d.prevValue)!
70
- const eachOuterRadius = arcScale(d.value)!
71
-
72
- const interpolateRadius = d3.interpolate(prevEachOuterRadius, eachOuterRadius)
73
-
74
- return (t: number) => {
75
-
76
- const outerRadius = interpolateRadius(t)
77
-
78
- const arc = d3.arc()
79
- .innerRadius(0)
80
- .outerRadius(outerRadius)
81
- .padAngle(rosePadAngle)
82
- .padRadius(outerRadius)
83
- .cornerRadius(cornerRadius)
84
-
85
- return arc(d as any)
86
- }
87
- }
88
- }
89
-
90
- // function renderPie ({ selection, data, tweenArc, transitionDuration, pathClassName }: {
91
- // selection: d3.Selection<SVGGElement, unknown, any, unknown>
92
- // data: PieDatum[]
93
- // // arc: d3.Arc<any, d3.DefaultArcObject>
94
- // tweenArc: (d: PieDatum) => (t: number) => string
95
- // transitionDuration: number
96
- // pathClassName: string
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
- // pathSelection
107
- // .transition('graphicMove')
108
- // .duration(transitionDuration)
109
- // .attrTween('d', tweenArc)
110
-
111
- // return pathSelection
112
- // }
113
-
114
- function highlight ({ pathSelection, ids, fullParams, fullChartParams, tweenArc }: {
115
- pathSelection: d3.Selection<SVGPathElement, PieDatum, any, any>
116
- ids: string[]
117
- fullParams: RoseParams
118
- fullChartParams: ChartParams
119
- // arc: d3.Arc<any, d3.DefaultArcObject>
120
- tweenArc: (d: PieDatum) => (t: number) => string
121
- }) {
122
- pathSelection.interrupt('highlight')
123
-
124
- if (!ids.length) {
125
- // 取消放大
126
- pathSelection
127
- .transition('highlight')
128
- .style('opacity', 1)
129
- .attr('d', (d: PieDatum) => {
130
- return tweenArc(d)(1)
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 arc!({
146
- // ...d,
147
- // startAngle: d.startAngle - 0.5,
148
- // endAngle: d.endAngle + 0.5
149
- // })
150
- // })
151
- .attr('d', (d: PieDatum) => {
152
- return tweenArc({
153
- ...d,
154
- startAngle: d.startAngle - fullParams.mouseoverAngleIncrease,
155
- endAngle: d.endAngle + fullParams.mouseoverAngleIncrease
156
- })(1)
157
- })
158
- // .on('interrupt', () => {
159
- // // this.pathSelection!.select('path').attr('d', (d) => {
160
- // // return this.arc!(d as any)
161
- // // })
162
- // this.initHighlight()
163
- // })
164
- } else {
165
- // 取消放大
166
- segment
167
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
168
- .transition('highlight')
169
- .attr('d', (d: PieDatum) => {
170
- return tweenArc(d)(1)
171
- })
172
- }
173
- })
174
- }
175
-
176
- // 各別的pie
177
- function createEachRose (pluginName: string, context: {
178
- containerSelection: d3.Selection<SVGGElement, any, any, unknown>
179
- computedData$: Observable<ComputedDatumSeries[][]>
180
- visibleComputedData$: Observable<ComputedDatumSeries[][]>
181
- visibleComputedLayoutData$: Observable<ComputedDatumSeries[][]>
182
- containerVisibleComputedLayoutData$: Observable<ComputedDatumSeries[]>
183
- SeriesDataMap$: Observable<Map<string, ComputedDatumSeries[]>>
184
- fullParams$: Observable<RoseParams>
185
- fullChartParams$: Observable<ChartParams>
186
- seriesHighlight$: Observable<ComputedDatumSeries[]>
187
- seriesContainerPosition$: Observable<SeriesContainerPosition>
188
- event$: Subject<EventSeries>
189
- }) {
190
- const destroy$ = new Subject()
191
-
192
- const pathClassName = getClassName(pluginName, 'path')
193
-
194
- let lastPieData: PieDatum[] = [] // 紀錄補間動畫前次的資料
195
-
196
- const shorterSideWith$ = context.seriesContainerPosition$.pipe(
197
- takeUntil(destroy$),
198
- map(d => d.width < d.height ? d.width : d.height),
199
- distinctUntilChanged()
200
- )
201
-
202
- const pieData$: Observable<PieDatum[]> = combineLatest({
203
- containerVisibleComputedLayoutData: context.containerVisibleComputedLayoutData$,
204
- fullParams: context.fullParams$,
205
- }).pipe(
206
- takeUntil(destroy$),
207
- switchMap(async (d) => d),
208
- map(data => {
209
- const eachAngle = roseEndAngle / data.containerVisibleComputedLayoutData.length
210
- return data.containerVisibleComputedLayoutData.map((d, i) => {
211
- return {
212
- id: d.id,
213
- data: d,
214
- index: i,
215
- value: d.value,
216
- startAngle: eachAngle * i,
217
- endAngle: eachAngle * (i + 1),
218
- padAngle: rosePadAngle,
219
- prevValue: (lastPieData[i] && lastPieData[i].id === d.id) ? lastPieData[i].value : 0
220
- }
221
- })
222
- })
223
- )
224
-
225
- const highlightTarget$ = context.fullChartParams$.pipe(
226
- takeUntil(destroy$),
227
- map(d => d.highlightTarget),
228
- distinctUntilChanged()
229
- )
230
-
231
- const maxValue$ = context.visibleComputedLayoutData$.pipe(
232
- map(data => Math.max(...data.flat().map(d => d.value))),
233
- distinctUntilChanged()
234
- )
235
-
236
- // context.visibleComputedLayoutData$.subscribe(data => {
237
- // console.log('visibleComputedLayoutData$', data)
238
- // })
239
-
240
- const tweenArc$ = combineLatest({
241
- fullParams: context.fullParams$,
242
- axisWidth: shorterSideWith$,
243
- maxValue: maxValue$
244
- }).pipe(
245
- takeUntil(destroy$),
246
- switchMap(async d => d),
247
- map((data) => {
248
- return makeTweenArcFn({
249
- cornerRadius: data.fullParams.cornerRadius,
250
- outerRadius: data.fullParams.outerRadius,
251
- axisWidth: data.axisWidth,
252
- maxValue: data.maxValue,
253
- arcScaleType: data.fullParams.arcScaleType
254
- })
255
- })
256
- )
257
-
258
- const transitionDuration$ = context.fullChartParams$.pipe(
259
- takeUntil(destroy$),
260
- map(d => d.transitionDuration),
261
- distinctUntilChanged()
262
- )
263
-
264
- // 是否在transition中
265
- const isTransitionMoving$ = new BehaviorSubject<boolean>(false)
266
-
267
- const pathSelection$ = new Observable<d3.Selection<SVGPathElement, PieDatum, any, any>>(subscriber => {
268
- combineLatest({
269
- pieData: pieData$,
270
- tweenArc: tweenArc$,
271
- transitionDuration: transitionDuration$,
272
- }).pipe(
273
- takeUntil(destroy$),
274
- switchMap(async d => d)
275
- ).subscribe(data => {
276
- const pieData = data.pieData.map((d, i) => {
277
- d.prevValue = (lastPieData[i] && lastPieData[i].id === d.id) ? lastPieData[i].value : 0
278
- return d
279
- })
280
-
281
- isTransitionMoving$.next(true)
282
-
283
- const pathSelection: d3.Selection<SVGPathElement, PieDatum, any, any> = context.containerSelection
284
- .selectAll<SVGPathElement, PieDatum>('path')
285
- .data(pieData, d => d.id)
286
- .join('path')
287
- .classed(pathClassName, true)
288
- .style('cursor', 'pointer')
289
- .attr('fill', (d, i) => d.data.color)
290
- pathSelection.interrupt('graphicMove')
291
- pathSelection
292
- .transition('graphicMove')
293
- .duration(data.transitionDuration)
294
- .attrTween('d', data.tweenArc)
295
- .on('end', () => {
296
- subscriber.next(pathSelection)
297
-
298
- isTransitionMoving$.next(false)
299
- // lastPieData = Object.assign([], data.pieData)
300
- // console.log('lastPieData', lastPieData)
301
- })
302
- lastPieData = Object.assign([], pieData)
303
-
304
- })
305
- }).pipe(
306
- shareReplay(1)
307
- )
308
-
309
- combineLatest({
310
- pathSelection: pathSelection$,
311
- SeriesDataMap: context.SeriesDataMap$,
312
- computedData: context.computedData$,
313
- highlightTarget: highlightTarget$
314
- }).pipe(
315
- takeUntil(destroy$),
316
- switchMap(async d => d)
317
- ).subscribe(data => {
318
- data.pathSelection
319
- .on('mouseover', (event, pieDatum) => {
320
- event.stopPropagation()
321
-
322
- context.event$.next({
323
- type: 'series',
324
- eventName: 'mouseover',
325
- pluginName,
326
- highlightTarget: data.highlightTarget,
327
- datum: pieDatum.data,
328
- series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
329
- seriesIndex: pieDatum.data.seriesIndex,
330
- seriesLabel: pieDatum.data.seriesLabel,
331
- event,
332
- data: data.computedData
333
- })
334
- })
335
- .on('mousemove', (event, pieDatum) => {
336
- event.stopPropagation()
337
-
338
- context.event$.next({
339
- type: 'series',
340
- eventName: 'mousemove',
341
- pluginName,
342
- highlightTarget: data.highlightTarget,
343
- datum: pieDatum.data,
344
- series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
345
- seriesIndex: pieDatum.data.seriesIndex,
346
- seriesLabel: pieDatum.data.seriesLabel,
347
- event,
348
- data: data.computedData,
349
- })
350
- })
351
- .on('mouseout', (event, pieDatum) => {
352
- event.stopPropagation()
353
-
354
- context.event$.next({
355
- type: 'series',
356
- eventName: 'mouseout',
357
- pluginName,
358
- highlightTarget: data.highlightTarget,
359
- datum: pieDatum.data,
360
- series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
361
- seriesIndex: pieDatum.data.seriesIndex,
362
- seriesLabel: pieDatum.data.seriesLabel,
363
- event,
364
- data: data.computedData,
365
- })
366
- })
367
- .on('click', (event, pieDatum) => {
368
- event.stopPropagation()
369
-
370
- context.event$.next({
371
- type: 'series',
372
- eventName: 'click',
373
- pluginName,
374
- highlightTarget: data.highlightTarget,
375
- datum: pieDatum.data,
376
- series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
377
- seriesIndex: pieDatum.data.seriesIndex,
378
- seriesLabel: pieDatum.data.seriesLabel,
379
- event,
380
- data: data.computedData,
381
- })
382
- })
383
- })
384
-
385
- combineLatest({
386
- pathSelection: pathSelection$,
387
- highlight: context.seriesHighlight$.pipe(
388
- map(data => data.map(d => d.id))
389
- ),
390
- fullParams: context.fullParams$,
391
- fullChartParams: context.fullChartParams$,
392
- // arc: arc$,
393
- tweenArc: tweenArc$,
394
- isTransitionMoving: isTransitionMoving$
395
- }).pipe(
396
- takeUntil(destroy$),
397
- switchMap(async d => d),
398
- filter(d => !d.isTransitionMoving) // 避免資料變更時的動畫和highlight的動畫重覆執行
399
- ).subscribe(data => {
400
- highlight({
401
- pathSelection: data.pathSelection,
402
- ids: data.highlight,
403
- fullParams: data.fullParams,
404
- fullChartParams: data.fullChartParams,
405
- tweenArc: data.tweenArc,
406
- // arcMouseover: data.arcMouseover
407
- })
408
- })
409
-
410
-
411
-
412
-
413
- return () => {
414
- destroy$.next(undefined)
415
- }
416
- }
417
-
418
- export const Rose = defineSeriesPlugin(pluginName, DEFAULT_ROSE_PARAMS)(({ selection, name, subject, observer }) => {
419
- const destroy$ = new Subject()
420
-
421
- const { seriesCenterSelection$ } = seriesCenterSelectionObservable({
422
- selection: selection,
423
- pluginName,
424
- separateSeries$: observer.separateSeries$,
425
- seriesLabels$: observer.seriesLabels$,
426
- seriesContainerPosition$: observer.seriesContainerPosition$
427
- })
428
-
429
- const unsubscribeFnArr: (() => void)[] = []
430
-
431
- seriesCenterSelection$
432
- .pipe(
433
- takeUntil(destroy$)
434
- )
435
- .subscribe(seriesCenterSelection => {
436
- // 每次重新計算時,清除之前的訂閱
437
- unsubscribeFnArr.forEach(fn => fn())
438
-
439
- seriesCenterSelection.each((d, containerIndex, g) => {
440
- const containerSelection = d3.select(g[containerIndex])
441
-
442
- const containerVisibleComputedLayoutData$ = observer.visibleComputedLayoutData$.pipe(
443
- takeUntil(destroy$),
444
- map(data => JSON.parse(JSON.stringify(data[containerIndex] ?? data[0])))
445
- )
446
-
447
- const containerPosition$ = observer.seriesContainerPosition$.pipe(
448
- takeUntil(destroy$),
449
- map(data => JSON.parse(JSON.stringify(data[containerIndex] ?? data[0])))
450
- )
451
-
452
- unsubscribeFnArr[containerIndex] = createEachRose(pluginName, {
453
- containerSelection: containerSelection,
454
- computedData$: observer.computedData$,
455
- visibleComputedData$: observer.visibleComputedData$,
456
- visibleComputedLayoutData$: observer.visibleComputedLayoutData$,
457
- containerVisibleComputedLayoutData$: containerVisibleComputedLayoutData$,
458
- SeriesDataMap$: observer.SeriesDataMap$,
459
- fullParams$: observer.fullParams$,
460
- fullChartParams$: observer.fullChartParams$,
461
- seriesHighlight$: observer.seriesHighlight$,
462
- seriesContainerPosition$: containerPosition$,
463
- event$: subject.event$,
464
- })
465
-
466
- })
467
- })
468
-
469
- return () => {
470
- destroy$.next(undefined)
471
- unsubscribeFnArr.forEach(fn => fn())
472
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ filter,
6
+ switchMap,
7
+ takeUntil,
8
+ distinctUntilChanged,
9
+ shareReplay,
10
+ Observable,
11
+ Subject,
12
+ BehaviorSubject } from 'rxjs'
13
+ import type {
14
+ ComputedDataSeries,
15
+ ComputedDatumSeries,
16
+ SeriesContainerPosition,
17
+ ChartParams,
18
+ EventSeries,
19
+ Layout } from '@orbcharts/core'
20
+ import type { D3PieDatum } from '../seriesUtils'
21
+ import type { RoseParams } from '../types'
22
+ import {
23
+ defineSeriesPlugin } from '@orbcharts/core'
24
+ import { DEFAULT_ROSE_PARAMS } from '../defaults'
25
+ // import { makePieData } from '../seriesUtils'
26
+ // import { getD3TransitionEase, makeD3Arc } from '../../utils/d3Utils'
27
+ import { getClassName } from '../../utils/orbchartsUtils'
28
+ import { seriesCenterSelectionObservable } from '../seriesObservables'
29
+
30
+ // @Q@ 暫時先寫在這裡,之後pie一起重構後再放到seriesUtils
31
+ export interface PieDatum extends D3PieDatum {
32
+ data: ComputedDatumSeries
33
+ id: string
34
+ prevValue: number // 補間動畫用的(前次資料的value)
35
+ }
36
+
37
+ const pluginName = 'Rose'
38
+
39
+ const roseInnerRadius = 0
40
+ const roseStartAngle = 0
41
+ const roseEndAngle = Math.PI * 2
42
+ const rosePadAngle = 0
43
+
44
+ function makeTweenArcFn ({ cornerRadius, outerRadius, axisWidth, maxValue, arcScaleType }: {
45
+ // interpolateRadius: (t: number) => number
46
+ outerRadius: number
47
+ cornerRadius: number
48
+ axisWidth: number
49
+ maxValue: number
50
+ arcScaleType: 'radius' | 'area'
51
+ }): (d: PieDatum) => (t: number) => string {
52
+
53
+ const outerRadiusWidth = (axisWidth / 2) * outerRadius
54
+
55
+ // const arcScale = d3.scaleLinear()
56
+ // .domain([0, maxValue])
57
+ // .range([0, outerRadiusWidth])
58
+
59
+ const exponent = arcScaleType === 'area'
60
+ ? 0.5 // 比例映射面積(0.5為取平方根)
61
+ : 1 // 比例映射半徑
62
+
63
+ const arcScale = d3.scalePow()
64
+ .domain([0, maxValue])
65
+ .range([0, outerRadiusWidth])
66
+ .exponent(exponent)
67
+
68
+ return (d: PieDatum) => {
69
+ const prevEachOuterRadius = arcScale(d.prevValue)!
70
+ const eachOuterRadius = arcScale(d.value)!
71
+
72
+ const interpolateRadius = d3.interpolate(prevEachOuterRadius, eachOuterRadius)
73
+
74
+ return (t: number) => {
75
+
76
+ const outerRadius = interpolateRadius(t)
77
+
78
+ const arc = d3.arc()
79
+ .innerRadius(0)
80
+ .outerRadius(outerRadius)
81
+ .padAngle(rosePadAngle)
82
+ .padRadius(outerRadius)
83
+ .cornerRadius(cornerRadius)
84
+
85
+ return arc(d as any)
86
+ }
87
+ }
88
+ }
89
+
90
+ // function renderPie ({ selection, data, tweenArc, transitionDuration, pathClassName }: {
91
+ // selection: d3.Selection<SVGGElement, unknown, any, unknown>
92
+ // data: PieDatum[]
93
+ // // arc: d3.Arc<any, d3.DefaultArcObject>
94
+ // tweenArc: (d: PieDatum) => (t: number) => string
95
+ // transitionDuration: number
96
+ // pathClassName: string
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
+ // pathSelection
107
+ // .transition('graphicMove')
108
+ // .duration(transitionDuration)
109
+ // .attrTween('d', tweenArc)
110
+
111
+ // return pathSelection
112
+ // }
113
+
114
+ function highlight ({ pathSelection, ids, fullParams, fullChartParams, tweenArc }: {
115
+ pathSelection: d3.Selection<SVGPathElement, PieDatum, any, any>
116
+ ids: string[]
117
+ fullParams: RoseParams
118
+ fullChartParams: ChartParams
119
+ // arc: d3.Arc<any, d3.DefaultArcObject>
120
+ tweenArc: (d: PieDatum) => (t: number) => string
121
+ }) {
122
+ pathSelection.interrupt('highlight')
123
+
124
+ if (!ids.length) {
125
+ // 取消放大
126
+ pathSelection
127
+ .transition('highlight')
128
+ .style('opacity', 1)
129
+ .attr('d', (d: PieDatum) => {
130
+ return tweenArc(d)(1)
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 arc!({
146
+ // ...d,
147
+ // startAngle: d.startAngle - 0.5,
148
+ // endAngle: d.endAngle + 0.5
149
+ // })
150
+ // })
151
+ .attr('d', (d: PieDatum) => {
152
+ return tweenArc({
153
+ ...d,
154
+ startAngle: d.startAngle - fullParams.mouseoverAngleIncrease,
155
+ endAngle: d.endAngle + fullParams.mouseoverAngleIncrease
156
+ })(1)
157
+ })
158
+ // .on('interrupt', () => {
159
+ // // this.pathSelection!.select('path').attr('d', (d) => {
160
+ // // return this.arc!(d as any)
161
+ // // })
162
+ // this.initHighlight()
163
+ // })
164
+ } else {
165
+ // 取消放大
166
+ segment
167
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
168
+ .transition('highlight')
169
+ .attr('d', (d: PieDatum) => {
170
+ return tweenArc(d)(1)
171
+ })
172
+ }
173
+ })
174
+ }
175
+
176
+ // 各別的pie
177
+ function createEachRose (pluginName: string, context: {
178
+ containerSelection: d3.Selection<SVGGElement, any, any, unknown>
179
+ computedData$: Observable<ComputedDatumSeries[][]>
180
+ visibleComputedData$: Observable<ComputedDatumSeries[][]>
181
+ visibleComputedLayoutData$: Observable<ComputedDatumSeries[][]>
182
+ containerVisibleComputedLayoutData$: Observable<ComputedDatumSeries[]>
183
+ SeriesDataMap$: Observable<Map<string, ComputedDatumSeries[]>>
184
+ fullParams$: Observable<RoseParams>
185
+ fullChartParams$: Observable<ChartParams>
186
+ seriesHighlight$: Observable<ComputedDatumSeries[]>
187
+ seriesContainerPosition$: Observable<SeriesContainerPosition>
188
+ event$: Subject<EventSeries>
189
+ }) {
190
+ const destroy$ = new Subject()
191
+
192
+ const pathClassName = getClassName(pluginName, 'path')
193
+
194
+ let lastPieData: PieDatum[] = [] // 紀錄補間動畫前次的資料
195
+
196
+ const shorterSideWith$ = context.seriesContainerPosition$.pipe(
197
+ takeUntil(destroy$),
198
+ map(d => d.width < d.height ? d.width : d.height),
199
+ distinctUntilChanged()
200
+ )
201
+
202
+ const pieData$: Observable<PieDatum[]> = combineLatest({
203
+ containerVisibleComputedLayoutData: context.containerVisibleComputedLayoutData$,
204
+ fullParams: context.fullParams$,
205
+ }).pipe(
206
+ takeUntil(destroy$),
207
+ switchMap(async (d) => d),
208
+ map(data => {
209
+ const eachAngle = roseEndAngle / data.containerVisibleComputedLayoutData.length
210
+ return data.containerVisibleComputedLayoutData.map((d, i) => {
211
+ return {
212
+ id: d.id,
213
+ data: d,
214
+ index: i,
215
+ value: d.value,
216
+ startAngle: eachAngle * i,
217
+ endAngle: eachAngle * (i + 1),
218
+ padAngle: rosePadAngle,
219
+ prevValue: (lastPieData[i] && lastPieData[i].id === d.id) ? lastPieData[i].value : 0
220
+ }
221
+ })
222
+ })
223
+ )
224
+
225
+ const highlightTarget$ = context.fullChartParams$.pipe(
226
+ takeUntil(destroy$),
227
+ map(d => d.highlightTarget),
228
+ distinctUntilChanged()
229
+ )
230
+
231
+ const maxValue$ = context.visibleComputedLayoutData$.pipe(
232
+ map(data => Math.max(...data.flat().map(d => d.value))),
233
+ distinctUntilChanged()
234
+ )
235
+
236
+ // context.visibleComputedLayoutData$.subscribe(data => {
237
+ // console.log('visibleComputedLayoutData$', data)
238
+ // })
239
+
240
+ const tweenArc$ = combineLatest({
241
+ fullParams: context.fullParams$,
242
+ axisWidth: shorterSideWith$,
243
+ maxValue: maxValue$
244
+ }).pipe(
245
+ takeUntil(destroy$),
246
+ switchMap(async d => d),
247
+ map((data) => {
248
+ return makeTweenArcFn({
249
+ cornerRadius: data.fullParams.cornerRadius,
250
+ outerRadius: data.fullParams.outerRadius,
251
+ axisWidth: data.axisWidth,
252
+ maxValue: data.maxValue,
253
+ arcScaleType: data.fullParams.arcScaleType
254
+ })
255
+ })
256
+ )
257
+
258
+ const transitionDuration$ = context.fullChartParams$.pipe(
259
+ takeUntil(destroy$),
260
+ map(d => d.transitionDuration),
261
+ distinctUntilChanged()
262
+ )
263
+
264
+ // 是否在transition中
265
+ const isTransitionMoving$ = new BehaviorSubject<boolean>(false)
266
+
267
+ const pathSelection$ = new Observable<d3.Selection<SVGPathElement, PieDatum, any, any>>(subscriber => {
268
+ combineLatest({
269
+ pieData: pieData$,
270
+ tweenArc: tweenArc$,
271
+ transitionDuration: transitionDuration$,
272
+ }).pipe(
273
+ takeUntil(destroy$),
274
+ switchMap(async d => d)
275
+ ).subscribe(data => {
276
+ const pieData = data.pieData.map((d, i) => {
277
+ d.prevValue = (lastPieData[i] && lastPieData[i].id === d.id) ? lastPieData[i].value : 0
278
+ return d
279
+ })
280
+
281
+ isTransitionMoving$.next(true)
282
+
283
+ const pathSelection: d3.Selection<SVGPathElement, PieDatum, any, any> = context.containerSelection
284
+ .selectAll<SVGPathElement, PieDatum>('path')
285
+ .data(pieData, d => d.id)
286
+ .join('path')
287
+ .classed(pathClassName, true)
288
+ .style('cursor', 'pointer')
289
+ .attr('fill', (d, i) => d.data.color)
290
+ pathSelection.interrupt('graphicMove')
291
+ pathSelection
292
+ .transition('graphicMove')
293
+ .duration(data.transitionDuration)
294
+ .attrTween('d', data.tweenArc)
295
+ .on('end', () => {
296
+ subscriber.next(pathSelection)
297
+
298
+ isTransitionMoving$.next(false)
299
+ // lastPieData = Object.assign([], data.pieData)
300
+ // console.log('lastPieData', lastPieData)
301
+ })
302
+ lastPieData = Object.assign([], pieData)
303
+
304
+ })
305
+ }).pipe(
306
+ shareReplay(1)
307
+ )
308
+
309
+ combineLatest({
310
+ pathSelection: pathSelection$,
311
+ SeriesDataMap: context.SeriesDataMap$,
312
+ computedData: context.computedData$,
313
+ highlightTarget: highlightTarget$
314
+ }).pipe(
315
+ takeUntil(destroy$),
316
+ switchMap(async d => d)
317
+ ).subscribe(data => {
318
+ data.pathSelection
319
+ .on('mouseover', (event, pieDatum) => {
320
+ event.stopPropagation()
321
+
322
+ context.event$.next({
323
+ type: 'series',
324
+ eventName: 'mouseover',
325
+ pluginName,
326
+ highlightTarget: data.highlightTarget,
327
+ datum: pieDatum.data,
328
+ series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
329
+ seriesIndex: pieDatum.data.seriesIndex,
330
+ seriesLabel: pieDatum.data.seriesLabel,
331
+ event,
332
+ data: data.computedData
333
+ })
334
+ })
335
+ .on('mousemove', (event, pieDatum) => {
336
+ event.stopPropagation()
337
+
338
+ context.event$.next({
339
+ type: 'series',
340
+ eventName: 'mousemove',
341
+ pluginName,
342
+ highlightTarget: data.highlightTarget,
343
+ datum: pieDatum.data,
344
+ series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
345
+ seriesIndex: pieDatum.data.seriesIndex,
346
+ seriesLabel: pieDatum.data.seriesLabel,
347
+ event,
348
+ data: data.computedData,
349
+ })
350
+ })
351
+ .on('mouseout', (event, pieDatum) => {
352
+ event.stopPropagation()
353
+
354
+ context.event$.next({
355
+ type: 'series',
356
+ eventName: 'mouseout',
357
+ pluginName,
358
+ highlightTarget: data.highlightTarget,
359
+ datum: pieDatum.data,
360
+ series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
361
+ seriesIndex: pieDatum.data.seriesIndex,
362
+ seriesLabel: pieDatum.data.seriesLabel,
363
+ event,
364
+ data: data.computedData,
365
+ })
366
+ })
367
+ .on('click', (event, pieDatum) => {
368
+ event.stopPropagation()
369
+
370
+ context.event$.next({
371
+ type: 'series',
372
+ eventName: 'click',
373
+ pluginName,
374
+ highlightTarget: data.highlightTarget,
375
+ datum: pieDatum.data,
376
+ series: data.SeriesDataMap.get(pieDatum.data.seriesLabel)!,
377
+ seriesIndex: pieDatum.data.seriesIndex,
378
+ seriesLabel: pieDatum.data.seriesLabel,
379
+ event,
380
+ data: data.computedData,
381
+ })
382
+ })
383
+ })
384
+
385
+ combineLatest({
386
+ pathSelection: pathSelection$,
387
+ highlight: context.seriesHighlight$.pipe(
388
+ map(data => data.map(d => d.id))
389
+ ),
390
+ fullParams: context.fullParams$,
391
+ fullChartParams: context.fullChartParams$,
392
+ // arc: arc$,
393
+ tweenArc: tweenArc$,
394
+ isTransitionMoving: isTransitionMoving$
395
+ }).pipe(
396
+ takeUntil(destroy$),
397
+ switchMap(async d => d),
398
+ filter(d => !d.isTransitionMoving) // 避免資料變更時的動畫和highlight的動畫重覆執行
399
+ ).subscribe(data => {
400
+ highlight({
401
+ pathSelection: data.pathSelection,
402
+ ids: data.highlight,
403
+ fullParams: data.fullParams,
404
+ fullChartParams: data.fullChartParams,
405
+ tweenArc: data.tweenArc,
406
+ // arcMouseover: data.arcMouseover
407
+ })
408
+ })
409
+
410
+
411
+
412
+
413
+ return () => {
414
+ destroy$.next(undefined)
415
+ }
416
+ }
417
+
418
+ export const Rose = defineSeriesPlugin(pluginName, DEFAULT_ROSE_PARAMS)(({ selection, name, subject, observer }) => {
419
+ const destroy$ = new Subject()
420
+
421
+ const { seriesCenterSelection$ } = seriesCenterSelectionObservable({
422
+ selection: selection,
423
+ pluginName,
424
+ separateSeries$: observer.separateSeries$,
425
+ seriesLabels$: observer.seriesLabels$,
426
+ seriesContainerPosition$: observer.seriesContainerPosition$
427
+ })
428
+
429
+ const unsubscribeFnArr: (() => void)[] = []
430
+
431
+ seriesCenterSelection$
432
+ .pipe(
433
+ takeUntil(destroy$)
434
+ )
435
+ .subscribe(seriesCenterSelection => {
436
+ // 每次重新計算時,清除之前的訂閱
437
+ unsubscribeFnArr.forEach(fn => fn())
438
+
439
+ seriesCenterSelection.each((d, containerIndex, g) => {
440
+ const containerSelection = d3.select(g[containerIndex])
441
+
442
+ const containerVisibleComputedLayoutData$ = observer.visibleComputedLayoutData$.pipe(
443
+ takeUntil(destroy$),
444
+ map(data => JSON.parse(JSON.stringify(data[containerIndex] ?? data[0])))
445
+ )
446
+
447
+ const containerPosition$ = observer.seriesContainerPosition$.pipe(
448
+ takeUntil(destroy$),
449
+ map(data => JSON.parse(JSON.stringify(data[containerIndex] ?? data[0])))
450
+ )
451
+
452
+ unsubscribeFnArr[containerIndex] = createEachRose(pluginName, {
453
+ containerSelection: containerSelection,
454
+ computedData$: observer.computedData$,
455
+ visibleComputedData$: observer.visibleComputedData$,
456
+ visibleComputedLayoutData$: observer.visibleComputedLayoutData$,
457
+ containerVisibleComputedLayoutData$: containerVisibleComputedLayoutData$,
458
+ SeriesDataMap$: observer.SeriesDataMap$,
459
+ fullParams$: observer.fullParams$,
460
+ fullChartParams$: observer.fullChartParams$,
461
+ seriesHighlight$: observer.seriesHighlight$,
462
+ seriesContainerPosition$: containerPosition$,
463
+ event$: subject.event$,
464
+ })
465
+
466
+ })
467
+ })
468
+
469
+ return () => {
470
+ destroy$.next(undefined)
471
+ unsubscribeFnArr.forEach(fn => fn())
472
+ }
473
473
  })