@orbcharts/plugins-basic 3.0.0-alpha.49 → 3.0.0-alpha.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +6325 -6272
  3. package/dist/orbcharts-plugins-basic.umd.js +8 -8
  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/multiGrid/defaults.d.ts +2 -1
  9. package/dist/src/multiGrid/index.d.ts +2 -0
  10. package/dist/src/multiGrid/multiGridObservables.d.ts +1 -1
  11. package/dist/src/multiGrid/plugins/MultiValueStackAxis.d.ts +1 -0
  12. package/dist/src/multiGrid/plugins/OverlappingValueStackAxes.d.ts +1 -0
  13. package/dist/src/multiGrid/types.d.ts +3 -0
  14. package/dist/src/series/types.d.ts +2 -3
  15. package/package.json +42 -42
  16. package/src/base/BaseBarStack.ts +778 -778
  17. package/src/base/BaseBars.ts +764 -764
  18. package/src/base/BaseBarsTriangle.ts +672 -672
  19. package/src/base/BaseDots.ts +513 -502
  20. package/src/base/BaseGroupAxis.ts +562 -496
  21. package/src/base/BaseLegend.ts +641 -641
  22. package/src/base/BaseLineAreas.ts +625 -625
  23. package/src/base/BaseLines.ts +699 -699
  24. package/src/base/BaseValueAxis.ts +478 -478
  25. package/src/base/types.ts +2 -2
  26. package/src/grid/defaults.ts +125 -121
  27. package/src/grid/gridObservables.ts +248 -247
  28. package/src/grid/index.ts +15 -15
  29. package/src/grid/plugins/BarStack.ts +43 -50
  30. package/src/grid/plugins/Bars.ts +44 -51
  31. package/src/grid/plugins/{BarsDiverging.ts → BarsPN.ts} +41 -41
  32. package/src/grid/plugins/BarsTriangle.ts +42 -50
  33. package/src/grid/plugins/Dots.ts +37 -37
  34. package/src/grid/plugins/GridLegend.ts +59 -59
  35. package/src/grid/plugins/GroupAux.ts +645 -645
  36. package/src/grid/plugins/GroupAxis.ts +35 -42
  37. package/src/grid/plugins/LineAreas.ts +39 -39
  38. package/src/grid/plugins/Lines.ts +38 -38
  39. package/src/grid/plugins/ScalingArea.ts +173 -173
  40. package/src/grid/plugins/ValueAxis.ts +36 -43
  41. package/src/grid/plugins/ValueStackAxis.ts +38 -79
  42. package/src/grid/types.ts +122 -120
  43. package/src/index.ts +9 -9
  44. package/src/multiGrid/defaults.ts +152 -147
  45. package/src/multiGrid/index.ts +14 -12
  46. package/src/multiGrid/multiGridObservables.ts +44 -43
  47. package/src/multiGrid/plugins/MultiBarStack.ts +78 -78
  48. package/src/multiGrid/plugins/MultiBars.ts +77 -77
  49. package/src/multiGrid/plugins/MultiBarsTriangle.ts +77 -77
  50. package/src/multiGrid/plugins/MultiDots.ts +65 -65
  51. package/src/multiGrid/plugins/MultiGridLegend.ts +89 -89
  52. package/src/multiGrid/plugins/MultiGroupAxis.ts +69 -69
  53. package/src/multiGrid/plugins/MultiLineAreas.ts +67 -67
  54. package/src/multiGrid/plugins/MultiLines.ts +66 -66
  55. package/src/multiGrid/plugins/MultiValueAxis.ts +69 -69
  56. package/src/multiGrid/plugins/MultiValueStackAxis.ts +69 -0
  57. package/src/multiGrid/plugins/OverlappingValueAxes.ts +166 -173
  58. package/src/multiGrid/plugins/OverlappingValueStackAxes.ts +167 -0
  59. package/src/multiGrid/types.ts +71 -67
  60. package/src/noneData/defaults.ts +64 -64
  61. package/src/noneData/index.ts +3 -3
  62. package/src/noneData/plugins/Container.ts +10 -10
  63. package/src/noneData/plugins/Tooltip.ts +310 -310
  64. package/src/noneData/types.ts +26 -26
  65. package/src/series/defaults.ts +126 -126
  66. package/src/series/index.ts +9 -9
  67. package/src/series/plugins/Bubbles.ts +545 -602
  68. package/src/series/plugins/Pie.ts +576 -576
  69. package/src/series/plugins/PieEventTexts.ts +262 -262
  70. package/src/series/plugins/PieLabels.ts +304 -304
  71. package/src/series/plugins/Rose.ts +472 -472
  72. package/src/series/plugins/RoseLabels.ts +362 -362
  73. package/src/series/plugins/SeriesLegend.ts +59 -59
  74. package/src/series/seriesObservables.ts +145 -145
  75. package/src/series/seriesUtils.ts +51 -51
  76. package/src/series/types.ts +83 -83
  77. package/src/tree/defaults.ts +22 -22
  78. package/src/tree/index.ts +3 -3
  79. package/src/tree/plugins/TreeLegend.ts +59 -59
  80. package/src/tree/plugins/TreeMap.ts +305 -305
  81. package/src/tree/types.ts +23 -23
  82. package/src/utils/commonUtils.ts +21 -21
  83. package/src/utils/d3Graphics.ts +124 -124
  84. package/src/utils/d3Utils.ts +73 -73
  85. package/src/utils/observables.ts +14 -14
  86. package/src/utils/orbchartsUtils.ts +100 -100
  87. package/tsconfig.dev.json +16 -16
  88. package/tsconfig.json +13 -13
  89. package/tsconfig.prod.json +13 -13
  90. package/vite.config.js +49 -49
  91. package/dist/src/grid/plugins/BarsDiverging.d.ts +0 -1
