@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,693 +1,700 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- map,
5
- filter,
6
- switchMap,
7
- takeUntil,
8
- distinctUntilChanged,
9
- Observable,
10
- Subject } from 'rxjs'
11
- import type { BasePluginFn } from './types'
12
- import type {
13
- ComputedDatumGrid,
14
- ComputedDataGrid,
15
- DataFormatterGrid,
16
- EventGrid,
17
- ContainerPosition,
18
- ChartParams,
19
- Layout,
20
- TransformData } from '@orbcharts/core'
21
- import { getD3TransitionEase } from '../utils/d3Utils'
22
- import { getClassName, getUniID } from '../utils/orbchartsUtils'
23
- import { gridGroupPositionFnObservable } from '../grid/gridObservables'
24
- import { gridSelectionsObservable } from '../grid/gridObservables'
25
-
26
- export interface BaseLinesParams {
27
- // lineType: LineType
28
- lineCurve: string
29
- lineWidth: number
30
- // labelFn: (d: ComputedDatumSeries) => string
31
- // labelPositionFn: (d: ComputedDatumSeries) => 'top' | 'bottom' | 'left' | 'right' | 'center'
32
- // labelStyleFn: (d: ComputedDatumSeries) => string
33
- // labelFontSizeFn: (d: ComputedDatumSeries) => number
34
- // labelColorFn: (d: ComputedDatumSeries) => string
35
- // labelPadding: number
36
- }
37
-
38
- interface BaseLinesContext {
39
- selection: d3.Selection<any, unknown, any, unknown>
40
- computedData$: Observable<ComputedDataGrid>
41
- existSeriesLabels$: Observable<string[]>
42
- SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
43
- GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
44
- fullDataFormatter$: Observable<DataFormatterGrid>
45
- fullParams$: Observable<BaseLinesParams>
46
- fullChartParams$: Observable<ChartParams>
47
- gridAxesTransform$: Observable<TransformData>
48
- gridGraphicTransform$: Observable<TransformData>
49
- gridAxesSize$: Observable<{
50
- width: number;
51
- height: number;
52
- }>
53
- gridHighlight$: Observable<ComputedDatumGrid[]>
54
- gridContainer$: Observable<ContainerPosition[]>
55
- event$: Subject<EventGrid>
56
- }
57
-
58
-
59
- type ClipPathDatum = {
60
- id: string;
61
- // x: number;
62
- // y: number;
63
- width: number;
64
- height: number;
65
- }
66
-
67
- // const pluginName = 'Lines'
68
- // const pathClassName = getClassName(pluginName, 'path')
69
-
70
-
71
- function createLinePath (lineCurve: string = 'curveLinear'): d3.Line<ComputedDatumGrid> {
72
- return d3.line<ComputedDatumGrid>()
73
- .x((d) => d.axisX)
74
- .y((d) => d.axisY)
75
- .curve((d3 as any)[lineCurve])
76
- }
77
-
78
- // 依無值的資料分段
79
- function makeSegmentData (data: ComputedDatumGrid[]): ComputedDatumGrid[][] {
80
- let segmentData: ComputedDatumGrid[][] = [[]]
81
-
82
- let currentIndex = 0
83
- for (let i in data) {
84
- if (data[i].visible == false || data[i].value === undefined || data[i].value === null) {
85
- // 換下一段的 index
86
- if (segmentData[currentIndex].length) {
87
- currentIndex ++
88
- segmentData[currentIndex] = []
89
- }
90
- continue
91
- }
92
- segmentData[currentIndex].push(data[i])
93
- }
94
-
95
- return segmentData
96
- }
97
-
98
-
99
- function renderLines ({ selection, pathClassName, segmentData, linePath, params }: {
100
- selection: d3.Selection<SVGGElement, unknown, any, unknown>
101
- pathClassName: string
102
- segmentData: ComputedDatumGrid[][]
103
- linePath: d3.Line<ComputedDatumGrid>
104
- params: BaseLinesParams
105
- }): d3.Selection<SVGPathElement, ComputedDatumGrid[], any, any> {
106
- // if (!data[0]) {
107
- // return undefined
108
- // }
109
-
110
- const lines = selection
111
- .selectAll<SVGPathElement, ComputedDatumGrid[]>('path')
112
- .data(segmentData, (d, i) => d.length ? `${d[0].id}_${d[d.length - 1].id}` : i) // 以線段起迄id結合為線段id
113
- .join(
114
- enter => {
115
- return enter
116
- .append<SVGPathElement>('path')
117
- .classed(pathClassName, true)
118
- .attr("fill","none")
119
- .attr('pointer-events', 'visibleStroke') // 只對線條產生事件
120
- .style('vector-effect', 'non-scaling-stroke')
121
- .style('cursor', 'pointer')
122
- },
123
- update => update,
124
- exit => exit.remove()
125
- )
126
- .attr("stroke-width", params.lineWidth)
127
- .attr("stroke", (d, i) => d[0] && d[0].color)
128
- .attr("d", (d) => {
129
- return linePath(d)
130
- })
131
-
132
- return lines
133
- }
134
-
135
- function highlightLines ({ selection, seriesLabel, fullChartParams }: {
136
- selection: d3.Selection<any, string, any, any>
137
- seriesLabel: string | null
138
- fullChartParams: ChartParams
139
- }) {
140
- selection.interrupt('highlight')
141
- if (!seriesLabel) {
142
- // remove highlight
143
- selection
144
- .transition('highlight')
145
- .duration(200)
146
- .style('opacity', 1)
147
- return
148
- }
149
-
150
- selection
151
- .each((currentSeriesLabel, i, n) => {
152
- // const currentSeriesLabel = d[0] ? d[0].seriesLabel : ''
153
-
154
- if (currentSeriesLabel === seriesLabel) {
155
- d3.select(n[i])
156
- .style('opacity', 1)
157
- } else {
158
- d3.select(n[i])
159
- .style('opacity', fullChartParams.styles.unhighlightedOpacity)
160
- }
161
- })
162
- }
163
-
164
- function renderClipPath ({ defsSelection, clipPathData, transitionDuration, transitionEase }: {
165
- defsSelection: d3.Selection<SVGDefsElement, any, any, any>
166
- clipPathData: ClipPathDatum[]
167
- transitionDuration: number
168
- transitionEase: string
169
- }) {
170
- const clipPath = defsSelection
171
- .selectAll<SVGClipPathElement, Layout>('clipPath')
172
- .data(clipPathData)
173
- .join(
174
- enter => {
175
- return enter
176
- .append('clipPath')
177
- },
178
- update => update,
179
- exit => exit.remove()
180
- )
181
- .attr('id', d => d.id)
182
- .each((d, i, g) => {
183
- const rect = d3.select(g[i])
184
- .selectAll<SVGRectElement, typeof d>('rect')
185
- .data([d])
186
- .join(
187
- enter => {
188
- const enterSelection = enter
189
- .append('rect')
190
- enterSelection
191
- .transition()
192
- .duration(transitionDuration)
193
- .ease(getD3TransitionEase(transitionEase))
194
- // .delay(100) // @Q@ 不知為何如果沒加 delay位置會有點跑掉
195
- .tween('tween', (_d, _i, _g) => {
196
- return (t) => {
197
- const transitionWidth = _d.width * t
198
-
199
- enterSelection
200
- .attr('x', 0)
201
- .attr('y', 0)
202
- .attr('width', _d => transitionWidth)
203
- .attr('height', _d => _d.height)
204
- }
205
- })
206
- return enterSelection
207
- },
208
- update => {
209
- return update
210
- .attr('x', 0)
211
- .attr('y', 0)
212
- .attr('width', _d => _d.width)
213
- .attr('height', _d => _d.height)
214
- },
215
- exit => exit.remove()
216
- )
217
- })
218
-
219
- }
220
-
221
- export const createBaseLines: BasePluginFn<BaseLinesContext> = (pluginName: string, {
222
- selection,
223
- computedData$,
224
- existSeriesLabels$,
225
- SeriesDataMap$,
226
- GroupDataMap$,
227
- fullParams$,
228
- fullDataFormatter$,
229
- fullChartParams$,
230
- gridAxesTransform$,
231
- gridGraphicTransform$,
232
- gridAxesSize$,
233
- gridHighlight$,
234
- gridContainer$,
235
- event$
236
- }) => {
237
-
238
- const destroy$ = new Subject()
239
-
240
- const clipPathID = getUniID(pluginName, 'clipPath-box')
241
- const pathClassName = getClassName(pluginName, 'path')
242
- // const clipPathSeriesID = getUniID(pluginName, 'clipPath')
243
-
244
- // const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection
245
- // .append('g')
246
- // .attr('clip-path', `url(#${clipPathID})`)
247
- // const defsSelection: d3.Selection<SVGDefsElement, any, any, any> = axisSelection.append('defs')
248
- // const graphicGSelection: d3.Selection<SVGGElement, any, any, any> = axisSelection.append('g')
249
- // const graphicSelection$: Subject<d3.Selection<SVGGElement, string, any, any>> = new Subject()
250
-
251
-
252
- // gridAxesTransform$
253
- // .pipe(
254
- // takeUntil(destroy$),
255
- // map(d => d.value),
256
- // distinctUntilChanged()
257
- // ).subscribe(d => {
258
- // axisSelection
259
- // .style('transform', d)
260
- // })
261
-
262
- // gridGraphicTransform$
263
- // .pipe(
264
- // takeUntil(destroy$),
265
- // map(d => d.value),
266
- // distinctUntilChanged()
267
- // ).subscribe(d => {
268
- // graphicGSelection
269
- // .transition()
270
- // .duration(50)
271
- // .style('transform', d)
272
- // })
273
-
274
- // const seriesSelection$ = computedData$.pipe(
275
- // takeUntil(destroy$),
276
- // distinctUntilChanged((a, b) => {
277
- // // 只有當series的數量改變時,才重新計算
278
- // return a.length === b.length
279
- // }),
280
- // map((computedData, i) => {
281
- // return selection
282
- // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${seriesClassName}`)
283
- // .data(computedData, d => d[0] ? d[0].seriesIndex : i)
284
- // .join(
285
- // enter => {
286
- // return enter
287
- // .append('g')
288
- // .classed(seriesClassName, true)
289
- // .each((d, i, g) => {
290
- // const axesSelection = d3.select(g[i])
291
- // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${axesClassName}`)
292
- // .data([i])
293
- // .join(
294
- // enter => {
295
- // return enter
296
- // .append('g')
297
- // .classed(axesClassName, true)
298
- // .attr('clip-path', `url(#${clipPathID})`)
299
- // .each((d, i, g) => {
300
- // const defsSelection = d3.select(g[i])
301
- // .selectAll<SVGDefsElement, any>('defs')
302
- // .data([i])
303
- // .join('defs')
304
-
305
- // const graphicGSelection = d3.select(g[i])
306
- // .selectAll<SVGGElement, any>('g')
307
- // .data([i])
308
- // .join('g')
309
- // .classed(graphicClassName, true)
310
- // })
311
- // },
312
- // update => update,
313
- // exit => exit.remove()
314
- // )
315
- // })
316
- // },
317
- // update => update,
318
- // exit => exit.remove()
319
- // )
320
- // })
321
- // )
322
-
323
- // combineLatest({
324
- // seriesSelection: seriesSelection$,
325
- // gridContainer: gridContainer$
326
- // }).pipe(
327
- // takeUntil(destroy$),
328
- // switchMap(async d => d)
329
- // ).subscribe(data => {
330
- // data.seriesSelection
331
- // .transition()
332
- // .attr('transform', (d, i) => {
333
- // const translate = data.gridContainer[i].translate
334
- // const scale = data.gridContainer[i].scale
335
- // return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
336
- // })
337
- // })
338
-
339
-
340
- // const axesSelection$ = combineLatest({
341
- // seriesSelection: seriesSelection$,
342
- // gridAxesTransform: gridAxesTransform$
343
- // }).pipe(
344
- // takeUntil(destroy$),
345
- // switchMap(async d => d),
346
- // map(data => {
347
- // return data.seriesSelection
348
- // .select<SVGGElement>(`g.${axesClassName}`)
349
- // .style('transform', data.gridAxesTransform.value)
350
- // })
351
- // )
352
- // const defsSelection$ = axesSelection$.pipe(
353
- // takeUntil(destroy$),
354
- // map(axesSelection => {
355
- // return axesSelection.select<SVGDefsElement>('defs')
356
- // })
357
- // )
358
- // const graphicGSelection$ = combineLatest({
359
- // axesSelection: axesSelection$,
360
- // gridGraphicTransform: gridGraphicTransform$
361
- // }).pipe(
362
- // takeUntil(destroy$),
363
- // switchMap(async d => d),
364
- // map(data => {
365
- // const graphicGSelection = data.axesSelection
366
- // .select<SVGGElement>(`g.${graphicClassName}`)
367
- // .attr('clip-path', (d) => `url(#${clipPathSeriesID}-${d[0] ? d[0].seriesLabel : ''})`)
368
- // graphicGSelection
369
- // .transition()
370
- // .duration(50)
371
- // .style('transform', data.gridGraphicTransform.value)
372
- // return graphicGSelection
373
- // })
374
- // )
375
-
376
- const {
377
- seriesSelection$,
378
- axesSelection$,
379
- defsSelection$,
380
- graphicGSelection$
381
- } = gridSelectionsObservable({
382
- selection,
383
- pluginName,
384
- clipPathID,
385
- existSeriesLabels$,
386
- gridContainer$,
387
- gridAxesTransform$,
388
- gridGraphicTransform$
389
- })
390
-
391
- const linePath$: Observable<d3.Line<ComputedDatumGrid>> = new Observable(subscriber => {
392
- const paramsSubscription = fullParams$
393
- .pipe(
394
- takeUntil(destroy$)
395
- )
396
- .subscribe(d => {
397
- if (!d) return
398
- const linePath = createLinePath(d.lineCurve)
399
- subscriber.next(linePath)
400
- })
401
- return () => {
402
- paramsSubscription.unsubscribe()
403
- }
404
- })
405
-
406
- // 顯示範圍內的series labels
407
- const seriesLabels$: Observable<string[]> = new Observable(subscriber => {
408
- computedData$.pipe(
409
- takeUntil(destroy$),
410
- // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
411
- switchMap(async (d) => d),
412
- ).subscribe(data => {
413
- const labels = data[0] && data[0][0]
414
- ? data.map(d => d[0].seriesLabel)
415
- : []
416
- subscriber.next(labels)
417
- })
418
- })
419
-
420
- // const axisSize$ = gridAxisSizeObservable({
421
- // fullDataFormatter$,
422
- // computedLayout$
423
- // })
424
-
425
- const transitionDuration$ = fullChartParams$
426
- .pipe(
427
- map(d => d.transitionDuration),
428
- distinctUntilChanged()
429
- )
430
-
431
- const transitionEase$ = fullChartParams$
432
- .pipe(
433
- map(d => d.transitionEase),
434
- distinctUntilChanged()
435
- )
436
-
437
- const clipPathSubscription = combineLatest({
438
- defsSelection: defsSelection$,
439
- seriesLabels: seriesLabels$,
440
- axisSize: gridAxesSize$,
441
- transitionDuration: transitionDuration$,
442
- transitionEase: transitionEase$
443
- }).pipe(
444
- takeUntil(destroy$),
445
- // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
446
- switchMap(async (d) => d),
447
- ).subscribe(data => {
448
- // 外層的遮罩
449
- const clipPathBox = [{
450
- id: clipPathID,
451
- width: data.axisSize.width,
452
- height: data.axisSize.height
453
- }]
454
- // 各別線條的遮罩(各別動畫)
455
- const clipPathData = clipPathBox.concat(
456
- data.seriesLabels.map(d => {
457
- return {
458
- id: `orbcharts__clipPath_${d}`,
459
- width: data.axisSize.width,
460
- height: data.axisSize.height
461
- }
462
- })
463
- )
464
- renderClipPath({
465
- defsSelection: data.defsSelection,
466
- clipPathData,
467
- transitionDuration: data.transitionDuration,
468
- transitionEase: data.transitionEase
469
- })
470
- })
471
-
472
- // const SeriesDataMap$ = computedData$.pipe(
473
- // map(d => makeGridSeriesDataMap(d))
474
- // )
475
-
476
- // const GroupDataMap$ = computedData$.pipe(
477
- // map(d => makeGridGroupDataMap(d))
478
- // )
479
-
480
- const DataMap$ = computedData$.pipe(
481
- map(d => {
482
- const DataMap: Map<string, ComputedDatumGrid> = new Map()
483
- d.flat().forEach(_d => DataMap.set(_d.id, _d))
484
- return DataMap
485
- })
486
- )
487
-
488
- // 取得事件座標的group資料
489
- const gridGroupPositionFn$ = gridGroupPositionFnObservable({
490
- fullDataFormatter$,
491
- gridAxesSize$: gridAxesSize$,
492
- computedData$: computedData$,
493
- fullChartParams$: fullChartParams$
494
- })
495
-
496
- const highlightTarget$ = fullChartParams$.pipe(
497
- takeUntil(destroy$),
498
- map(d => d.highlightTarget),
499
- distinctUntilChanged()
500
- )
501
-
502
- const graphSubscription = combineLatest({
503
- graphicGSelection: graphicGSelection$,
504
- seriesLabels: seriesLabels$,
505
- computedData: computedData$,
506
- SeriesDataMap: SeriesDataMap$,
507
- GroupDataMap: GroupDataMap$,
508
- linePath: linePath$,
509
- params: fullParams$,
510
- highlightTarget: highlightTarget$,
511
- gridGroupPositionFn: gridGroupPositionFn$,
512
- }).pipe(
513
- takeUntil(destroy$),
514
- // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
515
- switchMap(async (d) => d),
516
- ).subscribe(data => {
517
-
518
- // const updateGraphic = data.graphicGSelection
519
- // .selectAll<SVGGElement, number>('g')
520
- // .data(data.seriesLabels, (d, i) => d)
521
- // const enterGraphic = updateGraphic.enter()
522
- // .append('g')
523
- // .classed(graphicClassName, true)
524
- // updateGraphic.exit().remove()
525
- // const graphicSelection = updateGraphic.merge(enterGraphic)
526
- // .attr('clip-path', (d, i) => `url(#orbcharts__clipPath_${d})`)
527
-
528
- // 繪圖
529
- data.graphicGSelection.each((d, i, all) => {
530
- // 將資料分段
531
- const segmentData = makeSegmentData(data.computedData[i] ?? [])
532
-
533
- const pathSelection = renderLines({
534
- selection: d3.select(all[i]),
535
- pathClassName,
536
- linePath: data.linePath,
537
- segmentData: segmentData,
538
- params: data.params
539
- })
540
-
541
- pathSelection
542
- .on('mouseover', (event, datum) => {
543
- event.stopPropagation()
544
-
545
- const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
546
- const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
547
- const groupData = data.GroupDataMap.get(groupLabel)!
548
- const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
549
- const _datum = targetDatum ?? datum[0]
550
-
551
- event$.next({
552
- type: 'grid',
553
- eventName: 'mouseover',
554
- pluginName,
555
- highlightTarget: data.highlightTarget,
556
- datum: _datum,
557
- gridIndex: _datum.gridIndex,
558
- series: data.SeriesDataMap.get(_datum.seriesLabel)!,
559
- seriesIndex: _datum.seriesIndex,
560
- seriesLabel: _datum.seriesLabel,
561
- groups: data.GroupDataMap.get(_datum.groupLabel)!,
562
- groupIndex: _datum.groupIndex,
563
- groupLabel: _datum.groupLabel,
564
- event,
565
- data: data.computedData
566
- })
567
- })
568
- .on('mousemove', (event, datum) => {
569
- event.stopPropagation()
570
-
571
- const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
572
- const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
573
- const groupData = data.GroupDataMap.get(groupLabel)!
574
- const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
575
- const _datum = targetDatum ?? datum[0]
576
-
577
- event$.next({
578
- type: 'grid',
579
- eventName: 'mousemove',
580
- pluginName,
581
- highlightTarget: data.highlightTarget,
582
- datum: _datum,
583
- gridIndex: _datum.gridIndex,
584
- series: data.SeriesDataMap.get(_datum.seriesLabel)!,
585
- seriesIndex: _datum.seriesIndex,
586
- seriesLabel: _datum.seriesLabel,
587
- groups: data.GroupDataMap.get(_datum.groupLabel)!,
588
- groupIndex: _datum.groupIndex,
589
- groupLabel: _datum.groupLabel,
590
- event,
591
- data: data.computedData
592
- })
593
- })
594
- .on('mouseout', (event, datum) => {
595
- event.stopPropagation()
596
-
597
- const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
598
- const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
599
- const groupData = data.GroupDataMap.get(groupLabel)!
600
- const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
601
- const _datum = targetDatum ?? datum[0]
602
-
603
- event$.next({
604
- type: 'grid',
605
- eventName: 'mouseout',
606
- pluginName,
607
- highlightTarget: data.highlightTarget,
608
- datum: _datum,
609
- gridIndex: _datum.gridIndex,
610
- series: data.SeriesDataMap.get(_datum.seriesLabel)!,
611
- seriesIndex: _datum.seriesIndex,
612
- seriesLabel: _datum.seriesLabel,
613
- groups: data.GroupDataMap.get(_datum.groupLabel)!,
614
- groupIndex: _datum.groupIndex,
615
- groupLabel: _datum.groupLabel,
616
- event,
617
- data: data.computedData
618
- })
619
- })
620
- .on('click', (event, datum) => {
621
- event.stopPropagation()
622
-
623
- const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
624
- const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
625
- const groupData = data.GroupDataMap.get(groupLabel)!
626
- const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
627
- const _datum = targetDatum ?? datum[0]
628
-
629
- event$.next({
630
- type: 'grid',
631
- eventName: 'click',
632
- pluginName,
633
- highlightTarget: data.highlightTarget,
634
- datum: _datum,
635
- gridIndex: _datum.gridIndex,
636
- series: data.SeriesDataMap.get(_datum.seriesLabel)!,
637
- seriesIndex: _datum.seriesIndex,
638
- seriesLabel: _datum.seriesLabel,
639
- groups: data.GroupDataMap.get(_datum.groupLabel)!,
640
- groupIndex: _datum.groupIndex,
641
- groupLabel: _datum.groupLabel,
642
- event,
643
- data: data.computedData
644
- })
645
- })
646
-
647
- })
648
-
649
-
650
-
651
- // graphicSelection$.next(graphicSelection)
652
-
653
-
654
- // pathSelection = renderLines({
655
- // selection: graphicSelection,
656
- // linePath: d.linePath,
657
- // data: d.computedData
658
- // })
659
- })
660
-
661
- // const datumList$ = computedData$.pipe(
662
- // takeUntil(destroy$),
663
- // map(d => d.flat())
664
- // )
665
- // const highlight$ = highlightObservable({ datumList$, fullChartParams$, event$: store.event$ })
666
- // const highlightSubscription = gridHighlight$.subscribe()
667
-
668
- fullChartParams$.pipe(
669
- takeUntil(destroy$),
670
- filter(d => d.highlightTarget === 'series'),
671
- switchMap(d => combineLatest({
672
- graphicGSelection: graphicGSelection$,
673
- gridHighlight: gridHighlight$,
674
- DataMap: DataMap$,
675
- fullChartParams: fullChartParams$
676
- }).pipe(
677
- takeUntil(destroy$),
678
- switchMap(async d => d)
679
- ))
680
- ).subscribe(data => {
681
- const seriesLabel = data.gridHighlight[0] ? data.gridHighlight[0].seriesLabel : null
682
- highlightLines({
683
- selection: data.graphicGSelection,
684
- seriesLabel,
685
- fullChartParams: data.fullChartParams
686
- })
687
- })
688
-
689
- return () => {
690
- destroy$.next(undefined)
691
- // highlightSubscription.unsubscribe()
692
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ filter,
6
+ switchMap,
7
+ takeUntil,
8
+ distinctUntilChanged,
9
+ Observable,
10
+ Subject } from 'rxjs'
11
+ import type { BasePluginFn } from './types'
12
+ import type {
13
+ ComputedDatumGrid,
14
+ ComputedDataGrid,
15
+ ComputedLayoutDatumGrid,
16
+ ComputedLayoutDataGrid,
17
+ DataFormatterGrid,
18
+ EventGrid,
19
+ GridContainerPosition,
20
+ ChartParams,
21
+ Layout,
22
+ TransformData } from '@orbcharts/core'
23
+ import { getD3TransitionEase } from '../utils/d3Utils'
24
+ import { getClassName, getUniID } from '../utils/orbchartsUtils'
25
+ import { gridGroupPositionFnObservable } from '../grid/gridObservables'
26
+ import { gridSelectionsObservable } from '../grid/gridObservables'
27
+
28
+ export interface BaseLinesParams {
29
+ // lineType: LineType
30
+ lineCurve: string
31
+ lineWidth: number
32
+ // labelFn: (d: ComputedDatumSeries) => string
33
+ // labelPositionFn: (d: ComputedDatumSeries) => 'top' | 'bottom' | 'left' | 'right' | 'center'
34
+ // labelStyleFn: (d: ComputedDatumSeries) => string
35
+ // labelFontSizeFn: (d: ComputedDatumSeries) => number
36
+ // labelColorFn: (d: ComputedDatumSeries) => string
37
+ // labelPadding: number
38
+ }
39
+
40
+ interface BaseLinesContext {
41
+ selection: d3.Selection<any, unknown, any, unknown>
42
+ computedData$: Observable<ComputedDataGrid>
43
+ computedLayoutData$: Observable<ComputedLayoutDataGrid>
44
+ visibleComputedData$: Observable<ComputedDatumGrid[][]>
45
+ visibleComputedLayoutData$: Observable<ComputedLayoutDataGrid>
46
+ seriesLabels$: Observable<string[]>
47
+ SeriesDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
48
+ GroupDataMap$: Observable<Map<string, ComputedDatumGrid[]>>
49
+ fullDataFormatter$: Observable<DataFormatterGrid>
50
+ fullParams$: Observable<BaseLinesParams>
51
+ fullChartParams$: Observable<ChartParams>
52
+ gridAxesTransform$: Observable<TransformData>
53
+ gridGraphicTransform$: Observable<TransformData>
54
+ gridAxesSize$: Observable<{
55
+ width: number;
56
+ height: number;
57
+ }>
58
+ gridHighlight$: Observable<ComputedDatumGrid[]>
59
+ gridContainerPosition$: Observable<GridContainerPosition[]>
60
+ event$: Subject<EventGrid>
61
+ }
62
+
63
+
64
+ type ClipPathDatum = {
65
+ id: string;
66
+ // x: number;
67
+ // y: number;
68
+ width: number;
69
+ height: number;
70
+ }
71
+
72
+ // const pluginName = 'Lines'
73
+ // const pathClassName = getClassName(pluginName, 'path')
74
+
75
+
76
+ function createLinePath (lineCurve: string = 'curveLinear'): d3.Line<ComputedLayoutDatumGrid> {
77
+ return d3.line<ComputedLayoutDatumGrid>()
78
+ .x((d) => d.axisX)
79
+ .y((d) => d.axisY)
80
+ .curve((d3 as any)[lineCurve])
81
+ }
82
+
83
+ // 依無值的資料分段
84
+ function makeSegmentData (data: ComputedLayoutDatumGrid[]): ComputedLayoutDatumGrid[][] {
85
+ let segmentData: ComputedLayoutDatumGrid[][] = [[]]
86
+
87
+ let currentIndex = 0
88
+ for (let i in data) {
89
+ if (data[i].visible == false || data[i].value === undefined || data[i].value === null) {
90
+ // 換下一段的 index
91
+ if (segmentData[currentIndex].length) {
92
+ currentIndex ++
93
+ segmentData[currentIndex] = []
94
+ }
95
+ continue
96
+ }
97
+ segmentData[currentIndex].push(data[i])
98
+ }
99
+
100
+ return segmentData
101
+ }
102
+
103
+
104
+ function renderLines ({ selection, pathClassName, segmentData, linePath, params }: {
105
+ selection: d3.Selection<SVGGElement, unknown, any, unknown>
106
+ pathClassName: string
107
+ segmentData: ComputedLayoutDatumGrid[][]
108
+ linePath: d3.Line<ComputedLayoutDatumGrid>
109
+ params: BaseLinesParams
110
+ }): d3.Selection<SVGPathElement, ComputedLayoutDatumGrid[], any, any> {
111
+ // if (!data[0]) {
112
+ // return undefined
113
+ // }
114
+
115
+ const lines = selection
116
+ .selectAll<SVGPathElement, ComputedLayoutDatumGrid[]>('path')
117
+ .data(segmentData, (d, i) => d.length ? `${d[0].id}_${d[d.length - 1].id}` : i) // 以線段起迄id結合為線段id
118
+ .join(
119
+ enter => {
120
+ return enter
121
+ .append<SVGPathElement>('path')
122
+ .classed(pathClassName, true)
123
+ .attr("fill","none")
124
+ .attr('pointer-events', 'visibleStroke') // 只對線條產生事件
125
+ .style('vector-effect', 'non-scaling-stroke')
126
+ .style('cursor', 'pointer')
127
+ },
128
+ update => update,
129
+ exit => exit.remove()
130
+ )
131
+ .attr("stroke-width", params.lineWidth)
132
+ .attr("stroke", (d, i) => d[0] && d[0].color)
133
+ .attr("d", (d) => {
134
+ return linePath(d)
135
+ })
136
+
137
+ return lines
138
+ }
139
+
140
+ function highlightLines ({ selection, seriesLabel, fullChartParams }: {
141
+ selection: d3.Selection<any, string, any, any>
142
+ seriesLabel: string | null
143
+ fullChartParams: ChartParams
144
+ }) {
145
+ selection.interrupt('highlight')
146
+ if (!seriesLabel) {
147
+ // remove highlight
148
+ selection
149
+ .transition('highlight')
150
+ .duration(200)
151
+ .style('opacity', 1)
152
+ return
153
+ }
154
+
155
+ selection
156
+ .each((currentSeriesLabel, i, n) => {
157
+ // const currentSeriesLabel = d[0] ? d[0].seriesLabel : ''
158
+
159
+ if (currentSeriesLabel === seriesLabel) {
160
+ d3.select(n[i])
161
+ .style('opacity', 1)
162
+ } else {
163
+ d3.select(n[i])
164
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
165
+ }
166
+ })
167
+ }
168
+
169
+ function renderClipPath ({ defsSelection, clipPathData, transitionDuration, transitionEase }: {
170
+ defsSelection: d3.Selection<SVGDefsElement, any, any, any>
171
+ clipPathData: ClipPathDatum[]
172
+ transitionDuration: number
173
+ transitionEase: string
174
+ }) {
175
+ const clipPath = defsSelection
176
+ .selectAll<SVGClipPathElement, Layout>('clipPath')
177
+ .data(clipPathData)
178
+ .join(
179
+ enter => {
180
+ return enter
181
+ .append('clipPath')
182
+ },
183
+ update => update,
184
+ exit => exit.remove()
185
+ )
186
+ .attr('id', d => d.id)
187
+ .each((d, i, g) => {
188
+ const rect = d3.select(g[i])
189
+ .selectAll<SVGRectElement, typeof d>('rect')
190
+ .data([d])
191
+ .join(
192
+ enter => {
193
+ const enterSelection = enter
194
+ .append('rect')
195
+ enterSelection
196
+ .transition()
197
+ .duration(transitionDuration)
198
+ .ease(getD3TransitionEase(transitionEase))
199
+ // .delay(100) // @Q@ 不知為何如果沒加 delay位置會有點跑掉
200
+ .tween('tween', (_d, _i, _g) => {
201
+ return (t) => {
202
+ const transitionWidth = _d.width * t
203
+
204
+ enterSelection
205
+ .attr('x', 0)
206
+ .attr('y', 0)
207
+ .attr('width', _d => transitionWidth)
208
+ .attr('height', _d => _d.height)
209
+ }
210
+ })
211
+ return enterSelection
212
+ },
213
+ update => {
214
+ return update
215
+ .attr('x', 0)
216
+ .attr('y', 0)
217
+ .attr('width', _d => _d.width)
218
+ .attr('height', _d => _d.height)
219
+ },
220
+ exit => exit.remove()
221
+ )
222
+ })
223
+
224
+ }
225
+
226
+ export const createBaseLines: BasePluginFn<BaseLinesContext> = (pluginName: string, {
227
+ selection,
228
+ computedData$,
229
+ computedLayoutData$,
230
+ visibleComputedData$,
231
+ visibleComputedLayoutData$,
232
+ seriesLabels$,
233
+ SeriesDataMap$,
234
+ GroupDataMap$,
235
+ fullParams$,
236
+ fullDataFormatter$,
237
+ fullChartParams$,
238
+ gridAxesTransform$,
239
+ gridGraphicTransform$,
240
+ gridAxesSize$,
241
+ gridHighlight$,
242
+ gridContainerPosition$,
243
+ event$
244
+ }) => {
245
+
246
+ const destroy$ = new Subject()
247
+
248
+ const clipPathID = getUniID(pluginName, 'clipPath-box')
249
+ const pathClassName = getClassName(pluginName, 'path')
250
+ // const clipPathSeriesID = getUniID(pluginName, 'clipPath')
251
+
252
+ // const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection
253
+ // .append('g')
254
+ // .attr('clip-path', `url(#${clipPathID})`)
255
+ // const defsSelection: d3.Selection<SVGDefsElement, any, any, any> = axisSelection.append('defs')
256
+ // const graphicGSelection: d3.Selection<SVGGElement, any, any, any> = axisSelection.append('g')
257
+ // const graphicSelection$: Subject<d3.Selection<SVGGElement, string, any, any>> = new Subject()
258
+
259
+
260
+ // gridAxesTransform$
261
+ // .pipe(
262
+ // takeUntil(destroy$),
263
+ // map(d => d.value),
264
+ // distinctUntilChanged()
265
+ // ).subscribe(d => {
266
+ // axisSelection
267
+ // .style('transform', d)
268
+ // })
269
+
270
+ // gridGraphicTransform$
271
+ // .pipe(
272
+ // takeUntil(destroy$),
273
+ // map(d => d.value),
274
+ // distinctUntilChanged()
275
+ // ).subscribe(d => {
276
+ // graphicGSelection
277
+ // .transition()
278
+ // .duration(50)
279
+ // .style('transform', d)
280
+ // })
281
+
282
+ // const seriesSelection$ = computedData$.pipe(
283
+ // takeUntil(destroy$),
284
+ // distinctUntilChanged((a, b) => {
285
+ // // 只有當series的數量改變時,才重新計算
286
+ // return a.length === b.length
287
+ // }),
288
+ // map((computedData, i) => {
289
+ // return selection
290
+ // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${seriesClassName}`)
291
+ // .data(computedData, d => d[0] ? d[0].seriesIndex : i)
292
+ // .join(
293
+ // enter => {
294
+ // return enter
295
+ // .append('g')
296
+ // .classed(seriesClassName, true)
297
+ // .each((d, i, g) => {
298
+ // const axesSelection = d3.select(g[i])
299
+ // .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${axesClassName}`)
300
+ // .data([i])
301
+ // .join(
302
+ // enter => {
303
+ // return enter
304
+ // .append('g')
305
+ // .classed(axesClassName, true)
306
+ // .attr('clip-path', `url(#${clipPathID})`)
307
+ // .each((d, i, g) => {
308
+ // const defsSelection = d3.select(g[i])
309
+ // .selectAll<SVGDefsElement, any>('defs')
310
+ // .data([i])
311
+ // .join('defs')
312
+
313
+ // const graphicGSelection = d3.select(g[i])
314
+ // .selectAll<SVGGElement, any>('g')
315
+ // .data([i])
316
+ // .join('g')
317
+ // .classed(graphicClassName, true)
318
+ // })
319
+ // },
320
+ // update => update,
321
+ // exit => exit.remove()
322
+ // )
323
+ // })
324
+ // },
325
+ // update => update,
326
+ // exit => exit.remove()
327
+ // )
328
+ // })
329
+ // )
330
+
331
+ // combineLatest({
332
+ // seriesSelection: seriesSelection$,
333
+ // gridContainerPosition: gridContainerPosition$
334
+ // }).pipe(
335
+ // takeUntil(destroy$),
336
+ // switchMap(async d => d)
337
+ // ).subscribe(data => {
338
+ // data.seriesSelection
339
+ // .transition()
340
+ // .attr('transform', (d, i) => {
341
+ // const translate = data.gridContainerPosition[i].translate
342
+ // const scale = data.gridContainerPosition[i].scale
343
+ // return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
344
+ // })
345
+ // })
346
+
347
+
348
+ // const axesSelection$ = combineLatest({
349
+ // seriesSelection: seriesSelection$,
350
+ // gridAxesTransform: gridAxesTransform$
351
+ // }).pipe(
352
+ // takeUntil(destroy$),
353
+ // switchMap(async d => d),
354
+ // map(data => {
355
+ // return data.seriesSelection
356
+ // .select<SVGGElement>(`g.${axesClassName}`)
357
+ // .style('transform', data.gridAxesTransform.value)
358
+ // })
359
+ // )
360
+ // const defsSelection$ = axesSelection$.pipe(
361
+ // takeUntil(destroy$),
362
+ // map(axesSelection => {
363
+ // return axesSelection.select<SVGDefsElement>('defs')
364
+ // })
365
+ // )
366
+ // const graphicGSelection$ = combineLatest({
367
+ // axesSelection: axesSelection$,
368
+ // gridGraphicTransform: gridGraphicTransform$
369
+ // }).pipe(
370
+ // takeUntil(destroy$),
371
+ // switchMap(async d => d),
372
+ // map(data => {
373
+ // const graphicGSelection = data.axesSelection
374
+ // .select<SVGGElement>(`g.${graphicClassName}`)
375
+ // .attr('clip-path', (d) => `url(#${clipPathSeriesID}-${d[0] ? d[0].seriesLabel : ''})`)
376
+ // graphicGSelection
377
+ // .transition()
378
+ // .duration(50)
379
+ // .style('transform', data.gridGraphicTransform.value)
380
+ // return graphicGSelection
381
+ // })
382
+ // )
383
+
384
+ const {
385
+ seriesSelection$,
386
+ axesSelection$,
387
+ defsSelection$,
388
+ graphicGSelection$
389
+ } = gridSelectionsObservable({
390
+ selection,
391
+ pluginName,
392
+ clipPathID,
393
+ seriesLabels$,
394
+ gridContainerPosition$,
395
+ gridAxesTransform$,
396
+ gridGraphicTransform$
397
+ })
398
+
399
+ const linePath$: Observable<d3.Line<ComputedLayoutDatumGrid>> = new Observable(subscriber => {
400
+ const paramsSubscription = fullParams$
401
+ .pipe(
402
+ takeUntil(destroy$)
403
+ )
404
+ .subscribe(d => {
405
+ if (!d) return
406
+ const linePath = createLinePath(d.lineCurve)
407
+ subscriber.next(linePath)
408
+ })
409
+ return () => {
410
+ paramsSubscription.unsubscribe()
411
+ }
412
+ })
413
+
414
+ // // 顯示範圍內的series labels
415
+ // const seriesLabels$: Observable<string[]> = new Observable(subscriber => {
416
+ // computedData$.pipe(
417
+ // takeUntil(destroy$),
418
+ // switchMap(async (d) => d),
419
+ // ).subscribe(data => {
420
+ // const labels = data[0] && data[0][0]
421
+ // ? data.map(d => d[0].seriesLabel)
422
+ // : []
423
+ // subscriber.next(labels)
424
+ // })
425
+ // })
426
+
427
+ // const axisSize$ = gridAxisSizeObservable({
428
+ // fullDataFormatter$,
429
+ // computedLayout$
430
+ // })
431
+
432
+ const transitionDuration$ = fullChartParams$
433
+ .pipe(
434
+ map(d => d.transitionDuration),
435
+ distinctUntilChanged()
436
+ )
437
+
438
+ const transitionEase$ = fullChartParams$
439
+ .pipe(
440
+ map(d => d.transitionEase),
441
+ distinctUntilChanged()
442
+ )
443
+
444
+ const clipPathSubscription = combineLatest({
445
+ defsSelection: defsSelection$,
446
+ seriesLabels: seriesLabels$,
447
+ axisSize: gridAxesSize$,
448
+ transitionDuration: transitionDuration$,
449
+ transitionEase: transitionEase$
450
+ }).pipe(
451
+ takeUntil(destroy$),
452
+ switchMap(async (d) => d),
453
+ ).subscribe(data => {
454
+ // 外層的遮罩
455
+ const clipPathBox = [{
456
+ id: clipPathID,
457
+ width: data.axisSize.width,
458
+ height: data.axisSize.height
459
+ }]
460
+ // 各別線條的遮罩(各別動畫)
461
+ const clipPathData = clipPathBox.concat(
462
+ data.seriesLabels.map(d => {
463
+ return {
464
+ id: `orbcharts__clipPath_${d}`,
465
+ width: data.axisSize.width,
466
+ height: data.axisSize.height
467
+ }
468
+ })
469
+ )
470
+ renderClipPath({
471
+ defsSelection: data.defsSelection,
472
+ clipPathData,
473
+ transitionDuration: data.transitionDuration,
474
+ transitionEase: data.transitionEase
475
+ })
476
+ })
477
+
478
+ // const SeriesDataMap$ = computedData$.pipe(
479
+ // map(d => makeGridSeriesDataMap(d))
480
+ // )
481
+
482
+ // const GroupDataMap$ = computedData$.pipe(
483
+ // map(d => makeGridGroupDataMap(d))
484
+ // )
485
+
486
+ const DataMap$ = computedData$.pipe(
487
+ map(d => {
488
+ const DataMap: Map<string, ComputedDatumGrid> = new Map()
489
+ d.flat().forEach(_d => DataMap.set(_d.id, _d))
490
+ return DataMap
491
+ })
492
+ )
493
+
494
+ // 取得事件座標的group資料
495
+ const gridGroupPositionFn$ = gridGroupPositionFnObservable({
496
+ fullDataFormatter$,
497
+ gridAxesSize$: gridAxesSize$,
498
+ computedData$: computedData$,
499
+ fullChartParams$: fullChartParams$
500
+ })
501
+
502
+ const highlightTarget$ = fullChartParams$.pipe(
503
+ takeUntil(destroy$),
504
+ map(d => d.highlightTarget),
505
+ distinctUntilChanged()
506
+ )
507
+
508
+ const pathSelectionArr$ = combineLatest({
509
+ graphicGSelection: graphicGSelection$,
510
+ visibleComputedLayoutData: visibleComputedLayoutData$,
511
+ linePath: linePath$,
512
+ params: fullParams$,
513
+ }).pipe(
514
+ takeUntil(destroy$),
515
+ switchMap(async (d) => d),
516
+ map(data => {
517
+ // const updateGraphic = data.graphicGSelection
518
+ // .selectAll<SVGGElement, number>('g')
519
+ // .data(data.seriesLabels, (d, i) => d)
520
+ // const enterGraphic = updateGraphic.enter()
521
+ // .append('g')
522
+ // .classed(graphicClassName, true)
523
+ // updateGraphic.exit().remove()
524
+ // const graphicSelection = updateGraphic.merge(enterGraphic)
525
+ // .attr('clip-path', (d, i) => `url(#orbcharts__clipPath_${d})`)
526
+ let pathSelectionArr: d3.Selection<SVGPathElement, ComputedLayoutDatumGrid[], any, any>[] = []
527
+
528
+ // 繪圖
529
+ data.graphicGSelection.each((d, i, all) => {
530
+ // 將資料分段
531
+ const segmentData = makeSegmentData(data.visibleComputedLayoutData[i] ?? [])
532
+
533
+ pathSelectionArr[i] = renderLines({
534
+ selection: d3.select(all[i]),
535
+ pathClassName,
536
+ linePath: data.linePath,
537
+ segmentData: segmentData,
538
+ params: data.params
539
+ })
540
+ })
541
+
542
+ return pathSelectionArr
543
+ })
544
+
545
+
546
+ )
547
+
548
+ combineLatest({
549
+ pathSelectionArr: pathSelectionArr$,
550
+ computedData: computedData$,
551
+ SeriesDataMap: SeriesDataMap$,
552
+ GroupDataMap: GroupDataMap$,
553
+ highlightTarget: highlightTarget$,
554
+ gridGroupPositionFn: gridGroupPositionFn$,
555
+ }).pipe(
556
+ takeUntil(destroy$),
557
+ switchMap(async (d) => d)
558
+ ).subscribe(data => {
559
+ data.pathSelectionArr.forEach(pathSelection => {
560
+ pathSelection
561
+ .on('mouseover', (event, datum) => {
562
+ event.stopPropagation()
563
+
564
+ const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
565
+ const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
566
+ const groupData = data.GroupDataMap.get(groupLabel)!
567
+ const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
568
+ const _datum = targetDatum ?? datum[0]
569
+
570
+ event$.next({
571
+ type: 'grid',
572
+ eventName: 'mouseover',
573
+ pluginName,
574
+ highlightTarget: data.highlightTarget,
575
+ datum: _datum,
576
+ gridIndex: _datum.gridIndex,
577
+ series: data.SeriesDataMap.get(_datum.seriesLabel)!,
578
+ seriesIndex: _datum.seriesIndex,
579
+ seriesLabel: _datum.seriesLabel,
580
+ groups: data.GroupDataMap.get(_datum.groupLabel)!,
581
+ groupIndex: _datum.groupIndex,
582
+ groupLabel: _datum.groupLabel,
583
+ event,
584
+ data: data.computedData
585
+ })
586
+ })
587
+ .on('mousemove', (event, datum) => {
588
+ event.stopPropagation()
589
+
590
+ const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
591
+ const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
592
+ const groupData = data.GroupDataMap.get(groupLabel)!
593
+ const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
594
+ const _datum = targetDatum ?? datum[0]
595
+
596
+ event$.next({
597
+ type: 'grid',
598
+ eventName: 'mousemove',
599
+ pluginName,
600
+ highlightTarget: data.highlightTarget,
601
+ datum: _datum,
602
+ gridIndex: _datum.gridIndex,
603
+ series: data.SeriesDataMap.get(_datum.seriesLabel)!,
604
+ seriesIndex: _datum.seriesIndex,
605
+ seriesLabel: _datum.seriesLabel,
606
+ groups: data.GroupDataMap.get(_datum.groupLabel)!,
607
+ groupIndex: _datum.groupIndex,
608
+ groupLabel: _datum.groupLabel,
609
+ event,
610
+ data: data.computedData
611
+ })
612
+ })
613
+ .on('mouseout', (event, datum) => {
614
+ event.stopPropagation()
615
+
616
+ const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
617
+ const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
618
+ const groupData = data.GroupDataMap.get(groupLabel)!
619
+ const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
620
+ const _datum = targetDatum ?? datum[0]
621
+
622
+ event$.next({
623
+ type: 'grid',
624
+ eventName: 'mouseout',
625
+ pluginName,
626
+ highlightTarget: data.highlightTarget,
627
+ datum: _datum,
628
+ gridIndex: _datum.gridIndex,
629
+ series: data.SeriesDataMap.get(_datum.seriesLabel)!,
630
+ seriesIndex: _datum.seriesIndex,
631
+ seriesLabel: _datum.seriesLabel,
632
+ groups: data.GroupDataMap.get(_datum.groupLabel)!,
633
+ groupIndex: _datum.groupIndex,
634
+ groupLabel: _datum.groupLabel,
635
+ event,
636
+ data: data.computedData
637
+ })
638
+ })
639
+ .on('click', (event, datum) => {
640
+ event.stopPropagation()
641
+
642
+ const seriesLabel = datum[0] ? datum[0].seriesLabel : ''
643
+ const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
644
+ const groupData = data.GroupDataMap.get(groupLabel)!
645
+ const targetDatum = groupData.find(d => d.seriesLabel === seriesLabel)
646
+ const _datum = targetDatum ?? datum[0]
647
+
648
+ event$.next({
649
+ type: 'grid',
650
+ eventName: 'click',
651
+ pluginName,
652
+ highlightTarget: data.highlightTarget,
653
+ datum: _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
+ })
666
+ })
667
+
668
+ // const datumList$ = computedData$.pipe(
669
+ // takeUntil(destroy$),
670
+ // map(d => d.flat())
671
+ // )
672
+ // const highlight$ = highlightObservable({ datumList$, fullChartParams$, event$: store.event$ })
673
+ // const highlightSubscription = gridHighlight$.subscribe()
674
+
675
+ fullChartParams$.pipe(
676
+ takeUntil(destroy$),
677
+ filter(d => d.highlightTarget === 'series'),
678
+ switchMap(d => combineLatest({
679
+ graphicGSelection: graphicGSelection$,
680
+ gridHighlight: gridHighlight$,
681
+ DataMap: DataMap$,
682
+ fullChartParams: fullChartParams$
683
+ }).pipe(
684
+ takeUntil(destroy$),
685
+ switchMap(async d => d)
686
+ ))
687
+ ).subscribe(data => {
688
+ const seriesLabel = data.gridHighlight[0] ? data.gridHighlight[0].seriesLabel : null
689
+ highlightLines({
690
+ selection: data.graphicGSelection,
691
+ seriesLabel,
692
+ fullChartParams: data.fullChartParams
693
+ })
694
+ })
695
+
696
+ return () => {
697
+ destroy$.next(undefined)
698
+ // highlightSubscription.unsubscribe()
699
+ }
693
700
  }