@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,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
+ })