@orbcharts/plugins-basic 3.0.0-alpha.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. package/LICENSE +201 -0
  2. package/package.json +41 -0
  3. package/src/grid/defaults.ts +95 -0
  4. package/src/grid/gridObservables.ts +114 -0
  5. package/src/grid/index.ts +12 -0
  6. package/src/grid/plugins/BarStack.ts +661 -0
  7. package/src/grid/plugins/Bars.ts +604 -0
  8. package/src/grid/plugins/BarsTriangle.ts +594 -0
  9. package/src/grid/plugins/Dots.ts +427 -0
  10. package/src/grid/plugins/GroupArea.ts +636 -0
  11. package/src/grid/plugins/GroupAxis.ts +363 -0
  12. package/src/grid/plugins/Lines.ts +528 -0
  13. package/src/grid/plugins/Ranking.ts +0 -0
  14. package/src/grid/plugins/RankingAxis.ts +0 -0
  15. package/src/grid/plugins/ScalingArea.ts +168 -0
  16. package/src/grid/plugins/ValueAxis.ts +356 -0
  17. package/src/grid/plugins/ValueStackAxis.ts +372 -0
  18. package/src/grid/types.ts +102 -0
  19. package/src/index.ts +7 -0
  20. package/src/multiGrid/index.ts +0 -0
  21. package/src/multiGrid/plugins/Diverging.ts +0 -0
  22. package/src/multiGrid/plugins/DivergingAxes.ts +0 -0
  23. package/src/multiGrid/plugins/TwoScaleAxes.ts +0 -0
  24. package/src/multiGrid/plugins/TwoScales.ts +0 -0
  25. package/src/multiValue/index.ts +0 -0
  26. package/src/multiValue/plugins/Scatter.ts +0 -0
  27. package/src/multiValue/plugins/ScatterAxes.ts +0 -0
  28. package/src/noneData/defaults.ts +47 -0
  29. package/src/noneData/index.ts +4 -0
  30. package/src/noneData/plugins/Container.ts +11 -0
  31. package/src/noneData/plugins/Tooltip.ts +305 -0
  32. package/src/noneData/types.ts +26 -0
  33. package/src/relationship/index.ts +0 -0
  34. package/src/relationship/plugins/Relationship.ts +0 -0
  35. package/src/series/defaults.ts +82 -0
  36. package/src/series/index.ts +6 -0
  37. package/src/series/plugins/Bubbles.ts +553 -0
  38. package/src/series/plugins/Pie.ts +603 -0
  39. package/src/series/plugins/PieEventTexts.ts +194 -0
  40. package/src/series/plugins/PieLabels.ts +289 -0
  41. package/src/series/plugins/Waffle.ts +0 -0
  42. package/src/series/seriesUtils.ts +51 -0
  43. package/src/series/types.ts +53 -0
  44. package/src/tree/index.ts +0 -0
  45. package/src/tree/plugins/TreeMap.ts +0 -0
  46. package/src/utils/commonUtils.ts +22 -0
  47. package/src/utils/d3Graphics.ts +125 -0
  48. package/src/utils/d3Utils.ts +73 -0
  49. package/src/utils/observables.ts +14 -0
  50. package/src/utils/orbchartsUtils.ts +70 -0
  51. package/tsconfig.json +14 -0
  52. package/vite.config.js +45 -0
