@orbcharts/plugins-basic 3.0.0-beta.13 → 3.0.0-beta.15

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 (100) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic/src/utils/d3Utils.d.ts +1 -0
  3. package/dist/orbcharts-plugins-basic.es.js +5558 -5443
  4. package/dist/orbcharts-plugins-basic.umd.js +159 -78
  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/BaseBars.ts +765 -765
  10. package/src/base/BaseBarsTriangle.ts +676 -676
  11. package/src/base/BaseDots.ts +464 -464
  12. package/src/base/BaseGroupAxis.ts +679 -679
  13. package/src/base/BaseLegend.ts +684 -684
  14. package/src/base/BaseLineAreas.ts +629 -629
  15. package/src/base/BaseLines.ts +706 -706
  16. package/src/base/BaseStackedBar.ts +782 -782
  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 +250 -246
  22. package/src/grid/gridObservables.ts +554 -554
  23. package/src/grid/index.ts +16 -16
  24. package/src/grid/plugins/Bars.ts +69 -69
  25. package/src/grid/plugins/BarsPN.ts +66 -66
  26. package/src/grid/plugins/BarsTriangle.ts +73 -73
  27. package/src/grid/plugins/Dots.ts +68 -68
  28. package/src/grid/plugins/GridLegend.ts +107 -107
  29. package/src/grid/plugins/GridTooltip.ts +66 -66
  30. package/src/grid/plugins/GridZoom.ts +218 -218
  31. package/src/grid/plugins/GroupAux.ts +1103 -1103
  32. package/src/grid/plugins/GroupAxis.ts +97 -97
  33. package/src/grid/plugins/LineAreas.ts +65 -65
  34. package/src/grid/plugins/Lines.ts +59 -59
  35. package/src/grid/plugins/StackedBar.ts +64 -64
  36. package/src/grid/plugins/StackedValueAxis.ts +96 -96
  37. package/src/grid/plugins/ValueAxis.ts +94 -94
  38. package/src/index.ts +6 -6
  39. package/src/multiGrid/defaults.ts +228 -224
  40. package/src/multiGrid/index.ts +14 -14
  41. package/src/multiGrid/multiGridObservables.ts +49 -49
  42. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  43. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  44. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  45. package/src/multiGrid/plugins/MultiGridLegend.ts +159 -159
  46. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  47. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  48. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  49. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  50. package/src/multiGrid/plugins/MultiStackedBar.ts +106 -106
  51. package/src/multiGrid/plugins/MultiStackedValueAxis.ts +134 -134
  52. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  53. package/src/multiGrid/plugins/OverlappingStackedValueAxes.ts +299 -299
  54. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  55. package/src/multiValue/defaults.ts +166 -166
  56. package/src/multiValue/index.ts +8 -8
  57. package/src/multiValue/multiValueObservables.ts +297 -297
  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 -554
  62. package/src/multiValue/plugins/XYAux.ts +681 -681
  63. package/src/multiValue/plugins/XYAxes.ts +684 -684
  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/relationship/defaults.ts +218 -196
  70. package/src/relationship/index.ts +5 -5
  71. package/src/relationship/plugins/ForceDirected.ts +1168 -1168
  72. package/src/relationship/plugins/ForceDirectedBubbles.ts +1403 -1395
  73. package/src/relationship/plugins/RelationshipLegend.ts +100 -100
  74. package/src/relationship/plugins/RelationshipTooltip.ts +66 -66
  75. package/src/relationship/relationshipObservables.ts +49 -49
  76. package/src/series/defaults.ts +230 -207
  77. package/src/series/index.ts +9 -9
  78. package/src/series/plugins/Bubbles.ts +620 -606
  79. package/src/series/plugins/Pie.ts +623 -623
  80. package/src/series/plugins/PieEventTexts.ts +284 -284
  81. package/src/series/plugins/PieLabels.ts +640 -640
  82. package/src/series/plugins/Rose.ts +516 -516
  83. package/src/series/plugins/RoseLabels.ts +600 -600
  84. package/src/series/plugins/SeriesLegend.ts +107 -107
  85. package/src/series/plugins/SeriesTooltip.ts +66 -66
  86. package/src/series/seriesObservables.ts +145 -145
  87. package/src/series/seriesUtils.ts +51 -51
  88. package/src/tree/defaults.ts +100 -78
  89. package/src/tree/index.ts +4 -4
  90. package/src/tree/plugins/TreeLegend.ts +100 -100
  91. package/src/tree/plugins/TreeMap.ts +341 -333
  92. package/src/tree/plugins/TreeTooltip.ts +66 -66
  93. package/src/utils/commonUtils.ts +21 -21
  94. package/src/utils/d3Graphics.ts +174 -174
  95. package/src/utils/d3Utils.ts +92 -74
  96. package/src/utils/observables.ts +14 -14
  97. package/src/utils/orbchartsUtils.ts +129 -115
  98. package/tsconfig.base.json +13 -13
  99. package/tsconfig.json +2 -2
  100. package/vite.config.js +22 -22
