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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.33",
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
+ }