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

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.
Files changed (80) hide show
  1. package/dist/orbcharts-plugins-basic.es.js +10136 -9414
  2. package/dist/orbcharts-plugins-basic.umd.js +10 -10
  3. package/dist/src/base/BaseBarStack.d.ts +5 -1
  4. package/dist/src/base/BaseBars.d.ts +5 -1
  5. package/dist/src/base/BaseBarsTriangle.d.ts +5 -1
  6. package/dist/src/base/BaseDots.d.ts +4 -1
  7. package/dist/src/base/BaseGroupAxis.d.ts +35 -0
  8. package/dist/src/base/BaseLines.d.ts +3 -1
  9. package/dist/src/base/BaseValueAxis.d.ts +36 -0
  10. package/dist/src/grid/defaults.d.ts +3 -3
  11. package/dist/src/grid/gridObservables.d.ts +18 -4
  12. package/dist/src/grid/index.d.ts +1 -1
  13. package/dist/src/grid/plugins/GroupAux.d.ts +3 -0
  14. package/dist/src/grid/plugins/GroupAxis.d.ts +1 -3
  15. package/dist/src/grid/plugins/ValueAxis.d.ts +1 -3
  16. package/dist/src/grid/plugins/ValueStackAxis.d.ts +1 -3
  17. package/dist/src/grid/types.d.ts +1 -1
  18. package/dist/src/multiGrid/defaults.d.ts +9 -2
  19. package/dist/src/multiGrid/index.d.ts +8 -1
  20. package/dist/src/multiGrid/multiGridObservables.d.ts +12 -0
  21. package/dist/src/multiGrid/plugins/MultiBarStack.d.ts +1 -0
  22. package/dist/src/multiGrid/plugins/MultiBars.d.ts +1 -0
  23. package/dist/src/multiGrid/plugins/MultiBarsTriangle.d.ts +1 -0
  24. package/dist/src/multiGrid/plugins/MultiDots.d.ts +1 -0
  25. package/dist/src/multiGrid/plugins/MultiGroupAxis.d.ts +1 -0
  26. package/dist/src/multiGrid/plugins/MultiLines.d.ts +1 -0
  27. package/dist/src/multiGrid/plugins/MultiValueAxis.d.ts +1 -0
  28. package/dist/src/multiGrid/plugins/OverlappingValueAxes.d.ts +1 -0
  29. package/dist/src/multiGrid/types.d.ts +31 -0
  30. package/package.json +2 -2
  31. package/src/base/BaseBarStack.ts +375 -198
  32. package/src/base/BaseBars.ts +297 -191
  33. package/src/base/BaseBarsTriangle.ts +344 -229
  34. package/src/base/BaseDots.ts +285 -117
  35. package/src/base/BaseGroupAxis.ts +497 -0
  36. package/src/base/BaseLegend.ts +54 -5
  37. package/src/base/BaseLines.ts +180 -50
  38. package/src/base/BaseValueAxis.ts +475 -0
  39. package/src/grid/defaults.ts +3 -3
  40. package/src/grid/gridObservables.ts +163 -14
  41. package/src/grid/index.ts +1 -1
  42. package/src/grid/plugins/BarStack.ts +4 -0
  43. package/src/grid/plugins/Bars.ts +4 -0
  44. package/src/grid/plugins/BarsTriangle.ts +4 -0
  45. package/src/grid/plugins/Dots.ts +3 -0
  46. package/src/grid/plugins/{GroupArea.ts → GroupAux.ts} +24 -24
  47. package/src/grid/plugins/GroupAxis.ts +16 -348
  48. package/src/grid/plugins/Lines.ts +2 -0
  49. package/src/grid/plugins/ScalingArea.ts +9 -6
  50. package/src/grid/plugins/ValueAxis.ts +13 -337
  51. package/src/grid/plugins/ValueStackAxis.ts +35 -336
  52. package/src/grid/types.ts +1 -1
  53. package/src/index.ts +1 -0
  54. package/src/multiGrid/defaults.ts +120 -14
  55. package/src/multiGrid/index.ts +9 -2
  56. package/src/multiGrid/multiGridObservables.ts +279 -0
  57. package/src/multiGrid/plugins/MultiBarStack.ts +60 -0
  58. package/src/multiGrid/plugins/MultiBars.ts +59 -0
  59. package/src/multiGrid/plugins/MultiBarsTriangle.ts +58 -0
  60. package/src/multiGrid/plugins/MultiDots.ts +58 -0
  61. package/src/multiGrid/plugins/MultiGridLegend.ts +2 -7
  62. package/src/multiGrid/plugins/MultiGroupAxis.ts +53 -0
  63. package/src/multiGrid/plugins/MultiLines.ts +58 -0
  64. package/src/multiGrid/plugins/MultiValueAxis.ts +53 -0
  65. package/src/multiGrid/plugins/OverlappingValueAxes.ts +165 -0
  66. package/src/multiGrid/types.ts +39 -0
  67. package/tsconfig.dev.json +17 -0
  68. package/tsconfig.prod.json +14 -0
  69. package/vite.config.js +5 -0
  70. package/dist/src/grid/plugins/GroupArea.d.ts +0 -3
  71. package/dist/src/multiGrid/plugins/BarsAndLines.d.ts +0 -1
  72. package/dist/src/multiGrid/plugins/FirstGroupScaleAxis.d.ts +0 -0
  73. package/dist/src/multiGrid/plugins/TwoValueScaleAxes.d.ts +0 -0
  74. package/src/multiGrid/plugins/BarStackAndLines.ts +0 -0
  75. package/src/multiGrid/plugins/BarsAndLines.ts +0 -126
  76. package/src/multiGrid/plugins/BarsTriangleAndLines.ts +0 -0
  77. package/src/multiGrid/plugins/FirstGroupScaleAxis.ts +0 -0
  78. package/src/multiGrid/plugins/TwoValueScaleAxes.ts +0 -0
  79. /package/dist/src/{multiGrid/plugins/BarStackAndLines.d.ts → base/BaseGroupArea.d.ts} +0 -0
  80. /package/{dist/src/multiGrid/plugins/BarsTriangleAndLines.d.ts → src/base/BaseGroupArea.ts} +0 -0
