@orbcharts/plugin-basic 4.0.0-pre-alpha.0

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 (208) hide show
  1. package/LICENSE +201 -0
  2. package/dist/orbcharts-plugin-basic.es.js +25335 -0
  3. package/dist/orbcharts-plugin-basic.umd.js +341 -0
  4. package/dist/plugin-basic/src/baseLayers/BaseBars.d.ts +38 -0
  5. package/dist/plugin-basic/src/baseLayers/BaseBarsTriangle.d.ts +37 -0
  6. package/dist/plugin-basic/src/baseLayers/BaseCategoryAxis.d.ts +42 -0
  7. package/dist/plugin-basic/src/baseLayers/BaseDots.d.ts +38 -0
  8. package/dist/plugin-basic/src/baseLayers/BaseLegend.d.ts +31 -0
  9. package/dist/plugin-basic/src/baseLayers/BaseLineAreas.d.ts +36 -0
  10. package/dist/plugin-basic/src/baseLayers/BaseLines.d.ts +36 -0
  11. package/dist/plugin-basic/src/baseLayers/BaseStackedBars.d.ts +41 -0
  12. package/dist/plugin-basic/src/baseLayers/BaseTooltip.d.ts +47 -0
  13. package/dist/plugin-basic/src/baseLayers/BaseValueAxis.d.ts +38 -0
  14. package/dist/plugin-basic/src/baseLayers/BaseXAxis.d.ts +25 -0
  15. package/dist/plugin-basic/src/baseLayers/BaseXZoom.d.ts +22 -0
  16. package/dist/plugin-basic/src/baseLayers/BaseYAxis.d.ts +23 -0
  17. package/dist/plugin-basic/src/baseLayers/types.d.ts +171 -0
  18. package/dist/plugin-basic/src/const/layerIndex.d.ts +10 -0
  19. package/dist/plugin-basic/src/const/sharedPluginParams.d.ts +6 -0
  20. package/dist/plugin-basic/src/index.d.ts +2 -0
  21. package/dist/plugin-basic/src/plugins/CompositionPlot/CompositionPlot.d.ts +22 -0
  22. package/dist/plugin-basic/src/plugins/CompositionPlot/contextObservables.d.ts +40 -0
  23. package/dist/plugin-basic/src/plugins/CompositionPlot/defaults.d.ts +10 -0
  24. package/dist/plugin-basic/src/plugins/CompositionPlot/index.d.ts +3 -0
  25. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/Bubbles.d.ts +16 -0
  26. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/Indicator.d.ts +0 -0
  27. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/Pie.d.ts +16 -0
  28. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/PieEventTexts.d.ts +16 -0
  29. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/PieLabels.d.ts +16 -0
  30. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/Rose.d.ts +16 -0
  31. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/RoseLabels.d.ts +16 -0
  32. package/dist/plugin-basic/src/plugins/CompositionPlot/layers/Waffle.d.ts +0 -0
  33. package/dist/plugin-basic/src/plugins/CompositionPlot/types.d.ts +110 -0
  34. package/dist/plugin-basic/src/plugins/CompositionPlot/utils.d.ts +19 -0
  35. package/dist/plugin-basic/src/plugins/HierarchyPlot/HierarchyPlot.d.ts +22 -0
  36. package/dist/plugin-basic/src/plugins/HierarchyPlot/contextObservables.d.ts +16 -0
  37. package/dist/plugin-basic/src/plugins/HierarchyPlot/defaults.d.ts +4 -0
  38. package/dist/plugin-basic/src/plugins/HierarchyPlot/index.d.ts +3 -0
  39. package/dist/plugin-basic/src/plugins/HierarchyPlot/layers/TreeMap.d.ts +16 -0
  40. package/dist/plugin-basic/src/plugins/HierarchyPlot/types.d.ts +29 -0
  41. package/dist/plugin-basic/src/plugins/Legend/Legend.d.ts +22 -0
  42. package/dist/plugin-basic/src/plugins/Legend/contextObservables.d.ts +9 -0
  43. package/dist/plugin-basic/src/plugins/Legend/defaults.d.ts +4 -0
  44. package/dist/plugin-basic/src/plugins/Legend/index.d.ts +3 -0
  45. package/dist/plugin-basic/src/plugins/Legend/layers/Legend.d.ts +16 -0
  46. package/dist/plugin-basic/src/plugins/Legend/types.d.ts +31 -0
  47. package/dist/plugin-basic/src/plugins/Legend/utils.d.ts +19 -0
  48. package/dist/plugin-basic/src/plugins/NetworkPlot/NetworkPlot.d.ts +22 -0
  49. package/dist/plugin-basic/src/plugins/NetworkPlot/contextObservables.d.ts +19 -0
  50. package/dist/plugin-basic/src/plugins/NetworkPlot/defaults.d.ts +5 -0
  51. package/dist/plugin-basic/src/plugins/NetworkPlot/index.d.ts +3 -0
  52. package/dist/plugin-basic/src/plugins/NetworkPlot/layers/ForceDirected.d.ts +16 -0
  53. package/dist/plugin-basic/src/plugins/NetworkPlot/layers/ForceDirectedBubbles.d.ts +16 -0
  54. package/dist/plugin-basic/src/plugins/NetworkPlot/types.d.ts +117 -0
  55. package/dist/plugin-basic/src/plugins/ScatterPlot/ScatterPlot.d.ts +22 -0
  56. package/dist/plugin-basic/src/plugins/ScatterPlot/contextObservables.d.ts +140 -0
  57. package/dist/plugin-basic/src/plugins/ScatterPlot/defaults.d.ts +8 -0
  58. package/dist/plugin-basic/src/plugins/ScatterPlot/index.d.ts +3 -0
  59. package/dist/plugin-basic/src/plugins/ScatterPlot/layers/Scatter.d.ts +16 -0
  60. package/dist/plugin-basic/src/plugins/ScatterPlot/layers/ScatterBubbles.d.ts +16 -0
  61. package/dist/plugin-basic/src/plugins/ScatterPlot/layers/XYAux.d.ts +16 -0
  62. package/dist/plugin-basic/src/plugins/ScatterPlot/layers/XYAxes.d.ts +16 -0
  63. package/dist/plugin-basic/src/plugins/ScatterPlot/layers/XZoom.d.ts +16 -0
  64. package/dist/plugin-basic/src/plugins/ScatterPlot/types.d.ts +146 -0
  65. package/dist/plugin-basic/src/plugins/SeriesPlot/SeriesPlot.d.ts +22 -0
  66. package/dist/plugin-basic/src/plugins/SeriesPlot/contextObservables.d.ts +77 -0
  67. package/dist/plugin-basic/src/plugins/SeriesPlot/defaults.d.ts +15 -0
  68. package/dist/plugin-basic/src/plugins/SeriesPlot/index.d.ts +3 -0
  69. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/Bars.d.ts +16 -0
  70. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/BarsPN.d.ts +16 -0
  71. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/BarsTriangle.d.ts +16 -0
  72. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/CategoryAux.d.ts +16 -0
  73. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/CategoryAxis.d.ts +16 -0
  74. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/CategoryZoom.d.ts +16 -0
  75. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/Dots.d.ts +16 -0
  76. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/LineAreas.d.ts +16 -0
  77. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/Lines.d.ts +16 -0
  78. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/StackedBars.d.ts +16 -0
  79. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/StackedValueAxis.d.ts +16 -0
  80. package/dist/plugin-basic/src/plugins/SeriesPlot/layers/ValueAxis.d.ts +16 -0
  81. package/dist/plugin-basic/src/plugins/SeriesPlot/types.d.ts +140 -0
  82. package/dist/plugin-basic/src/plugins/Tooltip/Tooltip.d.ts +22 -0
  83. package/dist/plugin-basic/src/plugins/Tooltip/contextObservables.d.ts +9 -0
  84. package/dist/plugin-basic/src/plugins/Tooltip/defaults.d.ts +4 -0
  85. package/dist/plugin-basic/src/plugins/Tooltip/index.d.ts +3 -0
  86. package/dist/plugin-basic/src/plugins/Tooltip/layers/Tooltip.d.ts +16 -0
  87. package/dist/plugin-basic/src/plugins/Tooltip/types.d.ts +35 -0
  88. package/dist/plugin-basic/src/plugins/Tooltip/utils.d.ts +19 -0
  89. package/dist/plugin-basic/src/plugins/index.d.ts +7 -0
  90. package/dist/plugin-basic/src/types/BaseLayer.d.ts +3 -0
  91. package/dist/plugin-basic/src/types/Common.d.ts +14 -0
  92. package/dist/plugin-basic/src/types/ComputedData.d.ts +27 -0
  93. package/dist/plugin-basic/src/types/PluginParams.d.ts +66 -0
  94. package/dist/plugin-basic/src/types/index.d.ts +3 -0
  95. package/dist/plugin-basic/src/utils/commonUtils.d.ts +3 -0
  96. package/dist/plugin-basic/src/utils/d3Graphics.d.ts +24 -0
  97. package/dist/plugin-basic/src/utils/d3Scale.d.ts +28 -0
  98. package/dist/plugin-basic/src/utils/d3Utils.d.ts +14 -0
  99. package/dist/plugin-basic/src/utils/graphObservables.d.ts +0 -0
  100. package/dist/plugin-basic/src/utils/gridObservables.d.ts +51 -0
  101. package/dist/plugin-basic/src/utils/multivariateObservables.d.ts +74 -0
  102. package/dist/plugin-basic/src/utils/observables.d.ts +34 -0
  103. package/dist/plugin-basic/src/utils/orbchartsUtils.d.ts +26 -0
  104. package/dist/plugin-basic/src/utils/seriesObservables.d.ts +22 -0
  105. package/dist/plugin-basic/vite.config.d.ts +2 -0
  106. package/dist/src/index.d.ts +1 -0
  107. package/package.json +62 -0
  108. package/src/baseLayers/BaseBars.ts +783 -0
  109. package/src/baseLayers/BaseBarsTriangle.ts +692 -0
  110. package/src/baseLayers/BaseCategoryAxis.ts +708 -0
  111. package/src/baseLayers/BaseDots.ts +495 -0
  112. package/src/baseLayers/BaseLegend.ts +684 -0
  113. package/src/baseLayers/BaseLineAreas.ts +644 -0
  114. package/src/baseLayers/BaseLines.ts +721 -0
  115. package/src/baseLayers/BaseStackedBars.ts +818 -0
  116. package/src/baseLayers/BaseTooltip.ts +435 -0
  117. package/src/baseLayers/BaseValueAxis.ts +612 -0
  118. package/src/baseLayers/BaseXAxis.ts +412 -0
  119. package/src/baseLayers/BaseXZoom.ts +250 -0
  120. package/src/baseLayers/BaseYAxis.ts +371 -0
  121. package/src/baseLayers/types.ts +174 -0
  122. package/src/const/layerIndex.ts +36 -0
  123. package/src/const/sharedPluginParams.ts +29 -0
  124. package/src/index.ts +3 -0
  125. package/src/plugins/CompositionPlot/CompositionPlot.ts +308 -0
  126. package/src/plugins/CompositionPlot/contextObservables.ts +251 -0
  127. package/src/plugins/CompositionPlot/defaults.ts +162 -0
  128. package/src/plugins/CompositionPlot/index.ts +3 -0
  129. package/src/plugins/CompositionPlot/layers/Bubbles.ts +808 -0
  130. package/src/plugins/CompositionPlot/layers/Indicator.ts +0 -0
  131. package/src/plugins/CompositionPlot/layers/Pie.ts +776 -0
  132. package/src/plugins/CompositionPlot/layers/PieEventTexts.ts +326 -0
  133. package/src/plugins/CompositionPlot/layers/PieLabels.ts +651 -0
  134. package/src/plugins/CompositionPlot/layers/Rose.ts +546 -0
  135. package/src/plugins/CompositionPlot/layers/RoseLabels.ts +616 -0
  136. package/src/plugins/CompositionPlot/layers/Waffle.ts +0 -0
  137. package/src/plugins/CompositionPlot/types.ts +129 -0
  138. package/src/plugins/CompositionPlot/utils.ts +53 -0
  139. package/src/plugins/HierarchyPlot/HierarchyPlot.ts +190 -0
  140. package/src/plugins/HierarchyPlot/contextObservables.ts +136 -0
  141. package/src/plugins/HierarchyPlot/defaults.ts +31 -0
  142. package/src/plugins/HierarchyPlot/index.ts +3 -0
  143. package/src/plugins/HierarchyPlot/layers/TreeMap.ts +371 -0
  144. package/src/plugins/HierarchyPlot/types.ts +36 -0
  145. package/src/plugins/Legend/Legend.ts +151 -0
  146. package/src/plugins/Legend/contextObservables.ts +55 -0
  147. package/src/plugins/Legend/defaults.ts +37 -0
  148. package/src/plugins/Legend/index.ts +3 -0
  149. package/src/plugins/Legend/layers/Legend.ts +114 -0
  150. package/src/plugins/Legend/types.ts +45 -0
  151. package/src/plugins/Legend/utils.ts +53 -0
  152. package/src/plugins/NetworkPlot/NetworkPlot.ts +228 -0
  153. package/src/plugins/NetworkPlot/contextObservables.ts +123 -0
  154. package/src/plugins/NetworkPlot/defaults.ts +147 -0
  155. package/src/plugins/NetworkPlot/index.ts +3 -0
  156. package/src/plugins/NetworkPlot/layers/ForceDirected.ts +1048 -0
  157. package/src/plugins/NetworkPlot/layers/ForceDirectedBubbles.ts +1318 -0
  158. package/src/plugins/NetworkPlot/types.ts +146 -0
  159. package/src/plugins/ScatterPlot/ScatterPlot.ts +569 -0
  160. package/src/plugins/ScatterPlot/contextObservables.ts +901 -0
  161. package/src/plugins/ScatterPlot/defaults.ts +212 -0
  162. package/src/plugins/ScatterPlot/index.ts +3 -0
  163. package/src/plugins/ScatterPlot/layers/Scatter.ts +518 -0
  164. package/src/plugins/ScatterPlot/layers/ScatterBubbles.ts +670 -0
  165. package/src/plugins/ScatterPlot/layers/XYAux.ts +686 -0
  166. package/src/plugins/ScatterPlot/layers/XYAxes.ts +205 -0
  167. package/src/plugins/ScatterPlot/layers/XZoom.ts +48 -0
  168. package/src/plugins/ScatterPlot/types.ts +179 -0
  169. package/src/plugins/SeriesPlot/SeriesPlot.ts +494 -0
  170. package/src/plugins/SeriesPlot/contextObservables.ts +726 -0
  171. package/src/plugins/SeriesPlot/defaults.ts +142 -0
  172. package/src/plugins/SeriesPlot/index.ts +3 -0
  173. package/src/plugins/SeriesPlot/layers/Bars.ts +84 -0
  174. package/src/plugins/SeriesPlot/layers/BarsPN.ts +85 -0
  175. package/src/plugins/SeriesPlot/layers/BarsTriangle.ts +89 -0
  176. package/src/plugins/SeriesPlot/layers/CategoryAux.ts +1131 -0
  177. package/src/plugins/SeriesPlot/layers/CategoryAxis.ts +92 -0
  178. package/src/plugins/SeriesPlot/layers/CategoryZoom.ts +233 -0
  179. package/src/plugins/SeriesPlot/layers/Dots.ts +91 -0
  180. package/src/plugins/SeriesPlot/layers/LineAreas.ts +82 -0
  181. package/src/plugins/SeriesPlot/layers/Lines.ts +75 -0
  182. package/src/plugins/SeriesPlot/layers/StackedBars.ts +85 -0
  183. package/src/plugins/SeriesPlot/layers/StackedValueAxis.ts +111 -0
  184. package/src/plugins/SeriesPlot/layers/ValueAxis.ts +111 -0
  185. package/src/plugins/SeriesPlot/types.ts +201 -0
  186. package/src/plugins/Tooltip/Tooltip.ts +159 -0
  187. package/src/plugins/Tooltip/contextObservables.ts +55 -0
  188. package/src/plugins/Tooltip/defaults.ts +458 -0
  189. package/src/plugins/Tooltip/index.ts +3 -0
  190. package/src/plugins/Tooltip/layers/Tooltip.ts +90 -0
  191. package/src/plugins/Tooltip/types.ts +55 -0
  192. package/src/plugins/Tooltip/utils.ts +53 -0
  193. package/src/plugins/index.ts +8 -0
  194. package/src/types/BaseLayer.ts +3 -0
  195. package/src/types/Common.ts +20 -0
  196. package/src/types/ComputedData.ts +55 -0
  197. package/src/types/PluginParams.ts +81 -0
  198. package/src/types/index.ts +3 -0
  199. package/src/utils/commonUtils.ts +31 -0
  200. package/src/utils/d3Graphics.ts +177 -0
  201. package/src/utils/d3Scale.ts +198 -0
  202. package/src/utils/d3Utils.ts +92 -0
  203. package/src/utils/graphObservables.ts +0 -0
  204. package/src/utils/gridObservables.ts +637 -0
  205. package/src/utils/multivariateObservables.ts +790 -0
  206. package/src/utils/observables.ts +357 -0
  207. package/src/utils/orbchartsUtils.ts +335 -0
  208. package/src/utils/seriesObservables.ts +172 -0
