@orbcharts/plugins-basic 3.0.0-beta.7 → 3.0.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic/lib/plugins-basic-types.d.ts +1 -1
  3. package/dist/orbcharts-plugins-basic/src/base/{BaseBarStack.d.ts → BaseStackedBar.d.ts} +4 -4
  4. package/dist/orbcharts-plugins-basic/src/grid/defaults.d.ts +3 -3
  5. package/dist/orbcharts-plugins-basic/src/grid/index.d.ts +2 -2
  6. package/dist/orbcharts-plugins-basic/src/grid/plugins/Bars.d.ts +1 -1
  7. package/dist/orbcharts-plugins-basic/src/grid/plugins/BarsPN.d.ts +1 -1
  8. package/dist/orbcharts-plugins-basic/src/grid/plugins/BarsTriangle.d.ts +1 -1
  9. package/dist/orbcharts-plugins-basic/src/grid/plugins/Dots.d.ts +1 -1
  10. package/dist/orbcharts-plugins-basic/src/grid/plugins/GridLegend.d.ts +1 -1
  11. package/dist/orbcharts-plugins-basic/src/grid/plugins/GridTooltip.d.ts +1 -1
  12. package/dist/orbcharts-plugins-basic/src/grid/plugins/GridZoom.d.ts +1 -1
  13. package/dist/orbcharts-plugins-basic/src/grid/plugins/GroupAux.d.ts +1 -1
  14. package/dist/orbcharts-plugins-basic/src/grid/plugins/GroupAxis.d.ts +1 -1
  15. package/dist/orbcharts-plugins-basic/src/grid/plugins/LineAreas.d.ts +1 -1
  16. package/dist/orbcharts-plugins-basic/src/grid/plugins/Lines.d.ts +1 -1
  17. package/dist/orbcharts-plugins-basic/src/grid/plugins/StackedBar.d.ts +1 -0
  18. package/dist/orbcharts-plugins-basic/src/grid/plugins/StackedValueAxis.d.ts +1 -0
  19. package/dist/orbcharts-plugins-basic/src/grid/plugins/ValueAxis.d.ts +1 -1
  20. package/dist/orbcharts-plugins-basic/src/index.d.ts +1 -0
  21. package/dist/orbcharts-plugins-basic/src/multiGrid/defaults.d.ts +4 -4
  22. package/dist/orbcharts-plugins-basic/src/multiGrid/index.d.ts +3 -3
  23. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiBars.d.ts +1 -1
  24. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiBarsTriangle.d.ts +1 -1
  25. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiDots.d.ts +1 -1
  26. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiGridLegend.d.ts +1 -1
  27. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiGridTooltip.d.ts +1 -1
  28. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiGroupAxis.d.ts +1 -1
  29. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiLineAreas.d.ts +1 -1
  30. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiLines.d.ts +1 -1
  31. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiStackedBar.d.ts +1 -0
  32. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiStackedValueAxis.d.ts +1 -0
  33. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiValueAxis.d.ts +1 -1
  34. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/OverlappingStackedValueAxes.d.ts +1 -0
  35. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/OverlappingValueAxes.d.ts +1 -1
  36. package/dist/orbcharts-plugins-basic/src/multiValue/plugins/MultiValueLegend.d.ts +1 -1
  37. package/dist/orbcharts-plugins-basic/src/multiValue/plugins/MultiValueTooltip.d.ts +1 -1
  38. package/dist/orbcharts-plugins-basic/src/multiValue/plugins/Scatter.d.ts +1 -1
  39. package/dist/orbcharts-plugins-basic/src/multiValue/plugins/ScatterBubbles.d.ts +1 -1
  40. package/dist/orbcharts-plugins-basic/src/multiValue/plugins/XYAux.d.ts +1 -1
  41. package/dist/orbcharts-plugins-basic/src/multiValue/plugins/XYAxes.d.ts +1 -1
  42. package/dist/orbcharts-plugins-basic/src/multiValue/plugins/XYZoom.d.ts +1 -1
  43. package/dist/orbcharts-plugins-basic/src/noneData/plugins/Container.d.ts +0 -1
  44. package/dist/orbcharts-plugins-basic/src/noneData/plugins/Tooltip.d.ts +0 -3
  45. package/dist/orbcharts-plugins-basic/src/relationship/defaults.d.ts +6 -0
  46. package/dist/orbcharts-plugins-basic/src/relationship/index.d.ts +5 -0
  47. package/dist/orbcharts-plugins-basic/src/relationship/plugins/ForceDirected.d.ts +3 -0
  48. package/dist/orbcharts-plugins-basic/src/relationship/plugins/ForceDirectedBubbles.d.ts +3 -0
  49. package/dist/orbcharts-plugins-basic/src/relationship/plugins/RelationshipLegend.d.ts +1 -0
  50. package/dist/orbcharts-plugins-basic/src/relationship/plugins/RelationshipTooltip.d.ts +1 -0
  51. package/dist/orbcharts-plugins-basic/src/relationship/relationshipObservables.d.ts +1 -0
  52. package/dist/orbcharts-plugins-basic/src/series/plugins/Bubbles.d.ts +1 -1
  53. package/dist/orbcharts-plugins-basic/src/series/plugins/Pie.d.ts +1 -1
  54. package/dist/orbcharts-plugins-basic/src/series/plugins/PieEventTexts.d.ts +1 -1
  55. package/dist/orbcharts-plugins-basic/src/series/plugins/PieLabels.d.ts +1 -1
  56. package/dist/orbcharts-plugins-basic/src/series/plugins/Rose.d.ts +1 -1
  57. package/dist/orbcharts-plugins-basic/src/series/plugins/RoseLabels.d.ts +1 -1
  58. package/dist/orbcharts-plugins-basic/src/series/plugins/SeriesLegend.d.ts +1 -1
  59. package/dist/orbcharts-plugins-basic/src/series/plugins/SeriesTooltip.d.ts +1 -1
  60. package/dist/orbcharts-plugins-basic/src/tree/plugins/TreeLegend.d.ts +1 -1
  61. package/dist/orbcharts-plugins-basic/src/tree/plugins/TreeMap.d.ts +1 -1
  62. package/dist/orbcharts-plugins-basic/src/tree/plugins/TreeTooltip.d.ts +1 -1
  63. package/dist/orbcharts-plugins-basic/src/utils/commonUtils.d.ts +1 -1
  64. package/dist/orbcharts-plugins-basic/src/utils/orbchartsUtils.d.ts +1 -1
  65. package/dist/orbcharts-plugins-basic.es.js +9421 -8102
  66. package/dist/orbcharts-plugins-basic.umd.js +69 -51
  67. package/lib/core-types.ts +7 -7
  68. package/lib/core.ts +6 -6
  69. package/lib/plugins-basic-types.ts +6 -6
  70. package/package.json +44 -44
  71. package/src/base/BaseBars.ts +765 -765
  72. package/src/base/BaseBarsTriangle.ts +676 -676
  73. package/src/base/BaseDots.ts +464 -464
  74. package/src/base/BaseGroupAxis.ts +679 -679
  75. package/src/base/BaseLegend.ts +684 -684
  76. package/src/base/BaseLineAreas.ts +629 -629
  77. package/src/base/BaseLines.ts +706 -706
  78. package/src/base/{BaseBarStack.ts → BaseStackedBar.ts} +782 -782
  79. package/src/base/BaseTooltip.ts +385 -385
  80. package/src/base/BaseValueAxis.ts +583 -583
  81. package/src/base/types.ts +2 -2
  82. package/src/const.ts +30 -30
  83. package/src/grid/defaults.ts +246 -246
  84. package/src/grid/gridObservables.ts +554 -554
  85. package/src/grid/index.ts +16 -16
  86. package/src/grid/plugins/Bars.ts +69 -69
  87. package/src/grid/plugins/BarsPN.ts +66 -66
  88. package/src/grid/plugins/BarsTriangle.ts +73 -73
  89. package/src/grid/plugins/Dots.ts +68 -68
  90. package/src/grid/plugins/GridLegend.ts +107 -107
  91. package/src/grid/plugins/GridTooltip.ts +66 -66
  92. package/src/grid/plugins/GridZoom.ts +218 -218
  93. package/src/grid/plugins/GroupAux.ts +1103 -1103
  94. package/src/grid/plugins/GroupAxis.ts +97 -97
  95. package/src/grid/plugins/LineAreas.ts +65 -65
  96. package/src/grid/plugins/Lines.ts +59 -59
  97. package/src/grid/plugins/{BarStack.ts → StackedBar.ts} +64 -64
  98. package/src/grid/plugins/{ValueStackAxis.ts → StackedValueAxis.ts} +96 -96
  99. package/src/grid/plugins/ValueAxis.ts +94 -94
  100. package/src/index.ts +6 -10
  101. package/src/multiGrid/defaults.ts +224 -224
  102. package/src/multiGrid/index.ts +15 -15
  103. package/src/multiGrid/multiGridObservables.ts +49 -49
  104. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  105. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  106. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  107. package/src/multiGrid/plugins/MultiGridLegend.ts +159 -159
  108. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  109. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  110. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  111. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  112. package/src/multiGrid/plugins/{MultiBarStack.ts → MultiStackedBar.ts} +106 -106
  113. package/src/multiGrid/plugins/{MultiValueStackAxis.ts → MultiStackedValueAxis.ts} +134 -134
  114. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  115. package/src/multiGrid/plugins/{OverlappingValueStackAxes.ts → OverlappingStackedValueAxes.ts} +299 -299
  116. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  117. package/src/multiValue/defaults.ts +166 -166
  118. package/src/multiValue/index.ts +8 -8
  119. package/src/multiValue/multiValueObservables.ts +297 -297
  120. package/src/multiValue/plugins/MultiValueLegend.ts +107 -107
  121. package/src/multiValue/plugins/MultiValueTooltip.ts +66 -66
  122. package/src/multiValue/plugins/Scatter.ts +426 -426
  123. package/src/multiValue/plugins/ScatterBubbles.ts +554 -554
  124. package/src/multiValue/plugins/XYAux.ts +681 -681
  125. package/src/multiValue/plugins/XYAxes.ts +684 -684
  126. package/src/multiValue/plugins/XYZoom.ts +299 -299
  127. package/src/noneData/defaults.ts +102 -102
  128. package/src/noneData/index.ts +3 -3
  129. package/src/noneData/plugins/Container.ts +28 -28
  130. package/src/noneData/plugins/Tooltip.ts +374 -374
  131. package/src/relationship/defaults.ts +196 -0
  132. package/src/relationship/index.ts +5 -0
  133. package/src/relationship/plugins/ForceDirected.ts +1167 -0
  134. package/src/relationship/plugins/ForceDirectedBubbles.ts +1391 -0
  135. package/src/relationship/plugins/RelationshipLegend.ts +100 -0
  136. package/src/relationship/plugins/RelationshipTooltip.ts +66 -0
  137. package/src/relationship/relationshipObservables.ts +50 -0
  138. package/src/series/defaults.ts +206 -206
  139. package/src/series/index.ts +9 -9
  140. package/src/series/plugins/Bubbles.ts +606 -603
  141. package/src/series/plugins/Pie.ts +623 -623
  142. package/src/series/plugins/PieEventTexts.ts +284 -283
  143. package/src/series/plugins/PieLabels.ts +640 -640
  144. package/src/series/plugins/Rose.ts +516 -516
  145. package/src/series/plugins/RoseLabels.ts +600 -600
  146. package/src/series/plugins/SeriesLegend.ts +107 -107
  147. package/src/series/plugins/SeriesTooltip.ts +66 -66
  148. package/src/series/seriesObservables.ts +145 -145
  149. package/src/series/seriesUtils.ts +51 -51
  150. package/src/tree/defaults.ts +78 -78
  151. package/src/tree/index.ts +4 -4
  152. package/src/tree/plugins/TreeLegend.ts +100 -100
  153. package/src/tree/plugins/TreeMap.ts +333 -333
  154. package/src/tree/plugins/TreeTooltip.ts +66 -66
  155. package/src/utils/commonUtils.ts +21 -21
  156. package/src/utils/d3Graphics.ts +174 -174
  157. package/src/utils/d3Utils.ts +74 -74
  158. package/src/utils/observables.ts +14 -14
  159. package/src/utils/orbchartsUtils.ts +115 -115
  160. package/tsconfig.base.json +13 -13
  161. package/tsconfig.json +2 -2
  162. package/vite.config.js +22 -22
  163. package/dist/orbcharts-plugins-basic/src/grid/plugins/BarStack.d.ts +0 -1
  164. package/dist/orbcharts-plugins-basic/src/grid/plugins/ValueStackAxis.d.ts +0 -1
  165. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiBarStack.d.ts +0 -1
  166. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/MultiValueStackAxis.d.ts +0 -1
  167. package/dist/orbcharts-plugins-basic/src/multiGrid/plugins/OverlappingValueStackAxes.d.ts +0 -1
  168. package/dist/orbcharts-plugins-basic/src/relationship/plugins/Relationship.d.ts +0 -0
  169. package/src/relationship/plugins/Relationship.ts +0 -0
