@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.
- package/dist/orbcharts-plugins-basic.es.js +10136 -9414
- package/dist/orbcharts-plugins-basic.umd.js +10 -10
- package/dist/src/base/BaseBarStack.d.ts +5 -1
- package/dist/src/base/BaseBars.d.ts +5 -1
- package/dist/src/base/BaseBarsTriangle.d.ts +5 -1
- package/dist/src/base/BaseDots.d.ts +4 -1
- package/dist/src/base/BaseGroupAxis.d.ts +35 -0
- package/dist/src/base/BaseLines.d.ts +3 -1
- package/dist/src/base/BaseValueAxis.d.ts +36 -0
- package/dist/src/grid/defaults.d.ts +3 -3
- package/dist/src/grid/gridObservables.d.ts +18 -4
- package/dist/src/grid/index.d.ts +1 -1
- package/dist/src/grid/plugins/GroupAux.d.ts +3 -0
- package/dist/src/grid/plugins/GroupAxis.d.ts +1 -3
- package/dist/src/grid/plugins/ValueAxis.d.ts +1 -3
- package/dist/src/grid/plugins/ValueStackAxis.d.ts +1 -3
- package/dist/src/grid/types.d.ts +1 -1
- package/dist/src/multiGrid/defaults.d.ts +9 -2
- package/dist/src/multiGrid/index.d.ts +8 -1
- package/dist/src/multiGrid/multiGridObservables.d.ts +12 -0
- package/dist/src/multiGrid/plugins/MultiBarStack.d.ts +1 -0
- package/dist/src/multiGrid/plugins/MultiBars.d.ts +1 -0
- package/dist/src/multiGrid/plugins/MultiBarsTriangle.d.ts +1 -0
- package/dist/src/multiGrid/plugins/MultiDots.d.ts +1 -0
- package/dist/src/multiGrid/plugins/MultiGroupAxis.d.ts +1 -0
- package/dist/src/multiGrid/plugins/MultiLines.d.ts +1 -0
- package/dist/src/multiGrid/plugins/MultiValueAxis.d.ts +1 -0
- package/dist/src/multiGrid/plugins/OverlappingValueAxes.d.ts +1 -0
- package/dist/src/multiGrid/types.d.ts +31 -0
- package/package.json +2 -2
- package/src/base/BaseBarStack.ts +375 -198
- package/src/base/BaseBars.ts +297 -191
- package/src/base/BaseBarsTriangle.ts +344 -229
- package/src/base/BaseDots.ts +285 -117
- package/src/base/BaseGroupAxis.ts +497 -0
- package/src/base/BaseLegend.ts +54 -5
- package/src/base/BaseLines.ts +180 -50
- package/src/base/BaseValueAxis.ts +475 -0
- package/src/grid/defaults.ts +3 -3
- package/src/grid/gridObservables.ts +163 -14
- package/src/grid/index.ts +1 -1
- package/src/grid/plugins/BarStack.ts +4 -0
- package/src/grid/plugins/Bars.ts +4 -0
- package/src/grid/plugins/BarsTriangle.ts +4 -0
- package/src/grid/plugins/Dots.ts +3 -0
- package/src/grid/plugins/{GroupArea.ts → GroupAux.ts} +24 -24
- package/src/grid/plugins/GroupAxis.ts +16 -348
- package/src/grid/plugins/Lines.ts +2 -0
- package/src/grid/plugins/ScalingArea.ts +9 -6
- package/src/grid/plugins/ValueAxis.ts +13 -337
- package/src/grid/plugins/ValueStackAxis.ts +35 -336
- package/src/grid/types.ts +1 -1
- package/src/index.ts +1 -0
- package/src/multiGrid/defaults.ts +120 -14
- package/src/multiGrid/index.ts +9 -2
- package/src/multiGrid/multiGridObservables.ts +279 -0
- package/src/multiGrid/plugins/MultiBarStack.ts +60 -0
- package/src/multiGrid/plugins/MultiBars.ts +59 -0
- package/src/multiGrid/plugins/MultiBarsTriangle.ts +58 -0
- package/src/multiGrid/plugins/MultiDots.ts +58 -0
- package/src/multiGrid/plugins/MultiGridLegend.ts +2 -7
- package/src/multiGrid/plugins/MultiGroupAxis.ts +53 -0
- package/src/multiGrid/plugins/MultiLines.ts +58 -0
- package/src/multiGrid/plugins/MultiValueAxis.ts +53 -0
- package/src/multiGrid/plugins/OverlappingValueAxes.ts +165 -0
- package/src/multiGrid/types.ts +39 -0
- package/tsconfig.dev.json +17 -0
- package/tsconfig.prod.json +14 -0
- package/vite.config.js +5 -0
- package/dist/src/grid/plugins/GroupArea.d.ts +0 -3
- package/dist/src/multiGrid/plugins/BarsAndLines.d.ts +0 -1
- package/dist/src/multiGrid/plugins/FirstGroupScaleAxis.d.ts +0 -0
- package/dist/src/multiGrid/plugins/TwoValueScaleAxes.d.ts +0 -0
- package/src/multiGrid/plugins/BarStackAndLines.ts +0 -0
- package/src/multiGrid/plugins/BarsAndLines.ts +0 -126
- package/src/multiGrid/plugins/BarsTriangleAndLines.ts +0 -0
- package/src/multiGrid/plugins/FirstGroupScaleAxis.ts +0 -0
- package/src/multiGrid/plugins/TwoValueScaleAxes.ts +0 -0
- /package/dist/src/{multiGrid/plugins/BarStackAndLines.d.ts → base/BaseGroupArea.d.ts} +0 -0
- /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
|
+
})
|
package/src/base/BaseLegend.ts
CHANGED
@@ -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({
|