@@ -1,503 +1,514 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- map,
5
- switchMap,
6
- takeUntil,
7
- distinctUntilChanged,
8
- Observable,
9
- Subject } from 'rxjs'
10
- import type { BasePluginFn } from './types'
11
- import type {
12
- ComputedDatumGrid,
13
- ComputedDataGrid,
14
- ComputedLayoutDataGrid,
15
- EventGrid,
16
- ChartParams,
17
- GridContainerPosition,
18
- Layout,
19
- TransformData,
20
- ColorType } from '@orbcharts/core'
21
- import { getDatumColor, getClassName, getUniID } from '../utils/orbchartsUtils'
22
- import { gridSelectionsObservable } from '../grid/gridObservables'
23
-
24
- export interface BaseDotsParams {
25
- radius: number
26
- fillColorType: ColorType
27
- strokeColorType: ColorType
28
- strokeWidth: number
29
- onlyShowHighlighted: boolean
30
- }
31
-
32
- interface BaseDotsContext {
33
- selection: d3.Selection<any, unknown, any, unknown>
34
- computedData$: Observable<ComputedDataGrid>
35
- computedLayoutData$: Observable<ComputedLayoutDataGrid>
36
- visibleComputedData$: Observable<ComputedDatumGrid[][]>
37
- visibleComputedLayoutData$: Observable<ComputedLayoutDataGrid>
38
- seriesLabels$: Observable<string[]>
39
- SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
40
- GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
41
- fullParams$: Observable<BaseDotsParams>
42
- fullChartParams$: Observable<ChartParams>
43
- gridAxesTransform$: Observable<TransformData>
44
- gridGraphicTransform$: Observable<TransformData>
45
- gridGraphicReverseScale$: Observable<[number, number][]>
46
- gridAxesSize$: Observable<{
47
- width: number;
48
- height: number;
49
- }>
50
- gridHighlight$: Observable<ComputedDatumGrid[]>
51
- gridContainerPosition$: Observable<GridContainerPosition[]>
52
- event$: Subject<EventGrid>
53
- }
54
-
55
-
56
- type ClipPathDatum = {
57
- id: string;
58
- // x: number;
59
- // y: number;
60
- width: number;
61
- height: number;
62
- }
63
-
64
- // const pluginName = 'Dots'
65
- // const circleGClassName = getClassName(pluginName, 'circleG')
66
- // const circleClassName = getClassName(pluginName, 'circle')
67
-
68
- function renderDots ({ graphicGSelection, circleGClassName, circleClassName, visibleComputedLayoutData, fullParams, fullChartParams, graphicReverseScale }: {
69
- graphicGSelection: d3.Selection<SVGGElement, any, any, any>
70
- circleGClassName: string
71
- circleClassName: string
72
- visibleComputedLayoutData: ComputedLayoutDataGrid
73
- fullParams: BaseDotsParams
74
- fullChartParams: ChartParams
75
- graphicReverseScale: [number, number][]
76
- }) {
77
- const createEnterDuration = (enter: d3.Selection<d3.EnterElement, ComputedDatumGrid, SVGGElement, any>) => {
78
- const enterSize = enter.size()
79
- const eachDuration = fullChartParams.transitionDuration / enterSize
80
- return eachDuration
81
- }
82
- // enterDuration
83
- let enterDuration = 0
84
-
85
- graphicGSelection
86
- .each((seriesData, seriesIndex, g) => {
87
- d3.select(g[seriesIndex])
88
- .selectAll<SVGGElement, ComputedDatumGrid>('g')
89
- .data(visibleComputedLayoutData[seriesIndex], d => d.id)
90
- .join(
91
- enter => {
92
- // enterDuration
93
- enterDuration = createEnterDuration(enter)
94
-
95
- return enter
96
- .append('g')
97
- .classed(circleGClassName, true)
98
- },
99
- update => update,
100
- exit => exit.remove()
101
- )
102
- .attr('transform', d => `translate(${d.axisX}, ${d.axisY})`)
103
- .each((d, i, g) => {
104
- const circle = d3.select(g[i])
105
- .selectAll('circle')
106
- .data([d])
107
- .join(
108
- enter => {
109
- return enter
110
- .append('circle')
111
- .style('cursor', 'pointer')
112
- .style('vector-effect', 'non-scaling-stroke')
113
- .classed(circleClassName, true)
114
- .attr('opacity', 0)
115
- .transition()
116
- .delay((_d, _i) => {
117
- return i * enterDuration
118
- })
119
- .attr('opacity', 1)
120
- },
121
- update => {
122
- return update
123
- .transition()
124
- .duration(50)
125
- // .attr('cx', d => d.axisX)
126
- // .attr('cy', d => d.axisY)
127
- .attr('opacity', 1)
128
- },
129
- exit => exit.remove()
130
- )
131
- .attr('r', fullParams.radius)
132
- .attr('fill', (d, i) => getDatumColor({ datum: d, colorType: fullParams.fillColorType, fullChartParams }))
133
- .attr('stroke', (d, i) => getDatumColor({ datum: d, colorType: fullParams.strokeColorType, fullChartParams }))
134
- .attr('stroke-width', fullParams.strokeWidth)
135
- .attr('transform', `scale(${graphicReverseScale[seriesIndex][0] ?? 1}, ${graphicReverseScale[seriesIndex][1] ?? 1})`)
136
- })
137
- })
138
-
139
- // const dots = graphicGSelection
140
- // .selectAll<SVGGElement, ComputedDatumGrid>('g')
141
- // .data(data, d => d.id)
142
- // .join(
143
- // enter => {
144
- // // enterDuration
145
- // enterDuration = createEnterDuration(enter)
146
-
147
- // return enter
148
- // .append('g')
149
- // .classed(circleGClassName, true)
150
- // },
151
- // update => update,
152
- // exit => exit.remove()
153
- // )
154
- // .attr('transform', d => `translate(${d.axisX}, ${d.axisY})`)
155
- // .each((d, i, g) => {
156
- // const circle = d3.select(g[i])
157
- // .selectAll('circle')
158
- // .data([d])
159
- // .join(
160
- // enter => {
161
- // return enter
162
- // .append('circle')
163
- // .style('cursor', 'pointer')
164
- // .style('vector-effect', 'non-scaling-stroke')
165
- // .classed(circleClassName, true)
166
- // .attr('opacity', 0)
167
- // .transition()
168
- // .delay((_d, _i) => {
169
- // return i * enterDuration
170
- // })
171
- // .attr('opacity', 1)
172
- // },
173
- // update => {
174
- // return update
175
- // .transition()
176
- // .duration(50)
177
- // // .attr('cx', d => d.axisX)
178
- // // .attr('cy', d => d.axisY)
179
- // .attr('opacity', 1)
180
- // },
181
- // exit => exit.remove()
182
- // )
183
- // .attr('r', fullParams.radius)
184
- // .attr('fill', (d, i) => getDatumColor({ datum: d, colorType: fullParams.fillColorType, fullChartParams }))
185
- // .attr('stroke', (d, i) => getDatumColor({ datum: d, colorType: fullParams.strokeColorType, fullChartParams }))
186
- // .attr('stroke-width', fullParams.strokeWidth)
187
- // .attr('transform', `scale(${graphicReverseScale[0]}, ${graphicReverseScale[1]})`)
188
- // })
189
-
190
- const graphicCircleSelection: d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown> = graphicGSelection.selectAll(`circle.${circleClassName}`)
191
-
192
- return graphicCircleSelection
193
- }
194
-
195
-
196
- function highlightDots ({ selection, ids, onlyShowHighlighted, fullChartParams }: {
197
- selection: d3.Selection<SVGGElement, ComputedDatumGrid, any, any>
198
- ids: string[]
199
- onlyShowHighlighted: boolean
200
- fullChartParams: ChartParams
201
- }) {
202
- selection.interrupt('highlight')
203
- if (!ids.length) {
204
- // remove highlight
205
- selection
206
- .transition('highlight')
207
- .duration(200)
208
- .style('opacity', onlyShowHighlighted === true ? 0 : 1)
209
- return
210
- }
211
-
212
- selection
213
- .each((d, i, n) => {
214
- if (ids.includes(d.id)) {
215
- d3.select(n[i])
216
- .style('opacity', 1)
217
- .transition('highlight')
218
- .duration(200)
219
- } else {
220
- d3.select(n[i])
221
- .style('opacity', onlyShowHighlighted === true ? 0 : fullChartParams.styles.unhighlightedOpacity)
222
- .transition('highlight')
223
- .duration(200)
224
- }
225
- })
226
- }
227
-
228
- function renderClipPath ({ defsSelection, clipPathData }: {
229
- defsSelection: d3.Selection<SVGDefsElement, any, any, any>
230
- clipPathData: ClipPathDatum[]
231
- }) {
232
- const clipPath = defsSelection
233
- .selectAll<SVGClipPathElement, Layout>('clipPath')
234
- .data(clipPathData)
235
- .join(
236
- enter => {
237
- return enter
238
- .append('clipPath')
239
- },
240
- update => update,
241
- exit => exit.remove()
242
- )
243
- .attr('id', d => d.id)
244
- .each((d, i, g) => {
245
- const rect = d3.select(g[i])
246
- .selectAll<SVGRectElement, typeof d>('rect')
247
- .data([d])
248
- .join('rect')
249
- .attr('x', 0)
250
- .attr('y', 0)
251
- .attr('width', _d => _d.width)
252
- .attr('height', _d => _d.height)
253
- })
254
-
255
- }
256
-
257
-
258
-
259
- export const createBaseDots: BasePluginFn<BaseDotsContext> = (pluginName: string, {
260
- selection,
261
- computedData$,
262
- computedLayoutData$,
263
- visibleComputedData$,
264
- visibleComputedLayoutData$,
265
- seriesLabels$,
266
- SeriesDataMap$,
267
- GroupDataMap$,
268
- fullParams$,
269
- fullChartParams$,
270
- gridAxesTransform$,
271
- gridGraphicTransform$,
272
- gridGraphicReverseScale$,
273
- gridAxesSize$,
274
- gridHighlight$,
275
- gridContainerPosition$,
276
- event$
277
- }) => {
278
-
279
- const destroy$ = new Subject()
280
-
281
- const clipPathID = getUniID(pluginName, 'clipPath-box')
282
- const circleGClassName = getClassName(pluginName, 'circleG')
283
- const circleClassName = getClassName(pluginName, 'circle')
284
-
285
- // const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection
286
- // .append('g')
287
- // .attr('clip-path', `url(#${clipPathID})`)
288
- // const defsSelection: d3.Selection<SVGDefsElement, any, any, any> = axisSelection.append('defs')
289
- // const dataAreaSelection: d3.Selection<SVGGElement, any, any, any> = axisSelection.append('g')
290
- // const graphicSelection$: Subject<d3.Selection<SVGGElement, ComputedDatumGrid, any, any>> = new Subject()
291
-
292
- const {
293
- seriesSelection$,
294
- axesSelection$,
295
- defsSelection$,
296
- graphicGSelection$
297
- } = gridSelectionsObservable({
298
- selection,
299
- pluginName,
300
- clipPathID,
301
- seriesLabels$,
302
- gridContainerPosition$,
303
- gridAxesTransform$,
304
- gridGraphicTransform$
305
- })
306
-
307
- const graphicReverseScale$: Observable<[number, number][]> = combineLatest({
308
- // gridGraphicTransform: gridGraphicTransform$,
309
- // gridContainerPosition: gridContainerPosition$,
310
- // gridAxesTransform: gridAxesTransform$
311
- computedData: computedData$,
312
- gridGraphicReverseScale: gridGraphicReverseScale$
313
- }).pipe(
314
- takeUntil(destroy$),
315
- switchMap(async data => data),
316
- map(data => {
317
- return data.computedData.map((series, seriesIndex) => {
318
- return data.gridGraphicReverseScale[seriesIndex]
319
- })
320
- })
321
- )
322
-
323
- const clipPathSubscription = combineLatest({
324
- defsSelection: defsSelection$,
325
- gridAxesSize: gridAxesSize$,
326
- }).pipe(
327
- takeUntil(destroy$),
328
- switchMap(async (d) => d),
329
- ).subscribe(data => {
330
- // 外層的遮罩
331
- const clipPathData = [{
332
- id: clipPathID,
333
- width: data.gridAxesSize.width,
334
- height: data.gridAxesSize.height
335
- }]
336
- renderClipPath({
337
- defsSelection: data.defsSelection,
338
- clipPathData,
339
- })
340
- })
341
-
342
- const highlightTarget$ = fullChartParams$.pipe(
343
- takeUntil(destroy$),
344
- map(d => d.highlightTarget),
345
- distinctUntilChanged()
346
- )
347
-
348
- const graphicSelection$ = combineLatest({
349
- graphicGSelection: graphicGSelection$,
350
- visibleComputedLayoutData: visibleComputedLayoutData$,
351
- graphicReverseScale: graphicReverseScale$,
352
- fullChartParams: fullChartParams$,
353
- fullParams: fullParams$,
354
- }).pipe(
355
- takeUntil(destroy$),
356
- switchMap(async (d) => d),
357
- map(data => {
358
- return renderDots({
359
- graphicGSelection: data.graphicGSelection,
360
- circleGClassName,
361
- circleClassName,
362
- visibleComputedLayoutData: data.visibleComputedLayoutData,
363
- fullParams: data.fullParams,
364
- fullChartParams: data.fullChartParams,
365
- graphicReverseScale: data.graphicReverseScale
366
- })
367
- })
368
- )
369
-
370
- combineLatest({
371
- graphicSelection: graphicSelection$,
372
- computedData: computedData$,
373
- SeriesDataMap: SeriesDataMap$,
374
- GroupDataMap: GroupDataMap$,
375
- highlightTarget: highlightTarget$
376
- }).pipe(
377
- takeUntil(destroy$),
378
- switchMap(async (d) => d),
379
- ).subscribe(data => {
380
-
381
- data.graphicSelection
382
- .on('mouseover', (event, datum) => {
383
- event.stopPropagation()
384
-
385
- event$.next({
386
- type: 'grid',
387
- eventName: 'mouseover',
388
- pluginName,
389
- highlightTarget: data.highlightTarget,
390
- datum,
391
- gridIndex: datum.gridIndex,
392
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
393
- seriesIndex: datum.seriesIndex,
394
- seriesLabel: datum.seriesLabel,
395
- groups: data.GroupDataMap.get(datum.groupLabel)!,
396
- groupIndex: datum.groupIndex,
397
- groupLabel: datum.groupLabel,
398
- event,
399
- data: data.computedData
400
- })
401
- })
402
- .on('mousemove', (event, datum) => {
403
- event.stopPropagation()
404
-
405
- event$.next({
406
- type: 'grid',
407
- eventName: 'mousemove',
408
- pluginName,
409
- highlightTarget: data.highlightTarget,
410
- data: data.computedData,
411
- datum,
412
- gridIndex: datum.gridIndex,
413
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
414
- seriesIndex: datum.seriesIndex,
415
- seriesLabel: datum.seriesLabel,
416
- groups: data.GroupDataMap.get(datum.groupLabel)!,
417
- groupIndex: datum.groupIndex,
418
- groupLabel: datum.groupLabel,
419
- event
420
- })
421
- })
422
- .on('mouseout', (event, datum) => {
423
- event.stopPropagation()
424
-
425
- event$.next({
426
- type: 'grid',
427
- eventName: 'mouseout',
428
- pluginName,
429
- highlightTarget: data.highlightTarget,
430
- datum,
431
- gridIndex: datum.gridIndex,
432
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
433
- seriesIndex: datum.seriesIndex,
434
- seriesLabel: datum.seriesLabel,
435
- groups: data.GroupDataMap.get(datum.groupLabel)!,
436
- groupIndex: datum.groupIndex,
437
- groupLabel: datum.groupLabel,
438
- event,
439
- data: data.computedData
440
- })
441
- })
442
- .on('click', (event, datum) => {
443
- event.stopPropagation()
444
-
445
- event$.next({
446
- type: 'grid',
447
- eventName: 'click',
448
- pluginName,
449
- highlightTarget: data.highlightTarget,
450
- datum,
451
- gridIndex: datum.gridIndex,
452
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
453
- seriesIndex: datum.seriesIndex,
454
- seriesLabel: datum.seriesLabel,
455
- groups: data.GroupDataMap.get(datum.groupLabel)!,
456
- groupIndex: datum.groupIndex,
457
- groupLabel: datum.groupLabel,
458
- event,
459
- data: data.computedData
460
- })
461
- })
462
-
463
- })
464
-
465
- // const datumList$ = computedData$.pipe(
466
- // takeUntil(destroy$),
467
- // map(d => d.flat())
468
- // )
469
- // const highlight$ = highlightObservable({ datumList$, fullChartParams$, event$: store.event$ })
470
- // const highlightSubscription = gridHighlight$.subscribe()
471
- const onlyShowHighlighted$ = fullParams$.pipe(
472
- takeUntil(destroy$),
473
- map(d => d.onlyShowHighlighted),
474
- distinctUntilChanged()
475
- )
476
-
477
- fullChartParams$.pipe(
478
- takeUntil(destroy$),
479
- switchMap(d => combineLatest({
480
- graphicSelection: graphicSelection$,
481
- highlight: gridHighlight$.pipe(
482
- map(data => data.map(d => d.id))
483
- ),
484
- onlyShowHighlighted: onlyShowHighlighted$,
485
- fullChartParams: fullChartParams$
486
- }).pipe(
487
- takeUntil(destroy$),
488
- switchMap(async d => d)
489
- ))
490
- ).subscribe(data => {
491
- highlightDots({
492
- selection: data.graphicSelection,
493
- ids: data.highlight,
494
- onlyShowHighlighted: data.onlyShowHighlighted,
495
- fullChartParams: data.fullChartParams
496
- })
497
- })
498
-
499
- return () => {
500
- destroy$.next(undefined)
501
- // highlightSubscription.unsubscribe()
502
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ switchMap,
6
+ takeUntil,
7
+ distinctUntilChanged,
8
+ Observable,
9
+ Subject } from 'rxjs'
10
+ import type { BasePluginFn } from './types'
11
+ import type {
12
+ ComputedDatumGrid,
13
+ ComputedDataGrid,
14
+ ComputedLayoutDataGrid,
15
+ EventGrid,
16
+ ChartParams,
17
+ GridContainerPosition,
18
+ Layout,
19
+ TransformData,
20
+ ColorType } from '@orbcharts/core'
21
+ import { getDatumColor, getClassName, getUniID } from '../utils/orbchartsUtils'
22
+ import { gridSelectionsObservable } from '../grid/gridObservables'
23
+
24
+ export interface BaseDotsParams {
25
+ radius: number
26
+ fillColorType: ColorType
27
+ strokeColorType: ColorType
28
+ strokeWidth: number
29
+ // strokeWidthWhileHighlight: number
30
+ onlyShowHighlighted: boolean
31
+ }
32
+
33
+ interface BaseDotsContext {
34
+ selection: d3.Selection<any, unknown, any, unknown>
35
+ computedData$: Observable<ComputedDataGrid>
36
+ computedLayoutData$: Observable<ComputedLayoutDataGrid>
37
+ visibleComputedData$: Observable<ComputedDatumGrid[][]>
38
+ visibleComputedLayoutData$: Observable<ComputedLayoutDataGrid>
39
+ seriesLabels$: Observable<string[]>
40
+ SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
41
+ GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
42
+ fullParams$: Observable<BaseDotsParams>
43
+ fullChartParams$: Observable<ChartParams>
44
+ gridAxesTransform$: Observable<TransformData>
45
+ gridGraphicTransform$: Observable<TransformData>
46
+ gridGraphicReverseScale$: Observable<[number, number][]>
47
+ gridAxesSize$: Observable<{
48
+ width: number;
49
+ height: number;
50
+ }>
51
+ gridHighlight$: Observable<ComputedDatumGrid[]>
52
+ gridContainerPosition$: Observable<GridContainerPosition[]>
53
+ event$: Subject<EventGrid>
54
+ }
55
+
56
+
57
+ type ClipPathDatum = {
58
+ id: string;
59
+ // x: number;
60
+ // y: number;
61
+ width: number;
62
+ height: number;
63
+ }
64
+
65
+ // const pluginName = 'Dots'
66
+ // const circleGClassName = getClassName(pluginName, 'circleG')
67
+ // const circleClassName = getClassName(pluginName, 'circle')
68
+
69
+ function renderDots ({ graphicGSelection, circleGClassName, circleClassName, visibleComputedLayoutData, fullParams, fullChartParams, graphicReverseScale }: {
70
+ graphicGSelection: d3.Selection<SVGGElement, any, any, any>
71
+ circleGClassName: string
72
+ circleClassName: string
73
+ visibleComputedLayoutData: ComputedLayoutDataGrid
74
+ fullParams: BaseDotsParams
75
+ fullChartParams: ChartParams
76
+ graphicReverseScale: [number, number][]
77
+ }) {
78
+ const createEnterDuration = (enter: d3.Selection<d3.EnterElement, ComputedDatumGrid, SVGGElement, any>) => {
79
+ const enterSize = enter.size()
80
+ const eachDuration = fullChartParams.transitionDuration / enterSize
81
+ return eachDuration
82
+ }
83
+ // enterDuration
84
+ let enterDuration = 0
85
+
86
+ graphicGSelection
87
+ .each((seriesData, seriesIndex, g) => {
88
+ d3.select(g[seriesIndex])
89
+ .selectAll<SVGGElement, ComputedDatumGrid>('g')
90
+ .data(visibleComputedLayoutData[seriesIndex], d => d.id)
91
+ .join(
92
+ enter => {
93
+ // enterDuration
94
+ enterDuration = createEnterDuration(enter)
95
+
96
+ return enter
97
+ .append('g')
98
+ .classed(circleGClassName, true)
99
+ },
100
+ update => update,
101
+ exit => exit.remove()
102
+ )
103
+ .attr('transform', d => `translate(${d.axisX}, ${d.axisY})`)
104
+ .each((d, i, g) => {
105
+ const circle = d3.select(g[i])
106
+ .selectAll('circle')
107
+ .data([d])
108
+ .join(
109
+ enter => {
110
+ return enter
111
+ .append('circle')
112
+ .style('cursor', 'pointer')
113
+ .style('vector-effect', 'non-scaling-stroke')
114
+ .classed(circleClassName, true)
115
+ .attr('opacity', 0)
116
+ .transition()
117
+ .delay((_d, _i) => {
118
+ return i * enterDuration
119
+ })
120
+ .attr('opacity', 1)
121
+ },
122
+ update => {
123
+ return update
124
+ .transition()
125
+ .duration(50)
126
+ // .attr('cx', d => d.axisX)
127
+ // .attr('cy', d => d.axisY)
128
+ .attr('opacity', 1)
129
+ },
130
+ exit => exit.remove()
131
+ )
132
+ .attr('r', fullParams.radius)
133
+ .attr('fill', (d, i) => getDatumColor({ datum: d, colorType: fullParams.fillColorType, fullChartParams }))
134
+ .attr('stroke', (d, i) => getDatumColor({ datum: d, colorType: fullParams.strokeColorType, fullChartParams }))
135
+ .attr('stroke-width', fullParams.strokeWidth)
136
+ .attr('transform', `scale(${graphicReverseScale[seriesIndex][0] ?? 1}, ${graphicReverseScale[seriesIndex][1] ?? 1})`)
137
+ })
138
+ })
139
+
140
+ // const dots = graphicGSelection
141
+ // .selectAll<SVGGElement, ComputedDatumGrid>('g')
142
+ // .data(data, d => d.id)
143
+ // .join(
144
+ // enter => {
145
+ // // enterDuration
146
+ // enterDuration = createEnterDuration(enter)
147
+
148
+ // return enter
149
+ // .append('g')
150
+ // .classed(circleGClassName, true)
151
+ // },
152
+ // update => update,
153
+ // exit => exit.remove()
154
+ // )
155
+ // .attr('transform', d => `translate(${d.axisX}, ${d.axisY})`)
156
+ // .each((d, i, g) => {
157
+ // const circle = d3.select(g[i])
158
+ // .selectAll('circle')
159
+ // .data([d])
160
+ // .join(
161
+ // enter => {
162
+ // return enter
163
+ // .append('circle')
164
+ // .style('cursor', 'pointer')
165
+ // .style('vector-effect', 'non-scaling-stroke')
166
+ // .classed(circleClassName, true)
167
+ // .attr('opacity', 0)
168
+ // .transition()
169
+ // .delay((_d, _i) => {
170
+ // return i * enterDuration
171
+ // })
172
+ // .attr('opacity', 1)
173
+ // },
174
+ // update => {
175
+ // return update
176
+ // .transition()
177
+ // .duration(50)
178
+ // // .attr('cx', d => d.axisX)
179
+ // // .attr('cy', d => d.axisY)
180
+ // .attr('opacity', 1)
181
+ // },
182
+ // exit => exit.remove()
183
+ // )
184
+ // .attr('r', fullParams.radius)
185
+ // .attr('fill', (d, i) => getDatumColor({ datum: d, colorType: fullParams.fillColorType, fullChartParams }))
186
+ // .attr('stroke', (d, i) => getDatumColor({ datum: d, colorType: fullParams.strokeColorType, fullChartParams }))
187
+ // .attr('stroke-width', fullParams.strokeWidth)
188
+ // .attr('transform', `scale(${graphicReverseScale[0]}, ${graphicReverseScale[1]})`)
189
+ // })
190
+
191
+ const graphicCircleSelection: d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown> = graphicGSelection.selectAll(`circle.${circleClassName}`)
192
+
193
+ return graphicCircleSelection
194
+ }
195
+
196
+
197
+ function highlightDots ({ selection, ids, onlyShowHighlighted, fullChartParams }: {
198
+ selection: d3.Selection<SVGGElement, ComputedDatumGrid, any, any>
199
+ ids: string[]
200
+ onlyShowHighlighted: boolean
201
+ // fullParams: BaseDotsParams
202
+ fullChartParams: ChartParams
203
+ }) {
204
+ selection.interrupt('highlight')
205
+ if (!ids.length) {
206
+ // remove highlight
207
+ selection
208
+ .transition('highlight')
209
+ .duration(200)
210
+ .style('opacity', onlyShowHighlighted === true ? 0 : 1)
211
+ // selection
212
+ // .attr('stroke-width', fullParams.strokeWidth)
213
+
214
+ return
215
+ }
216
+
217
+ selection
218
+ .each((d, i, n) => {
219
+ if (ids.includes(d.id)) {
220
+ const dot = d3.select(n[i])
221
+ dot
222
+ .style('opacity', 1)
223
+ .transition('highlight')
224
+ .duration(200)
225
+ // dot
226
+ // .attr('stroke-width', fullParams.strokeWidthWhileHighlight)
227
+ } else {
228
+ const dot = d3.select(n[i])
229
+ dot
230
+ .style('opacity', onlyShowHighlighted === true ? 0 : fullChartParams.styles.unhighlightedOpacity)
231
+ .transition('highlight')
232
+ .duration(200)
233
+ // dot
234
+ // .attr('stroke-width', fullParams.strokeWidth)
235
+
236
+ }
237
+ })
238
+ }
239
+
240
+ function renderClipPath ({ defsSelection, clipPathData }: {
241
+ defsSelection: d3.Selection<SVGDefsElement, any, any, any>
242
+ clipPathData: ClipPathDatum[]
243
+ }) {
244
+ const clipPath = defsSelection
245
+ .selectAll<SVGClipPathElement, Layout>('clipPath')
246
+ .data(clipPathData)
247
+ .join(
248
+ enter => {
249
+ return enter
250
+ .append('clipPath')
251
+ },
252
+ update => update,
253
+ exit => exit.remove()
254
+ )
255
+ .attr('id', d => d.id)
256
+ .each((d, i, g) => {
257
+ const rect = d3.select(g[i])
258
+ .selectAll<SVGRectElement, typeof d>('rect')
259
+ .data([d])
260
+ .join('rect')
261
+ .attr('x', 0)
262
+ .attr('y', 0)
263
+ .attr('width', _d => _d.width)
264
+ .attr('height', _d => _d.height)
265
+ })
266
+
267
+ }
268
+
269
+
270
+
271
+ export const createBaseDots: BasePluginFn<BaseDotsContext> = (pluginName: string, {
272
+ selection,
273
+ computedData$,
274
+ computedLayoutData$,
275
+ visibleComputedData$,
276
+ visibleComputedLayoutData$,
277
+ seriesLabels$,
278
+ SeriesDataMap$,
279
+ GroupDataMap$,
280
+ fullParams$,
281
+ fullChartParams$,
282
+ gridAxesTransform$,
283
+ gridGraphicTransform$,
284
+ gridGraphicReverseScale$,
285
+ gridAxesSize$,
286
+ gridHighlight$,
287
+ gridContainerPosition$,
288
+ event$
289
+ }) => {
290
+
291
+ const destroy$ = new Subject()
292
+
293
+ const clipPathID = getUniID(pluginName, 'clipPath-box')
294
+ const circleGClassName = getClassName(pluginName, 'circleG')
295
+ const circleClassName = getClassName(pluginName, 'circle')
296
+
297
+ // const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection
298
+ // .append('g')
299
+ // .attr('clip-path', `url(#${clipPathID})`)
300
+ // const defsSelection: d3.Selection<SVGDefsElement, any, any, any> = axisSelection.append('defs')
301
+ // const dataAreaSelection: d3.Selection<SVGGElement, any, any, any> = axisSelection.append('g')
302
+ // const graphicSelection$: Subject<d3.Selection<SVGGElement, ComputedDatumGrid, any, any>> = new Subject()
303
+
304
+ const {
305
+ seriesSelection$,
306
+ axesSelection$,
307
+ defsSelection$,
308
+ graphicGSelection$
309
+ } = gridSelectionsObservable({
310
+ selection,
311
+ pluginName,
312
+ clipPathID,
313
+ seriesLabels$,
314
+ gridContainerPosition$,
315
+ gridAxesTransform$,
316
+ gridGraphicTransform$
317
+ })
318
+
319
+ const graphicReverseScale$: Observable<[number, number][]> = combineLatest({
320
+ // gridGraphicTransform: gridGraphicTransform$,
321
+ // gridContainerPosition: gridContainerPosition$,
322
+ // gridAxesTransform: gridAxesTransform$
323
+ computedData: computedData$,
324
+ gridGraphicReverseScale: gridGraphicReverseScale$
325
+ }).pipe(
326
+ takeUntil(destroy$),
327
+ switchMap(async data => data),
328
+ map(data => {
329
+ return data.computedData.map((series, seriesIndex) => {
330
+ return data.gridGraphicReverseScale[seriesIndex]
331
+ })
332
+ })
333
+ )
334
+
335
+ const clipPathSubscription = combineLatest({
336
+ defsSelection: defsSelection$,
337
+ gridAxesSize: gridAxesSize$,
338
+ }).pipe(
339
+ takeUntil(destroy$),
340
+ switchMap(async (d) => d),
341
+ ).subscribe(data => {
342
+ // 外層的遮罩
343
+ const clipPathData = [{
344
+ id: clipPathID,
345
+ width: data.gridAxesSize.width,
346
+ height: data.gridAxesSize.height
347
+ }]
348
+ renderClipPath({
349
+ defsSelection: data.defsSelection,
350
+ clipPathData,
351
+ })
352
+ })
353
+
354
+ const highlightTarget$ = fullChartParams$.pipe(
355
+ takeUntil(destroy$),
356
+ map(d => d.highlightTarget),
357
+ distinctUntilChanged()
358
+ )
359
+
360
+ const graphicSelection$ = combineLatest({
361
+ graphicGSelection: graphicGSelection$,
362
+ visibleComputedLayoutData: visibleComputedLayoutData$,
363
+ graphicReverseScale: graphicReverseScale$,
364
+ fullChartParams: fullChartParams$,
365
+ fullParams: fullParams$,
366
+ }).pipe(
367
+ takeUntil(destroy$),
368
+ switchMap(async (d) => d),
369
+ map(data => {
370
+ return renderDots({
371
+ graphicGSelection: data.graphicGSelection,
372
+ circleGClassName,
373
+ circleClassName,
374
+ visibleComputedLayoutData: data.visibleComputedLayoutData,
375
+ fullParams: data.fullParams,
376
+ fullChartParams: data.fullChartParams,
377
+ graphicReverseScale: data.graphicReverseScale
378
+ })
379
+ })
380
+ )
381
+
382
+ combineLatest({
383
+ graphicSelection: graphicSelection$,
384
+ computedData: computedData$,
385
+ SeriesDataMap: SeriesDataMap$,
386
+ GroupDataMap: GroupDataMap$,
387
+ highlightTarget: highlightTarget$
388
+ }).pipe(
389
+ takeUntil(destroy$),
390
+ switchMap(async (d) => d),
391
+ ).subscribe(data => {
392
+
393
+ data.graphicSelection
394
+ .on('mouseover', (event, datum) => {
395
+ event.stopPropagation()
396
+
397
+ event$.next({
398
+ type: 'grid',
399
+ eventName: 'mouseover',
400
+ pluginName,
401
+ highlightTarget: data.highlightTarget,
402
+ datum,
403
+ gridIndex: datum.gridIndex,
404
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
405
+ seriesIndex: datum.seriesIndex,
406
+ seriesLabel: datum.seriesLabel,
407
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
408
+ groupIndex: datum.groupIndex,
409
+ groupLabel: datum.groupLabel,
410
+ event,
411
+ data: data.computedData
412
+ })
413
+ })
414
+ .on('mousemove', (event, datum) => {
415
+ event.stopPropagation()
416
+
417
+ event$.next({
418
+ type: 'grid',
419
+ eventName: 'mousemove',
420
+ pluginName,
421
+ highlightTarget: data.highlightTarget,
422
+ data: data.computedData,
423
+ datum,
424
+ gridIndex: datum.gridIndex,
425
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
426
+ seriesIndex: datum.seriesIndex,
427
+ seriesLabel: datum.seriesLabel,
428
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
429
+ groupIndex: datum.groupIndex,
430
+ groupLabel: datum.groupLabel,
431
+ event
432
+ })
433
+ })
434
+ .on('mouseout', (event, datum) => {
435
+ event.stopPropagation()
436
+
437
+ event$.next({
438
+ type: 'grid',
439
+ eventName: 'mouseout',
440
+ pluginName,
441
+ highlightTarget: data.highlightTarget,
442
+ datum,
443
+ gridIndex: datum.gridIndex,
444
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
445
+ seriesIndex: datum.seriesIndex,
446
+ seriesLabel: datum.seriesLabel,
447
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
448
+ groupIndex: datum.groupIndex,
449
+ groupLabel: datum.groupLabel,
450
+ event,
451
+ data: data.computedData
452
+ })
453
+ })
454
+ .on('click', (event, datum) => {
455
+ event.stopPropagation()
456
+
457
+ event$.next({
458
+ type: 'grid',
459
+ eventName: 'click',
460
+ pluginName,
461
+ highlightTarget: data.highlightTarget,
462
+ datum,
463
+ gridIndex: datum.gridIndex,
464
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
465
+ seriesIndex: datum.seriesIndex,
466
+ seriesLabel: datum.seriesLabel,
467
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
468
+ groupIndex: datum.groupIndex,
469
+ groupLabel: datum.groupLabel,
470
+ event,
471
+ data: data.computedData
472
+ })
473
+ })
474
+
475
+ })
476
+
477
+ // const datumList$ = computedData$.pipe(
478
+ // takeUntil(destroy$),
479
+ // map(d => d.flat())
480
+ // )
481
+ // const highlight$ = highlightObservable({ datumList$, fullChartParams$, event$: store.event$ })
482
+ // const highlightSubscription = gridHighlight$.subscribe()
483
+ const onlyShowHighlighted$ = fullParams$.pipe(
484
+ takeUntil(destroy$),
485
+ map(d => d.onlyShowHighlighted),
486
+ distinctUntilChanged()
487
+ )
488
+
489
+ combineLatest({
490
+ graphicSelection: graphicSelection$,
491
+ highlight: gridHighlight$.pipe(
492
+ map(data => data.map(d => d.id))
493
+ ),
494
+ onlyShowHighlighted: onlyShowHighlighted$,
495
+ // fullParams: fullParams$,
496
+ fullChartParams: fullChartParams$
497
+ }).pipe(
498
+ takeUntil(destroy$),
499
+ switchMap(async d => d)
500
+ ).subscribe(data => {
501
+ highlightDots({
502
+ selection: data.graphicSelection,
503
+ ids: data.highlight,
504
+ onlyShowHighlighted: data.onlyShowHighlighted,
505
+ // fullParams: data.fullParams,
506
+ fullChartParams: data.fullChartParams
507
+ })
508
+ })
509
+
510
+ return () => {
511
+ destroy$.next(undefined)
512
+ // highlightSubscription.unsubscribe()
513
+ }
503
514
  }