@orbcharts/plugins-basic 3.0.0-alpha.42 → 3.0.0-alpha.43

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +5477 -5426
  3. package/dist/orbcharts-plugins-basic.umd.js +8 -8
  4. package/dist/src/base/BaseBarStack.d.ts +1 -1
  5. package/dist/src/base/BaseBars.d.ts +1 -1
  6. package/dist/src/base/BaseBarsTriangle.d.ts +1 -1
  7. package/dist/src/base/BaseDots.d.ts +1 -1
  8. package/dist/src/base/BaseLineAreas.d.ts +1 -1
  9. package/dist/src/base/BaseLines.d.ts +1 -1
  10. package/dist/src/multiGrid/defaults.d.ts +2 -1
  11. package/dist/src/multiGrid/index.d.ts +1 -0
  12. package/dist/src/multiGrid/plugins/MultiLineAreas.d.ts +1 -0
  13. package/dist/src/multiGrid/types.d.ts +4 -4
  14. package/package.json +42 -42
  15. package/src/base/BaseBarStack.ts +881 -879
  16. package/src/base/BaseBars.ts +750 -748
  17. package/src/base/BaseBarsTriangle.ts +659 -657
  18. package/src/base/BaseDots.ts +639 -637
  19. package/src/base/BaseGroupAxis.ts +496 -496
  20. package/src/base/BaseLegend.ts +636 -636
  21. package/src/base/BaseLineAreas.ts +621 -624
  22. package/src/base/BaseLines.ts +692 -695
  23. package/src/base/BaseValueAxis.ts +479 -479
  24. package/src/base/types.ts +2 -2
  25. package/src/grid/defaults.ts +121 -121
  26. package/src/grid/gridObservables.ts +263 -263
  27. package/src/grid/index.ts +15 -15
  28. package/src/grid/plugins/BarStack.ts +37 -37
  29. package/src/grid/plugins/Bars.ts +37 -37
  30. package/src/grid/plugins/BarsDiverging.ts +39 -39
  31. package/src/grid/plugins/BarsTriangle.ts +34 -34
  32. package/src/grid/plugins/Dots.ts +35 -35
  33. package/src/grid/plugins/GridLegend.ts +58 -58
  34. package/src/grid/plugins/GroupAux.ts +643 -643
  35. package/src/grid/plugins/GroupAxis.ts +30 -30
  36. package/src/grid/plugins/LineAreas.ts +36 -36
  37. package/src/grid/plugins/Lines.ts +35 -35
  38. package/src/grid/plugins/ScalingArea.ts +174 -174
  39. package/src/grid/plugins/ValueAxis.ts +31 -31
  40. package/src/grid/plugins/ValueStackAxis.ts +70 -70
  41. package/src/grid/types.ts +120 -120
  42. package/src/index.ts +9 -9
  43. package/src/multiGrid/defaults.ts +147 -140
  44. package/src/multiGrid/index.ts +11 -10
  45. package/src/multiGrid/multiGridObservables.ts +289 -278
  46. package/src/multiGrid/plugins/MultiBarStack.ts +60 -60
  47. package/src/multiGrid/plugins/MultiBars.ts +59 -59
  48. package/src/multiGrid/plugins/MultiBarsTriangle.ts +58 -58
  49. package/src/multiGrid/plugins/MultiDots.ts +58 -58
  50. package/src/multiGrid/plugins/MultiGridLegend.ts +88 -88
  51. package/src/multiGrid/plugins/MultiGroupAxis.ts +53 -53
  52. package/src/multiGrid/plugins/MultiLineAreas.ts +59 -0
  53. package/src/multiGrid/plugins/MultiLines.ts +58 -58
  54. package/src/multiGrid/plugins/MultiValueAxis.ts +53 -53
  55. package/src/multiGrid/plugins/OverlappingValueAxes.ts +164 -165
  56. package/src/multiGrid/types.ts +67 -67
  57. package/src/noneData/defaults.ts +61 -61
  58. package/src/noneData/index.ts +3 -3
  59. package/src/noneData/plugins/Container.ts +10 -10
  60. package/src/noneData/plugins/Tooltip.ts +304 -304
  61. package/src/noneData/types.ts +26 -26
  62. package/src/series/defaults.ts +99 -99
  63. package/src/series/index.ts +6 -6
  64. package/src/series/plugins/Bubbles.ts +551 -549
  65. package/src/series/plugins/Pie.ts +600 -598
  66. package/src/series/plugins/PieEventTexts.ts +194 -194
  67. package/src/series/plugins/PieLabels.ts +288 -285
  68. package/src/series/plugins/SeriesLegend.ts +58 -58
  69. package/src/series/seriesUtils.ts +50 -50
  70. package/src/series/types.ts +67 -67
  71. package/src/tree/defaults.ts +22 -22
  72. package/src/tree/index.ts +3 -3
  73. package/src/tree/plugins/TreeLegend.ts +58 -58
  74. package/src/tree/plugins/TreeMap.ts +302 -300
  75. package/src/tree/types.ts +23 -23
  76. package/src/utils/commonUtils.ts +21 -21
  77. package/src/utils/d3Graphics.ts +124 -124
  78. package/src/utils/d3Utils.ts +73 -73
  79. package/src/utils/observables.ts +14 -14
  80. package/src/utils/orbchartsUtils.ts +100 -100
  81. package/tsconfig.dev.json +16 -16
  82. package/tsconfig.json +13 -13
  83. package/tsconfig.prod.json +13 -13
  84. package/vite.config.js +49 -49
