@orbcharts/plugins-basic 3.0.0-alpha.64 → 3.0.0-alpha.65

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,134 +27,378 @@ import { seriesCenterSelectionObservable } from '../seriesObservables'
27
27
  interface RenderDatum {
28
28
  pieDatum: PieDatum
29
29
  arcIndex: number
30
- arcLabel: string
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
- function makeRenderData (pieData: PieDatum[], arc: d3.Arc<any, d3.DefaultArcObject>, mouseoverArc: d3.Arc<any, d3.DefaultArcObject>, centroid: number): RenderDatum[] {
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] = mouseoverArc!.centroid(d as any)
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
- arcLabel: d.data.label,
49
- x: _x * centroid!,
50
- y: _y * centroid!,
51
- mouseoverX: _mouseoverX * centroid!,
52
- mouseoverY: _mouseoverY * centroid!
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 (selection: d3.Selection<SVGGElement, undefined, any, any>, data: RenderDatum[], pluginParams: PieLabelsParams, fullChartParams: ChartParams) {
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
- let update: d3.Selection<SVGPathElement, RenderDatum, any, any> = selection
63
- .selectAll<SVGPathElement, RenderDatum>('text')
102
+ const textSelection = labelGSelection
103
+ .selectAll<SVGTextElement, RenderDatum>('text')
64
104
  .data(data, d => d.pieDatum.id)
65
- let enter = update.enter()
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', 'middle')
77
- .style('dominant-baseline', 'middle')
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((d, i) => d.arcLabel)
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('fill', (d, i) => getDatumColor({ datum: d.pieDatum.data, colorType: pluginParams.labelColorType, fullChartParams }))
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 labelSelection
143
+ return textSelection
100
144
  }
101
145
 
102
- // function initHighlight ({ labelSelection, data, fullChartParams }: {
103
- // labelSelection: (d3.Selection<SVGPathElement, RenderDatum, any, any>)
104
- // data: RenderDatum[]
105
- // fullChartParams: ChartParams
106
- // }) {
107
- // removeHighlight({ labelSelection })
108
- // // if (fullParams.highlightSeriesId || fullParams.highlightDatumId) {
109
- // highlight({
110
- // labelSelection,
111
- // data,
112
- // id: fullChartParams.highlightDefault,
113
- // label: fullChartParams.highlightDefault,
114
- // fullChartParams
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
- function highlight ({ labelSelection, ids, fullChartParams }: {
120
- labelSelection: (d3.Selection<SVGPathElement, RenderDatum, any, any>)
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
- labelSelection.interrupt('highlight')
344
+ textSelection.interrupt('highlight')
345
+ lineSelection.interrupt('highlight')
125
346
 
126
347
  if (!ids.length) {
127
- labelSelection
128
- .transition()
348
+ textSelection
349
+ .transition('highlight')
129
350
  .duration(200)
130
351
  .attr('transform', (d) => {
131
- return 'translate(' + d.x + ',' + d.y + ')'
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
- labelSelection.each((d, i, n) => {
138
- const segment = d3.select<SVGPathElement, RenderDatum>(n[i])
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
- return 'translate(' + d.mouseoverX + ',' + d.mouseoverY + ')'
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 'translate(' + d.x + ',' + d.y + ')'
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
- let labelSelection$: Subject<d3.Selection<SVGPathElement, RenderDatum, any, any>> = new Subject()
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(pieData, arc, arcMouseover, data.fullParams.labelCentroid)
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
- const labelSelection = renderLabel(context.containerSelection, renderData, data.fullParams, data.fullChartParams)
512
+ }, 1000)
222
513
 
223
- labelSelection$.next(labelSelection)
514
+ textSelection$.next(textSelection)
224
515
 
225
516
  })
226
517
 
227
518
  combineLatest({
228
- labelSelection: labelSelection$,
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
- labelSelection: data.labelSelection,
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(rosePadAngle)
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.mouseoverAngleIncrease,
155
- endAngle: d.endAngle + fullParams.mouseoverAngleIncrease
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: rosePadAngle,
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')