@orbcharts/plugins-basic 3.0.0-alpha.61 → 3.0.0-alpha.63

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