@orbcharts/plugins-basic 3.0.0-alpha.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. package/LICENSE +201 -0
  2. package/package.json +41 -0
  3. package/src/grid/defaults.ts +95 -0
  4. package/src/grid/gridObservables.ts +114 -0
  5. package/src/grid/index.ts +12 -0
  6. package/src/grid/plugins/BarStack.ts +661 -0
  7. package/src/grid/plugins/Bars.ts +604 -0
  8. package/src/grid/plugins/BarsTriangle.ts +594 -0
  9. package/src/grid/plugins/Dots.ts +427 -0
  10. package/src/grid/plugins/GroupArea.ts +636 -0
  11. package/src/grid/plugins/GroupAxis.ts +363 -0
  12. package/src/grid/plugins/Lines.ts +528 -0
  13. package/src/grid/plugins/Ranking.ts +0 -0
  14. package/src/grid/plugins/RankingAxis.ts +0 -0
  15. package/src/grid/plugins/ScalingArea.ts +168 -0
  16. package/src/grid/plugins/ValueAxis.ts +356 -0
  17. package/src/grid/plugins/ValueStackAxis.ts +372 -0
  18. package/src/grid/types.ts +102 -0
  19. package/src/index.ts +7 -0
  20. package/src/multiGrid/index.ts +0 -0
  21. package/src/multiGrid/plugins/Diverging.ts +0 -0
  22. package/src/multiGrid/plugins/DivergingAxes.ts +0 -0
  23. package/src/multiGrid/plugins/TwoScaleAxes.ts +0 -0
  24. package/src/multiGrid/plugins/TwoScales.ts +0 -0
  25. package/src/multiValue/index.ts +0 -0
  26. package/src/multiValue/plugins/Scatter.ts +0 -0
  27. package/src/multiValue/plugins/ScatterAxes.ts +0 -0
  28. package/src/noneData/defaults.ts +47 -0
  29. package/src/noneData/index.ts +4 -0
  30. package/src/noneData/plugins/Container.ts +11 -0
  31. package/src/noneData/plugins/Tooltip.ts +305 -0
  32. package/src/noneData/types.ts +26 -0
  33. package/src/relationship/index.ts +0 -0
  34. package/src/relationship/plugins/Relationship.ts +0 -0
  35. package/src/series/defaults.ts +82 -0
  36. package/src/series/index.ts +6 -0
  37. package/src/series/plugins/Bubbles.ts +553 -0
  38. package/src/series/plugins/Pie.ts +603 -0
  39. package/src/series/plugins/PieEventTexts.ts +194 -0
  40. package/src/series/plugins/PieLabels.ts +289 -0
  41. package/src/series/plugins/Waffle.ts +0 -0
  42. package/src/series/seriesUtils.ts +51 -0
  43. package/src/series/types.ts +53 -0
  44. package/src/tree/index.ts +0 -0
  45. package/src/tree/plugins/TreeMap.ts +0 -0
  46. package/src/utils/commonUtils.ts +22 -0
  47. package/src/utils/d3Graphics.ts +125 -0
  48. package/src/utils/d3Utils.ts +73 -0
  49. package/src/utils/observables.ts +14 -0
  50. package/src/utils/orbchartsUtils.ts +70 -0
  51. package/tsconfig.json +14 -0
  52. package/vite.config.js +45 -0
