@orbcharts/plugins-basic 3.0.0-alpha.44 → 3.0.0-alpha.45

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. package/LICENSE +200 -200
  2. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseBarStack.d.ts +6 -4
  3. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseBars.d.ts +6 -4
  4. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseBarsTriangle.d.ts +7 -4
  5. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseDots.d.ts +5 -3
  6. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseGroupAxis.d.ts +3 -3
  7. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseLineAreas.d.ts +6 -3
  8. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseLines.d.ts +6 -3
  9. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseValueAxis.d.ts +3 -3
  10. package/dist/{src → orbcharts-plugins-basic/src}/grid/gridObservables.d.ts +4 -4
  11. package/dist/orbcharts-plugins-basic/src/index.d.ts +5 -0
  12. package/dist/orbcharts-plugins-basic/src/multiGrid/multiGridObservables.d.ts +8 -0
  13. package/dist/orbcharts-plugins-basic/src/series/plugins/PieEventTexts.d.ts +3 -0
  14. package/dist/orbcharts-plugins-basic/src/series/seriesObservables.d.ts +21 -0
  15. package/dist/{src → orbcharts-plugins-basic/src}/series/seriesUtils.d.ts +3 -3
  16. package/dist/orbcharts-plugins-basic.es.js +7696 -7529
  17. package/dist/orbcharts-plugins-basic.umd.js +8 -8
  18. package/dist/src/index.d.ts +1 -5
  19. package/package.json +42 -42
  20. package/src/base/BaseBarStack.ts +778 -881
  21. package/src/base/BaseBars.ts +764 -750
  22. package/src/base/BaseBarsTriangle.ts +672 -659
  23. package/src/base/BaseDots.ts +502 -639
  24. package/src/base/BaseGroupAxis.ts +496 -496
  25. package/src/base/BaseLegend.ts +641 -641
  26. package/src/base/BaseLineAreas.ts +625 -621
  27. package/src/base/BaseLines.ts +699 -692
  28. package/src/base/BaseValueAxis.ts +478 -479
  29. package/src/base/types.ts +2 -2
  30. package/src/grid/defaults.ts +121 -121
  31. package/src/grid/gridObservables.ts +247 -263
  32. package/src/grid/index.ts +15 -15
  33. package/src/grid/plugins/BarStack.ts +50 -37
  34. package/src/grid/plugins/Bars.ts +51 -37
  35. package/src/grid/plugins/BarsDiverging.ts +41 -39
  36. package/src/grid/plugins/BarsTriangle.ts +50 -34
  37. package/src/grid/plugins/Dots.ts +37 -35
  38. package/src/grid/plugins/GridLegend.ts +59 -59
  39. package/src/grid/plugins/GroupAux.ts +645 -646
  40. package/src/grid/plugins/GroupAxis.ts +42 -30
  41. package/src/grid/plugins/LineAreas.ts +39 -36
  42. package/src/grid/plugins/Lines.ts +38 -35
  43. package/src/grid/plugins/ScalingArea.ts +173 -174
  44. package/src/grid/plugins/ValueAxis.ts +43 -31
  45. package/src/grid/plugins/ValueStackAxis.ts +79 -70
  46. package/src/grid/types.ts +120 -120
  47. package/src/index.ts +9 -9
  48. package/src/multiGrid/defaults.ts +147 -147
  49. package/src/multiGrid/index.ts +11 -11
  50. package/src/multiGrid/multiGridObservables.ts +42 -289
  51. package/src/multiGrid/plugins/MultiBarStack.ts +74 -60
  52. package/src/multiGrid/plugins/MultiBars.ts +73 -59
  53. package/src/multiGrid/plugins/MultiBarsTriangle.ts +73 -58
  54. package/src/multiGrid/plugins/MultiDots.ts +60 -58
  55. package/src/multiGrid/plugins/MultiGridLegend.ts +89 -89
  56. package/src/multiGrid/plugins/MultiGroupAxis.ts +65 -53
  57. package/src/multiGrid/plugins/MultiLineAreas.ts +62 -59
  58. package/src/multiGrid/plugins/MultiLines.ts +61 -58
  59. package/src/multiGrid/plugins/MultiValueAxis.ts +65 -53
  60. package/src/multiGrid/plugins/OverlappingValueAxes.ts +169 -164
  61. package/src/multiGrid/types.ts +67 -67
  62. package/src/noneData/defaults.ts +64 -61
  63. package/src/noneData/index.ts +3 -3
  64. package/src/noneData/plugins/Container.ts +10 -10
  65. package/src/noneData/plugins/Tooltip.ts +310 -310
  66. package/src/noneData/types.ts +26 -26
  67. package/src/series/defaults.ts +109 -99
  68. package/src/series/index.ts +6 -6
  69. package/src/series/plugins/Bubbles.ts +571 -551
  70. package/src/series/plugins/Pie.ts +548 -600
  71. package/src/series/plugins/PieEventTexts.ts +258 -194
  72. package/src/series/plugins/PieLabels.ts +335 -288
  73. package/src/series/plugins/SeriesLegend.ts +59 -59
  74. package/src/series/seriesObservables.ts +145 -0
  75. package/src/series/seriesUtils.ts +50 -50
  76. package/src/series/types.ts +67 -67
  77. package/src/tree/defaults.ts +22 -22
  78. package/src/tree/index.ts +3 -3
  79. package/src/tree/plugins/TreeLegend.ts +59 -59
  80. package/src/tree/plugins/TreeMap.ts +305 -305
  81. package/src/tree/types.ts +23 -23
  82. package/src/utils/commonUtils.ts +21 -21
  83. package/src/utils/d3Graphics.ts +124 -124
  84. package/src/utils/d3Utils.ts +73 -73
  85. package/src/utils/observables.ts +14 -14
  86. package/src/utils/orbchartsUtils.ts +100 -100
  87. package/tsconfig.dev.json +16 -16
  88. package/tsconfig.json +16 -13
  89. package/tsconfig.prod.json +13 -13
  90. package/vite.config.js +49 -49
  91. package/dist/src/multiGrid/multiGridObservables.d.ts +0 -12
  92. package/dist/src/series/plugins/PieEventTexts.d.ts +0 -1
  93. /package/dist/{src → orbcharts-plugins-basic/src}/base/BaseGroupArea.d.ts +0 -0
  94. /package/dist/{src → orbcharts-plugins-basic/src}/base/BaseLegend.d.ts +0 -0
  95. /package/dist/{src → orbcharts-plugins-basic/src}/base/types.d.ts +0 -0
  96. /package/dist/{src → orbcharts-plugins-basic/src}/grid/defaults.d.ts +0 -0
  97. /package/dist/{src → orbcharts-plugins-basic/src}/grid/index.d.ts +0 -0
  98. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/BarStack.d.ts +0 -0
  99. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/Bars.d.ts +0 -0
  100. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/BarsDiverging.d.ts +0 -0
  101. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/BarsTriangle.d.ts +0 -0
  102. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/Dots.d.ts +0 -0
  103. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/GridLegend.d.ts +0 -0
  104. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/GroupAux.d.ts +0 -0
  105. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/GroupAxis.d.ts +0 -0
  106. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/LineAreas.d.ts +0 -0
  107. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/Lines.d.ts +0 -0
  108. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/Ranking.d.ts +0 -0
  109. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/RankingAxis.d.ts +0 -0
  110. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/ScalingArea.d.ts +0 -0
  111. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/ValueAxis.d.ts +0 -0
  112. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/ValueStackAxis.d.ts +0 -0
  113. /package/dist/{src → orbcharts-plugins-basic/src}/grid/types.d.ts +0 -0
  114. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/defaults.d.ts +0 -0
  115. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/index.d.ts +0 -0
  116. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiBarStack.d.ts +0 -0
  117. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiBars.d.ts +0 -0
  118. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiBarsTriangle.d.ts +0 -0
  119. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiDots.d.ts +0 -0
  120. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiGridLegend.d.ts +0 -0
  121. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiGroupAxis.d.ts +0 -0
  122. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiLineAreas.d.ts +0 -0
  123. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiLines.d.ts +0 -0
  124. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiValueAxis.d.ts +0 -0
  125. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/OverlappingValueAxes.d.ts +0 -0
  126. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/types.d.ts +0 -0
  127. /package/dist/{src → orbcharts-plugins-basic/src}/multiValue/index.d.ts +0 -0
  128. /package/dist/{src → orbcharts-plugins-basic/src}/multiValue/plugins/Scatter.d.ts +0 -0
  129. /package/dist/{src → orbcharts-plugins-basic/src}/multiValue/plugins/ScatterAxes.d.ts +0 -0
  130. /package/dist/{src → orbcharts-plugins-basic/src}/noneData/defaults.d.ts +0 -0
  131. /package/dist/{src → orbcharts-plugins-basic/src}/noneData/index.d.ts +0 -0
  132. /package/dist/{src → orbcharts-plugins-basic/src}/noneData/plugins/Container.d.ts +0 -0
  133. /package/dist/{src → orbcharts-plugins-basic/src}/noneData/plugins/Tooltip.d.ts +0 -0
  134. /package/dist/{src → orbcharts-plugins-basic/src}/noneData/types.d.ts +0 -0
  135. /package/dist/{src → orbcharts-plugins-basic/src}/relationship/index.d.ts +0 -0
  136. /package/dist/{src → orbcharts-plugins-basic/src}/relationship/plugins/Relationship.d.ts +0 -0
  137. /package/dist/{src → orbcharts-plugins-basic/src}/series/defaults.d.ts +0 -0
  138. /package/dist/{src → orbcharts-plugins-basic/src}/series/index.d.ts +0 -0
  139. /package/dist/{src → orbcharts-plugins-basic/src}/series/plugins/Bubbles.d.ts +0 -0
  140. /package/dist/{src → orbcharts-plugins-basic/src}/series/plugins/Pie.d.ts +0 -0
  141. /package/dist/{src → orbcharts-plugins-basic/src}/series/plugins/PieLabels.d.ts +0 -0
  142. /package/dist/{src → orbcharts-plugins-basic/src}/series/plugins/SeriesLegend.d.ts +0 -0
  143. /package/dist/{src → orbcharts-plugins-basic/src}/series/plugins/Waffle.d.ts +0 -0
  144. /package/dist/{src → orbcharts-plugins-basic/src}/series/types.d.ts +0 -0
  145. /package/dist/{src → orbcharts-plugins-basic/src}/tree/defaults.d.ts +0 -0
  146. /package/dist/{src → orbcharts-plugins-basic/src}/tree/index.d.ts +0 -0
  147. /package/dist/{src → orbcharts-plugins-basic/src}/tree/plugins/TreeLegend.d.ts +0 -0
  148. /package/dist/{src → orbcharts-plugins-basic/src}/tree/plugins/TreeMap.d.ts +0 -0
  149. /package/dist/{src → orbcharts-plugins-basic/src}/tree/types.d.ts +0 -0
  150. /package/dist/{src → orbcharts-plugins-basic/src}/utils/commonUtils.d.ts +0 -0
  151. /package/dist/{src → orbcharts-plugins-basic/src}/utils/d3Graphics.d.ts +0 -0
  152. /package/dist/{src → orbcharts-plugins-basic/src}/utils/d3Utils.d.ts +0 -0
  153. /package/dist/{src → orbcharts-plugins-basic/src}/utils/observables.d.ts +0 -0
  154. /package/dist/{src → orbcharts-plugins-basic/src}/utils/orbchartsUtils.d.ts +0 -0
  155. /package/dist/{vite.config.d.ts → orbcharts-plugins-basic/vite.config.d.ts} +0 -0
