@orbcharts/plugins-basic 3.0.1 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/LICENSE +200 -200
  2. package/dist/lib/gridObservables.d.ts +1 -0
  3. package/dist/orbcharts-plugins-basic.es.js +14800 -13543
  4. package/dist/orbcharts-plugins-basic.umd.js +129 -99
  5. package/dist/src/base/BaseOrdinalBubbles.d.ts +26 -0
  6. package/dist/src/base/BaseRacingBars.d.ts +2 -1
  7. package/dist/src/base/BaseRacingLabels.d.ts +1 -0
  8. package/dist/src/base/BaseXZoom.d.ts +18 -0
  9. package/dist/src/grid/gridObservables.d.ts +4 -7
  10. package/dist/src/multiValue/defaults.d.ts +5 -1
  11. package/dist/src/multiValue/index.d.ts +4 -0
  12. package/dist/src/multiValue/multiValueObservables.d.ts +14 -1
  13. package/dist/src/multiValue/plugins/OrdinalAux.d.ts +3 -0
  14. package/dist/src/multiValue/plugins/OrdinalAxis.d.ts +3 -0
  15. package/dist/src/multiValue/plugins/OrdinalBubbles.d.ts +3 -0
  16. package/dist/src/multiValue/plugins/OrdinalZoom.d.ts +1 -0
  17. package/lib/core-types.ts +7 -7
  18. package/lib/core.ts +6 -6
  19. package/lib/gridObservables.ts +6 -0
  20. package/lib/plugins-basic-types.ts +6 -6
  21. package/package.json +48 -44
  22. package/src/base/BaseBars.ts +765 -765
  23. package/src/base/BaseBarsTriangle.ts +676 -676
  24. package/src/base/BaseDots.ts +464 -464
  25. package/src/base/BaseGroupAxis.ts +691 -691
  26. package/src/base/BaseLegend.ts +684 -684
  27. package/src/base/BaseLineAreas.ts +629 -629
  28. package/src/base/BaseLines.ts +706 -706
  29. package/src/base/BaseOrdinalBubbles.ts +728 -0
  30. package/src/base/BaseRacingBars.ts +582 -551
  31. package/src/base/BaseRacingLabels.ts +404 -396
  32. package/src/base/BaseRacingValueLabels.ts +403 -403
  33. package/src/base/BaseStackedBars.ts +782 -782
  34. package/src/base/BaseTooltip.ts +386 -386
  35. package/src/base/BaseValueAxis.ts +600 -600
  36. package/src/base/BaseXAxis.ts +427 -427
  37. package/src/base/BaseXZoom.ts +242 -0
  38. package/src/base/BaseYAxis.ts +389 -389
  39. package/src/base/types.ts +2 -2
  40. package/src/const.ts +30 -30
  41. package/src/grid/defaults.ts +213 -213
  42. package/src/grid/gridObservables.ts +635 -612
  43. package/src/grid/index.ts +16 -16
  44. package/src/grid/plugins/Bars.ts +69 -69
  45. package/src/grid/plugins/BarsPN.ts +66 -66
  46. package/src/grid/plugins/BarsTriangle.ts +73 -73
  47. package/src/grid/plugins/Dots.ts +68 -68
  48. package/src/grid/plugins/GridLegend.ts +107 -107
  49. package/src/grid/plugins/GridTooltip.ts +66 -66
  50. package/src/grid/plugins/GroupAux.ts +1095 -1120
  51. package/src/grid/plugins/GroupAxis.ts +73 -73
  52. package/src/grid/plugins/GroupZoom.ts +218 -218
  53. package/src/grid/plugins/LineAreas.ts +65 -65
  54. package/src/grid/plugins/Lines.ts +59 -59
  55. package/src/grid/plugins/StackedBars.ts +64 -64
  56. package/src/grid/plugins/StackedValueAxis.ts +96 -96
  57. package/src/grid/plugins/ValueAxis.ts +94 -94
  58. package/src/index.ts +6 -6
  59. package/src/multiGrid/defaults.ts +244 -244
  60. package/src/multiGrid/index.ts +14 -14
  61. package/src/multiGrid/multiGridObservables.ts +50 -50
  62. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  63. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  64. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  65. package/src/multiGrid/plugins/MultiGridLegend.ts +169 -169
  66. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  67. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  68. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  69. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  70. package/src/multiGrid/plugins/MultiStackedBars.ts +106 -106
  71. package/src/multiGrid/plugins/MultiStackedValueAxis.ts +134 -134
  72. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  73. package/src/multiGrid/plugins/OverlappingStackedValueAxes.ts +300 -300
  74. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  75. package/src/multiValue/defaults.ts +523 -431
  76. package/src/multiValue/index.ts +16 -12
  77. package/src/multiValue/multiValueObservables.ts +781 -666
  78. package/src/multiValue/plugins/MultiValueLegend.ts +107 -107
  79. package/src/multiValue/plugins/MultiValueTooltip.ts +66 -66
  80. package/src/multiValue/plugins/OrdinalAux.ts +661 -0
  81. package/src/multiValue/plugins/OrdinalAxis.ts +525 -0
  82. package/src/multiValue/plugins/OrdinalBubbles.ts +226 -0
  83. package/src/multiValue/plugins/OrdinalZoom.ts +57 -0
  84. package/src/multiValue/plugins/RacingBars.ts +375 -373
  85. package/src/multiValue/plugins/RacingCounterTexts.ts +300 -300
  86. package/src/multiValue/plugins/RacingValueAxis.ts +114 -114
  87. package/src/multiValue/plugins/Scatter.ts +486 -426
  88. package/src/multiValue/plugins/ScatterBubbles.ts +635 -554
  89. package/src/multiValue/plugins/XAxis.ts +107 -107
  90. package/src/multiValue/plugins/XYAux.ts +683 -682
  91. package/src/multiValue/plugins/XYAxes.ts +194 -194
  92. package/src/multiValue/plugins/XYAxes_legacy.ts +683 -683
  93. package/src/multiValue/plugins/XZoom.ts +40 -299
  94. package/src/noneData/defaults.ts +102 -102
  95. package/src/noneData/index.ts +3 -3
  96. package/src/noneData/plugins/Container.ts +27 -27
  97. package/src/noneData/plugins/Tooltip.ts +373 -373
  98. package/src/relationship/defaults.ts +221 -221
  99. package/src/relationship/index.ts +5 -5
  100. package/src/relationship/plugins/ForceDirected.ts +1173 -1173
  101. package/src/relationship/plugins/ForceDirectedBubbles.ts +1411 -1411
  102. package/src/relationship/plugins/RelationshipLegend.ts +100 -100
  103. package/src/relationship/plugins/RelationshipTooltip.ts +66 -66
  104. package/src/relationship/relationshipObservables.ts +49 -49
  105. package/src/series/defaults.ts +221 -221
  106. package/src/series/index.ts +9 -9
  107. package/src/series/plugins/Bubbles.ts +636 -636
  108. package/src/series/plugins/Pie.ts +623 -623
  109. package/src/series/plugins/PieEventTexts.ts +284 -284
  110. package/src/series/plugins/PieLabels.ts +640 -640
  111. package/src/series/plugins/Rose.ts +516 -516
  112. package/src/series/plugins/RoseLabels.ts +600 -600
  113. package/src/series/plugins/SeriesLegend.ts +107 -107
  114. package/src/series/plugins/SeriesTooltip.ts +66 -66
  115. package/src/series/seriesObservables.ts +145 -145
  116. package/src/series/seriesUtils.ts +51 -51
  117. package/src/tree/defaults.ts +102 -102
  118. package/src/tree/index.ts +4 -4
  119. package/src/tree/plugins/TreeLegend.ts +100 -100
  120. package/src/tree/plugins/TreeMap.ts +341 -341
  121. package/src/tree/plugins/TreeTooltip.ts +66 -66
  122. package/src/utils/commonUtils.ts +31 -31
  123. package/src/utils/d3Graphics.ts +176 -176
  124. package/src/utils/d3Utils.ts +92 -92
  125. package/src/utils/observables.ts +14 -14
  126. package/src/utils/orbchartsUtils.ts +129 -129
  127. package/tsconfig.base.json +13 -13
  128. package/tsconfig.json +2 -2
  129. package/vite.config.js +22 -22
  130. package/dist/src/multiValue/plugins/OrdinalXAxis.d.ts +0 -0
  131. package/dist/src/multiValue/plugins/RankingAxis_legacy.d.ts +0 -0
  132. package/src/multiValue/plugins/OrdinalXAxis.ts +0 -0
  133. package/src/multiValue/plugins/RankingAxis_legacy.ts +0 -109
