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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. package/LICENSE +200 -200
  2. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseBarStack.d.ts +6 -4
  3. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseBars.d.ts +6 -4
  4. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseBarsTriangle.d.ts +7 -4
  5. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseDots.d.ts +5 -3
  6. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseGroupAxis.d.ts +3 -3
  7. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseLineAreas.d.ts +6 -3
  8. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseLines.d.ts +6 -3
  9. package/dist/{src → orbcharts-plugins-basic/src}/base/BaseValueAxis.d.ts +3 -3
  10. package/dist/{src → orbcharts-plugins-basic/src}/grid/gridObservables.d.ts +4 -4
  11. package/dist/orbcharts-plugins-basic/src/index.d.ts +5 -0
  12. package/dist/orbcharts-plugins-basic/src/multiGrid/multiGridObservables.d.ts +8 -0
  13. package/dist/orbcharts-plugins-basic/src/series/plugins/PieEventTexts.d.ts +3 -0
  14. package/dist/orbcharts-plugins-basic/src/series/seriesObservables.d.ts +21 -0
  15. package/dist/{src → orbcharts-plugins-basic/src}/series/seriesUtils.d.ts +3 -3
  16. package/dist/orbcharts-plugins-basic.es.js +7696 -7529
  17. package/dist/orbcharts-plugins-basic.umd.js +8 -8
  18. package/dist/src/index.d.ts +1 -5
  19. package/package.json +42 -42
  20. package/src/base/BaseBarStack.ts +778 -881
  21. package/src/base/BaseBars.ts +764 -750
  22. package/src/base/BaseBarsTriangle.ts +672 -659
  23. package/src/base/BaseDots.ts +502 -639
  24. package/src/base/BaseGroupAxis.ts +496 -496
  25. package/src/base/BaseLegend.ts +641 -641
  26. package/src/base/BaseLineAreas.ts +625 -621
  27. package/src/base/BaseLines.ts +699 -692
  28. package/src/base/BaseValueAxis.ts +478 -479
  29. package/src/base/types.ts +2 -2
  30. package/src/grid/defaults.ts +121 -121
  31. package/src/grid/gridObservables.ts +247 -263
  32. package/src/grid/index.ts +15 -15
  33. package/src/grid/plugins/BarStack.ts +50 -37
  34. package/src/grid/plugins/Bars.ts +51 -37
  35. package/src/grid/plugins/BarsDiverging.ts +41 -39
  36. package/src/grid/plugins/BarsTriangle.ts +50 -34
  37. package/src/grid/plugins/Dots.ts +37 -35
  38. package/src/grid/plugins/GridLegend.ts +59 -59
  39. package/src/grid/plugins/GroupAux.ts +645 -646
  40. package/src/grid/plugins/GroupAxis.ts +42 -30
  41. package/src/grid/plugins/LineAreas.ts +39 -36
  42. package/src/grid/plugins/Lines.ts +38 -35
  43. package/src/grid/plugins/ScalingArea.ts +173 -174
  44. package/src/grid/plugins/ValueAxis.ts +43 -31
  45. package/src/grid/plugins/ValueStackAxis.ts +79 -70
  46. package/src/grid/types.ts +120 -120
  47. package/src/index.ts +9 -9
  48. package/src/multiGrid/defaults.ts +147 -147
  49. package/src/multiGrid/index.ts +11 -11
  50. package/src/multiGrid/multiGridObservables.ts +42 -289
  51. package/src/multiGrid/plugins/MultiBarStack.ts +74 -60
  52. package/src/multiGrid/plugins/MultiBars.ts +73 -59
  53. package/src/multiGrid/plugins/MultiBarsTriangle.ts +73 -58
  54. package/src/multiGrid/plugins/MultiDots.ts +60 -58
  55. package/src/multiGrid/plugins/MultiGridLegend.ts +89 -89
  56. package/src/multiGrid/plugins/MultiGroupAxis.ts +65 -53
  57. package/src/multiGrid/plugins/MultiLineAreas.ts +62 -59
  58. package/src/multiGrid/plugins/MultiLines.ts +61 -58
  59. package/src/multiGrid/plugins/MultiValueAxis.ts +65 -53
  60. package/src/multiGrid/plugins/OverlappingValueAxes.ts +169 -164
  61. package/src/multiGrid/types.ts +67 -67
  62. package/src/noneData/defaults.ts +64 -61
  63. package/src/noneData/index.ts +3 -3
  64. package/src/noneData/plugins/Container.ts +10 -10
  65. package/src/noneData/plugins/Tooltip.ts +310 -310
  66. package/src/noneData/types.ts +26 -26
  67. package/src/series/defaults.ts +109 -99
  68. package/src/series/index.ts +6 -6
  69. package/src/series/plugins/Bubbles.ts +571 -551
  70. package/src/series/plugins/Pie.ts +548 -600
  71. package/src/series/plugins/PieEventTexts.ts +258 -194
  72. package/src/series/plugins/PieLabels.ts +335 -288
  73. package/src/series/plugins/SeriesLegend.ts +59 -59
  74. package/src/series/seriesObservables.ts +145 -0
  75. package/src/series/seriesUtils.ts +50 -50
  76. package/src/series/types.ts +67 -67
  77. package/src/tree/defaults.ts +22 -22
  78. package/src/tree/index.ts +3 -3
  79. package/src/tree/plugins/TreeLegend.ts +59 -59
  80. package/src/tree/plugins/TreeMap.ts +305 -305
  81. package/src/tree/types.ts +23 -23
  82. package/src/utils/commonUtils.ts +21 -21
  83. package/src/utils/d3Graphics.ts +124 -124
  84. package/src/utils/d3Utils.ts +73 -73
  85. package/src/utils/observables.ts +14 -14
  86. package/src/utils/orbchartsUtils.ts +100 -100
  87. package/tsconfig.dev.json +16 -16
  88. package/tsconfig.json +16 -13
  89. package/tsconfig.prod.json +13 -13
  90. package/vite.config.js +49 -49
  91. package/dist/src/multiGrid/multiGridObservables.d.ts +0 -12
  92. package/dist/src/series/plugins/PieEventTexts.d.ts +0 -1
  93. /package/dist/{src → orbcharts-plugins-basic/src}/base/BaseGroupArea.d.ts +0 -0
  94. /package/dist/{src → orbcharts-plugins-basic/src}/base/BaseLegend.d.ts +0 -0
  95. /package/dist/{src → orbcharts-plugins-basic/src}/base/types.d.ts +0 -0
  96. /package/dist/{src → orbcharts-plugins-basic/src}/grid/defaults.d.ts +0 -0
  97. /package/dist/{src → orbcharts-plugins-basic/src}/grid/index.d.ts +0 -0
  98. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/BarStack.d.ts +0 -0
  99. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/Bars.d.ts +0 -0
  100. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/BarsDiverging.d.ts +0 -0
  101. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/BarsTriangle.d.ts +0 -0
  102. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/Dots.d.ts +0 -0
  103. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/GridLegend.d.ts +0 -0
  104. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/GroupAux.d.ts +0 -0
  105. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/GroupAxis.d.ts +0 -0
  106. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/LineAreas.d.ts +0 -0
  107. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/Lines.d.ts +0 -0
  108. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/Ranking.d.ts +0 -0
  109. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/RankingAxis.d.ts +0 -0
  110. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/ScalingArea.d.ts +0 -0
  111. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/ValueAxis.d.ts +0 -0
  112. /package/dist/{src → orbcharts-plugins-basic/src}/grid/plugins/ValueStackAxis.d.ts +0 -0
  113. /package/dist/{src → orbcharts-plugins-basic/src}/grid/types.d.ts +0 -0
  114. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/defaults.d.ts +0 -0
  115. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/index.d.ts +0 -0
  116. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiBarStack.d.ts +0 -0
  117. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiBars.d.ts +0 -0
  118. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiBarsTriangle.d.ts +0 -0
  119. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiDots.d.ts +0 -0
  120. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiGridLegend.d.ts +0 -0
  121. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiGroupAxis.d.ts +0 -0
  122. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiLineAreas.d.ts +0 -0
  123. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiLines.d.ts +0 -0
  124. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/MultiValueAxis.d.ts +0 -0
  125. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/plugins/OverlappingValueAxes.d.ts +0 -0
  126. /package/dist/{src → orbcharts-plugins-basic/src}/multiGrid/types.d.ts +0 -0
  127. /package/dist/{src → orbcharts-plugins-basic/src}/multiValue/index.d.ts +0 -0
  128. /package/dist/{src → orbcharts-plugins-basic/src}/multiValue/plugins/Scatter.d.ts +0 -0
  129. /package/dist/{src → orbcharts-plugins-basic/src}/multiValue/plugins/ScatterAxes.d.ts +0 -0
  130. /package/dist/{src → orbcharts-plugins-basic/src}/noneData/defaults.d.ts +0 -0
  131. /package/dist/{src → orbcharts-plugins-basic/src}/noneData/index.d.ts +0 -0
  132. /package/dist/{src → orbcharts-plugins-basic/src}/noneData/plugins/Container.d.ts +0 -0
  133. /package/dist/{src → orbcharts-plugins-basic/src}/noneData/plugins/Tooltip.d.ts +0 -0
  134. /package/dist/{src → orbcharts-plugins-basic/src}/noneData/types.d.ts +0 -0
  135. /package/dist/{src → orbcharts-plugins-basic/src}/relationship/index.d.ts +0 -0
  136. /package/dist/{src → orbcharts-plugins-basic/src}/relationship/plugins/Relationship.d.ts +0 -0
  137. /package/dist/{src → orbcharts-plugins-basic/src}/series/defaults.d.ts +0 -0
  138. /package/dist/{src → orbcharts-plugins-basic/src}/series/index.d.ts +0 -0
  139. /package/dist/{src → orbcharts-plugins-basic/src}/series/plugins/Bubbles.d.ts +0 -0
  140. /package/dist/{src → orbcharts-plugins-basic/src}/series/plugins/Pie.d.ts +0 -0
  141. /package/dist/{src → orbcharts-plugins-basic/src}/series/plugins/PieLabels.d.ts +0 -0
  142. /package/dist/{src → orbcharts-plugins-basic/src}/series/plugins/SeriesLegend.d.ts +0 -0
  143. /package/dist/{src → orbcharts-plugins-basic/src}/series/plugins/Waffle.d.ts +0 -0
  144. /package/dist/{src → orbcharts-plugins-basic/src}/series/types.d.ts +0 -0
  145. /package/dist/{src → orbcharts-plugins-basic/src}/tree/defaults.d.ts +0 -0
  146. /package/dist/{src → orbcharts-plugins-basic/src}/tree/index.d.ts +0 -0
  147. /package/dist/{src → orbcharts-plugins-basic/src}/tree/plugins/TreeLegend.d.ts +0 -0
  148. /package/dist/{src → orbcharts-plugins-basic/src}/tree/plugins/TreeMap.d.ts +0 -0
  149. /package/dist/{src → orbcharts-plugins-basic/src}/tree/types.d.ts +0 -0
  150. /package/dist/{src → orbcharts-plugins-basic/src}/utils/commonUtils.d.ts +0 -0
  151. /package/dist/{src → orbcharts-plugins-basic/src}/utils/d3Graphics.d.ts +0 -0
  152. /package/dist/{src → orbcharts-plugins-basic/src}/utils/d3Utils.d.ts +0 -0
  153. /package/dist/{src → orbcharts-plugins-basic/src}/utils/observables.d.ts +0 -0
  154. /package/dist/{src → orbcharts-plugins-basic/src}/utils/orbchartsUtils.d.ts +0 -0
  155. /package/dist/{vite.config.d.ts → orbcharts-plugins-basic/vite.config.d.ts} +0 -0