@@ -0,0 +1,684 @@
1
+ import * as d3 from 'd3'
2
+ import {
3
+ combineLatest,
4
+ map,
5
+ switchMap,
6
+ debounceTime,
7
+ takeUntil,
8
+ shareReplay,
9
+ distinctUntilChanged,
10
+ Observable,
11
+ Subject } from 'rxjs'
12
+ import type { BaseLayerFn } from '../types/BaseLayer'
13
+ import type { Layout, GraphicStyles } from '../types/PluginParams'
14
+ import { createClassName, getColor, getColorScheme } from '../utils/orbchartsUtils'
15
+ import { measureTextWidth } from '../utils/commonUtils'
16
+ import { ColorType, Theme } from '@orbcharts/core'
17
+ import { Placement } from '../types'
18
+
19
+ export interface BaseLegendParams {
20
+ placement: Placement;
21
+ padding: number;
22
+ backgroundFill: ColorType;
23
+ backgroundStroke: ColorType;
24
+ textColorType: ColorType;
25
+ gap: number;
26
+ labelList: Array<{
27
+ listRectWidth: number;
28
+ listRectHeight: number;
29
+ listRectRadius: number;
30
+ }>;
31
+ }
32
+
33
+ interface BaseLegendContext {
34
+ rootSelection: d3.Selection<any, unknown, any, unknown>
35
+ pluginName: string
36
+ layerName: string
37
+ legendLabels$: Observable<string[]>
38
+ baseLegendParams$: Observable<BaseLegendParams>
39
+ layout$: Observable<Layout>
40
+ theme$: Observable<Theme>
41
+ fontSizePx$: Observable<number>
42
+ }
43
+
44
+ // 第1層 - 定位的容器
45
+ interface RootPosition {
46
+ x:number
47
+ y:number
48
+ }
49
+
50
+ // 第2層 - 卡片
51
+ interface LegendCard {
52
+ width: number
53
+ height: number
54
+ translateX:number
55
+ translateY:number
56
+ }
57
+
58
+ // 第3層 - 圖例列表
59
+ interface LegendList {
60
+ direction: 'row' | 'column'
61
+ width: number
62
+ height: number
63
+ translateX:number
64
+ translateY:number
65
+ // list: LegendItem[][]
66
+ }
67
+
68
+ // 第4層 - 圖例項目
69
+ interface LegendItem {
70
+ id: string // seriesLabel
71
+ seriesLabel: string
72
+ seriesIndex: number
73
+ lineIndex: number
74
+ itemIndex: number // 行內的item
75
+ text: string
76
+ itemWidth: number
77
+ translateX: number
78
+ translateY: number
79
+ color: string
80
+ listRectWidth: number
81
+ listRectHeight: number
82
+ listRectRadius: number
83
+ }
84
+
85
+ interface ListStyle {
86
+ listRectWidth: number
87
+ listRectHeight: number
88
+ listRectRadius: number
89
+ }
90
+
91
+ const noneLabelText = ' - ' // 沒有label時的預設文字
92
+
93
+ const defaultListStyle: ListStyle = {
94
+ listRectWidth: 14,
95
+ listRectHeight: 14,
96
+ listRectRadius: 0,
97
+ }
98
+
99
+ function getSeriesColor (seriesIndex: number, theme: Theme) {
100
+ const colorIndex = seriesIndex < theme.colors[getColorScheme(theme.colorScheme)].data.length
101
+ ? seriesIndex
102
+ : seriesIndex % theme.colors[getColorScheme(theme.colorScheme)].data.length
103
+ return theme.colors[getColorScheme(theme.colorScheme)].data[colorIndex]
104
+ }
105
+
106
+
107
+ export const createBaseLegend: BaseLayerFn<BaseLegendContext> = ({
108
+ pluginName,
109
+ layerName,
110
+ rootSelection,
111
+ legendLabels$,
112
+ baseLegendParams$,
113
+ layout$,
114
+ theme$,
115
+ fontSizePx$
116
+ }) => {
117
+
118
+ const rootPositionClassName = createClassName(pluginName, layerName, 'root-position')
119
+ const legendCardClassName = createClassName(pluginName, layerName, 'legend-card')
120
+ const legendListClassName = createClassName(pluginName, layerName, 'legend-list')
121
+ const legendItemClassName = createClassName(pluginName, layerName, 'legend-item')
122
+
123
+ const destroy$ = new Subject()
124
+
125
+ // const legendLabels$: Observable<string[]> = SeriesDataMap$.pipe(
126
+ // takeUntil(destroy$),
127
+ // map(data => {
128
+ // return Array.from(data.keys())
129
+ // })
130
+ // )
131
+
132
+ const SeriesLabelColorMap$ = combineLatest({
133
+ legendLabels: legendLabels$,
134
+ theme: theme$
135
+ }).pipe(
136
+ takeUntil(destroy$),
137
+ debounceTime(0),
138
+ map(data => {
139
+ const SeriesLabelColorMap: Map<string, string> = new Map()
140
+ let accIndex = 0
141
+ data.legendLabels.forEach((label, i) => {
142
+ if (!SeriesLabelColorMap.has(label)) {
143
+ const color = getSeriesColor(accIndex, data.theme)
144
+ SeriesLabelColorMap.set(label, color)
145
+ accIndex ++
146
+ }
147
+ })
148
+ return SeriesLabelColorMap
149
+ })
150
+ )
151
+
152
+ // 對應legendLabels是否顯示(只顯示不重覆的)
153
+ const visibleList$ = legendLabels$.pipe(
154
+ takeUntil(destroy$),
155
+ map(data => {
156
+ const AccSeriesLabelSet = new Set()
157
+ let visibleList: boolean[] = []
158
+ data.forEach(d => {
159
+ if (AccSeriesLabelSet.has(d)) {
160
+ visibleList.push(false) // 已存在則不顯示
161
+ } else {
162
+ visibleList.push(true)
163
+ }
164
+ AccSeriesLabelSet.add(d) // 累加已存在的seriesLabel
165
+ })
166
+ return visibleList
167
+ })
168
+ )
169
+
170
+ const position$: Observable<"top" | "bottom" | "left" | "right"> = baseLegendParams$.pipe(
171
+ takeUntil(destroy$),
172
+ map(data => {
173
+ const position: "top" | "bottom" | "left" | "right" = data.placement === 'top' || data.placement === 'top-start' || data.placement === 'top-end'
174
+ ? 'top'
175
+ : data.placement === 'bottom' || data.placement === 'bottom-start' || data.placement === 'bottom-end'
176
+ ? 'bottom'
177
+ : data.placement === 'left' || data.placement === 'left-start' || data.placement === 'left-end'
178
+ ? 'left'
179
+ : 'right'
180
+ return position
181
+ }),
182
+ distinctUntilChanged((a, b) => a === b),
183
+ shareReplay(1)
184
+ )
185
+
186
+ const justify$: Observable<"start" | "center" | "end"> = baseLegendParams$.pipe(
187
+ takeUntil(destroy$),
188
+ map(data => {
189
+ const justify: "start" | "center" | "end" = data.placement === 'top-start' || data.placement === 'bottom-start' || data.placement === 'left-start' || data.placement === 'right-start'
190
+ ? 'start'
191
+ : data.placement === 'top-end' || data.placement === 'bottom-end' || data.placement === 'left-end' || data.placement === 'right-end'
192
+ ? 'end'
193
+ : data.placement === 'top' || data.placement === 'bottom' || data.placement === 'left' || data.placement === 'right'
194
+ ? 'center'
195
+ : 'center'
196
+ return justify
197
+ }),
198
+ distinctUntilChanged((a, b) => a === b),
199
+ shareReplay(1)
200
+ )
201
+
202
+ const lineDirection$ = position$.pipe(
203
+ takeUntil(destroy$),
204
+ map(data => {
205
+ return data === 'bottom' || data === 'top'
206
+ ? 'row'
207
+ : 'column'
208
+ }),
209
+ distinctUntilChanged((a, b) => a === b),
210
+ shareReplay(1)
211
+ )
212
+
213
+ const lineMaxSize$ = combineLatest({
214
+ baseLegendParams: baseLegendParams$,
215
+ position: position$,
216
+ layout: layout$
217
+ }).pipe(
218
+ takeUntil(destroy$),
219
+ debounceTime(0),
220
+ map(data => {
221
+ const ourterSize = (data.baseLegendParams.padding) * 2 + (data.baseLegendParams.gap * 2) // 卡片離場景的間距 & 卡片內的間距
222
+
223
+ return data.position === 'bottom' || data.position === 'top'
224
+ ? data.layout.rootWidth - ourterSize
225
+ : data.layout.rootHeight - ourterSize
226
+ })
227
+ )
228
+
229
+ const rootPosition$ = combineLatest({
230
+ layout: layout$,
231
+ position: position$,
232
+ justify: justify$
233
+ }).pipe(
234
+ takeUntil(destroy$),
235
+ debounceTime(0),
236
+ map(data => {
237
+ let x = 0
238
+ let y = 0
239
+ if (data.position === 'bottom') {
240
+ y = data.layout.rootHeight
241
+ if (data.justify === 'start') {
242
+ x = 0
243
+ } else if (data.justify === 'center') {
244
+ x = data.layout.rootWidth / 2
245
+ } else if (data.justify === 'end') {
246
+ x = data.layout.rootWidth
247
+ }
248
+ } else if (data.position === 'right') {
249
+ x = data.layout.rootWidth
250
+ if (data.justify === 'start') {
251
+ y = 0
252
+ } else if (data.justify === 'center') {
253
+ y = data.layout.rootHeight / 2
254
+ } else if (data.justify === 'end') {
255
+ y = data.layout.rootHeight
256
+ }
257
+ } else if (data.position === 'top') {
258
+ y = 0
259
+ if (data.justify === 'start') {
260
+ x = 0
261
+ } else if (data.justify === 'center') {
262
+ x = data.layout.rootWidth / 2
263
+ } else if (data.justify === 'end') {
264
+ x = data.layout.rootWidth
265
+ }
266
+ } else if (data.position === 'left') {
267
+ x = 0
268
+ if (data.justify === 'start') {
269
+ y = 0
270
+ } else if (data.justify === 'center') {
271
+ y = data.layout.rootHeight / 2
272
+ } else if (data.justify === 'end') {
273
+ y = data.layout.rootHeight
274
+ }
275
+ }
276
+
277
+ return {
278
+ x,
279
+ y
280
+ }
281
+ })
282
+ )
283
+
284
+ const rootPositionSelection$: Observable<d3.Selection<SVGGElement, RootPosition, any, any>> = rootPosition$.pipe(
285
+ takeUntil(destroy$),
286
+ map(data => {
287
+
288
+ return rootSelection
289
+ .selectAll<SVGGElement, RootPosition>(`g.${rootPositionClassName}`)
290
+ .data([data])
291
+ .join(
292
+ enter => {
293
+ return enter
294
+ .append('g')
295
+ .classed(rootPositionClassName, true)
296
+ .attr('transform', d => `translate(${d.x}, ${d.y})`)
297
+ },
298
+ update => {
299
+ return update
300
+ .transition()
301
+ .attr('transform', d => `translate(${d.x}, ${d.y})`)
302
+ },
303
+ exit => exit.remove()
304
+ )
305
+ })
306
+ )
307
+
308
+ const defaultListStyle$ = baseLegendParams$.pipe(
309
+ takeUntil(destroy$),
310
+ map(data => {
311
+ return data.labelList[0] ? data.labelList[0] : defaultListStyle
312
+ })
313
+ )
314
+
315
+ // 先計算list內每個item
316
+ const lengendItems$: Observable<LegendItem[][]> = combineLatest({
317
+ visibleList: visibleList$,
318
+ baseLegendParams: baseLegendParams$,
319
+ legendLabels: legendLabels$,
320
+ lineDirection: lineDirection$,
321
+ lineMaxSize: lineMaxSize$,
322
+ defaultListStyle: defaultListStyle$,
323
+ SeriesLabelColorMap: SeriesLabelColorMap$,
324
+ fontSizePx: fontSizePx$
325
+ }).pipe(
326
+ takeUntil(destroy$),
327
+ debounceTime(0),
328
+ map(data => {
329
+ return data.legendLabels.reduce((prev: LegendItem[][], _current, currentIndex) => {
330
+ // visible為flase則不加入
331
+ if (!data.visibleList[currentIndex]) {
332
+ return prev
333
+ }
334
+
335
+ const currentText = _current !== '' ? _current : noneLabelText
336
+
337
+ const textWidth = measureTextWidth(currentText, data.fontSizePx)
338
+ const itemWidth = (data.fontSizePx * 1.5) + textWidth
339
+ // const color = getSeriesColor(currentIndex, data.styles)
340
+ const color = data.SeriesLabelColorMap.get(_current)
341
+ const lastItem: LegendItem | null = prev[0] && prev[0][0]
342
+ ? prev[prev.length - 1][prev[prev.length - 1].length - 1]
343
+ : null
344
+
345
+ const { translateX, translateY, lineIndex, itemIndex } = ((_data, _prev, _lastItem) => {
346
+ let translateX = 0
347
+ let translateY = 0
348
+ let lineIndex = 0
349
+ let itemIndex = 0
350
+
351
+ if (_data.lineDirection === 'column') {
352
+ let tempTranslateY = _lastItem
353
+ ? _lastItem.translateY + _data.fontSizePx + _data.baseLegendParams.gap
354
+ : 0
355
+
356
+ if ((tempTranslateY + _data.fontSizePx) > _data.lineMaxSize) {
357
+ // 換行
358
+ lineIndex = _lastItem.lineIndex + 1
359
+ itemIndex = 0
360
+ translateY = 0
361
+ // 前一行最寬寬度
362
+ const maxItemWidthInLastLine = _prev[_prev.length - 1].reduce((p, c) => {
363
+ return c.itemWidth > p ? c.itemWidth : p
364
+ }, 0)
365
+ translateX = _lastItem.translateX + maxItemWidthInLastLine + _data.baseLegendParams.gap
366
+ } else {
367
+ lineIndex = _lastItem ? _lastItem.lineIndex : 0
368
+ itemIndex = _lastItem ? _lastItem.itemIndex + 1 : 0
369
+ translateY = tempTranslateY
370
+ translateX = _lastItem ? _lastItem.translateX : 0
371
+ }
372
+ } else {
373
+ let tempTranslateX = _lastItem
374
+ ? _lastItem.translateX + _lastItem.itemWidth + _data.baseLegendParams.gap
375
+ : 0
376
+ if ((tempTranslateX + itemWidth) > _data.lineMaxSize) {
377
+ // 換行
378
+ lineIndex = _lastItem.lineIndex + 1
379
+ itemIndex = 0
380
+ translateX = 0
381
+ } else {
382
+ lineIndex = _lastItem ? _lastItem.lineIndex : 0
383
+ itemIndex = _lastItem ? _lastItem.itemIndex + 1 : 0
384
+ translateX = tempTranslateX
385
+ }
386
+ translateY = (_data.fontSizePx + _data.baseLegendParams.gap) * lineIndex
387
+ }
388
+
389
+ return { translateX, translateY, lineIndex, itemIndex }
390
+ })(data, prev, lastItem)
391
+
392
+ if (!prev[lineIndex]) {
393
+ prev[lineIndex] = []
394
+ }
395
+
396
+ const listStyle = data.baseLegendParams.labelList[itemIndex] ? data.baseLegendParams.labelList[itemIndex] : data.defaultListStyle
397
+
398
+ prev[lineIndex].push({
399
+ id: currentText,
400
+ seriesLabel: currentText,
401
+ seriesIndex: currentIndex,
402
+ lineIndex,
403
+ itemIndex,
404
+ text: currentText,
405
+ itemWidth,
406
+ translateX,
407
+ translateY,
408
+ color,
409
+ listRectWidth: listStyle.listRectWidth,
410
+ listRectHeight: listStyle.listRectHeight,
411
+ listRectRadius: listStyle.listRectRadius
412
+ })
413
+
414
+ return prev
415
+ }, [])
416
+ }),
417
+ shareReplay(1)
418
+ )
419
+
420
+ // 依list計算出來的排序位置來計算整體容器的尺寸
421
+ const lengendList$: Observable<LegendList> = combineLatest({
422
+ baseLegendParams: baseLegendParams$,
423
+ lineDirection: lineDirection$,
424
+ lengendItems: lengendItems$,
425
+ fontSizePx: fontSizePx$
426
+ }).pipe(
427
+ takeUntil(destroy$),
428
+ debounceTime(0),
429
+ map(data => {
430
+ // 依list計算出來的排序位置來計算整體容器的偏移位置
431
+ const { width, height } = ((_data, _lengendItems) => {
432
+ let width = 0
433
+ let height = 0
434
+
435
+ if (!_lengendItems.length || !_lengendItems[0].length) {
436
+ return { width, height }
437
+ }
438
+
439
+ const firstLineLastItem = _lengendItems[0][_lengendItems[0].length - 1]
440
+ if (_data.lineDirection === 'column') {
441
+ width = _lengendItems.reduce((p, c) => {
442
+ const maxWidthInLine = c.reduce((_p, _c) => {
443
+ // 找出最寬的寬度
444
+ return _c.itemWidth > _p ? _c.itemWidth : _p
445
+ }, 0)
446
+ // 每行寬度加總
447
+ return p + maxWidthInLine
448
+ }, 0)
449
+ width += _data.baseLegendParams.gap * (_lengendItems.length - 1)
450
+ height = firstLineLastItem.translateY + _data.fontSizePx
451
+ } else {
452
+ width = firstLineLastItem.translateX + firstLineLastItem.itemWidth
453
+ height = (_data.fontSizePx * _lengendItems.length) + (_data.baseLegendParams.gap * (_lengendItems.length - 1))
454
+ }
455
+
456
+ return { width, height }
457
+ })(data, data.lengendItems)
458
+
459
+ return <LegendList>{
460
+ direction: data.lineDirection,
461
+ width,
462
+ height,
463
+ translateX: data.baseLegendParams.gap,
464
+ translateY: data.baseLegendParams.gap
465
+ }
466
+ }),
467
+ shareReplay(1)
468
+ )
469
+
470
+ const legendCard$: Observable<LegendCard> = combineLatest({
471
+ baseLegendParams: baseLegendParams$,
472
+ position: position$,
473
+ justify: justify$,
474
+ lengendList: lengendList$
475
+ }).pipe(
476
+ takeUntil(destroy$),
477
+ debounceTime(0),
478
+ map(data => {
479
+ const width = data.lengendList.width + (data.baseLegendParams.gap * 2)
480
+ const height = data.lengendList.height + (data.baseLegendParams.gap * 2)
481
+ let translateX = 0
482
+ let translateY = 0
483
+
484
+ if (data.position === 'left') {
485
+ if (data.justify === 'start') {
486
+ translateX = data.baseLegendParams.padding
487
+ translateY = data.baseLegendParams.padding
488
+ } else if (data.justify === 'center') {
489
+ translateX = data.baseLegendParams.padding
490
+ translateY = - height / 2
491
+ } else if (data.justify === 'end') {
492
+ translateX = data.baseLegendParams.padding
493
+ translateY = - height - data.baseLegendParams.padding
494
+ }
495
+ } else if (data.position === 'right') {
496
+ if (data.justify === 'start') {
497
+ translateX = - width - data.baseLegendParams.padding
498
+ translateY = data.baseLegendParams.padding
499
+ } else if (data.justify === 'center') {
500
+ translateX = - width - data.baseLegendParams.padding
501
+ translateY = - height / 2
502
+ } else if (data.justify === 'end') {
503
+ translateX = - width - data.baseLegendParams.padding
504
+ translateY = - height - data.baseLegendParams.padding
505
+ }
506
+ } else if (data.position === 'top') {
507
+ if (data.justify === 'start') {
508
+ translateX = data.baseLegendParams.padding
509
+ translateY = data.baseLegendParams.padding
510
+ } else if (data.justify === 'center') {
511
+ translateX = - width / 2
512
+ translateY = data.baseLegendParams.padding
513
+ } else if (data.justify === 'end') {
514
+ translateX = - width - data.baseLegendParams.padding
515
+ translateY = data.baseLegendParams.padding
516
+ }
517
+ } else {
518
+ if (data.justify === 'start') {
519
+ translateX = data.baseLegendParams.padding
520
+ translateY = - height - data.baseLegendParams.padding
521
+ } else if (data.justify === 'center') {
522
+ translateX = - width / 2
523
+ translateY = - height - data.baseLegendParams.padding
524
+ } else if (data.justify === 'end') {
525
+ translateX = - width - data.baseLegendParams.padding
526
+ translateY = - height - data.baseLegendParams.padding
527
+ }
528
+ }
529
+ // translateX += _data.baseLegendParams.offset[0]
530
+ // translateY += _data.baseLegendParams.offset[1]
531
+
532
+ return {
533
+ width,
534
+ height,
535
+ translateX,
536
+ translateY
537
+ }
538
+ })
539
+ )
540
+
541
+ const lengendCardSelection$ = combineLatest({
542
+ rootPositionSelection: rootPositionSelection$,
543
+ baseLegendParams: baseLegendParams$,
544
+ theme: theme$,
545
+ legendCard: legendCard$
546
+ }).pipe(
547
+ takeUntil(destroy$),
548
+ debounceTime(0),
549
+ map(data => {
550
+ return data.rootPositionSelection
551
+ .selectAll<SVGGElement, BaseLegendParams>('g')
552
+ .data([data.legendCard])
553
+ .join(
554
+ enter => {
555
+ return enter
556
+ .append('g')
557
+ .classed(legendCardClassName, true)
558
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
559
+ },
560
+ update => {
561
+ return update
562
+ .transition()
563
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
564
+ },
565
+ exit => exit.remove()
566
+ )
567
+ .each((d, i, g) => {
568
+ const rect = d3.select(g[i])
569
+ .selectAll('rect')
570
+ .data([d])
571
+ .join('rect')
572
+ .attr('width', d => d.width)
573
+ .attr('height', d => d.height)
574
+ .attr('fill', getColor(data.baseLegendParams.backgroundFill, data.theme))
575
+ .attr('stroke', getColor(data.baseLegendParams.backgroundStroke, data.theme))
576
+ })
577
+ })
578
+ )
579
+
580
+
581
+ const lengendListSelection$ = combineLatest({
582
+ lengendCardSelection: lengendCardSelection$,
583
+ baseLegendParams: baseLegendParams$,
584
+ lengendList: lengendList$
585
+ }).pipe(
586
+ takeUntil(destroy$),
587
+ debounceTime(0),
588
+ map(data => {
589
+ return data.lengendCardSelection
590
+ .selectAll<SVGGElement, BaseLegendParams>('g')
591
+ .data([data.lengendList])
592
+ .join(
593
+ enter => {
594
+ return enter
595
+ .append('g')
596
+ .classed(legendListClassName, true)
597
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
598
+ },
599
+ update => {
600
+ return update
601
+ .transition()
602
+ .attr('transform', d => `translate(${d.translateX}, ${d.translateY})`)
603
+ },
604
+ exit => exit.remove()
605
+ )
606
+ })
607
+ )
608
+
609
+ const itemSelection$ = combineLatest({
610
+ lengendListSelection: lengendListSelection$,
611
+ baseLegendParams: baseLegendParams$,
612
+ theme: theme$,
613
+ lengendItems: lengendItems$,
614
+ fontSizePx: fontSizePx$
615
+ }).pipe(
616
+ takeUntil(destroy$),
617
+ debounceTime(0),
618
+ map(data => {
619
+ const items = data.lengendItems[0] ? data.lengendItems.flat() : []
620
+
621
+ return data.lengendListSelection
622
+ .selectAll<SVGGElement, string>(`g.${legendItemClassName}`)
623
+ .data(items)
624
+ .join(
625
+ enter => {
626
+ return enter
627
+ .append('g')
628
+ .classed(legendItemClassName, true)
629
+ .attr('cursor', 'default')
630
+ },
631
+ update => update,
632
+ exit => exit.remove()
633
+ )
634
+ .attr('transform', (d, i) => {
635
+ return `translate(${d.translateX}, ${d.translateY})`
636
+ })
637
+ .each((d, i, g) => {
638
+ const rectCenterX = data.fontSizePx / 2
639
+ const transformRectWidth = - d.listRectWidth / 2
640
+ const transformRectHeight = - d.listRectHeight / 2
641
+ // 方塊
642
+ d3.select(g[i])
643
+ .selectAll('rect')
644
+ .data([d])
645
+ .join('rect')
646
+ .attr('x', rectCenterX)
647
+ .attr('y', rectCenterX)
648
+ .attr('width', _d => _d.listRectWidth)
649
+ .attr('height', _d => _d.listRectHeight)
650
+ .attr('transform', _d => `translate(${transformRectWidth}, ${transformRectHeight})`)
651
+ .attr('fill', _d => _d.color)
652
+ .attr('rx', _d => _d.listRectRadius)
653
+ // 文字
654
+ d3.select(g[i])
655
+ .selectAll('text')
656
+ .data([d])
657
+ .join(
658
+ enter => {
659
+ return enter
660
+ .append('text')
661
+ .attr('dominant-baseline', 'hanging')
662
+ },
663
+ update => {
664
+ return update
665
+ },
666
+ exit => exit.remove()
667
+ )
668
+ .attr('x', data.fontSizePx * 1.5)
669
+ .attr('font-size', data.theme.fontSize)
670
+ .attr('fill', d => data.baseLegendParams.textColorType === 'data'
671
+ ? getSeriesColor(d.seriesIndex, data.theme)
672
+ : getColor(data.baseLegendParams.textColorType, data.theme))
673
+ .text(d => d.text)
674
+ })
675
+ })
676
+ )
677
+
678
+ itemSelection$.subscribe()
679
+
680
+ return () => {
681
+ rootSelection.select(`g.${rootPositionClassName}`).remove()
682
+ destroy$.next(undefined)
683
+ }
684
+ }