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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic/src/utils/d3Utils.d.ts +2 -2
  3. package/dist/orbcharts-plugins-basic.es.js +5001 -4981
  4. package/dist/orbcharts-plugins-basic.umd.js +28 -28
  5. package/lib/core-types.ts +7 -7
  6. package/lib/core.ts +6 -6
  7. package/lib/plugins-basic-types.ts +6 -6
  8. package/package.json +44 -44
  9. package/src/base/BaseBarStack.ts +782 -780
  10. package/src/base/BaseBars.ts +765 -765
  11. package/src/base/BaseBarsTriangle.ts +676 -674
  12. package/src/base/BaseDots.ts +464 -464
  13. package/src/base/BaseGroupAxis.ts +679 -679
  14. package/src/base/BaseLegend.ts +684 -684
  15. package/src/base/BaseLineAreas.ts +629 -629
  16. package/src/base/BaseLines.ts +706 -706
  17. package/src/base/BaseTooltip.ts +385 -385
  18. package/src/base/BaseValueAxis.ts +583 -583
  19. package/src/base/types.ts +2 -2
  20. package/src/const.ts +30 -30
  21. package/src/grid/defaults.ts +246 -244
  22. package/src/grid/gridObservables.ts +554 -554
  23. package/src/grid/index.ts +16 -16
  24. package/src/grid/plugins/BarStack.ts +64 -64
  25. package/src/grid/plugins/Bars.ts +69 -69
  26. package/src/grid/plugins/BarsPN.ts +66 -66
  27. package/src/grid/plugins/BarsTriangle.ts +73 -73
  28. package/src/grid/plugins/Dots.ts +68 -68
  29. package/src/grid/plugins/GridLegend.ts +107 -107
  30. package/src/grid/plugins/GridTooltip.ts +66 -66
  31. package/src/grid/plugins/GridZoom.ts +218 -218
  32. package/src/grid/plugins/GroupAux.ts +1103 -1103
  33. package/src/grid/plugins/GroupAxis.ts +97 -97
  34. package/src/grid/plugins/LineAreas.ts +65 -65
  35. package/src/grid/plugins/Lines.ts +59 -59
  36. package/src/grid/plugins/ValueAxis.ts +94 -94
  37. package/src/grid/plugins/ValueStackAxis.ts +96 -96
  38. package/src/index.ts +10 -10
  39. package/src/multiGrid/defaults.ts +224 -224
  40. package/src/multiGrid/index.ts +14 -14
  41. package/src/multiGrid/multiGridObservables.ts +49 -49
  42. package/src/multiGrid/plugins/MultiBarStack.ts +106 -106
  43. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  44. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  45. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  46. package/src/multiGrid/plugins/MultiGridLegend.ts +159 -159
  47. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  48. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  49. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  50. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  51. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  52. package/src/multiGrid/plugins/MultiValueStackAxis.ts +134 -134
  53. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  54. package/src/multiGrid/plugins/OverlappingValueStackAxes.ts +299 -299
  55. package/src/multiValue/defaults.ts +166 -162
  56. package/src/multiValue/index.ts +8 -8
  57. package/src/multiValue/multiValueObservables.ts +297 -258
  58. package/src/multiValue/plugins/MultiValueLegend.ts +107 -107
  59. package/src/multiValue/plugins/MultiValueTooltip.ts +66 -66
  60. package/src/multiValue/plugins/Scatter.ts +426 -426
  61. package/src/multiValue/plugins/ScatterBubbles.ts +554 -551
  62. package/src/multiValue/plugins/XYAux.ts +681 -681
  63. package/src/multiValue/plugins/XYAxes.ts +684 -676
  64. package/src/multiValue/plugins/XYZoom.ts +299 -299
  65. package/src/noneData/defaults.ts +102 -102
  66. package/src/noneData/index.ts +3 -3
  67. package/src/noneData/plugins/Container.ts +27 -27
  68. package/src/noneData/plugins/Tooltip.ts +373 -373
  69. package/src/series/defaults.ts +206 -206
  70. package/src/series/index.ts +9 -9
  71. package/src/series/plugins/Bubbles.ts +603 -603
  72. package/src/series/plugins/Pie.ts +623 -623
  73. package/src/series/plugins/PieEventTexts.ts +283 -283
  74. package/src/series/plugins/PieLabels.ts +640 -640
  75. package/src/series/plugins/Rose.ts +516 -516
  76. package/src/series/plugins/RoseLabels.ts +600 -600
  77. package/src/series/plugins/SeriesLegend.ts +107 -107
  78. package/src/series/plugins/SeriesTooltip.ts +66 -66
  79. package/src/series/seriesObservables.ts +145 -145
  80. package/src/series/seriesUtils.ts +51 -51
  81. package/src/tree/defaults.ts +78 -78
  82. package/src/tree/index.ts +4 -4
  83. package/src/tree/plugins/TreeLegend.ts +100 -100
  84. package/src/tree/plugins/TreeMap.ts +333 -333
  85. package/src/tree/plugins/TreeTooltip.ts +66 -66
  86. package/src/utils/commonUtils.ts +21 -21
  87. package/src/utils/d3Graphics.ts +174 -174
  88. package/src/utils/d3Utils.ts +74 -74
  89. package/src/utils/observables.ts +14 -14
  90. package/src/utils/orbchartsUtils.ts +116 -101
  91. package/tsconfig.base.json +13 -13
  92. package/tsconfig.json +2 -2
  93. package/vite.config.js +22 -22