@@ -1,396 +1,404 @@
1
- import * as d3 from 'd3'
2
- import {
3
- Observable,
4
- Subject,
5
- combineLatest,
6
- takeUntil,
7
- map,
8
- distinctUntilChanged,
9
- of,
10
- iif,
11
- switchMap,
12
- shareReplay
13
- } from 'rxjs'
14
- import type {
15
- ChartParams,
16
- ComputedDatumMultiValue,
17
- ComputedDataMultiValue,
18
- ComputedXYDatumMultiValue,
19
- ContainerSize,
20
- ContainerPositionScaled,
21
- DataFormatterMultiValue,
22
- Layout
23
- } from '../../lib/core-types'
24
- import type { BaseRacingLabelsParams } from '../../lib/plugins-basic-types'
25
- import type { BasePluginFn } from './types'
26
- import { getColor, getDatumColor, getClassName, getUniID } from '../utils/orbchartsUtils'
27
- import { multiValueContainerSelectionsObservable } from '../multiValue/multiValueObservables'
28
-
29
- interface BaseRacingAxisContext {
30
- selection: d3.Selection<any, unknown, any, unknown>
31
- computedData$: Observable<ComputedDataMultiValue>
32
- visibleComputedRankingData$: Observable<ComputedDatumMultiValue[][]>
33
- rankingScaleList$: Observable<Array<d3.ScalePoint<string>>>
34
- xScale$: Observable<(n: number) => number>
35
- fullParams$: Observable<BaseRacingLabelsParams>
36
- fullDataFormatter$: Observable<DataFormatterMultiValue>
37
- fullChartParams$: Observable<ChartParams>
38
- // layout$: Observable<Layout>
39
- containerPosition$: Observable<ContainerPositionScaled[]>
40
- containerSize$: Observable<ContainerSize>
41
- isCategorySeprate$: Observable<boolean>
42
- // xyValueIndex$: Observable<[number, number]>
43
- }
44
-
45
- type ClipPathDatum = {
46
- id: string;
47
- // x: number;
48
- // y: number;
49
- width: number;
50
- height: number;
51
- }
52
-
53
- // const pluginName = 'RacingAxis'
54
-
55
- const yTickTextAnchor = 'end'
56
- const yTickDominantBaseline = 'middle'
57
- const yAxisLabelAnchor = 'end'
58
- const yAxisLabelDominantBaseline = 'auto'
59
- // const textClassName = getClassName(pluginName, 'yLabel')
60
-
61
- function renderRacingAxisLabel ({ selection, textClassName, fullParams, containerSize, fullDataFormatter, fullChartParams, textReverseTransform }: {
62
- selection: d3.Selection<SVGGElement, any, any, any>,
63
- textClassName: string
64
- fullParams: BaseRacingLabelsParams
65
- // axisLabelAlign: TextAlign
66
- containerSize: ContainerSize
67
- fullDataFormatter: DataFormatterMultiValue,
68
- fullChartParams: ChartParams
69
- textReverseTransform: string,
70
- }) {
71
- const offsetX = fullParams.barLabel.padding - fullParams.axisLabel.offset[0]
72
- const offsetY = - fullParams.barLabel.padding - fullParams.axisLabel.offset[1]
73
- let labelX = - offsetX
74
- let labelY = - offsetY
75
-
76
- selection
77
- .attr('transform', d => `translate(0, ${containerSize.height})`)
78
- .selectAll<SVGTextElement, BaseRacingLabelsParams>(`text`)
79
- .data([fullParams])
80
- .join(
81
- enter => {
82
- return enter
83
- .append('text')
84
- .style('font-weight', 'bold')
85
- },
86
- update => update,
87
- exit => exit.remove()
88
- )
89
- .attr('text-anchor', yAxisLabelAnchor)
90
- .attr('dominant-baseline', yAxisLabelDominantBaseline)
91
- .attr('font-size', fullChartParams.styles.textSize)
92
- .style('fill', getColor(fullParams.axisLabel.colorType, fullChartParams))
93
- // .style('transform', textReverseTransform)
94
- // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
95
- .attr('x', labelX)
96
- .attr('y', labelY)
97
- .text(d => fullDataFormatter.yAxis.label)
98
- }
99
-
100
- function renderRacingLabels ({ selection, fullParams, fullChartParams, rankingScale, valueScale, categoryData }: {
101
- selection: d3.Selection<SVGGElement, any, any, any>,
102
- fullParams: BaseRacingLabelsParams
103
- fullChartParams: ChartParams
104
- rankingScale: d3.ScalePoint<string>
105
- valueScale: ((n: number) => number)
106
- categoryData: ComputedDatumMultiValue[]
107
- // textReverseTransformWithRotate: string,
108
- // xyValueIndex: [number, number]
109
- }) {
110
- const labelData = fullParams.barLabel.position === 'none' ? [] : categoryData
111
-
112
- const labelSelection = selection
113
- .selectAll<SVGGElement, string>(`text`)
114
- .data(labelData, (d: ComputedDatumMultiValue) => d.id)
115
- // .join('g')
116
- // .classed(boxClassName, true)
117
- .join(
118
- enter => {
119
- return enter
120
- .append('text')
121
- .style('font-weight', 'bold')
122
- .attr('x', d => valueScale(d.value[d.xValueIndex] ?? 0) - fullParams.barLabel.padding)
123
- .attr('y', d => rankingScale(d.label)!)
124
- },
125
- update => {
126
- return update
127
- .transition()
128
- .duration(fullChartParams.transitionDuration)
129
- .ease(d3.easeLinear)
130
- // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
131
- .attr('x', d => valueScale(d.value[d.xValueIndex] ?? 0) - fullParams.barLabel.padding)
132
- .attr('y', d => rankingScale(d.label)!)
133
- },
134
- exit => exit.remove()
135
- )
136
- .attr('text-anchor', yTickTextAnchor)
137
- .attr('dominant-baseline', yTickDominantBaseline)
138
- .attr('font-size', fullChartParams.styles.textSize)
139
- .style('fill', d => getDatumColor({
140
- colorType: fullParams.barLabel.colorType,
141
- fullChartParams: fullChartParams,
142
- datum: d
143
- }))
144
- // .style('transform', textReverseTransformWithRotate)
145
- .text(d => d.label)
146
-
147
- return labelSelection
148
- }
149
-
150
-
151
- function renderClipPath ({ defsSelection, clipPathData }: {
152
- defsSelection: d3.Selection<SVGDefsElement, any, any, any>
153
- clipPathData: ClipPathDatum[]
154
- // textReverseTransform: string
155
- }) {
156
- const clipPath = defsSelection
157
- .selectAll<SVGClipPathElement, Layout>('clipPath')
158
- .data(clipPathData)
159
- .join(
160
- enter => {
161
- return enter
162
- .append('clipPath')
163
- },
164
- update => update,
165
- exit => exit.remove()
166
- )
167
- .attr('id', d => d.id)
168
- // .attr('transform', textReverseTransform)
169
- .each((d, i, g) => {
170
- const rect = d3.select(g[i])
171
- .selectAll<SVGRectElement, typeof d>('rect')
172
- .data([d])
173
- .join(
174
- enter => {
175
- return enter
176
- .append('rect')
177
- },
178
- update => update,
179
- exit => exit.remove()
180
- )
181
- .attr('x', _d => - _d.width)
182
- .attr('y', 0)
183
- .attr('width', _d => _d.width * 2)
184
- .attr('height', _d => _d.height)
185
- })
186
- }
187
-
188
- export const createBaseRacingLabels: BasePluginFn<BaseRacingAxisContext> = (pluginName: string, {
189
- selection,
190
- computedData$,
191
- visibleComputedRankingData$,
192
- rankingScaleList$,
193
- xScale$,
194
- fullParams$,
195
- fullDataFormatter$,
196
- fullChartParams$,
197
- // layout$,
198
- containerPosition$,
199
- containerSize$,
200
- isCategorySeprate$,
201
- // xyValueIndex$
202
- }) => {
203
-
204
- const destroy$ = new Subject()
205
-
206
- // const containerClassName = getClassName(pluginName, 'container')
207
- const boxClassName = getClassName(pluginName, 'box')
208
- const textClassName = getClassName(pluginName, 'text')
209
- const clipPathID = getUniID(pluginName, 'clipPath-box')
210
-
211
- const containerSelection$ = multiValueContainerSelectionsObservable({
212
- selection,
213
- pluginName,
214
- clipPathID: null,
215
- computedData$,
216
- containerPosition$,
217
- isCategorySeprate$,
218
- }).pipe(
219
- takeUntil(destroy$),
220
- )
221
-
222
- // const containerSelection$ = combineLatest({
223
- // computedData: computedData$.pipe(
224
- // distinctUntilChanged((a, b) => {
225
- // // 只有當series的數量改變時,才重新計算
226
- // return a.length === b.length
227
- // }),
228
- // ),
229
- // isCategorySeprate: isCategorySeprate$
230
- // }).pipe(
231
- // takeUntil(destroy$),
232
- // switchMap(async (d) => d),
233
- // map(data => {
234
- // return data.isCategorySeprate
235
- // // category分開的時候顯示各別axis
236
- // ? data.computedData
237
- // // category合併的時候只顯示第一個axis
238
- // : [data.computedData[0]]
239
- // }),
240
- // map((computedData, i) => {
241
- // return selection
242
- // .selectAll<SVGGElement, ComputedDatumMultiValue[]>(`g.${containerClassName}`)
243
- // .data(computedData, d => d[0] ? d[0].categoryIndex : i)
244
- // .join('g')
245
- // .classed(containerClassName, true)
246
- // })
247
- // )
248
-
249
- // combineLatest({
250
- // containerSelection: containerSelection$,
251
- // containerPosition: containerPosition$
252
- // }).pipe(
253
- // takeUntil(destroy$),
254
- // switchMap(async d => d)
255
- // ).subscribe(data => {
256
- // data.containerSelection
257
- // .attr('transform', (d, i) => {
258
- // const containerPosition = data.containerPosition[i] ?? data.containerPosition[0]
259
- // const translate = containerPosition.translate
260
- // const scale = containerPosition.scale
261
- // // return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
262
- // return `translate(${translate[0]}, ${translate[1]})`
263
- // })
264
- // // .attr('opacity', 0)
265
- // // .transition()
266
- // // .attr('opacity', 1)
267
- // })
268
-
269
- const textReverseTransform$ = containerPosition$.pipe(
270
- takeUntil(destroy$),
271
- switchMap(async (d) => d),
272
- map(containerPosition => {
273
- // const axesRotateXYReverseValue = `rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
274
- // const axesRotateReverseValue = `rotate(${data.gridAxesReverseTransform.rotate}deg)`
275
- const containerScaleReverseValue = `scale(${1 / containerPosition[0].scale[0]}, ${1 / containerPosition[0].scale[1]})`
276
- // 抵消最外層scale
277
- return `${containerScaleReverseValue}`
278
- }),
279
- distinctUntilChanged()
280
- )
281
-
282
- // const textReverseTransformWithRotate$ = combineLatest({
283
- // textReverseTransform: textReverseTransform$,
284
- // fullParams: fullParams$,
285
- // }).pipe(
286
- // takeUntil(destroy$),
287
- // switchMap(async (d) => d),
288
- // map(data => {
289
- // // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
290
- // return `${data.textReverseTransform} rotate(${data.fullParams.barLabel.rotate}deg)`
291
- // })
292
- // )
293
-
294
- // const rankingLabelList$ = visibleComputedRankingData$.pipe(
295
- // takeUntil(destroy$),
296
- // map(data => {
297
- // return data.map(categoryData => categoryData.map(d => d.label))
298
- // })
299
- // )
300
-
301
- const valueScale$: Observable<((n: number) => number)> = fullParams$.pipe(
302
- map(fullParams => fullParams.barLabel.position),
303
- distinctUntilChanged(),
304
- switchMap(position => {
305
- return iif(
306
- () => position === 'inside',
307
- xScale$,
308
- of(() => 0)
309
- )
310
- })
311
- )
312
-
313
- containerSize$.subscribe(data => {
314
- const defsSelection = selection.selectAll<SVGDefsElement, any>('defs')
315
- .data([clipPathID])
316
- .join('defs')
317
- const clipPathData = [{
318
- id: clipPathID,
319
- width: data.width,
320
- height: data.height
321
- }]
322
- renderClipPath({
323
- defsSelection: defsSelection,
324
- clipPathData,
325
- // textReverseTransform: data.textReverseTransform
326
- })
327
- })
328
-
329
- combineLatest({
330
- containerSelection: containerSelection$,
331
- fullParams: fullParams$,
332
- // layout: layout$,
333
- containerSize: containerSize$,
334
- fullDataFormatter: fullDataFormatter$,
335
- fullChartParams: fullChartParams$,
336
- visibleComputedRankingData: visibleComputedRankingData$,
337
- // rankingLabelList: rankingLabelList$,
338
- rankingScaleList: rankingScaleList$,
339
- valueScale: valueScale$,
340
- textReverseTransform: textReverseTransform$,
341
- // textReverseTransformWithRotate: textReverseTransformWithRotate$,
342
- // xyValueIndex: xyValueIndex$
343
- }).pipe(
344
- takeUntil(destroy$),
345
- switchMap(async (d) => d),
346
- ).subscribe(data => {
347
-
348
- data.containerSelection.each((d, i, g) => {
349
- const _containerSelection = d3.select(g[i])
350
- // const rankingLabels = data.rankingLabelList[i]
351
- const rankingScale = data.rankingScaleList[i]
352
- if (!rankingScale) {
353
- return
354
- }
355
-
356
- // const containerClipPathID = `${clipPathID}-${i}`
357
- const axisSelection = _containerSelection
358
- .selectAll<SVGGElement, any>(`g.${boxClassName}`)
359
- .data([i])
360
- .join('g')
361
- .attr('class', boxClassName)
362
- .attr('clip-path', `url(#${clipPathID})`)
363
- const axisLabelSelection = _containerSelection
364
- .selectAll<SVGGElement, BaseRacingLabelsParams>(`g.${textClassName}`)
365
- .data([data.fullParams])
366
- .join('g')
367
- .classed(textClassName, true)
368
-
369
- renderRacingLabels({
370
- selection: axisSelection,
371
- fullParams: data.fullParams,
372
- fullChartParams: data.fullChartParams,
373
- rankingScale: rankingScale,
374
- categoryData: data.visibleComputedRankingData[i],
375
- // textReverseTransformWithRotate: data.textReverseTransformWithRotate,
376
- valueScale: data.valueScale,
377
- // xyValueIndex: data.xyValueIndex,
378
- })
379
-
380
- renderRacingAxisLabel({
381
- selection: axisLabelSelection,
382
- textClassName,
383
- fullParams: data.fullParams,
384
- containerSize: data.containerSize,
385
- fullDataFormatter: data.fullDataFormatter,
386
- fullChartParams: data.fullChartParams,
387
- textReverseTransform: data.textReverseTransform,
388
- })
389
- })
390
-
391
- })
392
-
393
- return () => {
394
- destroy$.next(undefined)
395
- }
396
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ Observable,
4
+ Subject,
5
+ combineLatest,
6
+ takeUntil,
7
+ map,
8
+ distinctUntilChanged,
9
+ of,
10
+ iif,
11
+ switchMap,
12
+ shareReplay
13
+ } from 'rxjs'
14
+ import type {
15
+ ChartParams,
16
+ ComputedDatumMultiValue,
17
+ ComputedDataMultiValue,
18
+ ComputedXYDatumMultiValue,
19
+ ContainerSize,
20
+ ContainerPositionScaled,
21
+ DataFormatterMultiValue,
22
+ Layout
23
+ } from '../../lib/core-types'
24
+ import type { BaseRacingLabelsParams } from '../../lib/plugins-basic-types'
25
+ import type { BasePluginFn } from './types'
26
+ import { getColor, getDatumColor, getClassName, getUniID } from '../utils/orbchartsUtils'
27
+ import { getD3TransitionEase } from '../utils/d3Utils'
28
+ import { multiValueContainerSelectionsObservable } from '../multiValue/multiValueObservables'
29
+
30
+ interface BaseRacingAxisContext {
31
+ selection: d3.Selection<any, unknown, any, unknown>
32
+ computedData$: Observable<ComputedDataMultiValue>
33
+ visibleComputedRankingData$: Observable<ComputedDatumMultiValue[][]>
34
+ rankingScaleList$: Observable<Array<d3.ScalePoint<string>>>
35
+ xScale$: Observable<(n: number) => number>
36
+ fullParams$: Observable<BaseRacingLabelsParams>
37
+ fullDataFormatter$: Observable<DataFormatterMultiValue>
38
+ fullChartParams$: Observable<ChartParams>
39
+ transitionEase$: Observable<string>
40
+ // layout$: Observable<Layout>
41
+ containerPosition$: Observable<ContainerPositionScaled[]>
42
+ containerSize$: Observable<ContainerSize>
43
+ isCategorySeprate$: Observable<boolean>
44
+ // xyValueIndex$: Observable<[number, number]>
45
+ }
46
+
47
+ type ClipPathDatum = {
48
+ id: string;
49
+ // x: number;
50
+ // y: number;
51
+ width: number;
52
+ height: number;
53
+ }
54
+
55
+ // const pluginName = 'RacingAxis'
56
+
57
+ const yTickTextAnchor = 'end'
58
+ const yTickDominantBaseline = 'middle'
59
+ const yAxisLabelAnchor = 'end'
60
+ const yAxisLabelDominantBaseline = 'auto'
61
+ // const textClassName = getClassName(pluginName, 'yLabel')
62
+
63
+ function renderRacingAxisLabel ({ selection, textClassName, fullParams, containerSize, fullDataFormatter, fullChartParams }: {
64
+ selection: d3.Selection<SVGGElement, any, any, any>,
65
+ textClassName: string
66
+ fullParams: BaseRacingLabelsParams
67
+ // axisLabelAlign: TextAlign
68
+ containerSize: ContainerSize
69
+ fullDataFormatter: DataFormatterMultiValue,
70
+ fullChartParams: ChartParams
71
+ // textReverseTransform: string,
72
+ }) {
73
+ const offsetX = fullParams.barLabel.padding - fullParams.axisLabel.offset[0]
74
+ const offsetY = - fullParams.barLabel.padding - fullParams.axisLabel.offset[1]
75
+ let labelX = - offsetX
76
+ let labelY = - offsetY
77
+
78
+ selection
79
+ .attr('transform', d => `translate(0, ${containerSize.height})`)
80
+ .selectAll<SVGTextElement, BaseRacingLabelsParams>(`text`)
81
+ .data([fullParams])
82
+ .join(
83
+ enter => {
84
+ return enter
85
+ .append('text')
86
+ .style('font-weight', 'bold')
87
+ },
88
+ update => update,
89
+ exit => exit.remove()
90
+ )
91
+ .attr('text-anchor', yAxisLabelAnchor)
92
+ .attr('dominant-baseline', yAxisLabelDominantBaseline)
93
+ .attr('font-size', fullChartParams.styles.textSize)
94
+ .style('fill', getColor(fullParams.axisLabel.colorType, fullChartParams))
95
+ // .style('transform', textReverseTransform)
96
+ // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
97
+ .attr('x', labelX)
98
+ .attr('y', labelY)
99
+ .text(d => fullDataFormatter.yAxis.label)
100
+ }
101
+
102
+ function renderRacingLabels ({ selection, fullParams, fullChartParams, transitionEase, rankingScale, valueScale, categoryData }: {
103
+ selection: d3.Selection<SVGGElement, any, any, any>,
104
+ fullParams: BaseRacingLabelsParams
105
+ fullChartParams: ChartParams
106
+ transitionEase: string
107
+ rankingScale: d3.ScalePoint<string>
108
+ valueScale: ((n: number) => number)
109
+ categoryData: ComputedDatumMultiValue[]
110
+ // textReverseTransformWithRotate: string,
111
+ // xyValueIndex: [number, number]
112
+ }) {
113
+ const labelData = fullParams.barLabel.position === 'none' ? [] : categoryData
114
+
115
+ const labelSelection = selection
116
+ .selectAll<SVGGElement, string>(`text`)
117
+ .data(labelData, (d: ComputedDatumMultiValue) => d.id)
118
+ // .join('g')
119
+ // .classed(boxClassName, true)
120
+ .join(
121
+ enter => {
122
+ return enter
123
+ .append('text')
124
+ .style('font-weight', 'bold')
125
+ .style('pointer-events', 'none')
126
+ .attr('x', d => valueScale(d.value[d.xValueIndex] ?? 0) - fullParams.barLabel.padding)
127
+ .attr('y', d => rankingScale(d.label)!)
128
+ },
129
+ update => {
130
+ return update
131
+ .transition()
132
+ .duration(fullChartParams.transitionDuration)
133
+ // .ease(d3.easeLinear)
134
+ .ease(getD3TransitionEase(transitionEase))
135
+ // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
136
+ .attr('x', d => valueScale(d.value[d.xValueIndex] ?? 0) - fullParams.barLabel.padding)
137
+ .attr('y', d => rankingScale(d.label)!)
138
+ },
139
+ exit => exit.remove()
140
+ )
141
+ .attr('text-anchor', yTickTextAnchor)
142
+ .attr('dominant-baseline', yTickDominantBaseline)
143
+ .attr('font-size', fullChartParams.styles.textSize)
144
+ .style('fill', d => getDatumColor({
145
+ colorType: fullParams.barLabel.colorType,
146
+ fullChartParams: fullChartParams,
147
+ datum: d
148
+ }))
149
+ // .style('transform', textReverseTransformWithRotate)
150
+ .text(d => d.label)
151
+
152
+ return labelSelection
153
+ }
154
+
155
+
156
+ function renderClipPath ({ defsSelection, clipPathData }: {
157
+ defsSelection: d3.Selection<SVGDefsElement, any, any, any>
158
+ clipPathData: ClipPathDatum[]
159
+ // textReverseTransform: string
160
+ }) {
161
+ const clipPath = defsSelection
162
+ .selectAll<SVGClipPathElement, Layout>('clipPath')
163
+ .data(clipPathData)
164
+ .join(
165
+ enter => {
166
+ return enter
167
+ .append('clipPath')
168
+ },
169
+ update => update,
170
+ exit => exit.remove()
171
+ )
172
+ .attr('id', d => d.id)
173
+ // .attr('transform', textReverseTransform)
174
+ .each((d, i, g) => {
175
+ const rect = d3.select(g[i])
176
+ .selectAll<SVGRectElement, typeof d>('rect')
177
+ .data([d])
178
+ .join(
179
+ enter => {
180
+ return enter
181
+ .append('rect')
182
+ },
183
+ update => update,
184
+ exit => exit.remove()
185
+ )
186
+ .attr('x', _d => - _d.width)
187
+ .attr('y', 0)
188
+ .attr('width', _d => _d.width * 2)
189
+ .attr('height', _d => _d.height)
190
+ })
191
+ }
192
+
193
+ export const createBaseRacingLabels: BasePluginFn<BaseRacingAxisContext> = (pluginName: string, {
194
+ selection,
195
+ computedData$,
196
+ visibleComputedRankingData$,
197
+ rankingScaleList$,
198
+ xScale$,
199
+ fullParams$,
200
+ fullDataFormatter$,
201
+ fullChartParams$,
202
+ transitionEase$,
203
+ // layout$,
204
+ containerPosition$,
205
+ containerSize$,
206
+ isCategorySeprate$,
207
+ // xyValueIndex$
208
+ }) => {
209
+
210
+ const destroy$ = new Subject()
211
+
212
+ // const containerClassName = getClassName(pluginName, 'container')
213
+ const boxClassName = getClassName(pluginName, 'box')
214
+ const textClassName = getClassName(pluginName, 'text')
215
+ const clipPathID = getUniID(pluginName, 'clipPath-box')
216
+
217
+ const containerSelection$ = multiValueContainerSelectionsObservable({
218
+ selection,
219
+ pluginName,
220
+ clipPathID: null,
221
+ computedData$,
222
+ containerPosition$,
223
+ isCategorySeprate$,
224
+ }).pipe(
225
+ takeUntil(destroy$),
226
+ )
227
+
228
+ // const containerSelection$ = combineLatest({
229
+ // computedData: computedData$.pipe(
230
+ // distinctUntilChanged((a, b) => {
231
+ // // 只有當series的數量改變時,才重新計算
232
+ // return a.length === b.length
233
+ // }),
234
+ // ),
235
+ // isCategorySeprate: isCategorySeprate$
236
+ // }).pipe(
237
+ // takeUntil(destroy$),
238
+ // switchMap(async (d) => d),
239
+ // map(data => {
240
+ // return data.isCategorySeprate
241
+ // // category分開的時候顯示各別axis
242
+ // ? data.computedData
243
+ // // category合併的時候只顯示第一個axis
244
+ // : [data.computedData[0]]
245
+ // }),
246
+ // map((computedData, i) => {
247
+ // return selection
248
+ // .selectAll<SVGGElement, ComputedDatumMultiValue[]>(`g.${containerClassName}`)
249
+ // .data(computedData, d => d[0] ? d[0].categoryIndex : i)
250
+ // .join('g')
251
+ // .classed(containerClassName, true)
252
+ // })
253
+ // )
254
+
255
+ // combineLatest({
256
+ // containerSelection: containerSelection$,
257
+ // containerPosition: containerPosition$
258
+ // }).pipe(
259
+ // takeUntil(destroy$),
260
+ // switchMap(async d => d)
261
+ // ).subscribe(data => {
262
+ // data.containerSelection
263
+ // .attr('transform', (d, i) => {
264
+ // const containerPosition = data.containerPosition[i] ?? data.containerPosition[0]
265
+ // const translate = containerPosition.translate
266
+ // const scale = containerPosition.scale
267
+ // // return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
268
+ // return `translate(${translate[0]}, ${translate[1]})`
269
+ // })
270
+ // // .attr('opacity', 0)
271
+ // // .transition()
272
+ // // .attr('opacity', 1)
273
+ // })
274
+
275
+ // const textReverseTransform$ = containerPosition$.pipe(
276
+ // takeUntil(destroy$),
277
+ // switchMap(async (d) => d),
278
+ // map(containerPosition => {
279
+ // // const axesRotateXYReverseValue = `rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
280
+ // // const axesRotateReverseValue = `rotate(${data.gridAxesReverseTransform.rotate}deg)`
281
+ // const containerScaleReverseValue = `scale(${1 / containerPosition[0].scale[0]}, ${1 / containerPosition[0].scale[1]})`
282
+ // // 抵消最外層scale
283
+ // return `${containerScaleReverseValue}`
284
+ // }),
285
+ // distinctUntilChanged()
286
+ // )
287
+
288
+ // const textReverseTransformWithRotate$ = combineLatest({
289
+ // textReverseTransform: textReverseTransform$,
290
+ // fullParams: fullParams$,
291
+ // }).pipe(
292
+ // takeUntil(destroy$),
293
+ // switchMap(async (d) => d),
294
+ // map(data => {
295
+ // // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
296
+ // return `${data.textReverseTransform} rotate(${data.fullParams.barLabel.rotate}deg)`
297
+ // })
298
+ // )
299
+
300
+ // const rankingLabelList$ = visibleComputedRankingData$.pipe(
301
+ // takeUntil(destroy$),
302
+ // map(data => {
303
+ // return data.map(categoryData => categoryData.map(d => d.label))
304
+ // })
305
+ // )
306
+
307
+ const valueScale$: Observable<((n: number) => number)> = fullParams$.pipe(
308
+ map(fullParams => fullParams.barLabel.position),
309
+ distinctUntilChanged(),
310
+ switchMap(position => {
311
+ return iif(
312
+ () => position === 'inside',
313
+ xScale$,
314
+ of(() => 0)
315
+ )
316
+ })
317
+ )
318
+
319
+ containerSize$.subscribe(data => {
320
+ const defsSelection = selection.selectAll<SVGDefsElement, any>('defs')
321
+ .data([clipPathID])
322
+ .join('defs')
323
+ const clipPathData = [{
324
+ id: clipPathID,
325
+ width: data.width,
326
+ height: data.height
327
+ }]
328
+ renderClipPath({
329
+ defsSelection: defsSelection,
330
+ clipPathData,
331
+ // textReverseTransform: data.textReverseTransform
332
+ })
333
+ })
334
+
335
+ combineLatest({
336
+ containerSelection: containerSelection$,
337
+ fullParams: fullParams$,
338
+ // layout: layout$,
339
+ containerSize: containerSize$,
340
+ fullDataFormatter: fullDataFormatter$,
341
+ fullChartParams: fullChartParams$,
342
+ transitionEase: transitionEase$,
343
+ visibleComputedRankingData: visibleComputedRankingData$,
344
+ // rankingLabelList: rankingLabelList$,
345
+ rankingScaleList: rankingScaleList$,
346
+ valueScale: valueScale$,
347
+ // textReverseTransform: textReverseTransform$,
348
+ // textReverseTransformWithRotate: textReverseTransformWithRotate$,
349
+ // xyValueIndex: xyValueIndex$
350
+ }).pipe(
351
+ takeUntil(destroy$),
352
+ switchMap(async (d) => d),
353
+ ).subscribe(data => {
354
+
355
+ data.containerSelection.each((d, i, g) => {
356
+ const _containerSelection = d3.select(g[i])
357
+ // const rankingLabels = data.rankingLabelList[i]
358
+ const rankingScale = data.rankingScaleList[i]
359
+ if (!rankingScale) {
360
+ return
361
+ }
362
+
363
+ // const containerClipPathID = `${clipPathID}-${i}`
364
+ const axisSelection = _containerSelection
365
+ .selectAll<SVGGElement, any>(`g.${boxClassName}`)
366
+ .data([i])
367
+ .join('g')
368
+ .attr('class', boxClassName)
369
+ .attr('clip-path', `url(#${clipPathID})`)
370
+ const axisLabelSelection = _containerSelection
371
+ .selectAll<SVGGElement, BaseRacingLabelsParams>(`g.${textClassName}`)
372
+ .data([data.fullParams])
373
+ .join('g')
374
+ .classed(textClassName, true)
375
+
376
+ renderRacingLabels({
377
+ selection: axisSelection,
378
+ fullParams: data.fullParams,
379
+ fullChartParams: data.fullChartParams,
380
+ transitionEase: data.transitionEase,
381
+ rankingScale: rankingScale,
382
+ categoryData: data.visibleComputedRankingData[i],
383
+ // textReverseTransformWithRotate: data.textReverseTransformWithRotate,
384
+ valueScale: data.valueScale,
385
+ // xyValueIndex: data.xyValueIndex,
386
+ })
387
+
388
+ renderRacingAxisLabel({
389
+ selection: axisLabelSelection,
390
+ textClassName,
391
+ fullParams: data.fullParams,
392
+ containerSize: data.containerSize,
393
+ fullDataFormatter: data.fullDataFormatter,
394
+ fullChartParams: data.fullChartParams,
395
+ // textReverseTransform: data.textReverseTransform,
396
+ })
397
+ })
398
+
399
+ })
400
+
401
+ return () => {
402
+ destroy$.next(undefined)
403
+ }
404
+ }