@orbcharts/plugins-basic 3.0.0-alpha.24

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 (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
+ })