@orbcharts/plugins-basic 3.0.0-alpha.50 → 3.0.0-alpha.52

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