@@ -0,0 +1,305 @@
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, PluginConstructor, ChartType } from '@orbcharts/core'
16
+ import { defineNoneDataPlugin } from '@orbcharts/core'
17
+ import { getSvgGElementSize, appendSvg } from '../../utils/d3Utils'
18
+ import { getColor, getClassName } from '../../utils/orbchartsUtils'
19
+ import { TOOLTIP_PARAMS } from '../defaults'
20
+
21
+ interface TooltipStyle {
22
+ backgroundColor: string
23
+ backgroundOpacity: number
24
+ strokeColor: string
25
+ offset: [number, number]
26
+ padding: number
27
+ textColor: string
28
+ textSize: number
29
+ }
30
+
31
+ const pluginName = 'Tooltip'
32
+ const gClassName = getClassName(pluginName, 'g')
33
+ const boxClassName = getClassName(pluginName, 'box')
34
+
35
+ function textToSvg (textArr: string[], textStyle: TooltipStyle) {
36
+ const lineHeight = textStyle.textSize * 1.5
37
+ return textArr
38
+ .filter(d => d != '')
39
+ .map((text, i) => {
40
+ const top = i * lineHeight
41
+ return `<text font-size="${textStyle.textSize}" fill="${textStyle.textColor}" x="0" y="${top}" style="dominant-baseline:text-before-edge">${text}</text>`
42
+ })
43
+ .join()
44
+ }
45
+
46
+ function renderTooltip ({ rootSelection, pluginName, rootWidth, rootHeight, svgString, tooltipStyle, event }: {
47
+ rootSelection: d3.Selection<any, unknown, any, unknown>
48
+ pluginName: string
49
+ rootWidth: number
50
+ rootHeight: number
51
+ svgString: string
52
+ tooltipStyle: TooltipStyle
53
+ event: MouseEvent
54
+ }) {
55
+ // if (!svgString) {
56
+ // return
57
+ // }
58
+ // const rootSelection = d3.select('svg.bpcharts__root')
59
+ // console.log('tooltip', { selection, rootWidth, rootHeight, svgString, event })
60
+ rootSelection.interrupt('fadeout')
61
+ const radius = 5
62
+
63
+ // data(svg string無值則為空陣列)
64
+ const contentData = svgString ? [svgString] : []
65
+ const styleData = svgString ? [tooltipStyle] : []
66
+ // tooltipG <g>
67
+ const tooltipG = rootSelection
68
+ .selectAll<SVGGElement, string>(`g.${gClassName}`)
69
+ .data(contentData)
70
+ .join(
71
+ enter => {
72
+ return enter
73
+ .append('g')
74
+ .classed(gClassName, true)
75
+ .attr('pointer-events', 'none')
76
+ },
77
+ update => {
78
+ return update
79
+ },
80
+ exit => {
81
+ return exit
82
+ // .transition('fadeout')
83
+ // .duration(0)
84
+ // .delay(500)
85
+ .style('opacity', 0)
86
+ .remove()
87
+ }
88
+ )
89
+ .attr('transform', () => `translate(${event.offsetX}, ${event.offsetY})`)
90
+
91
+ // tooltipBox <g><g>
92
+ const tooltipBox = tooltipG
93
+ .selectAll<SVGGElement, string>(`g.${boxClassName}`)
94
+ .data(styleData)
95
+ .join(
96
+ enter => {
97
+ return enter
98
+ .append('g')
99
+ .classed(getClassName(pluginName, 'box'), true)
100
+ },
101
+ )
102
+
103
+ // rect <g><g><rect>
104
+ const rect = tooltipBox
105
+ .selectAll<SVGRectElement, string>('rect')
106
+ .data(styleData)
107
+ .join(
108
+ enter => {
109
+ return enter
110
+ .append('rect')
111
+ .attr('rx', radius)
112
+ .attr('ry', radius)
113
+ }
114
+ )
115
+ .attr('fill', d => d.backgroundColor)
116
+ .attr('stroke', d => d.strokeColor)
117
+ .attr('opacity', d => d.backgroundOpacity)
118
+
119
+ // text <g><g><g>
120
+ const contentG = tooltipBox
121
+ .selectAll<SVGGElement, string>('g')
122
+ .data(contentData)
123
+ .join(
124
+ enter => {
125
+ return enter
126
+ .append('g')
127
+ .classed(getClassName(pluginName, 'content'), true)
128
+ .attr('transform', () => `translate(${tooltipStyle.padding}, ${tooltipStyle.padding})`)
129
+ }
130
+ )
131
+ // 使用字串加入svg
132
+ if (contentData.length) {
133
+ appendSvg(contentG, contentData[0])
134
+ }
135
+ const contentSize = (contentG?.node()) ? getSvgGElementSize(contentG!) : { width: 0, height: 0 }
136
+
137
+ // rect size
138
+ rect
139
+ .attr('width', contentSize.width + tooltipStyle.padding * 2)
140
+ .attr('height', contentSize.height + tooltipStyle.padding * 2)
141
+
142
+ // -- tooltipG --
143
+ // 取得tooltip <g>的尺寸
144
+ const tooltipSize = (tooltipBox?.node()) ? getSvgGElementSize(tooltipBox!) : { width: 0, height: 0 }
145
+ // const minX = 0
146
+ const maxX = rootWidth - tooltipSize.width
147
+ // const minY = 0
148
+ const maxY = rootHeight - tooltipSize.height
149
+
150
+ // -- 相對游標位置的offset --
151
+ const offsetX = (event.offsetX + tooltipStyle.offset[0]) > maxX ? maxX - event.offsetX : tooltipStyle.offset[0]
152
+ const offsetY = (event.offsetY + tooltipStyle.offset[1]) > maxY ? maxY - event.offsetY : tooltipStyle.offset[1]
153
+ tooltipBox.attr('transform', d => `translate(${offsetX}, ${offsetY})`)
154
+ tooltipBox.attr('transform', d => `translate(${offsetX}, ${offsetY})`)
155
+
156
+
157
+
158
+ // const minX = containerSize.x
159
+ // const maxX = containerSize.x + containerSize.width - tooltipSize.width
160
+ // const minY = containerSize.y
161
+ // const maxY = containerSize.y + containerSize.height - tooltipSize.height
162
+
163
+
164
+ // .style('position', 'absolute')
165
+ // .style('z-index', 10000)
166
+ // .style('left', (d) => {
167
+ // const x = d.x + this.fullParams.offsetX! - containerSize.x
168
+ // if (x < minX) {
169
+ // return `${minX}px`
170
+ // } else if (x > maxX) {
171
+ // return `${maxX}px`
172
+ // }
173
+ // return `${x}px`
174
+ // })
175
+ // .style('top', (d) => {
176
+ // const y = d.y + this.fullParams.offsetY! - containerSize.y
177
+ // if (y < minY) {
178
+ // return `${minY}px`
179
+ // } else if (y > maxY) {
180
+ // return `${maxY}px`
181
+ // }
182
+ // return `${y}px`
183
+ // })
184
+ // .html((d) => d.contentHtml)
185
+
186
+
187
+ }
188
+
189
+ export const Tooltip: PluginConstructor<any, string, any> = defineNoneDataPlugin(pluginName, TOOLTIP_PARAMS)(({ selection, rootSelection, name, chartType, observer, subject }) => {
190
+ const destroy$ = new Subject()
191
+
192
+ // 事件觸發
193
+ const eventMouseover$: Observable<EventTypeMap<any>> = subject.event$.pipe(
194
+ takeUntil(destroy$),
195
+ filter(d => d.eventName === 'mouseover' || d.eventName === 'mousemove'),
196
+ // distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
197
+ // map(d => d as EventTypeMap<typeof chartType>)
198
+ )
199
+ const eventMouseout$: Observable<EventTypeMap<any>> = subject.event$.pipe(
200
+ takeUntil(destroy$),
201
+ filter(d => d.eventName === 'mouseout'),
202
+ // distinctUntilChanged((prev, current) => prev.eventName === current.eventName)
203
+ // map(d => d as EventTypeMap<typeof chartType>)
204
+ )
205
+
206
+ const tooltipStyle$: Observable<TooltipStyle> = combineLatest({
207
+ fullChartParams: observer.fullChartParams$,
208
+ fullParams: observer.fullParams$
209
+ }).pipe(
210
+ takeUntil(destroy$),
211
+ switchMap(async d => d),
212
+ map(data => {
213
+ return {
214
+ backgroundColor: getColor(data.fullParams.backgroundColorType, data.fullChartParams),
215
+ backgroundOpacity: data.fullParams.backgroundOpacity,
216
+ strokeColor: getColor(data.fullParams.strokeColorType, data.fullChartParams),
217
+ offset: data.fullParams.offset,
218
+ padding: data.fullParams.padding,
219
+ textSize: data.fullChartParams.styles.textSize,
220
+ textColor: getColor(data.fullParams.textColorType, data.fullChartParams),
221
+ }
222
+ })
223
+ )
224
+
225
+ const contentRenderFn$: Observable<((eventData: EventTypeMap<any>) => string)> = combineLatest({
226
+ fullParams: observer.fullParams$,
227
+ tooltipStyle: tooltipStyle$
228
+ }).pipe(
229
+ takeUntil(destroy$),
230
+ switchMap(async d => d),
231
+ map(d => {
232
+ if (d.fullParams.svgRenderFn) {
233
+ return d.fullParams.svgRenderFn
234
+ }
235
+ // 將textRenderFn回傳的資料使用<text>包裝起來
236
+ return (eventData: EventTypeMap<any>) => {
237
+ const textArr = d.fullParams.textRenderFn(eventData as any)
238
+ return textToSvg(textArr, d.tooltipStyle)
239
+ }
240
+ })
241
+ )
242
+
243
+ const mouseoverTooltipSvg$ = combineLatest({
244
+ event: eventMouseover$,
245
+ contentRenderFn: contentRenderFn$
246
+ }).pipe(
247
+ takeUntil(destroy$),
248
+ switchMap(async d => d),
249
+ map(d => d.contentRenderFn(d.event))
250
+ )
251
+
252
+ const mouseoutTooltipSvg$ = eventMouseout$.pipe(
253
+ takeUntil(destroy$),
254
+ map(d => '')
255
+ )
256
+
257
+ const event$ = merge(eventMouseover$, eventMouseout$)
258
+ .pipe(
259
+ takeUntil(destroy$),
260
+ // filter(d => {
261
+ // return (d.eventName === 'mouseover' || d.eventName === 'mousemove' || d.eventName === 'mouseout')
262
+ // && d.event != undefined
263
+ // }),
264
+ map(d => d.event!),
265
+ )
266
+
267
+ combineLatest({
268
+ svgString: merge(mouseoverTooltipSvg$, mouseoutTooltipSvg$),
269
+ event: event$,
270
+ layout: observer.layout$,
271
+ tooltipStyle: tooltipStyle$,
272
+ }).pipe(
273
+ takeUntil(destroy$),
274
+ switchMap(async d => d),
275
+ ).subscribe(data => {
276
+ // console.log('svgString', data.svgString)
277
+ renderTooltip({
278
+ rootSelection,
279
+ pluginName: name,
280
+ rootWidth: data.layout.rootWidth,
281
+ rootHeight: data.layout.rootHeight,
282
+ svgString: data.svgString,
283
+ tooltipStyle: data.tooltipStyle,
284
+ event: data.event
285
+ })
286
+ })
287
+
288
+
289
+
290
+ // const chartType$ = eventMouseover$.pipe(
291
+ // takeUntil(destroy$),
292
+ // map(d => d.type),
293
+ // distinctUntilChanged()
294
+ // )
295
+
296
+
297
+ // eventMouseover$.subscribe(event => {
298
+
299
+ // })
300
+
301
+
302
+ return function unsubscribe () {
303
+ destroy$.next(undefined)
304
+ }
305
+ })
@@ -0,0 +1,26 @@
1
+ import type { ColorType, ChartType, EventTypeMap, EventBase } from '@orbcharts/core'
2
+
3
+ export interface ContainerPluginParams {
4
+ header: {
5
+ height: number
6
+ text: string[]
7
+ textStyle: string[]
8
+ }
9
+ footer: {
10
+ height: number
11
+ text: string[]
12
+ textStyle: string[]
13
+ }
14
+ }
15
+
16
+ export type TooltipParams = {
17
+ backgroundColorType: ColorType
18
+ backgroundOpacity: number
19
+ strokeColorType: ColorType
20
+ textColorType: ColorType
21
+ offset: [number, number]
22
+ padding: number
23
+ textRenderFn: <T extends ChartType> (eventData: EventTypeMap<T>) => string[]
24
+ svgRenderFn: (<T extends ChartType> (eventData: EventTypeMap<T>) => string) | null
25
+ }
26
+
File without changes
File without changes
@@ -0,0 +1,82 @@
1
+ import type { ComputedDatumSeries, EventSeries, EventName, ColorType } from '@orbcharts/core'
2
+ import {
3
+ BubblesPluginParams,
4
+ PiePluginParams,
5
+ PieEventTextsPluginParams,
6
+ PieLabelsPluginParams } from './types'
7
+
8
+ export const DEFAULT_BUBBLES_PLUGIN_PARAMS: BubblesPluginParams = {
9
+ force: {
10
+ strength: 0.03, // 泡泡引力
11
+ velocityDecay: 0.2, // 衰減數
12
+ collisionSpacing: 2 // 泡泡間距
13
+ },
14
+ bubbleText: {
15
+ fillRate: 0.6,
16
+ lineHeight: 12,
17
+ lineLengthMin: 4
18
+ },
19
+ highlightRIncrease: 0,
20
+ scaleType: 'area'
21
+ }
22
+
23
+ export const DEFAULT_PIE_PLUGIN_PARAMS: PiePluginParams = {
24
+ // padding: {
25
+ // top: 50,
26
+ // right: 70,
27
+ // bottom: 50,
28
+ // left: 70
29
+ // },
30
+ outerRadius: 0.95,
31
+ innerRadius: 0,
32
+ outerMouseoverRadius: 1,
33
+ // label?: LabelStyle
34
+ enterDuration: 800,
35
+ startAngle: 0,
36
+ endAngle: Math.PI * 2,
37
+ padAngle: 0.02,
38
+ // padRadius: 100,
39
+ cornerRadius: 0,
40
+ // highlightTarget: 'datum',
41
+ // highlightId: null,
42
+ // highlightLabel: null,
43
+ }
44
+
45
+ export const DEFAULT_PIE_EVENT_TEXTS_PARAMS: PieEventTextsPluginParams = {
46
+ eventFn: (d: EventSeries, eventName: EventName, t: number) => {
47
+ if (eventName === 'mouseover' || eventName === 'mousemove') {
48
+ return [String(d.datum!.value)]
49
+ }
50
+ return [String(Math.round(d.data.reduce((previous,current)=>previous+(current.value??0),0)*t))]
51
+ },
52
+ textAttrs: [
53
+ {
54
+ "transform": "translate(0, 0)"
55
+ }
56
+ ],
57
+ textStyles: [
58
+ {
59
+ "font-weight": "bold",
60
+ "text-anchor": "middle",
61
+ "pointer-events": "none",
62
+ "dominant-baseline": "middle",
63
+ "font-size": 64,
64
+ "fill": "#000"
65
+ }
66
+ ]
67
+ }
68
+
69
+ export const DEFAULT_PIE_LABELS_PARAMS: PieLabelsPluginParams = {
70
+ // solidColor: undefined,
71
+ // colors: DEFAULT_COLORS,
72
+ outerRadius: 0.95,
73
+ outerMouseoverRadius: 1,
74
+ // innerRadius: 0,
75
+ // enterDuration: 800,
76
+ startAngle: 0,
77
+ endAngle: Math.PI * 2,
78
+ labelCentroid: 2.3,
79
+ // fontSize: 12,
80
+ labelColorType: 'series',
81
+ labelFn: d => String(d.value),
82
+ }
@@ -0,0 +1,6 @@
1
+ export * from './defaults'
2
+ export * from './types'
3
+ export { Bubbles } from './plugins/Bubbles'
4
+ export { Pie } from './plugins/Pie'
5
+ export { PieEventTexts } from './plugins/PieEventTexts'
6
+ export { PieLabels } from './plugins/PieLabels'