@orbcharts/plugins-basic 3.0.0-beta.10 → 3.0.0-beta.12

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 (99) hide show
  1. package/LICENSE +200 -200
  2. package/dist/orbcharts-plugins-basic.es.js +179 -178
  3. package/dist/orbcharts-plugins-basic.umd.js +28 -28
  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 +679 -679
  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/BaseStackedBar.ts +782 -782
  16. package/src/base/BaseTooltip.ts +385 -385
  17. package/src/base/BaseValueAxis.ts +583 -583
  18. package/src/base/types.ts +2 -2
  19. package/src/const.ts +30 -30
  20. package/src/grid/defaults.ts +246 -246
  21. package/src/grid/gridObservables.ts +554 -554
  22. package/src/grid/index.ts +16 -16
  23. package/src/grid/plugins/Bars.ts +69 -69
  24. package/src/grid/plugins/BarsPN.ts +66 -66
  25. package/src/grid/plugins/BarsTriangle.ts +73 -73
  26. package/src/grid/plugins/Dots.ts +68 -68
  27. package/src/grid/plugins/GridLegend.ts +107 -107
  28. package/src/grid/plugins/GridTooltip.ts +66 -66
  29. package/src/grid/plugins/GridZoom.ts +218 -218
  30. package/src/grid/plugins/GroupAux.ts +1103 -1103
  31. package/src/grid/plugins/GroupAxis.ts +97 -97
  32. package/src/grid/plugins/LineAreas.ts +65 -65
  33. package/src/grid/plugins/Lines.ts +59 -59
  34. package/src/grid/plugins/StackedBar.ts +64 -64
  35. package/src/grid/plugins/StackedValueAxis.ts +96 -96
  36. package/src/grid/plugins/ValueAxis.ts +94 -94
  37. package/src/index.ts +6 -6
  38. package/src/multiGrid/defaults.ts +224 -224
  39. package/src/multiGrid/index.ts +14 -14
  40. package/src/multiGrid/multiGridObservables.ts +49 -49
  41. package/src/multiGrid/plugins/MultiBars.ts +108 -108
  42. package/src/multiGrid/plugins/MultiBarsTriangle.ts +114 -114
  43. package/src/multiGrid/plugins/MultiDots.ts +102 -102
  44. package/src/multiGrid/plugins/MultiGridLegend.ts +159 -159
  45. package/src/multiGrid/plugins/MultiGridTooltip.ts +66 -66
  46. package/src/multiGrid/plugins/MultiGroupAxis.ts +137 -137
  47. package/src/multiGrid/plugins/MultiLineAreas.ts +107 -107
  48. package/src/multiGrid/plugins/MultiLines.ts +101 -101
  49. package/src/multiGrid/plugins/MultiStackedBar.ts +106 -106
  50. package/src/multiGrid/plugins/MultiStackedValueAxis.ts +134 -134
  51. package/src/multiGrid/plugins/MultiValueAxis.ts +134 -134
  52. package/src/multiGrid/plugins/OverlappingStackedValueAxes.ts +299 -299
  53. package/src/multiGrid/plugins/OverlappingValueAxes.ts +300 -300
  54. package/src/multiValue/defaults.ts +166 -166
  55. package/src/multiValue/index.ts +8 -8
  56. package/src/multiValue/multiValueObservables.ts +297 -297
  57. package/src/multiValue/plugins/MultiValueLegend.ts +107 -107
  58. package/src/multiValue/plugins/MultiValueTooltip.ts +66 -66
  59. package/src/multiValue/plugins/Scatter.ts +426 -426
  60. package/src/multiValue/plugins/ScatterBubbles.ts +554 -554
  61. package/src/multiValue/plugins/XYAux.ts +681 -681
  62. package/src/multiValue/plugins/XYAxes.ts +684 -684
  63. package/src/multiValue/plugins/XYZoom.ts +299 -299
  64. package/src/noneData/defaults.ts +102 -102
  65. package/src/noneData/index.ts +3 -3
  66. package/src/noneData/plugins/Container.ts +27 -27
  67. package/src/noneData/plugins/Tooltip.ts +373 -373
  68. package/src/relationship/defaults.ts +196 -195
  69. package/src/relationship/index.ts +5 -5
  70. package/src/relationship/plugins/ForceDirected.ts +1168 -1166
  71. package/src/relationship/plugins/ForceDirectedBubbles.ts +1395 -1390
  72. package/src/relationship/plugins/RelationshipLegend.ts +100 -100
  73. package/src/relationship/plugins/RelationshipTooltip.ts +66 -66
  74. package/src/relationship/relationshipObservables.ts +49 -49
  75. package/src/series/defaults.ts +207 -206
  76. package/src/series/index.ts +9 -9
  77. package/src/series/plugins/Bubbles.ts +606 -606
  78. package/src/series/plugins/Pie.ts +623 -623
  79. package/src/series/plugins/PieEventTexts.ts +284 -284
  80. package/src/series/plugins/PieLabels.ts +640 -640
  81. package/src/series/plugins/Rose.ts +516 -516
  82. package/src/series/plugins/RoseLabels.ts +600 -600
  83. package/src/series/plugins/SeriesLegend.ts +107 -107
  84. package/src/series/plugins/SeriesTooltip.ts +66 -66
  85. package/src/series/seriesObservables.ts +145 -145
  86. package/src/series/seriesUtils.ts +51 -51
  87. package/src/tree/defaults.ts +78 -78
  88. package/src/tree/index.ts +4 -4
  89. package/src/tree/plugins/TreeLegend.ts +100 -100
  90. package/src/tree/plugins/TreeMap.ts +333 -333
  91. package/src/tree/plugins/TreeTooltip.ts +66 -66
  92. package/src/utils/commonUtils.ts +21 -21
  93. package/src/utils/d3Graphics.ts +174 -174
  94. package/src/utils/d3Utils.ts +74 -74
  95. package/src/utils/observables.ts +14 -14
  96. package/src/utils/orbchartsUtils.ts +115 -115
  97. package/tsconfig.base.json +13 -13
  98. package/tsconfig.json +2 -2
  99. 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
  }