@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,427 @@
1
+ import * as d3 from 'd3'
2
+ import {
3
+ Observable,
4
+ combineLatest,
5
+ switchMap,
6
+ distinctUntilChanged,
7
+ filter,
8
+ first,
9
+ map,
10
+ takeUntil,
11
+ Subject } from 'rxjs'
12
+ import {
13
+ defineGridPlugin } from '@orbcharts/core'
14
+ import type {
15
+ ChartParams,
16
+ ComputedDatumGrid,
17
+ Layout } from '@orbcharts/core'
18
+ import type { DotsPluginParams } from '../types'
19
+ import { DEFAULT_DOTS_PLUGIN_PARAMS } from '../defaults'
20
+ import { getDatumColor, getClassName, getUniID } from '../../utils/orbchartsUtils'
21
+
22
+ type ClipPathDatum = {
23
+ id: string;
24
+ // x: number;
25
+ // y: number;
26
+ width: number;
27
+ height: number;
28
+ }
29
+
30
+ const pluginName = 'Dots'
31
+ const gClassName = getClassName(pluginName, 'g')
32
+ const circleClassName = getClassName(pluginName, 'circle')
33
+
34
+ function renderDots ({ selection, data, fullParams, fullChartParams, graphicOppositeScale }: {
35
+ selection: d3.Selection<SVGGElement, any, any, any>
36
+ data: ComputedDatumGrid[]
37
+ fullParams: DotsPluginParams
38
+ fullChartParams: ChartParams
39
+ graphicOppositeScale: [number, number]
40
+ }) {
41
+ const createEnterDuration = (enter: d3.Selection<d3.EnterElement, ComputedDatumGrid, SVGGElement, any>) => {
42
+ const enterSize = enter.size()
43
+ const eachDuration = fullChartParams.transitionDuration / enterSize
44
+ return eachDuration
45
+ }
46
+ // enterDuration
47
+ let enterDuration = 0
48
+
49
+ const dots = selection
50
+ .selectAll<SVGGElement, ComputedDatumGrid>('g')
51
+ .data(data, d => d.id)
52
+ .join(
53
+ enter => {
54
+ // enterDuration
55
+ enterDuration = createEnterDuration(enter)
56
+
57
+ return enter
58
+ .append('g')
59
+ .classed(gClassName, true)
60
+ },
61
+ update => update,
62
+ exit => exit.remove()
63
+ )
64
+ .attr('transform', d => `translate(${d.axisX}, ${d.axisY})`)
65
+ .each((d, i, g) => {
66
+ const circle = d3.select(g[i])
67
+ .selectAll('circle')
68
+ .data([d])
69
+ .join(
70
+ enter => {
71
+ return enter
72
+ .append('circle')
73
+ .style('cursor', 'pointer')
74
+ .style('vector-effect', 'non-scaling-stroke')
75
+ .classed(circleClassName, true)
76
+ .attr('opacity', 0)
77
+ .transition()
78
+ .delay((_d, _i) => {
79
+ return i * enterDuration
80
+ })
81
+ .attr('opacity', 1)
82
+ },
83
+ update => {
84
+ return update
85
+ .transition()
86
+ .duration(50)
87
+ // .attr('cx', d => d.axisX)
88
+ // .attr('cy', d => d.axisY)
89
+ .attr('opacity', 1)
90
+ },
91
+ exit => exit.remove()
92
+ )
93
+ .attr('r', fullParams.radius)
94
+ .attr('fill', (d, i) => getDatumColor({ datum: d, colorType: fullParams.fillColorType, fullChartParams }))
95
+ .attr('stroke', (d, i) => getDatumColor({ datum: d, colorType: fullParams.strokeColorType, fullChartParams }))
96
+ .attr('stroke-width', fullParams.strokeWidth)
97
+ .attr('transform', `scale(${graphicOppositeScale[0]}, ${graphicOppositeScale[1]})`)
98
+ })
99
+
100
+ return dots
101
+ }
102
+
103
+
104
+ function highlightDots ({ selection, ids, onlyShowHighlighted, fullChartParams }: {
105
+ selection: d3.Selection<SVGGElement, ComputedDatumGrid, any, any>
106
+ ids: string[]
107
+ onlyShowHighlighted: boolean
108
+ fullChartParams: ChartParams
109
+ }) {
110
+ selection.interrupt('highlight')
111
+ if (!ids.length) {
112
+ // remove highlight
113
+ selection
114
+ .transition('highlight')
115
+ .duration(200)
116
+ .style('opacity', onlyShowHighlighted === true ? 0 : 1)
117
+ return
118
+ }
119
+
120
+ selection
121
+ .each((d, i, n) => {
122
+ if (ids.includes(d.id)) {
123
+ d3.select(n[i])
124
+ .style('opacity', 1)
125
+ .transition('highlight')
126
+ .duration(200)
127
+ } else {
128
+ d3.select(n[i])
129
+ .style('opacity', onlyShowHighlighted === true ? 0 : fullChartParams.styles.unhighlightedOpacity)
130
+ .transition('highlight')
131
+ .duration(200)
132
+ }
133
+ })
134
+ }
135
+
136
+ function renderClipPath ({ defsSelection, clipPathData }: {
137
+ defsSelection: d3.Selection<SVGDefsElement, any, any, any>
138
+ clipPathData: ClipPathDatum[]
139
+ }) {
140
+ const clipPath = defsSelection
141
+ .selectAll<SVGClipPathElement, Layout>('clipPath')
142
+ .data(clipPathData)
143
+ .join(
144
+ enter => {
145
+ return enter
146
+ .append('clipPath')
147
+ },
148
+ update => update,
149
+ exit => exit.remove()
150
+ )
151
+ .attr('id', d => d.id)
152
+ .each((d, i, g) => {
153
+ const rect = d3.select(g[i])
154
+ .selectAll<SVGRectElement, typeof d>('rect')
155
+ .data([d])
156
+ .join('rect')
157
+ .attr('x', 0)
158
+ .attr('y', 0)
159
+ .attr('width', _d => _d.width)
160
+ .attr('height', _d => _d.height)
161
+ })
162
+
163
+ }
164
+
165
+ export const Dots = defineGridPlugin(pluginName, DEFAULT_DOTS_PLUGIN_PARAMS)(({ selection, name, subject, observer }) => {
166
+ // const axisGUpdate = selection
167
+ // .selectAll('g')
168
+ // .data()
169
+ const destroy$ = new Subject()
170
+
171
+ const clipPathID = getUniID(pluginName, 'clipPath-box')
172
+
173
+ // const rectSelection: d3.Selection<SVGRectElement, any, any, any> = selection
174
+ // .append('rect')
175
+ // .attr('pointer-events', 'none')
176
+ const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection
177
+ .append('g')
178
+ .attr('clip-path', `url(#${clipPathID})`)
179
+ const defsSelection: d3.Selection<SVGDefsElement, any, any, any> = axisSelection.append('defs')
180
+ const dataAreaSelection: d3.Selection<SVGGElement, any, any, any> = axisSelection.append('g')
181
+ const graphicSelection$: Subject<d3.Selection<SVGGElement, ComputedDatumGrid, any, any>> = new Subject()
182
+ // const dotSelection$: Subject<d3.Selection<SVGCircleElement, ComputedDatumGrid, SVGGElement, any>> = new Subject()
183
+
184
+ observer.gridAxesTransform$
185
+ .pipe(
186
+ takeUntil(destroy$),
187
+ map(d => d.value),
188
+ distinctUntilChanged()
189
+ ).subscribe(d => {
190
+ axisSelection
191
+ .style('transform', d)
192
+ })
193
+
194
+ observer.gridGraphicTransform$
195
+ .pipe(
196
+ takeUntil(destroy$),
197
+ switchMap(async d => d.value),
198
+ distinctUntilChanged()
199
+ ).subscribe(d => {
200
+ dataAreaSelection
201
+ .transition()
202
+ .duration(50)
203
+ .style('transform', d)
204
+ })
205
+
206
+ const graphicOppositeScale$: Observable<[number, number]> = observer.gridGraphicTransform$.pipe(
207
+ takeUntil(destroy$),
208
+ map(d => [1 / d.scale[0], 1 / d.scale[1]])
209
+ )
210
+
211
+ // const axisSize$ = gridAxisSizeObservable({
212
+ // dataFormatter$,
213
+ // observer.layout$
214
+ // })
215
+
216
+ // combineLatest({
217
+ // axisSized: axisSize$,
218
+ // computedLayout: observer.layout$
219
+ // }).pipe(
220
+ // takeUntil(destroy$),
221
+ // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
222
+ // switchMap(async (d) => d),
223
+ // ).subscribe(d => {
224
+ // rectSelection
225
+ // .style('transform', d.computedLayout.content.axesTransform)
226
+ // .attr('opacity', 0)
227
+ // .attr('width', d.axisSized.width)
228
+ // .attr('height', d.axisSized.height)
229
+ // // .transition()
230
+ // // .attr('opacity', 1)
231
+ // })
232
+ // selection.on('mouseover', (event, datum) => {
233
+
234
+ // console.log('selection mouseover', event, datum)
235
+ // })
236
+
237
+ const clipPathSubscription = observer.gridAxesSize$.pipe(
238
+ takeUntil(destroy$),
239
+ // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
240
+ switchMap(async (d) => d),
241
+ ).subscribe(data => {
242
+ // 外層的遮罩
243
+ const clipPathData = [{
244
+ id: clipPathID,
245
+ width: data.width,
246
+ height: data.height
247
+ }]
248
+ renderClipPath({
249
+ defsSelection,
250
+ clipPathData,
251
+ })
252
+ })
253
+
254
+ // const visibleComputedData$ = observer.computedData$.pipe(
255
+ // map(computedData => {
256
+ // return computedData
257
+ // .map(d => {
258
+ // return d.filter(_d => _d.visible == true)
259
+ // })
260
+ // })
261
+ // )
262
+
263
+ // const SeriesDataMap$ = visibleComputedData$.pipe(
264
+ // map(d => makeGridSeriesDataMap(d))
265
+ // )
266
+
267
+ // const GroupDataMap$ = visibleComputedData$.pipe(
268
+ // map(d => makeGridGroupDataMap(d))
269
+ // )
270
+
271
+ // const DataMap$ = computedData$.pipe(
272
+ // map(d => {
273
+ // const DataMap: Map<string, ComputedDatumGrid> = new Map()
274
+ // d.flat().forEach(_d => DataMap.set(_d.id, _d))
275
+ // return DataMap
276
+ // })
277
+ // )
278
+
279
+ const highlightTarget$ = observer.fullChartParams$.pipe(
280
+ takeUntil(destroy$),
281
+ map(d => d.highlightTarget),
282
+ distinctUntilChanged()
283
+ )
284
+
285
+ combineLatest({
286
+ computedData: observer.computedData$,
287
+ visibleComputedData: observer.visibleComputedData$,
288
+ SeriesDataMap: observer.SeriesDataMap$,
289
+ GroupDataMap: observer.GroupDataMap$,
290
+ graphicOppositeScale: graphicOppositeScale$,
291
+ fullChartParams: observer.fullChartParams$,
292
+ fullParams: observer.fullParams$,
293
+ highlightTarget: highlightTarget$
294
+ }).pipe(
295
+ takeUntil(destroy$),
296
+ // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
297
+ switchMap(async (d) => d),
298
+ ).subscribe(data => {
299
+
300
+ const graphicSelection = renderDots({
301
+ selection: dataAreaSelection,
302
+ data: data.visibleComputedData.flat(),
303
+ fullParams: data.fullParams,
304
+ fullChartParams: data.fullChartParams,
305
+ graphicOppositeScale: data.graphicOppositeScale
306
+ })
307
+
308
+ graphicSelection
309
+ .on('mouseover', (event, datum) => {
310
+ event.stopPropagation()
311
+
312
+ subject.event$.next({
313
+ type: 'grid',
314
+ eventName: 'mouseover',
315
+ pluginName: name,
316
+ highlightTarget: data.highlightTarget,
317
+ datum,
318
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
319
+ seriesIndex: datum.seriesIndex,
320
+ seriesLabel: datum.seriesLabel,
321
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
322
+ groupIndex: datum.groupIndex,
323
+ groupLabel: datum.groupLabel,
324
+ event,
325
+ data: data.computedData
326
+ })
327
+ })
328
+ .on('mousemove', (event, datum) => {
329
+ event.stopPropagation()
330
+
331
+ subject.event$.next({
332
+ type: 'grid',
333
+ eventName: 'mousemove',
334
+ pluginName: name,
335
+ highlightTarget: data.highlightTarget,
336
+ data: data.computedData,
337
+ datum,
338
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
339
+ seriesIndex: datum.seriesIndex,
340
+ seriesLabel: datum.seriesLabel,
341
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
342
+ groupIndex: datum.groupIndex,
343
+ groupLabel: datum.groupLabel,
344
+ event
345
+ })
346
+ })
347
+ .on('mouseout', (event, datum) => {
348
+ event.stopPropagation()
349
+
350
+ subject.event$.next({
351
+ type: 'grid',
352
+ eventName: 'mouseout',
353
+ pluginName: name,
354
+ highlightTarget: data.highlightTarget,
355
+ datum,
356
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
357
+ seriesIndex: datum.seriesIndex,
358
+ seriesLabel: datum.seriesLabel,
359
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
360
+ groupIndex: datum.groupIndex,
361
+ groupLabel: datum.groupLabel,
362
+ event,
363
+ data: data.computedData
364
+ })
365
+ })
366
+ .on('click', (event, datum) => {
367
+ event.stopPropagation()
368
+
369
+ subject.event$.next({
370
+ type: 'grid',
371
+ eventName: 'click',
372
+ pluginName: name,
373
+ highlightTarget: data.highlightTarget,
374
+ datum,
375
+ series: data.SeriesDataMap.get(datum.seriesLabel)!,
376
+ seriesIndex: datum.seriesIndex,
377
+ seriesLabel: datum.seriesLabel,
378
+ groups: data.GroupDataMap.get(datum.groupLabel)!,
379
+ groupIndex: datum.groupIndex,
380
+ groupLabel: datum.groupLabel,
381
+ event,
382
+ data: data.computedData
383
+ })
384
+ })
385
+
386
+ graphicSelection$.next(graphicSelection)
387
+
388
+ })
389
+
390
+ // const datumList$ = observer.computedData$.pipe(
391
+ // takeUntil(destroy$),
392
+ // map(d => d.flat())
393
+ // )
394
+ // const highlight$ = highlightObservable({ datumList$, fullChartParams$, event$: store.event$ })
395
+ const highlightSubscription = observer.gridHighlight$.subscribe()
396
+ const onlyShowHighlighted$ = observer.fullParams$.pipe(
397
+ takeUntil(destroy$),
398
+ map(d => d.onlyShowHighlighted),
399
+ distinctUntilChanged()
400
+ )
401
+
402
+ observer.fullChartParams$.pipe(
403
+ takeUntil(destroy$),
404
+ switchMap(d => combineLatest({
405
+ graphicSelection: graphicSelection$,
406
+ highlight: observer.gridHighlight$,
407
+ onlyShowHighlighted: onlyShowHighlighted$,
408
+ fullChartParams: observer.fullChartParams$
409
+ }).pipe(
410
+ takeUntil(destroy$),
411
+ switchMap(async d => d)
412
+ ))
413
+ ).subscribe(data => {
414
+ highlightDots({
415
+ selection: data.graphicSelection,
416
+ ids: data.highlight,
417
+ onlyShowHighlighted: data.onlyShowHighlighted,
418
+ fullChartParams: data.fullChartParams
419
+ })
420
+ })
421
+
422
+
423
+ return () => {
424
+ highlightSubscription.unsubscribe()
425
+ destroy$.next(undefined)
426
+ }
427
+ })