@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,363 @@
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 { createAxisPointScale } from '@orbcharts/core'
14
+ import type {
15
+ DataFormatterGrid,
16
+ ChartParams,
17
+ TransformData } from '@orbcharts/core'
18
+ import type { GroupingAxisParams } from '../types'
19
+ import { DEFAULT_GROUPING_AXIS_PLUGIN_PARAMS } from '../defaults'
20
+ import { parseTickFormatValue } from '../../utils/d3Utils'
21
+ import { getColor, getClassName } from '../../utils/orbchartsUtils'
22
+
23
+ interface TextAlign {
24
+ textAnchor: "start" | "middle" | "end"
25
+ dominantBaseline: "middle" | "auto" | "hanging"
26
+ }
27
+
28
+ const pluginName = 'GroupAxis'
29
+ const xAxisClassName = getClassName(pluginName, 'xAxis')
30
+ const groupingLabelClassName = getClassName(pluginName, 'groupingLabel')
31
+ const defaultTickSize = 6
32
+
33
+ function renderPointAxis ({ selection, params, tickTextAlign, axisLabelAlign, gridAxesSize, fullDataFormatter, chartParams, groupScale, contentTransform }: {
34
+ selection: d3.Selection<SVGGElement, any, any, any>,
35
+ params: GroupingAxisParams
36
+ tickTextAlign: TextAlign
37
+ axisLabelAlign: TextAlign
38
+ gridAxesSize: { width: number, height: number }
39
+ fullDataFormatter: DataFormatterGrid,
40
+ chartParams: ChartParams
41
+ groupScale: d3.ScalePoint<string>
42
+ contentTransform: string
43
+ // tickTextFormatter: string | ((label: any) => string)
44
+ }) {
45
+
46
+ const xAxisSelection = selection
47
+ .selectAll<SVGGElement, GroupingAxisParams>(`g.${xAxisClassName}`)
48
+ .data([params])
49
+ .join('g')
50
+ .classed(xAxisClassName, true)
51
+
52
+ const axisLabelSelection = selection
53
+ .selectAll<SVGGElement, GroupingAxisParams>(`g.${groupingLabelClassName}`)
54
+ .data([params])
55
+ .join('g')
56
+ .classed(groupingLabelClassName, true)
57
+ .each((d, i, g) => {
58
+ const text = d3.select(g[i])
59
+ .selectAll<SVGTextElement, GroupingAxisParams>('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', `${chartParams.styles.textSize}px`)
73
+ .style('fill', getColor(params.labelColorType, chartParams))
74
+ .style('transform', contentTransform)
75
+ .text(d => fullDataFormatter.groupAxis.label)
76
+ })
77
+ .attr('transform', d => `translate(${gridAxesSize.width + d.tickPadding + params.labelOffset[0]}, ${- d.tickPadding - defaultTickSize - params.labelOffset[1]})`)
78
+
79
+
80
+ // 設定X軸刻度
81
+ // const xAxis = d3.axisBottom(groupScale)
82
+ const xAxis = d3.axisTop(groupScale)
83
+ .scale(groupScale)
84
+ .tickSize(params.tickFullLine == true
85
+ ? -gridAxesSize.height
86
+ : defaultTickSize)
87
+ .tickSizeOuter(0)
88
+ .tickFormat(d => parseTickFormatValue(d, params.tickFormat))
89
+ .tickPadding(params.tickPadding)
90
+
91
+ const xAxisEl = xAxisSelection
92
+ .transition()
93
+ .duration(100)
94
+ .call(xAxis)
95
+ // .attr('text-anchor', () => params.tickTextRotate !== false ? 'end' : 'middle')
96
+ // .attr('text-anchor', () => 'middle')
97
+
98
+ xAxisEl.selectAll('line')
99
+ .style('fill', 'none')
100
+ .style('stroke', params.tickLineVisible == true ? getColor(params.tickColorType, chartParams) : 'none')
101
+ .style('stroke-dasharray', params.tickFullLineDasharray)
102
+ .attr('pointer-events', 'none')
103
+
104
+ xAxisEl.selectAll('path')
105
+ .style('fill', 'none')
106
+ .style('stroke', params.axisLineVisible == true ? getColor(params.axisLineColorType, chartParams) : 'none')
107
+ .style('shape-rendering', 'crispEdges')
108
+
109
+ // const xText = xAxisEl.selectAll('text')
110
+ const xText = xAxisSelection.selectAll('text')
111
+ .style('font-family', 'sans-serif')
112
+ .style('font-size', `${chartParams.styles.textSize}px`)
113
+ // .style('font-weight', 'bold')
114
+ .style('color', getColor(params.tickTextColorType, chartParams))
115
+ .attr('text-anchor', tickTextAlign.textAnchor)
116
+ .attr('dominant-baseline', tickTextAlign.dominantBaseline)
117
+ .attr('transform-origin', `0 -${params.tickPadding + defaultTickSize}`)
118
+ .style('transform', contentTransform)
119
+ // if (params.textRotate === true) {
120
+ // xText.attr('transform', 'translate(0,0) rotate(-45)')
121
+ // } else if (typeof params.textRotate === 'number') {
122
+ // xText.attr('transform', `translate(0,0) rotate(${params.tickTextRotate})`)
123
+ // }
124
+
125
+ return xAxisSelection
126
+ }
127
+
128
+
129
+ export const GroupAxis = defineGridPlugin(pluginName, DEFAULT_GROUPING_AXIS_PLUGIN_PARAMS)(({ selection, name, observer, subject }) => {
130
+
131
+ const destroy$ = new Subject()
132
+
133
+ // const axisGUpdate = selection
134
+ // .selectAll('g')
135
+ // .data()
136
+
137
+ const axisSelection: d3.Selection<SVGGElement, any, any, any> = selection.append('g')
138
+ // let graphicSelection: d3.Selection<SVGGElement, any, any, any> | undefined
139
+ // let pathSelection: d3.Selection<SVGPathElement, ComputedDatumGrid[], any, any> | undefined
140
+ // .style('transform', 'translate(0px, 0px) scale(1)')
141
+
142
+ observer.gridAxesTransform$
143
+ .pipe(
144
+ takeUntil(destroy$),
145
+ map(d => d.value),
146
+ distinctUntilChanged()
147
+ ).subscribe(d => {
148
+ axisSelection
149
+ .style('transform', d)
150
+ .attr('opacity', 0)
151
+ .transition()
152
+ .attr('opacity', 1)
153
+ })
154
+
155
+
156
+ // const gridAxesSize$ = gridAxisSizeObservable({
157
+ // fullDataFormatter$,
158
+ // observer.layout$
159
+ // })
160
+
161
+
162
+ // const tickTextFormatter$ = fullDataFormatter$
163
+ // .pipe(
164
+ // map(d => {
165
+ // return d.grid.seriesType === 'row' ? d.grid.columnLabelFormat : d.grid.rowLabelFormat
166
+ // })
167
+ // )
168
+
169
+ // const contentTransform$: Observable<string> = new Observable(subscriber => {
170
+ // combineLatest({
171
+ // params: observer.fullParams$,
172
+ // gridAxesTransform: observer.gridAxesTransform$
173
+ // }).pipe(
174
+ // takeUntil(destroy$),
175
+ // // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
176
+ // switchMap(async (d) => d),
177
+ // ).subscribe(data => {
178
+
179
+ // const transformData = Object.assign({}, data.gridAxesTransform)
180
+
181
+ // const value = getAxesTransformValue({
182
+ // translate: [0, 0],
183
+ // scale: [transformData.scale[0] * -1, transformData.scale[1] * -1],
184
+ // rotate: transformData.rotate * -1 + data.params.tickTextRotate,
185
+ // rotateX: transformData.rotateX * -1,
186
+ // rotateY: transformData.rotateY * -1
187
+ // })
188
+
189
+ // subscriber.next(value)
190
+ // })
191
+ // })
192
+ // const oppositeTransform$: Observable<TransformData> = observer.gridAxesTransform$.pipe(
193
+ // takeUntil(destroy$),
194
+ // map(d => {
195
+ // const translate: [number, number] = [d.translate[0] * -1, d.translate[1] * -1]
196
+ // const scale: [number, number] = [d.scale[0] * -1, d.scale[1] * -1]
197
+ // const rotate = d.rotate * -1
198
+ // const rotateX = d.rotateX * -1
199
+ // const rotateY = d.rotateY * -1
200
+ // return {
201
+ // translate,
202
+ // scale,
203
+ // rotate,
204
+ // rotateX,
205
+ // rotateY,
206
+ // value: ''
207
+ // }
208
+ // }),
209
+ // )
210
+ const contentTransform$ = combineLatest({
211
+ fullParams: observer.fullParams$,
212
+ gridAxesOppositeTransform: observer.gridAxesOppositeTransform$
213
+ }).pipe(
214
+ takeUntil(destroy$),
215
+ switchMap(async data => {
216
+ const rotate = data.gridAxesOppositeTransform.rotate + data.fullParams.tickTextRotate
217
+ 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)`
218
+ }),
219
+ distinctUntilChanged()
220
+ )
221
+
222
+ const groupScale$: Observable<d3.ScalePoint<string>> = new Observable(subscriber => {
223
+ combineLatest({
224
+ fullDataFormatter: observer.fullDataFormatter$,
225
+ gridAxesSize: observer.gridAxesSize$,
226
+ computedData: observer.computedData$
227
+ }).pipe(
228
+ takeUntil(destroy$),
229
+ switchMap(async (d) => d),
230
+ ).subscribe(data => {
231
+ const groupMin = 0
232
+ const groupMax = data.computedData[0] ? data.computedData[0].length - 1 : 0
233
+ const groupScaleDomainMin = data.fullDataFormatter.groupAxis.scaleDomain[0] === 'auto'
234
+ ? groupMin - data.fullDataFormatter.groupAxis.scalePadding
235
+ : data.fullDataFormatter.groupAxis.scaleDomain[0] as number - data.fullDataFormatter.groupAxis.scalePadding
236
+ const groupScaleDomainMax = data.fullDataFormatter.groupAxis.scaleDomain[1] === 'auto'
237
+ ? groupMax + data.fullDataFormatter.groupAxis.scalePadding
238
+ : data.fullDataFormatter.groupAxis.scaleDomain[1] as number + data.fullDataFormatter.groupAxis.scalePadding
239
+
240
+ const groupingLength = data.computedData[0]
241
+ ? data.computedData[0].length
242
+ : 0
243
+
244
+ let _labels = data.fullDataFormatter.grid.seriesType === 'row'
245
+ // ? data.fullDataFormatter.grid.columnLabels
246
+ // : data.fullDataFormatter.grid.rowLabels
247
+ ? (data.computedData[0] ?? []).map(d => d.groupLabel)
248
+ : data.computedData.map(d => d[0].groupLabel)
249
+
250
+ const axisLabels = new Array(groupingLength).fill(0)
251
+ .map((d, i) => {
252
+ return _labels[i] != null
253
+ ? _labels[i]
254
+ : String(i) // 沒有label則用序列號填充
255
+ })
256
+ .filter((d, i) => {
257
+ return i >= groupScaleDomainMin && i <= groupScaleDomainMax
258
+ })
259
+
260
+
261
+ const padding = data.fullDataFormatter.groupAxis.scalePadding
262
+
263
+ const groupScale = createAxisPointScale({
264
+ axisLabels,
265
+ axisWidth: data.gridAxesSize.width,
266
+ padding
267
+ })
268
+
269
+ subscriber.next(groupScale)
270
+ })
271
+ })
272
+
273
+ const tickTextAlign$: Observable<TextAlign> = observer.fullDataFormatter$.pipe(
274
+ takeUntil(destroy$),
275
+ map(d => {
276
+ let textAnchor: 'start' | 'middle' | 'end' = 'middle'
277
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
278
+
279
+ if (d.groupAxis.position === 'bottom') {
280
+ textAnchor = 'middle'
281
+ dominantBaseline = 'hanging'
282
+ } else if (d.groupAxis.position === 'top') {
283
+ textAnchor = 'middle'
284
+ dominantBaseline = 'auto'
285
+ } else if (d.groupAxis.position === 'left') {
286
+ textAnchor = 'end'
287
+ dominantBaseline = 'middle'
288
+ } else if (d.groupAxis.position === 'right') {
289
+ textAnchor = 'start'
290
+ dominantBaseline = 'middle'
291
+ }
292
+ return {
293
+ textAnchor,
294
+ dominantBaseline
295
+ }
296
+ })
297
+ )
298
+
299
+ const axisLabelAlign$: Observable<TextAlign> = observer.fullDataFormatter$.pipe(
300
+ takeUntil(destroy$),
301
+ map(d => {
302
+ let textAnchor: 'start' | 'middle' | 'end' = 'start'
303
+ let dominantBaseline: 'auto' | 'middle' | 'hanging' = 'hanging'
304
+
305
+ if (d.groupAxis.position === 'bottom') {
306
+ dominantBaseline = 'hanging'
307
+ } else if (d.groupAxis.position === 'top') {
308
+ dominantBaseline = 'auto'
309
+ } else if (d.groupAxis.position === 'left') {
310
+ textAnchor = 'end'
311
+ } else if (d.groupAxis.position === 'right') {
312
+ textAnchor = 'start'
313
+ }
314
+ if (d.valueAxis.position === 'left') {
315
+ textAnchor = 'start'
316
+ } else if (d.valueAxis.position === 'right') {
317
+ textAnchor = 'end'
318
+ } else if (d.valueAxis.position === 'bottom') {
319
+ dominantBaseline = 'auto'
320
+ } else if (d.valueAxis.position === 'top') {
321
+ dominantBaseline = 'hanging'
322
+ }
323
+ return {
324
+ textAnchor,
325
+ dominantBaseline
326
+ }
327
+ })
328
+ )
329
+
330
+ combineLatest({
331
+ params: observer.fullParams$,
332
+ tickTextAlign: tickTextAlign$,
333
+ axisLabelAlign: axisLabelAlign$,
334
+ gridAxesSize: observer.gridAxesSize$,
335
+ fullDataFormatter: observer.fullDataFormatter$,
336
+ chartParams: observer.fullChartParams$,
337
+ groupScale: groupScale$,
338
+ contentTransform: contentTransform$,
339
+ // tickTextFormatter: tickTextFormatter$
340
+ }).pipe(
341
+ takeUntil(destroy$),
342
+ // 轉換後會退訂前一個未完成的訂閱事件,因此可以取到「同時間」最後一次的訂閱事件
343
+ switchMap(async (d) => d),
344
+ ).subscribe(data => {
345
+ // console.log('data.fullDataFormatter.groupAxis', data.fullDataFormatter.groupAxis)
346
+ renderPointAxis({
347
+ selection: axisSelection,
348
+ params: data.params,
349
+ tickTextAlign: data.tickTextAlign,
350
+ axisLabelAlign: data.axisLabelAlign,
351
+ gridAxesSize: data.gridAxesSize,
352
+ fullDataFormatter: data.fullDataFormatter,
353
+ chartParams: data.chartParams,
354
+ groupScale: data.groupScale,
355
+ contentTransform: data.contentTransform,
356
+ // tickTextFormatter: data.tickTextFormatter
357
+ })
358
+ })
359
+
360
+ return () => {
361
+ destroy$.next(undefined)
362
+ }
363
+ })