@orbcharts/plugins-basic 3.0.0-alpha.67 → 3.0.0-alpha.69

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +3436 -3358
  3. package/dist/orbcharts-plugins-basic.umd.js +14 -13
  4. package/dist/src/utils/d3Graphics.d.ts +10 -0
  5. package/package.json +42 -42
  6. package/src/base/BaseBarStack.ts +779 -779
  7. package/src/base/BaseBars.ts +764 -764
  8. package/src/base/BaseBarsTriangle.ts +672 -672
  9. package/src/base/BaseDots.ts +513 -513
  10. package/src/base/BaseGroupAxis.ts +675 -652
  11. package/src/base/BaseLegend.ts +642 -642
  12. package/src/base/BaseLineAreas.ts +628 -628
  13. package/src/base/BaseLines.ts +704 -704
  14. package/src/base/BaseValueAxis.ts +578 -578
  15. package/src/base/types.ts +2 -2
  16. package/src/grid/defaults.ts +128 -128
  17. package/src/grid/gridObservables.ts +543 -543
  18. package/src/grid/index.ts +15 -15
  19. package/src/grid/plugins/BarStack.ts +43 -43
  20. package/src/grid/plugins/Bars.ts +44 -44
  21. package/src/grid/plugins/BarsPN.ts +41 -41
  22. package/src/grid/plugins/BarsTriangle.ts +42 -42
  23. package/src/grid/plugins/Dots.ts +37 -37
  24. package/src/grid/plugins/GridLegend.ts +59 -59
  25. package/src/grid/plugins/GroupAux.ts +1014 -991
  26. package/src/grid/plugins/GroupAxis.ts +36 -36
  27. package/src/grid/plugins/LineAreas.ts +40 -40
  28. package/src/grid/plugins/Lines.ts +40 -40
  29. package/src/grid/plugins/ScalingArea.ts +174 -174
  30. package/src/grid/plugins/ValueAxis.ts +36 -36
  31. package/src/grid/plugins/ValueStackAxis.ts +38 -38
  32. package/src/grid/types.ts +123 -123
  33. package/src/index.ts +9 -9
  34. package/src/multiGrid/defaults.ts +158 -158
  35. package/src/multiGrid/index.ts +13 -13
  36. package/src/multiGrid/multiGridObservables.ts +49 -49
  37. package/src/multiGrid/plugins/MultiBarStack.ts +78 -78
  38. package/src/multiGrid/plugins/MultiBars.ts +77 -77
  39. package/src/multiGrid/plugins/MultiBarsTriangle.ts +77 -77
  40. package/src/multiGrid/plugins/MultiDots.ts +65 -65
  41. package/src/multiGrid/plugins/MultiGridLegend.ts +89 -89
  42. package/src/multiGrid/plugins/MultiGroupAxis.ts +70 -70
  43. package/src/multiGrid/plugins/MultiLineAreas.ts +77 -77
  44. package/src/multiGrid/plugins/MultiLines.ts +77 -77
  45. package/src/multiGrid/plugins/MultiValueAxis.ts +69 -69
  46. package/src/multiGrid/plugins/MultiValueStackAxis.ts +69 -69
  47. package/src/multiGrid/plugins/OverlappingValueAxes.ts +170 -170
  48. package/src/multiGrid/plugins/OverlappingValueStackAxes.ts +169 -169
  49. package/src/multiGrid/types.ts +72 -72
  50. package/src/noneData/defaults.ts +102 -102
  51. package/src/noneData/index.ts +3 -3
  52. package/src/noneData/plugins/Container.ts +10 -10
  53. package/src/noneData/plugins/Tooltip.ts +327 -327
  54. package/src/noneData/types.ts +26 -26
  55. package/src/series/defaults.ts +149 -149
  56. package/src/series/index.ts +9 -9
  57. package/src/series/plugins/Bubbles.ts +545 -545
  58. package/src/series/plugins/Pie.ts +584 -584
  59. package/src/series/plugins/PieEventTexts.ts +262 -262
  60. package/src/series/plugins/PieLabels.ts +604 -598
  61. package/src/series/plugins/Rose.ts +481 -481
  62. package/src/series/plugins/RoseLabels.ts +571 -565
  63. package/src/series/plugins/SeriesLegend.ts +59 -59
  64. package/src/series/seriesObservables.ts +145 -145
  65. package/src/series/seriesUtils.ts +51 -51
  66. package/src/series/types.ts +87 -87
  67. package/src/tree/defaults.ts +23 -23
  68. package/src/tree/index.ts +3 -3
  69. package/src/tree/plugins/TreeLegend.ts +59 -59
  70. package/src/tree/plugins/TreeMap.ts +305 -305
  71. package/src/tree/types.ts +23 -23
  72. package/src/utils/commonUtils.ts +21 -21
  73. package/src/utils/d3Graphics.ts +174 -124
  74. package/src/utils/d3Utils.ts +73 -73
  75. package/src/utils/observables.ts +14 -14
  76. package/src/utils/orbchartsUtils.ts +100 -100
  77. package/tsconfig.base.json +13 -13
  78. package/tsconfig.json +2 -2
  79. package/vite.config.js +22 -22
