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