@orbcharts/plugins-basic 3.0.0-beta.19 → 3.0.0-beta.20

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 (110) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +60 -22
  3. package/dist/orbcharts-plugins-basic.umd.js +86 -66
  4. package/lib/core-types.ts +7 -7
  5. package/lib/core.ts +6 -6
  6. package/lib/plugins-basic-types.ts +6 -6
  7. package/package.json +44 -44
  8. package/src/base/BaseBars.ts +765 -765
  9. package/src/base/BaseBarsTriangle.ts +676 -676
  10. package/src/base/BaseDots.ts +464 -464
  11. package/src/base/BaseGroupAxis.ts +691 -691
  12. package/src/base/BaseLegend.ts +684 -684
  13. package/src/base/BaseLineAreas.ts +629 -629
  14. package/src/base/BaseLines.ts +706 -706
  15. package/src/base/BaseRacingBars.ts +551 -551
  16. package/src/base/BaseRacingLabels.ts +396 -396
  17. package/src/base/BaseRacingValueLabels.ts +403 -403
  18. package/src/base/BaseStackedBars.ts +782 -782
  19. package/src/base/BaseTooltip.ts +386 -386
  20. package/src/base/BaseValueAxis.ts +600 -600
  21. package/src/base/BaseXAxis.ts +427 -427
  22. package/src/base/BaseYAxis.ts +389 -389
  23. package/src/base/types.ts +2 -2
  24. package/src/const.ts +30 -30
  25. package/src/grid/defaults.ts +213 -213
  26. package/src/grid/gridObservables.ts +612 -612
  27. package/src/grid/index.ts +16 -16
  28. package/src/grid/plugins/Bars.ts +69 -69
  29. package/src/grid/plugins/BarsPN.ts +66 -66
  30. package/src/grid/plugins/BarsTriangle.ts +73 -73
  31. package/src/grid/plugins/Dots.ts +68 -68
  32. package/src/grid/plugins/GridLegend.ts +107 -107
  33. package/src/grid/plugins/GridTooltip.ts +66 -66
  34. package/src/grid/plugins/GroupAux.ts +1120 -1120
  35. package/src/grid/plugins/GroupAxis.ts +73 -73
  36. package/src/grid/plugins/GroupZoom.ts +218 -218
  37. package/src/grid/plugins/LineAreas.ts +65 -65
  38. package/src/grid/plugins/Lines.ts +59 -59
  39. package/src/grid/plugins/StackedBars.ts +64 -64
  40. package/src/grid/plugins/StackedValueAxis.ts +96 -96
  41. package/src/grid/plugins/ValueAxis.ts +94 -94
  42. package/src/index.ts +6 -6
  43. package/src/multiGrid/defaults.ts +244 -244
  44. package/src/multiGrid/index.ts +14 -14
  45. package/src/multiGrid/multiGridObservables.ts +50 -50
  46. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  47. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  48. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  49. package/src/multiGrid/plugins/MultiGridLegend.ts +169 -169
  50. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  51. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  52. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  53. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  54. package/src/multiGrid/plugins/MultiStackedBars.ts +106 -106
  55. package/src/multiGrid/plugins/MultiStackedValueAxis.ts +134 -134
  56. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  57. package/src/multiGrid/plugins/OverlappingStackedValueAxes.ts +300 -300
  58. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  59. package/src/multiValue/defaults.ts +431 -391
  60. package/src/multiValue/index.ts +12 -12
  61. package/src/multiValue/multiValueObservables.ts +666 -666
  62. package/src/multiValue/plugins/MultiValueLegend.ts +107 -107
  63. package/src/multiValue/plugins/MultiValueTooltip.ts +66 -66
  64. package/src/multiValue/plugins/RacingBars.ts +373 -373
  65. package/src/multiValue/plugins/RacingCounterTexts.ts +300 -300
  66. package/src/multiValue/plugins/RacingValueAxis.ts +114 -114
  67. package/src/multiValue/plugins/RankingAxis_legacy.ts +109 -109
  68. package/src/multiValue/plugins/Scatter.ts +426 -426
  69. package/src/multiValue/plugins/ScatterBubbles.ts +554 -554
  70. package/src/multiValue/plugins/XAxis.ts +107 -107
  71. package/src/multiValue/plugins/XYAux.ts +682 -682
  72. package/src/multiValue/plugins/XYAxes.ts +194 -194
  73. package/src/multiValue/plugins/XYAxes_legacy.ts +683 -683
  74. package/src/multiValue/plugins/XZoom.ts +299 -299
  75. package/src/noneData/defaults.ts +102 -102
  76. package/src/noneData/index.ts +3 -3
  77. package/src/noneData/plugins/Container.ts +27 -27
  78. package/src/noneData/plugins/Tooltip.ts +373 -373
  79. package/src/relationship/defaults.ts +221 -221
  80. package/src/relationship/index.ts +5 -5
  81. package/src/relationship/plugins/ForceDirected.ts +1173 -1173
  82. package/src/relationship/plugins/ForceDirectedBubbles.ts +1411 -1411
  83. package/src/relationship/plugins/RelationshipLegend.ts +100 -100
  84. package/src/relationship/plugins/RelationshipTooltip.ts +66 -66
  85. package/src/relationship/relationshipObservables.ts +49 -49
  86. package/src/series/defaults.ts +221 -221
  87. package/src/series/index.ts +9 -9
  88. package/src/series/plugins/Bubbles.ts +636 -636
  89. package/src/series/plugins/Pie.ts +623 -623
  90. package/src/series/plugins/PieEventTexts.ts +284 -284
  91. package/src/series/plugins/PieLabels.ts +640 -640
  92. package/src/series/plugins/Rose.ts +516 -516
  93. package/src/series/plugins/RoseLabels.ts +600 -600
  94. package/src/series/plugins/SeriesLegend.ts +107 -107
  95. package/src/series/plugins/SeriesTooltip.ts +66 -66
  96. package/src/series/seriesObservables.ts +145 -145
  97. package/src/series/seriesUtils.ts +51 -51
  98. package/src/tree/defaults.ts +102 -102
  99. package/src/tree/index.ts +4 -4
  100. package/src/tree/plugins/TreeLegend.ts +100 -100
  101. package/src/tree/plugins/TreeMap.ts +341 -341
  102. package/src/tree/plugins/TreeTooltip.ts +66 -66
  103. package/src/utils/commonUtils.ts +31 -31
  104. package/src/utils/d3Graphics.ts +176 -176
  105. package/src/utils/d3Utils.ts +92 -92
  106. package/src/utils/observables.ts +14 -14
  107. package/src/utils/orbchartsUtils.ts +129 -129
  108. package/tsconfig.base.json +13 -13
  109. package/tsconfig.json +2 -2
  110. package/vite.config.js +22 -22
