@orbcharts/plugins-basic 3.0.0-alpha.68 → 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,578 +1,578 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- switchMap,
5
- distinctUntilChanged,
6
- first,
7
- map,
8
- takeUntil,
9
- Observable,
10
- Subject } from 'rxjs'
11
- import { createAxisLinearScale } from '@orbcharts/core'
12
- import type { BasePluginFn } from './types'
13
- import type {
14
- ComputedDataGrid,
15
- DataFormatterGrid,
16
- ChartParams,
17
- ComputedDatumGrid,
18
- GridContainerPosition,
19
- TransformData,
20
- EventGrid,
21
- ColorType } from '@orbcharts/core'
22
- import { parseTickFormatValue } from '../utils/d3Utils'
23
- import { getColor, getMinAndMaxValue, getClassName, getUniID } from '../utils/orbchartsUtils'
24
-
25
- export interface BaseValueAxisParams {
26
- labelOffset: [number, number]
27
- labelColorType: ColorType
28
- axisLineVisible: boolean
29
- axisLineColorType: ColorType
30
- ticks: number
31
- tickFormat: string | ((text: d3.NumberValue) => string)
32
- tickLineVisible: boolean
33
- tickPadding: number
34
- tickFullLine: boolean
35
- tickFullLineDasharray: string
36
- tickColorType: ColorType
37
- tickTextRotate: number
38
- tickTextColorType: ColorType
39
- }
40
-
41
- interface BaseLinesContext {
42
- selection: d3.Selection<any, unknown, any, unknown>
43
- computedData$: Observable<ComputedDataGrid>
44
- fullParams$: Observable<BaseValueAxisParams>
45
- fullDataFormatter$: Observable<DataFormatterGrid>
46
- fullChartParams$: Observable<ChartParams>
47
- gridAxesTransform$: Observable<TransformData>
48
- gridAxesReverseTransform$: Observable<TransformData>
49
- gridAxesSize$: Observable<{
50
- width: number;
51
- height: number;
52
- }>
53
- gridContainerPosition$: Observable<GridContainerPosition[]>
54
- isSeriesSeprate$: Observable<boolean>
55
- }
56
-
57
- interface TextAlign {
58
- textAnchor: "start" | "middle" | "end"
59
- dominantBaseline: "middle" | "auto" | "hanging"
60
- }
61
-
62
- // const pluginName = 'ValueAxis'
63
- // const containerClassName = getClassName(pluginName, 'container')
64
- // const yAxisGClassName = getClassName(pluginName, 'yAxisG')
65
- // const yAxisClassName = getClassName(pluginName, 'yAxis')
66
- // const textClassName = getClassName(pluginName, 'text')
67
- const defaultTickSize = 6
68
-
69
- function renderAxisLabel ({ selection, textClassName, fullParams, axisLabelAlign, gridAxesSize, fullDataFormatter, fullChartParams, textReverseTransform }: {
70
- selection: d3.Selection<SVGGElement, any, any, any>,
71
- textClassName: string
72
- fullParams: BaseValueAxisParams
73
- axisLabelAlign: TextAlign
74
- gridAxesSize: { width: number, height: number }
75
- fullDataFormatter: DataFormatterGrid,
76
- fullChartParams: ChartParams
77
- textReverseTransform: string,
78
- }) {
79
- const offsetX = fullParams.tickPadding - fullParams.labelOffset[0]
80
- const offsetY = fullParams.tickPadding + fullParams.labelOffset[1]
81
- let labelX = 0
82
- let labelY = 0
83
- if (fullDataFormatter.grid.groupAxis.position === 'bottom') {
84
- // labelY = - gridAxesSize.height - offsetY
85
- labelY = - offsetY
86
- if (fullDataFormatter.grid.valueAxis.position === 'left') {
87
- labelX = - offsetX
88
- } else if (fullDataFormatter.grid.valueAxis.position === 'right') {
89
- labelX = offsetX
90
- }
91
- } else if (fullDataFormatter.grid.groupAxis.position === 'top') {
92
- // labelY = gridAxesSize.height + offsetY
93
- labelY = offsetY
94
- if (fullDataFormatter.grid.valueAxis.position === 'left') {
95
- labelX = - offsetX
96
- } else if (fullDataFormatter.grid.valueAxis.position === 'right') {
97
- labelX = offsetX
98
- }
99
- } else if (fullDataFormatter.grid.groupAxis.position === 'left') {
100
- // labelX = gridAxesSize.width + offsetX
101
- labelX = offsetX
102
- if (fullDataFormatter.grid.valueAxis.position === 'bottom') {
103
- labelY = offsetY
104
- } else if (fullDataFormatter.grid.valueAxis.position === 'top') {
105
- labelY = - offsetY
106
- }
107
- } else if (fullDataFormatter.grid.groupAxis.position === 'right') {
108
- labelX = - offsetX
109
- if (fullDataFormatter.grid.valueAxis.position === 'bottom') {
110
- labelY = offsetY
111
- } else if (fullDataFormatter.grid.valueAxis.position === 'top') {
112
- labelY = - offsetY
113
- }
114
- }
115
-
116
- const axisLabelSelection = selection
117
- .selectAll<SVGGElement, BaseValueAxisParams>(`g.${textClassName}`)
118
- .data([fullParams])
119
- .join('g')
120
- .classed(textClassName, true)
121
- .each((d, i, g) => {
122
- const text = d3.select(g[i])
123
- .selectAll<SVGTextElement, BaseValueAxisParams>(`text`)
124
- .data([d])
125
- .join(
126
- enter => {
127
- return enter
128
- .append('text')
129
- .style('font-weight', 'bold')
130
- },
131
- update => update,
132
- exit => exit.remove()
133
- )
134
- .attr('text-anchor', axisLabelAlign.textAnchor)
135
- .attr('dominant-baseline', axisLabelAlign.dominantBaseline)
136
- .attr('font-size', fullChartParams.styles.textSize)
137
- .style('fill', getColor(fullParams.labelColorType, fullChartParams))
138
- .style('transform', textReverseTransform)
139
- // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
140
- .attr('x', labelX)
141
- .attr('y', labelY)
142
- .text(d => fullDataFormatter.grid.valueAxis.label)
143
- })
144
- .attr('transform', d => `translate(0, ${gridAxesSize.height})`)
145
- // .attr('transform', d => `translate(${- fullParams.tickPadding + fullParams.labelOffset[0]}, ${gridAxesSize.height + fullParams.tickPadding + fullParams.labelOffset[1]})`)
146
-
147
-
148
- }
149
-
150
- function renderAxis ({ selection, yAxisClassName, fullParams, tickTextAlign, gridAxesSize, fullDataFormatter, fullChartParams, valueScale, textReverseTransformWithRotate, minAndMax }: {
151
- selection: d3.Selection<SVGGElement, any, any, any>,
152
- yAxisClassName: string
153
- fullParams: BaseValueAxisParams
154
- tickTextAlign: TextAlign
155
- gridAxesSize: { width: number, height: number }
156
- fullDataFormatter: DataFormatterGrid,
157
- fullChartParams: ChartParams
158
- valueScale: d3.ScaleLinear<number, number>
159
- textReverseTransformWithRotate: string,
160
- minAndMax: [number, number]
161
- }) {
162
-
163
- const yAxisSelection = selection
164
- .selectAll<SVGGElement, BaseValueAxisParams>(`g.${yAxisClassName}`)
165
- .data([fullParams])
166
- .join('g')
167
- .classed(yAxisClassName, true)
168
-
169
- const valueLength = minAndMax[1] - minAndMax[0]
170
-
171
- // const _valueScale = d3.scaleLinear()
172
- // .domain([0, 150])
173
- // .range([416.5, 791.349])
174
-
175
- // 刻度文字偏移
176
- let tickPadding = 0
177
- let textY = 0
178
- if (fullDataFormatter.grid.valueAxis.position === 'left') {
179
- tickPadding = fullParams.tickPadding
180
- textY = 0
181
- } else if (fullDataFormatter.grid.valueAxis.position === 'right') {
182
- tickPadding = - fullParams.tickPadding
183
- textY = 0
184
- } else if (fullDataFormatter.grid.valueAxis.position === 'bottom') {
185
- tickPadding = 0
186
- textY = fullParams.tickPadding
187
- } else if (fullDataFormatter.grid.valueAxis.position === 'top') {
188
- tickPadding = 0
189
- textY = - fullParams.tickPadding
190
- }
191
-
192
- // 設定Y軸刻度
193
- const yAxis = d3.axisLeft(valueScale)
194
- .scale(valueScale)
195
- .ticks(valueLength > fullParams.ticks
196
- ? fullParams.ticks
197
- : ((minAndMax[0] === 0 && minAndMax[1] === 0)
198
- ? 1
199
- : Math.ceil(valueLength))) // 刻度分段數量
200
- .tickFormat(d => parseTickFormatValue(d, fullParams.tickFormat))
201
- .tickSize(fullParams.tickFullLine == true
202
- ? -gridAxesSize.width
203
- : defaultTickSize)
204
- .tickPadding(tickPadding)
205
-
206
- const yAxisEl = yAxisSelection
207
- .transition()
208
- .duration(100)
209
- .call(yAxis)
210
-
211
- yAxisEl.selectAll('line')
212
- .style('fill', 'none')
213
- .style('stroke', fullParams.tickLineVisible == true ? getColor(fullParams.tickColorType, fullChartParams) : 'none')
214
- .style('stroke-dasharray', fullParams.tickFullLineDasharray)
215
- .attr('pointer-events', 'none')
216
-
217
- yAxisEl.selectAll('path')
218
- .style('fill', 'none')
219
- // .style('stroke', this.fullParams.axisLineColor!)
220
- .style('stroke', fullParams.axisLineVisible == true ? getColor(fullParams.axisLineColorType, fullChartParams) : 'none')
221
- .style('shape-rendering', 'crispEdges')
222
-
223
- // const yText = yAxisEl.selectAll('text')
224
- const yText = yAxisSelection.selectAll('text')
225
- // .style('font-family', 'sans-serif')
226
- .attr('font-size', fullChartParams.styles.textSize)
227
- .style('color', getColor(fullParams.tickTextColorType, fullChartParams))
228
- .attr('text-anchor', tickTextAlign.textAnchor)
229
- .attr('dominant-baseline', tickTextAlign.dominantBaseline)
230
- // .attr('dy', 0)
231
- .attr('y', textY)
232
- yText.style('transform', textReverseTransformWithRotate)
233
-
234
- // 抵消掉預設的偏移
235
- if (fullDataFormatter.grid.valueAxis.position === 'bottom' || fullDataFormatter.grid.valueAxis.position === 'top') {
236
- yText.attr('dy', 0)
237
- }
238
-
239
- return yAxisSelection
240
- }
241
-
242
-
243
-
244
- export const createBaseValueAxis: BasePluginFn<BaseLinesContext> = (pluginName: string, {
245
- selection,
246
- computedData$,
247
- fullParams$,
248
- fullDataFormatter$,
249
- fullChartParams$,
250
- gridAxesTransform$,
251
- gridAxesReverseTransform$,
252
- gridAxesSize$,
253
- gridContainerPosition$,
254
- isSeriesSeprate$,
255
- }) => {
256
-
257
- const destroy$ = new Subject()
258
-
259
- const containerClassName = getClassName(pluginName, 'container')
260
- const yAxisGClassName = getClassName(pluginName, 'yAxisG')
261
- const yAxisClassName = getClassName(pluginName, 'yAxis')
262
- const textClassName = getClassName(pluginName, 'text')
263
-
264
- const containerSelection$ = combineLatest({
265
- computedData: computedData$.pipe(
266
- distinctUntilChanged((a, b) => {
267
- // 只有當series的數量改變時,才重新計算
268
- return a.length === b.length
269
- }),
270
- ),
271
- isSeriesSeprate: isSeriesSeprate$
272
- }).pipe(
273
- takeUntil(destroy$),
274
- switchMap(async (d) => d),
275
- map(data => {
276
- return data.isSeriesSeprate
277
- // series分開的時候顯示各別axis
278
- ? data.computedData
279
- // series合併的時候只顯示第一個axis
280
- : [data.computedData[0]]
281
- }),
282
- map((computedData, i) => {
283
- return selection
284
- .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${containerClassName}`)
285
- .data(computedData, d => d[0] ? d[0].seriesIndex : i)
286
- .join('g')
287
- .classed(containerClassName, true)
288
- })
289
- )
290
-
291
- const axisSelection$ = containerSelection$.pipe(
292
- takeUntil(destroy$),
293
- map((containerSelection, i) => {
294
- return containerSelection
295
- .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${yAxisGClassName}`)
296
- .data([yAxisGClassName])
297
- .join('g')
298
- .classed(yAxisGClassName, true)
299
- })
300
- )
301
-
302
- combineLatest({
303
- containerSelection: containerSelection$,
304
- gridContainerPosition: gridContainerPosition$
305
- }).pipe(
306
- takeUntil(destroy$),
307
- switchMap(async d => d)
308
- ).subscribe(data => {
309
- data.containerSelection
310
- .attr('transform', (d, i) => {
311
- const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
312
- const translate = gridContainerPosition.translate
313
- const scale = gridContainerPosition.scale
314
- return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
315
- })
316
- // .attr('opacity', 0)
317
- // .transition()
318
- // .attr('opacity', 1)
319
- })
320
-
321
- combineLatest({
322
- axisSelection: axisSelection$,
323
- gridAxesTransform: gridAxesTransform$,
324
- }).pipe(
325
- takeUntil(destroy$),
326
- switchMap(async d => d)
327
- ).subscribe(data => {
328
- data.axisSelection
329
- .style('transform', data.gridAxesTransform.value)
330
- // .attr('opacity', 0)
331
- // .transition()
332
- // .attr('opacity', 1)
333
-
334
- })
335
-
336
- // const gridAxesSize$ = gridAxisSizeObservable({
337
- // fullDataFormatter$,
338
- // layout$
339
- // })
340
-
341
- // const textReverseTransform$: Observable<string> = new Observable(subscriber => {
342
- // combineLatest({
343
- // fullParams: fullParams$,
344
- // layout: layout$
345
- // }).pipe(
346
- // takeUntil(destroy$),
347
- // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
348
- // switchMap(async (d) => d),
349
- // ).subscribe(data => {
350
-
351
- // const transformData = Object.assign({}, data.layout.content.axesTransformData)
352
-
353
- // const value = getAxesTransformValue({
354
- // translate: [0, 0],
355
- // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
356
- // rotate: transformData.rotate * -1 + data.fullParams.tickTextRotate,
357
- // rotateX: transformData.rotateX * -1,
358
- // rotateY: transformData.rotateY * -1
359
- // })
360
-
361
- // subscriber.next(value)
362
- // })
363
- // })
364
- // const reverseTransform$: Observable<TransformData> = gridAxesTransform$.pipe(
365
- // takeUntil(destroy$),
366
- // map(d => {
367
- // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
368
- // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
369
- // const rotate = d.rotate * -1
370
- // const rotateX = d.rotateX * -1
371
- // const rotateY = d.rotateY * -1
372
- // return {
373
- // translate,
374
- // scale,
375
- // rotate,
376
- // rotateX,
377
- // rotateY,
378
- // value: ''
379
- // }
380
- // }),
381
- // )
382
- const textReverseTransform$ = combineLatest({
383
- gridAxesReverseTransform: gridAxesReverseTransform$,
384
- gridContainerPosition: gridContainerPosition$
385
- }).pipe(
386
- takeUntil(destroy$),
387
- switchMap(async (d) => d),
388
- map(data => {
389
- // const axisReverseTranslateValue = `translate(${data.gridAxesReverseTransform.translate[0]}px, ${data.gridAxesReverseTransform.translate[1]}px)`
390
- const axesRotateXYReverseValue = `rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
391
- const axesRotateReverseValue = `rotate(${data.gridAxesReverseTransform.rotate}deg)`
392
- const containerScaleReverseValue = `scale(${1 / data.gridContainerPosition[0].scale[0]}, ${1 / data.gridContainerPosition[0].scale[1]})`
393
- // 必須按照順序(先抵消外層rotate,再抵消最外層scale)
394
- return `${axesRotateXYReverseValue} ${axesRotateReverseValue} ${containerScaleReverseValue}`
395
- }),
396
- distinctUntilChanged()
397
- )
398
-
399
- const textReverseTransformWithRotate$ = combineLatest({
400
- textReverseTransform: textReverseTransform$,
401
- fullParams: fullParams$,
402
- }).pipe(
403
- takeUntil(destroy$),
404
- switchMap(async (d) => d),
405
- map(data => {
406
- // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
407
- return `${data.textReverseTransform} rotate(${data.fullParams.tickTextRotate}deg)`
408
- })
409
- )
410
-
411
- const minAndMax$: Observable<[number, number]> = new Observable(subscriber => {
412
- combineLatest({
413
- fullDataFormatter: fullDataFormatter$,
414
- gridAxesSize: gridAxesSize$,
415
- computedData: computedData$
416
- }).pipe(
417
- takeUntil(destroy$),
418
- switchMap(async (d) => d),
419
- ).subscribe(data => {
420
- const groupMin = 0
421
- const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
422
- // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
423
- // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
424
- // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
425
- const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] - data.fullDataFormatter.grid.groupAxis.scalePadding
426
- const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'max'
427
- ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
428
- : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
429
-
430
- const filteredData = data.computedData.map((d, i) => {
431
- return d.filter((_d, _i) => {
432
- return _i >= groupScaleDomainMin && _i <= groupScaleDomainMax
433
- })
434
- })
435
-
436
- const filteredMinAndMax = getMinAndMaxValue(filteredData.flat())
437
- if (filteredMinAndMax[0] === filteredMinAndMax[1]) {
438
- filteredMinAndMax[0] = filteredMinAndMax[1] - 1 // 避免最大及最小值相同造成無法計算scale
439
- }
440
- subscriber.next(filteredMinAndMax)
441
- })
442
- })
443
-
444
- const valueScale$: Observable<d3.ScaleLinear<number, number>> = new Observable(subscriber => {
445
- combineLatest({
446
- fullDataFormatter: fullDataFormatter$,
447
- gridAxesSize: gridAxesSize$,
448
- minAndMax: minAndMax$
449
- }).pipe(
450
- takeUntil(destroy$),
451
- switchMap(async (d) => d),
452
- ).subscribe(data => {
453
-
454
- const valueScale: d3.ScaleLinear<number, number> = createAxisLinearScale({
455
- maxValue: data.minAndMax[1],
456
- minValue: data.minAndMax[0],
457
- axisWidth: data.gridAxesSize.height,
458
- scaleDomain: data.fullDataFormatter.grid.valueAxis.scaleDomain,
459
- scaleRange: data.fullDataFormatter.grid.valueAxis.scaleRange
460
- })
461
-
462
- subscriber.next(valueScale)
463
- })
464
- })
465
-
466
- const tickTextAlign$: Observable<TextAlign> = combineLatest({
467
- fullDataFormatter: fullDataFormatter$,
468
- fullParams: fullParams$
469
- }).pipe(
470
- takeUntil(destroy$),
471
- switchMap(async (d) => d),
472
- map(data => {
473
- let textAnchor: 'start' | 'middle' | 'end' = 'start'
474
- let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
475
-
476
- if (data.fullDataFormatter.grid.valueAxis.position === 'left') {
477
- textAnchor = 'end'
478
- dominantBaseline = 'middle'
479
- } else if (data.fullDataFormatter.grid.valueAxis.position === 'right') {
480
- textAnchor = 'start'
481
- dominantBaseline = 'middle'
482
- } else if (data.fullDataFormatter.grid.valueAxis.position === 'bottom') {
483
- textAnchor = data.fullParams.tickTextRotate
484
- ? 'end'
485
- : 'middle'
486
- dominantBaseline = 'hanging'
487
- } else if (data.fullDataFormatter.grid.valueAxis.position === 'top') {
488
- textAnchor = data.fullParams.tickTextRotate
489
- ? 'start'
490
- : 'middle'
491
- dominantBaseline = 'auto'
492
- }
493
- return {
494
- textAnchor,
495
- dominantBaseline
496
- }
497
- })
498
- )
499
-
500
- const axisLabelAlign$: Observable<TextAlign> = fullDataFormatter$.pipe(
501
- takeUntil(destroy$),
502
- map(d => {
503
- let textAnchor: 'start' | 'middle' | 'end' = 'start'
504
- let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
505
-
506
- if (d.grid.groupAxis.position === 'bottom') {
507
- dominantBaseline = 'auto'
508
- } else if (d.grid.groupAxis.position === 'top') {
509
- dominantBaseline = 'hanging'
510
- } else if (d.grid.groupAxis.position === 'left') {
511
- textAnchor = 'start'
512
- } else if (d.grid.groupAxis.position === 'right') {
513
- textAnchor = 'end'
514
- }
515
- if (d.grid.valueAxis.position === 'left') {
516
- textAnchor = 'end'
517
- } else if (d.grid.valueAxis.position === 'right') {
518
- textAnchor = 'start'
519
- } else if (d.grid.valueAxis.position === 'bottom') {
520
- dominantBaseline = 'hanging'
521
- } else if (d.grid.valueAxis.position === 'top') {
522
- dominantBaseline = 'auto'
523
- }
524
- return {
525
- textAnchor,
526
- dominantBaseline
527
- }
528
- })
529
- )
530
-
531
-
532
- combineLatest({
533
- axisSelection: axisSelection$,
534
- fullParams: fullParams$,
535
- tickTextAlign: tickTextAlign$,
536
- axisLabelAlign: axisLabelAlign$,
537
- computedData: computedData$,
538
- gridAxesSize: gridAxesSize$,
539
- fullDataFormatter: fullDataFormatter$,
540
- fullChartParams: fullChartParams$,
541
- valueScale: valueScale$,
542
- textReverseTransform: textReverseTransform$,
543
- textReverseTransformWithRotate: textReverseTransformWithRotate$,
544
- minAndMax: minAndMax$
545
- }).pipe(
546
- takeUntil(destroy$),
547
- switchMap(async (d) => d),
548
- ).subscribe(data => {
549
-
550
- renderAxis({
551
- selection: data.axisSelection,
552
- yAxisClassName,
553
- fullParams: data.fullParams,
554
- tickTextAlign: data.tickTextAlign,
555
- gridAxesSize: data.gridAxesSize,
556
- fullDataFormatter: data.fullDataFormatter,
557
- fullChartParams: data.fullChartParams,
558
- valueScale: data.valueScale,
559
- textReverseTransformWithRotate: data.textReverseTransformWithRotate,
560
- minAndMax: data.minAndMax
561
- })
562
-
563
- renderAxisLabel({
564
- selection: data.axisSelection,
565
- textClassName,
566
- fullParams: data.fullParams,
567
- axisLabelAlign: data.axisLabelAlign,
568
- gridAxesSize: data.gridAxesSize,
569
- fullDataFormatter: data.fullDataFormatter,
570
- fullChartParams: data.fullChartParams,
571
- textReverseTransform: data.textReverseTransform,
572
- })
573
- })
574
-
575
- return () => {
576
- destroy$.next(undefined)
577
- }
578
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ switchMap,
5
+ distinctUntilChanged,
6
+ first,
7
+ map,
8
+ takeUntil,
9
+ Observable,
10
+ Subject } from 'rxjs'
11
+ import { createAxisLinearScale } from '@orbcharts/core'
12
+ import type { BasePluginFn } from './types'
13
+ import type {
14
+ ComputedDataGrid,
15
+ DataFormatterGrid,
16
+ ChartParams,
17
+ ComputedDatumGrid,
18
+ GridContainerPosition,
19
+ TransformData,
20
+ EventGrid,
21
+ ColorType } from '@orbcharts/core'
22
+ import { parseTickFormatValue } from '../utils/d3Utils'
23
+ import { getColor, getMinAndMaxValue, getClassName, getUniID } from '../utils/orbchartsUtils'
24
+
25
+ export interface BaseValueAxisParams {
26
+ labelOffset: [number, number]
27
+ labelColorType: ColorType
28
+ axisLineVisible: boolean
29
+ axisLineColorType: ColorType
30
+ ticks: number
31
+ tickFormat: string | ((text: d3.NumberValue) => string)
32
+ tickLineVisible: boolean
33
+ tickPadding: number
34
+ tickFullLine: boolean
35
+ tickFullLineDasharray: string
36
+ tickColorType: ColorType
37
+ tickTextRotate: number
38
+ tickTextColorType: ColorType
39
+ }
40
+
41
+ interface BaseLinesContext {
42
+ selection: d3.Selection<any, unknown, any, unknown>
43
+ computedData$: Observable<ComputedDataGrid>
44
+ fullParams$: Observable<BaseValueAxisParams>
45
+ fullDataFormatter$: Observable<DataFormatterGrid>
46
+ fullChartParams$: Observable<ChartParams>
47
+ gridAxesTransform$: Observable<TransformData>
48
+ gridAxesReverseTransform$: Observable<TransformData>
49
+ gridAxesSize$: Observable<{
50
+ width: number;
51
+ height: number;
52
+ }>
53
+ gridContainerPosition$: Observable<GridContainerPosition[]>
54
+ isSeriesSeprate$: Observable<boolean>
55
+ }
56
+
57
+ interface TextAlign {
58
+ textAnchor: "start" | "middle" | "end"
59
+ dominantBaseline: "middle" | "auto" | "hanging"
60
+ }
61
+
62
+ // const pluginName = 'ValueAxis'
63
+ // const containerClassName = getClassName(pluginName, 'container')
64
+ // const yAxisGClassName = getClassName(pluginName, 'yAxisG')
65
+ // const yAxisClassName = getClassName(pluginName, 'yAxis')
66
+ // const textClassName = getClassName(pluginName, 'text')
67
+ const defaultTickSize = 6
68
+
69
+ function renderAxisLabel ({ selection, textClassName, fullParams, axisLabelAlign, gridAxesSize, fullDataFormatter, fullChartParams, textReverseTransform }: {
70
+ selection: d3.Selection<SVGGElement, any, any, any>,
71
+ textClassName: string
72
+ fullParams: BaseValueAxisParams
73
+ axisLabelAlign: TextAlign
74
+ gridAxesSize: { width: number, height: number }
75
+ fullDataFormatter: DataFormatterGrid,
76
+ fullChartParams: ChartParams
77
+ textReverseTransform: string,
78
+ }) {
79
+ const offsetX = fullParams.tickPadding - fullParams.labelOffset[0]
80
+ const offsetY = fullParams.tickPadding + fullParams.labelOffset[1]
81
+ let labelX = 0
82
+ let labelY = 0
83
+ if (fullDataFormatter.grid.groupAxis.position === 'bottom') {
84
+ // labelY = - gridAxesSize.height - offsetY
85
+ labelY = - offsetY
86
+ if (fullDataFormatter.grid.valueAxis.position === 'left') {
87
+ labelX = - offsetX
88
+ } else if (fullDataFormatter.grid.valueAxis.position === 'right') {
89
+ labelX = offsetX
90
+ }
91
+ } else if (fullDataFormatter.grid.groupAxis.position === 'top') {
92
+ // labelY = gridAxesSize.height + offsetY
93
+ labelY = offsetY
94
+ if (fullDataFormatter.grid.valueAxis.position === 'left') {
95
+ labelX = - offsetX
96
+ } else if (fullDataFormatter.grid.valueAxis.position === 'right') {
97
+ labelX = offsetX
98
+ }
99
+ } else if (fullDataFormatter.grid.groupAxis.position === 'left') {
100
+ // labelX = gridAxesSize.width + offsetX
101
+ labelX = offsetX
102
+ if (fullDataFormatter.grid.valueAxis.position === 'bottom') {
103
+ labelY = offsetY
104
+ } else if (fullDataFormatter.grid.valueAxis.position === 'top') {
105
+ labelY = - offsetY
106
+ }
107
+ } else if (fullDataFormatter.grid.groupAxis.position === 'right') {
108
+ labelX = - offsetX
109
+ if (fullDataFormatter.grid.valueAxis.position === 'bottom') {
110
+ labelY = offsetY
111
+ } else if (fullDataFormatter.grid.valueAxis.position === 'top') {
112
+ labelY = - offsetY
113
+ }
114
+ }
115
+
116
+ const axisLabelSelection = selection
117
+ .selectAll<SVGGElement, BaseValueAxisParams>(`g.${textClassName}`)
118
+ .data([fullParams])
119
+ .join('g')
120
+ .classed(textClassName, true)
121
+ .each((d, i, g) => {
122
+ const text = d3.select(g[i])
123
+ .selectAll<SVGTextElement, BaseValueAxisParams>(`text`)
124
+ .data([d])
125
+ .join(
126
+ enter => {
127
+ return enter
128
+ .append('text')
129
+ .style('font-weight', 'bold')
130
+ },
131
+ update => update,
132
+ exit => exit.remove()
133
+ )
134
+ .attr('text-anchor', axisLabelAlign.textAnchor)
135
+ .attr('dominant-baseline', axisLabelAlign.dominantBaseline)
136
+ .attr('font-size', fullChartParams.styles.textSize)
137
+ .style('fill', getColor(fullParams.labelColorType, fullChartParams))
138
+ .style('transform', textReverseTransform)
139
+ // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
140
+ .attr('x', labelX)
141
+ .attr('y', labelY)
142
+ .text(d => fullDataFormatter.grid.valueAxis.label)
143
+ })
144
+ .attr('transform', d => `translate(0, ${gridAxesSize.height})`)
145
+ // .attr('transform', d => `translate(${- fullParams.tickPadding + fullParams.labelOffset[0]}, ${gridAxesSize.height + fullParams.tickPadding + fullParams.labelOffset[1]})`)
146
+
147
+
148
+ }
149
+
150
+ function renderAxis ({ selection, yAxisClassName, fullParams, tickTextAlign, gridAxesSize, fullDataFormatter, fullChartParams, valueScale, textReverseTransformWithRotate, minAndMax }: {
151
+ selection: d3.Selection<SVGGElement, any, any, any>,
152
+ yAxisClassName: string
153
+ fullParams: BaseValueAxisParams
154
+ tickTextAlign: TextAlign
155
+ gridAxesSize: { width: number, height: number }
156
+ fullDataFormatter: DataFormatterGrid,
157
+ fullChartParams: ChartParams
158
+ valueScale: d3.ScaleLinear<number, number>
159
+ textReverseTransformWithRotate: string,
160
+ minAndMax: [number, number]
161
+ }) {
162
+
163
+ const yAxisSelection = selection
164
+ .selectAll<SVGGElement, BaseValueAxisParams>(`g.${yAxisClassName}`)
165
+ .data([fullParams])
166
+ .join('g')
167
+ .classed(yAxisClassName, true)
168
+
169
+ const valueLength = minAndMax[1] - minAndMax[0]
170
+
171
+ // const _valueScale = d3.scaleLinear()
172
+ // .domain([0, 150])
173
+ // .range([416.5, 791.349])
174
+
175
+ // 刻度文字偏移
176
+ let tickPadding = 0
177
+ let textY = 0
178
+ if (fullDataFormatter.grid.valueAxis.position === 'left') {
179
+ tickPadding = fullParams.tickPadding
180
+ textY = 0
181
+ } else if (fullDataFormatter.grid.valueAxis.position === 'right') {
182
+ tickPadding = - fullParams.tickPadding
183
+ textY = 0
184
+ } else if (fullDataFormatter.grid.valueAxis.position === 'bottom') {
185
+ tickPadding = 0
186
+ textY = fullParams.tickPadding
187
+ } else if (fullDataFormatter.grid.valueAxis.position === 'top') {
188
+ tickPadding = 0
189
+ textY = - fullParams.tickPadding
190
+ }
191
+
192
+ // 設定Y軸刻度
193
+ const yAxis = d3.axisLeft(valueScale)
194
+ .scale(valueScale)
195
+ .ticks(valueLength > fullParams.ticks
196
+ ? fullParams.ticks
197
+ : ((minAndMax[0] === 0 && minAndMax[1] === 0)
198
+ ? 1
199
+ : Math.ceil(valueLength))) // 刻度分段數量
200
+ .tickFormat(d => parseTickFormatValue(d, fullParams.tickFormat))
201
+ .tickSize(fullParams.tickFullLine == true
202
+ ? -gridAxesSize.width
203
+ : defaultTickSize)
204
+ .tickPadding(tickPadding)
205
+
206
+ const yAxisEl = yAxisSelection
207
+ .transition()
208
+ .duration(100)
209
+ .call(yAxis)
210
+
211
+ yAxisEl.selectAll('line')
212
+ .style('fill', 'none')
213
+ .style('stroke', fullParams.tickLineVisible == true ? getColor(fullParams.tickColorType, fullChartParams) : 'none')
214
+ .style('stroke-dasharray', fullParams.tickFullLineDasharray)
215
+ .attr('pointer-events', 'none')
216
+
217
+ yAxisEl.selectAll('path')
218
+ .style('fill', 'none')
219
+ // .style('stroke', this.fullParams.axisLineColor!)
220
+ .style('stroke', fullParams.axisLineVisible == true ? getColor(fullParams.axisLineColorType, fullChartParams) : 'none')
221
+ .style('shape-rendering', 'crispEdges')
222
+
223
+ // const yText = yAxisEl.selectAll('text')
224
+ const yText = yAxisSelection.selectAll('text')
225
+ // .style('font-family', 'sans-serif')
226
+ .attr('font-size', fullChartParams.styles.textSize)
227
+ .style('color', getColor(fullParams.tickTextColorType, fullChartParams))
228
+ .attr('text-anchor', tickTextAlign.textAnchor)
229
+ .attr('dominant-baseline', tickTextAlign.dominantBaseline)
230
+ // .attr('dy', 0)
231
+ .attr('y', textY)
232
+ yText.style('transform', textReverseTransformWithRotate)
233
+
234
+ // 抵消掉預設的偏移
235
+ if (fullDataFormatter.grid.valueAxis.position === 'bottom' || fullDataFormatter.grid.valueAxis.position === 'top') {
236
+ yText.attr('dy', 0)
237
+ }
238
+
239
+ return yAxisSelection
240
+ }
241
+
242
+
243
+
244
+ export const createBaseValueAxis: BasePluginFn<BaseLinesContext> = (pluginName: string, {
245
+ selection,
246
+ computedData$,
247
+ fullParams$,
248
+ fullDataFormatter$,
249
+ fullChartParams$,
250
+ gridAxesTransform$,
251
+ gridAxesReverseTransform$,
252
+ gridAxesSize$,
253
+ gridContainerPosition$,
254
+ isSeriesSeprate$,
255
+ }) => {
256
+
257
+ const destroy$ = new Subject()
258
+
259
+ const containerClassName = getClassName(pluginName, 'container')
260
+ const yAxisGClassName = getClassName(pluginName, 'yAxisG')
261
+ const yAxisClassName = getClassName(pluginName, 'yAxis')
262
+ const textClassName = getClassName(pluginName, 'text')
263
+
264
+ const containerSelection$ = combineLatest({
265
+ computedData: computedData$.pipe(
266
+ distinctUntilChanged((a, b) => {
267
+ // 只有當series的數量改變時,才重新計算
268
+ return a.length === b.length
269
+ }),
270
+ ),
271
+ isSeriesSeprate: isSeriesSeprate$
272
+ }).pipe(
273
+ takeUntil(destroy$),
274
+ switchMap(async (d) => d),
275
+ map(data => {
276
+ return data.isSeriesSeprate
277
+ // series分開的時候顯示各別axis
278
+ ? data.computedData
279
+ // series合併的時候只顯示第一個axis
280
+ : [data.computedData[0]]
281
+ }),
282
+ map((computedData, i) => {
283
+ return selection
284
+ .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${containerClassName}`)
285
+ .data(computedData, d => d[0] ? d[0].seriesIndex : i)
286
+ .join('g')
287
+ .classed(containerClassName, true)
288
+ })
289
+ )
290
+
291
+ const axisSelection$ = containerSelection$.pipe(
292
+ takeUntil(destroy$),
293
+ map((containerSelection, i) => {
294
+ return containerSelection
295
+ .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${yAxisGClassName}`)
296
+ .data([yAxisGClassName])
297
+ .join('g')
298
+ .classed(yAxisGClassName, true)
299
+ })
300
+ )
301
+
302
+ combineLatest({
303
+ containerSelection: containerSelection$,
304
+ gridContainerPosition: gridContainerPosition$
305
+ }).pipe(
306
+ takeUntil(destroy$),
307
+ switchMap(async d => d)
308
+ ).subscribe(data => {
309
+ data.containerSelection
310
+ .attr('transform', (d, i) => {
311
+ const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
312
+ const translate = gridContainerPosition.translate
313
+ const scale = gridContainerPosition.scale
314
+ return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
315
+ })
316
+ // .attr('opacity', 0)
317
+ // .transition()
318
+ // .attr('opacity', 1)
319
+ })
320
+
321
+ combineLatest({
322
+ axisSelection: axisSelection$,
323
+ gridAxesTransform: gridAxesTransform$,
324
+ }).pipe(
325
+ takeUntil(destroy$),
326
+ switchMap(async d => d)
327
+ ).subscribe(data => {
328
+ data.axisSelection
329
+ .style('transform', data.gridAxesTransform.value)
330
+ // .attr('opacity', 0)
331
+ // .transition()
332
+ // .attr('opacity', 1)
333
+
334
+ })
335
+
336
+ // const gridAxesSize$ = gridAxisSizeObservable({
337
+ // fullDataFormatter$,
338
+ // layout$
339
+ // })
340
+
341
+ // const textReverseTransform$: Observable<string> = new Observable(subscriber => {
342
+ // combineLatest({
343
+ // fullParams: fullParams$,
344
+ // layout: layout$
345
+ // }).pipe(
346
+ // takeUntil(destroy$),
347
+ // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
348
+ // switchMap(async (d) => d),
349
+ // ).subscribe(data => {
350
+
351
+ // const transformData = Object.assign({}, data.layout.content.axesTransformData)
352
+
353
+ // const value = getAxesTransformValue({
354
+ // translate: [0, 0],
355
+ // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
356
+ // rotate: transformData.rotate * -1 + data.fullParams.tickTextRotate,
357
+ // rotateX: transformData.rotateX * -1,
358
+ // rotateY: transformData.rotateY * -1
359
+ // })
360
+
361
+ // subscriber.next(value)
362
+ // })
363
+ // })
364
+ // const reverseTransform$: Observable<TransformData> = gridAxesTransform$.pipe(
365
+ // takeUntil(destroy$),
366
+ // map(d => {
367
+ // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
368
+ // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
369
+ // const rotate = d.rotate * -1
370
+ // const rotateX = d.rotateX * -1
371
+ // const rotateY = d.rotateY * -1
372
+ // return {
373
+ // translate,
374
+ // scale,
375
+ // rotate,
376
+ // rotateX,
377
+ // rotateY,
378
+ // value: ''
379
+ // }
380
+ // }),
381
+ // )
382
+ const textReverseTransform$ = combineLatest({
383
+ gridAxesReverseTransform: gridAxesReverseTransform$,
384
+ gridContainerPosition: gridContainerPosition$
385
+ }).pipe(
386
+ takeUntil(destroy$),
387
+ switchMap(async (d) => d),
388
+ map(data => {
389
+ // const axisReverseTranslateValue = `translate(${data.gridAxesReverseTransform.translate[0]}px, ${data.gridAxesReverseTransform.translate[1]}px)`
390
+ const axesRotateXYReverseValue = `rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
391
+ const axesRotateReverseValue = `rotate(${data.gridAxesReverseTransform.rotate}deg)`
392
+ const containerScaleReverseValue = `scale(${1 / data.gridContainerPosition[0].scale[0]}, ${1 / data.gridContainerPosition[0].scale[1]})`
393
+ // 必須按照順序(先抵消外層rotate,再抵消最外層scale)
394
+ return `${axesRotateXYReverseValue} ${axesRotateReverseValue} ${containerScaleReverseValue}`
395
+ }),
396
+ distinctUntilChanged()
397
+ )
398
+
399
+ const textReverseTransformWithRotate$ = combineLatest({
400
+ textReverseTransform: textReverseTransform$,
401
+ fullParams: fullParams$,
402
+ }).pipe(
403
+ takeUntil(destroy$),
404
+ switchMap(async (d) => d),
405
+ map(data => {
406
+ // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
407
+ return `${data.textReverseTransform} rotate(${data.fullParams.tickTextRotate}deg)`
408
+ })
409
+ )
410
+
411
+ const minAndMax$: Observable<[number, number]> = new Observable(subscriber => {
412
+ combineLatest({
413
+ fullDataFormatter: fullDataFormatter$,
414
+ gridAxesSize: gridAxesSize$,
415
+ computedData: computedData$
416
+ }).pipe(
417
+ takeUntil(destroy$),
418
+ switchMap(async (d) => d),
419
+ ).subscribe(data => {
420
+ const groupMin = 0
421
+ const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
422
+ // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
423
+ // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
424
+ // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
425
+ const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] - data.fullDataFormatter.grid.groupAxis.scalePadding
426
+ const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'max'
427
+ ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
428
+ : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
429
+
430
+ const filteredData = data.computedData.map((d, i) => {
431
+ return d.filter((_d, _i) => {
432
+ return _i >= groupScaleDomainMin && _i <= groupScaleDomainMax
433
+ })
434
+ })
435
+
436
+ const filteredMinAndMax = getMinAndMaxValue(filteredData.flat())
437
+ if (filteredMinAndMax[0] === filteredMinAndMax[1]) {
438
+ filteredMinAndMax[0] = filteredMinAndMax[1] - 1 // 避免最大及最小值相同造成無法計算scale
439
+ }
440
+ subscriber.next(filteredMinAndMax)
441
+ })
442
+ })
443
+
444
+ const valueScale$: Observable<d3.ScaleLinear<number, number>> = new Observable(subscriber => {
445
+ combineLatest({
446
+ fullDataFormatter: fullDataFormatter$,
447
+ gridAxesSize: gridAxesSize$,
448
+ minAndMax: minAndMax$
449
+ }).pipe(
450
+ takeUntil(destroy$),
451
+ switchMap(async (d) => d),
452
+ ).subscribe(data => {
453
+
454
+ const valueScale: d3.ScaleLinear<number, number> = createAxisLinearScale({
455
+ maxValue: data.minAndMax[1],
456
+ minValue: data.minAndMax[0],
457
+ axisWidth: data.gridAxesSize.height,
458
+ scaleDomain: data.fullDataFormatter.grid.valueAxis.scaleDomain,
459
+ scaleRange: data.fullDataFormatter.grid.valueAxis.scaleRange
460
+ })
461
+
462
+ subscriber.next(valueScale)
463
+ })
464
+ })
465
+
466
+ const tickTextAlign$: Observable<TextAlign> = combineLatest({
467
+ fullDataFormatter: fullDataFormatter$,
468
+ fullParams: fullParams$
469
+ }).pipe(
470
+ takeUntil(destroy$),
471
+ switchMap(async (d) => d),
472
+ map(data => {
473
+ let textAnchor: 'start' | 'middle' | 'end' = 'start'
474
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
475
+
476
+ if (data.fullDataFormatter.grid.valueAxis.position === 'left') {
477
+ textAnchor = 'end'
478
+ dominantBaseline = 'middle'
479
+ } else if (data.fullDataFormatter.grid.valueAxis.position === 'right') {
480
+ textAnchor = 'start'
481
+ dominantBaseline = 'middle'
482
+ } else if (data.fullDataFormatter.grid.valueAxis.position === 'bottom') {
483
+ textAnchor = data.fullParams.tickTextRotate
484
+ ? 'end'
485
+ : 'middle'
486
+ dominantBaseline = 'hanging'
487
+ } else if (data.fullDataFormatter.grid.valueAxis.position === 'top') {
488
+ textAnchor = data.fullParams.tickTextRotate
489
+ ? 'start'
490
+ : 'middle'
491
+ dominantBaseline = 'auto'
492
+ }
493
+ return {
494
+ textAnchor,
495
+ dominantBaseline
496
+ }
497
+ })
498
+ )
499
+
500
+ const axisLabelAlign$: Observable<TextAlign> = fullDataFormatter$.pipe(
501
+ takeUntil(destroy$),
502
+ map(d => {
503
+ let textAnchor: 'start' | 'middle' | 'end' = 'start'
504
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
505
+
506
+ if (d.grid.groupAxis.position === 'bottom') {
507
+ dominantBaseline = 'auto'
508
+ } else if (d.grid.groupAxis.position === 'top') {
509
+ dominantBaseline = 'hanging'
510
+ } else if (d.grid.groupAxis.position === 'left') {
511
+ textAnchor = 'start'
512
+ } else if (d.grid.groupAxis.position === 'right') {
513
+ textAnchor = 'end'
514
+ }
515
+ if (d.grid.valueAxis.position === 'left') {
516
+ textAnchor = 'end'
517
+ } else if (d.grid.valueAxis.position === 'right') {
518
+ textAnchor = 'start'
519
+ } else if (d.grid.valueAxis.position === 'bottom') {
520
+ dominantBaseline = 'hanging'
521
+ } else if (d.grid.valueAxis.position === 'top') {
522
+ dominantBaseline = 'auto'
523
+ }
524
+ return {
525
+ textAnchor,
526
+ dominantBaseline
527
+ }
528
+ })
529
+ )
530
+
531
+
532
+ combineLatest({
533
+ axisSelection: axisSelection$,
534
+ fullParams: fullParams$,
535
+ tickTextAlign: tickTextAlign$,
536
+ axisLabelAlign: axisLabelAlign$,
537
+ computedData: computedData$,
538
+ gridAxesSize: gridAxesSize$,
539
+ fullDataFormatter: fullDataFormatter$,
540
+ fullChartParams: fullChartParams$,
541
+ valueScale: valueScale$,
542
+ textReverseTransform: textReverseTransform$,
543
+ textReverseTransformWithRotate: textReverseTransformWithRotate$,
544
+ minAndMax: minAndMax$
545
+ }).pipe(
546
+ takeUntil(destroy$),
547
+ switchMap(async (d) => d),
548
+ ).subscribe(data => {
549
+
550
+ renderAxis({
551
+ selection: data.axisSelection,
552
+ yAxisClassName,
553
+ fullParams: data.fullParams,
554
+ tickTextAlign: data.tickTextAlign,
555
+ gridAxesSize: data.gridAxesSize,
556
+ fullDataFormatter: data.fullDataFormatter,
557
+ fullChartParams: data.fullChartParams,
558
+ valueScale: data.valueScale,
559
+ textReverseTransformWithRotate: data.textReverseTransformWithRotate,
560
+ minAndMax: data.minAndMax
561
+ })
562
+
563
+ renderAxisLabel({
564
+ selection: data.axisSelection,
565
+ textClassName,
566
+ fullParams: data.fullParams,
567
+ axisLabelAlign: data.axisLabelAlign,
568
+ gridAxesSize: data.gridAxesSize,
569
+ fullDataFormatter: data.fullDataFormatter,
570
+ fullChartParams: data.fullChartParams,
571
+ textReverseTransform: data.textReverseTransform,
572
+ })
573
+ })
574
+
575
+ return () => {
576
+ destroy$.next(undefined)
577
+ }
578
+ }