@@ -1,1104 +1,1104 @@
1
- import * as d3 from 'd3'
2
- import {
3
- // of,
4
- iif,
5
- EMPTY,
6
- combineLatest,
7
- switchMap,
8
- map,
9
- filter,
10
- first,
11
- takeUntil,
12
- distinctUntilChanged,
13
- shareReplay,
14
- Subject,
15
- Observable } from 'rxjs'
16
- import {
17
- defineGridPlugin } from '../../../lib/core'
18
- import type { DefinePluginConfig } from '../../../lib/core-types'
19
- import type {
20
- TransformData,
21
- DataFormatterGrid,
22
- ChartParams } from '../../../lib/core-types'
23
- import { DEFAULT_GROUP_AUX_PARAMS } from '../defaults'
24
- import { parseTickFormatValue } from '../../utils/d3Utils'
25
- import { measureTextWidth } from '../../utils/commonUtils'
26
- import { getColor, getClassName, getUniID } from '../../utils/orbchartsUtils'
27
- import { d3EventObservable } from '../../utils/observables'
28
- import { gridGroupPositionObservable } from '../gridObservables'
29
- import type { GroupAuxParams } from '../../../lib/plugins-basic-types'
30
- import { gridSelectionsObservable } from '../gridObservables'
31
- import { renderTspansOnAxis } from '../../utils/d3Graphics'
32
- import { LAYER_INDEX_OF_AUX } from '../../const'
33
-
34
- interface LineDatum {
35
- id: string
36
- x1: number
37
- x2: number
38
- y1: number
39
- y2: number
40
- }
41
-
42
- interface LabelDatum {
43
- id: string
44
- text: string
45
- textArr: string[]
46
- textWidth: number
47
- textHeight: number
48
- x: number
49
- y: number
50
- }
51
-
52
- const pluginName = 'GroupAux'
53
- const labelClassName = getClassName(pluginName, 'label-box')
54
-
55
- const pluginConfig: DefinePluginConfig<typeof pluginName, typeof DEFAULT_GROUP_AUX_PARAMS> = {
56
- name: pluginName,
57
- defaultParams: DEFAULT_GROUP_AUX_PARAMS,
58
- layerIndex: LAYER_INDEX_OF_AUX,
59
- validator: (params, { validateColumns }) => {
60
- const result = validateColumns(params, {
61
- showLine: {
62
- toBeTypes: ['boolean']
63
- },
64
- showLabel: {
65
- toBeTypes: ['boolean']
66
- },
67
- lineDashArray: {
68
- toBeTypes: ['string']
69
- },
70
- lineColorType: {
71
- toBeOption: 'ColorType'
72
- },
73
- labelColorType: {
74
- toBeOption: 'ColorType'
75
- },
76
- labelTextColorType: {
77
- toBeOption: 'ColorType'
78
- },
79
- labelTextFormat: {
80
- toBeTypes: ['string', 'Function']
81
- },
82
- labelPadding: {
83
- toBeTypes: ['number']
84
- },
85
- labelRotate: {
86
- toBeTypes: ['number']
87
- }
88
- })
89
- return result
90
- }
91
- }
92
-
93
- function createLineData ({ groupLabel, axisX, axisHeight, fullParams }: {
94
- groupLabel: string
95
- axisX: number
96
- axisHeight: number
97
- fullParams: GroupAuxParams
98
- }): LineDatum[] {
99
- return fullParams.showLine && groupLabel
100
- ? [{
101
- id: groupLabel,
102
- x1: axisX,
103
- x2: axisX,
104
- y1: 0,
105
- y2: axisHeight
106
- }]
107
- : []
108
- }
109
-
110
- function createLabelData ({ groupLabel, axisX, fullParams, textSizePx, rowAmount }: {
111
- groupLabel: string
112
- axisX: number
113
- fullParams: GroupAuxParams
114
- textSizePx: number
115
- rowAmount: number
116
- }) {
117
- const text = parseTickFormatValue(groupLabel, fullParams.labelTextFormat)
118
- const textArr = text.split('\n')
119
- const maxLengthText = textArr.reduce((acc, current) => current.length > acc.length ? current : acc, '')
120
- const textWidth = measureTextWidth(maxLengthText, textSizePx)
121
- const textHeight = textSizePx * textArr.length
122
- return fullParams.showLabel && groupLabel
123
- ? [{
124
- id: groupLabel,
125
- x: axisX,
126
- y: - fullParams.labelPadding * rowAmount, // rowAmount 是為了把外部 container 的變形逆轉回來
127
- text,
128
- textArr,
129
- textWidth,
130
- textHeight
131
- }]
132
- : []
133
- }
134
-
135
- function renderLine ({ selection, pluginName, lineData, fullParams, fullChartParams }: {
136
- selection: d3.Selection<any, string, any, unknown>
137
- pluginName: string
138
- lineData: LineDatum[]
139
- fullParams: GroupAuxParams
140
- fullChartParams: ChartParams
141
- }) {
142
- const gClassName = getClassName(pluginName, 'auxline')
143
- const auxLineSelection = selection
144
- .selectAll<SVGLineElement, LineDatum>(`line.${gClassName}`)
145
- .data(lineData)
146
- .join(
147
- enter => {
148
- return enter
149
- .append('line')
150
- .classed(gClassName, true)
151
- .style('stroke-width', 1)
152
- .style('pointer-events', 'none')
153
- .style('vector-effect', 'non-scaling-stroke')
154
- .attr('x1', d => d.x1)
155
- .attr('y1', d => d.y1)
156
- .attr('x2', d => d.x2)
157
- .attr('y2', d => d.y2)
158
- },
159
- update => {
160
- const updateSelection = update
161
- .transition()
162
- .duration(50)
163
- .attr('x1', d => d.x1)
164
- .attr('y1', d => d.y1)
165
- .attr('x2', d => d.x2)
166
- .attr('y2', d => d.y2)
167
- return updateSelection
168
- },
169
- exit => exit.remove()
170
- )
171
- .style('stroke', d => getColor(fullParams.lineColorType, fullChartParams))
172
- .style('stroke-dasharray', fullParams.lineDashArray ?? 'none')
173
-
174
- return auxLineSelection
175
- }
176
-
177
- function removeLine (selection: d3.Selection<any, string, any, unknown>) {
178
- const update = selection
179
- .selectAll<SVGLineElement, LineDatum>('line')
180
- .data([])
181
-
182
- update.exit().remove()
183
- }
184
-
185
- function renderLabel ({ selection, labelData, fullParams, fullDataFormatter, fullChartParams, textReverseTransformWithRotate, textSizePx }: {
186
- selection: d3.Selection<any, string, any, unknown>
187
- labelData: LabelDatum[]
188
- fullParams: GroupAuxParams
189
- fullDataFormatter: DataFormatterGrid
190
- fullChartParams: ChartParams
191
- // gridAxesReverseTransformValue: string
192
- textReverseTransformWithRotate: string
193
- textSizePx: number
194
- }) {
195
- // const rectHeight = textSizePx + 6
196
-
197
- const axisLabelSelection = selection
198
- .selectAll<SVGGElement, LabelDatum>(`g.${labelClassName}`)
199
- .data(labelData)
200
- .join(
201
- enter => {
202
- return enter
203
- .append('g')
204
- .classed(labelClassName, true)
205
- .style('cursor', 'pointer')
206
- .attr("transform", (d, i) => {
207
- return `translate(${d.x}, ${d.y})`
208
- })
209
- },
210
- update => {
211
- const updateSelection = update
212
- .transition()
213
- .duration(50)
214
- .attr("transform", (d, i) => {
215
- return `translate(${d.x}, ${d.y})`
216
- })
217
- return updateSelection
218
- },
219
- exit => exit.remove()
220
- )
221
- .each((datum, i, n) => {
222
- // const rectWidth = measureTextWidth(datum.text, textSizePx) + 12
223
- const rectWidth = datum.textWidth + 12
224
- const rectHeight = datum.textHeight + 6
225
- // -- label偏移位置 --
226
- let rectX = - rectWidth / 2
227
- let rectY = -2
228
- if (fullDataFormatter.grid.groupAxis.position === 'bottom') {
229
- rectX = fullParams.labelRotate
230
- ? - rectWidth + rectHeight // 有傾斜時以末端對齊(+height是為了修正移動太多)
231
- : - rectWidth / 2
232
- rectY = 2
233
- } else if (fullDataFormatter.grid.groupAxis.position === 'left') {
234
- rectX = - rectWidth + 2
235
- rectY = - rectHeight / 2
236
- } else if (fullDataFormatter.grid.groupAxis.position === 'right') {
237
- rectX = - 2
238
- rectY = - rectHeight / 2
239
- } else if (fullDataFormatter.grid.groupAxis.position === 'top') {
240
- rectX = fullParams.labelRotate
241
- ? - rectWidth + rectHeight // 有傾斜時以末端對齊(+height是為了修正移動太多)
242
- : - rectWidth / 2
243
- rectY = - rectHeight + 2
244
- }
245
-
246
- // -- rect --
247
- d3.select(n[i])
248
- .selectAll<SVGRectElement, LabelDatum>('rect')
249
- .data([datum])
250
- .join(
251
- enter => enter.append('rect')
252
- .style('cursor', 'pointer')
253
- .attr('rx', 5)
254
- .attr('ry', 5),
255
- update => update,
256
- exit => exit.remove()
257
- )
258
- .attr('width', d => `${rectWidth}px`)
259
- .attr('height', `${rectHeight}px`)
260
- .attr('fill', d => getColor(fullParams.labelColorType, fullChartParams))
261
- .attr('x', rectX)
262
- .attr('y', rectY - 3) // 奇怪的偏移修正
263
- .style('transform', textReverseTransformWithRotate)
264
-
265
- // const rectUpdate = d3.select(n[i])
266
- // .selectAll<SVGRectElement, LabelDatum>('rect')
267
- // .data([datum])
268
- // const rectEnter = rectUpdate
269
- // .enter()
270
- // .append('rect')
271
- // .attr('height', `${rectHeight}px`)
272
- // .attr('fill', d => getColor(fullParams.labelColorType, fullChartParams))
273
- // .attr('x', rectX)
274
- // .attr('y', rectY - 3) // 奇怪的偏移修正
275
- // .attr('rx', 5)
276
- // .attr('ry', 5)
277
- // .style('cursor', 'pointer')
278
- // // .style('pointer-events', 'none')
279
- // const rect = rectUpdate.merge(rectEnter)
280
- // .attr('width', d => `${rectWidth}px`)
281
- // .style('transform', textReverseTransformWithRotate)
282
- // rectUpdate.exit().remove()
283
-
284
- // -- text --
285
- d3.select(n[i])
286
- .selectAll<SVGTextElement, LabelDatum>('text')
287
- .data([datum])
288
- .join(
289
- enter => enter.append('text')
290
- .style('dominant-baseline', 'hanging')
291
- .style('cursor', 'pointer'),
292
- update => update,
293
- exit => exit.remove()
294
- )
295
- .style('transform', textReverseTransformWithRotate)
296
- .attr('fill', d => getColor(fullParams.labelTextColorType, fullChartParams))
297
- .attr('font-size', fullChartParams.styles.textSize)
298
- .attr('x', rectX + 6)
299
- .attr('y', rectY)
300
- .each((d, i, n) => {
301
- renderTspansOnAxis(d3.select(n[i]), {
302
- textArr: datum.textArr,
303
- textSizePx,
304
- groupAxisPosition: fullDataFormatter.grid.groupAxis.position
305
- })
306
- })
307
-
308
- // const textUpdate = d3.select(n[i])
309
- // .selectAll<SVGTextElement, LabelDatum>('text')
310
- // .data([datum])
311
- // const textEnter = textUpdate
312
- // .enter()
313
- // .append('text')
314
- // .style('dominant-baseline', 'hanging')
315
- // .style('cursor', 'pointer')
316
- // // .style('pointer-events', 'none')
317
- // const text = textUpdate.merge(textEnter)
318
- // // .text(d => d.text)
319
- // .style('transform', textReverseTransformWithRotate)
320
- // .attr('fill', d => getColor(fullParams.labelTextColorType, fullChartParams))
321
- // .attr('font-size', fullChartParams.styles.textSize)
322
- // .attr('x', rectX + 6)
323
- // .attr('y', rectY)
324
- // textUpdate.exit().remove()
325
-
326
- // text.each((d, i, n) => {
327
- // renderTspansOnAxis(d3.select(n[i]), {
328
- // textArr: datum.textArr,
329
- // textSizePx,
330
- // groupAxisPosition: fullDataFormatter.grid.groupAxis.position
331
- // })
332
- // })
333
- })
334
-
335
- return axisLabelSelection
336
- }
337
-
338
- function removeLabel (selection: d3.Selection<any, string, any, unknown>) {
339
- const gUpdate = selection
340
- .selectAll<SVGGElement, LabelDatum>(`g.${labelClassName}`)
341
- .data([])
342
-
343
- gUpdate.exit().remove()
344
- }
345
-
346
-
347
- export const GroupAux = defineGridPlugin(pluginConfig)(({ selection, rootSelection, name, subject, observer }) => {
348
- const destroy$ = new Subject()
349
-
350
- let isLabelMouseover = false
351
-
352
- const rootRectSelection: d3.Selection<SVGRectElement, any, any, any> = rootSelection
353
- .insert('rect', 'g')
354
- .classed(getClassName(pluginName, 'rect'), true)
355
- .attr('opacity', 0)
356
-
357
- // const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection
358
- // .append('g')
359
-
360
- const {
361
- seriesSelection$,
362
- axesSelection$,
363
- defsSelection$,
364
- graphicGSelection$
365
- } = gridSelectionsObservable({
366
- selection,
367
- pluginName,
368
- clipPathID: 'test',
369
- seriesLabels$: observer.isSeriesSeprate$.pipe(
370
- switchMap(isSeriesSeprate => {
371
- return iif(
372
- () => isSeriesSeprate,
373
- observer.seriesLabels$,
374
- // 如果沒分開的話只取一筆
375
- observer.seriesLabels$.pipe(
376
- map(d => [d[0]])
377
- )
378
- )
379
- })
380
- ),
381
- gridContainerPosition$: observer.gridContainerPosition$,
382
- gridAxesTransform$: observer.gridAxesTransform$,
383
- gridGraphicTransform$: observer.gridGraphicTransform$
384
- })
385
-
386
- observer.layout$.pipe(
387
- takeUntil(destroy$),
388
- ).subscribe(d => {
389
- rootRectSelection
390
- .attr('width', d.rootWidth)
391
- .attr('height', d.rootHeight)
392
- })
393
-
394
- // observer.gridAxesTransform$
395
- // .pipe(
396
- // takeUntil(destroy$),
397
- // map(d => d.value),
398
- // distinctUntilChanged()
399
- // ).subscribe(d => {
400
- // axisSelection
401
- // .style('transform', d)
402
- // })
403
-
404
- // const visibleComputedData$ = observer.computedData$.pipe(
405
- // takeUntil(destroy$),
406
- // map(data => {
407
- // const visibleComputedData = data
408
- // .map(d => {
409
- // return d.filter(_d => {
410
- // return _d.visible == true
411
- // })
412
- // })
413
- // .filter(d => d.length)
414
- // // console.log('visibleComputedData', visibleComputedData)
415
- // return visibleComputedData
416
- // })
417
- // )
418
-
419
- // const SeriesDataMap$ = visibleComputedData$.pipe(
420
- // map(d => makeGridSeriesDataMap(d))
421
- // )
422
-
423
- // const GroupDataMap$ = visibleComputedData$.pipe(
424
- // map(d => makeGridGroupDataMap(d))
425
- // )
426
-
427
- // const contentTransform$: Observable<string> = new Observable(subscriber => {
428
- // combineLatest({
429
- // fullParams: observer.fullParams$,
430
- // gridAxesTransform: observer.gridAxesTransform$
431
- // }).pipe(
432
- // takeUntil(destroy$),
433
- // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
434
- // switchMap(async (d) => d),
435
- // ).subscribe(data => {
436
-
437
- // const transformData = Object.assign({}, data.gridAxesTransform)
438
-
439
- // // const value = getAxesTransformValue({
440
- // // translate: [0, 0],
441
- // // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
442
- // // rotate: transformData.rotate * -1,
443
- // // rotateX: transformData.rotateX * -1,
444
- // // rotateY: transformData.rotateY * -1
445
- // // })
446
-
447
- // subscriber.next(transformData.value)
448
- // })
449
- // })
450
- // const reverseTransform$: Observable<TransformData> = observer.gridAxesTransform$.pipe(
451
- // takeUntil(destroy$),
452
- // map(d => {
453
- // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
454
- // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
455
- // const rotate = d.rotate * -1
456
- // const rotateX = d.rotateX * -1
457
- // const rotateY = d.rotateY * -1
458
- // return {
459
- // translate,
460
- // scale,
461
- // rotate,
462
- // rotateX,
463
- // rotateY,
464
- // value: ''
465
- // }
466
- // }),
467
- // )
468
- // const contentTransform$ = combineLatest({
469
- // fullParams: observer.fullParams$,
470
- // reverseTransform: reverseTransform$
471
- // }).pipe(
472
- // takeUntil(destroy$),
473
- // switchMap(async data => {
474
- // const translate = [0, 0]
475
- // return `translate(${translate[0]}px, ${translate[1]}px) rotate(${data.reverseTransform.rotate}deg) rotateX(${data.reverseTransform.rotateX}deg) rotateY(${data.reverseTransform.rotateY}deg)`
476
- // }),
477
- // distinctUntilChanged()
478
- // )
479
-
480
- // const groupScale$: Observable<d3.ScalePoint<string>> = new Observable(subscriber => {
481
- // combineLatest({
482
- // fullDataFormatter: observer.fullDataFormatter$,
483
- // gridAxesSize: observer.gridAxesSize$,
484
- // computedData: observer.computedData$
485
- // }).pipe(
486
- // takeUntil(destroy$),
487
- // switchMap(async (d) => d),
488
- // ).subscribe(data => {
489
- // const groupMin = 0
490
- // const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
491
- // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
492
- // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
493
- // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
494
- // const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'auto'
495
- // ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
496
- // : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
497
-
498
- // const groupingLength = data.computedData[0]
499
- // ? data.computedData[0].length
500
- // : 0
501
-
502
- // let _labels = data.fullDataFormatter.grid.seriesDirection === 'row'
503
- // // ? data.fullDataFormatter.grid.columnLabels
504
- // // : data.fullDataFormatter.grid.rowLabels
505
- // ? (data.computedData[0] ?? []).map(d => d.groupLabel)
506
- // : data.computedData.map(d => d[0].groupLabel)
507
-
508
- // const axisLabels = new Array(groupingLength).fill(0)
509
- // .map((d, i) => {
510
- // return _labels[i] != null
511
- // ? _labels[i]
512
- // : String(i) // 沒有label則用序列號填充
513
- // })
514
- // .filter((d, i) => {
515
- // return i >= groupScaleDomainMin && i <= groupScaleDomainMax
516
- // })
517
-
518
-
519
- // const padding = data.fullDataFormatter.grid.groupAxis.scalePadding
520
-
521
- // const groupScale = createLabelToAxisScale({
522
- // axisLabels,
523
- // axisWidth: data.gridAxesSize.width,
524
- // padding
525
- // })
526
-
527
- // subscriber.next(groupScale)
528
- // })
529
- // })
530
-
531
- // const groupScaleDomain$ = combineLatest({
532
- // fullDataFormatter: observer.fullDataFormatter$,
533
- // gridAxesSize: observer.gridAxesSize$,
534
- // computedData: observer.computedData$
535
- // }).pipe(
536
- // takeUntil(destroy$),
537
- // switchMap(async (d) => d),
538
- // map(data => {
539
- // const groupMin = 0
540
- // const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
541
- // // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
542
- // // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
543
- // // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
544
- // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] - data.fullDataFormatter.grid.groupAxis.scalePadding
545
- // const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'max'
546
- // ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
547
- // : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
548
-
549
- // return [groupScaleDomainMin, groupScaleDomainMax]
550
- // }),
551
- // shareReplay(1)
552
- // )
553
-
554
- const groupScale$ = combineLatest({
555
- groupScaleDomainValue: observer.groupScaleDomainValue$,
556
- gridAxesSize: observer.gridAxesSize$
557
- }).pipe(
558
- takeUntil(destroy$),
559
- switchMap(async (d) => d),
560
- map(data => {
561
- const groupScale: d3.ScaleLinear<number, number> = d3.scaleLinear()
562
- .domain(data.groupScaleDomainValue)
563
- .range([0, data.gridAxesSize.width])
564
- return groupScale
565
- })
566
- )
567
-
568
- // // 取得事件座標的group資料
569
- // const gridGroupPositionFn$ = gridGroupPositionFnObservable({
570
- // fullDataFormatter$: observer.fullDataFormatter$,
571
- // gridAxesSize$: observer.gridAxesSize$,
572
- // computedData$: observer.computedData$,
573
- // fullChartParams$: observer.fullChartParams$,
574
- // })
575
-
576
- const highlightTarget$ = observer.fullChartParams$.pipe(
577
- takeUntil(destroy$),
578
- map(d => d.highlightTarget),
579
- distinctUntilChanged()
580
- )
581
-
582
- // combineLatest({
583
- // computedData: observer.computedData$,
584
- // gridAxesSize: observer.gridAxesSize$,
585
- // fullParams: observer.fullParams$,
586
- // fullChartParams: observer.fullChartParams$,
587
- // highlightTarget: highlightTarget$,
588
- // SeriesDataMap: observer.SeriesDataMap$,
589
- // GroupDataMap: observer.GroupDataMap$,
590
- // gridGroupPositionFn: gridGroupPositionFn$,
591
- // groupScale: groupScale$,
592
- // }).pipe(
593
- // takeUntil(destroy$),
594
- // switchMap(async (d) => d),
595
- // ).subscribe(data => {
596
-
597
- // // store.selection
598
- // rootSelection
599
- // .on('mouseover', (event, datum) => {
600
- // // event.stopPropagation()
601
-
602
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
603
-
604
- // subject.event$.next({
605
- // type: 'grid',
606
- // pluginName: name,
607
- // eventName: 'mouseover',
608
- // highlightTarget: data.highlightTarget,
609
- // datum: null,
610
- // gridIndex: 0,
611
- // series: [],
612
- // seriesIndex: -1,
613
- // seriesLabel: '',
614
- // groups: data.GroupDataMap.get(groupLabel) ?? [],
615
- // // groups: [],
616
- // groupIndex,
617
- // groupLabel,
618
- // event,
619
- // data: data.computedData
620
- // })
621
- // })
622
- // .on('mousemove', (event, datum) => {
623
- // // event.stopPropagation()
624
-
625
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
626
-
627
- // subject.event$.next({
628
- // type: 'grid',
629
- // pluginName: name,
630
- // eventName: 'mousemove',
631
- // highlightTarget: data.highlightTarget,
632
- // datum: null,
633
- // gridIndex: 0,
634
- // series: [],
635
- // seriesIndex: -1,
636
- // seriesLabel: '',
637
- // groups: data.GroupDataMap.get(groupLabel) ?? [],
638
- // // groups: [],
639
- // groupIndex,
640
- // groupLabel,
641
- // event,
642
- // data: data.computedData
643
- // })
644
- // })
645
- // .on('mouseout', (event, datum) => {
646
- // // event.stopPropagation()
647
-
648
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
649
-
650
- // subject.event$.next({
651
- // type: 'grid',
652
- // pluginName: name,
653
- // eventName: 'mouseout',
654
- // highlightTarget: data.highlightTarget,
655
- // datum: null,
656
- // gridIndex: 0,
657
- // series: [],
658
- // seriesIndex: -1,
659
- // seriesLabel: '',
660
- // groups: data.GroupDataMap.get(groupLabel) ?? [],
661
- // // groups: [],
662
- // groupIndex,
663
- // groupLabel,
664
- // event,
665
- // data: data.computedData
666
- // })
667
- // })
668
- // .on('click', (event, datum) => {
669
- // event.stopPropagation()
670
-
671
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
672
-
673
- // subject.event$.next({
674
- // type: 'grid',
675
- // pluginName: name,
676
- // eventName: 'click',
677
- // highlightTarget: data.highlightTarget,
678
- // datum: null,
679
- // gridIndex: 0,
680
- // series: [],
681
- // seriesIndex: -1,
682
- // seriesLabel: '',
683
- // // groups: data.GroupDataMap.get(groupLabel) ?? [],
684
- // groups: [],
685
- // groupIndex,
686
- // groupLabel,
687
- // event,
688
- // data: data.computedData
689
- // })
690
- // })
691
-
692
- // // barSelection$.next(barSelection!)
693
- // })
694
-
695
- const rootMousemove$: Observable<any> = d3EventObservable(rootSelection, 'mousemove').pipe(
696
- takeUntil(destroy$),
697
- )
698
-
699
- // const mousemoveGroupLabel$ = combineLatest({
700
- // rootMousemove: rootMousemove$,
701
- // gridGroupPositionFn: gridGroupPositionFn$,
702
- // }).pipe(
703
- // takeUntil(destroy$),
704
- // switchMap(async d => d),
705
- // map(data => {
706
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(data.rootMousemove)
707
- // return { groupIndex, groupLabel }
708
- // }),
709
- // shareReplay(1)
710
- // )
711
-
712
-
713
- const textReverseTransform$ = combineLatest({
714
- gridAxesReverseTransform: observer.gridAxesReverseTransform$,
715
- gridContainerPosition: observer.gridContainerPosition$
716
- }).pipe(
717
- takeUntil(destroy$),
718
- switchMap(async (d) => d),
719
- map(data => {
720
- // const axisReverseTranslateValue = `translate(${data.gridAxesReverseTransform.translate[0]}px, ${data.gridAxesReverseTransform.translate[1]}px)`
721
- const axesRotateXYReverseValue = `rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
722
- const axesRotateReverseValue = `rotate(${data.gridAxesReverseTransform.rotate}deg)`
723
- const containerScaleReverseValue = `scale(${1 / data.gridContainerPosition[0].scale[0]}, ${1 / data.gridContainerPosition[0].scale[1]})`
724
- // 必須按照順序(先抵消外層rotate,再抵消最外層scale)
725
- return `${axesRotateXYReverseValue} ${axesRotateReverseValue} ${containerScaleReverseValue}`
726
- }),
727
- distinctUntilChanged()
728
- )
729
-
730
- const textReverseTransformWithRotate$ = combineLatest({
731
- textReverseTransform: textReverseTransform$,
732
- fullParams: observer.fullParams$,
733
- }).pipe(
734
- takeUntil(destroy$),
735
- switchMap(async (d) => d),
736
- map(data => {
737
- // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
738
- return `${data.textReverseTransform} rotate(${data.fullParams.labelRotate}deg)`
739
- })
740
- )
741
-
742
- const columnAmount$ = observer.gridContainerPosition$.pipe(
743
- takeUntil(destroy$),
744
- map(gridContainerPosition => {
745
- const maxColumnIndex = gridContainerPosition.reduce((acc, current) => {
746
- return current.columnIndex > acc ? current.columnIndex : acc
747
- }, 0)
748
- return maxColumnIndex + 1
749
- }),
750
- distinctUntilChanged()
751
- )
752
-
753
- const rowAmount$ = observer.gridContainerPosition$.pipe(
754
- takeUntil(destroy$),
755
- map(gridContainerPosition => {
756
- const maxRowIndex = gridContainerPosition.reduce((acc, current) => {
757
- return current.rowIndex > acc ? current.rowIndex : acc
758
- }, 0)
759
- return maxRowIndex + 1
760
- }),
761
- distinctUntilChanged()
762
- )
763
-
764
- const gridGroupPosition$ = gridGroupPositionObservable({
765
- rootSelection,
766
- fullDataFormatter$: observer.fullDataFormatter$,
767
- gridAxesSize$: observer.gridAxesSize$,
768
- computedData$: observer.computedData$,
769
- fullChartParams$: observer.fullChartParams$,
770
- gridContainerPosition$: observer.gridContainerPosition$,
771
- layout$: observer.layout$
772
- }).pipe(
773
- takeUntil(destroy$)
774
- )
775
-
776
- combineLatest({
777
- axesSelection: axesSelection$,
778
- columnAmount: columnAmount$,
779
- rowAmount: rowAmount$,
780
- layout: observer.layout$,
781
- rootMousemove: rootMousemove$,
782
- // gridGroupPositionFn: gridGroupPositionFn$,
783
- gridGroupPosition: gridGroupPosition$,
784
- computedData: observer.computedData$,
785
- groupScale: groupScale$,
786
- gridAxesSize: observer.gridAxesSize$,
787
- fullParams: observer.fullParams$,
788
- fullDataFormatter: observer.fullDataFormatter$,
789
- fullChartParams: observer.fullChartParams$,
790
- highlightTarget: highlightTarget$,
791
- // gridAxesReverseTransform: observer.gridAxesReverseTransform$,
792
- textReverseTransformWithRotate: textReverseTransformWithRotate$,
793
- GroupDataMap: observer.GroupDataMap$,
794
- textSizePx: observer.textSizePx$
795
- }).pipe(
796
- takeUntil(destroy$),
797
- switchMap(async d => d),
798
- ).subscribe(data => {
799
- // // 由於event座標是基於底層的,但是container會有多欄,所以要重新計算
800
- // const eventData = {
801
- // offsetX: data.rootMousemove.offsetX * data.columnAmount % data.layout.rootWidth,
802
- // offsetY: data.rootMousemove.offsetY * data.rowAmount % data.layout.rootHeight
803
- // }
804
- // 依event的座標取得group資料
805
- const { groupIndex, groupLabel } = data.gridGroupPosition
806
- // console.log('gridGroupPosition', groupIndex, groupLabel)
807
- const axisX = data.groupScale(groupIndex) ?? 0
808
- // console.log('axisX', axisX)
809
- const lineData = createLineData({
810
- groupLabel: groupLabel,
811
- axisX,
812
- axisHeight: data.gridAxesSize.height,
813
- fullParams: data.fullParams,
814
- })
815
- // console.log('lineData', lineData)
816
- renderLine({
817
- // selection: axisSelection,
818
- selection: data.axesSelection,
819
- pluginName: name,
820
- lineData,
821
- fullParams: data.fullParams,
822
- fullChartParams: data.fullChartParams
823
- })
824
- const labelData = createLabelData({
825
- groupLabel: groupLabel,
826
- axisX,
827
- fullParams: data.fullParams,
828
- textSizePx: data.textSizePx,
829
- rowAmount: data.rowAmount
830
- })
831
- const labelSelection = renderLabel({
832
- // selection: axisSelection,
833
- selection: data.axesSelection,
834
- labelData,
835
- fullParams: data.fullParams,
836
- fullDataFormatter: data.fullDataFormatter,
837
- fullChartParams: data.fullChartParams,
838
- // gridAxesReverseTransformValue: data.gridAxesReverseTransform.value,
839
- textReverseTransformWithRotate: data.textReverseTransformWithRotate,
840
- textSizePx: data.textSizePx,
841
- })
842
-
843
- // label的事件
844
- labelSelection
845
- .on('mouseover', (event, datum) => {
846
- event.stopPropagation()
847
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
848
-
849
- isLabelMouseover = true
850
-
851
- subject.event$.next({
852
- type: 'grid',
853
- pluginName: name,
854
- eventName: 'mouseover',
855
- highlightTarget: data.highlightTarget,
856
- datum: null,
857
- gridIndex: 0,
858
- series: [],
859
- seriesIndex: -1,
860
- seriesLabel: '',
861
- groups: data.GroupDataMap.get(groupLabel) ?? [],
862
- groupIndex,
863
- groupLabel,
864
- event,
865
- data: data.computedData
866
- })
867
- })
868
- .on('mousemove', (event, datum) => {
869
- event.stopPropagation()
870
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
871
-
872
- subject.event$.next({
873
- type: 'grid',
874
- pluginName: name,
875
- eventName: 'mousemove',
876
- highlightTarget: data.highlightTarget,
877
- datum: null,
878
- gridIndex: 0,
879
- series: [],
880
- seriesIndex: -1,
881
- seriesLabel: '',
882
- groups: data.GroupDataMap.get(groupLabel) ?? [],
883
- groupIndex,
884
- groupLabel,
885
- event,
886
- data: data.computedData
887
- })
888
- })
889
- .on('mouseout', (event, datum) => {
890
- event.stopPropagation()
891
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
892
-
893
- isLabelMouseover = false
894
-
895
- subject.event$.next({
896
- type: 'grid',
897
- pluginName: name,
898
- eventName: 'mouseout',
899
- highlightTarget: data.highlightTarget,
900
- datum: null,
901
- gridIndex: 0,
902
- series: [],
903
- seriesIndex: -1,
904
- seriesLabel: '',
905
- groups: data.GroupDataMap.get(groupLabel) ?? [],
906
- groupIndex,
907
- groupLabel,
908
- event,
909
- data: data.computedData
910
- })
911
- })
912
- .on('click', (event, datum) => {
913
- event.stopPropagation()
914
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
915
-
916
- subject.event$.next({
917
- type: 'grid',
918
- pluginName: name,
919
- eventName: 'click',
920
- highlightTarget: data.highlightTarget,
921
- datum: null,
922
- gridIndex: 0,
923
- series: [],
924
- seriesIndex: -1,
925
- seriesLabel: '',
926
- groups: data.GroupDataMap.get(groupLabel) ?? [],
927
- groupIndex,
928
- groupLabel,
929
- event,
930
- data: data.computedData
931
- })
932
- })
933
-
934
- })
935
-
936
- // // -- highlight(無論highlightTarget設定為何,一律依從groupLabel來顯示) --
937
- // combineLatest({
938
- // event: subject.event$.pipe(
939
- // filter(d => d.eventName === 'mouseover' || d.eventName === 'mousemove')
940
- // ),
941
- // computedData: observer.computedData$,
942
- // groupScale: groupScale$,
943
- // gridAxesSize: observer.gridAxesSize$,
944
- // fullParams: observer.fullParams$,
945
- // fullChartParams: observer.fullChartParams$,
946
- // highlightTarget: highlightTarget$,
947
- // gridAxesReverseTransform: observer.gridAxesReverseTransform$,
948
- // GroupDataMap: observer.GroupDataMap$,
949
- // gridGroupPositionFn: gridGroupPositionFn$,
950
- // textSizePx: observer.textSizePx$
951
- // }).pipe(
952
- // takeUntil(destroy$),
953
- // switchMap(async d => d)
954
- // ).subscribe(data => {
955
- // // const groups = data.event.eventName === 'mouseover' || data.event.eventName === 'mousemove'
956
- // // ? data.event.groups
957
- // // : []
958
-
959
- // // const groupLabel = data.event.eventName === 'mouseover' || data.event.eventName === 'mousemove'
960
- // // ? data.event.groupLabel
961
- // // : ''
962
- // const axisX = data.groupScale(data.event.groupLabel) ?? 0
963
-
964
- // const lineData = createLineData({
965
- // groupLabel: data.event.groupLabel,
966
- // axisX,
967
- // axisHeight: data.gridAxesSize.height,
968
- // fullParams: data.fullParams,
969
- // })
970
- // renderLine({
971
- // selection: axisSelection,
972
- // pluginName: name,
973
- // lineData,
974
- // fullParams: data.fullParams,
975
- // fullChartParams: data.fullChartParams
976
- // })
977
- // const labelData = createLabelData({
978
- // groupLabel: data.event.groupLabel,
979
- // axisX,
980
- // fullParams: data.fullParams
981
- // })
982
- // const labelSelection = renderLabel({
983
- // selection: axisSelection,
984
- // labelData,
985
- // fullParams: data.fullParams,
986
- // fullChartParams: data.fullChartParams,
987
- // gridAxesReverseTransformValue: data.gridAxesReverseTransform.value,
988
- // textSizePx: data.textSizePx
989
- // })
990
-
991
- // // label的事件
992
- // labelSelection
993
- // .on('mouseover', (event, datum) => {
994
-
995
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
996
-
997
- // subject.event$.next({
998
- // type: 'grid',
999
- // pluginName: name,
1000
- // eventName: 'mouseover',
1001
- // highlightTarget: data.highlightTarget,
1002
- // datum: null,
1003
- // gridIndex: 0,
1004
- // series: [],
1005
- // seriesIndex: -1,
1006
- // seriesLabel: '',
1007
- // groups: data.event.groups,
1008
- // groupIndex,
1009
- // groupLabel,
1010
- // event,
1011
- // data: data.computedData
1012
- // })
1013
- // })
1014
- // .on('mousemove', (event, datum) => {
1015
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
1016
-
1017
- // subject.event$.next({
1018
- // type: 'grid',
1019
- // pluginName: name,
1020
- // eventName: 'mousemove',
1021
- // highlightTarget: data.highlightTarget,
1022
- // datum: null,
1023
- // gridIndex: 0,
1024
- // series: [],
1025
- // seriesIndex: -1,
1026
- // seriesLabel: '',
1027
- // groups: data.event.groups,
1028
- // groupIndex,
1029
- // groupLabel,
1030
- // event,
1031
- // data: data.computedData
1032
- // })
1033
- // })
1034
- // .on('mouseout', (event, datum) => {
1035
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
1036
-
1037
- // subject.event$.next({
1038
- // type: 'grid',
1039
- // pluginName: name,
1040
- // eventName: 'mouseout',
1041
- // highlightTarget: data.highlightTarget,
1042
- // datum: null,
1043
- // gridIndex: 0,
1044
- // series: [],
1045
- // seriesIndex: -1,
1046
- // seriesLabel: '',
1047
- // groups: data.event.groups,
1048
- // groupIndex,
1049
- // groupLabel,
1050
- // event,
1051
- // data: data.computedData
1052
- // })
1053
- // })
1054
- // .on('click', (event, datum) => {
1055
- // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
1056
-
1057
- // subject.event$.next({
1058
- // type: 'grid',
1059
- // pluginName: name,
1060
- // eventName: 'click',
1061
- // highlightTarget: data.highlightTarget,
1062
- // datum: null,
1063
- // gridIndex: 0,
1064
- // series: [],
1065
- // seriesIndex: -1,
1066
- // seriesLabel: '',
1067
- // groups: data.event.groups,
1068
- // groupIndex,
1069
- // groupLabel,
1070
- // event,
1071
- // data: data.computedData
1072
- // })
1073
- // })
1074
- // })
1075
-
1076
-
1077
-
1078
- const rootRectMouseout$ = d3EventObservable(rootRectSelection, 'mouseout').pipe(
1079
- takeUntil(destroy$),
1080
- )
1081
-
1082
- combineLatest({
1083
- rootRectMouseout: rootRectMouseout$,
1084
- axesSelection: axesSelection$,
1085
- }).pipe(
1086
- takeUntil(destroy$),
1087
- switchMap(async d => d)
1088
- ).subscribe(data => {
1089
- setTimeout(() => {
1090
- // @Q@ workaround - 不知為何和 label 會有衝突,當滑鼠移動到 label 上時,會觸發 mouseout 事件
1091
- if (isLabelMouseover == true) {
1092
- return
1093
- }
1094
-
1095
- removeLine(data.axesSelection)
1096
- removeLabel(data.axesSelection)
1097
- })
1098
- })
1099
-
1100
- return () => {
1101
- destroy$.next(undefined)
1102
- rootRectSelection.remove()
1103
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ // of,
4
+ iif,
5
+ EMPTY,
6
+ combineLatest,
7
+ switchMap,
8
+ map,
9
+ filter,
10
+ first,
11
+ takeUntil,
12
+ distinctUntilChanged,
13
+ shareReplay,
14
+ Subject,
15
+ Observable } from 'rxjs'
16
+ import {
17
+ defineGridPlugin } from '../../../lib/core'
18
+ import type { DefinePluginConfig } from '../../../lib/core-types'
19
+ import type {
20
+ TransformData,
21
+ DataFormatterGrid,
22
+ ChartParams } from '../../../lib/core-types'
23
+ import { DEFAULT_GROUP_AUX_PARAMS } from '../defaults'
24
+ import { parseTickFormatValue } from '../../utils/d3Utils'
25
+ import { measureTextWidth } from '../../utils/commonUtils'
26
+ import { getColor, getClassName, getUniID } from '../../utils/orbchartsUtils'
27
+ import { d3EventObservable } from '../../utils/observables'
28
+ import { gridGroupPositionObservable } from '../gridObservables'
29
+ import type { GroupAuxParams } from '../../../lib/plugins-basic-types'
30
+ import { gridSelectionsObservable } from '../gridObservables'
31
+ import { renderTspansOnAxis } from '../../utils/d3Graphics'
32
+ import { LAYER_INDEX_OF_AUX } from '../../const'
33
+
34
+ interface LineDatum {
35
+ id: string
36
+ x1: number
37
+ x2: number
38
+ y1: number
39
+ y2: number
40
+ }
41
+
42
+ interface LabelDatum {
43
+ id: string
44
+ text: string
45
+ textArr: string[]
46
+ textWidth: number
47
+ textHeight: number
48
+ x: number
49
+ y: number
50
+ }
51
+
52
+ const pluginName = 'GroupAux'
53
+ const labelClassName = getClassName(pluginName, 'label-box')
54
+
55
+ const pluginConfig: DefinePluginConfig<typeof pluginName, typeof DEFAULT_GROUP_AUX_PARAMS> = {
56
+ name: pluginName,
57
+ defaultParams: DEFAULT_GROUP_AUX_PARAMS,
58
+ layerIndex: LAYER_INDEX_OF_AUX,
59
+ validator: (params, { validateColumns }) => {
60
+ const result = validateColumns(params, {
61
+ showLine: {
62
+ toBeTypes: ['boolean']
63
+ },
64
+ showLabel: {
65
+ toBeTypes: ['boolean']
66
+ },
67
+ lineDashArray: {
68
+ toBeTypes: ['string']
69
+ },
70
+ lineColorType: {
71
+ toBeOption: 'ColorType'
72
+ },
73
+ labelColorType: {
74
+ toBeOption: 'ColorType'
75
+ },
76
+ labelTextColorType: {
77
+ toBeOption: 'ColorType'
78
+ },
79
+ labelTextFormat: {
80
+ toBeTypes: ['string', 'Function']
81
+ },
82
+ labelPadding: {
83
+ toBeTypes: ['number']
84
+ },
85
+ labelRotate: {
86
+ toBeTypes: ['number']
87
+ }
88
+ })
89
+ return result
90
+ }
91
+ }
92
+
93
+ function createLineData ({ groupLabel, axisX, axisHeight, fullParams }: {
94
+ groupLabel: string
95
+ axisX: number
96
+ axisHeight: number
97
+ fullParams: GroupAuxParams
98
+ }): LineDatum[] {
99
+ return fullParams.showLine && groupLabel
100
+ ? [{
101
+ id: groupLabel,
102
+ x1: axisX,
103
+ x2: axisX,
104
+ y1: 0,
105
+ y2: axisHeight
106
+ }]
107
+ : []
108
+ }
109
+
110
+ function createLabelData ({ groupLabel, axisX, fullParams, textSizePx, rowAmount }: {
111
+ groupLabel: string
112
+ axisX: number
113
+ fullParams: GroupAuxParams
114
+ textSizePx: number
115
+ rowAmount: number
116
+ }) {
117
+ const text = parseTickFormatValue(groupLabel, fullParams.labelTextFormat)
118
+ const textArr = text.split('\n')
119
+ const maxLengthText = textArr.reduce((acc, current) => current.length > acc.length ? current : acc, '')
120
+ const textWidth = measureTextWidth(maxLengthText, textSizePx)
121
+ const textHeight = textSizePx * textArr.length
122
+ return fullParams.showLabel && groupLabel
123
+ ? [{
124
+ id: groupLabel,
125
+ x: axisX,
126
+ y: - fullParams.labelPadding * rowAmount, // rowAmount 是為了把外部 container 的變形逆轉回來
127
+ text,
128
+ textArr,
129
+ textWidth,
130
+ textHeight
131
+ }]
132
+ : []
133
+ }
134
+
135
+ function renderLine ({ selection, pluginName, lineData, fullParams, fullChartParams }: {
136
+ selection: d3.Selection<any, string, any, unknown>
137
+ pluginName: string
138
+ lineData: LineDatum[]
139
+ fullParams: GroupAuxParams
140
+ fullChartParams: ChartParams
141
+ }) {
142
+ const gClassName = getClassName(pluginName, 'auxline')
143
+ const auxLineSelection = selection
144
+ .selectAll<SVGLineElement, LineDatum>(`line.${gClassName}`)
145
+ .data(lineData)
146
+ .join(
147
+ enter => {
148
+ return enter
149
+ .append('line')
150
+ .classed(gClassName, true)
151
+ .style('stroke-width', 1)
152
+ .style('pointer-events', 'none')
153
+ .style('vector-effect', 'non-scaling-stroke')
154
+ .attr('x1', d => d.x1)
155
+ .attr('y1', d => d.y1)
156
+ .attr('x2', d => d.x2)
157
+ .attr('y2', d => d.y2)
158
+ },
159
+ update => {
160
+ const updateSelection = update
161
+ .transition()
162
+ .duration(50)
163
+ .attr('x1', d => d.x1)
164
+ .attr('y1', d => d.y1)
165
+ .attr('x2', d => d.x2)
166
+ .attr('y2', d => d.y2)
167
+ return updateSelection
168
+ },
169
+ exit => exit.remove()
170
+ )
171
+ .style('stroke', d => getColor(fullParams.lineColorType, fullChartParams))
172
+ .style('stroke-dasharray', fullParams.lineDashArray ?? 'none')
173
+
174
+ return auxLineSelection
175
+ }
176
+
177
+ function removeLine (selection: d3.Selection<any, string, any, unknown>) {
178
+ const update = selection
179
+ .selectAll<SVGLineElement, LineDatum>('line')
180
+ .data([])
181
+
182
+ update.exit().remove()
183
+ }
184
+
185
+ function renderLabel ({ selection, labelData, fullParams, fullDataFormatter, fullChartParams, textReverseTransformWithRotate, textSizePx }: {
186
+ selection: d3.Selection<any, string, any, unknown>
187
+ labelData: LabelDatum[]
188
+ fullParams: GroupAuxParams
189
+ fullDataFormatter: DataFormatterGrid
190
+ fullChartParams: ChartParams
191
+ // gridAxesReverseTransformValue: string
192
+ textReverseTransformWithRotate: string
193
+ textSizePx: number
194
+ }) {
195
+ // const rectHeight = textSizePx + 6
196
+
197
+ const axisLabelSelection = selection
198
+ .selectAll<SVGGElement, LabelDatum>(`g.${labelClassName}`)
199
+ .data(labelData)
200
+ .join(
201
+ enter => {
202
+ return enter
203
+ .append('g')
204
+ .classed(labelClassName, true)
205
+ .style('cursor', 'pointer')
206
+ .attr("transform", (d, i) => {
207
+ return `translate(${d.x}, ${d.y})`
208
+ })
209
+ },
210
+ update => {
211
+ const updateSelection = update
212
+ .transition()
213
+ .duration(50)
214
+ .attr("transform", (d, i) => {
215
+ return `translate(${d.x}, ${d.y})`
216
+ })
217
+ return updateSelection
218
+ },
219
+ exit => exit.remove()
220
+ )
221
+ .each((datum, i, n) => {
222
+ // const rectWidth = measureTextWidth(datum.text, textSizePx) + 12
223
+ const rectWidth = datum.textWidth + 12
224
+ const rectHeight = datum.textHeight + 6
225
+ // -- label偏移位置 --
226
+ let rectX = - rectWidth / 2
227
+ let rectY = -2
228
+ if (fullDataFormatter.grid.groupAxis.position === 'bottom') {
229
+ rectX = fullParams.labelRotate
230
+ ? - rectWidth + rectHeight // 有傾斜時以末端對齊(+height是為了修正移動太多)
231
+ : - rectWidth / 2
232
+ rectY = 2
233
+ } else if (fullDataFormatter.grid.groupAxis.position === 'left') {
234
+ rectX = - rectWidth + 2
235
+ rectY = - rectHeight / 2
236
+ } else if (fullDataFormatter.grid.groupAxis.position === 'right') {
237
+ rectX = - 2
238
+ rectY = - rectHeight / 2
239
+ } else if (fullDataFormatter.grid.groupAxis.position === 'top') {
240
+ rectX = fullParams.labelRotate
241
+ ? - rectWidth + rectHeight // 有傾斜時以末端對齊(+height是為了修正移動太多)
242
+ : - rectWidth / 2
243
+ rectY = - rectHeight + 2
244
+ }
245
+
246
+ // -- rect --
247
+ d3.select(n[i])
248
+ .selectAll<SVGRectElement, LabelDatum>('rect')
249
+ .data([datum])
250
+ .join(
251
+ enter => enter.append('rect')
252
+ .style('cursor', 'pointer')
253
+ .attr('rx', 5)
254
+ .attr('ry', 5),
255
+ update => update,
256
+ exit => exit.remove()
257
+ )
258
+ .attr('width', d => `${rectWidth}px`)
259
+ .attr('height', `${rectHeight}px`)
260
+ .attr('fill', d => getColor(fullParams.labelColorType, fullChartParams))
261
+ .attr('x', rectX)
262
+ .attr('y', rectY - 3) // 奇怪的偏移修正
263
+ .style('transform', textReverseTransformWithRotate)
264
+
265
+ // const rectUpdate = d3.select(n[i])
266
+ // .selectAll<SVGRectElement, LabelDatum>('rect')
267
+ // .data([datum])
268
+ // const rectEnter = rectUpdate
269
+ // .enter()
270
+ // .append('rect')
271
+ // .attr('height', `${rectHeight}px`)
272
+ // .attr('fill', d => getColor(fullParams.labelColorType, fullChartParams))
273
+ // .attr('x', rectX)
274
+ // .attr('y', rectY - 3) // 奇怪的偏移修正
275
+ // .attr('rx', 5)
276
+ // .attr('ry', 5)
277
+ // .style('cursor', 'pointer')
278
+ // // .style('pointer-events', 'none')
279
+ // const rect = rectUpdate.merge(rectEnter)
280
+ // .attr('width', d => `${rectWidth}px`)
281
+ // .style('transform', textReverseTransformWithRotate)
282
+ // rectUpdate.exit().remove()
283
+
284
+ // -- text --
285
+ d3.select(n[i])
286
+ .selectAll<SVGTextElement, LabelDatum>('text')
287
+ .data([datum])
288
+ .join(
289
+ enter => enter.append('text')
290
+ .style('dominant-baseline', 'hanging')
291
+ .style('cursor', 'pointer'),
292
+ update => update,
293
+ exit => exit.remove()
294
+ )
295
+ .style('transform', textReverseTransformWithRotate)
296
+ .attr('fill', d => getColor(fullParams.labelTextColorType, fullChartParams))
297
+ .attr('font-size', fullChartParams.styles.textSize)
298
+ .attr('x', rectX + 6)
299
+ .attr('y', rectY)
300
+ .each((d, i, n) => {
301
+ renderTspansOnAxis(d3.select(n[i]), {
302
+ textArr: datum.textArr,
303
+ textSizePx,
304
+ groupAxisPosition: fullDataFormatter.grid.groupAxis.position
305
+ })
306
+ })
307
+
308
+ // const textUpdate = d3.select(n[i])
309
+ // .selectAll<SVGTextElement, LabelDatum>('text')
310
+ // .data([datum])
311
+ // const textEnter = textUpdate
312
+ // .enter()
313
+ // .append('text')
314
+ // .style('dominant-baseline', 'hanging')
315
+ // .style('cursor', 'pointer')
316
+ // // .style('pointer-events', 'none')
317
+ // const text = textUpdate.merge(textEnter)
318
+ // // .text(d => d.text)
319
+ // .style('transform', textReverseTransformWithRotate)
320
+ // .attr('fill', d => getColor(fullParams.labelTextColorType, fullChartParams))
321
+ // .attr('font-size', fullChartParams.styles.textSize)
322
+ // .attr('x', rectX + 6)
323
+ // .attr('y', rectY)
324
+ // textUpdate.exit().remove()
325
+
326
+ // text.each((d, i, n) => {
327
+ // renderTspansOnAxis(d3.select(n[i]), {
328
+ // textArr: datum.textArr,
329
+ // textSizePx,
330
+ // groupAxisPosition: fullDataFormatter.grid.groupAxis.position
331
+ // })
332
+ // })
333
+ })
334
+
335
+ return axisLabelSelection
336
+ }
337
+
338
+ function removeLabel (selection: d3.Selection<any, string, any, unknown>) {
339
+ const gUpdate = selection
340
+ .selectAll<SVGGElement, LabelDatum>(`g.${labelClassName}`)
341
+ .data([])
342
+
343
+ gUpdate.exit().remove()
344
+ }
345
+
346
+
347
+ export const GroupAux = defineGridPlugin(pluginConfig)(({ selection, rootSelection, name, subject, observer }) => {
348
+ const destroy$ = new Subject()
349
+
350
+ let isLabelMouseover = false
351
+
352
+ const rootRectSelection: d3.Selection<SVGRectElement, any, any, any> = rootSelection
353
+ .insert('rect', 'g')
354
+ .classed(getClassName(pluginName, 'rect'), true)
355
+ .attr('opacity', 0)
356
+
357
+ // const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection
358
+ // .append('g')
359
+
360
+ const {
361
+ seriesSelection$,
362
+ axesSelection$,
363
+ defsSelection$,
364
+ graphicGSelection$
365
+ } = gridSelectionsObservable({
366
+ selection,
367
+ pluginName,
368
+ clipPathID: 'test',
369
+ seriesLabels$: observer.isSeriesSeprate$.pipe(
370
+ switchMap(isSeriesSeprate => {
371
+ return iif(
372
+ () => isSeriesSeprate,
373
+ observer.seriesLabels$,
374
+ // 如果沒分開的話只取一筆
375
+ observer.seriesLabels$.pipe(
376
+ map(d => [d[0]])
377
+ )
378
+ )
379
+ })
380
+ ),
381
+ gridContainerPosition$: observer.gridContainerPosition$,
382
+ gridAxesTransform$: observer.gridAxesTransform$,
383
+ gridGraphicTransform$: observer.gridGraphicTransform$
384
+ })
385
+
386
+ observer.layout$.pipe(
387
+ takeUntil(destroy$),
388
+ ).subscribe(d => {
389
+ rootRectSelection
390
+ .attr('width', d.rootWidth)
391
+ .attr('height', d.rootHeight)
392
+ })
393
+
394
+ // observer.gridAxesTransform$
395
+ // .pipe(
396
+ // takeUntil(destroy$),
397
+ // map(d => d.value),
398
+ // distinctUntilChanged()
399
+ // ).subscribe(d => {
400
+ // axisSelection
401
+ // .style('transform', d)
402
+ // })
403
+
404
+ // const visibleComputedData$ = observer.computedData$.pipe(
405
+ // takeUntil(destroy$),
406
+ // map(data => {
407
+ // const visibleComputedData = data
408
+ // .map(d => {
409
+ // return d.filter(_d => {
410
+ // return _d.visible == true
411
+ // })
412
+ // })
413
+ // .filter(d => d.length)
414
+ // // console.log('visibleComputedData', visibleComputedData)
415
+ // return visibleComputedData
416
+ // })
417
+ // )
418
+
419
+ // const SeriesDataMap$ = visibleComputedData$.pipe(
420
+ // map(d => makeGridSeriesDataMap(d))
421
+ // )
422
+
423
+ // const GroupDataMap$ = visibleComputedData$.pipe(
424
+ // map(d => makeGridGroupDataMap(d))
425
+ // )
426
+
427
+ // const contentTransform$: Observable<string> = new Observable(subscriber => {
428
+ // combineLatest({
429
+ // fullParams: observer.fullParams$,
430
+ // gridAxesTransform: observer.gridAxesTransform$
431
+ // }).pipe(
432
+ // takeUntil(destroy$),
433
+ // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
434
+ // switchMap(async (d) => d),
435
+ // ).subscribe(data => {
436
+
437
+ // const transformData = Object.assign({}, data.gridAxesTransform)
438
+
439
+ // // const value = getAxesTransformValue({
440
+ // // translate: [0, 0],
441
+ // // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
442
+ // // rotate: transformData.rotate * -1,
443
+ // // rotateX: transformData.rotateX * -1,
444
+ // // rotateY: transformData.rotateY * -1
445
+ // // })
446
+
447
+ // subscriber.next(transformData.value)
448
+ // })
449
+ // })
450
+ // const reverseTransform$: Observable<TransformData> = observer.gridAxesTransform$.pipe(
451
+ // takeUntil(destroy$),
452
+ // map(d => {
453
+ // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
454
+ // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
455
+ // const rotate = d.rotate * -1
456
+ // const rotateX = d.rotateX * -1
457
+ // const rotateY = d.rotateY * -1
458
+ // return {
459
+ // translate,
460
+ // scale,
461
+ // rotate,
462
+ // rotateX,
463
+ // rotateY,
464
+ // value: ''
465
+ // }
466
+ // }),
467
+ // )
468
+ // const contentTransform$ = combineLatest({
469
+ // fullParams: observer.fullParams$,
470
+ // reverseTransform: reverseTransform$
471
+ // }).pipe(
472
+ // takeUntil(destroy$),
473
+ // switchMap(async data => {
474
+ // const translate = [0, 0]
475
+ // return `translate(${translate[0]}px, ${translate[1]}px) rotate(${data.reverseTransform.rotate}deg) rotateX(${data.reverseTransform.rotateX}deg) rotateY(${data.reverseTransform.rotateY}deg)`
476
+ // }),
477
+ // distinctUntilChanged()
478
+ // )
479
+
480
+ // const groupScale$: Observable<d3.ScalePoint<string>> = new Observable(subscriber => {
481
+ // combineLatest({
482
+ // fullDataFormatter: observer.fullDataFormatter$,
483
+ // gridAxesSize: observer.gridAxesSize$,
484
+ // computedData: observer.computedData$
485
+ // }).pipe(
486
+ // takeUntil(destroy$),
487
+ // switchMap(async (d) => d),
488
+ // ).subscribe(data => {
489
+ // const groupMin = 0
490
+ // const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
491
+ // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
492
+ // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
493
+ // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
494
+ // const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'auto'
495
+ // ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
496
+ // : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
497
+
498
+ // const groupingLength = data.computedData[0]
499
+ // ? data.computedData[0].length
500
+ // : 0
501
+
502
+ // let _labels = data.fullDataFormatter.grid.seriesDirection === 'row'
503
+ // // ? data.fullDataFormatter.grid.columnLabels
504
+ // // : data.fullDataFormatter.grid.rowLabels
505
+ // ? (data.computedData[0] ?? []).map(d => d.groupLabel)
506
+ // : data.computedData.map(d => d[0].groupLabel)
507
+
508
+ // const axisLabels = new Array(groupingLength).fill(0)
509
+ // .map((d, i) => {
510
+ // return _labels[i] != null
511
+ // ? _labels[i]
512
+ // : String(i) // 沒有label則用序列號填充
513
+ // })
514
+ // .filter((d, i) => {
515
+ // return i >= groupScaleDomainMin && i <= groupScaleDomainMax
516
+ // })
517
+
518
+
519
+ // const padding = data.fullDataFormatter.grid.groupAxis.scalePadding
520
+
521
+ // const groupScale = createLabelToAxisScale({
522
+ // axisLabels,
523
+ // axisWidth: data.gridAxesSize.width,
524
+ // padding
525
+ // })
526
+
527
+ // subscriber.next(groupScale)
528
+ // })
529
+ // })
530
+
531
+ // const groupScaleDomain$ = combineLatest({
532
+ // fullDataFormatter: observer.fullDataFormatter$,
533
+ // gridAxesSize: observer.gridAxesSize$,
534
+ // computedData: observer.computedData$
535
+ // }).pipe(
536
+ // takeUntil(destroy$),
537
+ // switchMap(async (d) => d),
538
+ // map(data => {
539
+ // const groupMin = 0
540
+ // const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
541
+ // // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
542
+ // // ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
543
+ // // : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
544
+ // const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] - data.fullDataFormatter.grid.groupAxis.scalePadding
545
+ // const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'max'
546
+ // ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
547
+ // : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
548
+
549
+ // return [groupScaleDomainMin, groupScaleDomainMax]
550
+ // }),
551
+ // shareReplay(1)
552
+ // )
553
+
554
+ const groupScale$ = combineLatest({
555
+ groupScaleDomainValue: observer.groupScaleDomainValue$,
556
+ gridAxesSize: observer.gridAxesSize$
557
+ }).pipe(
558
+ takeUntil(destroy$),
559
+ switchMap(async (d) => d),
560
+ map(data => {
561
+ const groupScale: d3.ScaleLinear<number, number> = d3.scaleLinear()
562
+ .domain(data.groupScaleDomainValue)
563
+ .range([0, data.gridAxesSize.width])
564
+ return groupScale
565
+ })
566
+ )
567
+
568
+ // // 取得事件座標的group資料
569
+ // const gridGroupPositionFn$ = gridGroupPositionFnObservable({
570
+ // fullDataFormatter$: observer.fullDataFormatter$,
571
+ // gridAxesSize$: observer.gridAxesSize$,
572
+ // computedData$: observer.computedData$,
573
+ // fullChartParams$: observer.fullChartParams$,
574
+ // })
575
+
576
+ const highlightTarget$ = observer.fullChartParams$.pipe(
577
+ takeUntil(destroy$),
578
+ map(d => d.highlightTarget),
579
+ distinctUntilChanged()
580
+ )
581
+
582
+ // combineLatest({
583
+ // computedData: observer.computedData$,
584
+ // gridAxesSize: observer.gridAxesSize$,
585
+ // fullParams: observer.fullParams$,
586
+ // fullChartParams: observer.fullChartParams$,
587
+ // highlightTarget: highlightTarget$,
588
+ // SeriesDataMap: observer.SeriesDataMap$,
589
+ // GroupDataMap: observer.GroupDataMap$,
590
+ // gridGroupPositionFn: gridGroupPositionFn$,
591
+ // groupScale: groupScale$,
592
+ // }).pipe(
593
+ // takeUntil(destroy$),
594
+ // switchMap(async (d) => d),
595
+ // ).subscribe(data => {
596
+
597
+ // // store.selection
598
+ // rootSelection
599
+ // .on('mouseover', (event, datum) => {
600
+ // // event.stopPropagation()
601
+
602
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
603
+
604
+ // subject.event$.next({
605
+ // type: 'grid',
606
+ // pluginName: name,
607
+ // eventName: 'mouseover',
608
+ // highlightTarget: data.highlightTarget,
609
+ // datum: null,
610
+ // gridIndex: 0,
611
+ // series: [],
612
+ // seriesIndex: -1,
613
+ // seriesLabel: '',
614
+ // groups: data.GroupDataMap.get(groupLabel) ?? [],
615
+ // // groups: [],
616
+ // groupIndex,
617
+ // groupLabel,
618
+ // event,
619
+ // data: data.computedData
620
+ // })
621
+ // })
622
+ // .on('mousemove', (event, datum) => {
623
+ // // event.stopPropagation()
624
+
625
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
626
+
627
+ // subject.event$.next({
628
+ // type: 'grid',
629
+ // pluginName: name,
630
+ // eventName: 'mousemove',
631
+ // highlightTarget: data.highlightTarget,
632
+ // datum: null,
633
+ // gridIndex: 0,
634
+ // series: [],
635
+ // seriesIndex: -1,
636
+ // seriesLabel: '',
637
+ // groups: data.GroupDataMap.get(groupLabel) ?? [],
638
+ // // groups: [],
639
+ // groupIndex,
640
+ // groupLabel,
641
+ // event,
642
+ // data: data.computedData
643
+ // })
644
+ // })
645
+ // .on('mouseout', (event, datum) => {
646
+ // // event.stopPropagation()
647
+
648
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
649
+
650
+ // subject.event$.next({
651
+ // type: 'grid',
652
+ // pluginName: name,
653
+ // eventName: 'mouseout',
654
+ // highlightTarget: data.highlightTarget,
655
+ // datum: null,
656
+ // gridIndex: 0,
657
+ // series: [],
658
+ // seriesIndex: -1,
659
+ // seriesLabel: '',
660
+ // groups: data.GroupDataMap.get(groupLabel) ?? [],
661
+ // // groups: [],
662
+ // groupIndex,
663
+ // groupLabel,
664
+ // event,
665
+ // data: data.computedData
666
+ // })
667
+ // })
668
+ // .on('click', (event, datum) => {
669
+ // event.stopPropagation()
670
+
671
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
672
+
673
+ // subject.event$.next({
674
+ // type: 'grid',
675
+ // pluginName: name,
676
+ // eventName: 'click',
677
+ // highlightTarget: data.highlightTarget,
678
+ // datum: null,
679
+ // gridIndex: 0,
680
+ // series: [],
681
+ // seriesIndex: -1,
682
+ // seriesLabel: '',
683
+ // // groups: data.GroupDataMap.get(groupLabel) ?? [],
684
+ // groups: [],
685
+ // groupIndex,
686
+ // groupLabel,
687
+ // event,
688
+ // data: data.computedData
689
+ // })
690
+ // })
691
+
692
+ // // barSelection$.next(barSelection!)
693
+ // })
694
+
695
+ const rootMousemove$: Observable<any> = d3EventObservable(rootSelection, 'mousemove').pipe(
696
+ takeUntil(destroy$),
697
+ )
698
+
699
+ // const mousemoveGroupLabel$ = combineLatest({
700
+ // rootMousemove: rootMousemove$,
701
+ // gridGroupPositionFn: gridGroupPositionFn$,
702
+ // }).pipe(
703
+ // takeUntil(destroy$),
704
+ // switchMap(async d => d),
705
+ // map(data => {
706
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(data.rootMousemove)
707
+ // return { groupIndex, groupLabel }
708
+ // }),
709
+ // shareReplay(1)
710
+ // )
711
+
712
+
713
+ const textReverseTransform$ = combineLatest({
714
+ gridAxesReverseTransform: observer.gridAxesReverseTransform$,
715
+ gridContainerPosition: observer.gridContainerPosition$
716
+ }).pipe(
717
+ takeUntil(destroy$),
718
+ switchMap(async (d) => d),
719
+ map(data => {
720
+ // const axisReverseTranslateValue = `translate(${data.gridAxesReverseTransform.translate[0]}px, ${data.gridAxesReverseTransform.translate[1]}px)`
721
+ const axesRotateXYReverseValue = `rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
722
+ const axesRotateReverseValue = `rotate(${data.gridAxesReverseTransform.rotate}deg)`
723
+ const containerScaleReverseValue = `scale(${1 / data.gridContainerPosition[0].scale[0]}, ${1 / data.gridContainerPosition[0].scale[1]})`
724
+ // 必須按照順序(先抵消外層rotate,再抵消最外層scale)
725
+ return `${axesRotateXYReverseValue} ${axesRotateReverseValue} ${containerScaleReverseValue}`
726
+ }),
727
+ distinctUntilChanged()
728
+ )
729
+
730
+ const textReverseTransformWithRotate$ = combineLatest({
731
+ textReverseTransform: textReverseTransform$,
732
+ fullParams: observer.fullParams$,
733
+ }).pipe(
734
+ takeUntil(destroy$),
735
+ switchMap(async (d) => d),
736
+ map(data => {
737
+ // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
738
+ return `${data.textReverseTransform} rotate(${data.fullParams.labelRotate}deg)`
739
+ })
740
+ )
741
+
742
+ const columnAmount$ = observer.gridContainerPosition$.pipe(
743
+ takeUntil(destroy$),
744
+ map(gridContainerPosition => {
745
+ const maxColumnIndex = gridContainerPosition.reduce((acc, current) => {
746
+ return current.columnIndex > acc ? current.columnIndex : acc
747
+ }, 0)
748
+ return maxColumnIndex + 1
749
+ }),
750
+ distinctUntilChanged()
751
+ )
752
+
753
+ const rowAmount$ = observer.gridContainerPosition$.pipe(
754
+ takeUntil(destroy$),
755
+ map(gridContainerPosition => {
756
+ const maxRowIndex = gridContainerPosition.reduce((acc, current) => {
757
+ return current.rowIndex > acc ? current.rowIndex : acc
758
+ }, 0)
759
+ return maxRowIndex + 1
760
+ }),
761
+ distinctUntilChanged()
762
+ )
763
+
764
+ const gridGroupPosition$ = gridGroupPositionObservable({
765
+ rootSelection,
766
+ fullDataFormatter$: observer.fullDataFormatter$,
767
+ gridAxesSize$: observer.gridAxesSize$,
768
+ computedData$: observer.computedData$,
769
+ fullChartParams$: observer.fullChartParams$,
770
+ gridContainerPosition$: observer.gridContainerPosition$,
771
+ layout$: observer.layout$
772
+ }).pipe(
773
+ takeUntil(destroy$)
774
+ )
775
+
776
+ combineLatest({
777
+ axesSelection: axesSelection$,
778
+ columnAmount: columnAmount$,
779
+ rowAmount: rowAmount$,
780
+ layout: observer.layout$,
781
+ rootMousemove: rootMousemove$,
782
+ // gridGroupPositionFn: gridGroupPositionFn$,
783
+ gridGroupPosition: gridGroupPosition$,
784
+ computedData: observer.computedData$,
785
+ groupScale: groupScale$,
786
+ gridAxesSize: observer.gridAxesSize$,
787
+ fullParams: observer.fullParams$,
788
+ fullDataFormatter: observer.fullDataFormatter$,
789
+ fullChartParams: observer.fullChartParams$,
790
+ highlightTarget: highlightTarget$,
791
+ // gridAxesReverseTransform: observer.gridAxesReverseTransform$,
792
+ textReverseTransformWithRotate: textReverseTransformWithRotate$,
793
+ GroupDataMap: observer.GroupDataMap$,
794
+ textSizePx: observer.textSizePx$
795
+ }).pipe(
796
+ takeUntil(destroy$),
797
+ switchMap(async d => d),
798
+ ).subscribe(data => {
799
+ // // 由於event座標是基於底層的,但是container會有多欄,所以要重新計算
800
+ // const eventData = {
801
+ // offsetX: data.rootMousemove.offsetX * data.columnAmount % data.layout.rootWidth,
802
+ // offsetY: data.rootMousemove.offsetY * data.rowAmount % data.layout.rootHeight
803
+ // }
804
+ // 依event的座標取得group資料
805
+ const { groupIndex, groupLabel } = data.gridGroupPosition
806
+ // console.log('gridGroupPosition', groupIndex, groupLabel)
807
+ const axisX = data.groupScale(groupIndex) ?? 0
808
+ // console.log('axisX', axisX)
809
+ const lineData = createLineData({
810
+ groupLabel: groupLabel,
811
+ axisX,
812
+ axisHeight: data.gridAxesSize.height,
813
+ fullParams: data.fullParams,
814
+ })
815
+ // console.log('lineData', lineData)
816
+ renderLine({
817
+ // selection: axisSelection,
818
+ selection: data.axesSelection,
819
+ pluginName: name,
820
+ lineData,
821
+ fullParams: data.fullParams,
822
+ fullChartParams: data.fullChartParams
823
+ })
824
+ const labelData = createLabelData({
825
+ groupLabel: groupLabel,
826
+ axisX,
827
+ fullParams: data.fullParams,
828
+ textSizePx: data.textSizePx,
829
+ rowAmount: data.rowAmount
830
+ })
831
+ const labelSelection = renderLabel({
832
+ // selection: axisSelection,
833
+ selection: data.axesSelection,
834
+ labelData,
835
+ fullParams: data.fullParams,
836
+ fullDataFormatter: data.fullDataFormatter,
837
+ fullChartParams: data.fullChartParams,
838
+ // gridAxesReverseTransformValue: data.gridAxesReverseTransform.value,
839
+ textReverseTransformWithRotate: data.textReverseTransformWithRotate,
840
+ textSizePx: data.textSizePx,
841
+ })
842
+
843
+ // label的事件
844
+ labelSelection
845
+ .on('mouseover', (event, datum) => {
846
+ event.stopPropagation()
847
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
848
+
849
+ isLabelMouseover = true
850
+
851
+ subject.event$.next({
852
+ type: 'grid',
853
+ pluginName: name,
854
+ eventName: 'mouseover',
855
+ highlightTarget: data.highlightTarget,
856
+ datum: null,
857
+ gridIndex: 0,
858
+ series: [],
859
+ seriesIndex: -1,
860
+ seriesLabel: '',
861
+ groups: data.GroupDataMap.get(groupLabel) ?? [],
862
+ groupIndex,
863
+ groupLabel,
864
+ event,
865
+ data: data.computedData
866
+ })
867
+ })
868
+ .on('mousemove', (event, datum) => {
869
+ event.stopPropagation()
870
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
871
+
872
+ subject.event$.next({
873
+ type: 'grid',
874
+ pluginName: name,
875
+ eventName: 'mousemove',
876
+ highlightTarget: data.highlightTarget,
877
+ datum: null,
878
+ gridIndex: 0,
879
+ series: [],
880
+ seriesIndex: -1,
881
+ seriesLabel: '',
882
+ groups: data.GroupDataMap.get(groupLabel) ?? [],
883
+ groupIndex,
884
+ groupLabel,
885
+ event,
886
+ data: data.computedData
887
+ })
888
+ })
889
+ .on('mouseout', (event, datum) => {
890
+ event.stopPropagation()
891
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
892
+
893
+ isLabelMouseover = false
894
+
895
+ subject.event$.next({
896
+ type: 'grid',
897
+ pluginName: name,
898
+ eventName: 'mouseout',
899
+ highlightTarget: data.highlightTarget,
900
+ datum: null,
901
+ gridIndex: 0,
902
+ series: [],
903
+ seriesIndex: -1,
904
+ seriesLabel: '',
905
+ groups: data.GroupDataMap.get(groupLabel) ?? [],
906
+ groupIndex,
907
+ groupLabel,
908
+ event,
909
+ data: data.computedData
910
+ })
911
+ })
912
+ .on('click', (event, datum) => {
913
+ event.stopPropagation()
914
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
915
+
916
+ subject.event$.next({
917
+ type: 'grid',
918
+ pluginName: name,
919
+ eventName: 'click',
920
+ highlightTarget: data.highlightTarget,
921
+ datum: null,
922
+ gridIndex: 0,
923
+ series: [],
924
+ seriesIndex: -1,
925
+ seriesLabel: '',
926
+ groups: data.GroupDataMap.get(groupLabel) ?? [],
927
+ groupIndex,
928
+ groupLabel,
929
+ event,
930
+ data: data.computedData
931
+ })
932
+ })
933
+
934
+ })
935
+
936
+ // // -- highlight(無論highlightTarget設定為何,一律依從groupLabel來顯示) --
937
+ // combineLatest({
938
+ // event: subject.event$.pipe(
939
+ // filter(d => d.eventName === 'mouseover' || d.eventName === 'mousemove')
940
+ // ),
941
+ // computedData: observer.computedData$,
942
+ // groupScale: groupScale$,
943
+ // gridAxesSize: observer.gridAxesSize$,
944
+ // fullParams: observer.fullParams$,
945
+ // fullChartParams: observer.fullChartParams$,
946
+ // highlightTarget: highlightTarget$,
947
+ // gridAxesReverseTransform: observer.gridAxesReverseTransform$,
948
+ // GroupDataMap: observer.GroupDataMap$,
949
+ // gridGroupPositionFn: gridGroupPositionFn$,
950
+ // textSizePx: observer.textSizePx$
951
+ // }).pipe(
952
+ // takeUntil(destroy$),
953
+ // switchMap(async d => d)
954
+ // ).subscribe(data => {
955
+ // // const groups = data.event.eventName === 'mouseover' || data.event.eventName === 'mousemove'
956
+ // // ? data.event.groups
957
+ // // : []
958
+
959
+ // // const groupLabel = data.event.eventName === 'mouseover' || data.event.eventName === 'mousemove'
960
+ // // ? data.event.groupLabel
961
+ // // : ''
962
+ // const axisX = data.groupScale(data.event.groupLabel) ?? 0
963
+
964
+ // const lineData = createLineData({
965
+ // groupLabel: data.event.groupLabel,
966
+ // axisX,
967
+ // axisHeight: data.gridAxesSize.height,
968
+ // fullParams: data.fullParams,
969
+ // })
970
+ // renderLine({
971
+ // selection: axisSelection,
972
+ // pluginName: name,
973
+ // lineData,
974
+ // fullParams: data.fullParams,
975
+ // fullChartParams: data.fullChartParams
976
+ // })
977
+ // const labelData = createLabelData({
978
+ // groupLabel: data.event.groupLabel,
979
+ // axisX,
980
+ // fullParams: data.fullParams
981
+ // })
982
+ // const labelSelection = renderLabel({
983
+ // selection: axisSelection,
984
+ // labelData,
985
+ // fullParams: data.fullParams,
986
+ // fullChartParams: data.fullChartParams,
987
+ // gridAxesReverseTransformValue: data.gridAxesReverseTransform.value,
988
+ // textSizePx: data.textSizePx
989
+ // })
990
+
991
+ // // label的事件
992
+ // labelSelection
993
+ // .on('mouseover', (event, datum) => {
994
+
995
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
996
+
997
+ // subject.event$.next({
998
+ // type: 'grid',
999
+ // pluginName: name,
1000
+ // eventName: 'mouseover',
1001
+ // highlightTarget: data.highlightTarget,
1002
+ // datum: null,
1003
+ // gridIndex: 0,
1004
+ // series: [],
1005
+ // seriesIndex: -1,
1006
+ // seriesLabel: '',
1007
+ // groups: data.event.groups,
1008
+ // groupIndex,
1009
+ // groupLabel,
1010
+ // event,
1011
+ // data: data.computedData
1012
+ // })
1013
+ // })
1014
+ // .on('mousemove', (event, datum) => {
1015
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
1016
+
1017
+ // subject.event$.next({
1018
+ // type: 'grid',
1019
+ // pluginName: name,
1020
+ // eventName: 'mousemove',
1021
+ // highlightTarget: data.highlightTarget,
1022
+ // datum: null,
1023
+ // gridIndex: 0,
1024
+ // series: [],
1025
+ // seriesIndex: -1,
1026
+ // seriesLabel: '',
1027
+ // groups: data.event.groups,
1028
+ // groupIndex,
1029
+ // groupLabel,
1030
+ // event,
1031
+ // data: data.computedData
1032
+ // })
1033
+ // })
1034
+ // .on('mouseout', (event, datum) => {
1035
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
1036
+
1037
+ // subject.event$.next({
1038
+ // type: 'grid',
1039
+ // pluginName: name,
1040
+ // eventName: 'mouseout',
1041
+ // highlightTarget: data.highlightTarget,
1042
+ // datum: null,
1043
+ // gridIndex: 0,
1044
+ // series: [],
1045
+ // seriesIndex: -1,
1046
+ // seriesLabel: '',
1047
+ // groups: data.event.groups,
1048
+ // groupIndex,
1049
+ // groupLabel,
1050
+ // event,
1051
+ // data: data.computedData
1052
+ // })
1053
+ // })
1054
+ // .on('click', (event, datum) => {
1055
+ // const { groupIndex, groupLabel } = data.gridGroupPositionFn(event)
1056
+
1057
+ // subject.event$.next({
1058
+ // type: 'grid',
1059
+ // pluginName: name,
1060
+ // eventName: 'click',
1061
+ // highlightTarget: data.highlightTarget,
1062
+ // datum: null,
1063
+ // gridIndex: 0,
1064
+ // series: [],
1065
+ // seriesIndex: -1,
1066
+ // seriesLabel: '',
1067
+ // groups: data.event.groups,
1068
+ // groupIndex,
1069
+ // groupLabel,
1070
+ // event,
1071
+ // data: data.computedData
1072
+ // })
1073
+ // })
1074
+ // })
1075
+
1076
+
1077
+
1078
+ const rootRectMouseout$ = d3EventObservable(rootRectSelection, 'mouseout').pipe(
1079
+ takeUntil(destroy$),
1080
+ )
1081
+
1082
+ combineLatest({
1083
+ rootRectMouseout: rootRectMouseout$,
1084
+ axesSelection: axesSelection$,
1085
+ }).pipe(
1086
+ takeUntil(destroy$),
1087
+ switchMap(async d => d)
1088
+ ).subscribe(data => {
1089
+ setTimeout(() => {
1090
+ // @Q@ workaround - 不知為何和 label 會有衝突,當滑鼠移動到 label 上時,會觸發 mouseout 事件
1091
+ if (isLabelMouseover == true) {
1092
+ return
1093
+ }
1094
+
1095
+ removeLine(data.axesSelection)
1096
+ removeLabel(data.axesSelection)
1097
+ })
1098
+ })
1099
+
1100
+ return () => {
1101
+ destroy$.next(undefined)
1102
+ rootRectSelection.remove()
1103
+ }
1104
1104
  })