@orbcharts/plugins-basic 3.0.0-alpha.63 → 3.0.0-alpha.65
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.
- package/dist/orbcharts-plugins-basic.es.js +5699 -5476
- package/dist/orbcharts-plugins-basic.umd.js +16 -10
- package/dist/src/base/BaseGroupAxis.d.ts +1 -0
- package/dist/src/noneData/index.d.ts +0 -1
- package/dist/src/noneData/types.d.ts +1 -1
- package/dist/src/series/types.d.ts +6 -1
- package/package.json +2 -2
- package/src/base/BaseGroupAxis.ts +35 -20
- package/src/base/BaseLegend.ts +1 -0
- package/src/grid/plugins/GroupAxis.ts +1 -0
- package/src/multiGrid/plugins/MultiGroupAxis.ts +1 -0
- package/src/multiGrid/plugins/OverlappingValueAxes.ts +3 -0
- package/src/multiGrid/plugins/OverlappingValueStackAxes.ts +1 -0
- package/src/noneData/index.ts +1 -1
- package/src/noneData/plugins/Tooltip.ts +26 -9
- package/src/noneData/types.ts +1 -1
- package/src/series/defaults.ts +15 -10
- package/src/series/plugins/Pie.ts +12 -4
- package/src/series/plugins/PieLabels.ts +357 -63
- package/src/series/plugins/Rose.ts +17 -8
- package/src/series/plugins/RoseLabels.ts +290 -87
- package/src/series/types.ts +9 -5
@@ -27,134 +27,378 @@ import { seriesCenterSelectionObservable } from '../seriesObservables'
|
|
27
27
|
interface RenderDatum {
|
28
28
|
pieDatum: PieDatum
|
29
29
|
arcIndex: number
|
30
|
-
|
30
|
+
arcLabels: string[]
|
31
|
+
lineStartX: number
|
32
|
+
lineStartY: number
|
33
|
+
lineStartMouseoverX: number
|
34
|
+
lineStartMouseoverY: number
|
31
35
|
x: number
|
32
36
|
y: number
|
33
37
|
mouseoverX: number
|
34
38
|
mouseoverY: number
|
39
|
+
textWidth: number, // 文字寬度
|
40
|
+
collisionShiftX: number // 避免碰撞的位移
|
41
|
+
collisionShiftY: number
|
42
|
+
quadrant: number // 第幾象限
|
35
43
|
}
|
36
44
|
|
37
45
|
const pluginName = 'PieLabels'
|
46
|
+
const labelGClassName = getClassName(pluginName, 'label-g')
|
47
|
+
const lineGClassName = getClassName(pluginName, 'line-g')
|
38
48
|
const textClassName = getClassName(pluginName, 'text')
|
39
49
|
|
40
|
-
|
50
|
+
const pieOuterCentroid = 2
|
51
|
+
|
52
|
+
function makeRenderData ({ pieData, arc, arcMouseover, labelCentroid, lineStartCentroid, fullParams }: {
|
53
|
+
pieData: PieDatum[]
|
54
|
+
arc: d3.Arc<any, d3.DefaultArcObject>
|
55
|
+
arcMouseover: d3.Arc<any, d3.DefaultArcObject>
|
56
|
+
labelCentroid: number
|
57
|
+
lineStartCentroid: number
|
58
|
+
fullParams: PieLabelsParams
|
59
|
+
}): RenderDatum[] {
|
41
60
|
return pieData
|
42
61
|
.map((d, i) => {
|
43
62
|
const [_x, _y] = arc!.centroid(d as any)
|
44
|
-
const [_mouseoverX, _mouseoverY] =
|
63
|
+
const [_mouseoverX, _mouseoverY] = arcMouseover!.centroid(d as any)
|
64
|
+
const arcLabel = fullParams.labelFn(d.data)
|
45
65
|
return {
|
46
66
|
pieDatum: d,
|
47
67
|
arcIndex: i,
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
68
|
+
arcLabels: arcLabel.split('\n'),
|
69
|
+
lineStartX: _x * lineStartCentroid,
|
70
|
+
lineStartY: _y * lineStartCentroid,
|
71
|
+
lineStartMouseoverX: _mouseoverX * lineStartCentroid,
|
72
|
+
lineStartMouseoverY: _mouseoverY * lineStartCentroid,
|
73
|
+
x: _x * labelCentroid!,
|
74
|
+
y: _y * labelCentroid!,
|
75
|
+
mouseoverX: _mouseoverX * labelCentroid!,
|
76
|
+
mouseoverY: _mouseoverY * labelCentroid!,
|
77
|
+
textWidth: 0, // 後面再做計算
|
78
|
+
collisionShiftX: 0, // 後面再做計算
|
79
|
+
collisionShiftY: 0, // 後面再做計算
|
80
|
+
quadrant: _x >= 0 && _y <= 0
|
81
|
+
? 1
|
82
|
+
: _x < 0 && _y <= 0
|
83
|
+
? 2
|
84
|
+
: _x < 0 && _y > 0
|
85
|
+
? 3
|
86
|
+
: 4
|
53
87
|
}
|
54
88
|
})
|
55
89
|
.filter(d => d.pieDatum.data.visible)
|
56
90
|
}
|
57
91
|
|
58
92
|
// 繪製圓餅圖
|
59
|
-
function renderLabel (
|
93
|
+
function renderLabel ({ labelGSelection, data, fullParams, fullChartParams, textSizePx }: {
|
94
|
+
labelGSelection: d3.Selection<SVGGElement, undefined, any, any>
|
95
|
+
data: RenderDatum[]
|
96
|
+
fullParams: PieLabelsParams
|
97
|
+
fullChartParams: ChartParams
|
98
|
+
textSizePx: number
|
99
|
+
}) {
|
60
100
|
// console.log(data)
|
61
101
|
// let update = this.gSelection.selectAll('g').data(pieData)
|
62
|
-
|
63
|
-
.selectAll<
|
102
|
+
const textSelection = labelGSelection
|
103
|
+
.selectAll<SVGTextElement, RenderDatum>('text')
|
64
104
|
.data(data, d => d.pieDatum.id)
|
65
|
-
|
66
|
-
.append<SVGPathElement>('text')
|
105
|
+
.join('text')
|
67
106
|
.classed(textClassName, true)
|
68
|
-
let exit = update.exit()
|
69
|
-
|
70
|
-
enter
|
71
|
-
.append('text')
|
72
|
-
|
73
|
-
const labelSelection = update.merge(enter)
|
74
|
-
labelSelection
|
75
107
|
.attr('font-weight', 'bold')
|
76
|
-
.attr('text-anchor', '
|
77
|
-
.style('dominant-baseline', '
|
108
|
+
.attr('text-anchor', d => d.quadrant == 1 || d.quadrant == 4 ? 'start' : 'end')
|
109
|
+
.style('dominant-baseline', d => d.quadrant == 1 || d.quadrant == 2 ? 'auto' : 'hanging')
|
78
110
|
// .style('pointer-events', 'none')
|
79
111
|
.style('cursor', d => fullChartParams.highlightTarget && fullChartParams.highlightTarget != 'none'
|
80
112
|
? 'pointer'
|
81
113
|
: 'none')
|
82
|
-
// .text(
|
83
|
-
.text(d => pluginParams.labelFn(d.pieDatum.data))
|
114
|
+
// .text(d => fullParams.labelFn(d.pieDatum.data))
|
84
115
|
.attr('font-size', fullChartParams.styles.textSize)
|
85
|
-
.attr('
|
116
|
+
.attr('x', 0)
|
117
|
+
.attr('y', 0)
|
118
|
+
.attr('fill', (d, i) => getDatumColor({ datum: d.pieDatum.data, colorType: fullParams.labelColorType, fullChartParams }))
|
119
|
+
.each((d, i, n) => {
|
120
|
+
const textNode = d3.select<SVGTextElement, RenderDatum>(n[i])
|
121
|
+
.selectAll('tspan')
|
122
|
+
.data(d.arcLabels)
|
123
|
+
.join('tspan')
|
124
|
+
.attr('x', 0)
|
125
|
+
.attr('y', (_d, _i) => d.quadrant == 1 || d.quadrant == 2
|
126
|
+
? - (d.arcLabels.length - 1 - _i) * textSizePx
|
127
|
+
: _i * textSizePx)
|
128
|
+
.text(d => d)
|
129
|
+
})
|
130
|
+
textSelection
|
86
131
|
.transition()
|
87
132
|
.attr('transform', (d) => {
|
88
133
|
// console.log('transform', d)
|
89
134
|
return 'translate(' + d.x + ',' + d.y + ')'
|
90
135
|
})
|
91
136
|
// .on('end', () => initHighlight({ labelSelection, data, fullChartParams }))
|
92
|
-
exit.remove()
|
93
137
|
|
94
138
|
// 如無新增資料則不用等動畫
|
95
139
|
// if (enter.size() == 0) {
|
96
140
|
// this.initHighlight()
|
97
141
|
// }
|
98
142
|
|
99
|
-
return
|
143
|
+
return textSelection
|
100
144
|
}
|
101
145
|
|
102
|
-
//
|
103
|
-
//
|
104
|
-
//
|
105
|
-
//
|
106
|
-
|
107
|
-
//
|
108
|
-
//
|
109
|
-
//
|
110
|
-
//
|
111
|
-
//
|
112
|
-
//
|
113
|
-
//
|
114
|
-
//
|
115
|
-
//
|
116
|
-
//
|
146
|
+
// // 獲取每個文字元素的邊界框並檢查是否重疊
|
147
|
+
// function resolveCollisions(labelSelection: d3.Selection<SVGPathElement, RenderDatum, any, any>, data: RenderDatum[]) {
|
148
|
+
// const textArray = labelSelection.nodes();
|
149
|
+
// const padding = 10; // 調整文字間的間距
|
150
|
+
|
151
|
+
// // 存儲每個標籤的當前位置
|
152
|
+
// const positions = textArray.map((textNode, i) => {
|
153
|
+
// const bbox = textNode.getBBox();
|
154
|
+
// // const arcCentroid = arc.centroid(data[i]);
|
155
|
+
// const arcCentroid = [data[i].x, data[i].y];
|
156
|
+
// return {
|
157
|
+
// node: textNode,
|
158
|
+
// x: arcCentroid[0],
|
159
|
+
// y: arcCentroid[1],
|
160
|
+
// width: bbox.width,
|
161
|
+
// height: bbox.height
|
162
|
+
// };
|
163
|
+
// });
|
164
|
+
// // console.log('positions', positions)
|
165
|
+
|
166
|
+
// for (let i = 0; i < positions.length; i++) {
|
167
|
+
// const a = positions[i];
|
168
|
+
|
169
|
+
// for (let j = i + 1; j < positions.length; j++) {
|
170
|
+
// const b = positions[j];
|
171
|
+
|
172
|
+
// // 檢查是否重疊
|
173
|
+
// if (!(a.x + a.width / 2 < b.x - b.width / 2 ||
|
174
|
+
// a.x - a.width / 2 > b.x + b.width / 2 ||
|
175
|
+
// a.y + a.height / 2 < b.y - b.height / 2 ||
|
176
|
+
// a.y - a.height / 2 > b.y + b.height / 2)) {
|
177
|
+
|
178
|
+
// // 如果有重疊,則位移其中一個文字,這裡我們進行上下位移
|
179
|
+
// const moveDown = (b.y > a.y) ? padding : -padding; // 決定方向
|
180
|
+
// b.y += moveDown; // 更新 b 的 y 座標
|
181
|
+
|
182
|
+
// // 更新 b 的 x 座標,根據與 a 的位置差異進行微調
|
183
|
+
// const moveRight = (b.x > a.x) ? padding : -padding;
|
184
|
+
// b.x += moveRight;
|
185
|
+
|
186
|
+
// // // 重新設置 b 的 transform 來移動
|
187
|
+
// d3.select(b.node)
|
188
|
+
// .transition()
|
189
|
+
// .attr("transform", `translate(${b.x},${b.y})`);
|
190
|
+
|
191
|
+
// data[j].collisionShiftX = moveRight
|
192
|
+
// data[j].collisionShiftY = moveDown
|
193
|
+
// }
|
194
|
+
// }
|
195
|
+
// }
|
117
196
|
// }
|
118
197
|
|
119
|
-
|
120
|
-
|
198
|
+
// 獲取每個文字元素的邊界框並檢查是否重疊
|
199
|
+
function resolveCollisions(textSelection: d3.Selection<SVGTextElement, RenderDatum, any, any>, data: RenderDatum[], textSizePx: number) {
|
200
|
+
const textArray = textSelection.nodes();
|
201
|
+
const padding = textSizePx // 調整文字間的間距
|
202
|
+
|
203
|
+
// 存儲每個標籤的當前位置
|
204
|
+
const positions = textArray.map((textNode, i) => {
|
205
|
+
const bbox = textNode.getBBox();
|
206
|
+
// const arcCentroid = arc.centroid(data[i]);
|
207
|
+
const arcCentroid = [data[i].x, data[i].y];
|
208
|
+
return {
|
209
|
+
node: textNode,
|
210
|
+
x: arcCentroid[0],
|
211
|
+
y: arcCentroid[1],
|
212
|
+
width: bbox.width,
|
213
|
+
height: bbox.height
|
214
|
+
}
|
215
|
+
})
|
216
|
+
|
217
|
+
// 順時針碰撞檢測(只處理 2、4 象限,將較後面的文字碰撞時往外偏移)
|
218
|
+
for (let i = 0; i < positions.length; i++) {
|
219
|
+
const a = positions[i]
|
220
|
+
|
221
|
+
for (let j = i + 1; j < positions.length; j++) {
|
222
|
+
const b = positions[j]
|
223
|
+
|
224
|
+
// 記錄文字寬度
|
225
|
+
data[i].textWidth = a.width
|
226
|
+
|
227
|
+
const ax = a.x + data[i].collisionShiftX
|
228
|
+
const ay = a.y + data[i].collisionShiftY
|
229
|
+
const bx = b.x + data[j].collisionShiftX
|
230
|
+
const by = b.y + data[j].collisionShiftY
|
231
|
+
|
232
|
+
// 檢查是否重疊
|
233
|
+
if (!(ax + a.width / 2 < bx - b.width / 2 ||
|
234
|
+
ax - a.width / 2 > bx + b.width / 2 ||
|
235
|
+
ay + a.height / 2 < by - b.height / 2 ||
|
236
|
+
ay - a.height / 2 > by + b.height / 2)) {
|
237
|
+
|
238
|
+
if (data[j].quadrant == 2) {
|
239
|
+
const moveDown = (by > ay)
|
240
|
+
? -padding * 2
|
241
|
+
: -padding
|
242
|
+
data[j].collisionShiftY += moveDown // 由後一個累加高度
|
243
|
+
} else if (data[j].quadrant == 4) {
|
244
|
+
const moveDown = (by > ay)
|
245
|
+
? padding
|
246
|
+
: padding * 2
|
247
|
+
data[j].collisionShiftY += moveDown // 由後一個累加高度
|
248
|
+
}
|
249
|
+
}
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
// 逆時針碰撞檢測(只處理 1、3 象限,將較前面的文字碰撞時往外偏移)
|
254
|
+
for (let i = positions.length - 1; i >= 0; i--) {
|
255
|
+
const a = positions[i]
|
256
|
+
|
257
|
+
for (let j = i - 1; j >= 0; j--) {
|
258
|
+
const b = positions[j]
|
259
|
+
|
260
|
+
// 記錄文字寬度
|
261
|
+
data[i].textWidth = a.width
|
262
|
+
|
263
|
+
const ax = a.x + data[i].collisionShiftX
|
264
|
+
const ay = a.y + data[i].collisionShiftY
|
265
|
+
const bx = b.x + data[j].collisionShiftX
|
266
|
+
const by = b.y + data[j].collisionShiftY
|
267
|
+
|
268
|
+
// 檢查是否重疊
|
269
|
+
if (!(ax + a.width / 2 < bx - b.width / 2 ||
|
270
|
+
ax - a.width / 2 > bx + b.width / 2 ||
|
271
|
+
ay + a.height / 2 < by - b.height / 2 ||
|
272
|
+
ay - a.height / 2 > by + b.height / 2)) {
|
273
|
+
|
274
|
+
if (data[j].quadrant == 1) {
|
275
|
+
const moveDown = (by > ay)
|
276
|
+
? -padding * 2
|
277
|
+
: -padding
|
278
|
+
data[j].collisionShiftY += moveDown // 由前一個累加高度
|
279
|
+
} else if (data[j].quadrant == 3) {
|
280
|
+
const moveDown = (by > ay)
|
281
|
+
? padding
|
282
|
+
: padding * 2
|
283
|
+
data[j].collisionShiftY += moveDown // 由前一個累加高度
|
284
|
+
}
|
285
|
+
}
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
// 全部算完再來 render
|
290
|
+
textSelection
|
291
|
+
.data(data)
|
292
|
+
.transition()
|
293
|
+
.attr('transform', (d) => {
|
294
|
+
return `translate(${d.x + d.collisionShiftX},${d.y + d.collisionShiftY})`
|
295
|
+
})
|
296
|
+
}
|
297
|
+
|
298
|
+
function renderLine ({ lineGSelection, data, fullParams, fullChartParams }: {
|
299
|
+
lineGSelection: d3.Selection<SVGGElement, undefined, any, any>
|
300
|
+
data: RenderDatum[]
|
301
|
+
fullParams: PieLabelsParams
|
302
|
+
fullChartParams: ChartParams
|
303
|
+
}) {
|
304
|
+
|
305
|
+
// 只顯示在有偏移的標籤
|
306
|
+
const filteredData = data.filter(d => d.collisionShiftX || d.collisionShiftY)
|
307
|
+
|
308
|
+
// 添加標籤的連接線
|
309
|
+
const lines = lineGSelection.selectAll<SVGPolylineElement, RenderDatum>("polyline")
|
310
|
+
.data(filteredData, d => d.pieDatum.id)
|
311
|
+
.join("polyline")
|
312
|
+
.attr("stroke", d => getDatumColor({ datum: d.pieDatum.data, colorType: fullParams.labelColorType, fullChartParams }))
|
313
|
+
.attr("stroke-width", 1)
|
314
|
+
.attr("fill", "none")
|
315
|
+
.attr("points", (d) => {
|
316
|
+
return [[d.lineStartX, d.lineStartY], [d.lineStartX, d.lineStartY]] as any // 畫出從弧線中心到延伸點的線
|
317
|
+
})
|
318
|
+
lines
|
319
|
+
.transition()
|
320
|
+
.attr("points", (d) => {
|
321
|
+
// const pos = arc.centroid(d) // 起點:弧線的中心點
|
322
|
+
// const outerPos = [pos[0] * 2.5, pos[1] * 2.5] // 外部延伸的點(乘以倍率來延長線段)
|
323
|
+
|
324
|
+
let lineEndX = d.x + d.collisionShiftX
|
325
|
+
let lineEndY = d.y + d.collisionShiftY
|
326
|
+
// if (d.lineStartX >= Math.abs(d.lineStartY)) {
|
327
|
+
// lineEndX -= d.textWidth / 2
|
328
|
+
// } else if (d.lineStartX <= - Math.abs(d.lineStartY)) {
|
329
|
+
// lineEndX += d.textWidth / 2
|
330
|
+
// }
|
331
|
+
|
332
|
+
return [[lineEndX, lineEndY], [d.lineStartX, d.lineStartY]] as any // 畫出從弧線中心到延伸點的線
|
333
|
+
})
|
334
|
+
|
335
|
+
return lines
|
336
|
+
}
|
337
|
+
|
338
|
+
function highlight ({ textSelection, lineSelection, ids, fullChartParams }: {
|
339
|
+
textSelection: d3.Selection<SVGTextElement, RenderDatum, any, any>
|
340
|
+
lineSelection: d3.Selection<SVGPolylineElement, RenderDatum, any, any>
|
121
341
|
ids: string[]
|
122
342
|
fullChartParams: ChartParams
|
123
343
|
}) {
|
124
|
-
|
344
|
+
textSelection.interrupt('highlight')
|
345
|
+
lineSelection.interrupt('highlight')
|
125
346
|
|
126
347
|
if (!ids.length) {
|
127
|
-
|
128
|
-
.transition()
|
348
|
+
textSelection
|
349
|
+
.transition('highlight')
|
129
350
|
.duration(200)
|
130
351
|
.attr('transform', (d) => {
|
131
|
-
return
|
352
|
+
return `translate(${d.x + d.collisionShiftX},${d.y + d.collisionShiftY})`
|
132
353
|
})
|
133
354
|
.style('opacity', 1)
|
355
|
+
lineSelection
|
356
|
+
.transition('highlight')
|
357
|
+
.duration(200)
|
358
|
+
.style('opacity', 1)
|
134
359
|
return
|
135
360
|
}
|
136
361
|
|
137
|
-
|
138
|
-
const segment = d3.select<
|
362
|
+
textSelection.each((d, i, n) => {
|
363
|
+
const segment = d3.select<SVGTextElement, RenderDatum>(n[i])
|
139
364
|
|
140
365
|
if (ids.includes(d.pieDatum.id)) {
|
141
366
|
segment
|
142
367
|
.style('opacity', 1)
|
143
|
-
.transition()
|
368
|
+
.transition('highlight')
|
144
369
|
.duration(200)
|
145
370
|
.attr('transform', (d) => {
|
146
|
-
|
371
|
+
// 如果已經有偏移過,則使用偏移後的位置(如果再改變的話很容易造成文字重疊)
|
372
|
+
if (d.collisionShiftX || d.collisionShiftY) {
|
373
|
+
return `translate(${d.x + d.collisionShiftX},${d.y + d.collisionShiftY})`
|
374
|
+
}
|
375
|
+
return `translate(${d.mouseoverX + d.collisionShiftX},${d.mouseoverY + d.collisionShiftY})`
|
147
376
|
})
|
148
377
|
} else {
|
149
378
|
segment
|
150
379
|
.style('opacity', fullChartParams.styles.unhighlightedOpacity)
|
151
|
-
.transition()
|
380
|
+
.transition('highlight')
|
152
381
|
.duration(200)
|
153
382
|
.attr('transform', (d) => {
|
154
|
-
return
|
383
|
+
return `translate(${d.x + d.collisionShiftX},${d.y + d.collisionShiftY})`
|
155
384
|
})
|
156
385
|
}
|
157
386
|
})
|
387
|
+
lineSelection.each((d, i, n) => {
|
388
|
+
const segment = d3.select<SVGPolylineElement, RenderDatum>(n[i])
|
389
|
+
|
390
|
+
if (ids.includes(d.pieDatum.data.id)) {
|
391
|
+
segment
|
392
|
+
.style('opacity', 1)
|
393
|
+
.transition('highlight')
|
394
|
+
.duration(200)
|
395
|
+
} else {
|
396
|
+
segment
|
397
|
+
.style('opacity', fullChartParams.styles.unhighlightedOpacity)
|
398
|
+
.transition('highlight')
|
399
|
+
.duration(200)
|
400
|
+
}
|
401
|
+
})
|
158
402
|
}
|
159
403
|
|
160
404
|
|
@@ -165,14 +409,23 @@ function createEachPieLabel (pluginName: string, context: {
|
|
165
409
|
// SeriesDataMap$: Observable<Map<string, ComputedDatumSeries[]>>
|
166
410
|
fullParams$: Observable<PieLabelsParams>
|
167
411
|
fullChartParams$: Observable<ChartParams>
|
412
|
+
textSizePx$: Observable<number>
|
168
413
|
seriesHighlight$: Observable<ComputedDatumSeries[]>
|
169
414
|
seriesContainerPosition$: Observable<SeriesContainerPosition>
|
170
415
|
event$: Subject<EventSeries>
|
171
416
|
}) {
|
172
417
|
const destroy$ = new Subject()
|
173
418
|
|
419
|
+
context.containerSelection.selectAll('g').remove()
|
420
|
+
|
421
|
+
const lineGSelection: d3.Selection<SVGGElement, any, any, unknown> = context.containerSelection.append('g')
|
422
|
+
lineGSelection.classed(lineGClassName, true)
|
423
|
+
const labelGSelection: d3.Selection<SVGGElement, any, any, unknown> = context.containerSelection.append('g')
|
424
|
+
labelGSelection.classed(labelGClassName, true)
|
425
|
+
|
174
426
|
// const graphicSelection: d3.Selection<SVGGElement, any, any, any> = selection.append('g')
|
175
|
-
|
427
|
+
const textSelection$: Subject<d3.Selection<SVGTextElement, RenderDatum, any, any>> = new Subject()
|
428
|
+
const lineSelection$: Subject<d3.Selection<SVGPolylineElement, RenderDatum, any, any>> = new Subject()
|
176
429
|
let renderData: RenderDatum[] = []
|
177
430
|
|
178
431
|
const shorterSideWith$ = context.seriesContainerPosition$.pipe(
|
@@ -181,18 +434,28 @@ function createEachPieLabel (pluginName: string, context: {
|
|
181
434
|
distinctUntilChanged()
|
182
435
|
)
|
183
436
|
|
437
|
+
const lineStartCentroid$ = context.fullParams$.pipe(
|
438
|
+
takeUntil(destroy$),
|
439
|
+
map(d => {
|
440
|
+
return d.labelCentroid >= pieOuterCentroid
|
441
|
+
? pieOuterCentroid // 當 label在 pie的外側時,線條從 pie的邊緣開始
|
442
|
+
: d.labelCentroid // 當 label在 pie的內側時,線條從 label未偏移前的位置開始
|
443
|
+
|
444
|
+
})
|
445
|
+
)
|
446
|
+
|
184
447
|
combineLatest({
|
185
448
|
shorterSideWith: shorterSideWith$,
|
186
449
|
containerVisibleComputedLayoutData: context.containerVisibleComputedLayoutData$,
|
187
450
|
fullParams: context.fullParams$,
|
188
|
-
fullChartParams: context.fullChartParams
|
451
|
+
fullChartParams: context.fullChartParams$,
|
452
|
+
textSizePx: context.textSizePx$,
|
453
|
+
lineStartCentroid: lineStartCentroid$
|
189
454
|
}).pipe(
|
190
455
|
takeUntil(destroy$),
|
191
456
|
switchMap(async (d) => d),
|
192
457
|
).subscribe(data => {
|
193
458
|
|
194
|
-
// const shorterSideWith = data.layout.width < data.layout.height ? data.layout.width : data.layout.height
|
195
|
-
|
196
459
|
// 弧產生器 (d3.arc())
|
197
460
|
const arc = makeD3Arc({
|
198
461
|
axisWidth: data.shorterSideWith,
|
@@ -216,16 +479,45 @@ function createEachPieLabel (pluginName: string, context: {
|
|
216
479
|
endAngle: data.fullParams.endAngle
|
217
480
|
})
|
218
481
|
|
219
|
-
renderData = makeRenderData(
|
482
|
+
renderData = makeRenderData({
|
483
|
+
pieData,
|
484
|
+
arc,
|
485
|
+
arcMouseover,
|
486
|
+
labelCentroid: data.fullParams.labelCentroid,
|
487
|
+
lineStartCentroid: data.lineStartCentroid,
|
488
|
+
fullParams: data.fullParams
|
489
|
+
})
|
490
|
+
|
491
|
+
// 先移除線條,等偏移後再重新繪製
|
492
|
+
lineGSelection.selectAll('polyline').remove()
|
493
|
+
|
494
|
+
const textSelection = renderLabel({
|
495
|
+
labelGSelection,
|
496
|
+
data: renderData,
|
497
|
+
fullParams: data.fullParams,
|
498
|
+
fullChartParams: data.fullChartParams,
|
499
|
+
textSizePx: data.textSizePx
|
500
|
+
})
|
501
|
+
|
502
|
+
// 等 label 本身的 transition 結束後再進行碰撞檢測
|
503
|
+
setTimeout(() => {
|
504
|
+
// resolveCollisions(labelSelection, renderData)
|
505
|
+
// 偏移 label
|
506
|
+
resolveCollisions(textSelection, renderData, data.textSizePx)
|
507
|
+
|
508
|
+
const lineSelection = renderLine({ lineGSelection, data: renderData, fullParams: data.fullParams, fullChartParams: data.fullChartParams })
|
509
|
+
|
510
|
+
lineSelection$.next(lineSelection)
|
220
511
|
|
221
|
-
|
512
|
+
}, 1000)
|
222
513
|
|
223
|
-
|
514
|
+
textSelection$.next(textSelection)
|
224
515
|
|
225
516
|
})
|
226
517
|
|
227
518
|
combineLatest({
|
228
|
-
|
519
|
+
textSelection: textSelection$,
|
520
|
+
lineSelection: lineSelection$,
|
229
521
|
highlight: context.seriesHighlight$.pipe(
|
230
522
|
map(data => data.map(d => d.id))
|
231
523
|
),
|
@@ -235,7 +527,8 @@ function createEachPieLabel (pluginName: string, context: {
|
|
235
527
|
switchMap(async d => d)
|
236
528
|
).subscribe(data => {
|
237
529
|
highlight({
|
238
|
-
|
530
|
+
textSelection: data.textSelection,
|
531
|
+
lineSelection: data.lineSelection,
|
239
532
|
ids: data.highlight,
|
240
533
|
fullChartParams: data.fullChartParams,
|
241
534
|
})
|
@@ -290,6 +583,7 @@ export const PieLabels = defineSeriesPlugin(pluginName, DEFAULT_PIE_LABELS_PARAM
|
|
290
583
|
// SeriesDataMap$: observer.SeriesDataMap$,
|
291
584
|
fullParams$: observer.fullParams$,
|
292
585
|
fullChartParams$: observer.fullChartParams$,
|
586
|
+
textSizePx$: observer.textSizePx$,
|
293
587
|
seriesHighlight$: observer.seriesHighlight$,
|
294
588
|
seriesContainerPosition$: containerPosition$,
|
295
589
|
event$: subject.event$,
|
@@ -24,7 +24,7 @@ import {
|
|
24
24
|
import { DEFAULT_ROSE_PARAMS } from '../defaults'
|
25
25
|
// import { makePieData } from '../seriesUtils'
|
26
26
|
// import { getD3TransitionEase, makeD3Arc } from '../../utils/d3Utils'
|
27
|
-
import { getClassName } from '../../utils/orbchartsUtils'
|
27
|
+
import { getDatumColor, getClassName } from '../../utils/orbchartsUtils'
|
28
28
|
import { seriesCenterSelectionObservable } from '../seriesObservables'
|
29
29
|
|
30
30
|
// @Q@ 暫時先寫在這裡,之後pie一起重構後再放到seriesUtils
|
@@ -39,15 +39,15 @@ const pluginName = 'Rose'
|
|
39
39
|
const roseInnerRadius = 0
|
40
40
|
const roseStartAngle = 0
|
41
41
|
const roseEndAngle = Math.PI * 2
|
42
|
-
const rosePadAngle = 0
|
43
42
|
|
44
|
-
function makeTweenArcFn ({ cornerRadius, outerRadius, axisWidth, maxValue, arcScaleType }: {
|
43
|
+
function makeTweenArcFn ({ cornerRadius, outerRadius, axisWidth, maxValue, arcScaleType, fullParams }: {
|
45
44
|
// interpolateRadius: (t: number) => number
|
46
45
|
outerRadius: number
|
47
46
|
cornerRadius: number
|
48
47
|
axisWidth: number
|
49
48
|
maxValue: number
|
50
49
|
arcScaleType: 'radius' | 'area'
|
50
|
+
fullParams: RoseParams
|
51
51
|
}): (d: PieDatum) => (t: number) => string {
|
52
52
|
|
53
53
|
const outerRadiusWidth = (axisWidth / 2) * outerRadius
|
@@ -78,7 +78,7 @@ function makeTweenArcFn ({ cornerRadius, outerRadius, axisWidth, maxValue, arcSc
|
|
78
78
|
const arc = d3.arc()
|
79
79
|
.innerRadius(0)
|
80
80
|
.outerRadius(outerRadius)
|
81
|
-
.padAngle(
|
81
|
+
.padAngle(fullParams.padAngle)
|
82
82
|
.padRadius(outerRadius)
|
83
83
|
.cornerRadius(cornerRadius)
|
84
84
|
|
@@ -151,8 +151,8 @@ function highlight ({ pathSelection, ids, fullParams, fullChartParams, tweenArc
|
|
151
151
|
.attr('d', (d: PieDatum) => {
|
152
152
|
return tweenArc({
|
153
153
|
...d,
|
154
|
-
startAngle: d.startAngle - fullParams.
|
155
|
-
endAngle: d.endAngle + fullParams.
|
154
|
+
startAngle: d.startAngle - fullParams.angleIncreaseWhileHighlight,
|
155
|
+
endAngle: d.endAngle + fullParams.angleIncreaseWhileHighlight
|
156
156
|
})(1)
|
157
157
|
})
|
158
158
|
// .on('interrupt', () => {
|
@@ -215,7 +215,7 @@ function createEachRose (pluginName: string, context: {
|
|
215
215
|
value: d.value,
|
216
216
|
startAngle: eachAngle * i,
|
217
217
|
endAngle: eachAngle * (i + 1),
|
218
|
-
padAngle:
|
218
|
+
padAngle: data.fullParams.padAngle,
|
219
219
|
prevValue: (lastPieData[i] && lastPieData[i].id === d.id) ? lastPieData[i].value : 0
|
220
220
|
}
|
221
221
|
})
|
@@ -250,7 +250,8 @@ function createEachRose (pluginName: string, context: {
|
|
250
250
|
outerRadius: data.fullParams.outerRadius,
|
251
251
|
axisWidth: data.axisWidth,
|
252
252
|
maxValue: data.maxValue,
|
253
|
-
arcScaleType: data.fullParams.arcScaleType
|
253
|
+
arcScaleType: data.fullParams.arcScaleType,
|
254
|
+
fullParams: data.fullParams
|
254
255
|
})
|
255
256
|
})
|
256
257
|
)
|
@@ -269,6 +270,8 @@ function createEachRose (pluginName: string, context: {
|
|
269
270
|
pieData: pieData$,
|
270
271
|
tweenArc: tweenArc$,
|
271
272
|
transitionDuration: transitionDuration$,
|
273
|
+
fullParams: context.fullParams$,
|
274
|
+
fullChartParams: context.fullChartParams$
|
272
275
|
}).pipe(
|
273
276
|
takeUntil(destroy$),
|
274
277
|
switchMap(async d => d)
|
@@ -287,6 +290,12 @@ function createEachRose (pluginName: string, context: {
|
|
287
290
|
.classed(pathClassName, true)
|
288
291
|
.style('cursor', 'pointer')
|
289
292
|
.attr('fill', (d, i) => d.data.color)
|
293
|
+
.attr('stroke', (d, i) => getDatumColor({
|
294
|
+
datum: d.data,
|
295
|
+
colorType: data.fullParams.strokeColorType,
|
296
|
+
fullChartParams: data.fullChartParams
|
297
|
+
}))
|
298
|
+
.attr('stroke-width', data.fullParams.strokeWidth)
|
290
299
|
pathSelection.interrupt('graphicMove')
|
291
300
|
pathSelection
|
292
301
|
.transition('graphicMove')
|