@orbcharts/plugins-basic 3.0.0-alpha.32 → 3.0.0-alpha.34

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