@orbcharts/plugins-basic 3.0.0-alpha.37 → 3.0.0-alpha.39

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,3 +2,4 @@ export * from './grid';
2
2
  export * from './multiGrid';
3
3
  export * from './noneData';
4
4
  export * from './series';
5
+ export * from './tree';
@@ -0,0 +1,4 @@
1
+ import { TreeMapParams, TreeLegendParams } from './types';
2
+
3
+ export declare const DEFAULT_TREE_MAP_PARAMS: TreeMapParams;
4
+ export declare const DEFAULT_TREE_LEGEND_PARAMS: TreeLegendParams;
@@ -0,0 +1,4 @@
1
+ export * from './defaults';
2
+ export * from './types';
3
+ export { TreeLegend } from './plugins/TreeLegend';
4
+ export { TreeMap } from './plugins/TreeMap';
@@ -0,0 +1 @@
1
+ export declare const TreeLegend: import('@orbcharts/core').PluginConstructor<"tree", string, import('..').TreeLegendParams>;
@@ -0,0 +1,3 @@
1
+ import { TreeMapParams } from '../types';
2
+
3
+ export declare const TreeMap: import('@orbcharts/core').PluginConstructor<"tree", string, TreeMapParams>;
@@ -0,0 +1,20 @@
1
+ import { ColorType, ComputedDataTree } from '@orbcharts/core';
2
+
3
+ export interface TreeMapParams {
4
+ paddingInner: number;
5
+ paddingOuter: number;
6
+ labelColorType: ColorType;
7
+ squarifyRatio: number;
8
+ sort: (a: ComputedDataTree, b: ComputedDataTree) => number;
9
+ }
10
+ export interface TreeLegendParams {
11
+ position: 'top' | 'bottom' | 'left' | 'right';
12
+ justify: 'start' | 'center' | 'end';
13
+ padding: number;
14
+ backgroundFill: ColorType;
15
+ backgroundStroke: ColorType;
16
+ gap: number;
17
+ listRectWidth: number;
18
+ listRectHeight: number;
19
+ listRectRadius: number;
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbcharts/plugins-basic",
3
- "version": "3.0.0-alpha.37",
3
+ "version": "3.0.0-alpha.39",
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.36",
38
+ "@orbcharts/core": "^3.0.0-alpha.37",
39
39
  "d3": "^7.8.5",
40
40
  "rxjs": "^7.8.1"
41
41
  }
package/src/index.ts CHANGED
@@ -6,4 +6,5 @@
6
6
  export * from './grid'
7
7
  export * from './multiGrid'
8
8
  export * from './noneData'