@@ -1,653 +1,676 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- switchMap,
5
- distinctUntilChanged,
6
- of,
7
- first,
8
- map,
9
- takeUntil,
10
- shareReplay,
11
- Observable,
12
- Subject } from 'rxjs'
13
- import type { ColorType, ComputedDataGrid, GridContainerPosition } from '@orbcharts/core'
14
- import type { BasePluginFn } from './types'
15
- import type {
16
- ComputedDatumGrid,
17
- DataFormatterGrid,
18
- ChartParams,
19
- TransformData } from '@orbcharts/core'
20
- import { parseTickFormatValue } from '../utils/d3Utils'
21
- import { getColor, getClassName } from '../utils/orbchartsUtils'
22
-
23
- export interface BaseGroupAxisParams {
24
- // xLabel: string
25
- // labelAnchor: 'start' | 'end'
26
- labelOffset: [number, number]
27
- labelColorType: ColorType
28
- axisLineVisible: boolean
29
- axisLineColorType: ColorType
30
- ticks: number | null | 'all'
31
- tickFormat: string | ((text: any) => string)
32
- tickLineVisible: boolean
33
- tickPadding: number
34
- tickFullLine: boolean
35
- tickFullLineDasharray: string
36
- tickColorType: ColorType
37
- // axisLineColor: string
38
- // axisLabelColor: string
39
- tickTextRotate: number
40
- tickTextColorType: ColorType
41
- }
42
-
43
- interface BaseGroupAxisContext {
44
- selection: d3.Selection<any, unknown, any, unknown>
45
- computedData$: Observable<ComputedDataGrid>
46
- fullParams$: Observable<BaseGroupAxisParams>
47
- fullDataFormatter$: Observable<DataFormatterGrid>
48
- fullChartParams$: Observable<ChartParams>
49
- gridAxesTransform$: Observable<TransformData>
50
- gridAxesReverseTransform$: Observable<TransformData>
51
- gridAxesSize$: Observable<{
52
- width: number;
53
- height: number;
54
- }>
55
- gridContainerPosition$: Observable<GridContainerPosition[]>
56
- isSeriesSeprate$: Observable<boolean>
57
- textSizePx$: Observable<number>
58
- }
59
-
60
- interface TextAlign {
61
- textAnchor: "start" | "middle" | "end"
62
- dominantBaseline: "middle" | "auto" | "hanging"
63
- }
64
-
65
- // const pluginName = 'GroupAxis'
66
- // const containerClassName = getClassName(pluginName, 'container')
67
- // const xAxisGClassName = getClassName(pluginName, 'xAxisG')
68
- // const xAxisClassName = getClassName(pluginName, 'xAxis')
69
- // const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
70
- const defaultTickSize = 6
71
-
72
- function renderAxisLabel ({ selection, groupingLabelClassName, fullParams, axisLabelAlign, gridAxesSize, fullDataFormatter, chartParams, textReverseTransform }: {
73
- selection: d3.Selection<SVGGElement, any, any, any>,
74
- groupingLabelClassName: string
75
- fullParams: BaseGroupAxisParams
76
- axisLabelAlign: TextAlign
77
- gridAxesSize: { width: number, height: number }
78
- fullDataFormatter: DataFormatterGrid,
79
- chartParams: ChartParams
80
- textReverseTransform: string
81
- }) {
82
-
83
- const offsetX = fullParams.tickPadding + fullParams.labelOffset[0]
84
- const offsetY = fullParams.tickPadding + fullParams.labelOffset[1]
85
- let labelX = 0
86
- let labelY = 0
87
- if (fullDataFormatter.grid.groupAxis.position === 'bottom') {
88
- labelY = offsetY
89
- if (fullDataFormatter.grid.valueAxis.position === 'left') {
90
- labelX = offsetX
91
- } else if (fullDataFormatter.grid.valueAxis.position === 'right') {
92
- labelX = - offsetX
93
- }
94
- } else if (fullDataFormatter.grid.groupAxis.position === 'top') {
95
- labelY = - offsetY
96
- if (fullDataFormatter.grid.valueAxis.position === 'left') {
97
- labelX = offsetX
98
- } else if (fullDataFormatter.grid.valueAxis.position === 'right') {
99
- labelX = - offsetX
100
- }
101
- } else if (fullDataFormatter.grid.groupAxis.position === 'left') {
102
- labelX = - offsetX
103
- if (fullDataFormatter.grid.valueAxis.position === 'bottom') {
104
- labelY = - offsetY
105
- } else if (fullDataFormatter.grid.valueAxis.position === 'top') {
106
- labelY = offsetY
107
- }
108
- } else if (fullDataFormatter.grid.groupAxis.position === 'right') {
109
- labelX = offsetX
110
- if (fullDataFormatter.grid.valueAxis.position === 'bottom') {
111
- labelY = - offsetY
112
- } else if (fullDataFormatter.grid.valueAxis.position === 'top') {
113
- labelY = offsetY
114
- }
115
- }
116
-
117
- const axisLabelSelection = selection
118
- .selectAll<SVGGElement, BaseGroupAxisParams>(`g.${groupingLabelClassName}`)
119
- .data([fullParams])
120
- .join('g')
121
- .classed(groupingLabelClassName, true)
122
- .each((d, i, g) => {
123
- const text = d3.select(g[i])
124
- .selectAll<SVGTextElement, BaseGroupAxisParams>('text')
125
- .data([d])
126
- .join(
127
- enter => {
128
- return enter
129
- .append('text')
130
- .style('font-weight', 'bold')
131
- },
132
- update => update,
133
- exit => exit.remove()
134
- )
135
- .attr('text-anchor', axisLabelAlign.textAnchor)
136
- .attr('dominant-baseline', axisLabelAlign.dominantBaseline)
137
- .attr('font-size', chartParams.styles.textSize)
138
- .style('fill', getColor(fullParams.labelColorType, chartParams))
139
- .style('transform', textReverseTransform)
140
- // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
141
- .attr('x', labelX)
142
- .attr('y', labelY)
143
- .text(d => fullDataFormatter.grid.groupAxis.label)
144
- })
145
- .attr('transform', d => `translate(${gridAxesSize.width}, 0)`)
146
- // .attr('transform', d => `translate(${gridAxesSize.width + d.tickPadding + fullParams.labelOffset[0]}, ${- d.tickPadding - fullParams.labelOffset[1]})`)
147
-
148
- }
149
-
150
- function renderAxis ({ selection, xAxisClassName, fullParams, tickTextAlign, gridAxesSize, fullDataFormatter, chartParams, groupScale, groupScaleDomain, groupLabels, textReverseTransformWithRotate, textSizePx }: {
151
- selection: d3.Selection<SVGGElement, any, any, any>,
152
- xAxisClassName: string
153
- fullParams: BaseGroupAxisParams
154
- tickTextAlign: TextAlign
155
- gridAxesSize: { width: number, height: number }
156
- fullDataFormatter: DataFormatterGrid,
157
- chartParams: ChartParams
158
- groupScale: d3.ScaleLinear<number, number>
159
- groupScaleDomain: number[]
160
- groupLabels: string[]
161
- textReverseTransformWithRotate: string
162
- textSizePx: number
163
- }) {
164
-
165
- const xAxisSelection = selection
166
- .selectAll<SVGGElement, BaseGroupAxisParams>(`g.${xAxisClassName}`)
167
- .data([fullParams])
168
- .join('g')
169
- .classed(xAxisClassName, true)
170
-
171
- // 計算所有範圍內groupLabels數量(顯示所有刻度)
172
- const allTicksAmount = Math.floor(groupScaleDomain[1]) - Math.ceil(groupScaleDomain[0]) + 1
173
-
174
- // 刻度文字偏移
175
- let tickPadding = 0
176
- let textX = 0
177
- if (fullDataFormatter.grid.groupAxis.position === 'left') {
178
- tickPadding = 0
179
- textX = - fullParams.tickPadding
180
- } else if (fullDataFormatter.grid.groupAxis.position === 'right') {
181
- tickPadding = 0
182
- textX = fullParams.tickPadding
183
- } else if (fullDataFormatter.grid.groupAxis.position === 'bottom') {
184
- tickPadding = fullParams.tickPadding
185
- textX = 0
186
- } else if (fullDataFormatter.grid.groupAxis.position === 'top') {
187
- tickPadding = - fullParams.tickPadding
188
- textX = - 0
189
- }
190
-
191
- // 設定X軸刻度
192
- const xAxis = d3.axisBottom(groupScale)
193
- .scale(groupScale)
194
- .ticks(fullParams.ticks === 'all'
195
- ? allTicksAmount
196
- : fullParams.ticks > allTicksAmount
197
- ? allTicksAmount // 不顯示超過groupLabels數量的刻度
198
- : fullParams.ticks)
199
- .tickSize(fullParams.tickFullLine == true
200
- ? gridAxesSize.height
201
- : - defaultTickSize)
202
- .tickSizeOuter(0)
203
- .tickFormat((groupIndex: number) => {
204
- // 用index對應到groupLabel
205
- const groupLabel = groupLabels[groupIndex] ?? '' // 非整數index不顯示
206
- return parseTickFormatValue(groupLabel, fullParams.tickFormat)
207
- })
208
- .tickPadding(tickPadding)
209
-
210
- const xAxisEl = xAxisSelection
211
- .transition()
212
- .duration(100)
213
- .call(xAxis)
214
- .on('end', (self, t) => {
215
- // 先等transition結束再處理文字,否則會被原本的文字覆蓋
216
- xAxisEl
217
- .selectAll('.tick text')
218
- .each((groupIndex: number, i, n) => {
219
- // -- 將原本單行文字改為多行文字 --
220
- const groupLabel = groupLabels[groupIndex] ?? '' // 非整數index不顯示
221
- const groupLabelTspans = groupLabel.split('\n')
222
- const textSelection = d3.select(n[i])
223
- .text(null) // 先清空原本的 text
224
-
225
- const textX = Number(textSelection.attr('x'))
226
- let textY = Number(textSelection.attr('y'))
227
- if (fullDataFormatter.grid.groupAxis.position === 'top') {
228
- // 當文字在上方時,要往上偏移第一行的高度
229
- textY -= (groupLabelTspans.length - 1) * textSizePx
230
- }
231
-
232
- textSelection
233
- .selectAll('tspan')
234
- .data(groupLabelTspans)
235
- .join('tspan')
236
- .attr('x', textX)
237
- .attr('y', (_d, _i) => textY + _i * textSizePx)
238
- .text(d => d)
239
- })
240
- })
241
-
242
- xAxisEl.selectAll('line')
243
- .style('fill', 'none')
244
- .style('stroke', fullParams.tickLineVisible == true ? getColor(fullParams.tickColorType, chartParams) : 'none')
245
- .style('stroke-dasharray', fullParams.tickFullLineDasharray)
246
- .attr('pointer-events', 'none')
247
-
248
- xAxisEl.selectAll('path')
249
- .style('fill', 'none')
250
- .style('stroke', fullParams.axisLineVisible == true ? getColor(fullParams.axisLineColorType, chartParams) : 'none')
251
- .style('shape-rendering', 'crispEdges')
252
-
253
- const xText = xAxisSelection.selectAll<SVGTextElement, BaseGroupAxisParams>('text')
254
- // .style('font-family', 'sans-serif')
255
- .attr('font-size', chartParams.styles.textSize)
256
- // .style('font-weight', 'bold')
257
- .style('color', getColor(fullParams.tickTextColorType, chartParams))
258
- .attr('text-anchor', tickTextAlign.textAnchor)
259
- .attr('dominant-baseline', tickTextAlign.dominantBaseline)
260
- .attr('x', textX)
261
- .style('transform', textReverseTransformWithRotate)
262
-
263
- // 抵消掉預設的偏移
264
- // if (fullDataFormatter.grid.groupAxis.position === 'left' || fullDataFormatter.grid.groupAxis.position === 'right') {
265
- xText.attr('dy', 0)
266
- // }
267
-
268
- return xAxisSelection
269
- }
270
-
271
-
272
- export const createBaseGroupAxis: BasePluginFn<BaseGroupAxisContext> = ((pluginName: string, {
273
- selection,
274
- computedData$,
275
- fullParams$,
276
- fullDataFormatter$,
277
- fullChartParams$,
278
- gridAxesTransform$,
279
- gridAxesReverseTransform$,
280
- gridAxesSize$,
281
- gridContainerPosition$,
282
- isSeriesSeprate$,
283
- textSizePx$,
284
- }) => {
285
-
286
- const destroy$ = new Subject()
287
-
288
- const containerClassName = getClassName(pluginName, 'container')
289
- const xAxisGClassName = getClassName(pluginName, 'xAxisG')
290
- const xAxisClassName = getClassName(pluginName, 'xAxis')
291
- const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
292
-
293
- const containerSelection$ = combineLatest({
294
- computedData: computedData$.pipe(
295
- distinctUntilChanged((a, b) => {
296
- // 只有當series的數量改變時,才重新計算
297
- return a.length === b.length
298
- }),
299
- ),
300
- isSeriesSeprate: isSeriesSeprate$
301
- }).pipe(
302
- takeUntil(destroy$),
303
- switchMap(async (d) => d),
304
- map(data => {
305
- return data.isSeriesSeprate
306
- // series分開的時候顯示各別axis
307
- ? data.computedData
308
- // series合併的時候只顯示第一個axis
309
- : [data.computedData[0]]
310
- }),
311
- map((computedData, i) => {
312
- return selection
313
- .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${containerClassName}`)
314
- .data(computedData, d => d[0] ? d[0].seriesIndex : i)
315
- .join('g')
316
- .classed(containerClassName, true)
317
- })
318
- )
319
-
320
- const axisSelection$ = containerSelection$.pipe(
321
- takeUntil(destroy$),
322
- map((containerSelection, i) => {
323
- return containerSelection
324
- .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${xAxisGClassName}`)
325
- .data([xAxisGClassName])
326
- .join('g')
327
- .classed(xAxisGClassName, true)
328
- })
329
- )
330
-
331
- combineLatest({
332
- containerSelection: containerSelection$,
333
- gridContainerPosition: gridContainerPosition$
334
- }).pipe(
335
- takeUntil(destroy$),
336
- switchMap(async d => d)
337
- ).subscribe(data => {
338
- data.containerSelection
339
- .attr('transform', (d, i) => {
340
- const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
341
- const translate = gridContainerPosition.translate
342
- const scale = gridContainerPosition.scale
343
- return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
344
- })
345
- // .attr('opacity', 0)
346
- // .transition()
347
- // .attr('opacity', 1)
348
- })
349
-
350
- combineLatest({
351
- axisSelection: axisSelection$,
352
- gridAxesTransform: gridAxesTransform$,
353
- }).pipe(
354
- takeUntil(destroy$),
355
- switchMap(async d => d)
356
- ).subscribe(data => {
357
- data.axisSelection
358
- .style('transform', data.gridAxesTransform.value)
359
- // .attr('opacity', 0)
360
- // .transition()
361
- // .attr('opacity', 1)
362
-
363
- })
364
-
365
-
366
- // const gridAxesSize$ = gridAxisSizeObservable({
367
- // fullDataFormatter$,
368
- // layout$
369
- // })
370
-
371
-
372
- // const tickTextFormatter$ = fullDataFormatter$
373
- // .pipe(
374
- // map(d => {
375
- // return d.grid.seriesDirection === 'row' ? d.grid.columnLabelFormat : d.grid.rowLabelFormat
376
- // })
377
- // )
378
-
379
- // const textReverseTransform$: Observable<string> = new Observable(subscriber => {
380
- // combineLatest({
381
- // params: fullParams$,
382
- // gridAxesTransform: gridAxesTransform$
383
- // }).pipe(
384
- // takeUntil(destroy$),
385
- // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
386
- // switchMap(async (d) => d),
387
- // ).subscribe(data => {
388
-
389
- // const transformData = Object.assign({}, data.gridAxesTransform)
390
-
391
- // const value = getAxesTransformValue({
392
- // translate: [0, 0],
393
- // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
394
- // rotate: transformData.rotate * -1 + data.params.tickTextRotate,
395
- // rotateX: transformData.rotateX * -1,
396
- // rotateY: transformData.rotateY * -1
397
- // })
398
-
399
- // subscriber.next(value)
400
- // })
401
- // })
402
- // const reverseTransform$: Observable<TransformData> = gridAxesTransform$.pipe(
403
- // takeUntil(destroy$),
404
- // map(d => {
405
- // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
406
- // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
407
- // const rotate = d.rotate * -1
408
- // const rotateX = d.rotateX * -1
409
- // const rotateY = d.rotateY * -1
410
- // return {
411
- // translate,
412
- // scale,
413
- // rotate,
414
- // rotateX,
415
- // rotateY,
416
- // value: ''
417
- // }
418
- // }),
419
- // )
420
- const textReverseTransform$ = combineLatest({
421
- gridAxesReverseTransform: gridAxesReverseTransform$,
422
- gridContainerPosition: gridContainerPosition$
423
- }).pipe(
424
- takeUntil(destroy$),
425
- switchMap(async (d) => d),
426
- map(data => {
427
- // const axisReverseTranslateValue = `translate(${data.gridAxesReverseTransform.translate[0]}px, ${data.gridAxesReverseTransform.translate[1]}px)`
428
- const axesRotateXYReverseValue = `rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
429
- const axesRotateReverseValue = `rotate(${data.gridAxesReverseTransform.rotate}deg)`
430
- const containerScaleReverseValue = `scale(${1 / data.gridContainerPosition[0].scale[0]}, ${1 / data.gridContainerPosition[0].scale[1]})`
431
- // 必須按照順序(先抵消外層rotate,再抵消最外層scale)
432
- return `${axesRotateXYReverseValue} ${axesRotateReverseValue} ${containerScaleReverseValue}`
433
- }),
434
- distinctUntilChanged()
435
- )
436
-
437
- const textReverseTransformWithRotate$ = combineLatest({
438
- textReverseTransform: textReverseTransform$,
439
- fullParams: fullParams$,
440
- }).pipe(
441
- takeUntil(destroy$),
442
- switchMap(async (d) => d),
443
- map(data => {
444
- // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
445
- return `${data.textReverseTransform} rotate(${data.fullParams.tickTextRotate}deg)`
446
- })
447
- )
448
-
449
- // 使用pointScale計算非連續性比例尺
450
- // const groupScale$: Observable<d3.ScalePoint<string>> = new Observable(subscriber => {
451
- // combineLatest({
452
- // fullDataFormatter: fullDataFormatter$,
453
- // gridAxesSize: gridAxesSize$,
454
- // computedData: computedData$
455
- // }).pipe(
456
- // takeUntil(destroy$),
457
- // switchMap(async (d) => d),
458
- // ).subscribe(data => {
459
- // const groupMin = 0
460
- // const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
461
- // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
462
- // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
463
- // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
464
- // const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'auto'
465
- // ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
466
- // : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
467
-
468
- // const groupingLength = data.computedData[0]
469
- // ? data.computedData[0].length
470
- // : 0
471
-
472
- // let _labels = (data.computedData[0] ?? []).map(d => d.groupLabel)
473
-
474
- // const axisLabels = new Array(groupingLength).fill(0)
475
- // .map((d, i) => {
476
- // return _labels[i] != null
477
- // ? _labels[i]
478
- // : String(i) // 沒有label則用序列號填充
479
- // })
480
- // .filter((d, i) => {
481
- // return i >= groupScaleDomainMin && i <= groupScaleDomainMax
482
- // })
483
-
484
-
485
- // const padding = data.fullDataFormatter.grid.groupAxis.scalePadding
486
-
487
- // const groupScale = createAxisPointScale({
488
- // axisLabels,
489
- // axisWidth: data.gridAxesSize.width,
490
- // padding
491
- // })
492
-
493
- // subscriber.next(groupScale)
494
- // })
495
- // })
496
-
497
- const groupScaleDomain$ = combineLatest({
498
- fullDataFormatter: fullDataFormatter$,
499
- gridAxesSize: gridAxesSize$,
500
- computedData: computedData$
501
- }).pipe(
502
- takeUntil(destroy$),
503
- switchMap(async (d) => d),
504
- map(data => {
505
- const groupMin = 0
506
- const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
507
- // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
508
- // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
509
- // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
510
- const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] - data.fullDataFormatter.grid.groupAxis.scalePadding
511
- const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'max'
512
- ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
513
- : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
514
-
515
- return [groupScaleDomainMin, groupScaleDomainMax]
516
- }),
517
- shareReplay(1)
518
- )
519
-
520
- const groupScale$ = combineLatest({
521
- groupScaleDomain: groupScaleDomain$,
522
- gridAxesSize: gridAxesSize$
523
- }).pipe(
524
- takeUntil(destroy$),
525
- switchMap(async (d) => d),
526
- map(data => {
527
- const groupScale: d3.ScaleLinear<number, number> = d3.scaleLinear()
528
- .domain(data.groupScaleDomain)
529
- .range([0, data.gridAxesSize.width])
530
- return groupScale
531
- })
532
- )
533
-
534
- const groupLabels$ = computedData$.pipe(
535
- map(computedData => (computedData[0] ?? []).map(d => d.groupLabel))
536
- )
537
-
538
- const tickTextAlign$: Observable<TextAlign> = combineLatest({
539
- fullDataFormatter: fullDataFormatter$,
540
- fullParams: fullParams$
541
- }).pipe(
542
- takeUntil(destroy$),
543
- switchMap(async (d) => d),
544
- map(data => {
545
- let textAnchor: 'start' | 'middle' | 'end' = 'middle'
546
- let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
547
-
548
- if (data.fullDataFormatter.grid.groupAxis.position === 'bottom') {
549
- textAnchor = data.fullParams.tickTextRotate
550
- ? 'end'
551
- : 'middle'
552
- dominantBaseline = 'hanging'
553
- } else if (data.fullDataFormatter.grid.groupAxis.position === 'top') {
554
- textAnchor = data.fullParams.tickTextRotate
555
- ? 'start'
556
- : 'middle'
557
- dominantBaseline = 'auto'
558
- } else if (data.fullDataFormatter.grid.groupAxis.position === 'left') {
559
- textAnchor = 'end'
560
- dominantBaseline = 'middle'
561
- } else if (data.fullDataFormatter.grid.groupAxis.position === 'right') {
562
- textAnchor = 'start'
563
- dominantBaseline = 'middle'
564
- }
565
- return {
566
- textAnchor,
567
- dominantBaseline
568
- }
569
- })
570
- )
571
-
572
- const axisLabelAlign$: Observable<TextAlign> = fullDataFormatter$.pipe(
573
- takeUntil(destroy$),
574
- map(d => {
575
- let textAnchor: 'start' | 'middle' | 'end' = 'start'
576
- let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
577
-
578
- if (d.grid.groupAxis.position === 'bottom') {
579
- dominantBaseline = 'hanging'
580
- } else if (d.grid.groupAxis.position === 'top') {
581
- dominantBaseline = 'auto'
582
- } else if (d.grid.groupAxis.position === 'left') {
583
- textAnchor = 'end'
584
- } else if (d.grid.groupAxis.position === 'right') {
585
- textAnchor = 'start'
586
- }
587
- if (d.grid.valueAxis.position === 'left') {
588
- textAnchor = 'start'
589
- } else if (d.grid.valueAxis.position === 'right') {
590
- textAnchor = 'end'
591
- } else if (d.grid.valueAxis.position === 'bottom') {
592
- dominantBaseline = 'auto'
593
- } else if (d.grid.valueAxis.position === 'top') {
594
- dominantBaseline = 'hanging'
595
- }
596
- return {
597
- textAnchor,
598
- dominantBaseline
599
- }
600
- })
601
- )
602
-
603
- combineLatest({
604
- axisSelection: axisSelection$,
605
- fullParams: fullParams$,
606
- tickTextAlign: tickTextAlign$,
607
- axisLabelAlign: axisLabelAlign$,
608
- gridAxesSize: gridAxesSize$,
609
- fullDataFormatter: fullDataFormatter$,
610
- chartParams: fullChartParams$,
611
- groupScale: groupScale$,
612
- groupScaleDomain: groupScaleDomain$,
613
- groupLabels: groupLabels$,
614
- textReverseTransform: textReverseTransform$,
615
- textReverseTransformWithRotate: textReverseTransformWithRotate$,
616
- textSizePx: textSizePx$
617
- // tickTextFormatter: tickTextFormatter$
618
- }).pipe(
619
- takeUntil(destroy$),
620
- switchMap(async (d) => d),
621
- ).subscribe(data => {
622
-
623
- renderAxis({
624
- selection: data.axisSelection,
625
- xAxisClassName,
626
- fullParams: data.fullParams,
627
- tickTextAlign: data.tickTextAlign,
628
- gridAxesSize: data.gridAxesSize,
629
- fullDataFormatter: data.fullDataFormatter,
630
- chartParams: data.chartParams,
631
- groupScale: data.groupScale,
632
- groupScaleDomain: data.groupScaleDomain,
633
- groupLabels: data.groupLabels,
634
- textReverseTransformWithRotate: data.textReverseTransformWithRotate,
635
- textSizePx: data.textSizePx
636
- })
637
-
638
- renderAxisLabel({
639
- selection: data.axisSelection,
640
- groupingLabelClassName,
641
- fullParams: data.fullParams,
642
- axisLabelAlign: data.axisLabelAlign,
643
- gridAxesSize: data.gridAxesSize,
644
- fullDataFormatter: data.fullDataFormatter,
645
- chartParams: data.chartParams,
646
- textReverseTransform: data.textReverseTransform,
647
- })
648
- })
649
-
650
- return () => {
651
- destroy$.next(undefined)
652
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ switchMap,
5
+ distinctUntilChanged,
6
+ of,
7
+ first,
8
+ map,
9
+ takeUntil,
10
+ shareReplay,
11
+ Observable,
12
+ Subject } from 'rxjs'
13
+ import type { ColorType, ComputedDataGrid, GridContainerPosition } from '@orbcharts/core'
14
+ import type { BasePluginFn } from './types'
15
+ import type {
16
+ ComputedDatumGrid,
17
+ DataFormatterGrid,
18
+ ChartParams,
19
+ TransformData } from '@orbcharts/core'
20
+ import { parseTickFormatValue } from '../utils/d3Utils'
21
+ import { getColor, getClassName } from '../utils/orbchartsUtils'
22
+ import { renderTspansOnAxis } from '../utils/d3Graphics'
23
+
24
+ export interface BaseGroupAxisParams {
25
+ // xLabel: string
26
+ // labelAnchor: 'start' | 'end'
27
+ labelOffset: [number, number]
28
+ labelColorType: ColorType
29
+ axisLineVisible: boolean
30
+ axisLineColorType: ColorType
31
+ ticks: number | null | 'all'
32
+ tickFormat: string | ((text: any) => string)
33
+ tickLineVisible: boolean
34
+ tickPadding: number
35
+ tickFullLine: boolean
36
+ tickFullLineDasharray: string
37
+ tickColorType: ColorType
38
+ // axisLineColor: string
39
+ // axisLabelColor: string
40
+ tickTextRotate: number
41
+ tickTextColorType: ColorType
42
+ }
43
+
44
+ interface BaseGroupAxisContext {
45
+ selection: d3.Selection<any, unknown, any, unknown>
46
+ computedData$: Observable<ComputedDataGrid>
47
+ fullParams$: Observable<BaseGroupAxisParams>
48
+ fullDataFormatter$: Observable<DataFormatterGrid>
49
+ fullChartParams$: Observable<ChartParams>
50
+ gridAxesTransform$: Observable<TransformData>
51
+ gridAxesReverseTransform$: Observable<TransformData>
52
+ gridAxesSize$: Observable<{
53
+ width: number;
54
+ height: number;
55
+ }>
56
+ gridContainerPosition$: Observable<GridContainerPosition[]>
57
+ isSeriesSeprate$: Observable<boolean>
58
+ textSizePx$: Observable<number>
59
+ }
60
+
61
+ interface TextAlign {
62
+ textAnchor: "start" | "middle" | "end"
63
+ dominantBaseline: "middle" | "auto" | "hanging"
64
+ }
65
+
66
+ interface GroupLabelData {
67
+ text: string
68
+ textArr: string[]
69
+ }
70
+
71
+ // const pluginName = 'GroupAxis'
72
+ // const containerClassName = getClassName(pluginName, 'container')
73
+ // const xAxisGClassName = getClassName(pluginName, 'xAxisG')
74
+ // const xAxisClassName = getClassName(pluginName, 'xAxis')
75
+ // const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
76
+ const defaultTickSize = 6
77
+
78
+ function createGroupLabelData (groupLabels: string[], tickFormat: string | ((text: any) => string)): GroupLabelData[] {
79
+ return groupLabels.map((_text, i) => {
80
+ const text = parseTickFormatValue(_text, tickFormat)
81
+ const textArr = text.split('\n')
82
+
83
+ return {
84
+ text,
85
+ textArr
86
+ }
87
+ })
88
+ }
89
+
90
+ function renderAxisLabel ({ selection, groupingLabelClassName, fullParams, axisLabelAlign, gridAxesSize, fullDataFormatter, chartParams, textReverseTransform }: {
91
+ selection: d3.Selection<SVGGElement, any, any, any>,
92
+ groupingLabelClassName: string
93
+ fullParams: BaseGroupAxisParams
94
+ axisLabelAlign: TextAlign
95
+ gridAxesSize: { width: number, height: number }
96
+ fullDataFormatter: DataFormatterGrid,
97
+ chartParams: ChartParams
98
+ textReverseTransform: string
99
+ }) {
100
+
101
+ const offsetX = fullParams.tickPadding + fullParams.labelOffset[0]
102
+ const offsetY = fullParams.tickPadding + fullParams.labelOffset[1]
103
+ let labelX = 0
104
+ let labelY = 0
105
+ if (fullDataFormatter.grid.groupAxis.position === 'bottom') {
106
+ labelY = offsetY
107
+ if (fullDataFormatter.grid.valueAxis.position === 'left') {
108
+ labelX = offsetX
109
+ } else if (fullDataFormatter.grid.valueAxis.position === 'right') {
110
+ labelX = - offsetX
111
+ }
112
+ } else if (fullDataFormatter.grid.groupAxis.position === 'top') {
113
+ labelY = - offsetY
114
+ if (fullDataFormatter.grid.valueAxis.position === 'left') {
115
+ labelX = offsetX
116
+ } else if (fullDataFormatter.grid.valueAxis.position === 'right') {
117
+ labelX = - offsetX
118
+ }
119
+ } else if (fullDataFormatter.grid.groupAxis.position === 'left') {
120
+ labelX = - offsetX
121
+ if (fullDataFormatter.grid.valueAxis.position === 'bottom') {
122
+ labelY = - offsetY
123
+ } else if (fullDataFormatter.grid.valueAxis.position === 'top') {
124
+ labelY = offsetY
125
+ }
126
+ } else if (fullDataFormatter.grid.groupAxis.position === 'right') {
127
+ labelX = offsetX
128
+ if (fullDataFormatter.grid.valueAxis.position === 'bottom') {
129
+ labelY = - offsetY
130
+ } else if (fullDataFormatter.grid.valueAxis.position === 'top') {
131
+ labelY = offsetY
132
+ }
133
+ }
134
+
135
+ const axisLabelSelection = selection
136
+ .selectAll<SVGGElement, BaseGroupAxisParams>(`g.${groupingLabelClassName}`)
137
+ .data([fullParams])
138
+ .join('g')
139
+ .classed(groupingLabelClassName, true)
140
+ .each((d, i, g) => {
141
+ const text = d3.select(g[i])
142
+ .selectAll<SVGTextElement, BaseGroupAxisParams>('text')
143
+ .data([d])
144
+ .join(
145
+ enter => {
146
+ return enter
147
+ .append('text')
148
+ .style('font-weight', 'bold')
149
+ },
150
+ update => update,
151
+ exit => exit.remove()
152
+ )
153
+ .attr('text-anchor', axisLabelAlign.textAnchor)
154
+ .attr('dominant-baseline', axisLabelAlign.dominantBaseline)
155
+ .attr('font-size', chartParams.styles.textSize)
156
+ .style('fill', getColor(fullParams.labelColorType, chartParams))
157
+ .style('transform', textReverseTransform)
158
+ // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
159
+ .attr('x', labelX)
160
+ .attr('y', labelY)
161
+ .text(d => fullDataFormatter.grid.groupAxis.label)
162
+ })
163
+ .attr('transform', d => `translate(${gridAxesSize.width}, 0)`)
164
+ // .attr('transform', d => `translate(${gridAxesSize.width + d.tickPadding + fullParams.labelOffset[0]}, ${- d.tickPadding - fullParams.labelOffset[1]})`)
165
+
166
+ }
167
+
168
+ function renderAxis ({ selection, xAxisClassName, fullParams, tickTextAlign, gridAxesSize, fullDataFormatter, chartParams, groupScale, groupScaleDomain, groupLabelData, textReverseTransformWithRotate, textSizePx }: {
169
+ selection: d3.Selection<SVGGElement, any, any, any>,
170
+ xAxisClassName: string
171
+ fullParams: BaseGroupAxisParams
172
+ tickTextAlign: TextAlign
173
+ gridAxesSize: { width: number, height: number }
174
+ fullDataFormatter: DataFormatterGrid,
175
+ chartParams: ChartParams
176
+ groupScale: d3.ScaleLinear<number, number>
177
+ groupScaleDomain: number[]
178
+ // groupLabels: string[]
179
+ groupLabelData: GroupLabelData[]
180
+ textReverseTransformWithRotate: string
181
+ textSizePx: number
182
+ }) {
183
+
184
+ const xAxisSelection = selection
185
+ .selectAll<SVGGElement, BaseGroupAxisParams>(`g.${xAxisClassName}`)
186
+ .data([fullParams])
187
+ .join('g')
188
+ .classed(xAxisClassName, true)
189
+
190
+ // 計算所有範圍內groupLabels數量(顯示所有刻度)
191
+ const allTicksAmount = Math.floor(groupScaleDomain[1]) - Math.ceil(groupScaleDomain[0]) + 1
192
+
193
+ // 刻度文字偏移
194
+ let tickPadding = 0
195
+ let textX = 0
196
+ if (fullDataFormatter.grid.groupAxis.position === 'left') {
197
+ tickPadding = 0
198
+ textX = - fullParams.tickPadding
199
+ } else if (fullDataFormatter.grid.groupAxis.position === 'right') {
200
+ tickPadding = 0
201
+ textX = fullParams.tickPadding
202
+ } else if (fullDataFormatter.grid.groupAxis.position === 'bottom') {
203
+ tickPadding = fullParams.tickPadding
204
+ textX = 0
205
+ } else if (fullDataFormatter.grid.groupAxis.position === 'top') {
206
+ tickPadding = - fullParams.tickPadding
207
+ textX = - 0
208
+ }
209
+
210
+ // 設定X軸刻度
211
+ const xAxis = d3.axisBottom(groupScale)
212
+ .scale(groupScale)
213
+ .ticks(fullParams.ticks === 'all'
214
+ ? allTicksAmount
215
+ : fullParams.ticks > allTicksAmount
216
+ ? allTicksAmount // 不顯示超過groupLabels數量的刻度
217
+ : fullParams.ticks)
218
+ .tickSize(fullParams.tickFullLine == true
219
+ ? gridAxesSize.height
220
+ : - defaultTickSize)
221
+ .tickSizeOuter(0)
222
+ .tickFormat((groupIndex: number) => {
223
+ // 用index對應到groupLabel
224
+ // const groupLabel = groupLabels[groupIndex] ?? '' // 非整數index不顯示
225
+ // return parseTickFormatValue(groupLabel, fullParams.tickFormat)
226
+ return groupLabelData[groupIndex]?.text ?? ''
227
+ })
228
+ .tickPadding(tickPadding)
229
+
230
+ const xAxisEl = xAxisSelection
231
+ .transition()
232
+ .duration(100)
233
+ .call(xAxis)
234
+ .on('end', (self, t) => {
235
+ // 先等transition結束再處理文字,否則會被原本的文字覆蓋
236
+ xAxisEl
237
+ .selectAll('.tick text')
238
+ .each((groupIndex: number, i, n) => {
239
+ // const groupLabel = groupLabels[groupIndex] ?? '' // 非整數index不顯示
240
+ // const groupLabelText = parseTickFormatValue(groupLabel, fullParams.tickFormat)
241
+ const textArr = groupLabelData[groupIndex]?.textArr ?? []
242
+
243
+ // 將原本單行文字改為多行文字
244
+ renderTspansOnAxis(d3.select(n[i]), {
245
+ textArr,
246
+ textSizePx,
247
+ groupAxisPosition: fullDataFormatter.grid.groupAxis.position
248
+ })
249
+ })
250
+ })
251
+
252
+ xAxisEl.selectAll('line')
253
+ .style('fill', 'none')
254
+ .style('stroke', fullParams.tickLineVisible == true ? getColor(fullParams.tickColorType, chartParams) : 'none')
255
+ .style('stroke-dasharray', fullParams.tickFullLineDasharray)
256
+ .attr('pointer-events', 'none')
257
+
258
+ xAxisEl.selectAll('path')
259
+ .style('fill', 'none')
260
+ .style('stroke', fullParams.axisLineVisible == true ? getColor(fullParams.axisLineColorType, chartParams) : 'none')
261
+ .style('shape-rendering', 'crispEdges')
262
+
263
+ const xText = xAxisSelection.selectAll<SVGTextElement, BaseGroupAxisParams>('text')
264
+ // .style('font-family', 'sans-serif')
265
+ .attr('font-size', chartParams.styles.textSize)
266
+ // .style('font-weight', 'bold')
267
+ .attr('fill', getColor(fullParams.tickTextColorType, chartParams))
268
+ .attr('text-anchor', tickTextAlign.textAnchor)
269
+ .attr('dominant-baseline', tickTextAlign.dominantBaseline)
270
+ .attr('x', textX)
271
+ .style('transform', textReverseTransformWithRotate)
272
+
273
+ // 抵消掉預設的偏移
274
+ // if (fullDataFormatter.grid.groupAxis.position === 'left' || fullDataFormatter.grid.groupAxis.position === 'right') {
275
+ xText.attr('dy', 0)
276
+ // }
277
+
278
+ return xAxisSelection
279
+ }
280
+
281
+
282
+ export const createBaseGroupAxis: BasePluginFn<BaseGroupAxisContext> = ((pluginName: string, {
283
+ selection,
284
+ computedData$,
285
+ fullParams$,
286
+ fullDataFormatter$,
287
+ fullChartParams$,
288
+ gridAxesTransform$,
289
+ gridAxesReverseTransform$,
290
+ gridAxesSize$,
291
+ gridContainerPosition$,
292
+ isSeriesSeprate$,
293
+ textSizePx$,
294
+ }) => {
295
+
296
+ const destroy$ = new Subject()
297
+
298
+ const containerClassName = getClassName(pluginName, 'container')
299
+ const xAxisGClassName = getClassName(pluginName, 'xAxisG')
300
+ const xAxisClassName = getClassName(pluginName, 'xAxis')
301
+ const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
302
+
303
+ const containerSelection$ = combineLatest({
304
+ computedData: computedData$.pipe(
305
+ distinctUntilChanged((a, b) => {
306
+ // 只有當series的數量改變時,才重新計算
307
+ return a.length === b.length
308
+ }),
309
+ ),
310
+ isSeriesSeprate: isSeriesSeprate$
311
+ }).pipe(
312
+ takeUntil(destroy$),
313
+ switchMap(async (d) => d),
314
+ map(data => {
315
+ return data.isSeriesSeprate
316
+ // series分開的時候顯示各別axis
317
+ ? data.computedData
318
+ // series合併的時候只顯示第一個axis
319
+ : [data.computedData[0]]
320
+ }),
321
+ map((computedData, i) => {
322
+ return selection
323
+ .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${containerClassName}`)
324
+ .data(computedData, d => d[0] ? d[0].seriesIndex : i)
325
+ .join('g')
326
+ .classed(containerClassName, true)
327
+ })
328
+ )
329
+
330
+ const axisSelection$ = containerSelection$.pipe(
331
+ takeUntil(destroy$),
332
+ map((containerSelection, i) => {
333
+ return containerSelection
334
+ .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${xAxisGClassName}`)
335
+ .data([xAxisGClassName])
336
+ .join('g')
337
+ .classed(xAxisGClassName, true)
338
+ })
339
+ )
340
+
341
+ combineLatest({
342
+ containerSelection: containerSelection$,
343
+ gridContainerPosition: gridContainerPosition$
344
+ }).pipe(
345
+ takeUntil(destroy$),
346
+ switchMap(async d => d)
347
+ ).subscribe(data => {
348
+ data.containerSelection
349
+ .attr('transform', (d, i) => {
350
+ const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
351
+ const translate = gridContainerPosition.translate
352
+ const scale = gridContainerPosition.scale
353
+ return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
354
+ })
355
+ // .attr('opacity', 0)
356
+ // .transition()
357
+ // .attr('opacity', 1)
358
+ })
359
+
360
+ combineLatest({
361
+ axisSelection: axisSelection$,
362
+ gridAxesTransform: gridAxesTransform$,
363
+ }).pipe(
364
+ takeUntil(destroy$),
365
+ switchMap(async d => d)
366
+ ).subscribe(data => {
367
+ data.axisSelection
368
+ .style('transform', data.gridAxesTransform.value)
369
+ // .attr('opacity', 0)
370
+ // .transition()
371
+ // .attr('opacity', 1)
372
+
373
+ })
374
+
375
+
376
+ // const gridAxesSize$ = gridAxisSizeObservable({
377
+ // fullDataFormatter$,
378
+ // layout$
379
+ // })
380
+
381
+
382
+ // const tickTextFormatter$ = fullDataFormatter$
383
+ // .pipe(
384
+ // map(d => {
385
+ // return d.grid.seriesDirection === 'row' ? d.grid.columnLabelFormat : d.grid.rowLabelFormat
386
+ // })
387
+ // )
388
+
389
+ // const textReverseTransform$: Observable<string> = new Observable(subscriber => {
390
+ // combineLatest({
391
+ // params: fullParams$,
392
+ // gridAxesTransform: gridAxesTransform$
393
+ // }).pipe(
394
+ // takeUntil(destroy$),
395
+ // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
396
+ // switchMap(async (d) => d),
397
+ // ).subscribe(data => {
398
+
399
+ // const transformData = Object.assign({}, data.gridAxesTransform)
400
+
401
+ // const value = getAxesTransformValue({
402
+ // translate: [0, 0],
403
+ // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
404
+ // rotate: transformData.rotate * -1 + data.params.tickTextRotate,
405
+ // rotateX: transformData.rotateX * -1,
406
+ // rotateY: transformData.rotateY * -1
407
+ // })
408
+
409
+ // subscriber.next(value)
410
+ // })
411
+ // })
412
+ // const reverseTransform$: Observable<TransformData> = gridAxesTransform$.pipe(
413
+ // takeUntil(destroy$),
414
+ // map(d => {
415
+ // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
416
+ // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
417
+ // const rotate = d.rotate * -1
418
+ // const rotateX = d.rotateX * -1
419
+ // const rotateY = d.rotateY * -1
420
+ // return {
421
+ // translate,
422
+ // scale,
423
+ // rotate,
424
+ // rotateX,
425
+ // rotateY,
426
+ // value: ''
427
+ // }
428
+ // }),
429
+ // )
430
+ const textReverseTransform$ = combineLatest({
431
+ gridAxesReverseTransform: gridAxesReverseTransform$,
432
+ gridContainerPosition: gridContainerPosition$
433
+ }).pipe(
434
+ takeUntil(destroy$),
435
+ switchMap(async (d) => d),
436
+ map(data => {
437
+ // const axisReverseTranslateValue = `translate(${data.gridAxesReverseTransform.translate[0]}px, ${data.gridAxesReverseTransform.translate[1]}px)`
438
+ const axesRotateXYReverseValue = `rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
439
+ const axesRotateReverseValue = `rotate(${data.gridAxesReverseTransform.rotate}deg)`
440
+ const containerScaleReverseValue = `scale(${1 / data.gridContainerPosition[0].scale[0]}, ${1 / data.gridContainerPosition[0].scale[1]})`
441
+ // 必須按照順序(先抵消外層rotate,再抵消最外層scale)
442
+ return `${axesRotateXYReverseValue} ${axesRotateReverseValue} ${containerScaleReverseValue}`
443
+ }),
444
+ distinctUntilChanged()
445
+ )
446
+
447
+ const textReverseTransformWithRotate$ = combineLatest({
448
+ textReverseTransform: textReverseTransform$,
449
+ fullParams: fullParams$,
450
+ }).pipe(
451
+ takeUntil(destroy$),
452
+ switchMap(async (d) => d),
453
+ map(data => {
454
+ // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
455
+ return `${data.textReverseTransform} rotate(${data.fullParams.tickTextRotate}deg)`
456
+ })
457
+ )
458
+
459
+ // 使用pointScale計算非連續性比例尺
460
+ // const groupScale$: Observable<d3.ScalePoint<string>> = new Observable(subscriber => {
461
+ // combineLatest({
462
+ // fullDataFormatter: fullDataFormatter$,
463
+ // gridAxesSize: gridAxesSize$,
464
+ // computedData: computedData$
465
+ // }).pipe(
466
+ // takeUntil(destroy$),
467
+ // switchMap(async (d) => d),
468
+ // ).subscribe(data => {
469
+ // const groupMin = 0
470
+ // const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
471
+ // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
472
+ // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
473
+ // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
474
+ // const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'auto'
475
+ // ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
476
+ // : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
477
+
478
+ // const groupingLength = data.computedData[0]
479
+ // ? data.computedData[0].length
480
+ // : 0
481
+
482
+ // let _labels = (data.computedData[0] ?? []).map(d => d.groupLabel)
483
+
484
+ // const axisLabels = new Array(groupingLength).fill(0)
485
+ // .map((d, i) => {
486
+ // return _labels[i] != null
487
+ // ? _labels[i]
488
+ // : String(i) // 沒有label則用序列號填充
489
+ // })
490
+ // .filter((d, i) => {
491
+ // return i >= groupScaleDomainMin && i <= groupScaleDomainMax
492
+ // })
493
+
494
+
495
+ // const padding = data.fullDataFormatter.grid.groupAxis.scalePadding
496
+
497
+ // const groupScale = createAxisPointScale({
498
+ // axisLabels,
499
+ // axisWidth: data.gridAxesSize.width,
500
+ // padding
501
+ // })
502
+
503
+ // subscriber.next(groupScale)
504
+ // })
505
+ // })
506
+
507
+ const groupScaleDomain$ = combineLatest({
508
+ fullDataFormatter: fullDataFormatter$,
509
+ gridAxesSize: gridAxesSize$,
510
+ computedData: computedData$
511
+ }).pipe(
512
+ takeUntil(destroy$),
513
+ switchMap(async (d) => d),
514
+ map(data => {
515
+ const groupMin = 0
516
+ const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
517
+ // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
518
+ // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
519
+ // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
520
+ const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] - data.fullDataFormatter.grid.groupAxis.scalePadding
521
+ const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'max'
522
+ ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
523
+ : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
524
+
525
+ return [groupScaleDomainMin, groupScaleDomainMax]
526
+ }),
527
+ shareReplay(1)
528
+ )
529
+
530
+ const groupScale$ = combineLatest({
531
+ groupScaleDomain: groupScaleDomain$,
532
+ gridAxesSize: gridAxesSize$
533
+ }).pipe(
534
+ takeUntil(destroy$),
535
+ switchMap(async (d) => d),
536
+ map(data => {
537
+ const groupScale: d3.ScaleLinear<number, number> = d3.scaleLinear()
538
+ .domain(data.groupScaleDomain)
539
+ .range([0, data.gridAxesSize.width])
540
+ return groupScale
541
+ })
542
+ )
543
+
544
+ const groupLabels$ = computedData$.pipe(
545
+ map(computedData => (computedData[0] ?? []).map(d => d.groupLabel))
546
+ )
547
+
548
+ const tickTextAlign$: Observable<TextAlign> = combineLatest({
549
+ fullDataFormatter: fullDataFormatter$,
550
+ fullParams: fullParams$
551
+ }).pipe(
552
+ takeUntil(destroy$),
553
+ switchMap(async (d) => d),
554
+ map(data => {
555
+ let textAnchor: 'start' | 'middle' | 'end' = 'middle'
556
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
557
+
558
+ if (data.fullDataFormatter.grid.groupAxis.position === 'bottom') {
559
+ textAnchor = data.fullParams.tickTextRotate
560
+ ? 'end'
561
+ : 'middle'
562
+ dominantBaseline = 'hanging'
563
+ } else if (data.fullDataFormatter.grid.groupAxis.position === 'top') {
564
+ textAnchor = data.fullParams.tickTextRotate
565
+ ? 'start'
566
+ : 'middle'
567
+ dominantBaseline = 'auto'
568
+ } else if (data.fullDataFormatter.grid.groupAxis.position === 'left') {
569
+ textAnchor = 'end'
570
+ dominantBaseline = 'middle'
571
+ } else if (data.fullDataFormatter.grid.groupAxis.position === 'right') {
572
+ textAnchor = 'start'
573
+ dominantBaseline = 'middle'
574
+ }
575
+ return {
576
+ textAnchor,
577
+ dominantBaseline
578
+ }
579
+ })
580
+ )
581
+
582
+ const axisLabelAlign$: Observable<TextAlign> = fullDataFormatter$.pipe(
583
+ takeUntil(destroy$),
584
+ map(d => {
585
+ let textAnchor: 'start' | 'middle' | 'end' = 'start'
586
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
587
+
588
+ if (d.grid.groupAxis.position === 'bottom') {
589
+ dominantBaseline = 'hanging'
590
+ } else if (d.grid.groupAxis.position === 'top') {
591
+ dominantBaseline = 'auto'
592
+ } else if (d.grid.groupAxis.position === 'left') {
593
+ textAnchor = 'end'
594
+ } else if (d.grid.groupAxis.position === 'right') {
595
+ textAnchor = 'start'
596
+ }
597
+ if (d.grid.valueAxis.position === 'left') {
598
+ textAnchor = 'start'
599
+ } else if (d.grid.valueAxis.position === 'right') {
600
+ textAnchor = 'end'
601
+ } else if (d.grid.valueAxis.position === 'bottom') {
602
+ dominantBaseline = 'auto'
603
+ } else if (d.grid.valueAxis.position === 'top') {
604
+ dominantBaseline = 'hanging'
605
+ }
606
+ return {
607
+ textAnchor,
608
+ dominantBaseline
609
+ }
610
+ })
611
+ )
612
+
613
+ const groupLabelData$ = combineLatest({
614
+ groupLabels: groupLabels$,
615
+ fullParams: fullParams$
616
+ }).pipe(
617
+ takeUntil(destroy$),
618
+ switchMap(async (d) => d),
619
+ map(data => {
620
+ return createGroupLabelData(data.groupLabels, data.fullParams.tickFormat)
621
+ })
622
+ )
623
+
624
+ combineLatest({
625
+ axisSelection: axisSelection$,
626
+ fullParams: fullParams$,
627
+ tickTextAlign: tickTextAlign$,
628
+ axisLabelAlign: axisLabelAlign$,
629
+ gridAxesSize: gridAxesSize$,
630
+ fullDataFormatter: fullDataFormatter$,
631
+ chartParams: fullChartParams$,
632
+ groupScale: groupScale$,
633
+ groupScaleDomain: groupScaleDomain$,
634
+ // groupLabels: groupLabels$,
635
+ groupLabelData: groupLabelData$,
636
+ textReverseTransform: textReverseTransform$,
637
+ textReverseTransformWithRotate: textReverseTransformWithRotate$,
638
+ textSizePx: textSizePx$
639
+ // tickTextFormatter: tickTextFormatter$
640
+ }).pipe(
641
+ takeUntil(destroy$),
642
+ switchMap(async (d) => d),
643
+ ).subscribe(data => {
644
+
645
+ renderAxis({
646
+ selection: data.axisSelection,
647
+ xAxisClassName,
648
+ fullParams: data.fullParams,
649
+ tickTextAlign: data.tickTextAlign,
650
+ gridAxesSize: data.gridAxesSize,
651
+ fullDataFormatter: data.fullDataFormatter,
652
+ chartParams: data.chartParams,
653
+ groupScale: data.groupScale,
654
+ groupScaleDomain: data.groupScaleDomain,
655
+ // groupLabels: data.groupLabels,
656
+ groupLabelData: data.groupLabelData,
657
+ textReverseTransformWithRotate: data.textReverseTransformWithRotate,
658
+ textSizePx: data.textSizePx
659
+ })
660
+
661
+ renderAxisLabel({
662
+ selection: data.axisSelection,
663
+ groupingLabelClassName,
664
+ fullParams: data.fullParams,
665
+ axisLabelAlign: data.axisLabelAlign,
666
+ gridAxesSize: data.gridAxesSize,
667
+ fullDataFormatter: data.fullDataFormatter,
668
+ chartParams: data.chartParams,
669
+ textReverseTransform: data.textReverseTransform,
670
+ })
671
+ })
672
+
673
+ return () => {
674
+ destroy$.next(undefined)
675
+ }
653
676
  })