@@ -1,751 +1,765 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- map,
5
- switchMap,
6
- takeUntil,
7
- distinctUntilChanged,
8
- Observable,
9
- Subject } from 'rxjs'
10
- import type { BasePluginFn } from './types'
11
- import type {
12
- ComputedDatumGrid,
13
- ComputedDataGrid,
14
- ContainerPosition,
15
- EventGrid,
16
- ChartParams,
17
- Layout,
18
- TransformData } from '@orbcharts/core'
19
- import { getD3TransitionEase } from '../utils/d3Utils'
20
- import { getClassName, getUniID } from '../utils/orbchartsUtils'
21
- import { gridSelectionsObservable } from '../grid/gridObservables'
22
-
23
- export interface BaseBarsParams {
24
- // barType: BarType
25
- barWidth: number
26
- barPadding: number
27
- barGroupPadding: number // 群組和群組間的間隔
28
- barRadius: number | boolean
29
- }
30
-
31
- interface BaseBarsContext {
32
- selection: d3.Selection<any, unknown, any, unknown>
33
- computedData$: Observable<ComputedDataGrid>
34
- visibleComputedData$: Observable<ComputedDatumGrid[][]>
35
- existSeriesLabels$: Observable<string[]>
36
- SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
37
- GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
38
- fullParams$: Observable<BaseBarsParams>
39
- fullChartParams$: Observable<ChartParams>
40
- gridAxesTransform$: Observable<TransformData>
41
- gridGraphicTransform$: Observable<TransformData>
42
- gridGraphicReverseScale$: Observable<[number, number][]>
43
- gridAxesSize$: Observable<{
44
- width: number;
45
- height: number;
46
- }>
47
- gridHighlight$: Observable<ComputedDatumGrid[]>
48
- gridContainer$: Observable<ContainerPosition[]>
49
- isSeriesPositionSeprate$: Observable<boolean>
50
- event$: Subject<EventGrid>
51
- }
52
-
53
- interface RenderBarParams {
54
- graphicGSelection: d3.Selection<SVGGElement, string, any, any>
55
- rectClassName: string
56
- computedData: ComputedDatumGrid[][]
57
- zeroYArr: number[]
58
- groupLabels: string[]
59
- barScale: d3.ScalePoint<string>
60
- params: BaseBarsParams
61
- chartParams: ChartParams
62
- barWidth: number
63
- transformedBarRadius: [number, number][]
64
- delayGroup: number
65
- transitionItem: number
66
- isSeriesPositionSeprate: boolean
67
- }
68
-
69
- type ClipPathDatum = {
70
- id: string;
71
- // x: number;
72
- // y: number;
73
- width: number;
74
- height: number;
75
- }
76
-
77
- // const pluginName = 'Bars'
78
- // const rectClassName = getClassName(pluginName, 'rect')
79
- // group的delay在動畫中的佔比(剩餘部份的時間為圖形本身的動畫時間,因為delay時間和最後一個group的動畫時間加總為1)
80
- const groupDelayProportionOfDuration = 0.3
81
-
82
- function calcBarWidth ({ axisWidth, groupAmount, barAmountOfGroup, barPadding = 0, barGroupPadding = 0 }: {
83
- axisWidth: number
84
- groupAmount: number
85
- barAmountOfGroup: number
86
- barPadding: number
87
- barGroupPadding: number
88
- }) {
89
- const eachGroupWidth = axisWidth / (groupAmount - 1)
90
- const width = (eachGroupWidth - barGroupPadding) / barAmountOfGroup - barPadding
91
- return width > 1 ? width : 1
92
- }
93
-
94
- function makeBarScale (barWidth: number, seriesLabels: string[], params: BaseBarsParams) {
95
- const barHalfWidth = barWidth! / 2
96
- const barGroupWidth = barWidth * seriesLabels.length + params.barPadding! * seriesLabels.length
97
- return d3.scalePoint()
98
- .domain(seriesLabels)
99
- .range([-barGroupWidth / 2 + barHalfWidth, barGroupWidth / 2 - barHalfWidth])
100
- }
101
-
102
- function calcDelayGroup (barGroupAmount: number, totalDuration: number) {
103
- if (barGroupAmount <= 1) {
104
- // 一筆內計算會出錯所以不算
105
- return 0
106
- }
107
- return totalDuration / (barGroupAmount - 1) * groupDelayProportionOfDuration // 依group數量計算
108
- }
109
-
110
- function calctransitionItem (barGroupAmount: number, totalDuration: number) {
111
- if (barGroupAmount <= 1) {
112
- // 一筆內不會有delay
113
- return totalDuration
114
- }
115
- return totalDuration * (1 - groupDelayProportionOfDuration) // delay後剩餘的時間
116
- }
117
- // let _data: ComputedDatumGrid[][] = []
118
-
119
- function renderRectBars ({ graphicGSelection, rectClassName, computedData, zeroYArr, groupLabels, barScale, params, chartParams, barWidth, transformedBarRadius, delayGroup, transitionItem, isSeriesPositionSeprate }: RenderBarParams) {
120
-
121
- const barHalfWidth = barWidth! / 2
122
-
123
- graphicGSelection
124
- .each((seriesData, seriesIndex, g) => {
125
- d3.select(g[seriesIndex])
126
- .selectAll<SVGGElement, ComputedDatumGrid>(`rect.${rectClassName}`)
127
- .data(computedData[seriesIndex] ?? [], d => d.id)
128
- .join(
129
- enter => {
130
- // console.log('enter')
131
- return enter
132
- .append('rect')
133
- .classed(rectClassName, true)
134
- .attr('cursor', 'pointer')
135
- .attr('height', d => 0)
136
- },
137
- update => update,
138
- exit => exit.remove()
139
- )
140
- .attr('transform', (d, i) => `translate(${(d ? d.axisX : 0) - barHalfWidth}, ${0})`)
141
- .attr('fill', d => d.color)
142
- .attr('y', d => d.axisY < zeroYArr[seriesIndex] ? d.axisY : zeroYArr[seriesIndex])
143
- .attr('x', d => isSeriesPositionSeprate ? 0 : barScale(d.seriesLabel)!)
144
- .attr('width', barWidth!)
145
- .attr('rx', transformedBarRadius[seriesIndex][0] ?? 1)
146
- .attr('ry', transformedBarRadius[seriesIndex][1] ?? 1)
147
- .transition()
148
- .duration(transitionItem)
149
- .ease(getD3TransitionEase(chartParams.transitionEase))
150
- .delay((d, i) => d.groupIndex * delayGroup)
151
- .attr('height', d => Math.abs(d.axisYFromZero))
152
- })
153
-
154
-
155
- // graphicGSelection
156
- // .each((d, seriesIndex, g) => {
157
- // d3.select(g[seriesIndex])
158
- // .selectAll<SVGGElement, ComputedDatumGrid>(`g.${barClassName}`)
159
- // .data(computedData[seriesIndex], d => d.seriesIndex)
160
- // .join('g')
161
- // .classed(barClassName, true)
162
- // .attr('transform', (d, i) => `translate(${d ? d.axisX : 0}, ${0})`)
163
- // .each((datum, i, g) => {
164
- // d3.select(g[i])
165
- // .selectAll<SVGRectElement, ComputedDatumGrid>(`rect.${rectClassName}`)
166
- // .data([computedData[seriesIndex][i]], d => d.id)
167
- // .join(
168
- // enter => {
169
- // return enter
170
- // .append('rect')
171
- // .classed(rectClassName, true)
172
- // .attr('height', d => 0)
173
- // },
174
- // update => update,
175
- // exit => exit.remove()
176
- // )
177
- // .attr('cursor', 'pointer')
178
- // .attr('fill', d => d.color)
179
- // .attr('y', d => d.axisY < zeroYArr[seriesIndex] ? d.axisY : zeroYArr[seriesIndex])
180
- // .attr('x', d => isSeriesPositionSeprate ? 0 : barScale(d.seriesLabel)!)
181
- // .attr('width', barWidth!)
182
- // .attr('transform', `translate(${-barHalfWidth}, 0)`)
183
- // // .attr('rx', params.barRadius == true ? barHalfWidth
184
- // // : params.barRadius == false ? 0
185
- // // : typeof params.barRadius == 'number' ? params.barRadius
186
- // // : 0)
187
- // .attr('rx', transformedBarRadius[0])
188
- // .attr('ry', transformedBarRadius[1])
189
- // .transition()
190
- // .duration(transitionItem)
191
- // .ease(getD3TransitionEase(chartParams.transitionEase))
192
- // .delay((d, i) => d.groupIndex * delayGroup)
193
- // .attr('height', d => Math.abs(d.axisYFromZero))
194
- // })
195
- // })
196
-
197
- const graphicBarSelection: d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown> = graphicGSelection.selectAll(`rect.${rectClassName}`)
198
-
199
- return graphicBarSelection
200
- }
201
-
202
- function renderClipPath ({ defsSelection, clipPathData }: {
203
- defsSelection: d3.Selection<SVGDefsElement, any, any, any>
204
- clipPathData: ClipPathDatum[]
205
- }) {
206
- const clipPath = defsSelection
207
- .selectAll<SVGClipPathElement, Layout>('clipPath')
208
- .data(clipPathData)
209
- .join(
210
- enter => {
211
- return enter
212
- .append('clipPath')
213
- },
214
- update => update,
215
- exit => exit.remove()
216
- )
217
- .attr('id', d => d.id)
218
- .each((d, i, g) => {
219
- const rect = d3.select(g[i])
220
- .selectAll<SVGRectElement, typeof d>('rect')
221
- .data([d])
222
- .join(
223
- enter => {
224
- return enter
225
- .append('rect')
226
- },
227
- update => update,
228
- exit => exit.remove()
229
- )
230
- .attr('x', 0)
231
- .attr('y', 0)
232
- .attr('width', _d => _d.width)
233
- .attr('height', _d => _d.height)
234
- })
235
- }
236
-
237
- function highlight ({ selection, ids, fullChartParams }: {
238
- selection: d3.Selection<any, ComputedDatumGrid, any, any>
239
- ids: string[]
240
- fullChartParams: ChartParams
241
- }) {
242
- selection.interrupt('highlight')
243
-
244
- if (!ids.length) {
245
- // remove highlight
246
- selection
247
- .transition('highlight')
248
- .duration(200)
249
- .style('opacity', 1)
250
- return
251
- }
252
-
253
- selection
254
- .each((d, i, n) => {
255
- if (ids.includes(d.id)) {
256
- d3.select(n[i])
257
- .style('opacity', 1)
258
- } else {
259
- d3.select(n[i])
260
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
261
- }
262
- })
263
- }
264
-
265
-
266
- export const createBaseBars: BasePluginFn<BaseBarsContext> = (pluginName: string, {
267
- selection,
268
- computedData$,
269
- visibleComputedData$,
270
- existSeriesLabels$,
271
- SeriesDataMap$,
272
- GroupDataMap$,
273
- fullParams$,
274
- fullChartParams$,
275
- gridAxesTransform$,
276
- gridGraphicTransform$,
277
- gridGraphicReverseScale$,
278
- gridAxesSize$,
279
- gridHighlight$,
280
- gridContainer$,
281
- isSeriesPositionSeprate$,
282
- event$
283
- }) => {
284
-
285
- const destroy$ = new Subject()
286
-
287
- const clipPathID = getUniID(pluginName, 'clipPath-box')
288
- const rectClassName = getClassName(pluginName, 'rect')
289
-
290
- // const seriesSelection$ = computedData$.pipe(
291
- // takeUntil(destroy$),
292
- // distinctUntilChanged((a, b) => {
293
- // // 只有當series的數量改變時,才重新計算
294
- // return a.length === b.length
295
- // }),
296
- // map((computedData, i) => {
297
- // return selection
298
- // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${seriesClassName}`)
299
- // .data(computedData, d => d[0] ? d[0].seriesIndex : i)
300
- // .join(
301
- // enter => {
302
- // return enter
303
- // .append('g')
304
- // .classed(seriesClassName, true)
305
- // .each((d, i, g) => {
306
- // const axesSelection = d3.select(g[i])
307
- // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${axesClassName}`)
308
- // .data([i])
309
- // .join(
310
- // enter => {
311
- // return enter
312
- // .append('g')
313
- // .classed(axesClassName, true)
314
- // .attr('clip-path', `url(#${clipPathID})`)
315
- // .each((d, i, g) => {
316
- // const defsSelection = d3.select(g[i])
317
- // .selectAll<SVGDefsElement, any>('defs')
318
- // .data([i])
319
- // .join('defs')
320
-
321
- // const graphicGSelection = d3.select(g[i])
322
- // .selectAll<SVGGElement, any>('g')
323
- // .data([i])
324
- // .join('g')
325
- // .classed(graphicClassName, true)
326
- // })
327
- // },
328
- // update => update,
329
- // exit => exit.remove()
330
- // )
331
- // })
332
- // },
333
- // update => update,
334
- // exit => exit.remove()
335
- // )
336
- // })
337
- // )
338
-
339
- // combineLatest({
340
- // seriesSelection: seriesSelection$,
341
- // gridContainer: gridContainer$
342
- // }).pipe(
343
- // takeUntil(destroy$),
344
- // switchMap(async d => d)
345
- // ).subscribe(data => {
346
- // data.seriesSelection
347
- // .transition()
348
- // .attr('transform', (d, i) => {
349
- // const translate = data.gridContainer[i].translate
350
- // const scale = data.gridContainer[i].scale
351
- // return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
352
- // })
353
- // })
354
-
355
-
356
- // const axesSelection$ = combineLatest({
357
- // seriesSelection: seriesSelection$,
358
- // gridAxesTransform: gridAxesTransform$
359
- // }).pipe(
360
- // takeUntil(destroy$),
361
- // switchMap(async d => d),
362
- // map(data => {
363
- // return data.seriesSelection
364
- // .select<SVGGElement>(`g.${axesClassName}`)
365
- // .style('transform', data.gridAxesTransform.value)
366
- // })
367
- // )
368
- // const defsSelection$ = axesSelection$.pipe(
369
- // takeUntil(destroy$),
370
- // map(axesSelection => {
371
- // return axesSelection.select<SVGDefsElement>('defs')
372
- // })
373
- // )
374
- // const graphicGSelection$ = combineLatest({
375
- // axesSelection: axesSelection$,
376
- // gridGraphicTransform: gridGraphicTransform$
377
- // }).pipe(
378
- // takeUntil(destroy$),
379
- // switchMap(async d => d),
380
- // map(data => {
381
- // const graphicGSelection = data.axesSelection
382
- // .select<SVGGElement>(`g.${graphicClassName}`)
383
- // graphicGSelection
384
- // .transition()
385
- // .duration(50)
386
- // .style('transform', data.gridGraphicTransform.value)
387
- // return graphicGSelection
388
- // })
389
- // )
390
-
391
- const {
392
- seriesSelection$,
393
- axesSelection$,
394
- defsSelection$,
395
- graphicGSelection$
396
- } = gridSelectionsObservable({
397
- selection,
398
- pluginName,
399
- clipPathID,
400
- existSeriesLabels$,
401
- gridContainer$,
402
- gridAxesTransform$,
403
- gridGraphicTransform$
404
- })
405
-
406
- const zeroYArr$ = visibleComputedData$.pipe(
407
- takeUntil(destroy$),
408
- map(data => {
409
- return data.map(d => {
410
- return d[0] ? d[0].axisY - d[0].axisYFromZero : 0
411
- })
412
- }),
413
- distinctUntilChanged()
414
- )
415
-
416
- const barWidth$ = combineLatest({
417
- computedData: computedData$,
418
- visibleComputedData: visibleComputedData$,
419
- params: fullParams$,
420
- gridAxesSize: gridAxesSize$,
421
- isSeriesPositionSeprate: isSeriesPositionSeprate$
422
- }).pipe(
423
- takeUntil(destroy$),
424
- switchMap(async d => d),
425
- map(data => {
426
- if (data.params.barWidth) {
427
- return data.params.barWidth
428
- } else if (data.isSeriesPositionSeprate) {
429
- return calcBarWidth({
430
- axisWidth: data.gridAxesSize.width,
431
- groupAmount: data.computedData[0] ? data.computedData[0].length : 0,
432
- barAmountOfGroup: 1,
433
- barPadding: data.params.barPadding,
434
- barGroupPadding: data.params.barGroupPadding
435
- })
436
- } else {
437
- return calcBarWidth({
438
- axisWidth: data.gridAxesSize.width,
439
- groupAmount: data.computedData[0] ? data.computedData[0].length : 0,
440
- barAmountOfGroup: data.visibleComputedData.length,
441
- barPadding: data.params.barPadding,
442
- barGroupPadding: data.params.barGroupPadding
443
- })
444
- }
445
- }),
446
- distinctUntilChanged()
447
- )
448
-
449
- // 圓角的值 [rx, ry]
450
- const transformedBarRadius$: Observable<[number, number][]> = combineLatest({
451
- computedData: computedData$,
452
- // gridGraphicTransform: gridGraphicTransform$,
453
- barWidth: barWidth$,
454
- params: fullParams$,
455
- // gridContainer: gridContainer$,
456
- // gridAxesTransform: gridAxesTransform$
457
- gridGraphicReverseScale: gridGraphicReverseScale$
458
- }).pipe(
459
- takeUntil(destroy$),
460
- switchMap(async data => data),
461
- map(data => {
462
- const barHalfWidth = data.barWidth! / 2
463
- const radius = data.params.barRadius === true ? barHalfWidth
464
- : data.params.barRadius === false ? 0
465
- : typeof data.params.barRadius == 'number' ? data.params.barRadius
466
- : 0
467
-
468
- return data.computedData.map((series, seriesIndex) => {
469
- const gridGraphicReverseScale = data.gridGraphicReverseScale[seriesIndex] ?? data.gridGraphicReverseScale[0]
470
-
471
- let transformedRx = radius * gridGraphicReverseScale[0]
472
- let transformedRy = radius * gridGraphicReverseScale[1]
473
- // if (radius == 0) {
474
- // transformedRx = 0
475
- // transformedRy = 0
476
- // } else if (data.gridAxesTransform.rotate == 0) {
477
- // transformedRx = radius
478
- // // 抵消外層scale的變型
479
- // / data.gridGraphicTransform.scale[0] / data.gridContainer[0].scale[0]
480
- // transformedRy = radius
481
- // // 抵消外層scale的變型
482
- // / data.gridGraphicTransform.scale[1] / data.gridContainer[0].scale[1]
483
- // } else if (data.gridAxesTransform.rotate != 0) {
484
- // transformedRx = radius
485
- // // 抵消外層scale的變型,由於有90度的旋轉,所以外層 (container) x和y的scale要互換
486
- // / data.gridGraphicTransform.scale[0] / data.gridContainer[0].scale[1]
487
- // transformedRy = radius
488
- // // 抵消外層scale的變型,由於有90度的旋轉,所以外層 (container) x和y的scale要互換
489
- // / data.gridGraphicTransform.scale[1] / data.gridContainer[0].scale[0]
490
- // }
491
-
492
- // 如果計算出來的x圓角值大於寬度一半則進行修正
493
- if (transformedRx > barHalfWidth) {
494
- const rScale = barHalfWidth / transformedRx
495
- transformedRx = transformedRx * rScale
496
- transformedRy = transformedRy * rScale
497
- }
498
-
499
- return [transformedRx, transformedRy]
500
- })
501
- })
502
- )
503
-
504
-
505
- const seriesLabels$ = visibleComputedData$.pipe(
506
- takeUntil(destroy$),
507
- map(data => {
508
- const SeriesLabelSet: Set<string> = new Set()
509
- data.forEach(d => {
510
- d.forEach(_d => {
511
- SeriesLabelSet.add(_d.seriesLabel)
512
- })
513
- })
514
- return Array.from(SeriesLabelSet)
515
- })
516
- )
517
-
518
- const groupLabels$ = visibleComputedData$.pipe(
519
- takeUntil(destroy$),
520
- map(data => {
521
- const GroupLabelSet: Set<string> = new Set()
522
- data.forEach(d => {
523
- d.forEach(_d => {
524
- GroupLabelSet.add(_d.groupLabel)
525
- })
526
- })
527
- return Array.from(GroupLabelSet)
528
- })
529
- )
530
-
531
- const barScale$ = combineLatest({
532
- seriesLabels: seriesLabels$,
533
- barWidth: barWidth$,
534
- params: fullParams$,
535
- }).pipe(
536
- takeUntil(destroy$),
537
- switchMap(async d => d),
538
- map(data => {
539
- return makeBarScale(data.barWidth, data.seriesLabels, data.params)
540
- })
541
- )
542
-
543
- const transitionDuration$ = fullChartParams$.pipe(
544
- takeUntil(destroy$),
545
- map(d => d.transitionDuration),
546
- distinctUntilChanged()
547
- )
548
-
549
- const delayGroup$ = new Observable<number>(subscriber => {
550
- combineLatest({
551
- groupLabels: groupLabels$,
552
- transitionDuration: transitionDuration$,
553
- }).pipe(
554
- switchMap(async d => d)
555
- ).subscribe(data => {
556
- const delay = calcDelayGroup(data.groupLabels.length, data.transitionDuration)
557
- subscriber.next(delay)
558
- })
559
- }).pipe(
560
- takeUntil(destroy$),
561
- distinctUntilChanged()
562
- )
563
-
564
- const transitionItem$ = new Observable<number>(subscriber => {
565
- combineLatest({
566
- groupLabels: groupLabels$,
567
- transitionDuration: transitionDuration$
568
- }).pipe(
569
- switchMap(async d => d)
570
- ).subscribe(data => {
571
- const transition = calctransitionItem(data.groupLabels.length, data.transitionDuration)
572
- subscriber.next(transition)
573
- })
574
- }).pipe(
575
- takeUntil(destroy$),
576
- distinctUntilChanged()
577
- )
578
-
579
- combineLatest({
580
- defsSelection: defsSelection$,
581
- gridAxesSize: gridAxesSize$,
582
- }).pipe(
583
- takeUntil(destroy$),
584
- switchMap(async d => d)
585
- ).subscribe(data => {
586
- const clipPathData = [{
587
- id: clipPathID,
588
- width: data.gridAxesSize.width,
589
- height: data.gridAxesSize.height
590
- }]
591
- renderClipPath({
592
- defsSelection: data.defsSelection,
593
- clipPathData
594
- })
595
- })
596
-
597
-
598
- const highlightTarget$ = fullChartParams$.pipe(
599
- takeUntil(destroy$),
600
- map(d => d.highlightTarget),
601
- distinctUntilChanged()
602
- )
603
-
604
- const barSelection$ = new Subject<d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown>>()
605
-
606
- combineLatest({
607
- graphicGSelection: graphicGSelection$,
608
- computedData: computedData$,
609
- // barData$: barData$,
610
- zeroYArr: zeroYArr$,
611
- groupLabels: groupLabels$,
612
- barScale: barScale$,
613
- params: fullParams$,
614
- chartParams: fullChartParams$,
615
- highlightTarget: highlightTarget$,
616
- barWidth: barWidth$,
617
- transformedBarRadius: transformedBarRadius$,
618
- delayGroup: delayGroup$,
619
- transitionItem: transitionItem$,
620
- SeriesDataMap: SeriesDataMap$,
621
- GroupDataMap: GroupDataMap$,
622
- isSeriesPositionSeprate: isSeriesPositionSeprate$
623
- }).pipe(
624
- takeUntil(destroy$),
625
- switchMap(async (d) => d),
626
- ).subscribe(data => {
627
-
628
- const barSelection = renderRectBars({
629
- graphicGSelection: data.graphicGSelection,
630
- rectClassName,
631
- computedData: data.computedData,
632
- zeroYArr: data.zeroYArr,
633
- groupLabels: data.groupLabels,
634
- barScale: data.barScale,
635
- params: data.params,
636
- chartParams: data.chartParams,
637
- barWidth: data.barWidth,
638
- transformedBarRadius: data.transformedBarRadius,
639
- delayGroup: data.delayGroup,
640
- transitionItem: data.transitionItem,
641
- isSeriesPositionSeprate: data.isSeriesPositionSeprate
642
- })
643
-
644
- barSelection!
645
- .on('mouseover', (event, datum) => {
646
- event.stopPropagation()
647
-
648
- event$.next({
649
- type: 'grid',
650
- eventName: 'mouseover',
651
- pluginName,
652
- highlightTarget: data.highlightTarget,
653
- datum,
654
- gridIndex: datum.gridIndex,
655
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
656
- seriesIndex: datum.seriesIndex,
657
- seriesLabel: datum.seriesLabel,
658
- groups: data.GroupDataMap.get(datum.groupLabel)!,
659
- groupIndex: datum.groupIndex,
660
- groupLabel: datum.groupLabel,
661
- event,
662
- data: data.computedData
663
- })
664
- })
665
- .on('mousemove', (event, datum) => {
666
- event.stopPropagation()
667
-
668
- event$.next({
669
- type: 'grid',
670
- eventName: 'mousemove',
671
- pluginName,
672
- highlightTarget: data.highlightTarget,
673
- datum,
674
- gridIndex: datum.gridIndex,
675
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
676
- seriesIndex: datum.seriesIndex,
677
- seriesLabel: datum.seriesLabel,
678
- groups: data.GroupDataMap.get(datum.groupLabel)!,
679
- groupIndex: datum.groupIndex,
680
- groupLabel: datum.groupLabel,
681
- event,
682
- data: data.computedData
683
- })
684
- })
685
- .on('mouseout', (event, datum) => {
686
- event.stopPropagation()
687
-
688
- event$.next({
689
- type: 'grid',
690
- eventName: 'mouseout',
691
- pluginName,
692
- highlightTarget: data.highlightTarget,
693
- datum,
694
- gridIndex: datum.gridIndex,
695
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
696
- seriesIndex: datum.seriesIndex,
697
- seriesLabel: datum.seriesLabel,
698
- groups: data.GroupDataMap.get(datum.groupLabel)!,
699
- groupIndex: datum.groupIndex,
700
- groupLabel: datum.groupLabel,
701
- event,
702
- data: data.computedData
703
- })
704
- })
705
- .on('click', (event, datum) => {
706
- event.stopPropagation()
707
-
708
- event$.next({
709
- type: 'grid',
710
- eventName: 'click',
711
- pluginName,
712
- highlightTarget: data.highlightTarget,
713
- datum,
714
- gridIndex: datum.gridIndex,
715
- series: data.SeriesDataMap.get(datum.seriesLabel)!,
716
- seriesIndex: datum.seriesIndex,
717
- seriesLabel: datum.seriesLabel,
718
- groups: data.GroupDataMap.get(datum.groupLabel)!,
719
- groupIndex: datum.groupIndex,
720
- groupLabel: datum.groupLabel,
721
- event,
722
- data: data.computedData
723
- })
724
- })
725
-
726
- barSelection$.next(barSelection!)
727
- })
728
-
729
-
730
- combineLatest({
731
- barSelection: barSelection$,
732
- highlight: gridHighlight$.pipe(
733
- map(data => data.map(d => d.id))
734
- ),
735
- fullChartParams: fullChartParams$
736
- }).pipe(
737
- takeUntil(destroy$),
738
- switchMap(async d => d)
739
- ).subscribe(data => {
740
- highlight({
741
- selection: data.barSelection,
742
- ids: data.highlight,
743
- fullChartParams: data.fullChartParams
744
- })
745
- })
746
-
747
-
748
- return () => {
749
- destroy$.next(undefined)
750
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ switchMap,
6
+ takeUntil,
7
+ distinctUntilChanged,
8
+ shareReplay,
9
+ Observable,
10
+ Subject } from 'rxjs'
11
+ import type { BasePluginFn } from './types'
12
+ import type {
13
+ ComputedDatumGrid,
14
+ ComputedDataGrid,
15
+ ComputedLayoutDataGrid,
16
+ DataFormatterTypeMap,
17
+ GridContainerPosition,
18
+ EventGrid,
19
+ ChartParams,
20
+ Layout,
21
+ TransformData } from '@orbcharts/core'
22
+ import { getD3TransitionEase } from '../utils/d3Utils'
23
+ import { getClassName, getUniID } from '../utils/orbchartsUtils'
24
+ import { gridSelectionsObservable } from '../grid/gridObservables'
25
+
26
+ export interface BaseBarsParams {
27
+ // barType: BarType
28
+ barWidth: number
29
+ barPadding: number
30
+ barGroupPadding: number // 群組和群組間的間隔
31
+ barRadius: number | boolean
32
+ }
33
+
34
+ interface BaseBarsContext {
35
+ selection: d3.Selection<any, unknown, any, unknown>
36
+ computedData$: Observable<ComputedDataGrid>
37
+ computedLayoutData$: Observable<ComputedLayoutDataGrid>
38
+ visibleComputedData$: Observable<ComputedDatumGrid[][]>
39
+ visibleComputedLayoutData$: Observable<ComputedLayoutDataGrid>
40
+ seriesLabels$: Observable<string[]>
41
+ SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
42
+ GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
43
+ fullParams$: Observable<BaseBarsParams>
44
+ fullChartParams$: Observable<ChartParams>
45
+ gridAxesTransform$: Observable<TransformData>
46
+ gridGraphicTransform$: Observable<TransformData>
47
+ gridGraphicReverseScale$: Observable<[number, number][]>
48
+ gridAxesSize$: Observable<{
49
+ width: number;
50
+ height: number;
51
+ }>
52
+ gridHighlight$: Observable<ComputedDatumGrid[]>
53
+ gridContainerPosition$: Observable<GridContainerPosition[]>
54
+ isSeriesSeprate$: Observable<boolean>
55
+ event$: Subject<EventGrid>
56
+ }
57
+
58
+ interface RenderBarParams {
59
+ graphicGSelection: d3.Selection<SVGGElement, string, any, any>
60
+ rectClassName: string
61
+ visibleComputedLayoutData: ComputedLayoutDataGrid
62
+ zeroYArr: number[]
63
+ groupLabels: string[]
64
+ barScale: d3.ScalePoint<string>
65
+ params: BaseBarsParams
66
+ chartParams: ChartParams
67
+ barWidth: number
68
+ transformedBarRadius: [number, number][]
69
+ delayGroup: number
70
+ transitionItem: number
71
+ isSeriesSeprate: boolean
72
+ }
73
+
74
+ type ClipPathDatum = {
75
+ id: string;
76
+ // x: number;
77
+ // y: number;
78
+ width: number;
79
+ height: number;
80
+ }
81
+
82
+ // const pluginName = 'Bars'
83
+ // const rectClassName = getClassName(pluginName, 'rect')
84
+ // group的delay在動畫中的佔比(剩餘部份的時間為圖形本身的動畫時間,因為delay時間和最後一個group的動畫時間加總為1)
85
+ const groupDelayProportionOfDuration = 0.3
86
+
87
+ function calcBarWidth ({ axisWidth, groupAmount, barAmountOfGroup, barPadding = 0, barGroupPadding = 0 }: {
88
+ axisWidth: number
89
+ groupAmount: number
90
+ barAmountOfGroup: number
91
+ barPadding: number
92
+ barGroupPadding: number
93
+ }) {
94
+ const eachGroupWidth = groupAmount > 1
95
+ ? axisWidth / (groupAmount - 1) // -1是因為要扣掉兩側的padding
96
+ : axisWidth
97
+ const width = (eachGroupWidth - barGroupPadding) / barAmountOfGroup - barPadding
98
+ return width > 1 ? width : 1
99
+ }
100
+
101
+ function makeBarScale (barWidth: number, seriesLabels: string[], params: BaseBarsParams) {
102
+ const barHalfWidth = barWidth! / 2
103
+ const barGroupWidth = barWidth * seriesLabels.length + params.barPadding! * seriesLabels.length
104
+ return d3.scalePoint()
105
+ .domain(seriesLabels)
106
+ .range([-barGroupWidth / 2 + barHalfWidth, barGroupWidth / 2 - barHalfWidth])
107
+ }
108
+
109
+ function calcDelayGroup (barGroupAmount: number, totalDuration: number) {
110
+ if (barGroupAmount <= 1) {
111
+ // 一筆內計算會出錯所以不算
112
+ return 0
113
+ }
114
+ return totalDuration / (barGroupAmount - 1) * groupDelayProportionOfDuration // 依group數量計算
115
+ }
116
+
117
+ function calctransitionItem (barGroupAmount: number, totalDuration: number) {
118
+ if (barGroupAmount <= 1) {
119
+ // 一筆內不會有delay
120
+ return totalDuration
121
+ }
122
+ return totalDuration * (1 - groupDelayProportionOfDuration) // delay後剩餘的時間
123
+ }
124
+ // let _data: ComputedDatumGrid[][] = []
125
+
126
+ function renderRectBars ({ graphicGSelection, rectClassName, visibleComputedLayoutData, zeroYArr, groupLabels, barScale, params, chartParams, barWidth, transformedBarRadius, delayGroup, transitionItem, isSeriesSeprate }: RenderBarParams) {
127
+
128
+ const barHalfWidth = barWidth! / 2
129
+
130
+ graphicGSelection
131
+ .each((seriesData, seriesIndex, g) => {
132
+ d3.select(g[seriesIndex])
133
+ .selectAll<SVGGElement, ComputedDatumGrid>(`rect.${rectClassName}`)
134
+ .data(visibleComputedLayoutData[seriesIndex] ?? [], d => d.id)
135
+ .join(
136
+ enter => {
137
+ // console.log('enter')
138
+ return enter
139
+ .append('rect')
140
+ .classed(rectClassName, true)
141
+ .attr('cursor', 'pointer')
142
+ .attr('height', d => 0)
143
+ },
144
+ update => update,
145
+ exit => exit.remove()
146
+ )
147
+ .attr('transform', (d, i) => `translate(${(d ? d.axisX : 0) - barHalfWidth}, ${0})`)
148
+ .attr('fill', d => d.color)
149
+ .attr('y', d => d.axisY < zeroYArr[seriesIndex] ? d.axisY : zeroYArr[seriesIndex])
150
+ .attr('x', d => isSeriesSeprate ? 0 : barScale(d.seriesLabel)!)
151
+ .attr('width', barWidth!)
152
+ .attr('rx', transformedBarRadius[seriesIndex][0] ?? 1)
153
+ .attr('ry', transformedBarRadius[seriesIndex][1] ?? 1)
154
+ .transition()
155
+ .duration(transitionItem)
156
+ .ease(getD3TransitionEase(chartParams.transitionEase))
157
+ .delay((d, i) => d.groupIndex * delayGroup)
158
+ .attr('height', d => Math.abs(d.axisYFromZero))
159
+ })
160
+
161
+
162
+ // graphicGSelection
163
+ // .each((d, seriesIndex, g) => {
164
+ // d3.select(g[seriesIndex])
165
+ // .selectAll<SVGGElement, ComputedDatumGrid>(`g.${barClassName}`)
166
+ // .data(computedData[seriesIndex], d => d.seriesIndex)
167
+ // .join('g')
168
+ // .classed(barClassName, true)
169
+ // .attr('transform', (d, i) => `translate(${d ? d.axisX : 0}, ${0})`)
170
+ // .each((datum, i, g) => {
171
+ // d3.select(g[i])
172
+ // .selectAll<SVGRectElement, ComputedDatumGrid>(`rect.${rectClassName}`)
173
+ // .data([computedData[seriesIndex][i]], d => d.id)
174
+ // .join(
175
+ // enter => {
176
+ // return enter
177
+ // .append('rect')
178
+ // .classed(rectClassName, true)
179
+ // .attr('height', d => 0)
180
+ // },
181
+ // update => update,
182
+ // exit => exit.remove()
183
+ // )
184
+ // .attr('cursor', 'pointer')
185
+ // .attr('fill', d => d.color)
186
+ // .attr('y', d => d.axisY < zeroYArr[seriesIndex] ? d.axisY : zeroYArr[seriesIndex])
187
+ // .attr('x', d => isSeriesSeprate ? 0 : barScale(d.seriesLabel)!)
188
+ // .attr('width', barWidth!)
189
+ // .attr('transform', `translate(${-barHalfWidth}, 0)`)
190
+ // // .attr('rx', params.barRadius == true ? barHalfWidth
191
+ // // : params.barRadius == false ? 0
192
+ // // : typeof params.barRadius == 'number' ? params.barRadius
193
+ // // : 0)
194
+ // .attr('rx', transformedBarRadius[0])
195
+ // .attr('ry', transformedBarRadius[1])
196
+ // .transition()
197
+ // .duration(transitionItem)
198
+ // .ease(getD3TransitionEase(chartParams.transitionEase))
199
+ // .delay((d, i) => d.groupIndex * delayGroup)
200
+ // .attr('height', d => Math.abs(d.axisYFromZero))
201
+ // })
202
+ // })
203
+
204
+ const graphicBarSelection: d3.Selection<SVGRectElement, ComputedDatumGrid, SVGGElement, unknown> = graphicGSelection.selectAll(`rect.${rectClassName}`)
205
+
206
+ return graphicBarSelection
207
+ }
208
+
209
+ function renderClipPath ({ defsSelection, clipPathData }: {
210
+ defsSelection: d3.Selection<SVGDefsElement, any, any, any>
211
+ clipPathData: ClipPathDatum[]
212
+ }) {
213
+ const clipPath = defsSelection
214
+ .selectAll<SVGClipPathElement, Layout>('clipPath')
215
+ .data(clipPathData)
216
+ .join(
217
+ enter => {
218
+ return enter
219
+ .append('clipPath')
220
+ },
221
+ update => update,
222
+ exit => exit.remove()
223
+ )
224
+ .attr('id', d => d.id)
225
+ .each((d, i, g) => {
226
+ const rect = d3.select(g[i])
227
+ .selectAll<SVGRectElement, typeof d>('rect')
228
+ .data([d])
229
+ .join(
230
+ enter => {
231
+ return enter
232
+ .append('rect')
233
+ },
234
+ update => update,
235
+ exit => exit.remove()
236
+ )
237
+ .attr('x', 0)
238
+ .attr('y', 0)
239
+ .attr('width', _d => _d.width)
240
+ .attr('height', _d => _d.height)
241
+ })
242
+ }
243
+
244
+ function highlight ({ selection, ids, fullChartParams }: {
245
+ selection: d3.Selection<any, ComputedDatumGrid, any, any>
246
+ ids: string[]
247
+ fullChartParams: ChartParams
248
+ }) {
249
+ selection.interrupt('highlight')
250
+
251
+ if (!ids.length) {
252
+ // remove highlight
253
+ selection
254
+ .transition('highlight')
255
+ .duration(200)
256
+ .style('opacity', 1)
257
+ return
258
+ }
259
+
260
+ selection
261
+ .each((d, i, n) => {
262
+ if (ids.includes(d.id)) {
263
+ d3.select(n[i])
264
+ .style('opacity', 1)
265
+ } else {
266
+ d3.select(n[i])
267
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
268
+ }
269
+ })
270
+ }
271
+
272
+
273
+ export const createBaseBars: BasePluginFn<BaseBarsContext> = (pluginName: string, {
274
+ selection,
275
+ computedData$,
276
+ computedLayoutData$,
277
+ visibleComputedData$,
278
+ visibleComputedLayoutData$,
279
+ seriesLabels$,
280
+ SeriesDataMap$,
281
+ GroupDataMap$,
282
+ fullParams$,
283
+ fullChartParams$,
284
+ gridAxesTransform$,
285
+ gridGraphicTransform$,
286
+ gridGraphicReverseScale$,
287
+ gridAxesSize$,
288
+ gridHighlight$,
289
+ gridContainerPosition$,
290
+ isSeriesSeprate$,
291
+ event$
292
+ }) => {
293
+
294
+ const destroy$ = new Subject()
295
+
296
+ const clipPathID = getUniID(pluginName, 'clipPath-box')
297
+ const rectClassName = getClassName(pluginName, 'rect')
298
+
299
+ // const seriesSelection$ = computedData$.pipe(
300
+ // takeUntil(destroy$),
301
+ // distinctUntilChanged((a, b) => {
302
+ // // 只有當series的數量改變時,才重新計算
303
+ // return a.length === b.length
304
+ // }),
305
+ // map((computedData, i) => {
306
+ // return selection
307
+ // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${seriesClassName}`)
308
+ // .data(computedData, d => d[0] ? d[0].seriesIndex : i)
309
+ // .join(
310
+ // enter => {
311
+ // return enter
312
+ // .append('g')
313
+ // .classed(seriesClassName, true)
314
+ // .each((d, i, g) => {
315
+ // const axesSelection = d3.select(g[i])
316
+ // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${axesClassName}`)
317
+ // .data([i])
318
+ // .join(
319
+ // enter => {
320
+ // return enter
321
+ // .append('g')
322
+ // .classed(axesClassName, true)
323
+ // .attr('clip-path', `url(#${clipPathID})`)
324
+ // .each((d, i, g) => {
325
+ // const defsSelection = d3.select(g[i])
326
+ // .selectAll<SVGDefsElement, any>('defs')
327
+ // .data([i])
328
+ // .join('defs')
329
+
330
+ // const graphicGSelection = d3.select(g[i])
331
+ // .selectAll<SVGGElement, any>('g')
332
+ // .data([i])
333
+ // .join('g')
334
+ // .classed(graphicClassName, true)
335
+ // })
336
+ // },
337
+ // update => update,
338
+ // exit => exit.remove()
339
+ // )
340
+ // })
341
+ // },
342
+ // update => update,
343
+ // exit => exit.remove()
344
+ // )
345
+ // })
346
+ // )
347
+
348
+ // combineLatest({
349
+ // seriesSelection: seriesSelection$,
350
+ // gridContainerPosition: gridContainerPosition$
351
+ // }).pipe(
352
+ // takeUntil(destroy$),
353
+ // switchMap(async d => d)
354
+ // ).subscribe(data => {
355
+ // data.seriesSelection
356
+ // .transition()
357
+ // .attr('transform', (d, i) => {
358
+ // const translate = data.gridContainerPosition[i].translate
359
+ // const scale = data.gridContainerPosition[i].scale
360
+ // return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
361
+ // })
362
+ // })
363
+
364
+
365
+ // const axesSelection$ = combineLatest({
366
+ // seriesSelection: seriesSelection$,
367
+ // gridAxesTransform: gridAxesTransform$
368
+ // }).pipe(
369
+ // takeUntil(destroy$),
370
+ // switchMap(async d => d),
371
+ // map(data => {
372
+ // return data.seriesSelection
373
+ // .select<SVGGElement>(`g.${axesClassName}`)
374
+ // .style('transform', data.gridAxesTransform.value)
375
+ // })
376
+ // )
377
+ // const defsSelection$ = axesSelection$.pipe(
378
+ // takeUntil(destroy$),
379
+ // map(axesSelection => {
380
+ // return axesSelection.select<SVGDefsElement>('defs')
381
+ // })
382
+ // )
383
+ // const graphicGSelection$ = combineLatest({
384
+ // axesSelection: axesSelection$,
385
+ // gridGraphicTransform: gridGraphicTransform$
386
+ // }).pipe(
387
+ // takeUntil(destroy$),
388
+ // switchMap(async d => d),
389
+ // map(data => {
390
+ // const graphicGSelection = data.axesSelection
391
+ // .select<SVGGElement>(`g.${graphicClassName}`)
392
+ // graphicGSelection
393
+ // .transition()
394
+ // .duration(50)
395
+ // .style('transform', data.gridGraphicTransform.value)
396
+ // return graphicGSelection
397
+ // })
398
+ // )
399
+
400
+ const {
401
+ seriesSelection$,
402
+ axesSelection$,
403
+ defsSelection$,
404
+ graphicGSelection$
405
+ } = gridSelectionsObservable({
406
+ selection,
407
+ pluginName,
408
+ clipPathID,
409
+ seriesLabels$,
410
+ gridContainerPosition$,
411
+ gridAxesTransform$,
412
+ gridGraphicTransform$
413
+ })
414
+
415
+ const zeroYArr$ = visibleComputedLayoutData$.pipe(
416
+ takeUntil(destroy$),
417
+ map(data => {
418
+ return data.map(d => {
419
+ return d[0] ? d[0].axisY - d[0].axisYFromZero : 0
420
+ })
421
+ }),
422
+ distinctUntilChanged()
423
+ )
424
+
425
+ const barWidth$ = combineLatest({
426
+ computedData: computedData$,
427
+ visibleComputedData: visibleComputedData$,
428
+ params: fullParams$,
429
+ gridAxesSize: gridAxesSize$,
430
+ isSeriesSeprate: isSeriesSeprate$
431
+ }).pipe(
432
+ takeUntil(destroy$),
433
+ switchMap(async d => d),
434
+ map(data => {
435
+ if (data.params.barWidth) {
436
+ return data.params.barWidth
437
+ } else if (data.isSeriesSeprate) {
438
+ return calcBarWidth({
439
+ axisWidth: data.gridAxesSize.width,
440
+ groupAmount: data.computedData[0] ? data.computedData[0].length : 0,
441
+ barAmountOfGroup: 1,
442
+ barPadding: data.params.barPadding,
443
+ barGroupPadding: data.params.barGroupPadding
444
+ })
445
+ } else {
446
+ return calcBarWidth({
447
+ axisWidth: data.gridAxesSize.width,
448
+ groupAmount: data.computedData[0] ? data.computedData[0].length : 0,
449
+ barAmountOfGroup: data.visibleComputedData.length,
450
+ barPadding: data.params.barPadding,
451
+ barGroupPadding: data.params.barGroupPadding
452
+ })
453
+ }
454
+ }),
455
+ distinctUntilChanged()
456
+ )
457
+
458
+ // 圓角的值 [rx, ry]
459
+ const transformedBarRadius$: Observable<[number, number][]> = combineLatest({
460
+ computedData: computedData$,
461
+ // gridGraphicTransform: gridGraphicTransform$,
462
+ barWidth: barWidth$,
463
+ params: fullParams$,
464
+ // gridContainerPosition: gridContainerPosition$,
465
+ // gridAxesTransform: gridAxesTransform$
466
+ gridGraphicReverseScale: gridGraphicReverseScale$
467
+ }).pipe(
468
+ takeUntil(destroy$),
469
+ switchMap(async data => data),
470
+ map(data => {
471
+ const barHalfWidth = data.barWidth! / 2
472
+ const radius = data.params.barRadius === true ? barHalfWidth
473
+ : data.params.barRadius === false ? 0
474
+ : typeof data.params.barRadius == 'number' ? data.params.barRadius
475
+ : 0
476
+
477
+ return data.computedData.map((series, seriesIndex) => {
478
+ const gridGraphicReverseScale = data.gridGraphicReverseScale[seriesIndex] ?? data.gridGraphicReverseScale[0]
479
+
480
+ let transformedRx = radius * gridGraphicReverseScale[0]
481
+ let transformedRy = radius * gridGraphicReverseScale[1]
482
+ // if (radius == 0) {
483
+ // transformedRx = 0
484
+ // transformedRy = 0
485
+ // } else if (data.gridAxesTransform.rotate == 0) {
486
+ // transformedRx = radius
487
+ // // 抵消外層scale的變型
488
+ // / data.gridGraphicTransform.scale[0] / data.gridContainerPosition[0].scale[0]
489
+ // transformedRy = radius
490
+ // // 抵消外層scale的變型
491
+ // / data.gridGraphicTransform.scale[1] / data.gridContainerPosition[0].scale[1]
492
+ // } else if (data.gridAxesTransform.rotate != 0) {
493
+ // transformedRx = radius
494
+ // // 抵消外層scale的變型,由於有90度的旋轉,所以外層 (container) x和y的scale要互換
495
+ // / data.gridGraphicTransform.scale[0] / data.gridContainerPosition[0].scale[1]
496
+ // transformedRy = radius
497
+ // // 抵消外層scale的變型,由於有90度的旋轉,所以外層 (container) x和y的scale要互換
498
+ // / data.gridGraphicTransform.scale[1] / data.gridContainerPosition[0].scale[0]
499
+ // }
500
+
501
+ // 如果計算出來的x圓角值大於寬度一半則進行修正
502
+ if (transformedRx > barHalfWidth) {
503
+ const rScale = barHalfWidth / transformedRx
504
+ transformedRx = transformedRx * rScale
505
+ transformedRy = transformedRy * rScale
506
+ }
507
+
508
+ return [transformedRx, transformedRy]
509
+ })
510
+ })
511
+ )
512
+
513
+
514
+ // const seriesLabels$ = visibleComputedData$.pipe(
515
+ // takeUntil(destroy$),
516
+ // map(data => {
517
+ // const SeriesLabelSet: Set<string> = new Set()
518
+ // data.forEach(d => {
519
+ // d.forEach(_d => {
520
+ // SeriesLabelSet.add(_d.seriesLabel)
521
+ // })
522
+ // })
523
+ // return Array.from(SeriesLabelSet)
524
+ // })
525
+ // )
526
+
527
+ const groupLabels$ = visibleComputedData$.pipe(
528
+ takeUntil(destroy$),
529
+ map(data => {
530
+ const GroupLabelSet: Set<string> = new Set()
531
+ data.forEach(d => {
532
+ d.forEach(_d => {
533
+ GroupLabelSet.add(_d.groupLabel)
534
+ })
535
+ })
536
+ return Array.from(GroupLabelSet)
537
+ })
538
+ )
539
+
540
+ const barScale$ = combineLatest({
541
+ seriesLabels: seriesLabels$,
542
+ barWidth: barWidth$,
543
+ params: fullParams$,
544
+ }).pipe(
545
+ takeUntil(destroy$),
546
+ switchMap(async d => d),
547
+ map(data => {
548
+ return makeBarScale(data.barWidth, data.seriesLabels, data.params)
549
+ })
550
+ )
551
+
552
+ const transitionDuration$ = fullChartParams$.pipe(
553
+ takeUntil(destroy$),
554
+ map(d => d.transitionDuration),
555
+ distinctUntilChanged()
556
+ )
557
+
558
+ const delayGroup$ = new Observable<number>(subscriber => {
559
+ combineLatest({
560
+ groupLabels: groupLabels$,
561
+ transitionDuration: transitionDuration$,
562
+ }).pipe(
563
+ switchMap(async d => d)
564
+ ).subscribe(data => {
565
+ const delay = calcDelayGroup(data.groupLabels.length, data.transitionDuration)
566
+ subscriber.next(delay)
567
+ })
568
+ }).pipe(
569
+ takeUntil(destroy$),
570
+ distinctUntilChanged()
571
+ )
572
+
573
+ const transitionItem$ = new Observable<number>(subscriber => {
574
+ combineLatest({
575
+ groupLabels: groupLabels$,
576
+ transitionDuration: transitionDuration$
577
+ }).pipe(
578
+ switchMap(async d => d)
579
+ ).subscribe(data => {
580
+ const transition = calctransitionItem(data.groupLabels.length, data.transitionDuration)
581
+ subscriber.next(transition)
582
+ })
583
+ }).pipe(
584
+ takeUntil(destroy$),
585
+ distinctUntilChanged()
586
+ )
587
+
588
+ combineLatest({
589
+ defsSelection: defsSelection$,
590
+ gridAxesSize: gridAxesSize$,
591
+ }).pipe(
592
+ takeUntil(destroy$),
593
+ switchMap(async d => d)
594
+ ).subscribe(data => {
595
+ const clipPathData = [{
596
+ id: clipPathID,
597
+ width: data.gridAxesSize.width,
598
+ height: data.gridAxesSize.height
599
+ }]
600
+ renderClipPath({
601
+ defsSelection: data.defsSelection,
602
+ clipPathData
603
+ })
604
+ })
605
+
606
+
607
+ const highlightTarget$ = fullChartParams$.pipe(
608
+ takeUntil(destroy$),
609
+ map(d => d.highlightTarget),
610
+ distinctUntilChanged()
611
+ )
612
+
613
+ const barSelection$ = combineLatest({
614
+ graphicGSelection: graphicGSelection$,
615
+ visibleComputedLayoutData: visibleComputedLayoutData$,
616
+ // barData$: barData$,
617
+ zeroYArr: zeroYArr$,
618
+ groupLabels: groupLabels$,
619
+ barScale: barScale$,
620
+ params: fullParams$,
621
+ chartParams: fullChartParams$,
622
+ highlightTarget: highlightTarget$,
623
+ barWidth: barWidth$,
624
+ transformedBarRadius: transformedBarRadius$,
625
+ delayGroup: delayGroup$,
626
+ transitionItem: transitionItem$,
627
+ isSeriesSeprate: isSeriesSeprate$
628
+ }).pipe(
629
+ takeUntil(destroy$),
630
+ switchMap(async (d) => d),
631
+ map(data => {
632
+ return renderRectBars({
633
+ graphicGSelection: data.graphicGSelection,
634
+ rectClassName,
635
+ visibleComputedLayoutData: data.visibleComputedLayoutData,
636
+ zeroYArr: data.zeroYArr,
637
+ groupLabels: data.groupLabels,
638
+ barScale: data.barScale,
639
+ params: data.params,
640
+ chartParams: data.chartParams,
641
+ barWidth: data.barWidth,
642
+ transformedBarRadius: data.transformedBarRadius,
643
+ delayGroup: data.delayGroup,
644
+ transitionItem: data.transitionItem,
645
+ isSeriesSeprate: data.isSeriesSeprate
646
+ })
647
+ })
648
+ )
649
+
650
+ combineLatest({
651
+ barSelection: barSelection$,
652
+ computedData: computedData$,
653
+ highlightTarget: highlightTarget$,
654
+ SeriesDataMap: SeriesDataMap$,
655
+ GroupDataMap: GroupDataMap$,
656
+ }).pipe(
657
+ takeUntil(destroy$),
658
+ switchMap(async (d) => d),
659
+ ).subscribe(data => {
660
+ data.barSelection
661
+ .on('mouseover', (event, datum) => {
662
+ event.stopPropagation()
663
+
664
+ event$.next({
665
+ type: 'grid',
666
+ eventName: 'mouseover',
667
+ pluginName,
668
+ highlightTarget: data.highlightTarget,
669
+ datum,
670
+ gridIndex: datum.gridIndex,
671
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
672
+ seriesIndex: datum.seriesIndex,
673
+ seriesLabel: datum.seriesLabel,
674
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
675
+ groupIndex: datum.groupIndex,
676
+ groupLabel: datum.groupLabel,
677
+ event,
678
+ data: data.computedData
679
+ })
680
+ })
681
+ .on('mousemove', (event, datum) => {
682
+ event.stopPropagation()
683
+
684
+ event$.next({
685
+ type: 'grid',
686
+ eventName: 'mousemove',
687
+ pluginName,
688
+ highlightTarget: data.highlightTarget,
689
+ datum,
690
+ gridIndex: datum.gridIndex,
691
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
692
+ seriesIndex: datum.seriesIndex,
693
+ seriesLabel: datum.seriesLabel,
694
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
695
+ groupIndex: datum.groupIndex,
696
+ groupLabel: datum.groupLabel,
697
+ event,
698
+ data: data.computedData
699
+ })
700
+ })
701
+ .on('mouseout', (event, datum) => {
702
+ event.stopPropagation()
703
+
704
+ event$.next({
705
+ type: 'grid',
706
+ eventName: 'mouseout',
707
+ pluginName,
708
+ highlightTarget: data.highlightTarget,
709
+ datum,
710
+ gridIndex: datum.gridIndex,
711
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
712
+ seriesIndex: datum.seriesIndex,
713
+ seriesLabel: datum.seriesLabel,
714
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
715
+ groupIndex: datum.groupIndex,
716
+ groupLabel: datum.groupLabel,
717
+ event,
718
+ data: data.computedData
719
+ })
720
+ })
721
+ .on('click', (event, datum) => {
722
+ event.stopPropagation()
723
+
724
+ event$.next({
725
+ type: 'grid',
726
+ eventName: 'click',
727
+ pluginName,
728
+ highlightTarget: data.highlightTarget,
729
+ datum,
730
+ gridIndex: datum.gridIndex,
731
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
732
+ seriesIndex: datum.seriesIndex,
733
+ seriesLabel: datum.seriesLabel,
734
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
735
+ groupIndex: datum.groupIndex,
736
+ groupLabel: datum.groupLabel,
737
+ event,
738
+ data: data.computedData
739
+ })
740
+ })
741
+ })
742
+
743
+
744
+ combineLatest({
745
+ barSelection: barSelection$,
746
+ highlight: gridHighlight$.pipe(
747
+ map(data => data.map(d => d.id))
748
+ ),
749
+ fullChartParams: fullChartParams$
750
+ }).pipe(
751
+ takeUntil(destroy$),
752
+ switchMap(async d => d)
753
+ ).subscribe(data => {
754
+ highlight({
755
+ selection: data.barSelection,
756
+ ids: data.highlight,
757
+ fullChartParams: data.fullChartParams
758
+ })
759
+ })
760
+
761
+
762
+ return () => {
763
+ destroy$.next(undefined)
764
+ }
751
765
  }