@@ -1,386 +1,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 } 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
- measureTextWidth
292
- },
293
- styles: data.tooltipStyle
294
- }
295
- )
296
- // string
297
- if (typeof renderText === 'string') {
298
- const trimText = renderText.trim()
299
- const isSvgText = trimText.slice(0, 1) === '<' && trimText.slice(trimText.length - 1, trimText.length) === '>'
300
-
301
- if (isSvgText) {
302
- return renderText // svg字串
303
- } else {
304
- const textArr = renderText.split('\n')
305
- return textToSvg(textArr, data.tooltipStyle) // 多行文字轉svg字串
306
- }
307
- }
308
- // string[]
309
- else if (Array.isArray(renderText)) {
310
- return textToSvg(renderText, data.tooltipStyle) // 多行文字轉svg字串
311
- }
312
- return ''
313
- }
314
- })
315
- )
316
-
317
- const mouseoverTooltipSvg$ = combineLatest({
318
- event: eventMouseover$,
319
- contentRenderFn: contentRenderFn$
320
- }).pipe(
321
- takeUntil(destroy$),
322
- switchMap(async d => d),
323
- map(d => d.contentRenderFn(d.event))
324
- )
325
-
326
- const mouseoutTooltipSvg$ = eventMouseout$.pipe(
327
- takeUntil(destroy$),
328
- map(d => '')
329
- )
330
-
331
- const svgString$ = merge(mouseoverTooltipSvg$, mouseoutTooltipSvg$)
332
- .pipe(
333
- takeUntil(destroy$),
334
- distinctUntilChanged((a, b) => a === b)
335
- )
336
-
337
- const eventTooltip$ = merge(eventMouseover$, eventMouseout$)
338
- .pipe(
339
- takeUntil(destroy$),
340
- // filter(d => {
341
- // return (d.eventName === 'mouseover' || d.eventName === 'mousemove' || d.eventName === 'mouseout')
342
- // && d.event != undefined
343
- // }),
344
- map(d => d.event!),
345
- )
346
-
347
- combineLatest({
348
- svgString: svgString$,
349
- eventTooltip: eventTooltip$,
350
- layout: layout$,
351
- tooltipStyle: tooltipStyle$,
352
- }).pipe(
353
- takeUntil(destroy$),
354
- switchMap(async d => d),
355
- ).subscribe(data => {
356
- renderTooltip({
357
- rootSelection,
358
- pluginName,
359
- gClassName,
360
- boxClassName,
361
- rootWidth: data.layout.rootWidth,
362
- rootHeight: data.layout.rootHeight,
363
- svgString: data.svgString,
364
- tooltipStyle: data.tooltipStyle,
365
- event: data.eventTooltip
366
- })
367
- })
368
-
369
-
370
-
371
- // const chartType$ = eventMouseover$.pipe(
372
- // takeUntil(destroy$),
373
- // map(d => d.type),
374
- // distinctUntilChanged()
375
- // )
376
-
377
-
378
- // eventMouseover$.subscribe(event => {
379
-
380
- // })
381
-
382
-
383
- return () => {
384
- destroy$.next(undefined)
385
- }
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 } 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
+ measureTextWidth
292
+ },
293
+ styles: data.tooltipStyle
294
+ }
295
+ )
296
+ // string
297
+ if (typeof renderText === 'string') {
298
+ const trimText = renderText.trim()
299
+ const isSvgText = trimText.slice(0, 1) === '<' && trimText.slice(trimText.length - 1, trimText.length) === '>'
300
+
301
+ if (isSvgText) {
302
+ return renderText // svg字串
303
+ } else {
304
+ const textArr = renderText.split('\n')
305
+ return textToSvg(textArr, data.tooltipStyle) // 多行文字轉svg字串
306
+ }
307
+ }
308
+ // string[]
309
+ else if (Array.isArray(renderText)) {
310
+ return textToSvg(renderText, data.tooltipStyle) // 多行文字轉svg字串
311
+ }
312
+ return ''
313
+ }
314
+ })
315
+ )
316
+
317
+ const mouseoverTooltipSvg$ = combineLatest({
318
+ event: eventMouseover$,
319
+ contentRenderFn: contentRenderFn$
320
+ }).pipe(
321
+ takeUntil(destroy$),
322
+ switchMap(async d => d),
323
+ map(d => d.contentRenderFn(d.event))
324
+ )
325
+
326
+ const mouseoutTooltipSvg$ = eventMouseout$.pipe(
327
+ takeUntil(destroy$),
328
+ map(d => '')
329
+ )
330
+
331
+ const svgString$ = merge(mouseoverTooltipSvg$, mouseoutTooltipSvg$)
332
+ .pipe(
333
+ takeUntil(destroy$),
334
+ distinctUntilChanged((a, b) => a === b)
335
+ )
336
+
337
+ const eventTooltip$ = merge(eventMouseover$, eventMouseout$)
338
+ .pipe(
339
+ takeUntil(destroy$),
340
+ // filter(d => {
341
+ // return (d.eventName === 'mouseover' || d.eventName === 'mousemove' || d.eventName === 'mouseout')
342
+ // && d.event != undefined
343
+ // }),
344
+ map(d => d.event!),
345
+ )
346
+
347
+ combineLatest({
348
+ svgString: svgString$,
349
+ eventTooltip: eventTooltip$,
350
+ layout: layout$,
351
+ tooltipStyle: tooltipStyle$,
352
+ }).pipe(
353
+ takeUntil(destroy$),
354
+ switchMap(async d => d),
355
+ ).subscribe(data => {
356
+ renderTooltip({
357
+ rootSelection,
358
+ pluginName,
359
+ gClassName,
360
+ boxClassName,
361
+ rootWidth: data.layout.rootWidth,
362
+ rootHeight: data.layout.rootHeight,
363
+ svgString: data.svgString,
364
+ tooltipStyle: data.tooltipStyle,
365
+ event: data.eventTooltip
366
+ })
367
+ })
368
+
369
+
370
+
371
+ // const chartType$ = eventMouseover$.pipe(
372
+ // takeUntil(destroy$),
373
+ // map(d => d.type),
374
+ // distinctUntilChanged()
375
+ // )
376
+
377
+
378
+ // eventMouseover$.subscribe(event => {
379
+
380
+ // })
381
+
382
+
383
+ return () => {
384
+ destroy$.next(undefined)
385
+ }
386
386
  }