@@ -0,0 +1,356 @@
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ switchMap,
5
+ distinctUntilChanged,
6
+ first,
7
+ map,
8
+ takeUntil,
9
+ Observable,
10
+ Subject } from 'rxjs'
11
+ import {
12
+ defineGridPlugin } from '@orbcharts/core'
13
+ import { createAxisLinearScale } from '@orbcharts/core'
14
+ import type {
15
+ DataFormatterGrid,
16
+ ChartParams,
17
+ TransformData } from '@orbcharts/core'
18
+ import type { ValueAxisParams } from '../types'
19
+ import { DEFAULT_VALUE_AXIS_PLUGIN_PARAMS } from '../defaults'
20
+ import { parseTickFormatValue } from '../../utils/d3Utils'
21
+ import { getColor, getMinAndMaxValue, getClassName, getUniID } from '../../utils/orbchartsUtils'
22
+
23
+ interface TextAlign {
24
+ textAnchor: "start" | "middle" | "end"
25
+ dominantBaseline: "middle" | "auto" | "hanging"
26
+ }
27
+
28
+ const pluginName = 'ValueAxis'
29
+ const gClassName = getClassName(pluginName, 'g')
30
+ const textClassName = getClassName(pluginName, 'text')
31
+ const defaultTickSize = 6
32
+
33
+ function renderLinearAxis ({ selection, fullParams, tickTextAlign, axisLabelAlign, gridAxesSize, fullDataFormatter, fullChartParams, valueScale, contentTransform, minAndMax }: {
34
+ selection: d3.Selection<SVGGElement, any, any, any>,
35
+ fullParams: ValueAxisParams
36
+ tickTextAlign: TextAlign
37
+ axisLabelAlign: TextAlign
38
+ gridAxesSize: { width: number, height: number }
39
+ fullDataFormatter: DataFormatterGrid,
40
+ fullChartParams: ChartParams
41
+ valueScale: d3.ScaleLinear<number, number>
42
+ contentTransform: string,
43
+ minAndMax: [number, number]
44
+ }) {
45
+
46
+ const yAxisSelection = selection
47
+ .selectAll<SVGGElement, ValueAxisParams>(`g.${gClassName}`)
48
+ .data([fullParams])
49
+ .join('g')
50
+ .classed(gClassName, true)
51
+
52
+ const axisLabelSelection = selection
53
+ .selectAll<SVGGElement, ValueAxisParams>(`g.${textClassName}`)
54
+ .data([fullParams])
55
+ .join('g')
56
+ .classed(textClassName, true)
57
+ .each((d, i, g) => {
58
+ const text = d3.select(g[i])
59
+ .selectAll<SVGTextElement, ValueAxisParams>(`text`)
60
+ .data([d])
61
+ .join(
62
+ enter => {
63
+ return enter
64
+ .append('text')
65
+ .style('font-weight', 'bold')
66
+ },
67
+ update => update,
68
+ exit => exit.remove()
69
+ )
70
+ .attr('text-anchor', axisLabelAlign.textAnchor)
71
+ .attr('dominant-baseline', axisLabelAlign.dominantBaseline)
72
+ .style('font-size', `${fullChartParams.styles.textSize}px`)
73
+ .style('fill', getColor(fullParams.labelColorType, fullChartParams))
74
+ .style('transform', contentTransform)
75
+ .text(d => fullDataFormatter.valueAxis.label)
76
+ })
77
+ .attr('transform', d => `translate(${- d.tickPadding + fullParams.labelOffset[0]}, ${gridAxesSize.height + d.tickPadding + fullParams.labelOffset[1]})`)
78
+
79
+ const valueLength = minAndMax[1] - minAndMax[0]
80
+
81
+ // 設定Y軸刻度
82
+ const yAxis = d3.axisLeft(valueScale)
83
+ .scale(valueScale)
84
+ .ticks(valueLength > fullParams.ticks
85
+ ? fullParams.ticks
86
+ : ((minAndMax[0] === 0 && minAndMax[1] === 0)
87
+ ? 1
88
+ : Math.ceil(valueLength))) // 刻度分段數量
89
+ .tickFormat(d => parseTickFormatValue(d, fullParams.tickFormat))
90
+ .tickSize(fullParams.tickFullLine == true
91
+ ? -gridAxesSize.width
92
+ : defaultTickSize)
93
+ .tickPadding(fullParams.tickPadding)
94
+
95
+ const yAxisEl = yAxisSelection
96
+ .transition()
97
+ .duration(100)
98
+ .call(yAxis)
99
+
100
+ yAxisEl.selectAll('line')
101
+ .style('fill', 'none')
102
+ .style('stroke', fullParams.tickLineVisible == true ? getColor(fullParams.tickColorType, fullChartParams) : 'none')
103
+ .style('stroke-dasharray', fullParams.tickFullLineDasharray)
104
+ .attr('pointer-events', 'none')
105
+
106
+ yAxisEl.selectAll('path')
107
+ .style('fill', 'none')
108
+ // .style('stroke', this.fullParams.axisLineColor!)
109
+ .style('stroke', fullParams.axisLineVisible == true ? getColor(fullParams.axisLineColorType, fullChartParams) : 'none')
110
+ .style('shape-rendering', 'crispEdges')
111
+
112
+ // const yText = yAxisEl.selectAll('text')
113
+ const yText = yAxisSelection.selectAll('text')
114
+ .style('font-family', 'sans-serif')
115
+ .style('font-size', `${fullChartParams.styles.textSize}px`)
116
+ .style('color', getColor(fullParams.tickTextColorType, fullChartParams))
117
+ .attr('text-anchor', tickTextAlign.textAnchor)
118
+ .attr('dominant-baseline', tickTextAlign.dominantBaseline)
119
+ .attr('transform-origin', `-${fullParams.tickPadding + defaultTickSize} 0`)
120
+ yText.style('transform', contentTransform)
121
+
122
+ return yAxisSelection
123
+ }
124
+
125
+
126
+ export const ValueAxis = defineGridPlugin(pluginName, DEFAULT_VALUE_AXIS_PLUGIN_PARAMS)(({ selection, name, observer, subject }) => {
127
+
128
+ const destroy$ = new Subject()
129
+
130
+ // const axisGUpdate = selection
131
+ // .selectAll('g')
132
+ // .data()
133
+
134
+ const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection.append('g')
135
+ // let graphicSelection: d3.Selection<SVGGElement, any, any, any> | undefined
136
+ // let pathSelection: d3.Selection<SVGPathElement, ComputedDatumGrid[], any, any> | undefined
137
+ // .style('transform', 'translate(0px, 0px) scale(1)')
138
+
139
+ observer.gridAxesTransform$
140
+ .pipe(
141
+ takeUntil(destroy$),
142
+ map(d => d.value),
143
+ distinctUntilChanged()
144
+ ).subscribe(d => {
145
+ axisSelection
146
+ .style('transform', d)
147
+ .attr('opacity', 0)
148
+ .transition()
149
+ .attr('opacity', 1)
150
+ })
151
+
152
+ // const gridAxesSize$ = gridAxisSizeObservable({
153
+ // fullDataFormatter$,
154
+ // layout$
155
+ // })
156
+
157
+ // const contentTransform$: Observable<string> = new Observable(subscriber => {
158
+ // combineLatest({
159
+ // fullParams: fullParams$,
160
+ // layout: layout$
161
+ // }).pipe(
162
+ // takeUntil(destroy$),
163
+ // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
164
+ // switchMap(async (d) => d),
165
+ // ).subscribe(data => {
166
+
167
+ // const transformData = Object.assign({}, data.layout.content.axesTransformData)
168
+
169
+ // const value = getAxesTransformValue({
170
+ // translate: [0, 0],
171
+ // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
172
+ // rotate: transformData.rotate * -1 + data.fullParams.tickTextRotate,
173
+ // rotateX: transformData.rotateX * -1,
174
+ // rotateY: transformData.rotateY * -1
175
+ // })
176
+
177
+ // subscriber.next(value)
178
+ // })
179
+ // })
180
+ // const oppositeTransform$: Observable<TransformData> = observer.gridAxesTransform$.pipe(
181
+ // takeUntil(destroy$),
182
+ // map(d => {
183
+ // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
184
+ // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
185
+ // const rotate = d.rotate * -1
186
+ // const rotateX = d.rotateX * -1
187
+ // const rotateY = d.rotateY * -1
188
+ // return {
189
+ // translate,
190
+ // scale,
191
+ // rotate,
192
+ // rotateX,
193
+ // rotateY,
194
+ // value: ''
195
+ // }
196
+ // }),
197
+ // )
198
+ const contentTransform$ = combineLatest({
199
+ fullParams: observer.fullParams$,
200
+ gridAxesOppositeTransform: observer.gridAxesOppositeTransform$
201
+ }).pipe(
202
+ takeUntil(destroy$),
203
+ switchMap(async data => {
204
+ const rotate = data.gridAxesOppositeTransform.rotate + data.fullParams.tickTextRotate
205
+ return `translate(${data.gridAxesOppositeTransform.translate[0]}px, ${data.gridAxesOppositeTransform.translate[1]}px) rotate(${rotate}deg) rotateX(${data.gridAxesOppositeTransform.rotateX}deg) rotateY(${data.gridAxesOppositeTransform.rotateY}deg)`
206
+ }),
207
+ distinctUntilChanged()
208
+ )
209
+
210
+ const minAndMax$: Observable<[number, number]> = new Observable(subscriber => {
211
+ combineLatest({
212
+ fullDataFormatter: observer.fullDataFormatter$,
213
+ gridAxesSize: observer.gridAxesSize$,
214
+ computedData: observer.computedData$
215
+ }).pipe(
216
+ takeUntil(destroy$),
217
+ // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
218
+ switchMap(async (d) => d),
219
+ ).subscribe(data => {
220
+ const groupMin = 0
221
+ const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
222
+ const groupScaleDomainMin = data.fullDataFormatter.groupAxis.scaleDomain[0] === 'auto'
223
+ ? groupMin - data.fullDataFormatter.groupAxis.scalePadding
224
+ : data.fullDataFormatter.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.groupAxis.scalePadding
225
+ const groupScaleDomainMax = data.fullDataFormatter.groupAxis.scaleDomain[1] === 'auto'
226
+ ? groupMax + data.fullDataFormatter.groupAxis.scalePadding
227
+ : data.fullDataFormatter.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.groupAxis.scalePadding
228
+
229
+ const filteredData = data.computedData.map((d, i) => {
230
+ return d.filter((_d, _i) => {
231
+ return _i >= groupScaleDomainMin && _i <= groupScaleDomainMax
232
+ })
233
+ })
234
+
235
+ const filteredMinAndMax = getMinAndMaxValue(filteredData.flat())
236
+
237
+ subscriber.next(filteredMinAndMax)
238
+ })
239
+ })
240
+
241
+ const valueScale$: Observable<d3.ScaleLinear<number, number>> = new Observable(subscriber => {
242
+ combineLatest({
243
+ fullDataFormatter: observer.fullDataFormatter$,
244
+ gridAxesSize: observer.gridAxesSize$,
245
+ minAndMax: minAndMax$
246
+ }).pipe(
247
+ takeUntil(destroy$),
248
+ // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
249
+ switchMap(async (d) => d),
250
+ ).subscribe(data => {
251
+
252
+ const valueScale: d3.ScaleLinear<number, number> = createAxisLinearScale({
253
+ maxValue: data.minAndMax[1],
254
+ minValue: data.minAndMax[0],
255
+ axisWidth: data.gridAxesSize.height,
256
+ scaleDomain: data.fullDataFormatter.valueAxis.scaleDomain,
257
+ scaleRange: data.fullDataFormatter.valueAxis.scaleRange
258
+ })
259
+
260
+ subscriber.next(valueScale)
261
+ })
262
+ })
263
+
264
+ const tickTextAlign$: Observable<TextAlign> = observer.fullDataFormatter$.pipe(
265
+ takeUntil(destroy$),
266
+ map(d => {
267
+ let textAnchor: 'start' | 'middle' | 'end' = 'start'
268
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
269
+
270
+ if (d.valueAxis.position === 'left') {
271
+ textAnchor = 'end'
272
+ dominantBaseline = 'middle'
273
+ } else if (d.valueAxis.position === 'right') {
274
+ textAnchor = 'start'
275
+ dominantBaseline = 'middle'
276
+ } else if (d.valueAxis.position === 'bottom') {
277
+ textAnchor = 'middle'
278
+ dominantBaseline = 'hanging'
279
+ } else if (d.valueAxis.position === 'top') {
280
+ textAnchor = 'middle'
281
+ dominantBaseline = 'auto'
282
+ }
283
+ return {
284
+ textAnchor,
285
+ dominantBaseline
286
+ }
287
+ })
288
+ )
289
+
290
+ const axisLabelAlign$: Observable<TextAlign> = observer.fullDataFormatter$.pipe(
291
+ takeUntil(destroy$),
292
+ map(d => {
293
+ let textAnchor: 'start' | 'middle' | 'end' = 'start'
294
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
295
+
296
+ if (d.groupAxis.position === 'bottom') {
297
+ dominantBaseline = 'auto'
298
+ } else if (d.groupAxis.position === 'top') {
299
+ dominantBaseline = 'hanging'
300
+ } else if (d.groupAxis.position === 'left') {
301
+ textAnchor = 'start'
302
+ } else if (d.groupAxis.position === 'right') {
303
+ textAnchor = 'end'
304
+ }
305
+ if (d.valueAxis.position === 'left') {
306
+ textAnchor = 'end'
307
+ } else if (d.valueAxis.position === 'right') {
308
+ textAnchor = 'start'
309
+ } else if (d.valueAxis.position === 'bottom') {
310
+ dominantBaseline = 'hanging'
311
+ } else if (d.valueAxis.position === 'top') {
312
+ dominantBaseline = 'auto'
313
+ }
314
+ return {
315
+ textAnchor,
316
+ dominantBaseline
317
+ }
318
+ })
319
+ )
320
+
321
+
322
+ combineLatest({
323
+ fullParams: observer.fullParams$,
324
+ tickTextAlign: tickTextAlign$,
325
+ axisLabelAlign: axisLabelAlign$,
326
+ computedData: observer.computedData$,
327
+ gridAxesSize: observer.gridAxesSize$,
328
+ fullDataFormatter: observer.fullDataFormatter$,
329
+ fullChartParams: observer.fullChartParams$,
330
+ valueScale: valueScale$,
331
+ contentTransform: contentTransform$,
332
+ minAndMax: minAndMax$
333
+ }).pipe(
334
+ takeUntil(destroy$),
335
+ // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
336
+ switchMap(async (d) => d),
337
+ ).subscribe(data => {
338
+
339
+ renderLinearAxis({
340
+ selection: axisSelection,
341
+ fullParams: data.fullParams,
342
+ tickTextAlign: data.tickTextAlign,
343
+ axisLabelAlign: data.axisLabelAlign,
344
+ gridAxesSize: data.gridAxesSize,
345
+ fullDataFormatter: data.fullDataFormatter,
346
+ fullChartParams: data.fullChartParams,
347
+ valueScale: data.valueScale,
348
+ contentTransform: data.contentTransform,
349
+ minAndMax: data.minAndMax
350
+ })
351
+ })
352
+
353
+ return () => {
354
+ destroy$.next(undefined)
355
+ }
356
+ })