@@ -1,685 +1,685 @@
1
- import * as d3 from 'd3'
2
- import {
3
- Observable,
4
- Subject,
5
- combineLatest,
6
- takeUntil,
7
- map,
8
- distinctUntilChanged,
9
- switchMap,
10
- shareReplay
11
- } from 'rxjs'
12
- import type {
13
- ColorType,
14
- ChartParams,
15
- ComputedDatumMultiValue,
16
- DataFormatterMultiValue,
17
- DefinePluginConfig,
18
- } from '../../../lib/core-types'
19
- import {
20
- defineMultiValuePlugin,
21
- createValueToAxisScale,
22
- getMinAndMax
23
- } from '../../../lib/core'
24
- import type { XYAxesParams
25
- } from '../../../lib/plugins-basic-types'
26
- import { DEFAULT_X_Y_AXES_PARAMS } from '../defaults'
27
- import { LAYER_INDEX_OF_AXIS } from '../../const'
28
- import { getColor, getDatumColor, getClassName, getUniID } from '../../utils/orbchartsUtils'
29
- import { parseTickFormatValue } from '../../utils/d3Utils'
30
- import { filteredMinMaxXYDataObservable } from '../../../../orbcharts-core/src/utils/multiValueObservables'
31
- // import { multiValueSelectionsObservable } from '../multiValueObservables'
32
-
33
- interface TextAlign {
34
- textAnchor: "start" | "middle" | "end"
35
- dominantBaseline: "middle" | "auto" | "hanging"
36
- }
37
-
38
- // interface Axis {
39
- // labelOffset: [number, number]
40
- // labelColorType: ColorType
41
- // axisLineVisible: boolean
42
- // axisLineColorType: ColorType
43
- // ticks: number | null
44
- // tickFormat: string | ((text: d3.NumberValue) => string)
45
- // tickLineVisible: boolean
46
- // tickPadding: number
47
- // tickFullLine: boolean
48
- // tickFullLineDasharray: string
49
- // tickColorType: ColorType
50
- // tickTextColorType: ColorType
51
- // }
52
-
53
- const pluginName = 'XYAxes'
54
-
55
- const defaultTickSize = 6
56
-
57
- const xTickTextAnchor = 'middle'
58
- const xTickDominantBaseline = 'hanging'
59
- const xAxisLabelAnchor = 'start'
60
- const xAxisLabelDominantBaseline = 'hanging'
61
- const yTickTextAnchor = 'end'
62
- const yTickDominantBaseline = 'middle'
63
- const yAxisLabelAnchor = 'end'
64
- const yAxisLabelDominantBaseline = 'auto'
65
-
66
- const pluginConfig: DefinePluginConfig<typeof pluginName, typeof DEFAULT_X_Y_AXES_PARAMS> = {
67
- name: pluginName,
68
- defaultParams: DEFAULT_X_Y_AXES_PARAMS,
69
- layerIndex: LAYER_INDEX_OF_AXIS,
70
- validator: (params, { validateColumns }) => {
71
- const result = validateColumns(params, {
72
- xAxis: {
73
- toBeTypes: ['object']
74
- },
75
- yAxis: {
76
- toBeTypes: ['object']
77
- }
78
- })
79
- if (params.xAxis) {
80
- const forceResult = validateColumns(params.xAxis, {
81
- labelOffset: {
82
- toBe: '[number, number]',
83
- test: (value: any) => {
84
- return Array.isArray(value)
85
- && value.length === 2
86
- && typeof value[0] === 'number'
87
- && typeof value[1] === 'number'
88
- }
89
- },
90
- labelColorType: {
91
- toBeOption: 'ColorType',
92
- },
93
- axisLineVisible: {
94
- toBeTypes: ['boolean']
95
- },
96
- axisLineColorType: {
97
- toBeOption: 'ColorType',
98
- },
99
- ticks: {
100
- toBeTypes: ['number', 'null']
101
- },
102
- tickFormat: {
103
- toBeTypes: ['string', 'Function']
104
- },
105
- tickLineVisible: {
106
- toBeTypes: ['boolean']
107
- },
108
- tickPadding: {
109
- toBeTypes: ['number']
110
- },
111
- tickFullLine: {
112
- toBeTypes: ['boolean']
113
- },
114
- tickFullLineDasharray: {
115
- toBeTypes: ['string']
116
- },
117
- tickColorType: {
118
- toBeOption: 'ColorType',
119
- },
120
- tickTextColorType: {
121
- toBeOption: 'ColorType',
122
- }
123
- })
124
- if (forceResult.status === 'error') {
125
- return forceResult
126
- }
127
- }
128
- if (params.yAxis) {
129
- const forceResult = validateColumns(params.yAxis, {
130
- labelOffset: {
131
- toBe: '[number, number]',
132
- test: (value: any) => {
133
- return Array.isArray(value)
134
- && value.length === 2
135
- && typeof value[0] === 'number'
136
- && typeof value[1] === 'number'
137
- }
138
- },
139
- labelColorType: {
140
- toBeOption: 'ColorType',
141
- },
142
- axisLineVisible: {
143
- toBeTypes: ['boolean']
144
- },
145
- axisLineColorType: {
146
- toBeOption: 'ColorType',
147
- },
148
- ticks: {
149
- toBeTypes: ['number', 'null']
150
- },
151
- tickFormat: {
152
- toBeTypes: ['string', 'Function']
153
- },
154
- tickLineVisible: {
155
- toBeTypes: ['boolean']
156
- },
157
- tickPadding: {
158
- toBeTypes: ['number']
159
- },
160
- tickFullLine: {
161
- toBeTypes: ['boolean']
162
- },
163
- tickFullLineDasharray: {
164
- toBeTypes: ['string']
165
- },
166
- tickColorType: {
167
- toBeOption: 'ColorType',
168
- },
169
- tickTextColorType: {
170
- toBeOption: 'ColorType',
171
- }
172
- })
173
- if (forceResult.status === 'error') {
174
- return forceResult
175
- }
176
- }
177
- return result
178
- }
179
- }
180
-
181
- function renderXAxisLabel ({ selection, xLabelClassName, fullParams, layout, fullDataFormatter, fullChartParams, textReverseTransform }: {
182
- selection: d3.Selection<SVGGElement, any, any, any>,
183
- xLabelClassName: string
184
- fullParams: XYAxesParams
185
- // axisLabelAlign: TextAlign
186
- layout: { width: number, height: number }
187
- fullDataFormatter: DataFormatterMultiValue,
188
- fullChartParams: ChartParams
189
- textReverseTransform: string,
190
- }) {
191
- const offsetX = fullParams.xAxis.tickPadding + fullParams.xAxis.labelOffset[0]
192
- const offsetY = fullParams.xAxis.tickPadding + fullParams.xAxis.labelOffset[1]
193
- let labelX = offsetX
194
- let labelY = offsetY
195
-
196
- const axisLabelSelection = selection
197
- .selectAll<SVGGElement, XYAxesParams>(`g.${xLabelClassName}`)
198
- .data([fullParams])
199
- .join('g')
200
- .classed(xLabelClassName, true)
201
- .each((d, i, g) => {
202
- const text = d3.select(g[i])
203
- .selectAll<SVGTextElement, XYAxesParams>(`text`)
204
- .data([d])
205
- .join(
206
- enter => {
207
- return enter
208
- .append('text')
209
- .style('font-weight', 'bold')
210
- },
211
- update => update,
212
- exit => exit.remove()
213
- )
214
- .attr('text-anchor', xAxisLabelAnchor)
215
- .attr('dominant-baseline', xAxisLabelDominantBaseline)
216
- .attr('font-size', fullChartParams.styles.textSize)
217
- .style('fill', getColor(fullParams.xAxis.labelColorType, fullChartParams))
218
- .style('transform', textReverseTransform)
219
- // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
220
- .attr('x', labelX)
221
- .attr('y', labelY)
222
- .text(d => fullDataFormatter.xAxis.label)
223
- })
224
- .attr('transform', d => `translate(${layout.width}, ${layout.height})`)
225
- }
226
-
227
- function renderYAxisLabel ({ selection, yLabelClassName, fullParams, layout, fullDataFormatter, fullChartParams, textReverseTransform }: {
228
- selection: d3.Selection<SVGGElement, any, any, any>,
229
- yLabelClassName: string
230
- fullParams: XYAxesParams
231
- // axisLabelAlign: TextAlign
232
- layout: { width: number, height: number }
233
- fullDataFormatter: DataFormatterMultiValue,
234
- fullChartParams: ChartParams
235
- textReverseTransform: string,
236
- }) {
237
- const offsetX = fullParams.yAxis.tickPadding - fullParams.yAxis.labelOffset[0]
238
- const offsetY = fullParams.yAxis.tickPadding + fullParams.yAxis.labelOffset[1]
239
- let labelX = - offsetX
240
- let labelY = - offsetY
241
-
242
- const axisLabelSelection = selection
243
- .selectAll<SVGGElement, XYAxesParams>(`g.${yLabelClassName}`)
244
- .data([fullParams])
245
- .join('g')
246
- .classed(yLabelClassName, true)
247
- .each((d, i, g) => {
248
- const text = d3.select(g[i])
249
- .selectAll<SVGTextElement, XYAxesParams>(`text`)
250
- .data([d])
251
- .join(
252
- enter => {
253
- return enter
254
- .append('text')
255
- .style('font-weight', 'bold')
256
- },
257
- update => update,
258
- exit => exit.remove()
259
- )
260
- .attr('text-anchor', yAxisLabelAnchor)
261
- .attr('dominant-baseline', yAxisLabelDominantBaseline)
262
- .attr('font-size', fullChartParams.styles.textSize)
263
- .style('fill', getColor(fullParams.yAxis.labelColorType, fullChartParams))
264
- .style('transform', textReverseTransform)
265
- // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
266
- .attr('x', labelX)
267
- .attr('y', labelY)
268
- .text(d => fullDataFormatter.yAxis.label)
269
- })
270
- // .attr('transform', d => `translate(0, ${layout.height})`)
271
- }
272
-
273
- function renderXAxis ({ selection, xAxisClassName, fullParams, layout, fullDataFormatter, fullChartParams, xScale, textReverseTransform, minMaxXY }: {
274
- selection: d3.Selection<SVGGElement, any, any, any>,
275
- xAxisClassName: string
276
- fullParams: XYAxesParams
277
- // tickTextAlign: TextAlign
278
- layout: { width: number, height: number }
279
- fullDataFormatter: DataFormatterMultiValue,
280
- fullChartParams: ChartParams
281
- xScale: d3.ScaleLinear<number, number>
282
- textReverseTransform: string,
283
- minMaxXY: {
284
- minX: number;
285
- maxX: number;
286
- minY: number;
287
- maxY: number;
288
- }
289
- }) {
290
-
291
- const xAxisSelection = selection
292
- .selectAll<SVGGElement, XYAxesParams>(`g.${xAxisClassName}`)
293
- .data([fullParams])
294
- .join('g')
295
- .classed(xAxisClassName, true)
296
- .attr('transform', `translate(0, ${layout.height})`)
297
-
298
- // const _xScale = d3.scaleLinear()
299
- // .domain([0, 150])
300
- // .range([416.5, 791.349])
301
-
302
- // 刻度文字偏移
303
- let tickPadding = fullParams.xAxis.tickPadding
304
-
305
- // 設定Y軸刻度
306
- const xAxis = d3.axisBottom(xScale)
307
- .scale(xScale)
308
- .ticks(fullParams.xAxis.ticks) // 刻度分段數量
309
- .tickFormat(d => parseTickFormatValue(d, fullParams.xAxis.tickFormat))
310
- .tickSize(fullParams.xAxis.tickFullLine == true
311
- ? -layout.height
312
- : defaultTickSize)
313
- .tickSizeOuter(-layout.height)
314
- .tickPadding(tickPadding)
315
-
316
- const xAxisEl = xAxisSelection
317
- .transition()
318
- .duration(100)
319
- .call(xAxis)
320
-
321
- xAxisEl.selectAll('line')
322
- .style('fill', 'none')
323
- .style('stroke', fullParams.xAxis.tickLineVisible == true ? getColor(fullParams.xAxis.tickColorType, fullChartParams) : 'none')
324
- .style('stroke-dasharray', fullParams.xAxis.tickFullLineDasharray)
325
- .attr('pointer-events', 'none')
326
-
327
- xAxisEl.selectAll('path')
328
- .style('fill', 'none')
329
- // .style('stroke', this.fullParams.axisLineColor!)
330
- .style('stroke', fullParams.xAxis.axisLineVisible == true ? getColor(fullParams.xAxis.axisLineColorType, fullChartParams) : 'none')
331
- .style('shape-rendering', 'crispEdges')
332
-
333
- // const xText = xAxisEl.selectAll('text')
334
- const xText = xAxisSelection.selectAll('text')
335
- // .style('font-family', 'sans-serif')
336
- .attr('font-size', fullChartParams.styles.textSize)
337
- .style('color', getColor(fullParams.xAxis.tickTextColorType, fullChartParams))
338
- .attr('text-anchor', xTickTextAnchor)
339
- .attr('dominant-baseline', xTickDominantBaseline)
340
- .attr('dy', 0)
341
- .attr('y', tickPadding)
342
- xText.style('transform', textReverseTransform)
343
-
344
- // // 抵消掉預設的偏移
345
- // if (fullDataFormatter.grid.valueAxis.position === 'bottom' || fullDataFormatter.grid.valueAxis.position === 'top') {
346
- // xText.attr('dy', 0)
347
- // }
348
-
349
- return xAxisSelection
350
- }
351
-
352
- function renderYAxis ({ selection, yAxisClassName, fullParams, layout, fullDataFormatter, fullChartParams, yScale, textReverseTransform, minMaxXY }: {
353
- selection: d3.Selection<SVGGElement, any, any, any>,
354
- yAxisClassName: string
355
- fullParams: XYAxesParams
356
- // tickTextAlign: TextAlign
357
- layout: { width: number, height: number }
358
- fullDataFormatter: DataFormatterMultiValue,
359
- fullChartParams: ChartParams
360
- yScale: d3.ScaleLinear<number, number>
361
- textReverseTransform: string,
362
- minMaxXY: {
363
- minX: number;
364
- maxX: number;
365
- minY: number;
366
- maxY: number;
367
- }
368
- }) {
369
-
370
- const yAxisSelection = selection
371
- .selectAll<SVGGElement, XYAxesParams>(`g.${yAxisClassName}`)
372
- .data([fullParams])
373
- .join('g')
374
- .classed(yAxisClassName, true)
375
-
376
- // const _yScale = d3.scaleLinear()
377
- // .domain([0, 150])
378
- // .range([416.5, 791.349])
379
-
380
- // 刻度文字偏移
381
- let tickPadding = fullParams.yAxis.tickPadding
382
-
383
- // 設定Y軸刻度
384
- const yAxis = d3.axisLeft(yScale)
385
- .scale(yScale)
386
- .ticks(fullParams.xAxis.ticks) // 刻度分段數量
387
- .tickFormat(d => parseTickFormatValue(d, fullParams.yAxis.tickFormat))
388
- .tickSize(fullParams.yAxis.tickFullLine == true
389
- ? -layout.width
390
- : defaultTickSize)
391
- .tickPadding(tickPadding)
392
-
393
- const yAxisEl = yAxisSelection
394
- .transition()
395
- .duration(100)
396
- .call(yAxis)
397
-
398
- yAxisEl.selectAll('line')
399
- .style('fill', 'none')
400
- .style('stroke', fullParams.yAxis.tickLineVisible == true ? getColor(fullParams.yAxis.tickColorType, fullChartParams) : 'none')
401
- .style('stroke-dasharray', fullParams.yAxis.tickFullLineDasharray)
402
- .attr('pointer-events', 'none')
403
-
404
- yAxisEl.selectAll('path')
405
- .style('fill', 'none')
406
- // .style('stroke', this.fullParams.axisLineColor!)
407
- .style('stroke', fullParams.yAxis.axisLineVisible == true ? getColor(fullParams.yAxis.axisLineColorType, fullChartParams) : 'none')
408
- .style('shape-rendering', 'crispEdges')
409
-
410
- // const yText = yAxisEl.selectAll('text')
411
- const yText = yAxisSelection.selectAll('text')
412
- // .style('font-family', 'sans-serif')
413
- .attr('font-size', fullChartParams.styles.textSize)
414
- .style('color', getColor(fullParams.yAxis.tickTextColorType, fullChartParams))
415
- .attr('text-anchor', yTickTextAnchor)
416
- .attr('dominant-baseline', yTickDominantBaseline)
417
- // .attr('dy', 0)
418
- .attr('x', - tickPadding)
419
- .attr('dy', 0)
420
- yText.style('transform', textReverseTransform)
421
-
422
- // // 抵消掉預設的偏移
423
- // if (fullDataFormatter.grid.valueAxis.position === 'bottom' || fullDataFormatter.grid.valueAxis.position === 'top') {
424
- // yText.attr('dy', 0)
425
- // }
426
-
427
- return yAxisSelection
428
- }
429
-
430
-
431
- export const XYAxes = defineMultiValuePlugin(pluginConfig)(({ selection, name, observer, subject }) => {
432
-
433
- const destroy$ = new Subject()
434
-
435
- const containerClassName = getClassName(pluginName, 'container')
436
- const xAxisGClassName = getClassName(pluginName, 'xAxisG')
437
- const xAxisClassName = getClassName(pluginName, 'xAxis')
438
- const yAxisGClassName = getClassName(pluginName, 'yAxisG')
439
- const yAxisClassName = getClassName(pluginName, 'yAxis')
440
- const xLabelClassName = getClassName(pluginName, 'xLabel')
441
- const yLabelClassName = getClassName(pluginName, 'yLabel')
442
-
443
- const containerSelection$ = combineLatest({
444
- computedData: observer.computedData$.pipe(
445
- distinctUntilChanged((a, b) => {
446
- // 只有當series的數量改變時,才重新計算
447
- return a.length === b.length
448
- }),
449
- ),
450
- isCategorySeprate: observer.isCategorySeprate$
451
- }).pipe(
452
- takeUntil(destroy$),
453
- switchMap(async (d) => d),
454
- map(data => {
455
- return data.isCategorySeprate
456
- // category分開的時候顯示各別axis
457
- ? data.computedData
458
- // category合併的時候只顯示第一個axis
459
- : [data.computedData[0]]
460
- }),
461
- map((computedData, i) => {
462
- return selection
463
- .selectAll<SVGGElement, ComputedDatumMultiValue[]>(`g.${containerClassName}`)
464
- .data(computedData, d => d[0] ? d[0].categoryIndex : i)
465
- .join('g')
466
- .classed(containerClassName, true)
467
- })
468
- )
469
-
470
- const axisSelection$ = containerSelection$.pipe(
471
- takeUntil(destroy$),
472
- map((containerSelection, i) => {
473
- return containerSelection
474
- .selectAll<SVGGElement, ComputedDatumMultiValue[]>(`g.${yAxisGClassName}`)
475
- .data([yAxisGClassName])
476
- .join('g')
477
- .classed(yAxisGClassName, true)
478
- })
479
- )
480
-
481
- combineLatest({
482
- containerSelection: containerSelection$,
483
- gridContainerPosition: observer.multiValueContainerPosition$
484
- }).pipe(
485
- takeUntil(destroy$),
486
- switchMap(async d => d)
487
- ).subscribe(data => {
488
- data.containerSelection
489
- .attr('transform', (d, i) => {
490
- const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
491
- const translate = gridContainerPosition.translate
492
- const scale = gridContainerPosition.scale
493
- return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
494
- })
495
- // .attr('opacity', 0)
496
- // .transition()
497
- // .attr('opacity', 1)
498
- })
499
-
500
- const textReverseTransform$ = observer.multiValueContainerPosition$.pipe(
501
- takeUntil(destroy$),
502
- switchMap(async (d) => d),
503
- map(multiValueContainerPosition => {
504
- // const axesRotateXYReverseValue = `rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
505
- // const axesRotateReverseValue = `rotate(${data.gridAxesReverseTransform.rotate}deg)`
506
- const containerScaleReverseValue = `scale(${1 / multiValueContainerPosition[0].scale[0]}, ${1 / multiValueContainerPosition[0].scale[1]})`
507
- // 抵消最外層scale
508
- return `${containerScaleReverseValue}`
509
- }),
510
- distinctUntilChanged()
511
- )
512
-
513
- // const minAndMax$: Observable<[number, number]> = new Observable(subscriber => {
514
- // combineLatest({
515
- // fullDataFormatter: observer.fullDataFormatter$,
516
- // computedData: observer.computedData$
517
- // }).pipe(
518
- // takeUntil(destroy$),
519
- // switchMap(async (d) => d),
520
- // ).subscribe(data => {
521
- // const groupMin = 0
522
- // const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
523
- // // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
524
- // // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
525
- // // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
526
- // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] - data.fullDataFormatter.grid.groupAxis.scalePadding
527
- // const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'max'
528
- // ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
529
- // : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
530
-
531
- // const filteredData = data.computedData.map((d, i) => {
532
- // return d.filter((_d, _i) => {
533
- // return _i >= groupScaleDomainMin && _i <= groupScaleDomainMax
534
- // })
535
- // })
536
-
537
- // const filteredMinAndMax = getMinAndMax(filteredData.flat().map(d => d.value[1]))
538
- // if (filteredMinAndMax[0] === filteredMinAndMax[1]) {
539
- // filteredMinAndMax[0] = filteredMinAndMax[1] - 1 // 避免最大及最小值相同造成無法計算scale
540
- // }
541
- // subscriber.next(filteredMinAndMax)
542
- // })
543
- // })
544
-
545
- const xScale$: Observable<d3.ScaleLinear<number, number>> = new Observable(subscriber => {
546
- combineLatest({
547
- fullDataFormatter: observer.fullDataFormatter$,
548
- layout: observer.layout$,
549
- // minMaxXY: observer.minMaxXY$
550
- filteredMinMaxXYData: observer.filteredMinMaxXYData$
551
- }).pipe(
552
- takeUntil(destroy$),
553
- switchMap(async (d) => d),
554
- ).subscribe(data => {
555
- if (!data.filteredMinMaxXYData.minXDatum || !data.filteredMinMaxXYData.maxXDatum
556
- || data.filteredMinMaxXYData.minXDatum.value[0] == null || data.filteredMinMaxXYData.maxXDatum.value[0] == null
557
- ) {
558
- return
559
- }
560
- let maxValue = data.filteredMinMaxXYData.maxXDatum.value[0]
561
- let minValue = data.filteredMinMaxXYData.minXDatum.value[0]
562
- if (maxValue === minValue && maxValue === 0) {
563
- // 避免最大及最小值同等於 0 造成無法計算scale
564
- maxValue = 1
565
- }
566
-
567
- const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
568
- maxValue,
569
- minValue,
570
- axisWidth: data.layout.width,
571
- scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
572
- scaleRange: data.fullDataFormatter.xAxis.scaleRange,
573
- })
574
-
575
- subscriber.next(xScale)
576
- })
577
- })
578
-
579
- const yScale$: Observable<d3.ScaleLinear<number, number>> = new Observable(subscriber => {
580
- combineLatest({
581
- fullDataFormatter: observer.fullDataFormatter$,
582
- layout: observer.layout$,
583
- // minMaxXY: observer.minMaxXY$
584
- filteredMinMaxXYData: observer.filteredMinMaxXYData$
585
- }).pipe(
586
- takeUntil(destroy$),
587
- switchMap(async (d) => d),
588
- ).subscribe(data => {
589
- if (!data.filteredMinMaxXYData.minYDatum || !data.filteredMinMaxXYData.maxYDatum
590
- || data.filteredMinMaxXYData.minYDatum.value[1] == null || data.filteredMinMaxXYData.maxYDatum.value[1] == null
591
- ) {
592
- return
593
- }
594
- let maxValue = data.filteredMinMaxXYData.maxYDatum.value[1]
595
- let minValue = data.filteredMinMaxXYData.minYDatum.value[1]
596
- if (maxValue === minValue && maxValue === 0) {
597
- // 避免最大及最小值同等於 0 造成無法計算scale
598
- maxValue = 1
599
- }
600
-
601
- const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
602
- maxValue,
603
- minValue,
604
- axisWidth: data.layout.height,
605
- scaleDomain: data.fullDataFormatter.yAxis.scaleDomain,
606
- scaleRange: data.fullDataFormatter.yAxis.scaleRange,
607
- reverse: true
608
- })
609
-
610
- subscriber.next(yScale)
611
- })
612
- })
613
-
614
-
615
- combineLatest({
616
- axisSelection: axisSelection$,
617
- fullParams: observer.fullParams$,
618
- // tickTextAlign: tickTextAlign$,
619
- // axisLabelAlign: axisLabelAlign$,
620
- computedData: observer.computedData$,
621
- layout: observer.layout$,
622
- fullDataFormatter: observer.fullDataFormatter$,
623
- fullChartParams: observer.fullChartParams$,
624
- xScale: xScale$,
625
- yScale: yScale$,
626
- textReverseTransform: textReverseTransform$,
627
- minMaxXY: observer.minMaxXY$
628
- }).pipe(
629
- takeUntil(destroy$),
630
- switchMap(async (d) => d),
631
- ).subscribe(data => {
632
-
633
- renderXAxis({
634
- selection: data.axisSelection,
635
- xAxisClassName,
636
- fullParams: data.fullParams,
637
- // tickTextAlign: data.tickTextAlign,
638
- layout: data.layout,
639
- fullDataFormatter: data.fullDataFormatter,
640
- fullChartParams: data.fullChartParams,
641
- xScale: data.xScale,
642
- textReverseTransform: data.textReverseTransform,
643
- minMaxXY: data.minMaxXY
644
- })
645
-
646
- renderYAxis({
647
- selection: data.axisSelection,
648
- yAxisClassName,
649
- fullParams: data.fullParams,
650
- // tickTextAlign: data.tickTextAlign,
651
- layout: data.layout,
652
- fullDataFormatter: data.fullDataFormatter,
653
- fullChartParams: data.fullChartParams,
654
- yScale: data.yScale,
655
- textReverseTransform: data.textReverseTransform,
656
- minMaxXY: data.minMaxXY
657
- })
658
-
659
- renderXAxisLabel({
660
- selection: data.axisSelection,
661
- xLabelClassName,
662
- fullParams: data.fullParams,
663
- // axisLabelAlign: data.axisLabelAlign,
664
- layout: data.layout,
665
- fullDataFormatter: data.fullDataFormatter,
666
- fullChartParams: data.fullChartParams,
667
- textReverseTransform: data.textReverseTransform,
668
- })
669
-
670
- renderYAxisLabel({
671
- selection: data.axisSelection,
672
- yLabelClassName,
673
- fullParams: data.fullParams,
674
- // axisLabelAlign: data.axisLabelAlign,
675
- layout: data.layout,
676
- fullDataFormatter: data.fullDataFormatter,
677
- fullChartParams: data.fullChartParams,
678
- textReverseTransform: data.textReverseTransform,
679
- })
680
- })
681
-
682
- return () => {
683
- destroy$.next(undefined)
684
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ Observable,
4
+ Subject,
5
+ combineLatest,
6
+ takeUntil,
7
+ map,
8
+ distinctUntilChanged,
9
+ switchMap,
10
+ shareReplay
11
+ } from 'rxjs'
12
+ import type {
13
+ ColorType,
14
+ ChartParams,
15
+ ComputedDatumMultiValue,
16
+ DataFormatterMultiValue,
17
+ DefinePluginConfig,
18
+ } from '../../../lib/core-types'
19
+ import {
20
+ defineMultiValuePlugin,
21
+ createValueToAxisScale,
22
+ getMinMax
23
+ } from '../../../lib/core'
24
+ import type { XYAxesParams
25
+ } from '../../../lib/plugins-basic-types'
26
+ import { DEFAULT_X_Y_AXES_PARAMS } from '../defaults'
27
+ import { LAYER_INDEX_OF_AXIS } from '../../const'
28
+ import { getColor, getDatumColor, getClassName, getUniID } from '../../utils/orbchartsUtils'
29
+ import { parseTickFormatValue } from '../../utils/d3Utils'
30
+ import { filteredMinMaxXYDataObservable } from '../../../../orbcharts-core/src/utils/multiValueObservables'
31
+ // import { multiValueSelectionsObservable } from '../multiValueObservables'
32
+
33
+ interface TextAlign {
34
+ textAnchor: "start" | "middle" | "end"
35
+ dominantBaseline: "middle" | "auto" | "hanging"
36
+ }
37
+
38
+ // interface Axis {
39
+ // labelOffset: [number, number]
40
+ // labelColorType: ColorType
41
+ // axisLineVisible: boolean
42
+ // axisLineColorType: ColorType
43
+ // ticks: number | null
44
+ // tickFormat: string | ((text: d3.NumberValue) => string)
45
+ // tickLineVisible: boolean
46
+ // tickPadding: number
47
+ // tickFullLine: boolean
48
+ // tickFullLineDasharray: string
49
+ // tickColorType: ColorType
50
+ // tickTextColorType: ColorType
51
+ // }
52
+
53
+ const pluginName = 'XYAxes'
54
+
55
+ const defaultTickSize = 6
56
+
57
+ const xTickTextAnchor = 'middle'
58
+ const xTickDominantBaseline = 'hanging'
59
+ const xAxisLabelAnchor = 'start'
60
+ const xAxisLabelDominantBaseline = 'hanging'
61
+ const yTickTextAnchor = 'end'
62
+ const yTickDominantBaseline = 'middle'
63
+ const yAxisLabelAnchor = 'end'
64
+ const yAxisLabelDominantBaseline = 'auto'
65
+
66
+ const pluginConfig: DefinePluginConfig<typeof pluginName, typeof DEFAULT_X_Y_AXES_PARAMS> = {
67
+ name: pluginName,
68
+ defaultParams: DEFAULT_X_Y_AXES_PARAMS,
69
+ layerIndex: LAYER_INDEX_OF_AXIS,
70
+ validator: (params, { validateColumns }) => {
71
+ const result = validateColumns(params, {
72
+ xAxis: {
73
+ toBeTypes: ['object']
74
+ },
75
+ yAxis: {
76
+ toBeTypes: ['object']
77
+ }
78
+ })
79
+ if (params.xAxis) {
80
+ const forceResult = validateColumns(params.xAxis, {
81
+ labelOffset: {
82
+ toBe: '[number, number]',
83
+ test: (value: any) => {
84
+ return Array.isArray(value)
85
+ && value.length === 2
86
+ && typeof value[0] === 'number'
87
+ && typeof value[1] === 'number'
88
+ }
89
+ },
90
+ labelColorType: {
91
+ toBeOption: 'ColorType',
92
+ },
93
+ axisLineVisible: {
94
+ toBeTypes: ['boolean']
95
+ },
96
+ axisLineColorType: {
97
+ toBeOption: 'ColorType',
98
+ },
99
+ ticks: {
100
+ toBeTypes: ['number', 'null']
101
+ },
102
+ tickFormat: {
103
+ toBeTypes: ['string', 'Function']
104
+ },
105
+ tickLineVisible: {
106
+ toBeTypes: ['boolean']
107
+ },
108
+ tickPadding: {
109
+ toBeTypes: ['number']
110
+ },
111
+ tickFullLine: {
112
+ toBeTypes: ['boolean']
113
+ },
114
+ tickFullLineDasharray: {
115
+ toBeTypes: ['string']
116
+ },
117
+ tickColorType: {
118
+ toBeOption: 'ColorType',
119
+ },
120
+ tickTextColorType: {
121
+ toBeOption: 'ColorType',
122
+ }
123
+ })
124
+ if (forceResult.status === 'error') {
125
+ return forceResult
126
+ }
127
+ }
128
+ if (params.yAxis) {
129
+ const forceResult = validateColumns(params.yAxis, {
130
+ labelOffset: {
131
+ toBe: '[number, number]',
132
+ test: (value: any) => {
133
+ return Array.isArray(value)
134
+ && value.length === 2
135
+ && typeof value[0] === 'number'
136
+ && typeof value[1] === 'number'
137
+ }
138
+ },
139
+ labelColorType: {
140
+ toBeOption: 'ColorType',
141
+ },
142
+ axisLineVisible: {
143
+ toBeTypes: ['boolean']
144
+ },
145
+ axisLineColorType: {
146
+ toBeOption: 'ColorType',
147
+ },
148
+ ticks: {
149
+ toBeTypes: ['number', 'null']
150
+ },
151
+ tickFormat: {
152
+ toBeTypes: ['string', 'Function']
153
+ },
154
+ tickLineVisible: {
155
+ toBeTypes: ['boolean']
156
+ },
157
+ tickPadding: {
158
+ toBeTypes: ['number']
159
+ },
160
+ tickFullLine: {
161
+ toBeTypes: ['boolean']
162
+ },
163
+ tickFullLineDasharray: {
164
+ toBeTypes: ['string']
165
+ },
166
+ tickColorType: {
167
+ toBeOption: 'ColorType',
168
+ },
169
+ tickTextColorType: {
170
+ toBeOption: 'ColorType',
171
+ }
172
+ })
173
+ if (forceResult.status === 'error') {
174
+ return forceResult
175
+ }
176
+ }
177
+ return result
178
+ }
179
+ }
180
+
181
+ function renderXAxisLabel ({ selection, xLabelClassName, fullParams, layout, fullDataFormatter, fullChartParams, textReverseTransform }: {
182
+ selection: d3.Selection<SVGGElement, any, any, any>,
183
+ xLabelClassName: string
184
+ fullParams: XYAxesParams
185
+ // axisLabelAlign: TextAlign
186
+ layout: { width: number, height: number }
187
+ fullDataFormatter: DataFormatterMultiValue,
188
+ fullChartParams: ChartParams
189
+ textReverseTransform: string,
190
+ }) {
191
+ const offsetX = fullParams.xAxis.tickPadding + fullParams.xAxis.labelOffset[0]
192
+ const offsetY = fullParams.xAxis.tickPadding + fullParams.xAxis.labelOffset[1]
193
+ let labelX = offsetX
194
+ let labelY = offsetY
195
+
196
+ const axisLabelSelection = selection
197
+ .selectAll<SVGGElement, XYAxesParams>(`g.${xLabelClassName}`)
198
+ .data([fullParams])
199
+ .join('g')
200
+ .classed(xLabelClassName, true)
201
+ .each((d, i, g) => {
202
+ const text = d3.select(g[i])
203
+ .selectAll<SVGTextElement, XYAxesParams>(`text`)
204
+ .data([d])
205
+ .join(
206
+ enter => {
207
+ return enter
208
+ .append('text')
209
+ .style('font-weight', 'bold')
210
+ },
211
+ update => update,
212
+ exit => exit.remove()
213
+ )
214
+ .attr('text-anchor', xAxisLabelAnchor)
215
+ .attr('dominant-baseline', xAxisLabelDominantBaseline)
216
+ .attr('font-size', fullChartParams.styles.textSize)
217
+ .style('fill', getColor(fullParams.xAxis.labelColorType, fullChartParams))
218
+ .style('transform', textReverseTransform)
219
+ // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
220
+ .attr('x', labelX)
221
+ .attr('y', labelY)
222
+ .text(d => fullDataFormatter.xAxis.label)
223
+ })
224
+ .attr('transform', d => `translate(${layout.width}, ${layout.height})`)
225
+ }
226
+
227
+ function renderYAxisLabel ({ selection, yLabelClassName, fullParams, layout, fullDataFormatter, fullChartParams, textReverseTransform }: {
228
+ selection: d3.Selection<SVGGElement, any, any, any>,
229
+ yLabelClassName: string
230
+ fullParams: XYAxesParams
231
+ // axisLabelAlign: TextAlign
232
+ layout: { width: number, height: number }
233
+ fullDataFormatter: DataFormatterMultiValue,
234
+ fullChartParams: ChartParams
235
+ textReverseTransform: string,
236
+ }) {
237
+ const offsetX = fullParams.yAxis.tickPadding - fullParams.yAxis.labelOffset[0]
238
+ const offsetY = fullParams.yAxis.tickPadding + fullParams.yAxis.labelOffset[1]
239
+ let labelX = - offsetX
240
+ let labelY = - offsetY
241
+
242
+ const axisLabelSelection = selection
243
+ .selectAll<SVGGElement, XYAxesParams>(`g.${yLabelClassName}`)
244
+ .data([fullParams])
245
+ .join('g')
246
+ .classed(yLabelClassName, true)
247
+ .each((d, i, g) => {
248
+ const text = d3.select(g[i])
249
+ .selectAll<SVGTextElement, XYAxesParams>(`text`)
250
+ .data([d])
251
+ .join(
252
+ enter => {
253
+ return enter
254
+ .append('text')
255
+ .style('font-weight', 'bold')
256
+ },
257
+ update => update,
258
+ exit => exit.remove()
259
+ )
260
+ .attr('text-anchor', yAxisLabelAnchor)
261
+ .attr('dominant-baseline', yAxisLabelDominantBaseline)
262
+ .attr('font-size', fullChartParams.styles.textSize)
263
+ .style('fill', getColor(fullParams.yAxis.labelColorType, fullChartParams))
264
+ .style('transform', textReverseTransform)
265
+ // 偏移使用 x, y 而非 transform 才不會受到外層 scale 變形影響
266
+ .attr('x', labelX)
267
+ .attr('y', labelY)
268
+ .text(d => fullDataFormatter.yAxis.label)
269
+ })
270
+ // .attr('transform', d => `translate(0, ${layout.height})`)
271
+ }
272
+
273
+ function renderXAxis ({ selection, xAxisClassName, fullParams, layout, fullDataFormatter, fullChartParams, xScale, textReverseTransform, minMaxXY }: {
274
+ selection: d3.Selection<SVGGElement, any, any, any>,
275
+ xAxisClassName: string
276
+ fullParams: XYAxesParams
277
+ // tickTextAlign: TextAlign
278
+ layout: { width: number, height: number }
279
+ fullDataFormatter: DataFormatterMultiValue,
280
+ fullChartParams: ChartParams
281
+ xScale: d3.ScaleLinear<number, number>
282
+ textReverseTransform: string,
283
+ minMaxXY: {
284
+ minX: number;
285
+ maxX: number;
286
+ minY: number;
287
+ maxY: number;
288
+ }
289
+ }) {
290
+
291
+ const xAxisSelection = selection
292
+ .selectAll<SVGGElement, XYAxesParams>(`g.${xAxisClassName}`)
293
+ .data([fullParams])
294
+ .join('g')
295
+ .classed(xAxisClassName, true)
296
+ .attr('transform', `translate(0, ${layout.height})`)
297
+
298
+ // const _xScale = d3.scaleLinear()
299
+ // .domain([0, 150])
300
+ // .range([416.5, 791.349])
301
+
302
+ // 刻度文字偏移
303
+ let tickPadding = fullParams.xAxis.tickPadding
304
+
305
+ // 設定Y軸刻度
306
+ const xAxis = d3.axisBottom(xScale)
307
+ .scale(xScale)
308
+ .ticks(fullParams.xAxis.ticks) // 刻度分段數量
309
+ .tickFormat(d => parseTickFormatValue(d, fullParams.xAxis.tickFormat))
310
+ .tickSize(fullParams.xAxis.tickFullLine == true
311
+ ? -layout.height
312
+ : defaultTickSize)
313
+ .tickSizeOuter(-layout.height)
314
+ .tickPadding(tickPadding)
315
+
316
+ const xAxisEl = xAxisSelection
317
+ .transition()
318
+ .duration(100)
319
+ .call(xAxis)
320
+
321
+ xAxisEl.selectAll('line')
322
+ .style('fill', 'none')
323
+ .style('stroke', fullParams.xAxis.tickLineVisible == true ? getColor(fullParams.xAxis.tickColorType, fullChartParams) : 'none')
324
+ .style('stroke-dasharray', fullParams.xAxis.tickFullLineDasharray)
325
+ .attr('pointer-events', 'none')
326
+
327
+ xAxisEl.selectAll('path')
328
+ .style('fill', 'none')
329
+ // .style('stroke', this.fullParams.axisLineColor!)
330
+ .style('stroke', fullParams.xAxis.axisLineVisible == true ? getColor(fullParams.xAxis.axisLineColorType, fullChartParams) : 'none')
331
+ .style('shape-rendering', 'crispEdges')
332
+
333
+ // const xText = xAxisEl.selectAll('text')
334
+ const xText = xAxisSelection.selectAll('text')
335
+ // .style('font-family', 'sans-serif')
336
+ .attr('font-size', fullChartParams.styles.textSize)
337
+ .style('color', getColor(fullParams.xAxis.tickTextColorType, fullChartParams))
338
+ .attr('text-anchor', xTickTextAnchor)
339
+ .attr('dominant-baseline', xTickDominantBaseline)
340
+ .attr('dy', 0)
341
+ .attr('y', tickPadding)
342
+ xText.style('transform', textReverseTransform)
343
+
344
+ // // 抵消掉預設的偏移
345
+ // if (fullDataFormatter.grid.valueAxis.position === 'bottom' || fullDataFormatter.grid.valueAxis.position === 'top') {
346
+ // xText.attr('dy', 0)
347
+ // }
348
+
349
+ return xAxisSelection
350
+ }
351
+
352
+ function renderYAxis ({ selection, yAxisClassName, fullParams, layout, fullDataFormatter, fullChartParams, yScale, textReverseTransform, minMaxXY }: {
353
+ selection: d3.Selection<SVGGElement, any, any, any>,
354
+ yAxisClassName: string
355
+ fullParams: XYAxesParams
356
+ // tickTextAlign: TextAlign
357
+ layout: { width: number, height: number }
358
+ fullDataFormatter: DataFormatterMultiValue,
359
+ fullChartParams: ChartParams
360
+ yScale: d3.ScaleLinear<number, number>
361
+ textReverseTransform: string,
362
+ minMaxXY: {
363
+ minX: number;
364
+ maxX: number;
365
+ minY: number;
366
+ maxY: number;
367
+ }
368
+ }) {
369
+
370
+ const yAxisSelection = selection
371
+ .selectAll<SVGGElement, XYAxesParams>(`g.${yAxisClassName}`)
372
+ .data([fullParams])
373
+ .join('g')
374
+ .classed(yAxisClassName, true)
375
+
376
+ // const _yScale = d3.scaleLinear()
377
+ // .domain([0, 150])
378
+ // .range([416.5, 791.349])
379
+
380
+ // 刻度文字偏移
381
+ let tickPadding = fullParams.yAxis.tickPadding
382
+
383
+ // 設定Y軸刻度
384
+ const yAxis = d3.axisLeft(yScale)
385
+ .scale(yScale)
386
+ .ticks(fullParams.xAxis.ticks) // 刻度分段數量
387
+ .tickFormat(d => parseTickFormatValue(d, fullParams.yAxis.tickFormat))
388
+ .tickSize(fullParams.yAxis.tickFullLine == true
389
+ ? -layout.width
390
+ : defaultTickSize)
391
+ .tickPadding(tickPadding)
392
+
393
+ const yAxisEl = yAxisSelection
394
+ .transition()
395
+ .duration(100)
396
+ .call(yAxis)
397
+
398
+ yAxisEl.selectAll('line')
399
+ .style('fill', 'none')
400
+ .style('stroke', fullParams.yAxis.tickLineVisible == true ? getColor(fullParams.yAxis.tickColorType, fullChartParams) : 'none')
401
+ .style('stroke-dasharray', fullParams.yAxis.tickFullLineDasharray)
402
+ .attr('pointer-events', 'none')
403
+
404
+ yAxisEl.selectAll('path')
405
+ .style('fill', 'none')
406
+ // .style('stroke', this.fullParams.axisLineColor!)
407
+ .style('stroke', fullParams.yAxis.axisLineVisible == true ? getColor(fullParams.yAxis.axisLineColorType, fullChartParams) : 'none')
408
+ .style('shape-rendering', 'crispEdges')
409
+
410
+ // const yText = yAxisEl.selectAll('text')
411
+ const yText = yAxisSelection.selectAll('text')
412
+ // .style('font-family', 'sans-serif')
413
+ .attr('font-size', fullChartParams.styles.textSize)
414
+ .style('color', getColor(fullParams.yAxis.tickTextColorType, fullChartParams))
415
+ .attr('text-anchor', yTickTextAnchor)
416
+ .attr('dominant-baseline', yTickDominantBaseline)
417
+ // .attr('dy', 0)
418
+ .attr('x', - tickPadding)
419
+ .attr('dy', 0)
420
+ yText.style('transform', textReverseTransform)
421
+
422
+ // // 抵消掉預設的偏移
423
+ // if (fullDataFormatter.grid.valueAxis.position === 'bottom' || fullDataFormatter.grid.valueAxis.position === 'top') {
424
+ // yText.attr('dy', 0)
425
+ // }
426
+
427
+ return yAxisSelection
428
+ }
429
+
430
+
431
+ export const XYAxes = defineMultiValuePlugin(pluginConfig)(({ selection, name, observer, subject }) => {
432
+
433
+ const destroy$ = new Subject()
434
+
435
+ const containerClassName = getClassName(pluginName, 'container')
436
+ const xAxisGClassName = getClassName(pluginName, 'xAxisG')
437
+ const xAxisClassName = getClassName(pluginName, 'xAxis')
438
+ const yAxisGClassName = getClassName(pluginName, 'yAxisG')
439
+ const yAxisClassName = getClassName(pluginName, 'yAxis')
440
+ const xLabelClassName = getClassName(pluginName, 'xLabel')
441
+ const yLabelClassName = getClassName(pluginName, 'yLabel')
442
+
443
+ const containerSelection$ = combineLatest({
444
+ computedData: observer.computedData$.pipe(
445
+ distinctUntilChanged((a, b) => {
446
+ // 只有當series的數量改變時,才重新計算
447
+ return a.length === b.length
448
+ }),
449
+ ),
450
+ isCategorySeprate: observer.isCategorySeprate$
451
+ }).pipe(
452
+ takeUntil(destroy$),
453
+ switchMap(async (d) => d),
454
+ map(data => {
455
+ return data.isCategorySeprate
456
+ // category分開的時候顯示各別axis
457
+ ? data.computedData
458
+ // category合併的時候只顯示第一個axis
459
+ : [data.computedData[0]]
460
+ }),
461
+ map((computedData, i) => {
462
+ return selection
463
+ .selectAll<SVGGElement, ComputedDatumMultiValue[]>(`g.${containerClassName}`)
464
+ .data(computedData, d => d[0] ? d[0].categoryIndex : i)
465
+ .join('g')
466
+ .classed(containerClassName, true)
467
+ })
468
+ )
469
+
470
+ const axisSelection$ = containerSelection$.pipe(
471
+ takeUntil(destroy$),
472
+ map((containerSelection, i) => {
473
+ return containerSelection
474
+ .selectAll<SVGGElement, ComputedDatumMultiValue[]>(`g.${yAxisGClassName}`)
475
+ .data([yAxisGClassName])
476
+ .join('g')
477
+ .classed(yAxisGClassName, true)
478
+ })
479
+ )
480
+
481
+ combineLatest({
482
+ containerSelection: containerSelection$,
483
+ gridContainerPosition: observer.multiValueContainerPosition$
484
+ }).pipe(
485
+ takeUntil(destroy$),
486
+ switchMap(async d => d)
487
+ ).subscribe(data => {
488
+ data.containerSelection
489
+ .attr('transform', (d, i) => {
490
+ const gridContainerPosition = data.gridContainerPosition[i] ?? data.gridContainerPosition[0]
491
+ const translate = gridContainerPosition.translate
492
+ const scale = gridContainerPosition.scale
493
+ return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
494
+ })
495
+ // .attr('opacity', 0)
496
+ // .transition()
497
+ // .attr('opacity', 1)
498
+ })
499
+
500
+ const textReverseTransform$ = observer.multiValueContainerPosition$.pipe(
501
+ takeUntil(destroy$),
502
+ switchMap(async (d) => d),
503
+ map(multiValueContainerPosition => {
504
+ // const axesRotateXYReverseValue = `rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
505
+ // const axesRotateReverseValue = `rotate(${data.gridAxesReverseTransform.rotate}deg)`
506
+ const containerScaleReverseValue = `scale(${1 / multiValueContainerPosition[0].scale[0]}, ${1 / multiValueContainerPosition[0].scale[1]})`
507
+ // 抵消最外層scale
508
+ return `${containerScaleReverseValue}`
509
+ }),
510
+ distinctUntilChanged()
511
+ )
512
+
513
+ // const minMax$: Observable<[number, number]> = new Observable(subscriber => {
514
+ // combineLatest({
515
+ // fullDataFormatter: observer.fullDataFormatter$,
516
+ // computedData: observer.computedData$
517
+ // }).pipe(
518
+ // takeUntil(destroy$),
519
+ // switchMap(async (d) => d),
520
+ // ).subscribe(data => {
521
+ // const groupMin = 0
522
+ // const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
523
+ // // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
524
+ // // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
525
+ // // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
526
+ // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] - data.fullDataFormatter.grid.groupAxis.scalePadding
527
+ // const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'max'
528
+ // ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
529
+ // : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
530
+
531
+ // const filteredData = data.computedData.map((d, i) => {
532
+ // return d.filter((_d, _i) => {
533
+ // return _i >= groupScaleDomainMin && _i <= groupScaleDomainMax
534
+ // })
535
+ // })
536
+
537
+ // const filteredMinMax = getMinMax(filteredData.flat().map(d => d.value[1]))
538
+ // if (filteredMinMax[0] === filteredMinMax[1]) {
539
+ // filteredMinMax[0] = filteredMinMax[1] - 1 // 避免最大及最小值相同造成無法計算scale
540
+ // }
541
+ // subscriber.next(filteredMinMax)
542
+ // })
543
+ // })
544
+
545
+ const xScale$: Observable<d3.ScaleLinear<number, number>> = new Observable(subscriber => {
546
+ combineLatest({
547
+ fullDataFormatter: observer.fullDataFormatter$,
548
+ layout: observer.layout$,
549
+ // minMaxXY: observer.minMaxXY$
550
+ filteredMinMaxXYData: observer.filteredMinMaxXYData$
551
+ }).pipe(
552
+ takeUntil(destroy$),
553
+ switchMap(async (d) => d),
554
+ ).subscribe(data => {
555
+ if (!data.filteredMinMaxXYData.minXDatum || !data.filteredMinMaxXYData.maxXDatum
556
+ || data.filteredMinMaxXYData.minXDatum.value[0] == null || data.filteredMinMaxXYData.maxXDatum.value[0] == null
557
+ ) {
558
+ return
559
+ }
560
+ let maxValue = data.filteredMinMaxXYData.maxXDatum.value[0]
561
+ let minValue = data.filteredMinMaxXYData.minXDatum.value[0]
562
+ if (maxValue === minValue && maxValue === 0) {
563
+ // 避免最大及最小值同等於 0 造成無法計算scale
564
+ maxValue = 1
565
+ }
566
+
567
+ const xScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
568
+ maxValue,
569
+ minValue,
570
+ axisWidth: data.layout.width,
571
+ scaleDomain: data.fullDataFormatter.xAxis.scaleDomain,
572
+ scaleRange: data.fullDataFormatter.xAxis.scaleRange,
573
+ })
574
+
575
+ subscriber.next(xScale)
576
+ })
577
+ })
578
+
579
+ const yScale$: Observable<d3.ScaleLinear<number, number>> = new Observable(subscriber => {
580
+ combineLatest({
581
+ fullDataFormatter: observer.fullDataFormatter$,
582
+ layout: observer.layout$,
583
+ // minMaxXY: observer.minMaxXY$
584
+ filteredMinMaxXYData: observer.filteredMinMaxXYData$
585
+ }).pipe(
586
+ takeUntil(destroy$),
587
+ switchMap(async (d) => d),
588
+ ).subscribe(data => {
589
+ if (!data.filteredMinMaxXYData.minYDatum || !data.filteredMinMaxXYData.maxYDatum
590
+ || data.filteredMinMaxXYData.minYDatum.value[1] == null || data.filteredMinMaxXYData.maxYDatum.value[1] == null
591
+ ) {
592
+ return
593
+ }
594
+ let maxValue = data.filteredMinMaxXYData.maxYDatum.value[1]
595
+ let minValue = data.filteredMinMaxXYData.minYDatum.value[1]
596
+ if (maxValue === minValue && maxValue === 0) {
597
+ // 避免最大及最小值同等於 0 造成無法計算scale
598
+ maxValue = 1
599
+ }
600
+
601
+ const yScale: d3.ScaleLinear<number, number> = createValueToAxisScale({
602
+ maxValue,
603
+ minValue,
604
+ axisWidth: data.layout.height,
605
+ scaleDomain: data.fullDataFormatter.yAxis.scaleDomain,
606
+ scaleRange: data.fullDataFormatter.yAxis.scaleRange,
607
+ reverse: true
608
+ })
609
+
610
+ subscriber.next(yScale)
611
+ })
612
+ })
613
+
614
+
615
+ combineLatest({
616
+ axisSelection: axisSelection$,
617
+ fullParams: observer.fullParams$,
618
+ // tickTextAlign: tickTextAlign$,
619
+ // axisLabelAlign: axisLabelAlign$,
620
+ computedData: observer.computedData$,
621
+ layout: observer.layout$,
622
+ fullDataFormatter: observer.fullDataFormatter$,
623
+ fullChartParams: observer.fullChartParams$,
624
+ xScale: xScale$,
625
+ yScale: yScale$,
626
+ textReverseTransform: textReverseTransform$,
627
+ minMaxXY: observer.minMaxXY$
628
+ }).pipe(
629
+ takeUntil(destroy$),
630
+ switchMap(async (d) => d),
631
+ ).subscribe(data => {
632
+
633
+ renderXAxis({
634
+ selection: data.axisSelection,
635
+ xAxisClassName,
636
+ fullParams: data.fullParams,
637
+ // tickTextAlign: data.tickTextAlign,
638
+ layout: data.layout,
639
+ fullDataFormatter: data.fullDataFormatter,
640
+ fullChartParams: data.fullChartParams,
641
+ xScale: data.xScale,
642
+ textReverseTransform: data.textReverseTransform,
643
+ minMaxXY: data.minMaxXY
644
+ })
645
+
646
+ renderYAxis({
647
+ selection: data.axisSelection,
648
+ yAxisClassName,
649
+ fullParams: data.fullParams,
650
+ // tickTextAlign: data.tickTextAlign,
651
+ layout: data.layout,
652
+ fullDataFormatter: data.fullDataFormatter,
653
+ fullChartParams: data.fullChartParams,
654
+ yScale: data.yScale,
655
+ textReverseTransform: data.textReverseTransform,
656
+ minMaxXY: data.minMaxXY
657
+ })
658
+
659
+ renderXAxisLabel({
660
+ selection: data.axisSelection,
661
+ xLabelClassName,
662
+ fullParams: data.fullParams,
663
+ // axisLabelAlign: data.axisLabelAlign,
664
+ layout: data.layout,
665
+ fullDataFormatter: data.fullDataFormatter,
666
+ fullChartParams: data.fullChartParams,
667
+ textReverseTransform: data.textReverseTransform,
668
+ })
669
+
670
+ renderYAxisLabel({
671
+ selection: data.axisSelection,
672
+ yLabelClassName,
673
+ fullParams: data.fullParams,
674
+ // axisLabelAlign: data.axisLabelAlign,
675
+ layout: data.layout,
676
+ fullDataFormatter: data.fullDataFormatter,
677
+ fullChartParams: data.fullChartParams,
678
+ textReverseTransform: data.textReverseTransform,
679
+ })
680
+ })
681
+
682
+ return () => {
683
+ destroy$.next(undefined)
684
+ }
685
685
  })