@@ -0,0 +1,497 @@
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ switchMap,
5
+ distinctUntilChanged,
6
+ of,
7
+ first,
8
+ map,
9
+ takeUntil,
10
+ Observable,
11
+ Subject } from 'rxjs'
12
+ import { createAxisPointScale } from '@orbcharts/core'
13
+ import type { ColorType, ComputedDataGrid, ContainerPosition } from '@orbcharts/core'
14
+ import type { BasePluginFn } from './types'
15
+ import type {
16
+ ComputedDatumGrid,
17
+ DataFormatterGrid,
18
+ ChartParams,
19
+ TransformData } from '@orbcharts/core'
20
+ import { parseTickFormatValue } from '../utils/d3Utils'
21
+ import { getColor, getClassName } from '../utils/orbchartsUtils'
22
+
23
+ export interface BaseGroupAxisParams {
24
+ // xLabel: string
25
+ // labelAnchor: 'start' | 'end'
26
+ labelOffset: [number, number]
27
+ labelColorType: ColorType
28
+ axisLineVisible: boolean
29
+ axisLineColorType: ColorType
30
+ tickFormat: string | ((text: any) => string)
31
+ tickLineVisible: boolean
32
+ tickPadding: number
33
+ tickFullLine: boolean
34
+ tickFullLineDasharray: string
35
+ tickColorType: ColorType
36
+ // axisLineColor: string
37
+ // axisLabelColor: string
38
+ tickTextRotate: number
39
+ tickTextColorType: ColorType
40
+ }
41
+
42
+ interface BaseGroupAxisContext {
43
+ selection: d3.Selection<any, unknown, any, unknown>
44
+ computedData$: Observable<ComputedDataGrid>
45
+ fullParams$: Observable<BaseGroupAxisParams>
46
+ fullDataFormatter$: Observable<DataFormatterGrid>
47
+ fullChartParams$: Observable<ChartParams>
48
+ gridAxesTransform$: Observable<TransformData>
49
+ gridAxesReverseTransform$: Observable<TransformData>
50
+ gridAxesSize$: Observable<{
51
+ width: number;
52
+ height: number;
53
+ }>
54
+ gridContainer$: Observable<ContainerPosition[]>
55
+ isSeriesPositionSeprate$: Observable<boolean>
56
+ }
57
+
58
+ interface TextAlign {
59
+ textAnchor: "start" | "middle" | "end"
60
+ dominantBaseline: "middle" | "auto" | "hanging"
61
+ }
62
+
63
+ // const pluginName = 'GroupAxis'
64
+ // const containerClassName = getClassName(pluginName, 'container')
65
+ // const xAxisGClassName = getClassName(pluginName, 'xAxisG')
66
+ // const xAxisClassName = getClassName(pluginName, 'xAxis')
67
+ // const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
68
+ const defaultTickSize = 6
69
+
70
+ function renderPointAxis ({ selection, xAxisClassName, groupingLabelClassName, params, tickTextAlign, axisLabelAlign, gridAxesSize, fullDataFormatter, chartParams, groupScale, textTransform }: {
71
+ selection: d3.Selection<SVGGElement, any, any, any>,
72
+ xAxisClassName: string
73
+ groupingLabelClassName: string
74
+ params: BaseGroupAxisParams
75
+ tickTextAlign: TextAlign
76
+ axisLabelAlign: TextAlign
77
+ gridAxesSize: { width: number, height: number }
78
+ fullDataFormatter: DataFormatterGrid,
79
+ chartParams: ChartParams
80
+ groupScale: d3.ScalePoint<string>
81
+ textTransform: string
82
+ // tickTextFormatter: string | ((label: any) => string)
83
+ }) {
84
+
85
+ const xAxisSelection = selection
86
+ .selectAll<SVGGElement, BaseGroupAxisParams>(`g.${xAxisClassName}`)
87
+ .data([params])
88
+ .join('g')
89
+ .classed(xAxisClassName, true)
90
+
91
+ const axisLabelSelection = selection
92
+ .selectAll<SVGGElement, BaseGroupAxisParams>(`g.${groupingLabelClassName}`)
93
+ .data([params])
94
+ .join('g')
95
+ .classed(groupingLabelClassName, true)
96
+ .each((d, i, g) => {
97
+ const text = d3.select(g[i])
98
+ .selectAll<SVGTextElement, BaseGroupAxisParams>('text')
99
+ .data([d])
100
+ .join(
101
+ enter => {
102
+ return enter
103
+ .append('text')
104
+ .style('font-weight', 'bold')
105
+ },
106
+ update => update,
107
+ exit => exit.remove()
108
+ )
109
+ .attr('text-anchor', axisLabelAlign.textAnchor)
110
+ .attr('dominant-baseline', axisLabelAlign.dominantBaseline)
111
+ .style('font-size', `${chartParams.styles.textSize}px`)
112
+ .style('fill', getColor(params.labelColorType, chartParams))
113
+ .style('transform', textTransform)
114
+ .text(d => fullDataFormatter.grid.groupAxis.label)
115
+ })
116
+ .attr('transform', d => `translate(${gridAxesSize.width + d.tickPadding + params.labelOffset[0]}, ${- d.tickPadding - defaultTickSize - params.labelOffset[1]})`)
117
+
118
+
119
+ // 設定X軸刻度
120
+ // const xAxis = d3.axisBottom(groupScale)
121
+ const xAxis = d3.axisTop(groupScale)
122
+ .scale(groupScale)
123
+ .tickSize(params.tickFullLine == true
124
+ ? -gridAxesSize.height
125
+ : defaultTickSize)
126
+ .tickSizeOuter(0)
127
+ .tickFormat(d => parseTickFormatValue(d, params.tickFormat))
128
+ .tickPadding(params.tickPadding)
129
+
130
+ const xAxisEl = xAxisSelection
131
+ .transition()
132
+ .duration(100)
133
+ .call(xAxis)
134
+ // .attr('text-anchor', () => params.tickTextRotate !== false ? 'end' : 'middle')
135
+ // .attr('text-anchor', () => 'middle')
136
+
137
+ xAxisEl.selectAll('line')
138
+ .style('fill', 'none')
139
+ .style('stroke', params.tickLineVisible == true ? getColor(params.tickColorType, chartParams) : 'none')
140
+ .style('stroke-dasharray', params.tickFullLineDasharray)
141
+ .attr('pointer-events', 'none')
142
+
143
+ xAxisEl.selectAll('path')
144
+ .style('fill', 'none')
145
+ .style('stroke', params.axisLineVisible == true ? getColor(params.axisLineColorType, chartParams) : 'none')
146
+ .style('shape-rendering', 'crispEdges')
147
+
148
+ // const xText = xAxisEl.selectAll('text')
149
+ // xAxisSelection.each((d, i, g) => {
150
+ // d3.select(g[i])
151
+ // .selectAll('text')
152
+ // .data([d])
153
+ // .join('text')
154
+ // .style('font-family', 'sans-serif')
155
+ // .style('font-size', `${chartParams.styles.textSize}px`)
156
+ // // .style('font-weight', 'bold')
157
+ // .style('color', getColor(params.tickTextColorType, chartParams))
158
+ // .attr('text-anchor', tickTextAlign.textAnchor)
159
+ // .attr('dominant-baseline', tickTextAlign.dominantBaseline)
160
+ // .attr('transform-origin', `0 -${params.tickPadding + defaultTickSize}`)
161
+ // .style('transform', textTransform)
162
+ // })
163
+ const xText = xAxisSelection.selectAll('text')
164
+ .style('font-family', 'sans-serif')
165
+ .style('font-size', `${chartParams.styles.textSize}px`)
166
+ // .style('font-weight', 'bold')
167
+ .style('color', getColor(params.tickTextColorType, chartParams))
168
+ .attr('text-anchor', tickTextAlign.textAnchor)
169
+ .attr('dominant-baseline', tickTextAlign.dominantBaseline)
170
+ .attr('transform-origin', `0 -${params.tickPadding + defaultTickSize}`)
171
+ .style('transform', textTransform)
172
+
173
+
174
+ return xAxisSelection
175
+ }
176
+
177
+
178
+ export const createBaseGroupAxis: BasePluginFn<BaseGroupAxisContext> = ((pluginName: string, {
179
+ selection,
180
+ computedData$,
181
+ fullParams$,
182
+ fullDataFormatter$,
183
+ fullChartParams$,
184
+ gridAxesTransform$,
185
+ gridAxesReverseTransform$,
186
+ gridAxesSize$,
187
+ gridContainer$,
188
+ isSeriesPositionSeprate$,
189
+ }) => {
190
+
191
+ const destroy$ = new Subject()
192
+
193
+ const containerClassName = getClassName(pluginName, 'container')
194
+ const xAxisGClassName = getClassName(pluginName, 'xAxisG')
195
+ const xAxisClassName = getClassName(pluginName, 'xAxis')
196
+ const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
197
+
198
+ const containerSelection$ = combineLatest({
199
+ computedData: computedData$.pipe(
200
+ distinctUntilChanged((a, b) => {
201
+ // 只有當series的數量改變時,才重新計算
202
+ return a.length === b.length
203
+ }),
204
+ ),
205
+ isSeriesPositionSeprate: isSeriesPositionSeprate$
206
+ }).pipe(
207
+ takeUntil(destroy$),
208
+ switchMap(async (d) => d),
209
+ map(data => {
210
+ return data.isSeriesPositionSeprate
211
+ // series分開的時候顯示各別axis
212
+ ? data.computedData
213
+ // series合併的時候只顯示第一個axis
214
+ : [data.computedData[0]]
215
+ }),
216
+ map((computedData, i) => {
217
+ return selection
218
+ .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${containerClassName}`)
219
+ .data(computedData, d => d[0] ? d[0].seriesIndex : i)
220
+ .join('g')
221
+ .classed(containerClassName, true)
222
+ })
223
+ )
224
+
225
+ const axisSelection$ = containerSelection$.pipe(
226
+ takeUntil(destroy$),
227
+ map((containerSelection, i) => {
228
+ return containerSelection
229
+ .selectAll<SVGGElement, ComputedDatumGrid[]>(`g.${xAxisGClassName}`)
230
+ .data([xAxisGClassName])
231
+ .join('g')
232
+ .classed(xAxisGClassName, true)
233
+ })
234
+ )
235
+
236
+ combineLatest({
237
+ containerSelection: containerSelection$,
238
+ gridContainer: gridContainer$
239
+ }).pipe(
240
+ takeUntil(destroy$),
241
+ switchMap(async d => d)
242
+ ).subscribe(data => {
243
+ data.containerSelection
244
+ .attr('transform', (d, i) => {
245
+ const gridContainer = data.gridContainer[i] ?? data.gridContainer[0]
246
+ const translate = gridContainer.translate
247
+ const scale = gridContainer.scale
248
+ return `translate(${translate[0]}, ${translate[1]}) scale(${scale[0]}, ${scale[1]})`
249
+ })
250
+ // .attr('opacity', 0)
251
+ // .transition()
252
+ // .attr('opacity', 1)
253
+ })
254
+
255
+ combineLatest({
256
+ axisSelection: axisSelection$,
257
+ gridAxesTransform: gridAxesTransform$,
258
+ }).pipe(
259
+ takeUntil(destroy$),
260
+ switchMap(async d => d)
261
+ ).subscribe(data => {
262
+ data.axisSelection
263
+ .style('transform', data.gridAxesTransform.value)
264
+ // .attr('opacity', 0)
265
+ // .transition()
266
+ // .attr('opacity', 1)
267
+
268
+ })
269
+
270
+
271
+ // const gridAxesSize$ = gridAxisSizeObservable({
272
+ // fullDataFormatter$,
273
+ // layout$
274
+ // })
275
+
276
+
277
+ // const tickTextFormatter$ = fullDataFormatter$
278
+ // .pipe(
279
+ // map(d => {
280
+ // return d.grid.seriesDirection === 'row' ? d.grid.columnLabelFormat : d.grid.rowLabelFormat
281
+ // })
282
+ // )
283
+
284
+ // const textTransform$: Observable<string> = new Observable(subscriber => {
285
+ // combineLatest({
286
+ // params: fullParams$,
287
+ // gridAxesTransform: gridAxesTransform$
288
+ // }).pipe(
289
+ // takeUntil(destroy$),
290
+ // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
291
+ // switchMap(async (d) => d),
292
+ // ).subscribe(data => {
293
+
294
+ // const transformData = Object.assign({}, data.gridAxesTransform)
295
+
296
+ // const value = getAxesTransformValue({
297
+ // translate: [0, 0],
298
+ // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
299
+ // rotate: transformData.rotate * -1 + data.params.tickTextRotate,
300
+ // rotateX: transformData.rotateX * -1,
301
+ // rotateY: transformData.rotateY * -1
302
+ // })
303
+
304
+ // subscriber.next(value)
305
+ // })
306
+ // })
307
+ // const reverseTransform$: Observable<TransformData> = gridAxesTransform$.pipe(
308
+ // takeUntil(destroy$),
309
+ // map(d => {
310
+ // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
311
+ // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
312
+ // const rotate = d.rotate * -1
313
+ // const rotateX = d.rotateX * -1
314
+ // const rotateY = d.rotateY * -1
315
+ // return {
316
+ // translate,
317
+ // scale,
318
+ // rotate,
319
+ // rotateX,
320
+ // rotateY,
321
+ // value: ''
322
+ // }
323
+ // }),
324
+ // )
325
+ const textTransform$ = combineLatest({
326
+ fullParams: fullParams$,
327
+ fullDataFormatter: fullDataFormatter$,
328
+ gridAxesReverseTransform: gridAxesReverseTransform$,
329
+ gridContainer: gridContainer$
330
+ }).pipe(
331
+ takeUntil(destroy$),
332
+ switchMap(async (d) => d),
333
+ map(data => {
334
+ const axisReverseTranslateValue = `translate(${data.gridAxesReverseTransform.translate[0]}px, ${data.gridAxesReverseTransform.translate[1]}px)`
335
+ const axisReverseRotateValue = `rotate(${data.gridAxesReverseTransform.rotate}deg) rotateX(${data.gridAxesReverseTransform.rotateX}deg) rotateY(${data.gridAxesReverseTransform.rotateY}deg)`
336
+ const containerScaleReverseScaleValue = `scale(${1 / data.gridContainer[0].scale[0]}, ${1 / data.gridContainer[0].scale[1]})`
337
+ const tickTextRotateDeg = (data.fullDataFormatter.grid.groupAxis.position === 'left' && data.fullDataFormatter.grid.valueAxis.position === 'top')
338
+ || (data.fullDataFormatter.grid.groupAxis.position === 'right' && data.fullDataFormatter.grid.valueAxis.position === 'bottom')
339
+ ? data.fullParams.tickTextRotate + 180 // 修正文字倒轉
340
+ : data.fullParams.tickTextRotate
341
+
342
+ const textRotateValue = `rotate(${tickTextRotateDeg}deg)`
343
+
344
+ // 必須按照順序(先抵消外層rotate,再抵消最外層scale,最後再做本身的rotate)
345
+ return `${axisReverseTranslateValue} ${axisReverseRotateValue} ${containerScaleReverseScaleValue} ${textRotateValue}`
346
+ }),
347
+ distinctUntilChanged()
348
+ )
349
+
350
+ const groupScale$: Observable<d3.ScalePoint<string>> = new Observable(subscriber => {
351
+ combineLatest({
352
+ fullDataFormatter: fullDataFormatter$,
353
+ gridAxesSize: gridAxesSize$,
354
+ computedData: computedData$
355
+ }).pipe(
356
+ takeUntil(destroy$),
357
+ switchMap(async (d) => d),
358
+ ).subscribe(data => {
359
+ const groupMin = 0
360
+ const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
361
+ const groupScaleDomainMin = data.fullDataFormatter.grid.groupAxis.scaleDomain[0] === 'auto'
362
+ ? groupMin - data.fullDataFormatter.grid.groupAxis.scalePadding
363
+ : data.fullDataFormatter.grid.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.grid.groupAxis.scalePadding
364
+ const groupScaleDomainMax = data.fullDataFormatter.grid.groupAxis.scaleDomain[1] === 'auto'
365
+ ? groupMax + data.fullDataFormatter.grid.groupAxis.scalePadding
366
+ : data.fullDataFormatter.grid.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.grid.groupAxis.scalePadding
367
+
368
+ const groupingLength = data.computedData[0]
369
+ ? data.computedData[0].length
370
+ : 0
371
+
372
+ let _labels = (data.computedData[0] ?? []).map(d => d.groupLabel)
373
+
374
+ const axisLabels = new Array(groupingLength).fill(0)
375
+ .map((d, i) => {
376
+ return _labels[i] != null
377
+ ? _labels[i]
378
+ : String(i) // 沒有label則用序列號填充
379
+ })
380
+ .filter((d, i) => {
381
+ return i >= groupScaleDomainMin && i <= groupScaleDomainMax
382
+ })
383
+
384
+
385
+ const padding = data.fullDataFormatter.grid.groupAxis.scalePadding
386
+
387
+ const groupScale = createAxisPointScale({
388
+ axisLabels,
389
+ axisWidth: data.gridAxesSize.width,
390
+ padding
391
+ })
392
+
393
+ subscriber.next(groupScale)
394
+ })
395
+ })
396
+
397
+ const tickTextAlign$: Observable<TextAlign> = combineLatest({
398
+ fullDataFormatter: fullDataFormatter$,
399
+ fullParams: fullParams$
400
+ }).pipe(
401
+ takeUntil(destroy$),
402
+ switchMap(async (d) => d),
403
+ map(data => {
404
+ let textAnchor: 'start' | 'middle' | 'end' = 'middle'
405
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
406
+
407
+ if (data.fullDataFormatter.grid.groupAxis.position === 'bottom') {
408
+ textAnchor = data.fullParams.tickTextRotate
409
+ ? 'end'
410
+ : 'middle'
411
+ dominantBaseline = 'hanging'
412
+ } else if (data.fullDataFormatter.grid.groupAxis.position === 'top') {
413
+ textAnchor = data.fullParams.tickTextRotate
414
+ ? 'end'
415
+ : 'middle'
416
+ dominantBaseline = 'auto'
417
+ } else if (data.fullDataFormatter.grid.groupAxis.position === 'left') {
418
+ textAnchor = 'end'
419
+ dominantBaseline = 'middle'
420
+ } else if (data.fullDataFormatter.grid.groupAxis.position === 'right') {
421
+ textAnchor = 'start'
422
+ dominantBaseline = 'middle'
423
+ }
424
+ return {
425
+ textAnchor,
426
+ dominantBaseline
427
+ }
428
+ })
429
+ )
430
+
431
+ const axisLabelAlign$: Observable<TextAlign> = fullDataFormatter$.pipe(
432
+ takeUntil(destroy$),
433
+ map(d => {
434
+ let textAnchor: 'start' | 'middle' | 'end' = 'start'
435
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
436
+
437
+ if (d.grid.groupAxis.position === 'bottom') {
438
+ dominantBaseline = 'hanging'
439
+ } else if (d.grid.groupAxis.position === 'top') {
440
+ dominantBaseline = 'auto'
441
+ } else if (d.grid.groupAxis.position === 'left') {
442
+ textAnchor = 'end'
443
+ } else if (d.grid.groupAxis.position === 'right') {
444
+ textAnchor = 'start'
445
+ }
446
+ if (d.grid.valueAxis.position === 'left') {
447
+ textAnchor = 'start'
448
+ } else if (d.grid.valueAxis.position === 'right') {
449
+ textAnchor = 'end'
450
+ } else if (d.grid.valueAxis.position === 'bottom') {
451
+ dominantBaseline = 'auto'
452
+ } else if (d.grid.valueAxis.position === 'top') {
453
+ dominantBaseline = 'hanging'
454
+ }
455
+ return {
456
+ textAnchor,
457
+ dominantBaseline
458
+ }
459
+ })
460
+ )
461
+
462
+ combineLatest({
463
+ axisSelection: axisSelection$,
464
+ params: fullParams$,
465
+ tickTextAlign: tickTextAlign$,
466
+ axisLabelAlign: axisLabelAlign$,
467
+ gridAxesSize: gridAxesSize$,
468
+ fullDataFormatter: fullDataFormatter$,
469
+ chartParams: fullChartParams$,
470
+ groupScale: groupScale$,
471
+ textTransform: textTransform$,
472
+ // tickTextFormatter: tickTextFormatter$
473
+ }).pipe(
474
+ takeUntil(destroy$),
475
+ switchMap(async (d) => d),
476
+ ).subscribe(data => {
477
+
478
+ renderPointAxis({
479
+ selection: data.axisSelection,
480
+ xAxisClassName,
481
+ groupingLabelClassName,
482
+ params: data.params,
483
+ tickTextAlign: data.tickTextAlign,
484
+ axisLabelAlign: data.axisLabelAlign,
485
+ gridAxesSize: data.gridAxesSize,
486
+ fullDataFormatter: data.fullDataFormatter,
487
+ chartParams: data.chartParams,
488
+ groupScale: data.groupScale,
489
+ textTransform: data.textTransform,
490
+ // tickTextFormatter: data.tickTextFormatter
491
+ })
492
+ })
493
+
494
+ return () => {
495
+ destroy$.next(undefined)
496
+ }
497
+ })
@@ -4,6 +4,7 @@ import {
4
4
  map,
5
5
  switchMap,
6
6
  takeUntil,
7
+ shareReplay,
7
8
  Observable,
8
9
  Subject } from 'rxjs'
9
10
  import type { BasePluginFn } from './types'
@@ -111,6 +112,44 @@ export const createBaseLegend: BasePluginFn<BaseLegendContext> = (pluginName: st
111
112
  // })
112
113
  // )
113
114
 
115
+ const SeriesLabelColorMap$ = combineLatest({
116
+ seriesLabels: seriesLabels$,
117
+ fullChartParams: fullChartParams$
118
+ }).pipe(
119
+ takeUntil(destroy$),
120
+ switchMap(async d => d),
121
+ map(data => {
122
+ const SeriesLabelColorMap: Map<string, string> = new Map()
123
+ let accIndex = 0
124
+ data.seriesLabels.forEach((label, i) => {
125
+ if (!SeriesLabelColorMap.has(label)) {
126
+ const color = getSeriesColor(accIndex, data.fullChartParams)
127
+ SeriesLabelColorMap.set(label, color)
128
+ accIndex ++
129
+ }
130
+ })
131
+ return SeriesLabelColorMap
132
+ })
133
+ )
134
+
135
+ // 對應seriesLabels是否顯示(只顯示不重覆的)
136
+ const visibleList$ = seriesLabels$.pipe(
137
+ takeUntil(destroy$),
138
+ map(data => {
139
+ const AccSeriesLabelSet = new Set()
140
+ let visibleList: boolean[] = []
141
+ data.forEach(d => {
142
+ if (AccSeriesLabelSet.has(d)) {
143
+ visibleList.push(false) // 已存在則不顯示
144
+ } else {
145
+ visibleList.push(true)
146
+ }
147
+ AccSeriesLabelSet.add(d) // 累加已存在的seriesLabel
148
+ })
149
+ return visibleList
150
+ })
151
+ )
152
+
114
153
  const lineDirection$ = fullParams$.pipe(
115
154
  takeUntil(destroy$),
116
155
  map(data => {
@@ -222,20 +261,28 @@ export const createBaseLegend: BasePluginFn<BaseLegendContext> = (pluginName: st
222
261
 
223
262
  // 先計算list內每個item
224
263
  const lengendItems$: Observable<LegendItem[][]> = combineLatest({
264
+ visibleList: visibleList$,
225
265
  fullParams: fullParams$,
226
266
  fullChartParams: fullChartParams$,
227
267
  seriesLabels: seriesLabels$,
228
268
  lineDirection: lineDirection$,
229
269
  lineMaxSize: lineMaxSize$,
230
- defaultListStyle: defaultListStyle$
270
+ defaultListStyle: defaultListStyle$,
271
+ SeriesLabelColorMap: SeriesLabelColorMap$
231
272
  }).pipe(
232
273
  takeUntil(destroy$),
233
274
  switchMap(async d => d),
234
275
  map(data => {
235
276
  return data.seriesLabels.reduce((prev: LegendItem[][], current, currentIndex) => {
277
+ // visible為flase則不加入
278
+ if (!data.visibleList[currentIndex]) {
279
+ return prev
280
+ }
281
+
236
282
  const textWidth = measureTextWidth(current, data.fullChartParams.styles.textSize)
237
283
  const itemWidth = (data.fullChartParams.styles.textSize * 1.5) + textWidth
238
- const color = getSeriesColor(currentIndex, data.fullChartParams)
284
+ // const color = getSeriesColor(currentIndex, data.fullChartParams)
285
+ const color = data.SeriesLabelColorMap.get(current)
239
286
  const lastItem: LegendItem | null = prev[0] && prev[0][0]
240
287
  ? prev[prev.length - 1][prev[prev.length - 1].length - 1]
241
288
  : null
@@ -311,7 +358,8 @@ export const createBaseLegend: BasePluginFn<BaseLegendContext> = (pluginName: st
311
358
 
312
359
  return prev
313
360
  }, [])
314
- })
361
+ }),
362
+ shareReplay(1)
315
363
  )
316
364
 
317
365
  // 依list計算出來的排序位置來計算整體容器的尺寸
@@ -353,14 +401,15 @@ export const createBaseLegend: BasePluginFn<BaseLegendContext> = (pluginName: st
353
401
  return { width, height }
354
402
  })(data, data.lengendItems)
355
403
 
356
- return {
404
+ return <LegendList>{
357
405
  direction: data.lineDirection,
358
406
  width,
359
407
  height,
360
408
  translateX: data.fullParams.gap,
361
409
  translateY: data.fullParams.gap
362
410
  }
363
- })
411
+ }),
412
+ shareReplay(1)
364
413
  )
365
414
 
366
415
  const legendCard$: Observable<LegendCard> = combineLatest({