@@ -1,749 +1,751 @@
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
- ContainerPosition,
15
- EventGrid,
16
- ChartParams,
17
- Layout,
18
- TransformData } from '@orbcharts/core'
19
- import { getD3TransitionEase } from '../utils/d3Utils'
20
- import { getClassName, getUniID } from '../utils/orbchartsUtils'
21
- import { gridSelectionsObservable } from '../grid/gridObservables'
22
-
23
- export interface BaseBarsParams {
24
- // barType: BarType
25
- barWidth: number
26
- barPadding: number
27
- barGroupPadding: number // 群組和群組間的間隔
28
- barRadius: number | boolean
29
- }
30
-
31
- interface BaseBarsContext {
32
- selection: d3.Selection<any, unknown, any, unknown>
33
- computedData$: Observable<ComputedDataGrid>
34
- visibleComputedData$: Observable<ComputedDatumGrid[][]>
35
- existSeriesLabels$: Observable<string[]>
36
- SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
37
- GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
38
- fullParams$: Observable<BaseBarsParams>
39
- fullChartParams$: Observable<ChartParams>
40
- gridAxesTransform$: Observable<TransformData>
41
- gridGraphicTransform$: Observable<TransformData>
42
- gridGraphicReverseScale$: Observable<[number, number][]>
43
- gridAxesSize$: Observable<{
44
- width: number;
45
- height: number;
46
- }>
47
- gridHighlight$: Observable<string[]>
48
- gridContainer$: Observable<ContainerPosition[]>
49
- isSeriesPositionSeprate$: Observable<boolean>
50
- event$: Subject<EventGrid>
51
- }
52
-
53
- interface RenderBarParams {
54
- graphicGSelection: d3.Selection<SVGGElement, string, any, any>
55
- rectClassName: string
56
- computedData: ComputedDatumGrid[][]
57
- zeroYArr: number[]
58
- groupLabels: string[]
59
- barScale: d3.ScalePoint<string>
60
- params: BaseBarsParams
61
- chartParams: ChartParams
62
- barWidth: number
63
- transformedBarRadius: [number, number][]
64
- delayGroup: number
65
- transitionItem: number
66
- isSeriesPositionSeprate: boolean
67
- }
68
-
69
- type ClipPathDatum = {
70
- id: string;
71
- // x: number;
72
- // y: number;
73
- width: number;
74
- height: number;
75
- }
76
-
77
- // const pluginName = 'Bars'
78
- // const rectClassName = getClassName(pluginName, 'rect')
79
- // group的delay在動畫中的佔比(剩餘部份的時間為圖形本身的動畫時間,因為delay時間和最後一個group的動畫時間加總為1)
80
- const groupDelayProportionOfDuration = 0.3
81
-
82
- function calcBarWidth ({ axisWidth, groupAmount, barAmountOfGroup, barPadding = 0, barGroupPadding = 0 }: {
83
- axisWidth: number
84
- groupAmount: number
85
- barAmountOfGroup: number
86
- barPadding: number
87
- barGroupPadding: number
88
- }) {
89
- const eachGroupWidth = axisWidth / (groupAmount - 1)
90
- const width = (eachGroupWidth - barGroupPadding) / barAmountOfGroup - barPadding
91
- return width > 1 ? width : 1
92
- }
93
-
94
- function makeBarScale (barWidth: number, seriesLabels: string[], params: BaseBarsParams) {
95
- const barHalfWidth = barWidth! / 2
96
- const barGroupWidth = barWidth * seriesLabels.length + params.barPadding! * seriesLabels.length
97
- return d3.scalePoint()
98
- .domain(seriesLabels)
99
- .range([-barGroupWidth / 2 + barHalfWidth, barGroupWidth / 2 - barHalfWidth])
100
- }
101
-
102
- function calcDelayGroup (barGroupAmount: number, totalDuration: number) {
103
- if (barGroupAmount <= 1) {
104
- // 一筆內計算會出錯所以不算
105
- return 0
106
- }
107
- return totalDuration / (barGroupAmount - 1) * groupDelayProportionOfDuration // 依group數量計算
108
- }
109
-
110
- function calctransitionItem (barGroupAmount: number, totalDuration: number) {
111
- if (barGroupAmount <= 1) {
112
- // 一筆內不會有delay
113
- return totalDuration
114
- }
115
- return totalDuration * (1 - groupDelayProportionOfDuration) // delay後剩餘的時間
116
- }
117
- // let _data: ComputedDatumGrid[][] = []
118
-
119
- function renderRectBars ({ graphicGSelection, rectClassName, computedData, zeroYArr, groupLabels, barScale, params, chartParams, barWidth, transformedBarRadius, delayGroup, transitionItem, isSeriesPositionSeprate }: RenderBarParams) {
120
-
121
- const barHalfWidth = barWidth! / 2
122
-
123
- graphicGSelection
124
- .each((seriesData, seriesIndex, g) => {
125
- d3.select(g[seriesIndex])
126
- .selectAll<SVGGElement, ComputedDatumGrid>(`rect.${rectClassName}`)
127
- .data(computedData[seriesIndex] ?? [], d => d.id)
128
- .join(
129
- enter => {
130
- // console.log('enter')
131
- return enter
132
- .append('rect')
133
- .classed(rectClassName, true)
134
- .attr('cursor', 'pointer')
135
- .attr('height', d => 0)
136
- },
137
- update => update,
138
- exit => exit.remove()
139
- )
140
- .attr('transform', (d, i) => `translate(${(d ? d.axisX : 0) - barHalfWidth}, ${0})`)
141
- .attr('fill', d => d.color)
142
- .attr('y', d => d.axisY < zeroYArr[seriesIndex] ? d.axisY : zeroYArr[seriesIndex])
143
- .attr('x', d => isSeriesPositionSeprate ? 0 : barScale(d.seriesLabel)!)
144
- .attr('width', barWidth!)
145
- .attr('rx', transformedBarRadius[seriesIndex][0] ?? 1)
146
- .attr('ry', transformedBarRadius[seriesIndex][1] ?? 1)
147
- .transition()
148
- .duration(transitionItem)
149
- .ease(getD3TransitionEase(chartParams.transitionEase))
150
- .delay((d, i) => d.groupIndex * delayGroup)
151
- .attr('height', d => Math.abs(d.axisYFromZero))
152
- })
153
-
154
-
155
- // graphicGSelection
156
- // .each((d, seriesIndex, g) => {
157
- // d3.select(g[seriesIndex])
158
- // .selectAll<SVGGElement, ComputedDatumGrid>(`g.${barClassName}`)
159
- // .data(computedData[seriesIndex], d => d.seriesIndex)
160
- // .join('g')
161
- // .classed(barClassName, true)
162
- // .attr('transform', (d, i) => `translate(${d ? d.axisX : 0}, ${0})`)
163
- // .each((datum, i, g) => {
164
- // d3.select(g[i])
165
- // .selectAll<SVGRectElement, ComputedDatumGrid>(`rect.${rectClassName}`)
166
- // .data([computedData[seriesIndex][i]], d => d.id)
167
- // .join(
168
- // enter => {
169
- // return enter
170
- // .append('rect')
171
- // .classed(rectClassName, true)
172
- // .attr('height', d => 0)
173
- // },
174
- // update => update,
175
- // exit => exit.remove()
176
- // )
177
- // .attr('cursor', 'pointer')
178
- // .attr('fill', d => d.color)
179
- // .attr('y', d => d.axisY < zeroYArr[seriesIndex] ? d.axisY : zeroYArr[seriesIndex])
180
- // .attr('x', d => isSeriesPositionSeprate ? 0 : barScale(d.seriesLabel)!)
181
- // .attr('width', barWidth!)
182
- // .attr('transform', `translate(${-barHalfWidth}, 0)`)
183
- // // .attr('rx', params.barRadius == true ? barHalfWidth
184
- // // : params.barRadius == false ? 0
185
- // // : typeof params.barRadius == 'number' ? params.barRadius
186
- // // : 0)
187
- // .attr('rx', transformedBarRadius[0])
188
- // .attr('ry', transformedBarRadius[1])
189
- // .transition()
190
- // .duration(transitionItem)
191
- // .ease(getD3TransitionEase(chartParams.transitionEase))
192
- // .delay((d, i) => d.groupIndex * delayGroup)
193
- // .attr('height', d => Math.abs(d.axisYFromZero))
194
- // })
195
- // })
196
-
197
- const graphicBarSelection: d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown> = graphicGSelection.selectAll(`rect.${rectClassName}`)
198
-
199
- return graphicBarSelection
200
- }
201
-
202
- function renderClipPath ({ defsSelection, clipPathData }: {
203
- defsSelection: d3.Selection<SVGDefsElement, any, any, any>
204
- clipPathData: ClipPathDatum[]
205
- }) {
206
- const clipPath = defsSelection
207
- .selectAll<SVGClipPathElement, Layout>('clipPath')
208
- .data(clipPathData)
209
- .join(
210
- enter => {
211
- return enter
212
- .append('clipPath')
213
- },
214
- update => update,
215
- exit => exit.remove()
216
- )
217
- .attr('id', d => d.id)
218
- .each((d, i, g) => {
219
- const rect = d3.select(g[i])
220
- .selectAll<SVGRectElement, typeof d>('rect')
221
- .data([d])
222
- .join(
223
- enter => {
224
- return enter
225
- .append('rect')
226
- },
227
- update => update,
228
- exit => exit.remove()
229
- )
230
- .attr('x', 0)
231
- .attr('y', 0)
232
- .attr('width', _d => _d.width)
233
- .attr('height', _d => _d.height)
234
- })
235
- }
236
-
237
- function highlight ({ selection, ids, fullChartParams }: {
238
- selection: d3.Selection<any, ComputedDatumGrid, any, any>
239
- ids: string[]
240
- fullChartParams: ChartParams
241
- }) {
242
- selection.interrupt('highlight')
243
-
244
- if (!ids.length) {
245
- // remove highlight
246
- selection
247
- .transition('highlight')
248
- .duration(200)
249
- .style('opacity', 1)
250
- return
251
- }
252
-
253
- selection
254
- .each((d, i, n) => {
255
- if (ids.includes(d.id)) {
256
- d3.select(n[i])
257
- .style('opacity', 1)
258
- } else {
259
- d3.select(n[i])
260
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
261
- }
262
- })
263
- }
264
-
265
-
266
- export const createBaseBars: BasePluginFn<BaseBarsContext> = (pluginName: string, {
267
- selection,
268
- computedData$,
269
- visibleComputedData$,
270
- existSeriesLabels$,
271
- SeriesDataMap$,
272
- GroupDataMap$,
273
- fullParams$,
274
- fullChartParams$,
275
- gridAxesTransform$,
276
- gridGraphicTransform$,
277
- gridGraphicReverseScale$,
278
- gridAxesSize$,
279
- gridHighlight$,
280
- gridContainer$,
281
- isSeriesPositionSeprate$,
282
- event$
283
- }) => {
284
-
285
- const destroy$ = new Subject()
286
-
287
- const clipPathID = getUniID(pluginName, 'clipPath-box')
288
- const rectClassName = getClassName(pluginName, 'rect')
289
-
290
- // const seriesSelection$ = computedData$.pipe(
291
- // takeUntil(destroy$),
292
- // distinctUntilChanged((a, b) => {
293
- // // 只有當series的數量改變時,才重新計算
294
- // return a.length === b.length
295
- // }),
296
- // map((computedData, i) => {
297
- // return selection
298
- // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${seriesClassName}`)
299
- // .data(computedData, d => d[0] ? d[0].seriesIndex : i)
300
- // .join(
301
- // enter => {
302
- // return enter
303
- // .append('g')
304
- // .classed(seriesClassName, true)
305
- // .each((d, i, g) => {
306
- // const axesSelection = d3.select(g[i])
307
- // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${axesClassName}`)
308
- // .data([i])
309
- // .join(
310
- // enter => {
311
- // return enter
312
- // .append('g')
313
- // .classed(axesClassName, true)
314
- // .attr('clip-path', `url(#${clipPathID})`)
315
- // .each((d, i, g) => {
316
- // const defsSelection = d3.select(g[i])
317
- // .selectAll<SVGDefsElement, any>('defs')
318
- // .data([i])
319
- // .join('defs')
320
-
321
- // const graphicGSelection = d3.select(g[i])
322
- // .selectAll<SVGGElement, any>('g')
323
- // .data([i])
324
- // .join('g')
325
- // .classed(graphicClassName, true)
326
- // })
327
- // },
328
- // update => update,
329
- // exit => exit.remove()
330
- // )
331
- // })
332
- // },
333
- // update => update,
334
- // exit => exit.remove()
335
- // )
336
- // })
337
- // )
338
-
339
- // combineLatest({
340
- // seriesSelection: seriesSelection$,
341
- // gridContainer: gridContainer$
342
- // }).pipe(
343
- // takeUntil(destroy$),
344
- // switchMap(async d => d)
345
- // ).subscribe(data => {
346
- // data.seriesSelection
347
- // .transition()
348
- // .attr('transform', (d, i) => {
349
- // const translate = data.gridContainer[i].translate
350
- // const scale = data.gridContainer[i].scale
351
- // return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
352
- // })
353
- // })
354
-
355
-
356
- // const axesSelection$ = combineLatest({
357
- // seriesSelection: seriesSelection$,
358
- // gridAxesTransform: gridAxesTransform$
359
- // }).pipe(
360
- // takeUntil(destroy$),
361
- // switchMap(async d => d),
362
- // map(data => {
363
- // return data.seriesSelection
364
- // .select<SVGGElement>(`g.${axesClassName}`)
365
- // .style('transform', data.gridAxesTransform.value)
366
- // })
367
- // )
368
- // const defsSelection$ = axesSelection$.pipe(
369
- // takeUntil(destroy$),
370
- // map(axesSelection => {
371
- // return axesSelection.select<SVGDefsElement>('defs')
372
- // })
373
- // )
374
- // const graphicGSelection$ = combineLatest({
375
- // axesSelection: axesSelection$,
376
- // gridGraphicTransform: gridGraphicTransform$
377
- // }).pipe(
378
- // takeUntil(destroy$),
379
- // switchMap(async d => d),
380
- // map(data => {
381
- // const graphicGSelection = data.axesSelection
382
- // .select<SVGGElement>(`g.${graphicClassName}`)
383
- // graphicGSelection
384
- // .transition()
385
- // .duration(50)
386
- // .style('transform', data.gridGraphicTransform.value)
387
- // return graphicGSelection
388
- // })
389
- // )
390
-
391
- const {
392
- seriesSelection$,
393
- axesSelection$,
394
- defsSelection$,
395
- graphicGSelection$
396
- } = gridSelectionsObservable({
397
- selection,
398
- pluginName,
399
- clipPathID,
400
- existSeriesLabels$,
401
- gridContainer$,
402
- gridAxesTransform$,
403
- gridGraphicTransform$
404
- })
405
-
406
- const zeroYArr$ = visibleComputedData$.pipe(
407
- takeUntil(destroy$),
408
- map(data => {
409
- return data.map(d => {
410
- return d[0] ? d[0].axisY - d[0].axisYFromZero : 0
411
- })
412
- }),
413
- distinctUntilChanged()
414
- )
415
-
416
- const barWidth$ = combineLatest({
417
- computedData: computedData$,
418
- visibleComputedData: visibleComputedData$,
419
- params: fullParams$,
420
- gridAxesSize: gridAxesSize$,
421
- isSeriesPositionSeprate: isSeriesPositionSeprate$
422
- }).pipe(
423
- takeUntil(destroy$),
424
- switchMap(async d => d),
425
- map(data => {
426
- if (data.params.barWidth) {
427
- return data.params.barWidth
428
- } else if (data.isSeriesPositionSeprate) {
429
- return calcBarWidth({
430
- axisWidth: data.gridAxesSize.width,
431
- groupAmount: data.computedData[0] ? data.computedData[0].length : 0,
432
- barAmountOfGroup: 1,
433
- barPadding: data.params.barPadding,
434
- barGroupPadding: data.params.barGroupPadding
435
- })
436
- } else {
437
- return calcBarWidth({
438
- axisWidth: data.gridAxesSize.width,
439
- groupAmount: data.computedData[0] ? data.computedData[0].length : 0,
440
- barAmountOfGroup: data.visibleComputedData.length,
441
- barPadding: data.params.barPadding,
442
- barGroupPadding: data.params.barGroupPadding
443
- })
444
- }
445
- }),
446
- distinctUntilChanged()
447
- )
448
-
449
- // 圓角的值 [rx, ry]
450
- const transformedBarRadius$: Observable<[number, number][]> = combineLatest({
451
- computedData: computedData$,
452
- // gridGraphicTransform: gridGraphicTransform$,
453
- barWidth: barWidth$,
454
- params: fullParams$,
455
- // gridContainer: gridContainer$,
456
- // gridAxesTransform: gridAxesTransform$
457
- gridGraphicReverseScale: gridGraphicReverseScale$
458
- }).pipe(
459
- takeUntil(destroy$),
460
- switchMap(async data => data),
461
- map(data => {
462
- const barHalfWidth = data.barWidth! / 2
463
- const radius = data.params.barRadius === true ? barHalfWidth
464
- : data.params.barRadius === false ? 0
465
- : typeof data.params.barRadius == 'number' ? data.params.barRadius
466
- : 0
467
-
468
- return data.computedData.map((series, seriesIndex) => {
469
- const gridGraphicReverseScale = data.gridGraphicReverseScale[seriesIndex] ?? data.gridGraphicReverseScale[0]
470
-
471
- let transformedRx = radius * gridGraphicReverseScale[0]
472
- let transformedRy = radius * gridGraphicReverseScale[1]
473
- // if (radius == 0) {
474
- // transformedRx = 0
475
- // transformedRy = 0
476
- // } else if (data.gridAxesTransform.rotate == 0) {
477
- // transformedRx = radius
478
- // // 抵消外層scale的變型
479
- // / data.gridGraphicTransform.scale[0] / data.gridContainer[0].scale[0]
480
- // transformedRy = radius
481
- // // 抵消外層scale的變型
482
- // / data.gridGraphicTransform.scale[1] / data.gridContainer[0].scale[1]
483
- // } else if (data.gridAxesTransform.rotate != 0) {
484
- // transformedRx = radius
485
- // // 抵消外層scale的變型,由於有90度的旋轉,所以外層 (container) x和y的scale要互換
486
- // / data.gridGraphicTransform.scale[0] / data.gridContainer[0].scale[1]
487
- // transformedRy = radius
488
- // // 抵消外層scale的變型,由於有90度的旋轉,所以外層 (container) x和y的scale要互換
489
- // / data.gridGraphicTransform.scale[1] / data.gridContainer[0].scale[0]
490
- // }
491
-
492
- // 如果計算出來的x圓角值大於寬度一半則進行修正
493
- if (transformedRx > barHalfWidth) {
494
- const rScale = barHalfWidth / transformedRx
495
- transformedRx = transformedRx * rScale
496
- transformedRy = transformedRy * rScale
497
- }
498
-
499
- return [transformedRx, transformedRy]
500
- })
501
- })
502
- )
503
-
504
-
505
- const seriesLabels$ = visibleComputedData$.pipe(
506
- takeUntil(destroy$),
507
- map(data => {
508
- const SeriesLabelSet: Set<string> = new Set()
509
- data.forEach(d => {
510
- d.forEach(_d => {
511
- SeriesLabelSet.add(_d.seriesLabel)
512
- })
513
- })
514
- return Array.from(SeriesLabelSet)
515
- })
516
- )
517
-
518
- const groupLabels$ = visibleComputedData$.pipe(
519
- takeUntil(destroy$),
520
- map(data => {
521
- const GroupLabelSet: Set<string> = new Set()
522
- data.forEach(d => {
523
- d.forEach(_d => {
524
- GroupLabelSet.add(_d.groupLabel)
525
- })
526
- })
527
- return Array.from(GroupLabelSet)
528
- })
529
- )
530
-
531
- const barScale$ = combineLatest({
532
- seriesLabels: seriesLabels$,
533
- barWidth: barWidth$,
534
- params: fullParams$,
535
- }).pipe(
536
- takeUntil(destroy$),
537
- switchMap(async d => d),
538
- map(data => {
539
- return makeBarScale(data.barWidth, data.seriesLabels, data.params)
540
- })
541
- )
542
-
543
- const transitionDuration$ = fullChartParams$.pipe(
544
- takeUntil(destroy$),
545
- map(d => d.transitionDuration),
546
- distinctUntilChanged()
547
- )
548
-
549
- const delayGroup$ = new Observable<number>(subscriber => {
550
- combineLatest({
551
- groupLabels: groupLabels$,
552
- transitionDuration: transitionDuration$,
553
- }).pipe(
554
- switchMap(async d => d)
555
- ).subscribe(data => {
556
- const delay = calcDelayGroup(data.groupLabels.length, data.transitionDuration)
557
- subscriber.next(delay)
558
- })
559
- }).pipe(
560
- takeUntil(destroy$),
561
- distinctUntilChanged()
562
- )
563
-
564
- const transitionItem$ = new Observable<number>(subscriber => {
565
- combineLatest({
566
- groupLabels: groupLabels$,
567
- transitionDuration: transitionDuration$
568
- }).pipe(
569
- switchMap(async d => d)
570
- ).subscribe(data => {
571
- const transition = calctransitionItem(data.groupLabels.length, data.transitionDuration)
572
- subscriber.next(transition)
573
- })
574
- }).pipe(
575
- takeUntil(destroy$),
576
- distinctUntilChanged()
577
- )
578
-
579
- combineLatest({
580
- defsSelection: defsSelection$,
581
- gridAxesSize: gridAxesSize$,
582
- }).pipe(
583
- takeUntil(destroy$),
584
- switchMap(async d => d)
585
- ).subscribe(data => {
586
- const clipPathData = [{
587
- id: clipPathID,
588
- width: data.gridAxesSize.width,
589
- height: data.gridAxesSize.height
590
- }]
591
- renderClipPath({
592
- defsSelection: data.defsSelection,
593
- clipPathData
594
- })
595
- })
596
-
597
-
598
- const highlightTarget$ = fullChartParams$.pipe(
599
- takeUntil(destroy$),
600
- map(d => d.highlightTarget),
601
- distinctUntilChanged()
602
- )
603
-
604
- const barSelection$ = new Subject<d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown>>()
605
-
606
- combineLatest({
607
- graphicGSelection: graphicGSelection$,
608
- computedData: computedData$,
609
- // barData$: barData$,
610
- zeroYArr: zeroYArr$,
611
- groupLabels: groupLabels$,
612
- barScale: barScale$,
613
- params: fullParams$,
614
- chartParams: fullChartParams$,
615
- highlightTarget: highlightTarget$,
616
- barWidth: barWidth$,
617
- transformedBarRadius: transformedBarRadius$,
618
- delayGroup: delayGroup$,
619
- transitionItem: transitionItem$,
620
- SeriesDataMap: SeriesDataMap$,
621
- GroupDataMap: GroupDataMap$,
622
- isSeriesPositionSeprate: isSeriesPositionSeprate$
623
- }).pipe(
624
- takeUntil(destroy$),
625
- switchMap(async (d) => d),
626
- ).subscribe(data => {
627
-
628
- const barSelection = renderRectBars({
629
- graphicGSelection: data.graphicGSelection,
630
- rectClassName,
631
- computedData: data.computedData,
632
- zeroYArr: data.zeroYArr,
633
- groupLabels: data.groupLabels,
634
- barScale: data.barScale,
635
- params: data.params,
636
- chartParams: data.chartParams,
637
- barWidth: data.barWidth,
638
- transformedBarRadius: data.transformedBarRadius,
639
- delayGroup: data.delayGroup,
640
- transitionItem: data.transitionItem,
641
- isSeriesPositionSeprate: data.isSeriesPositionSeprate
642
- })
643
-
644
- barSelection!
645
- .on('mouseover', (event, datum) => {
646
- event.stopPropagation()
647
-
648
- event$.next({
649
- type: 'grid',
650
- eventName: 'mouseover',
651
- pluginName,
652
- highlightTarget: data.highlightTarget,
653
- datum,
654
- gridIndex: datum.gridIndex,
655
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
656
- seriesIndex: datum.seriesIndex,
657
- seriesLabel: datum.seriesLabel,
658
- groups: data.GroupDataMap.get(datum.groupLabel)!,
659
- groupIndex: datum.groupIndex,
660
- groupLabel: datum.groupLabel,
661
- event,
662
- data: data.computedData
663
- })
664
- })
665
- .on('mousemove', (event, datum) => {
666
- event.stopPropagation()
667
-
668
- event$.next({
669
- type: 'grid',
670
- eventName: 'mousemove',
671
- pluginName,
672
- highlightTarget: data.highlightTarget,
673
- datum,
674
- gridIndex: datum.gridIndex,
675
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
676
- seriesIndex: datum.seriesIndex,
677
- seriesLabel: datum.seriesLabel,
678
- groups: data.GroupDataMap.get(datum.groupLabel)!,
679
- groupIndex: datum.groupIndex,
680
- groupLabel: datum.groupLabel,
681
- event,
682
- data: data.computedData
683
- })
684
- })
685
- .on('mouseout', (event, datum) => {
686
- event.stopPropagation()
687
-
688
- event$.next({
689
- type: 'grid',
690
- eventName: 'mouseout',
691
- pluginName,
692
- highlightTarget: data.highlightTarget,
693
- datum,
694
- gridIndex: datum.gridIndex,
695
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
696
- seriesIndex: datum.seriesIndex,
697
- seriesLabel: datum.seriesLabel,
698
- groups: data.GroupDataMap.get(datum.groupLabel)!,
699
- groupIndex: datum.groupIndex,
700
- groupLabel: datum.groupLabel,
701
- event,
702
- data: data.computedData
703
- })
704
- })
705
- .on('click', (event, datum) => {
706
- event.stopPropagation()
707
-
708
- event$.next({
709
- type: 'grid',
710
- eventName: 'click',
711
- pluginName,
712
- highlightTarget: data.highlightTarget,
713
- datum,
714
- gridIndex: datum.gridIndex,
715
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
716
- seriesIndex: datum.seriesIndex,
717
- seriesLabel: datum.seriesLabel,
718
- groups: data.GroupDataMap.get(datum.groupLabel)!,
719
- groupIndex: datum.groupIndex,
720
- groupLabel: datum.groupLabel,
721
- event,
722
- data: data.computedData
723
- })
724
- })
725
-
726
- barSelection$.next(barSelection!)
727
- })
728
-
729
-
730
- combineLatest({
731
- barSelection: barSelection$,
732
- highlight: gridHighlight$,
733
- fullChartParams: fullChartParams$
734
- }).pipe(
735
- takeUntil(destroy$),
736
- switchMap(async d => d)
737
- ).subscribe(data => {
738
- highlight({
739
- selection: data.barSelection,
740
- ids: data.highlight,
741
- fullChartParams: data.fullChartParams
742
- })
743
- })
744
-
745
-
746
- return () => {
747
- destroy$.next(undefined)
748
- }
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
+ ContainerPosition,
15
+ EventGrid,
16
+ ChartParams,
17
+ Layout,
18
+ TransformData } from '@orbcharts/core'
19
+ import { getD3TransitionEase } from '../utils/d3Utils'
20
+ import { getClassName, getUniID } from '../utils/orbchartsUtils'
21
+ import { gridSelectionsObservable } from '../grid/gridObservables'
22
+
23
+ export interface BaseBarsParams {
24
+ // barType: BarType
25
+ barWidth: number
26
+ barPadding: number
27
+ barGroupPadding: number // 群組和群組間的間隔
28
+ barRadius: number | boolean
29
+ }
30
+
31
+ interface BaseBarsContext {
32
+ selection: d3.Selection<any, unknown, any, unknown>
33
+ computedData$: Observable<ComputedDataGrid>
34
+ visibleComputedData$: Observable<ComputedDatumGrid[][]>
35
+ existSeriesLabels$: Observable<string[]>
36
+ SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
37
+ GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
38
+ fullParams$: Observable<BaseBarsParams>
39
+ fullChartParams$: Observable<ChartParams>
40
+ gridAxesTransform$: Observable<TransformData>
41
+ gridGraphicTransform$: Observable<TransformData>
42
+ gridGraphicReverseScale$: Observable<[number, number][]>
43
+ gridAxesSize$: Observable<{
44
+ width: number;
45
+ height: number;
46
+ }>
47
+ gridHighlight$: Observable<ComputedDatumGrid[]>
48
+ gridContainer$: Observable<ContainerPosition[]>
49
+ isSeriesPositionSeprate$: Observable<boolean>
50
+ event$: Subject<EventGrid>
51
+ }
52
+
53
+ interface RenderBarParams {
54
+ graphicGSelection: d3.Selection<SVGGElement, string, any, any>
55
+ rectClassName: string
56
+ computedData: ComputedDatumGrid[][]
57
+ zeroYArr: number[]
58
+ groupLabels: string[]
59
+ barScale: d3.ScalePoint<string>
60
+ params: BaseBarsParams
61
+ chartParams: ChartParams
62
+ barWidth: number
63
+ transformedBarRadius: [number, number][]
64
+ delayGroup: number
65
+ transitionItem: number
66
+ isSeriesPositionSeprate: boolean
67
+ }
68
+
69
+ type ClipPathDatum = {
70
+ id: string;
71
+ // x: number;
72
+ // y: number;
73
+ width: number;
74
+ height: number;
75
+ }
76
+
77
+ // const pluginName = 'Bars'
78
+ // const rectClassName = getClassName(pluginName, 'rect')
79
+ // group的delay在動畫中的佔比(剩餘部份的時間為圖形本身的動畫時間,因為delay時間和最後一個group的動畫時間加總為1)
80
+ const groupDelayProportionOfDuration = 0.3
81
+
82
+ function calcBarWidth ({ axisWidth, groupAmount, barAmountOfGroup, barPadding = 0, barGroupPadding = 0 }: {
83
+ axisWidth: number
84
+ groupAmount: number
85
+ barAmountOfGroup: number
86
+ barPadding: number
87
+ barGroupPadding: number
88
+ }) {
89
+ const eachGroupWidth = axisWidth / (groupAmount - 1)
90
+ const width = (eachGroupWidth - barGroupPadding) / barAmountOfGroup - barPadding
91
+ return width > 1 ? width : 1
92
+ }
93
+
94
+ function makeBarScale (barWidth: number, seriesLabels: string[], params: BaseBarsParams) {
95
+ const barHalfWidth = barWidth! / 2
96
+ const barGroupWidth = barWidth * seriesLabels.length + params.barPadding! * seriesLabels.length
97
+ return d3.scalePoint()
98
+ .domain(seriesLabels)
99
+ .range([-barGroupWidth / 2 + barHalfWidth, barGroupWidth / 2 - barHalfWidth])
100
+ }
101
+
102
+ function calcDelayGroup (barGroupAmount: number, totalDuration: number) {
103
+ if (barGroupAmount <= 1) {
104
+ // 一筆內計算會出錯所以不算
105
+ return 0
106
+ }
107
+ return totalDuration / (barGroupAmount - 1) * groupDelayProportionOfDuration // 依group數量計算
108
+ }
109
+
110
+ function calctransitionItem (barGroupAmount: number, totalDuration: number) {
111
+ if (barGroupAmount <= 1) {
112
+ // 一筆內不會有delay
113
+ return totalDuration
114
+ }
115
+ return totalDuration * (1 - groupDelayProportionOfDuration) // delay後剩餘的時間
116
+ }
117
+ // let _data: ComputedDatumGrid[][] = []
118
+
119
+ function renderRectBars ({ graphicGSelection, rectClassName, computedData, zeroYArr, groupLabels, barScale, params, chartParams, barWidth, transformedBarRadius, delayGroup, transitionItem, isSeriesPositionSeprate }: RenderBarParams) {
120
+
121
+ const barHalfWidth = barWidth! / 2
122
+
123
+ graphicGSelection
124
+ .each((seriesData, seriesIndex, g) => {
125
+ d3.select(g[seriesIndex])
126
+ .selectAll<SVGGElement, ComputedDatumGrid>(`rect.${rectClassName}`)
127
+ .data(computedData[seriesIndex] ?? [], d => d.id)
128
+ .join(
129
+ enter => {
130
+ // console.log('enter')
131
+ return enter
132
+ .append('rect')
133
+ .classed(rectClassName, true)
134
+ .attr('cursor', 'pointer')
135
+ .attr('height', d => 0)
136
+ },
137
+ update => update,
138
+ exit => exit.remove()
139
+ )
140
+ .attr('transform', (d, i) => `translate(${(d ? d.axisX : 0) - barHalfWidth}, ${0})`)
141
+ .attr('fill', d => d.color)
142
+ .attr('y', d => d.axisY < zeroYArr[seriesIndex] ? d.axisY : zeroYArr[seriesIndex])
143
+ .attr('x', d => isSeriesPositionSeprate ? 0 : barScale(d.seriesLabel)!)
144
+ .attr('width', barWidth!)
145
+ .attr('rx', transformedBarRadius[seriesIndex][0] ?? 1)
146
+ .attr('ry', transformedBarRadius[seriesIndex][1] ?? 1)
147
+ .transition()
148
+ .duration(transitionItem)
149
+ .ease(getD3TransitionEase(chartParams.transitionEase))
150
+ .delay((d, i) => d.groupIndex * delayGroup)
151
+ .attr('height', d => Math.abs(d.axisYFromZero))
152
+ })
153
+
154
+
155
+ // graphicGSelection
156
+ // .each((d, seriesIndex, g) => {
157
+ // d3.select(g[seriesIndex])
158
+ // .selectAll<SVGGElement, ComputedDatumGrid>(`g.${barClassName}`)
159
+ // .data(computedData[seriesIndex], d => d.seriesIndex)
160
+ // .join('g')
161
+ // .classed(barClassName, true)
162
+ // .attr('transform', (d, i) => `translate(${d ? d.axisX : 0}, ${0})`)
163
+ // .each((datum, i, g) => {
164
+ // d3.select(g[i])
165
+ // .selectAll<SVGRectElement, ComputedDatumGrid>(`rect.${rectClassName}`)
166
+ // .data([computedData[seriesIndex][i]], d => d.id)
167
+ // .join(
168
+ // enter => {
169
+ // return enter
170
+ // .append('rect')
171
+ // .classed(rectClassName, true)
172
+ // .attr('height', d => 0)
173
+ // },
174
+ // update => update,
175
+ // exit => exit.remove()
176
+ // )
177
+ // .attr('cursor', 'pointer')
178
+ // .attr('fill', d => d.color)
179
+ // .attr('y', d => d.axisY < zeroYArr[seriesIndex] ? d.axisY : zeroYArr[seriesIndex])
180
+ // .attr('x', d => isSeriesPositionSeprate ? 0 : barScale(d.seriesLabel)!)
181
+ // .attr('width', barWidth!)
182
+ // .attr('transform', `translate(${-barHalfWidth}, 0)`)
183
+ // // .attr('rx', params.barRadius == true ? barHalfWidth
184
+ // // : params.barRadius == false ? 0
185
+ // // : typeof params.barRadius == 'number' ? params.barRadius
186
+ // // : 0)
187
+ // .attr('rx', transformedBarRadius[0])
188
+ // .attr('ry', transformedBarRadius[1])
189
+ // .transition()
190
+ // .duration(transitionItem)
191
+ // .ease(getD3TransitionEase(chartParams.transitionEase))
192
+ // .delay((d, i) => d.groupIndex * delayGroup)
193
+ // .attr('height', d => Math.abs(d.axisYFromZero))
194
+ // })
195
+ // })
196
+
197
+ const graphicBarSelection: d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown> = graphicGSelection.selectAll(`rect.${rectClassName}`)
198
+
199
+ return graphicBarSelection
200
+ }
201
+
202
+ function renderClipPath ({ defsSelection, clipPathData }: {
203
+ defsSelection: d3.Selection<SVGDefsElement, any, any, any>
204
+ clipPathData: ClipPathDatum[]
205
+ }) {
206
+ const clipPath = defsSelection
207
+ .selectAll<SVGClipPathElement, Layout>('clipPath')
208
+ .data(clipPathData)
209
+ .join(
210
+ enter => {
211
+ return enter
212
+ .append('clipPath')
213
+ },
214
+ update => update,
215
+ exit => exit.remove()
216
+ )
217
+ .attr('id', d => d.id)
218
+ .each((d, i, g) => {
219
+ const rect = d3.select(g[i])
220
+ .selectAll<SVGRectElement, typeof d>('rect')
221
+ .data([d])
222
+ .join(
223
+ enter => {
224
+ return enter
225
+ .append('rect')
226
+ },
227
+ update => update,
228
+ exit => exit.remove()
229
+ )
230
+ .attr('x', 0)
231
+ .attr('y', 0)
232
+ .attr('width', _d => _d.width)
233
+ .attr('height', _d => _d.height)
234
+ })
235
+ }
236
+
237
+ function highlight ({ selection, ids, fullChartParams }: {
238
+ selection: d3.Selection<any, ComputedDatumGrid, any, any>
239
+ ids: string[]
240
+ fullChartParams: ChartParams
241
+ }) {
242
+ selection.interrupt('highlight')
243
+
244
+ if (!ids.length) {
245
+ // remove highlight
246
+ selection
247
+ .transition('highlight')
248
+ .duration(200)
249
+ .style('opacity', 1)
250
+ return
251
+ }
252
+
253
+ selection
254
+ .each((d, i, n) => {
255
+ if (ids.includes(d.id)) {
256
+ d3.select(n[i])
257
+ .style('opacity', 1)
258
+ } else {
259
+ d3.select(n[i])
260
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
261
+ }
262
+ })
263
+ }
264
+
265
+
266
+ export const createBaseBars: BasePluginFn<BaseBarsContext> = (pluginName: string, {
267
+ selection,
268
+ computedData$,
269
+ visibleComputedData$,
270
+ existSeriesLabels$,
271
+ SeriesDataMap$,
272
+ GroupDataMap$,
273
+ fullParams$,
274
+ fullChartParams$,
275
+ gridAxesTransform$,
276
+ gridGraphicTransform$,
277
+ gridGraphicReverseScale$,
278
+ gridAxesSize$,
279
+ gridHighlight$,
280
+ gridContainer$,
281
+ isSeriesPositionSeprate$,
282
+ event$
283
+ }) => {
284
+
285
+ const destroy$ = new Subject()
286
+
287
+ const clipPathID = getUniID(pluginName, 'clipPath-box')
288
+ const rectClassName = getClassName(pluginName, 'rect')
289
+
290
+ // const seriesSelection$ = computedData$.pipe(
291
+ // takeUntil(destroy$),
292
+ // distinctUntilChanged((a, b) => {
293
+ // // 只有當series的數量改變時,才重新計算
294
+ // return a.length === b.length
295
+ // }),
296
+ // map((computedData, i) => {
297
+ // return selection
298
+ // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${seriesClassName}`)
299
+ // .data(computedData, d => d[0] ? d[0].seriesIndex : i)
300
+ // .join(
301
+ // enter => {
302
+ // return enter
303
+ // .append('g')
304
+ // .classed(seriesClassName, true)
305
+ // .each((d, i, g) => {
306
+ // const axesSelection = d3.select(g[i])
307
+ // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${axesClassName}`)
308
+ // .data([i])
309
+ // .join(
310
+ // enter => {
311
+ // return enter
312
+ // .append('g')
313
+ // .classed(axesClassName, true)
314
+ // .attr('clip-path', `url(#${clipPathID})`)
315
+ // .each((d, i, g) => {
316
+ // const defsSelection = d3.select(g[i])
317
+ // .selectAll<SVGDefsElement, any>('defs')
318
+ // .data([i])
319
+ // .join('defs')
320
+
321
+ // const graphicGSelection = d3.select(g[i])
322
+ // .selectAll<SVGGElement, any>('g')
323
+ // .data([i])
324
+ // .join('g')
325
+ // .classed(graphicClassName, true)
326
+ // })
327
+ // },
328
+ // update => update,
329
+ // exit => exit.remove()
330
+ // )
331
+ // })
332
+ // },
333
+ // update => update,
334
+ // exit => exit.remove()
335
+ // )
336
+ // })
337
+ // )
338
+
339
+ // combineLatest({
340
+ // seriesSelection: seriesSelection$,
341
+ // gridContainer: gridContainer$
342
+ // }).pipe(
343
+ // takeUntil(destroy$),
344
+ // switchMap(async d => d)
345
+ // ).subscribe(data => {
346
+ // data.seriesSelection
347
+ // .transition()
348
+ // .attr('transform', (d, i) => {
349
+ // const translate = data.gridContainer[i].translate
350
+ // const scale = data.gridContainer[i].scale
351
+ // return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
352
+ // })
353
+ // })
354
+
355
+
356
+ // const axesSelection$ = combineLatest({
357
+ // seriesSelection: seriesSelection$,
358
+ // gridAxesTransform: gridAxesTransform$
359
+ // }).pipe(
360
+ // takeUntil(destroy$),
361
+ // switchMap(async d => d),
362
+ // map(data => {
363
+ // return data.seriesSelection
364
+ // .select<SVGGElement>(`g.${axesClassName}`)
365
+ // .style('transform', data.gridAxesTransform.value)
366
+ // })
367
+ // )
368
+ // const defsSelection$ = axesSelection$.pipe(
369
+ // takeUntil(destroy$),
370
+ // map(axesSelection => {
371
+ // return axesSelection.select<SVGDefsElement>('defs')
372
+ // })
373
+ // )
374
+ // const graphicGSelection$ = combineLatest({
375
+ // axesSelection: axesSelection$,
376
+ // gridGraphicTransform: gridGraphicTransform$
377
+ // }).pipe(
378
+ // takeUntil(destroy$),
379
+ // switchMap(async d => d),
380
+ // map(data => {
381
+ // const graphicGSelection = data.axesSelection
382
+ // .select<SVGGElement>(`g.${graphicClassName}`)
383
+ // graphicGSelection
384
+ // .transition()
385
+ // .duration(50)
386
+ // .style('transform', data.gridGraphicTransform.value)
387
+ // return graphicGSelection
388
+ // })
389
+ // )
390
+
391
+ const {
392
+ seriesSelection$,
393
+ axesSelection$,
394
+ defsSelection$,
395
+ graphicGSelection$
396
+ } = gridSelectionsObservable({
397
+ selection,
398
+ pluginName,
399
+ clipPathID,
400
+ existSeriesLabels$,
401
+ gridContainer$,
402
+ gridAxesTransform$,
403
+ gridGraphicTransform$
404
+ })
405
+
406
+ const zeroYArr$ = visibleComputedData$.pipe(
407
+ takeUntil(destroy$),
408
+ map(data => {
409
+ return data.map(d => {
410
+ return d[0] ? d[0].axisY - d[0].axisYFromZero : 0
411
+ })
412
+ }),
413
+ distinctUntilChanged()
414
+ )
415
+
416
+ const barWidth$ = combineLatest({
417
+ computedData: computedData$,
418
+ visibleComputedData: visibleComputedData$,
419
+ params: fullParams$,
420
+ gridAxesSize: gridAxesSize$,
421
+ isSeriesPositionSeprate: isSeriesPositionSeprate$
422
+ }).pipe(
423
+ takeUntil(destroy$),
424
+ switchMap(async d => d),
425
+ map(data => {
426
+ if (data.params.barWidth) {
427
+ return data.params.barWidth
428
+ } else if (data.isSeriesPositionSeprate) {
429
+ return calcBarWidth({
430
+ axisWidth: data.gridAxesSize.width,
431
+ groupAmount: data.computedData[0] ? data.computedData[0].length : 0,
432
+ barAmountOfGroup: 1,
433
+ barPadding: data.params.barPadding,
434
+ barGroupPadding: data.params.barGroupPadding
435
+ })
436
+ } else {
437
+ return calcBarWidth({
438
+ axisWidth: data.gridAxesSize.width,
439
+ groupAmount: data.computedData[0] ? data.computedData[0].length : 0,
440
+ barAmountOfGroup: data.visibleComputedData.length,
441
+ barPadding: data.params.barPadding,
442
+ barGroupPadding: data.params.barGroupPadding
443
+ })
444
+ }
445
+ }),
446
+ distinctUntilChanged()
447
+ )
448
+
449
+ // 圓角的值 [rx, ry]
450
+ const transformedBarRadius$: Observable<[number, number][]> = combineLatest({
451
+ computedData: computedData$,
452
+ // gridGraphicTransform: gridGraphicTransform$,
453
+ barWidth: barWidth$,
454
+ params: fullParams$,
455
+ // gridContainer: gridContainer$,
456
+ // gridAxesTransform: gridAxesTransform$
457
+ gridGraphicReverseScale: gridGraphicReverseScale$
458
+ }).pipe(
459
+ takeUntil(destroy$),
460
+ switchMap(async data => data),
461
+ map(data => {
462
+ const barHalfWidth = data.barWidth! / 2
463
+ const radius = data.params.barRadius === true ? barHalfWidth
464
+ : data.params.barRadius === false ? 0
465
+ : typeof data.params.barRadius == 'number' ? data.params.barRadius
466
+ : 0
467
+
468
+ return data.computedData.map((series, seriesIndex) => {
469
+ const gridGraphicReverseScale = data.gridGraphicReverseScale[seriesIndex] ?? data.gridGraphicReverseScale[0]
470
+
471
+ let transformedRx = radius * gridGraphicReverseScale[0]
472
+ let transformedRy = radius * gridGraphicReverseScale[1]
473
+ // if (radius == 0) {
474
+ // transformedRx = 0
475
+ // transformedRy = 0
476
+ // } else if (data.gridAxesTransform.rotate == 0) {
477
+ // transformedRx = radius
478
+ // // 抵消外層scale的變型
479
+ // / data.gridGraphicTransform.scale[0] / data.gridContainer[0].scale[0]
480
+ // transformedRy = radius
481
+ // // 抵消外層scale的變型
482
+ // / data.gridGraphicTransform.scale[1] / data.gridContainer[0].scale[1]
483
+ // } else if (data.gridAxesTransform.rotate != 0) {
484
+ // transformedRx = radius
485
+ // // 抵消外層scale的變型,由於有90度的旋轉,所以外層 (container) x和y的scale要互換
486
+ // / data.gridGraphicTransform.scale[0] / data.gridContainer[0].scale[1]
487
+ // transformedRy = radius
488
+ // // 抵消外層scale的變型,由於有90度的旋轉,所以外層 (container) x和y的scale要互換
489
+ // / data.gridGraphicTransform.scale[1] / data.gridContainer[0].scale[0]
490
+ // }
491
+
492
+ // 如果計算出來的x圓角值大於寬度一半則進行修正
493
+ if (transformedRx > barHalfWidth) {
494
+ const rScale = barHalfWidth / transformedRx
495
+ transformedRx = transformedRx * rScale
496
+ transformedRy = transformedRy * rScale
497
+ }
498
+
499
+ return [transformedRx, transformedRy]
500
+ })
501
+ })
502
+ )
503
+
504
+
505
+ const seriesLabels$ = visibleComputedData$.pipe(
506
+ takeUntil(destroy$),
507
+ map(data => {
508
+ const SeriesLabelSet: Set<string> = new Set()
509
+ data.forEach(d => {
510
+ d.forEach(_d => {
511
+ SeriesLabelSet.add(_d.seriesLabel)
512
+ })
513
+ })
514
+ return Array.from(SeriesLabelSet)
515
+ })
516
+ )
517
+
518
+ const groupLabels$ = visibleComputedData$.pipe(
519
+ takeUntil(destroy$),
520
+ map(data => {
521
+ const GroupLabelSet: Set<string> = new Set()
522
+ data.forEach(d => {
523
+ d.forEach(_d => {
524
+ GroupLabelSet.add(_d.groupLabel)
525
+ })
526
+ })
527
+ return Array.from(GroupLabelSet)
528
+ })
529
+ )
530
+
531
+ const barScale$ = combineLatest({
532
+ seriesLabels: seriesLabels$,
533
+ barWidth: barWidth$,
534
+ params: fullParams$,
535
+ }).pipe(
536
+ takeUntil(destroy$),
537
+ switchMap(async d => d),
538
+ map(data => {
539
+ return makeBarScale(data.barWidth, data.seriesLabels, data.params)
540
+ })
541
+ )
542
+
543
+ const transitionDuration$ = fullChartParams$.pipe(
544
+ takeUntil(destroy$),
545
+ map(d => d.transitionDuration),
546
+ distinctUntilChanged()
547
+ )
548
+
549
+ const delayGroup$ = new Observable<number>(subscriber => {
550
+ combineLatest({
551
+ groupLabels: groupLabels$,
552
+ transitionDuration: transitionDuration$,
553
+ }).pipe(
554
+ switchMap(async d => d)
555
+ ).subscribe(data => {
556
+ const delay = calcDelayGroup(data.groupLabels.length, data.transitionDuration)
557
+ subscriber.next(delay)
558
+ })
559
+ }).pipe(
560
+ takeUntil(destroy$),
561
+ distinctUntilChanged()
562
+ )
563
+
564
+ const transitionItem$ = new Observable<number>(subscriber => {
565
+ combineLatest({
566
+ groupLabels: groupLabels$,
567
+ transitionDuration: transitionDuration$
568
+ }).pipe(
569
+ switchMap(async d => d)
570
+ ).subscribe(data => {
571
+ const transition = calctransitionItem(data.groupLabels.length, data.transitionDuration)
572
+ subscriber.next(transition)
573
+ })
574
+ }).pipe(
575
+ takeUntil(destroy$),
576
+ distinctUntilChanged()
577
+ )
578
+
579
+ combineLatest({
580
+ defsSelection: defsSelection$,
581
+ gridAxesSize: gridAxesSize$,
582
+ }).pipe(
583
+ takeUntil(destroy$),
584
+ switchMap(async d => d)
585
+ ).subscribe(data => {
586
+ const clipPathData = [{
587
+ id: clipPathID,
588
+ width: data.gridAxesSize.width,
589
+ height: data.gridAxesSize.height
590
+ }]
591
+ renderClipPath({
592
+ defsSelection: data.defsSelection,
593
+ clipPathData
594
+ })
595
+ })
596
+
597
+
598
+ const highlightTarget$ = fullChartParams$.pipe(
599
+ takeUntil(destroy$),
600
+ map(d => d.highlightTarget),
601
+ distinctUntilChanged()
602
+ )
603
+
604
+ const barSelection$ = new Subject<d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown>>()
605
+
606
+ combineLatest({
607
+ graphicGSelection: graphicGSelection$,
608
+ computedData: computedData$,
609
+ // barData$: barData$,
610
+ zeroYArr: zeroYArr$,
611
+ groupLabels: groupLabels$,
612
+ barScale: barScale$,
613
+ params: fullParams$,
614
+ chartParams: fullChartParams$,
615
+ highlightTarget: highlightTarget$,
616
+ barWidth: barWidth$,
617
+ transformedBarRadius: transformedBarRadius$,
618
+ delayGroup: delayGroup$,
619
+ transitionItem: transitionItem$,
620
+ SeriesDataMap: SeriesDataMap$,
621
+ GroupDataMap: GroupDataMap$,
622
+ isSeriesPositionSeprate: isSeriesPositionSeprate$
623
+ }).pipe(
624
+ takeUntil(destroy$),
625
+ switchMap(async (d) => d),
626
+ ).subscribe(data => {
627
+
628
+ const barSelection = renderRectBars({
629
+ graphicGSelection: data.graphicGSelection,
630
+ rectClassName,
631
+ computedData: data.computedData,
632
+ zeroYArr: data.zeroYArr,
633
+ groupLabels: data.groupLabels,
634
+ barScale: data.barScale,
635
+ params: data.params,
636
+ chartParams: data.chartParams,
637
+ barWidth: data.barWidth,
638
+ transformedBarRadius: data.transformedBarRadius,
639
+ delayGroup: data.delayGroup,
640
+ transitionItem: data.transitionItem,
641
+ isSeriesPositionSeprate: data.isSeriesPositionSeprate
642
+ })
643
+
644
+ barSelection!
645
+ .on('mouseover', (event, datum) => {
646
+ event.stopPropagation()
647
+
648
+ event$.next({
649
+ type: 'grid',
650
+ eventName: 'mouseover',
651
+ pluginName,
652
+ highlightTarget: data.highlightTarget,
653
+ datum,
654
+ gridIndex: datum.gridIndex,
655
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
656
+ seriesIndex: datum.seriesIndex,
657
+ seriesLabel: datum.seriesLabel,
658
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
659
+ groupIndex: datum.groupIndex,
660
+ groupLabel: datum.groupLabel,
661
+ event,
662
+ data: data.computedData
663
+ })
664
+ })
665
+ .on('mousemove', (event, datum) => {
666
+ event.stopPropagation()
667
+
668
+ event$.next({
669
+ type: 'grid',
670
+ eventName: 'mousemove',
671
+ pluginName,
672
+ highlightTarget: data.highlightTarget,
673
+ datum,
674
+ gridIndex: datum.gridIndex,
675
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
676
+ seriesIndex: datum.seriesIndex,
677
+ seriesLabel: datum.seriesLabel,
678
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
679
+ groupIndex: datum.groupIndex,
680
+ groupLabel: datum.groupLabel,
681
+ event,
682
+ data: data.computedData
683
+ })
684
+ })
685
+ .on('mouseout', (event, datum) => {
686
+ event.stopPropagation()
687
+
688
+ event$.next({
689
+ type: 'grid',
690
+ eventName: 'mouseout',
691
+ pluginName,
692
+ highlightTarget: data.highlightTarget,
693
+ datum,
694
+ gridIndex: datum.gridIndex,
695
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
696
+ seriesIndex: datum.seriesIndex,
697
+ seriesLabel: datum.seriesLabel,
698
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
699
+ groupIndex: datum.groupIndex,
700
+ groupLabel: datum.groupLabel,
701
+ event,
702
+ data: data.computedData
703
+ })
704
+ })
705
+ .on('click', (event, datum) => {
706
+ event.stopPropagation()
707
+
708
+ event$.next({
709
+ type: 'grid',
710
+ eventName: 'click',
711
+ pluginName,
712
+ highlightTarget: data.highlightTarget,
713
+ datum,
714
+ gridIndex: datum.gridIndex,
715
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
716
+ seriesIndex: datum.seriesIndex,
717
+ seriesLabel: datum.seriesLabel,
718
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
719
+ groupIndex: datum.groupIndex,
720
+ groupLabel: datum.groupLabel,
721
+ event,
722
+ data: data.computedData
723
+ })
724
+ })
725
+
726
+ barSelection$.next(barSelection!)
727
+ })
728
+
729
+
730
+ combineLatest({
731
+ barSelection: barSelection$,
732
+ highlight: gridHighlight$.pipe(
733
+ map(data => data.map(d => d.id))
734
+ ),
735
+ fullChartParams: fullChartParams$
736
+ }).pipe(
737
+ takeUntil(destroy$),
738
+ switchMap(async d => d)
739
+ ).subscribe(data => {
740
+ highlight({
741
+ selection: data.barSelection,
742
+ ids: data.highlight,
743
+ fullChartParams: data.fullChartParams
744
+ })
745
+ })
746
+
747
+
748
+ return () => {
749
+ destroy$.next(undefined)
750
+ }
749
751
  }