9
- export * from './series'
9
+ export * from './series'
10
+ export * from './tree'
@@ -0,0 +1,21 @@
1
+ import type { TreeMapParams, TreeLegendParams } from './types'
2
+
3
+ export const DEFAULT_TREE_MAP_PARAMS: TreeMapParams = {
4
+ paddingInner: 2,
5
+ paddingOuter: 2,
6
+ labelColorType: 'primary',
7
+ squarifyRatio: 1.618034, // 黃金比例
8
+ sort: (a, b) => b.value - a.value
9
+ }
10
+
11
+ export const DEFAULT_TREE_LEGEND_PARAMS: TreeLegendParams = {
12
+ position: 'right',
13
+ justify: 'end',
14
+ padding: 28,
15
+ backgroundFill: 'none',
16
+ backgroundStroke: 'none',
17
+ gap: 10,
18
+ listRectWidth: 14,
19
+ listRectHeight: 14,
20
+ listRectRadius: 0,
21
+ }
package/src/tree/index.ts CHANGED
@@ -0,0 +1,4 @@
1
+ export * from './defaults'
2
+ export * from './types'
3
+ export { TreeLegend } from './plugins/TreeLegend'
4
+ export { TreeMap } from './plugins/TreeMap'
@@ -0,0 +1,58 @@
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ switchMap,
6
+ takeUntil,
7
+ Observable,
8
+ Subject } from 'rxjs'
9
+ import {
10
+ defineTreePlugin } from '@orbcharts/core'
11
+ import { DEFAULT_TREE_LEGEND_PARAMS } from '../defaults'
12
+ import { createBaseLegend } from '../../base/BaseLegend'
13
+
14
+ const pluginName = 'TreeLegend'
15
+
16
+ export const TreeLegend = defineTreePlugin(pluginName, DEFAULT_TREE_LEGEND_PARAMS)(({ selection, rootSelection, observer, subject }) => {
17
+
18
+ const destroy$ = new Subject()
19
+
20
+ const categoryLabels$: Observable<string[]> = observer.CategoryDataMap$.pipe(
21
+ takeUntil(destroy$),
22
+ map(data => {
23
+ return Array.from(data.keys())
24
+ })
25
+ )
26
+
27
+ // 全部列點矩型使用相同樣式參數
28
+ const fullParams$ = observer.fullParams$.pipe(
29
+ takeUntil(destroy$),
30
+ map(d => {
31
+ const seriesList = [
32
+ {
33
+ listRectWidth: d.listRectWidth,
34
+ listRectHeight: d.listRectHeight,
35
+ listRectRadius: d.listRectRadius,
36
+ }
37
+ ]
38
+ return {
39
+ ...d,
40
+ seriesList
41
+ }
42
+ })
43
+ )
44
+
45
+ const unsubscribeBaseLegend = createBaseLegend(pluginName, {
46
+ rootSelection,
47
+ seriesLabels$: categoryLabels$,
48
+ fullParams$,
49
+ layout$: observer.layout$,
50
+ fullChartParams$: observer.fullChartParams$
51
+ })
52
+
53
+ return () => {
54
+ destroy$.next(undefined)
55
+ unsubscribeBaseLegend()
56
+ }
57
+ })
58
+
@@ -0,0 +1,298 @@
1
+ import * as d3 from 'd3'
2
+ import {
3
+ Subject,
4
+ Observable,
5
+ of,
6
+ takeUntil,
7
+ map,
8
+ switchMap,
9
+ combineLatest,
10
+ debounceTime,
11
+ distinctUntilChanged } from 'rxjs'
12
+ import {
13
+ defineTreePlugin } from '@orbcharts/core'
14
+ import type { Layout, ComputedDataTree, DataFormatterTree, ChartParams } from '@orbcharts/core'
15
+ import type { TreeMapParams } from '../types'
16
+ import { DEFAULT_TREE_MAP_PARAMS } from '../defaults'
17
+ import { getClassName, getColor } from '../../utils/orbchartsUtils'
18
+
19
+ const pluginName = 'TreeMap'
20
+ const treeClassName = getClassName(pluginName, 'tree')
21
+
22
+ function renderTree ({ selection, treeData, fullParams, fullChartParams }: {
23
+ selection: d3.Selection<any, any, any, any>
24
+ treeData: d3.HierarchyRectangularNode<ComputedDataTree>[]
25
+ fullParams: TreeMapParams
26
+ fullChartParams: ChartParams
27
+ }) {
28
+ const padding = fullChartParams.styles.textSize / 2
29
+
30
+ const cell = selection.selectAll<SVGGElement, d3.HierarchyRectangularNode<ComputedDataTree>>(`g.${treeClassName}`)
31
+ .data(treeData, d => d.data.id)
32
+ .join('g')
33
+ .attr('class', treeClassName)
34
+
35
+ cell
36
+ // .transition()
37
+ // .duration(fullChartParams.transitionDuration)
38
+ .attr('transform', (d) => !d.x0 || !d.y0 ? null : `translate(${d.x0},${d.y0})`)
39
+ .each((d, i, nodes) => {
40
+ const eachCell = d3.select(nodes[i])
41
+
42
+ const tile = eachCell
43
+ .selectAll<SVGRectElement, d3.HierarchyRectangularNode<ComputedDataTree>>('rect')
44
+ .data([d], d => d.data.id)
45
+ .join('rect')
46
+ .attr("id", d => d.data.id)
47
+ .attr("class", "tile")
48
+ .attr("width", (d) => d.x1 - d.x0)
49
+ .attr("height", (d) => d.y1 - d.y0)
50
+ .attr('fill', d => d.data.color)
51
+ .attr('data-name', d => d.data.label)
52
+ .attr('data-category', d => d.data.categoryLabel)
53
+ .attr('data-value', d => d.data.value)
54
+
55
+ const label = eachCell
56
+ .selectAll('g')
57
+ .data([d])
58
+ .join('g')
59
+ .each((d, i, nodes) => {
60
+ const eachLabel = d3.select(nodes[i])
61
+ const text = eachLabel
62
+ .selectAll('text')
63
+ .data([d])
64
+ .join('text')
65
+ .text(d => d.data.label)
66
+ .attr('dominant-baseline', 'hanging')
67
+ .attr("x", padding)
68
+ .attr("y", padding)
69
+ .attr('font-size', fullChartParams.styles.textSize)
70
+ .each(function(d) {
71
+ // -- tspan(自動斷行) --
72
+ const textElement = d3.select(this);
73
+ const words = d.data.label.split(/\s+/).reverse() // 以空隔分割字串
74
+ let word;
75
+ let line: string[] = []
76
+ const lineHeight = fullChartParams.styles.textSize // 行高
77
+ const x = textElement.attr("x")
78
+ let y = textElement.attr("y")
79
+ let dy = 0
80
+ let tspan = textElement
81
+ .text(null)
82
+ .append("tspan")
83
+ .attr('cursor', 'default')
84
+ .attr('fill', getColor(fullParams.labelColorType, fullChartParams))
85
+ .attr('font-size', fullChartParams.styles.textSize)
86
+ .attr("x", x)
87
+ .attr("y", y)
88
+
89
+ while (word = words.pop()) {
90
+ line.push(word)
91
+ tspan.text(line.join(" "))
92
+ if (tspan.node().getComputedTextLength() > (d.x1 - d.x0 - padding)) {
93
+ line.pop()
94
+ tspan.text(line.join(" "))
95
+ line = [word]
96
+ dy += lineHeight
97
+ tspan = textElement
98
+ .append("tspan")
99
+ .attr('cursor', 'default')
100
+ .attr('fill', getColor(fullParams.labelColorType, fullChartParams))
101
+ .attr('font-size', fullChartParams.styles.textSize)
102
+ .attr("x", x)
103
+ .attr("y", y)
104
+ .attr("dy", dy + "px")
105
+ .text(word)
106
+ }
107
+ }
108
+ })
109
+ })
110
+
111
+ })
112
+
113
+ return cell
114
+ }
115
+
116
+ function highlight ({ selection, ids, fullChartParams }: {
117
+ selection: d3.Selection<any, d3.HierarchyRectangularNode<ComputedDataTree>, any, any>
118
+ ids: string[]
119
+ fullChartParams: ChartParams
120
+ }) {
121
+ selection.interrupt('highlight')
122
+
123
+ if (!ids.length) {
124
+ // remove highlight
125
+ selection
126
+ .transition('highlight')
127
+ .duration(200)
128
+ .style('opacity', 1)
129
+ return
130
+ }
131
+
132
+ selection
133
+ .each((d, i, n) => {
134
+ if (ids.includes(d.data.id)) {
135
+ d3.select(n[i])
136
+ .style('opacity', 1)
137
+ } else {
138
+ d3.select(n[i])
139
+ .style('opacity', fullChartParams.styles.unhighlightedOpacity)
140
+ }
141
+ })
142
+ }
143
+
144
+ export const TreeMap = defineTreePlugin(pluginName, DEFAULT_TREE_MAP_PARAMS)(({ selection, name, subject, observer }) => {
145
+ const destroy$ = new Subject()
146
+
147
+ const treeData$ = combineLatest({
148
+ layout: observer.layout$,
149
+ visibleComputedData: observer.visibleComputedData$,
150
+ fullParams: observer.fullParams$,
151
+ fullDataFormatter: observer.fullDataFormatter$,
152
+ fullChartParams: observer.fullChartParams$
153
+ }).pipe(
154
+ takeUntil(destroy$),
155
+ switchMap(async d => d),
156
+ map(data => {
157
+ const treemap = d3.treemap()
158
+ .size([data.layout.width, data.layout.height])
159
+ .paddingInner(data.fullParams.paddingInner)
160
+ .paddingOuter(data.fullParams.paddingOuter)
161
+ .round(true)
162
+ .tile(d3.treemapSquarify.ratio(data.fullParams.squarifyRatio))
163
+
164
+ const root = d3.hierarchy(data.visibleComputedData)
165
+ .sum(d => d.value)
166
+ .sort(data.fullParams.sort as (a: any, b: any) => number)
167
+
168
+ //call treemap
169
+ treemap(root)
170
+
171
+ const treeData: d3.HierarchyRectangularNode<ComputedDataTree>[] = root.leaves() as any
172
+
173
+ return treeData
174
+ })
175
+ )
176
+
177
+ const cellSelection$ = combineLatest({
178
+ selection: of(selection),
179
+ treeData: treeData$,
180
+ fullParams: observer.fullParams$,
181
+ fullChartParams: observer.fullChartParams$
182
+ }).pipe(
183
+ takeUntil(destroy$),
184
+ switchMap(async d => d),
185
+ map(data => {
186
+ return renderTree({
187
+ selection,
188
+ treeData: data.treeData,
189
+ fullParams: data.fullParams,
190
+ fullChartParams: data.fullChartParams
191
+ })
192
+ })
193
+ )
194
+
195
+ const highlightTarget$ = observer.fullChartParams$.pipe(
196
+ takeUntil(destroy$),
197
+ map(d => d.highlightTarget),
198
+ distinctUntilChanged()
199
+ )
200
+
201
+ combineLatest({
202
+ cellSelection: cellSelection$,
203
+ computedData: observer.computedData$,
204
+ treeData: treeData$,
205
+ fullParams: observer.fullParams$,
206
+ fullChartParams: observer.fullChartParams$,
207
+ highlightTarget: highlightTarget$,
208
+ CategoryDataMap: observer.CategoryDataMap$,
209
+ }).pipe(
210
+ takeUntil(destroy$),
211
+ switchMap(async d => d)
212
+ ).subscribe(data => {
213
+ data.cellSelection
214
+ .on('mouseover', (event, datum) => {
215
+ event.stopPropagation()
216
+
217
+ subject.event$.next({
218
+ type: 'tree',
219
+ eventName: 'mouseover',
220
+ pluginName,
221
+ highlightTarget: data.highlightTarget,
222
+ datum: datum.data,
223
+ category: data.CategoryDataMap.get(datum.data.categoryLabel)!,
224
+ categoryIndex: datum.data.categoryIndex,
225
+ categoryLabel: datum.data.categoryLabel,
226
+ event,
227
+ data: data.computedData
228
+ })
229
+ })
230
+ .on('mousemove', (event, datum) => {
231
+ event.stopPropagation()
232
+
233
+ subject.event$.next({
234
+ type: 'tree',
235
+ eventName: 'mousemove',
236
+ pluginName,
237
+ highlightTarget: data.highlightTarget,
238
+ datum: datum.data,
239
+ category: data.CategoryDataMap.get(datum.data.categoryLabel)!,
240
+ categoryIndex: datum.data.categoryIndex,
241
+ categoryLabel: datum.data.categoryLabel,
242
+ event,
243
+ data: data.computedData
244
+ })
245
+ })
246
+ .on('mouseout', (event, datum) => {
247
+ event.stopPropagation()
248
+
249
+ subject.event$.next({
250
+ type: 'tree',
251
+ eventName: 'mouseout',
252
+ pluginName,
253
+ highlightTarget: data.highlightTarget,
254
+ datum: datum.data,
255
+ category: data.CategoryDataMap.get(datum.data.categoryLabel)!,
256
+ categoryIndex: datum.data.categoryIndex,
257
+ categoryLabel: datum.data.categoryLabel,
258
+ event,
259
+ data: data.computedData
260
+ })
261
+ })
262
+ .on('click', (event, datum) => {
263
+ event.stopPropagation()
264
+
265
+ subject.event$.next({
266
+ type: 'tree',
267
+ eventName: 'click',
268
+ pluginName,
269
+ highlightTarget: data.highlightTarget,
270
+ datum: datum.data,
271
+ category: data.CategoryDataMap.get(datum.data.categoryLabel)!,
272
+ categoryIndex: datum.data.categoryIndex,
273
+ categoryLabel: datum.data.categoryLabel,
274
+ event,
275
+ data: data.computedData
276
+ })
277
+ })
278
+ })
279
+
280
+ combineLatest({
281
+ cellSelection: cellSelection$,
282
+ highlight: observer.treeHighlight$,
283
+ fullChartParams: observer.fullChartParams$
284
+ }).pipe(
285
+ takeUntil(destroy$),
286
+ switchMap(async d => d)
287
+ ).subscribe(data => {
288
+ highlight({
289
+ selection: data.cellSelection,
290
+ ids: data.highlight,
291
+ fullChartParams: data.fullChartParams
292
+ })
293
+ })
294
+
295
+ return () => {
296
+ destroy$.next(undefined)
297
+ }
298
+ })
@@ -0,0 +1,22 @@
1
+ import type { ColorType, ComputedDataTree } from '@orbcharts/core'
2
+
3
+ export interface TreeMapParams {
4
+ paddingInner: number
5
+ paddingOuter: number
6
+ labelColorType: ColorType
7
+ squarifyRatio: number
8
+ sort: (a: ComputedDataTree, b: ComputedDataTree) => number
9
+ }
10
+
11
+ export interface TreeLegendParams {
12
+ position: 'top' | 'bottom' | 'left' | 'right'
13
+ justify: 'start' | 'center' | 'end'
14
+ padding: number
15
+ backgroundFill: ColorType
16
+ backgroundStroke: ColorType
17
+ gap: number
18
+ listRectWidth: number
19
+ listRectHeight: number
20
+ listRectRadius: number
21
+ }
22
+