@@ -1,497 +1,497 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- switchMap,
5
- distinctUntilChanged,
6
- of,
7
- first,
8
- map,
9
- takeUntil,
10
- Observable,
11
- Subject } from 'rxjs'
12
- import { createAxisPointScale } from '@orbcharts/core'
13
- import type { ColorType, ComputedDataGrid, ContainerPosition } from '@orbcharts/core'
14
- import type { BasePluginFn } from './types'
15
- import type {
16
- ComputedDatumGrid,
17
- DataFormatterGrid,
18
- ChartParams,
19
- TransformData } from '@orbcharts/core'
20
- import { parseTickFormatValue } from '../utils/d3Utils'
21
- import { getColor, getClassName } from '../utils/orbchartsUtils'
22
-
23
- export interface BaseGroupAxisParams {
24
- // xLabel: string
25
- // labelAnchor: 'start' | 'end'
26
- labelOffset: [number, number]
27
- labelColorType: ColorType
28
- axisLineVisible: boolean
29
- axisLineColorType: ColorType
30
- tickFormat: string | ((text: any) => string)
31
- tickLineVisible: boolean
32
- tickPadding: number
33
- tickFullLine: boolean
34
- tickFullLineDasharray: string
35
- tickColorType: ColorType
36
- // axisLineColor: string
37
- // axisLabelColor: string
38
- tickTextRotate: number
39
- tickTextColorType: ColorType
40
- }
41
-
42
- interface BaseGroupAxisContext {
43
- selection: d3.Selection<any, unknown, any, unknown>
44
- computedData$: Observable<ComputedDataGrid>
45
- fullParams$: Observable<BaseGroupAxisParams>
46
- fullDataFormatter$: Observable<DataFormatterGrid>
47
- fullChartParams$: Observable<ChartParams>
48
- gridAxesTransform$: Observable<TransformData>
49
- gridAxesReverseTransform$: Observable<TransformData>
50
- gridAxesSize$: Observable<{
51
- width: number;
52
- height: number;
53
- }>
54
- gridContainer$: Observable<ContainerPosition[]>
55
- isSeriesPositionSeprate$: Observable<boolean>
56
- }
57
-
58
- interface TextAlign {
59
- textAnchor: "start" | "middle" | "end"
60
- dominantBaseline: "middle" | "auto" | "hanging"
61
- }
62
-
63
- // const pluginName = 'GroupAxis'
64
- // const containerClassName = getClassName(pluginName, 'container')
65
- // const xAxisGClassName = getClassName(pluginName, 'xAxisG')
66
- // const xAxisClassName = getClassName(pluginName, 'xAxis')
67
- // const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
68
- const defaultTickSize = 6
69
-
70
- function renderPointAxis ({ selection, xAxisClassName, groupingLabelClassName, params, tickTextAlign, axisLabelAlign, gridAxesSize, fullDataFormatter, chartParams, groupScale, textTransform }: {
71
- selection: d3.Selection<SVGGElement, any, any, any>,
72
- xAxisClassName: string
73
- groupingLabelClassName: string
74
- params: BaseGroupAxisParams
75
- tickTextAlign: TextAlign
76
- axisLabelAlign: TextAlign
77
- gridAxesSize: { width: number, height: number }
78
- fullDataFormatter: DataFormatterGrid,
79
- chartParams: ChartParams
80
- groupScale: d3.ScalePoint<string>
81
- textTransform: string
82
- // tickTextFormatter: string | ((label: any) => string)
83
- }) {
84
-
85
- const xAxisSelection = selection
86
- .selectAll<SVGGElement, BaseGroupAxisParams>(`g.${xAxisClassName}`)
87
- .data([params])
88
- .join('g')
89
- .classed(xAxisClassName, true)
90
-
91
- const axisLabelSelection = selection
92
- .selectAll<SVGGElement, BaseGroupAxisParams>(`g.${groupingLabelClassName}`)
93
- .data([params])
94
- .join('g')
95
- .classed(groupingLabelClassName, true)
96
- .each((d, i, g) => {
97
- const text = d3.select(g[i])
98
- .selectAll<SVGTextElement, BaseGroupAxisParams>('text')
99
- .data([d])
100
- .join(
101
- enter => {
102
- return enter
103
- .append('text')
104
- .style('font-weight', 'bold')
105
- },
106
- update => update,
107
- exit => exit.remove()
108
- )
109
- .attr('text-anchor', axisLabelAlign.textAnchor)
110
- .attr('dominant-baseline', axisLabelAlign.dominantBaseline)
111
- .attr('font-size', chartParams.styles.textSize)
112
- .style('fill', getColor(params.labelColorType, chartParams))
113
- .style('transform', textTransform)
114
- .text(d => fullDataFormatter.grid.groupAxis.label)
115
- })
116
- .attr('transform', d => `translate(${gridAxesSize.width + d.tickPadding + params.labelOffset[0]}, ${- d.tickPadding - defaultTickSize - params.labelOffset[1]})`)
117
-
118
-
119
- // 設定X軸刻度
120
- // const xAxis = d3.axisBottom(groupScale)
121
- const xAxis = d3.axisTop(groupScale)
122
- .scale(groupScale)
123
- .tickSize(params.tickFullLine == true
124
- ? -gridAxesSize.height
125
- : defaultTickSize)
126
- .tickSizeOuter(0)
127
- .tickFormat(d => parseTickFormatValue(d, params.tickFormat))
128
- .tickPadding(params.tickPadding)
129
-
130
- const xAxisEl = xAxisSelection
131
- .transition()
132
- .duration(100)
133
- .call(xAxis)
134
- // .attr('text-anchor', () => params.tickTextRotate !== false ? 'end' : 'middle')
135
- // .attr('text-anchor', () => 'middle')
136
-
137
- xAxisEl.selectAll('line')
138
- .style('fill', 'none')
139
- .style('stroke', params.tickLineVisible == true ? getColor(params.tickColorType, chartParams) : 'none')
140
- .style('stroke-dasharray', params.tickFullLineDasharray)
141
- .attr('pointer-events', 'none')
142
-
143
- xAxisEl.selectAll('path')
144
- .style('fill', 'none')
145
- .style('stroke', params.axisLineVisible == true ? getColor(params.axisLineColorType, chartParams) : 'none')
146
- .style('shape-rendering', 'crispEdges')
147
-
148
- // const xText = xAxisEl.selectAll('text')
149
- // xAxisSelection.each((d, i, g) => {
150
- // d3.select(g[i])
151
- // .selectAll('text')
152
- // .data([d])
153
- // .join('text')
154
- // .style('font-family', 'sans-serif')
155
- // .style('font-size', `${chartParams.styles.textSize}px`)
156
- // // .style('font-weight', 'bold')
157
- // .style('color', getColor(params.tickTextColorType, chartParams))
158
- // .attr('text-anchor', tickTextAlign.textAnchor)
159
- // .attr('dominant-baseline', tickTextAlign.dominantBaseline)
160
- // .attr('transform-origin', `0 -${params.tickPadding + defaultTickSize}`)
161
- // .style('transform', textTransform)
162
- // })
163
- const xText = xAxisSelection.selectAll('text')
164
- // .style('font-family', 'sans-serif')
165
- .attr('font-size', chartParams.styles.textSize)
166
- // .style('font-weight', 'bold')
167
- .style('color', getColor(params.tickTextColorType, chartParams))
168
- .attr('text-anchor', tickTextAlign.textAnchor)
169
- .attr('dominant-baseline', tickTextAlign.dominantBaseline)
170
- .attr('transform-origin', `0 -${params.tickPadding + defaultTickSize}`)
171
- .style('transform', textTransform)
172
-
173
-
174
- return xAxisSelection
175
- }
176
-
177
-
178
- export const createBaseGroupAxis: BasePluginFn<BaseGroupAxisContext> = ((pluginName: string, {
179
- selection,
180
- computedData$,
181
- fullParams$,
182
- fullDataFormatter$,
183
- fullChartParams$,
184
- gridAxesTransform$,
185
- gridAxesReverseTransform$,
186
- gridAxesSize$,
187
- gridContainer$,
188
- isSeriesPositionSeprate$,
189
- }) => {
190
-
191
- const destroy$ = new Subject()
192
-
193
- const containerClassName = getClassName(pluginName, 'container')
194
- const xAxisGClassName = getClassName(pluginName, 'xAxisG')
195
- const xAxisClassName = getClassName(pluginName, 'xAxis')
196
- const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
197
-
198
- const containerSelection$ = combineLatest({
199
- computedData: computedData$.pipe(
200
- distinctUntilChanged((a, b) => {
201
- // 只有當series的數量改變時,才重新計算
202
- return a.length === b.length
203
- }),
204
- ),
205
- isSeriesPositionSeprate: isSeriesPositionSeprate$
206
- }).pipe(
207
- takeUntil(destroy$),
208
- switchMap(async (d) => d),
209
- map(data => {
210
- return data.isSeriesPositionSeprate
211
- // series分開的時候顯示各別axis
212
- ? data.computedData
213
- // series合併的時候只顯示第一個axis
214
- : [data.computedData[0]]
215
- }),
216
- map((computedData, i) => {
217
- return selection
218
- .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${containerClassName}`)
219
- .data(computedData, d => d[0] ? d[0].seriesIndex : i)
220
- .join('g')
221
- .classed(containerClassName, true)
222
- })
223
- )
224
-
225
- const axisSelection$ = containerSelection$.pipe(
226
- takeUntil(destroy$),
227
- map((containerSelection, i) => {
228
- return containerSelection
229
- .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${xAxisGClassName}`)
230
- .data([xAxisGClassName])
231
- .join('g')
232
- .classed(xAxisGClassName, true)
233
- })
234
- )
235
-
236
- combineLatest({
237
- containerSelection: containerSelection$,
238
- gridContainer: gridContainer$
239
- }).pipe(
240
- takeUntil(destroy$),
241
- switchMap(async d => d)
242
- ).subscribe(data => {
243
- data.containerSelection
244
- .attr('transform', (d, i) => {
245
- const gridContainer = data.gridContainer[i] ?? data.gridContainer[0]
246
- const translate = gridContainer.translate
247
- const scale = gridContainer.scale
248
- return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
249
- })
250
- // .attr('opacity', 0)
251
- // .transition()
252
- // .attr('opacity', 1)
253
- })
254
-
255
- combineLatest({
256
- axisSelection: axisSelection$,
257
- gridAxesTransform: gridAxesTransform$,
258
- }).pipe(
259
- takeUntil(destroy$),
260
- switchMap(async d => d)
261
- ).subscribe(data => {
262
- data.axisSelection
263
- .style('transform', data.gridAxesTransform.value)
264
- // .attr('opacity', 0)
265
- // .transition()
266
- // .attr('opacity', 1)
267
-
268
- })
269
-
270
-
271
- // const gridAxesSize$ = gridAxisSizeObservable({
272
- // fullDataFormatter$,
273
- // layout$
274
- // })
275
-
276
-
277
- // const tickTextFormatter$ = fullDataFormatter$
278
- // .pipe(
279
- // map(d => {
280
- // return d.grid.seriesDirection === 'row' ? d.grid.columnLabelFormat : d.grid.rowLabelFormat
281
- // })
282
- // )
283
-
284
- // const textTransform$: Observable<string> = new Observable(subscriber => {
285
- // combineLatest({
286
- // params: fullParams$,
287
- // gridAxesTransform: gridAxesTransform$
288
- // }).pipe(
289
- // takeUntil(destroy$),
290
- // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
291
- // switchMap(async (d) => d),
292
- // ).subscribe(data => {
293
-
294
- // const transformData = Object.assign({}, data.gridAxesTransform)
295
-
296
- // const value = getAxesTransformValue({
297
- // translate: [0, 0],
298
- // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
299
- // rotate: transformData.rotate * -1 + data.params.tickTextRotate,
300
- // rotateX: transformData.rotateX * -1,
301
- // rotateY: transformData.rotateY * -1
302
- // })
303
-
304
- // subscriber.next(value)
305
- // })
306
- // })
307
- // const reverseTransform$: Observable<TransformData> = gridAxesTransform$.pipe(
308
- // takeUntil(destroy$),
309
- // map(d => {
310
- // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
311
- // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
312
- // const rotate = d.rotate * -1
313
- // const rotateX = d.rotateX * -1
314
- // const rotateY = d.rotateY * -1
315
- // return {
316
- // translate,
317
- // scale,
318
- // rotate,
319
- // rotateX,
320
- // rotateY,
321
- // value: ''
322
- // }
323
- // }),
324
- // )
325
- const textTransform$ = combineLatest({
326
- fullParams: fullParams$,
327
- fullDataFormatter: fullDataFormatter$,
328
- gridAxesReverseTransform: gridAxesReverseTransform$,
329
- gridContainer: gridContainer$
330
- }).pipe(
331
- takeUntil(destroy$),
332
- switchMap(async (d) => d),
333
- map(data => {
334
- const axisReverseTranslateValue = `translate(${data.gridAxesReverseTransform.translate[0]}px, ${data.gridAxesReverseTransform.translate[1]}px)`
335
- const axisReverseRotateValue = `rotate(${data.gridAxesReverseTransform.rotate}deg) rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
336
- const containerScaleReverseScaleValue = `scale(${1 / data.gridContainer[0].scale[0]}, ${1 / data.gridContainer[0].scale[1]})`
337
- const tickTextRotateDeg = (data.fullDataFormatter.grid.groupAxis.position === 'left' && data.fullDataFormatter.grid.valueAxis.position === 'top')
338
- || (data.fullDataFormatter.grid.groupAxis.position === 'right' && data.fullDataFormatter.grid.valueAxis.position === 'bottom')
339
- ? data.fullParams.tickTextRotate + 180 // 修正文字倒轉
340
- : data.fullParams.tickTextRotate
341
-
342
- const textRotateValue = `rotate(${tickTextRotateDeg}deg)`
343
-
344
- // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
345
- return `${axisReverseTranslateValue} ${axisReverseRotateValue} ${containerScaleReverseScaleValue} ${textRotateValue}`
346
- }),
347
- distinctUntilChanged()
348
- )
349
-
350
- const groupScale$: Observable<d3.ScalePoint<string>> = new Observable(subscriber => {
351
- combineLatest({
352
- fullDataFormatter: fullDataFormatter$,
353
- gridAxesSize: gridAxesSize$,
354
- computedData: computedData$
355
- }).pipe(
356
- takeUntil(destroy$),
357
- switchMap(async (d) => d),
358
- ).subscribe(data => {
359
- const groupMin = 0
360
- const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
361
- const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
362
- ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
363
- : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
364
- const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'auto'
365
- ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
366
- : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
367
-
368
- const groupingLength = data.computedData[0]
369
- ? data.computedData[0].length
370
- : 0
371
-
372
- let _labels = (data.computedData[0] ?? []).map(d => d.groupLabel)
373
-
374
- const axisLabels = new Array(groupingLength).fill(0)
375
- .map((d, i) => {
376
- return _labels[i] != null
377
- ? _labels[i]
378
- : String(i) // 沒有label則用序列號填充
379
- })
380
- .filter((d, i) => {
381
- return i >= groupScaleDomainMin && i <= groupScaleDomainMax
382
- })
383
-
384
-
385
- const padding = data.fullDataFormatter.grid.groupAxis.scalePadding
386
-
387
- const groupScale = createAxisPointScale({
388
- axisLabels,
389
- axisWidth: data.gridAxesSize.width,
390
- padding
391
- })
392
-
393
- subscriber.next(groupScale)
394
- })
395
- })
396
-
397
- const tickTextAlign$: Observable<TextAlign> = combineLatest({
398
- fullDataFormatter: fullDataFormatter$,
399
- fullParams: fullParams$
400
- }).pipe(
401
- takeUntil(destroy$),
402
- switchMap(async (d) => d),
403
- map(data => {
404
- let textAnchor: 'start' | 'middle' | 'end' = 'middle'
405
- let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
406
-
407
- if (data.fullDataFormatter.grid.groupAxis.position === 'bottom') {
408
- textAnchor = data.fullParams.tickTextRotate
409
- ? 'end'
410
- : 'middle'
411
- dominantBaseline = 'hanging'
412
- } else if (data.fullDataFormatter.grid.groupAxis.position === 'top') {
413
- textAnchor = data.fullParams.tickTextRotate
414
- ? 'end'
415
- : 'middle'
416
- dominantBaseline = 'auto'
417
- } else if (data.fullDataFormatter.grid.groupAxis.position === 'left') {
418
- textAnchor = 'end'
419
- dominantBaseline = 'middle'
420
- } else if (data.fullDataFormatter.grid.groupAxis.position === 'right') {
421
- textAnchor = 'start'
422
- dominantBaseline = 'middle'
423
- }
424
- return {
425
- textAnchor,
426
- dominantBaseline
427
- }
428
- })
429
- )
430
-
431
- const axisLabelAlign$: Observable<TextAlign> = fullDataFormatter$.pipe(
432
- takeUntil(destroy$),
433
- map(d => {
434
- let textAnchor: 'start' | 'middle' | 'end' = 'start'
435
- let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
436
-
437
- if (d.grid.groupAxis.position === 'bottom') {
438
- dominantBaseline = 'hanging'
439
- } else if (d.grid.groupAxis.position === 'top') {
440
- dominantBaseline = 'auto'
441
- } else if (d.grid.groupAxis.position === 'left') {
442
- textAnchor = 'end'
443
- } else if (d.grid.groupAxis.position === 'right') {
444
- textAnchor = 'start'
445
- }
446
- if (d.grid.valueAxis.position === 'left') {
447
- textAnchor = 'start'
448
- } else if (d.grid.valueAxis.position === 'right') {
449
- textAnchor = 'end'
450
- } else if (d.grid.valueAxis.position === 'bottom') {
451
- dominantBaseline = 'auto'
452
- } else if (d.grid.valueAxis.position === 'top') {
453
- dominantBaseline = 'hanging'
454
- }
455
- return {
456
- textAnchor,
457
- dominantBaseline
458
- }
459
- })
460
- )
461
-
462
- combineLatest({
463
- axisSelection: axisSelection$,
464
- params: fullParams$,
465
- tickTextAlign: tickTextAlign$,
466
- axisLabelAlign: axisLabelAlign$,
467
- gridAxesSize: gridAxesSize$,
468
- fullDataFormatter: fullDataFormatter$,
469
- chartParams: fullChartParams$,
470
- groupScale: groupScale$,
471
- textTransform: textTransform$,
472
- // tickTextFormatter: tickTextFormatter$
473
- }).pipe(
474
- takeUntil(destroy$),
475
- switchMap(async (d) => d),
476
- ).subscribe(data => {
477
-
478
- renderPointAxis({
479
- selection: data.axisSelection,
480
- xAxisClassName,
481
- groupingLabelClassName,
482
- params: data.params,
483
- tickTextAlign: data.tickTextAlign,
484
- axisLabelAlign: data.axisLabelAlign,
485
- gridAxesSize: data.gridAxesSize,
486
- fullDataFormatter: data.fullDataFormatter,
487
- chartParams: data.chartParams,
488
- groupScale: data.groupScale,
489
- textTransform: data.textTransform,
490
- // tickTextFormatter: data.tickTextFormatter
491
- })
492
- })
493
-
494
- return () => {
495
- destroy$.next(undefined)
496
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ switchMap,
5
+ distinctUntilChanged,
6
+ of,
7
+ first,
8
+ map,
9
+ takeUntil,
10
+ Observable,
11
+ Subject } from 'rxjs'
12
+ import { createAxisPointScale } from '@orbcharts/core'
13
+ import type { ColorType, ComputedDataGrid, GridContainerPosition } from '@orbcharts/core'
14
+ import type { BasePluginFn } from './types'
15
+ import type {
16
+ ComputedDatumGrid,
17
+ DataFormatterGrid,
18
+ ChartParams,
19
+ TransformData } from '@orbcharts/core'
20
+ import { parseTickFormatValue } from '../utils/d3Utils'
21
+ import { getColor, getClassName } from '../utils/orbchartsUtils'
22
+
23
+ export interface BaseGroupAxisParams {
24
+ // xLabel: string
25
+ // labelAnchor: 'start' | 'end'
26
+ labelOffset: [number, number]
27
+ labelColorType: ColorType
28
+ axisLineVisible: boolean
29
+ axisLineColorType: ColorType
30
+ tickFormat: string | ((text: any) => string)
31
+ tickLineVisible: boolean
32
+ tickPadding: number
33
+ tickFullLine: boolean
34
+ tickFullLineDasharray: string
35
+ tickColorType: ColorType
36
+ // axisLineColor: string
37
+ // axisLabelColor: string
38
+ tickTextRotate: number
39
+ tickTextColorType: ColorType
40
+ }
41
+
42
+ interface BaseGroupAxisContext {
43
+ selection: d3.Selection<any, unknown, any, unknown>
44
+ computedData$: Observable<ComputedDataGrid>
45
+ fullParams$: Observable<BaseGroupAxisParams>
46
+ fullDataFormatter$: Observable<DataFormatterGrid>
47
+ fullChartParams$: Observable<ChartParams>
48
+ gridAxesTransform$: Observable<TransformData>
49
+ gridAxesReverseTransform$: Observable<TransformData>
50
+ gridAxesSize$: Observable<{
51
+ width: number;
52
+ height: number;
53
+ }>
54
+ gridContainerPosition$: Observable<GridContainerPosition[]>
55
+ isSeriesSeprate$: Observable<boolean>
56
+ }
57
+
58
+ interface TextAlign {
59
+ textAnchor: "start" | "middle" | "end"
60
+ dominantBaseline: "middle" | "auto" | "hanging"
61
+ }
62
+
63
+ // const pluginName = 'GroupAxis'
64
+ // const containerClassName = getClassName(pluginName, 'container')
65
+ // const xAxisGClassName = getClassName(pluginName, 'xAxisG')
66
+ // const xAxisClassName = getClassName(pluginName, 'xAxis')
67
+ // const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
68
+ const defaultTickSize = 6
69
+
70
+ function renderPointAxis ({ selection, xAxisClassName, groupingLabelClassName, params, tickTextAlign, axisLabelAlign, gridAxesSize, fullDataFormatter, chartParams, groupScale, textTransform }: {
71
+ selection: d3.Selection<SVGGElement, any, any, any>,
72
+ xAxisClassName: string
73
+ groupingLabelClassName: string
74
+ params: BaseGroupAxisParams
75
+ tickTextAlign: TextAlign
76
+ axisLabelAlign: TextAlign
77
+ gridAxesSize: { width: number, height: number }
78
+ fullDataFormatter: DataFormatterGrid,
79
+ chartParams: ChartParams
80
+ groupScale: d3.ScalePoint<string>
81
+ textTransform: string
82
+ // tickTextFormatter: string | ((label: any) => string)
83
+ }) {
84
+
85
+ const xAxisSelection = selection
86
+ .selectAll<SVGGElement, BaseGroupAxisParams>(`g.${xAxisClassName}`)
87
+ .data([params])
88
+ .join('g')
89
+ .classed(xAxisClassName, true)
90
+
91
+ const axisLabelSelection = selection
92
+ .selectAll<SVGGElement, BaseGroupAxisParams>(`g.${groupingLabelClassName}`)
93
+ .data([params])
94
+ .join('g')
95
+ .classed(groupingLabelClassName, true)
96
+ .each((d, i, g) => {
97
+ const text = d3.select(g[i])
98
+ .selectAll<SVGTextElement, BaseGroupAxisParams>('text')
99
+ .data([d])
100
+ .join(
101
+ enter => {
102
+ return enter
103
+ .append('text')
104
+ .style('font-weight', 'bold')
105
+ },
106
+ update => update,
107
+ exit => exit.remove()
108
+ )
109
+ .attr('text-anchor', axisLabelAlign.textAnchor)
110
+ .attr('dominant-baseline', axisLabelAlign.dominantBaseline)
111
+ .attr('font-size', chartParams.styles.textSize)
112
+ .style('fill', getColor(params.labelColorType, chartParams))
113
+ .style('transform', textTransform)
114
+ .text(d => fullDataFormatter.grid.groupAxis.label)
115
+ })
116
+ .attr('transform', d => `translate(${gridAxesSize.width + d.tickPadding + params.labelOffset[0]}, ${- d.tickPadding - defaultTickSize - params.labelOffset[1]})`)
117
+
118
+
119
+ // 設定X軸刻度
120
+ // const xAxis = d3.axisBottom(groupScale)
121
+ const xAxis = d3.axisTop(groupScale)
122
+ .scale(groupScale)
123
+ .tickSize(params.tickFullLine == true
124
+ ? -gridAxesSize.height
125
+ : defaultTickSize)
126
+ .tickSizeOuter(0)
127
+ .tickFormat(d => parseTickFormatValue(d, params.tickFormat))
128
+ .tickPadding(params.tickPadding)
129
+
130
+ const xAxisEl = xAxisSelection
131
+ .transition()
132
+ .duration(100)
133
+ .call(xAxis)
134
+ // .attr('text-anchor', () => params.tickTextRotate !== false ? 'end' : 'middle')
135
+ // .attr('text-anchor', () => 'middle')
136
+
137
+ xAxisEl.selectAll('line')
138
+ .style('fill', 'none')
139
+ .style('stroke', params.tickLineVisible == true ? getColor(params.tickColorType, chartParams) : 'none')
140
+ .style('stroke-dasharray', params.tickFullLineDasharray)
141
+ .attr('pointer-events', 'none')
142
+
143
+ xAxisEl.selectAll('path')
144
+ .style('fill', 'none')
145
+ .style('stroke', params.axisLineVisible == true ? getColor(params.axisLineColorType, chartParams) : 'none')
146
+ .style('shape-rendering', 'crispEdges')
147
+
148
+ // const xText = xAxisEl.selectAll('text')
149
+ // xAxisSelection.each((d, i, g) => {
150
+ // d3.select(g[i])
151
+ // .selectAll('text')
152
+ // .data([d])
153
+ // .join('text')
154
+ // .style('font-family', 'sans-serif')
155
+ // .style('font-size', `${chartParams.styles.textSize}px`)
156
+ // // .style('font-weight', 'bold')
157
+ // .style('color', getColor(params.tickTextColorType, chartParams))
158
+ // .attr('text-anchor', tickTextAlign.textAnchor)
159
+ // .attr('dominant-baseline', tickTextAlign.dominantBaseline)
160
+ // .attr('transform-origin', `0 -${params.tickPadding + defaultTickSize}`)
161
+ // .style('transform', textTransform)
162
+ // })
163
+ const xText = xAxisSelection.selectAll('text')
164
+ // .style('font-family', 'sans-serif')
165
+ .attr('font-size', chartParams.styles.textSize)
166
+ // .style('font-weight', 'bold')
167
+ .style('color', getColor(params.tickTextColorType, chartParams))
168
+ .attr('text-anchor', tickTextAlign.textAnchor)
169
+ .attr('dominant-baseline', tickTextAlign.dominantBaseline)
170
+ .attr('transform-origin', `0 -${params.tickPadding + defaultTickSize}`)
171
+ .style('transform', textTransform)
172
+
173
+
174
+ return xAxisSelection
175
+ }
176
+
177
+
178
+ export const createBaseGroupAxis: BasePluginFn<BaseGroupAxisContext> = ((pluginName: string, {
179
+ selection,
180
+ computedData$,
181
+ fullParams$,
182
+ fullDataFormatter$,
183
+ fullChartParams$,
184
+ gridAxesTransform$,
185
+ gridAxesReverseTransform$,
186
+ gridAxesSize$,
187
+ gridContainerPosition$,
188
+ isSeriesSeprate$,
189
+ }) => {
190
+
191
+ const destroy$ = new Subject()
192
+
193
+ const containerClassName = getClassName(pluginName, 'container')
194
+ const xAxisGClassName = getClassName(pluginName, 'xAxisG')
195
+ const xAxisClassName = getClassName(pluginName, 'xAxis')
196
+ const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
197
+
198
+ const containerSelection$ = combineLatest({
199
+ computedData: computedData$.pipe(
200
+ distinctUntilChanged((a, b) => {
201
+ // 只有當series的數量改變時,才重新計算
202
+ return a.length === b.length
203
+ }),
204
+ ),
205
+ isSeriesSeprate: isSeriesSeprate$
206
+ }).pipe(
207
+ takeUntil(destroy$),
208
+ switchMap(async (d) => d),
209
+ map(data => {
210
+ return data.isSeriesSeprate
211
+ // series分開的時候顯示各別axis
212
+ ? data.computedData
213
+ // series合併的時候只顯示第一個axis
214
+ : [data.computedData[0]]
215
+ }),
216
+ map((computedData, i) => {
217
+ return selection
218
+ .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${containerClassName}`)
219
+ .data(computedData, d => d[0] ? d[0].seriesIndex : i)
220
+ .join('g')
221
+ .classed(containerClassName, true)
222
+ })
223
+ )
224
+
225
+ const axisSelection$ = containerSelection$.pipe(
226
+ takeUntil(destroy$),
227
+ map((containerSelection, i) => {
228
+ return containerSelection
229
+ .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${xAxisGClassName}`)
230
+ .data([xAxisGClassName])
231
+ .join('g')
232
+ .classed(xAxisGClassName, true)
233
+ })
234
+ )
235
+
236
+ combineLatest({
237
+ containerSelection: containerSelection$,
238
+ gridContainerPosition: gridContainerPosition$
239
+ }).pipe(
240
+ takeUntil(destroy$),
241
+ switchMap(async d => d)
242
+ ).subscribe(data => {
243
+ data.containerSelection
244
+ .attr('transform', (d, i) => {
245
+ const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
246
+ const translate = gridContainerPosition.translate
247
+ const scale = gridContainerPosition.scale
248
+ return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
249
+ })
250
+ // .attr('opacity', 0)
251
+ // .transition()
252
+ // .attr('opacity', 1)
253
+ })
254
+
255
+ combineLatest({
256
+ axisSelection: axisSelection$,
257
+ gridAxesTransform: gridAxesTransform$,
258
+ }).pipe(
259
+ takeUntil(destroy$),
260
+ switchMap(async d => d)
261
+ ).subscribe(data => {
262
+ data.axisSelection
263
+ .style('transform', data.gridAxesTransform.value)
264
+ // .attr('opacity', 0)
265
+ // .transition()
266
+ // .attr('opacity', 1)
267
+
268
+ })
269
+
270
+
271
+ // const gridAxesSize$ = gridAxisSizeObservable({
272
+ // fullDataFormatter$,
273
+ // layout$
274
+ // })
275
+
276
+
277
+ // const tickTextFormatter$ = fullDataFormatter$
278
+ // .pipe(
279
+ // map(d => {
280
+ // return d.grid.seriesDirection === 'row' ? d.grid.columnLabelFormat : d.grid.rowLabelFormat
281
+ // })
282
+ // )
283
+
284
+ // const textTransform$: Observable<string> = new Observable(subscriber => {
285
+ // combineLatest({
286
+ // params: fullParams$,
287
+ // gridAxesTransform: gridAxesTransform$
288
+ // }).pipe(
289
+ // takeUntil(destroy$),
290
+ // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
291
+ // switchMap(async (d) => d),
292
+ // ).subscribe(data => {
293
+
294
+ // const transformData = Object.assign({}, data.gridAxesTransform)
295
+
296
+ // const value = getAxesTransformValue({
297
+ // translate: [0, 0],
298
+ // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
299
+ // rotate: transformData.rotate * -1 + data.params.tickTextRotate,
300
+ // rotateX: transformData.rotateX * -1,
301
+ // rotateY: transformData.rotateY * -1
302
+ // })
303
+
304
+ // subscriber.next(value)
305
+ // })
306
+ // })
307
+ // const reverseTransform$: Observable<TransformData> = gridAxesTransform$.pipe(
308
+ // takeUntil(destroy$),
309
+ // map(d => {
310
+ // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
311
+ // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
312
+ // const rotate = d.rotate * -1
313
+ // const rotateX = d.rotateX * -1
314
+ // const rotateY = d.rotateY * -1
315
+ // return {
316
+ // translate,
317
+ // scale,
318
+ // rotate,
319
+ // rotateX,
320
+ // rotateY,
321
+ // value: ''
322
+ // }
323
+ // }),
324
+ // )
325
+ const textTransform$ = combineLatest({
326
+ fullParams: fullParams$,
327
+ fullDataFormatter: fullDataFormatter$,
328
+ gridAxesReverseTransform: gridAxesReverseTransform$,
329
+ gridContainerPosition: gridContainerPosition$
330
+ }).pipe(
331
+ takeUntil(destroy$),
332
+ switchMap(async (d) => d),
333
+ map(data => {
334
+ const axisReverseTranslateValue = `translate(${data.gridAxesReverseTransform.translate[0]}px, ${data.gridAxesReverseTransform.translate[1]}px)`
335
+ const axisReverseRotateValue = `rotate(${data.gridAxesReverseTransform.rotate}deg) rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
336
+ const containerScaleReverseScaleValue = `scale(${1 / data.gridContainerPosition[0].scale[0]}, ${1 / data.gridContainerPosition[0].scale[1]})`
337
+ const tickTextRotateDeg = (data.fullDataFormatter.grid.groupAxis.position === 'left' && data.fullDataFormatter.grid.valueAxis.position === 'top')
338
+ || (data.fullDataFormatter.grid.groupAxis.position === 'right' && data.fullDataFormatter.grid.valueAxis.position === 'bottom')
339
+ ? data.fullParams.tickTextRotate + 180 // 修正文字倒轉
340
+ : data.fullParams.tickTextRotate
341
+
342
+ const textRotateValue = `rotate(${tickTextRotateDeg}deg)`
343
+
344
+ // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
345
+ return `${axisReverseTranslateValue} ${axisReverseRotateValue} ${containerScaleReverseScaleValue} ${textRotateValue}`
346
+ }),
347
+ distinctUntilChanged()
348
+ )
349
+
350
+ const groupScale$: Observable<d3.ScalePoint<string>> = new Observable(subscriber => {
351
+ combineLatest({
352
+ fullDataFormatter: fullDataFormatter$,
353
+ gridAxesSize: gridAxesSize$,
354
+ computedData: computedData$
355
+ }).pipe(
356
+ takeUntil(destroy$),
357
+ switchMap(async (d) => d),
358
+ ).subscribe(data => {
359
+ const groupMin = 0
360
+ const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
361
+ const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
362
+ ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
363
+ : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
364
+ const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'auto'
365
+ ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
366
+ : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
367
+
368
+ const groupingLength = data.computedData[0]
369
+ ? data.computedData[0].length
370
+ : 0
371
+
372
+ let _labels = (data.computedData[0] ?? []).map(d => d.groupLabel)
373
+
374
+ const axisLabels = new Array(groupingLength).fill(0)
375
+ .map((d, i) => {
376
+ return _labels[i] != null
377
+ ? _labels[i]
378
+ : String(i) // 沒有label則用序列號填充
379
+ })
380
+ .filter((d, i) => {
381
+ return i >= groupScaleDomainMin && i <= groupScaleDomainMax
382
+ })
383
+
384
+
385
+ const padding = data.fullDataFormatter.grid.groupAxis.scalePadding
386
+
387
+ const groupScale = createAxisPointScale({
388
+ axisLabels,
389
+ axisWidth: data.gridAxesSize.width,
390
+ padding
391
+ })
392
+
393
+ subscriber.next(groupScale)
394
+ })
395
+ })
396
+
397
+ const tickTextAlign$: Observable<TextAlign> = combineLatest({
398
+ fullDataFormatter: fullDataFormatter$,
399
+ fullParams: fullParams$
400
+ }).pipe(
401
+ takeUntil(destroy$),
402
+ switchMap(async (d) => d),
403
+ map(data => {
404
+ let textAnchor: 'start' | 'middle' | 'end' = 'middle'
405
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
406
+
407
+ if (data.fullDataFormatter.grid.groupAxis.position === 'bottom') {
408
+ textAnchor = data.fullParams.tickTextRotate
409
+ ? 'end'
410
+ : 'middle'
411
+ dominantBaseline = 'hanging'
412
+ } else if (data.fullDataFormatter.grid.groupAxis.position === 'top') {
413
+ textAnchor = data.fullParams.tickTextRotate
414
+ ? 'end'
415
+ : 'middle'
416
+ dominantBaseline = 'auto'
417
+ } else if (data.fullDataFormatter.grid.groupAxis.position === 'left') {
418
+ textAnchor = 'end'
419
+ dominantBaseline = 'middle'
420
+ } else if (data.fullDataFormatter.grid.groupAxis.position === 'right') {
421
+ textAnchor = 'start'
422
+ dominantBaseline = 'middle'
423
+ }
424
+ return {
425
+ textAnchor,
426
+ dominantBaseline
427
+ }
428
+ })
429
+ )
430
+
431
+ const axisLabelAlign$: Observable<TextAlign> = fullDataFormatter$.pipe(
432
+ takeUntil(destroy$),
433
+ map(d => {
434
+ let textAnchor: 'start' | 'middle' | 'end' = 'start'
435
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
436
+
437
+ if (d.grid.groupAxis.position === 'bottom') {
438
+ dominantBaseline = 'hanging'
439
+ } else if (d.grid.groupAxis.position === 'top') {
440
+ dominantBaseline = 'auto'
441
+ } else if (d.grid.groupAxis.position === 'left') {
442
+ textAnchor = 'end'
443
+ } else if (d.grid.groupAxis.position === 'right') {
444
+ textAnchor = 'start'
445
+ }
446
+ if (d.grid.valueAxis.position === 'left') {
447
+ textAnchor = 'start'
448
+ } else if (d.grid.valueAxis.position === 'right') {
449
+ textAnchor = 'end'
450
+ } else if (d.grid.valueAxis.position === 'bottom') {
451
+ dominantBaseline = 'auto'
452
+ } else if (d.grid.valueAxis.position === 'top') {
453
+ dominantBaseline = 'hanging'
454
+ }
455
+ return {
456
+ textAnchor,
457
+ dominantBaseline
458
+ }
459
+ })
460
+ )
461
+
462
+ combineLatest({
463
+ axisSelection: axisSelection$,
464
+ params: fullParams$,
465
+ tickTextAlign: tickTextAlign$,
466
+ axisLabelAlign: axisLabelAlign$,
467
+ gridAxesSize: gridAxesSize$,
468
+ fullDataFormatter: fullDataFormatter$,
469
+ chartParams: fullChartParams$,
470
+ groupScale: groupScale$,
471
+ textTransform: textTransform$,
472
+ // tickTextFormatter: tickTextFormatter$
473
+ }).pipe(
474
+ takeUntil(destroy$),
475
+ switchMap(async (d) => d),
476
+ ).subscribe(data => {
477
+
478
+ renderPointAxis({
479
+ selection: data.axisSelection,
480
+ xAxisClassName,
481
+ groupingLabelClassName,
482
+ params: data.params,
483
+ tickTextAlign: data.tickTextAlign,
484
+ axisLabelAlign: data.axisLabelAlign,
485
+ gridAxesSize: data.gridAxesSize,
486
+ fullDataFormatter: data.fullDataFormatter,
487
+ chartParams: data.chartParams,
488
+ groupScale: data.groupScale,
489
+ textTransform: data.textTransform,
490
+ // tickTextFormatter: data.tickTextFormatter
491
+ })
492
+ })
493
+
494
+ return () => {
495
+ destroy$.next(undefined)
496
+ }
497
497
  })