@@ -1,387 +1,387 @@
1
- import * as d3 from 'd3'
2
- import {
3
- combineLatest,
4
- map,
5
- merge,
6
- filter,
7
- switchMap,
8
- first,
9
- iif,
10
- takeUntil,
11
- Subject,
12
- Observable,
13
- distinctUntilChanged } from 'rxjs'
14
- import type {
15
- EventTypeMap,
16
- PluginConstructor,
17
- ChartType,
18
- ChartParams,
19
- Layout
20
- } from '../../lib/core-types'
21
- import type { BaseTooltipParams, BaseTooltipStyle } from '../../lib/plugins-basic-types'
22
- import type { BasePluginFn } from './types'
23
- import { defineNoneDataPlugin, textSizePxObservable } from '../../lib/core'
24
- import { getSvgGElementSize, appendSvg } from '../utils/d3Utils'
25
- import { getColor, getClassName } from '../utils/orbchartsUtils'
26
- import { measureTextWidth, toCurrency } from '../utils/commonUtils'
27
-
28
- interface BaseTooltipContext {
29
- rootSelection: d3.Selection<any, unknown, any, unknown>
30
- fullParams$: Observable<BaseTooltipParams>
31
- fullChartParams$: Observable<ChartParams>
32
- layout$: Observable<Layout>
33
- event$: Subject<EventTypeMap<any>>
34
- }
35
-
36
- // export interface BaseTooltipStyle {
37
- // backgroundColor: string
38
- // backgroundOpacity: number
39
- // strokeColor: string
40
- // offset: [number, number]
41
- // padding: number
42
- // textColor: string
43
- // textSize: number | string // chartParams上的設定
44
- // textSizePx: number
45
- // }
46
-
47
- function textToSvg (_textArr: string[] | string | null | undefined, textStyle: BaseTooltipStyle) {
48
- const lineHeight = textStyle.textSizePx * 1.5
49
-
50
- const textArr = _textArr == null
51
- ? []
52
- : Array.isArray(_textArr)
53
- ? _textArr
54
- : typeof _textArr === 'string'
55
- ? _textArr.split('\n')
56
- : [_textArr]
57
-
58
- const tspan = textArr
59
- .filter(d => d != '')
60
- .map((text, i) => {
61
- const top = i * lineHeight
62
- return `<tspan x="0" y="${top}">${text}</tspan>`
63
- })
64
- .join('')
65
-
66
- if (tspan) {
67
- return `<text font-size="${textStyle.textSize}" fill="${textStyle.textColor}" x="0" y="0" style="dominant-baseline:text-before-edge">
68
- ${tspan}
69
- </text>`
70
- } else {
71
- return ''
72
- }
73
- }
74
-
75
- function renderTooltip ({ rootSelection, pluginName, gClassName, boxClassName, rootWidth, rootHeight, svgString, tooltipStyle, event }: {
76
- rootSelection: d3.Selection<any, unknown, any, unknown>
77
- pluginName: string
78
- gClassName: string
79
- boxClassName: string
80
- rootWidth: number
81
- rootHeight: number
82
- svgString: string
83
- tooltipStyle: BaseTooltipStyle
84
- event: MouseEvent
85
- }) {
86
- // if (!svgString) {
87
- // return
88
- // }
89
- // const rootSelection = d3.select('svg.bpcharts__root')
90
- // console.log('tooltip', { selection, rootWidth, rootHeight, svgString, event })
91
- rootSelection.interrupt('fadeout')
92
- const radius = 5
93
-
94
- // data(svg string無值則為空陣列)
95
- const contentData = svgString ? [svgString] : []
96
- const styleData = svgString ? [tooltipStyle] : []
97
- // tooltipG <g>
98
- const tooltipG = rootSelection
99
- .selectAll<SVGGElement, string>(`g.${gClassName}`)
100
- .data(contentData)
101
- .join(
102
- enter => {
103
- return enter
104
- .append('g')
105
- .classed(gClassName, true)
106
- .attr('pointer-events', 'none')
107
- },
108
- update => {
109
- return update
110
- },
111
- exit => {
112
- return exit
113
- // .transition('fadeout')
114
- // .duration(0)
115
- // .delay(500)
116
- .style('opacity', 0)
117
- .remove()
118
- }
119
- )
120
- .attr('transform', () => `translate(${event.offsetX}, ${event.offsetY})`)
121
-
122
- // tooltipBox <g><g>
123
- const tooltipBox = tooltipG
124
- .selectAll<SVGGElement, string>(`g.${boxClassName}`)
125
- .data(styleData)
126
- .join(
127
- enter => {
128
- return enter
129
- .append('g')
130
- .classed(getClassName(pluginName, 'box'), true)
131
- },
132
- )
133
-
134
- // rect <g><g><rect>
135
- const rect = tooltipBox
136
- .selectAll<SVGRectElement, string>('rect')
137
- .data(styleData)
138
- .join(
139
- enter => {
140
- return enter
141
- .append('rect')
142
- .attr('rx', radius)
143
- .attr('ry', radius)
144
- }
145
- )
146
- .attr('fill', d => d.backgroundColor)
147
- .attr('stroke', d => d.strokeColor)
148
- .attr('opacity', d => d.backgroundOpacity)
149
-
150
- // text <g><g><g>
151
- const contentG = tooltipBox
152
- .selectAll<SVGGElement, string>('g')
153
- .data(contentData)
154
- .join(
155
- enter => {
156
- return enter
157
- .append('g')
158
- .classed(getClassName(pluginName, 'content'), true)
159
- .attr('transform', () => `translate(${tooltipStyle.padding}, ${tooltipStyle.padding})`)
160
- }
161
- )
162
- // 使用字串加入svg
163
- if (contentData.length) {
164
- appendSvg(contentG, contentData[0])
165
- }
166
- const contentSize = (contentG?.node()) ? getSvgGElementSize(contentG!) : { width: 0, height: 0 }
167
-
168
- // rect size
169
- rect
170
- .attr('width', contentSize.width + tooltipStyle.padding * 2)
171
- .attr('height', contentSize.height + tooltipStyle.padding * 2)
172
-
173
- // -- tooltipG --
174
- // 取得tooltip <g>的尺寸
175
- const tooltipSize = (tooltipBox?.node()) ? getSvgGElementSize(tooltipBox!) : { width: 0, height: 0 }
176
- // const minX = 0
177
- const maxX = rootWidth - tooltipSize.width
178
- // const minY = 0
179
- const maxY = rootHeight - tooltipSize.height
180
-
181
- // -- 相對游標位置的offset --
182
- const offsetX = (event.offsetX + tooltipStyle.offset[0]) > maxX ? maxX - event.offsetX : tooltipStyle.offset[0]
183
- const offsetY = (event.offsetY + tooltipStyle.offset[1]) > maxY ? maxY - event.offsetY : tooltipStyle.offset[1]
184
- tooltipBox.attr('transform', d => `translate(${offsetX}, ${offsetY})`)
185
- tooltipBox.attr('transform', d => `translate(${offsetX}, ${offsetY})`)
186
-
187
- // const minX = containerSize.x
188
- // const maxX = containerSize.x + containerSize.width - tooltipSize.width
189
- // const minY = containerSize.y
190
- // const maxY = containerSize.y + containerSize.height - tooltipSize.height
191
-
192
- // .style('position', 'absolute')
193
- // .style('z-index', 10000)
194
- // .style('left', (d) => {
195
- // const x = d.x + this.fullParams.offsetX! - containerSize.x
196
- // if (x < minX) {
197
- // return `${minX}px`
198
- // } else if (x > maxX) {
199
- // return `${maxX}px`
200
- // }
201
- // return `${x}px`
202
- // })
203
- // .style('top', (d) => {
204
- // const y = d.y + this.fullParams.offsetY! - containerSize.y
205
- // if (y < minY) {
206
- // return `${minY}px`
207
- // } else if (y > maxY) {
208
- // return `${maxY}px`
209
- // }
210
- // return `${y}px`
211
- // })
212
- // .html((d) => d.contentHtml)
213
-
214
- }
215
-
216
- export const createBaseTooltip: BasePluginFn<BaseTooltipContext> = (pluginName: string, {
217
- rootSelection,
218
- fullParams$,
219
- fullChartParams$,
220
- layout$,
221
- event$
222
- }) => {
223
-
224
- const destroy$ = new Subject()
225
-
226
- const gClassName = getClassName(pluginName, 'g')
227
- const boxClassName = getClassName(pluginName, 'box')
228
-
229
- // 事件觸發
230
- const eventMouseover$: Observable<EventTypeMap<any>> = event$.pipe(
231
- takeUntil(destroy$),
232
- filter(d => d.eventName === 'mouseover' || d.eventName === 'mousemove'),
233
- // distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
234
- // map(d => d as EventTypeMap<typeof chartType>)
235
- )
236
- const eventMouseout$: Observable<EventTypeMap<any>> = event$.pipe(
237
- takeUntil(destroy$),
238
- filter(d => d.eventName === 'mouseout'),
239
- // distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
240
- // map(d => d as EventTypeMap<typeof chartType>)
241
- )
242
-
243
- const textSizePx$ = textSizePxObservable(fullChartParams$)
244
-
245
- const tooltipStyle$: Observable<BaseTooltipStyle> = combineLatest({
246
- fullChartParams: fullChartParams$,
247
- fullParams: fullParams$,
248
- textSizePx: textSizePx$
249
- }).pipe(
250
- takeUntil(destroy$),
251
- switchMap(async d => d),
252
- map(data => {
253
- return {
254
- backgroundColor: getColor(data.fullParams.backgroundColorType, data.fullChartParams),
255
- backgroundOpacity: data.fullParams.backgroundOpacity,
256
- strokeColor: getColor(data.fullParams.strokeColorType, data.fullChartParams),
257
- offset: data.fullParams.offset,
258
- padding: data.fullParams.padding,
259
- textSize: data.fullChartParams.styles.textSize,
260
- textSizePx: data.textSizePx,
261
- textColor: getColor(data.fullParams.textColorType, data.fullChartParams),
262
- seriesColors: data.fullChartParams.colors[data.fullChartParams.colorScheme].label
263
- }
264
- })
265
- )
266
-
267
- const contentRenderFn$: Observable<((eventData: EventTypeMap<any>) => string)> = combineLatest({
268
- fullParams: fullParams$,
269
- tooltipStyle: tooltipStyle$
270
- }).pipe(
271
- takeUntil(destroy$),
272
- switchMap(async d => d),
273
- map(data => {
274
- // if (data.fullParams.svgRenderFn) {
275
- // return data.fullParams.svgRenderFn
276
- // }
277
- // // 將textRenderFn回傳的資料使用<text>包裝起來
278
- // return (eventData: EventTypeMap<any>) => {
279
- // const textArr: string | string[] | null = data.fullParams.textRenderFn
280
- // ? data.fullParams.textRenderFn(eventData as any)
281
- // : null
282
- // return textToSvg(textArr, data.tooltipStyle)
283
- // }
284
- return (eventData: EventTypeMap<any>) => {
285
- const renderText: string | string[] = data.fullParams.renderFn(
286
- // mouseover事件的資料
287
- eventData,
288
- // context
289
- {
290
- utils: {
291
- toCurrency,
292
- measureTextWidth
293
- },
294
- styles: data.tooltipStyle
295
- }
296
- )
297
- // string
298
- if (typeof renderText === 'string') {
299
- const trimText = renderText.trim()
300
- const isSvgText = trimText.slice(0, 1) === '<' && trimText.slice(trimText.length - 1, trimText.length) === '>'
301
-
302
- if (isSvgText) {
303
- return renderText // svg字串
304
- } else {
305
- const textArr = renderText.split('\n')
306
- return textToSvg(textArr, data.tooltipStyle) // 多行文字轉svg字串
307
- }
308
- }
309
- // string[]
310
- else if (Array.isArray(renderText)) {
311
- return textToSvg(renderText, data.tooltipStyle) // 多行文字轉svg字串
312
- }
313
- return ''
314
- }
315
- })
316
- )
317
-
318
- const mouseoverTooltipSvg$ = combineLatest({
319
- event: eventMouseover$,
320
- contentRenderFn: contentRenderFn$
321
- }).pipe(
322
- takeUntil(destroy$),
323
- switchMap(async d => d),
324
- map(d => d.contentRenderFn(d.event))
325
- )
326
-
327
- const mouseoutTooltipSvg$ = eventMouseout$.pipe(
328
- takeUntil(destroy$),
329
- map(d => '')
330
- )
331
-
332
- const svgString$ = merge(mouseoverTooltipSvg$, mouseoutTooltipSvg$)
333
- .pipe(
334
- takeUntil(destroy$),
335
- distinctUntilChanged((a, b) => a === b)
336
- )
337
-
338
- const eventTooltip$ = merge(eventMouseover$, eventMouseout$)
339
- .pipe(
340
- takeUntil(destroy$),
341
- // filter(d => {
342
- // return (d.eventName === 'mouseover' || d.eventName === 'mousemove' || d.eventName === 'mouseout')
343
- // && d.event != undefined
344
- // }),
345
- map(d => d.event!),
346
- )
347
-
348
- combineLatest({
349
- svgString: svgString$,
350
- eventTooltip: eventTooltip$,
351
- layout: layout$,
352
- tooltipStyle: tooltipStyle$,
353
- }).pipe(
354
- takeUntil(destroy$),
355
- switchMap(async d => d),
356
- ).subscribe(data => {
357
- renderTooltip({
358
- rootSelection,
359
- pluginName,
360
- gClassName,
361
- boxClassName,
362
- rootWidth: data.layout.rootWidth,
363
- rootHeight: data.layout.rootHeight,
364
- svgString: data.svgString,
365
- tooltipStyle: data.tooltipStyle,
366
- event: data.eventTooltip
367
- })
368
- })
369
-
370
-
371
-
372
- // const chartType$ = eventMouseover$.pipe(
373
- // takeUntil(destroy$),
374
- // map(d => d.type),
375
- // distinctUntilChanged()
376
- // )
377
-
378
-
379
- // eventMouseover$.subscribe(event => {
380
-
381
- // })
382
-
383
-
384
- return () => {
385
- destroy$.next(undefined)
386
- }
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ merge,
6
+ filter,
7
+ switchMap,
8
+ first,
9
+ iif,
10
+ takeUntil,
11
+ Subject,
12
+ Observable,
13
+ distinctUntilChanged } from 'rxjs'
14
+ import type {
15
+ EventTypeMap,
16
+ PluginConstructor,
17
+ ChartType,
18
+ ChartParams,
19
+ Layout
20
+ } from '../../lib/core-types'
21
+ import type { BaseTooltipParams, BaseTooltipStyle } from '../../lib/plugins-basic-types'
22
+ import type { BasePluginFn } from './types'
23
+ import { defineNoneDataPlugin, textSizePxObservable } from '../../lib/core'
24
+ import { getSvgGElementSize, appendSvg } from '../utils/d3Utils'
25
+ import { getColor, getClassName } from '../utils/orbchartsUtils'
26
+ import { measureTextWidth, toCurrency } from '../utils/commonUtils'
27
+
28
+ interface BaseTooltipContext {
29
+ rootSelection: d3.Selection<any, unknown, any, unknown>
30
+ fullParams$: Observable<BaseTooltipParams>
31
+ fullChartParams$: Observable<ChartParams>
32
+ layout$: Observable<Layout>
33
+ event$: Subject<EventTypeMap<any>>
34
+ }
35
+
36
+ // export interface BaseTooltipStyle {
37
+ // backgroundColor: string
38
+ // backgroundOpacity: number
39
+ // strokeColor: string
40
+ // offset: [number, number]
41
+ // padding: number
42
+ // textColor: string
43
+ // textSize: number | string // chartParams上的設定
44
+ // textSizePx: number
45
+ // }
46
+
47
+ function textToSvg (_textArr: string[] | string | null | undefined, textStyle: BaseTooltipStyle) {
48
+ const lineHeight = textStyle.textSizePx * 1.5
49
+
50
+ const textArr = _textArr == null
51
+ ? []
52
+ : Array.isArray(_textArr)
53
+ ? _textArr
54
+ : typeof _textArr === 'string'
55
+ ? _textArr.split('\n')
56
+ : [_textArr]
57
+
58
+ const tspan = textArr
59
+ .filter(d => d != '')
60
+ .map((text, i) => {
61
+ const top = i * lineHeight
62
+ return `<tspan x="0" y="${top}">${text}</tspan>`
63
+ })
64
+ .join('')
65
+
66
+ if (tspan) {
67
+ return `<text font-size="${textStyle.textSize}" fill="${textStyle.textColor}" x="0" y="0" style="dominant-baseline:text-before-edge">
68
+ ${tspan}
69
+ </text>`
70
+ } else {
71
+ return ''
72
+ }
73
+ }
74
+
75
+ function renderTooltip ({ rootSelection, pluginName, gClassName, boxClassName, rootWidth, rootHeight, svgString, tooltipStyle, event }: {
76
+ rootSelection: d3.Selection<any, unknown, any, unknown>
77
+ pluginName: string
78
+ gClassName: string
79
+ boxClassName: string
80
+ rootWidth: number
81
+ rootHeight: number
82
+ svgString: string
83
+ tooltipStyle: BaseTooltipStyle
84
+ event: MouseEvent
85
+ }) {
86
+ // if (!svgString) {
87
+ // return
88
+ // }
89
+ // const rootSelection = d3.select('svg.bpcharts__root')
90
+ // console.log('tooltip', { selection, rootWidth, rootHeight, svgString, event })
91
+ rootSelection.interrupt('fadeout')
92
+ const radius = 5
93
+
94
+ // data(svg string無值則為空陣列)
95
+ const contentData = svgString ? [svgString] : []
96
+ const styleData = svgString ? [tooltipStyle] : []
97
+ // tooltipG <g>
98
+ const tooltipG = rootSelection
99
+ .selectAll<SVGGElement, string>(`g.${gClassName}`)
100
+ .data(contentData)
101
+ .join(
102
+ enter => {
103
+ return enter
104
+ .append('g')
105
+ .classed(gClassName, true)
106
+ .attr('pointer-events', 'none')
107
+ },
108
+ update => {
109
+ return update
110
+ },
111
+ exit => {
112
+ return exit
113
+ // .transition('fadeout')
114
+ // .duration(0)
115
+ // .delay(500)
116
+ .style('opacity', 0)
117
+ .remove()
118
+ }
119
+ )
120
+ .attr('transform', () => `translate(${event.offsetX}, ${event.offsetY})`)
121
+
122
+ // tooltipBox <g><g>
123
+ const tooltipBox = tooltipG
124
+ .selectAll<SVGGElement, string>(`g.${boxClassName}`)
125
+ .data(styleData)
126
+ .join(
127
+ enter => {
128
+ return enter
129
+ .append('g')
130
+ .classed(getClassName(pluginName, 'box'), true)
131
+ },
132
+ )
133
+
134
+ // rect <g><g><rect>
135
+ const rect = tooltipBox
136
+ .selectAll<SVGRectElement, string>('rect')
137
+ .data(styleData)
138
+ .join(
139
+ enter => {
140
+ return enter
141
+ .append('rect')
142
+ .attr('rx', radius)
143
+ .attr('ry', radius)
144
+ }
145
+ )
146
+ .attr('fill', d => d.backgroundColor)
147
+ .attr('stroke', d => d.strokeColor)
148
+ .attr('opacity', d => d.backgroundOpacity)
149
+
150
+ // text <g><g><g>
151
+ const contentG = tooltipBox
152
+ .selectAll<SVGGElement, string>('g')
153
+ .data(contentData)
154
+ .join(
155
+ enter => {
156
+ return enter
157
+ .append('g')
158
+ .classed(getClassName(pluginName, 'content'), true)
159
+ .attr('transform', () => `translate(${tooltipStyle.padding}, ${tooltipStyle.padding})`)
160
+ }
161
+ )
162
+ // 使用字串加入svg
163
+ if (contentData.length) {
164
+ appendSvg(contentG, contentData[0])
165
+ }
166
+ const contentSize = (contentG?.node()) ? getSvgGElementSize(contentG!) : { width: 0, height: 0 }
167
+
168
+ // rect size
169
+ rect
170
+ .attr('width', contentSize.width + tooltipStyle.padding * 2)
171
+ .attr('height', contentSize.height + tooltipStyle.padding * 2)
172
+
173
+ // -- tooltipG --
174
+ // 取得tooltip <g>的尺寸
175
+ const tooltipSize = (tooltipBox?.node()) ? getSvgGElementSize(tooltipBox!) : { width: 0, height: 0 }
176
+ // const minX = 0
177
+ const maxX = rootWidth - tooltipSize.width
178
+ // const minY = 0
179
+ const maxY = rootHeight - tooltipSize.height
180
+
181
+ // -- 相對游標位置的offset --
182
+ const offsetX = (event.offsetX + tooltipStyle.offset[0]) > maxX ? maxX - event.offsetX : tooltipStyle.offset[0]
183
+ const offsetY = (event.offsetY + tooltipStyle.offset[1]) > maxY ? maxY - event.offsetY : tooltipStyle.offset[1]
184
+ tooltipBox.attr('transform', d => `translate(${offsetX}, ${offsetY})`)
185
+ tooltipBox.attr('transform', d => `translate(${offsetX}, ${offsetY})`)
186
+
187
+ // const minX = containerSize.x
188
+ // const maxX = containerSize.x + containerSize.width - tooltipSize.width
189
+ // const minY = containerSize.y
190
+ // const maxY = containerSize.y + containerSize.height - tooltipSize.height
191
+
192
+ // .style('position', 'absolute')
193
+ // .style('z-index', 10000)
194
+ // .style('left', (d) => {
195
+ // const x = d.x + this.fullParams.offsetX! - containerSize.x
196
+ // if (x < minX) {
197
+ // return `${minX}px`
198
+ // } else if (x > maxX) {
199
+ // return `${maxX}px`
200
+ // }
201
+ // return `${x}px`
202
+ // })
203
+ // .style('top', (d) => {
204
+ // const y = d.y + this.fullParams.offsetY! - containerSize.y
205
+ // if (y < minY) {
206
+ // return `${minY}px`
207
+ // } else if (y > maxY) {
208
+ // return `${maxY}px`
209
+ // }
210
+ // return `${y}px`
211
+ // })
212
+ // .html((d) => d.contentHtml)
213
+
214
+ }
215
+
216
+ export const createBaseTooltip: BasePluginFn<BaseTooltipContext> = (pluginName: string, {
217
+ rootSelection,
218
+ fullParams$,
219
+ fullChartParams$,
220
+ layout$,
221
+ event$
222
+ }) => {
223
+
224
+ const destroy$ = new Subject()
225
+
226
+ const gClassName = getClassName(pluginName, 'g')
227
+ const boxClassName = getClassName(pluginName, 'box')
228
+
229
+ // 事件觸發
230
+ const eventMouseover$: Observable<EventTypeMap<any>> = event$.pipe(
231
+ takeUntil(destroy$),
232
+ filter(d => d.eventName === 'mouseover' || d.eventName === 'mousemove'),
233
+ // distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
234
+ // map(d => d as EventTypeMap<typeof chartType>)
235
+ )
236
+ const eventMouseout$: Observable<EventTypeMap<any>> = event$.pipe(
237
+ takeUntil(destroy$),
238
+ filter(d => d.eventName === 'mouseout'),
239
+ // distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
240
+ // map(d => d as EventTypeMap<typeof chartType>)
241
+ )
242
+
243
+ const textSizePx$ = textSizePxObservable(fullChartParams$)
244
+
245
+ const tooltipStyle$: Observable<BaseTooltipStyle> = combineLatest({
246
+ fullChartParams: fullChartParams$,
247
+ fullParams: fullParams$,
248
+ textSizePx: textSizePx$
249
+ }).pipe(
250
+ takeUntil(destroy$),
251
+ switchMap(async d => d),
252
+ map(data => {
253
+ return {
254
+ backgroundColor: getColor(data.fullParams.backgroundColorType, data.fullChartParams),
255
+ backgroundOpacity: data.fullParams.backgroundOpacity,
256
+ strokeColor: getColor(data.fullParams.strokeColorType, data.fullChartParams),
257
+ offset: data.fullParams.offset,
258
+ padding: data.fullParams.padding,
259
+ textSize: data.fullChartParams.styles.textSize,
260
+ textSizePx: data.textSizePx,
261
+ textColor: getColor(data.fullParams.textColorType, data.fullChartParams),
262
+ seriesColors: data.fullChartParams.colors[data.fullChartParams.colorScheme].label
263
+ }
264
+ })
265
+ )
266
+
267
+ const contentRenderFn$: Observable<((eventData: EventTypeMap<any>) => string)> = combineLatest({
268
+ fullParams: fullParams$,
269
+ tooltipStyle: tooltipStyle$
270
+ }).pipe(
271
+ takeUntil(destroy$),
272
+ switchMap(async d => d),
273
+ map(data => {
274
+ // if (data.fullParams.svgRenderFn) {
275
+ // return data.fullParams.svgRenderFn
276
+ // }
277
+ // // 將textRenderFn回傳的資料使用<text>包裝起來
278
+ // return (eventData: EventTypeMap<any>) => {
279
+ // const textArr: string | string[] | null = data.fullParams.textRenderFn
280
+ // ? data.fullParams.textRenderFn(eventData as any)
281
+ // : null
282
+ // return textToSvg(textArr, data.tooltipStyle)
283
+ // }
284
+ return (eventData: EventTypeMap<any>) => {
285
+ const renderText: string | string[] = data.fullParams.renderFn(
286
+ // mouseover事件的資料
287
+ eventData,
288
+ // context
289
+ {
290
+ utils: {
291
+ toCurrency,
292
+ measureTextWidth
293
+ },
294
+ styles: data.tooltipStyle
295
+ }
296
+ )
297
+ // string
298
+ if (typeof renderText === 'string') {
299
+ const trimText = renderText.trim()
300
+ const isSvgText = trimText.slice(0, 1) === '<' && trimText.slice(trimText.length - 1, trimText.length) === '>'
301
+
302
+ if (isSvgText) {
303
+ return renderText // svg字串
304
+ } else {
305
+ const textArr = renderText.split('\n')
306
+ return textToSvg(textArr, data.tooltipStyle) // 多行文字轉svg字串
307
+ }
308
+ }
309
+ // string[]
310
+ else if (Array.isArray(renderText)) {
311
+ return textToSvg(renderText, data.tooltipStyle) // 多行文字轉svg字串
312
+ }
313
+ return ''
314
+ }
315
+ })
316
+ )
317
+
318
+ const mouseoverTooltipSvg$ = combineLatest({
319
+ event: eventMouseover$,
320
+ contentRenderFn: contentRenderFn$
321
+ }).pipe(
322
+ takeUntil(destroy$),
323
+ switchMap(async d => d),
324
+ map(d => d.contentRenderFn(d.event))
325
+ )
326
+
327
+ const mouseoutTooltipSvg$ = eventMouseout$.pipe(
328
+ takeUntil(destroy$),
329
+ map(d => '')
330
+ )
331
+
332
+ const svgString$ = merge(mouseoverTooltipSvg$, mouseoutTooltipSvg$)
333
+ .pipe(
334
+ takeUntil(destroy$),
335
+ distinctUntilChanged((a, b) => a === b)
336
+ )
337
+
338
+ const eventTooltip$ = merge(eventMouseover$, eventMouseout$)
339
+ .pipe(
340
+ takeUntil(destroy$),
341
+ // filter(d => {
342
+ // return (d.eventName === 'mouseover' || d.eventName === 'mousemove' || d.eventName === 'mouseout')
343
+ // && d.event != undefined
344
+ // }),
345
+ map(d => d.event!),
346
+ )
347
+
348
+ combineLatest({
349
+ svgString: svgString$,
350
+ eventTooltip: eventTooltip$,
351
+ layout: layout$,
352
+ tooltipStyle: tooltipStyle$,
353
+ }).pipe(
354
+ takeUntil(destroy$),
355
+ switchMap(async d => d),
356
+ ).subscribe(data => {
357
+ renderTooltip({
358
+ rootSelection,
359
+ pluginName,
360
+ gClassName,
361
+ boxClassName,
362
+ rootWidth: data.layout.rootWidth,
363
+ rootHeight: data.layout.rootHeight,
364
+ svgString: data.svgString,
365
+ tooltipStyle: data.tooltipStyle,
366
+ event: data.eventTooltip
367
+ })
368
+ })
369
+
370
+
371
+
372
+ // const chartType$ = eventMouseover$.pipe(
373
+ // takeUntil(destroy$),
374
+ // map(d => d.type),
375
+ // distinctUntilChanged()
376
+ // )
377
+
378
+
379
+ // eventMouseover$.subscribe(event => {
380
+
381
+ // })
382
+
383
+
384
+ return () => {
385
+ destroy$.next(undefined)
386
+ }
387
387
  }