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

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 +4838 -4760
  3. package/dist/orbcharts-plugins-basic.umd.js +15 -14
  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 +176 -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,514 +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
- // 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
- }
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
+ }
514
514
  }