@easyv/charts 1.10.13 → 1.10.15

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.
@@ -34,10 +34,21 @@ import { pieLegendFormatter as legendFormatter } from "../formatter";
34
34
  import ringCss from "../css/piechart.module.css";
35
35
  import { useAiDataOfPie } from "../hooks";
36
36
  import { PieTooltip } from "./PieTooltip";
37
- import { toFixed } from "@easyv/utils/lib/common/utils";
37
+ import { getPieAdaptiveMarginPreset } from "../utils/legendPlacement";
38
38
 
39
39
  const PI = Math.PI;
40
40
 
41
+ const PIE_ADAPTIVE_MARGINS = {
42
+ right: { marginTop: 24, marginBottom: 24, marginLeft: 0, marginRight: 200 },
43
+ left: { marginTop: 24, marginBottom: 24, marginLeft: 200, marginRight: 0 },
44
+ top: { marginTop: 150, marginBottom: 24, marginLeft: 24, marginRight: 24 },
45
+ bottom: { marginTop: 24, marginBottom: 150, marginLeft: 24, marginRight: 24 },
46
+ hidden: { marginTop: 24, marginBottom: 24, marginLeft: 24, marginRight: 24 },
47
+ };
48
+
49
+ const getPieAdaptiveMargin = (show, alignment) =>
50
+ getPieAdaptiveMarginPreset(show, alignment, PIE_ADAPTIVE_MARGINS);
51
+
41
52
  const defaultChart = {
42
53
  outerRadius: 1,
43
54
  innerRadius: 0,
@@ -49,111 +60,111 @@ const defaultChart = {
49
60
  };
50
61
  const defaultAngle = { startAngle: 0, endAngle: 360, antiClockwise: false };
51
62
 
52
- const nameDy = (showValue, showPercent, mode, dir) => {
53
- if (showValue || showPercent) {
54
- if (mode == "vertical") {
55
- return dir == 1 ? "1.1em" : "-2.6em";
56
- } else {
57
- return 0;
58
- }
59
- } else {
60
- if (mode == "vertical") {
61
- return dir * 1.1 + "em";
62
- } else {
63
- return 0;
64
- }
65
- }
66
- };
67
- const valueDy = (value1, mode, dir) => {
68
- if (value1) {
69
- if (mode == "vertical") {
70
- return "1.5em";
71
- } else {
72
- return 0;
73
- }
74
- } else {
75
- if (mode == "vertical") {
76
- return dir == 1 ? "1.1em" : "-1.1em";
77
- } else {
78
- return 0;
79
- }
80
- }
81
- };
63
+ // const nameDy = (showValue, showPercent, mode, dir) => {
64
+ // if (showValue || showPercent) {
65
+ // if (mode == "vertical") {
66
+ // return dir == 1 ? "1.1em" : "-2.6em";
67
+ // } else {
68
+ // return 0;
69
+ // }
70
+ // } else {
71
+ // if (mode == "vertical") {
72
+ // return dir * 1.1 + "em";
73
+ // } else {
74
+ // return 0;
75
+ // }
76
+ // }
77
+ // };
78
+ // const valueDy = (value1, mode, dir) => {
79
+ // if (value1) {
80
+ // if (mode == "vertical") {
81
+ // return "1.5em";
82
+ // } else {
83
+ // return 0;
84
+ // }
85
+ // } else {
86
+ // if (mode == "vertical") {
87
+ // return dir == 1 ? "1.1em" : "-1.1em";
88
+ // } else {
89
+ // return 0;
90
+ // }
91
+ // }
92
+ // };
82
93
 
83
- const percentDy_ = (showName, showValue, mode, dir) => {
84
- if (showValue) {
85
- return 0;
86
- }
87
- if (showName) {
88
- if (mode == "vertical") {
89
- return "1.5em";
90
- } else {
91
- return 0;
92
- }
93
- } else {
94
- if (mode == "vertical") {
95
- return dir * 1.1 + "em";
96
- } else {
97
- return 0;
98
- }
99
- }
100
- };
94
+ // const percentDy_ = (showName, showValue, mode, dir) => {
95
+ // if (showValue) {
96
+ // return 0;
97
+ // }
98
+ // if (showName) {
99
+ // if (mode == "vertical") {
100
+ // return "1.5em";
101
+ // } else {
102
+ // return 0;
103
+ // }
104
+ // } else {
105
+ // if (mode == "vertical") {
106
+ // return dir * 1.1 + "em";
107
+ // } else {
108
+ // return 0;
109
+ // }
110
+ // }
111
+ // };
101
112
 
102
- const percentX = (showName, showValue, mode, x) => {
103
- if (showValue) {
104
- return "";
105
- }
106
- if (showName) {
107
- if (mode == "vertical") {
108
- return x;
109
- } else {
110
- return "";
111
- }
112
- } else {
113
- return x;
114
- }
115
- };
113
+ // const percentX = (showName, showValue, mode, x) => {
114
+ // if (showValue) {
115
+ // return "";
116
+ // }
117
+ // if (showName) {
118
+ // if (mode == "vertical") {
119
+ // return x;
120
+ // } else {
121
+ // return "";
122
+ // }
123
+ // } else {
124
+ // return x;
125
+ // }
126
+ // };
116
127
 
117
- const percentDx = (showName, showValue, mode) => {
118
- if (showValue) {
119
- return "0.5em";
120
- }
121
- if (showName) {
122
- if (mode == "vertical") {
123
- return 0;
124
- } else {
125
- return "0.5em";
126
- }
127
- } else {
128
- return 0;
129
- }
130
- };
128
+ // const percentDx = (showName, showValue, mode) => {
129
+ // if (showValue) {
130
+ // return "0.5em";
131
+ // }
132
+ // if (showName) {
133
+ // if (mode == "vertical") {
134
+ // return 0;
135
+ // } else {
136
+ // return "0.5em";
137
+ // }
138
+ // } else {
139
+ // return 0;
140
+ // }
141
+ // };
131
142
 
132
- const percentDy = (showName, showValue, mode) => {
133
- if (showValue) {
134
- return 0;
135
- }
136
- if (showName) {
137
- if (mode == "vertical") {
138
- return "1.5em";
139
- } else {
140
- return 0;
141
- }
142
- } else {
143
- return 0;
144
- }
145
- };
143
+ // const percentDy = (showName, showValue, mode) => {
144
+ // if (showValue) {
145
+ // return 0;
146
+ // }
147
+ // if (showName) {
148
+ // if (mode == "vertical") {
149
+ // return "1.5em";
150
+ // } else {
151
+ // return 0;
152
+ // }
153
+ // } else {
154
+ // return 0;
155
+ // }
156
+ // };
146
157
 
147
- const valueDx = (showName, mode) => {
148
- if (!showName) {
149
- return "";
150
- }
151
- if (mode == "vertical") {
152
- return "";
153
- } else {
154
- return "0.5em";
155
- }
156
- };
158
+ // const valueDx = (showName, mode) => {
159
+ // if (!showName) {
160
+ // return "";
161
+ // }
162
+ // if (mode == "vertical") {
163
+ // return "";
164
+ // } else {
165
+ // return "0.5em";
166
+ // }
167
+ // };
157
168
 
158
169
  const getCoord = (deg, radius) => {
159
170
  var x = Math.cos(deg) * radius,
@@ -254,7 +265,7 @@ const Component = memo(
254
265
  label,
255
266
  legend: { formatter = legendFormatter, ...legend },
256
267
  animation: { ringDuration, labelDuration } = {},
257
- margin: { marginLeft, marginTop },
268
+ margin: { marginLeft, marginTop, marginRight, marginBottom },
258
269
  },
259
270
  fan: {
260
271
  chart = defaultChart,
@@ -301,7 +312,45 @@ const Component = memo(
301
312
  height: chartHeight,
302
313
  triggerOnRelative,
303
314
  onEmit,
315
+ updateConfig,
304
316
  } = useContext(chartContext);
317
+ const { show: legendShow, name: { layoutMode: legendLayoutMode } = {} } =
318
+ legend.config;
319
+ const legendAlignment = legend.config?.layout?.alignment;
320
+ const adaptiveMarginPresetKey = useRef("");
321
+ useEffect(() => {
322
+ if (legendLayoutMode !== "Adaptive" || !updateConfig) return;
323
+ const presetKey = `${legendLayoutMode}-${legendShow}-${legendAlignment}`;
324
+ if (adaptiveMarginPresetKey.current === presetKey) return;
325
+ adaptiveMarginPresetKey.current = presetKey;
326
+ const target = getPieAdaptiveMargin(legendShow, legendAlignment);
327
+ updateConfig({
328
+ id,
329
+ type: "config",
330
+ payload: [
331
+ {
332
+ path: ["chart", "margin", "marginTop"],
333
+ field: "value",
334
+ value: target.marginTop,
335
+ },
336
+ {
337
+ path: ["chart", "margin", "marginBottom"],
338
+ field: "value",
339
+ value: target.marginBottom,
340
+ },
341
+ {
342
+ path: ["chart", "margin", "marginLeft"],
343
+ field: "value",
344
+ value: target.marginLeft,
345
+ },
346
+ {
347
+ path: ["chart", "margin", "marginRight"],
348
+ field: "value",
349
+ value: target.marginRight,
350
+ },
351
+ ],
352
+ });
353
+ }, [id, updateConfig, legendLayoutMode, legendShow, legendAlignment]);
305
354
  const [y, setY] = useState(1);
306
355
  const radius = (Math.min(chartWidth, chartHeight) / 2) * outerRadius;
307
356
 
@@ -626,6 +675,10 @@ const Component = memo(
626
675
  <Legend
627
676
  {...legend}
628
677
  height={chartHeight}
678
+ componentWidth={width}
679
+ marginLeft={marginLeft}
680
+ marginRight={marginRight}
681
+ isPieChart
629
682
  columnsSeries={columnsSeries}
630
683
  data={data}
631
684
  series={_arcs.map((arc) => ({
@@ -897,6 +950,10 @@ const Component = memo(
897
950
  <Legend
898
951
  {...legend}
899
952
  height={chartHeight}
953
+ componentWidth={width}
954
+ marginLeft={marginLeft}
955
+ marginRight={marginRight}
956
+ isPieChart
900
957
  data={data}
901
958
  columnsSeries={columnsSeries}
902
959
  series={_arcs.map((arc) => ({
@@ -1031,7 +1088,9 @@ const Current = ({
1031
1088
  value={nameTemp}
1032
1089
  speed={speed}
1033
1090
  style={{
1091
+ width: "100%",
1034
1092
  maxWidth,
1093
+ textAlign: "center",
1035
1094
  display: textOverflow == "marquee" ? "flex" : "bolck",
1036
1095
  justifyContent: "center",
1037
1096
  ...getFontStyle(nameFont),
@@ -11,7 +11,8 @@ interface flowText {
11
11
  speed: number;
12
12
  style: CSSProperties;
13
13
  config?: any;
14
- ShowType: string;
14
+ /** 历史兼容字段,图例等调用方可能传入,已不再影响省略号换行行为 */
15
+ ShowType?: string;
15
16
  }
16
17
 
17
18
  export default memo(
@@ -22,7 +23,6 @@ export default memo(
22
23
  style, // 样式
23
24
  speed = 5, // 动画速度
24
25
  config = {},
25
- ShowType,
26
26
  } = props;
27
27
  const { needTitle = true } = config;
28
28
 
@@ -64,7 +64,6 @@ export default memo(
64
64
  <div
65
65
  style={{
66
66
  ...styles,
67
- whiteSpace: ShowType == "Adaptive" ? "normal" : styles.whiteSpace,
68
67
  }}
69
68
  ref={ref}
70
69
  title={needTitle && type == "ellipsis" ? text || undefined : undefined}
@@ -92,13 +92,20 @@ export const pieLegendFormatter = (series, props) => {
92
92
 
93
93
  const _color = seriesColorType == "pure" ? pure : stops[0].color;
94
94
  const textMarginLeft = 5;
95
+ const { adaptiveMaxWidth, isPieAdaptive, chartWidth } = props;
96
+ const isSideAdaptive = !!adaptiveMaxWidth;
97
+ const isTopBottomAdaptive = isPieAdaptive && !isSideAdaptive;
98
+ const nameOverflowType =
99
+ isSideAdaptive || isTopBottomAdaptive ? "ellipsis" : textOverflow;
95
100
 
96
- // 1. 计算每列宽度(加上间距)
101
+ // 1. 计算每列宽度(加上间距);正上/正下自适应不按全局列宽对齐
97
102
  let columns = [];
98
- if (showName) columns.push(`${props.nameMaxWidth}px`);
99
- if (showValue) columns.push(`${props.valueMaxWidth + valueGap}px`);
100
- if (showPercent) columns.push(`${props.percentMaxWidth + percentGap}px`);
101
- if (series.fieldsData.length > 0) {
103
+ if (!isTopBottomAdaptive) {
104
+ if (showName) columns.push(`${props.nameMaxWidth}px`);
105
+ if (showValue) columns.push(`${props.valueMaxWidth + valueGap}px`);
106
+ if (showPercent) columns.push(`${props.percentMaxWidth + percentGap}px`);
107
+ }
108
+ if (!isTopBottomAdaptive && series.fieldsData.length > 0) {
102
109
  //网格布局新增的各列宽度
103
110
  series.fieldsData.forEach((item) => {
104
111
  const idx = item.fieldsColumnIndex ?? 0;
@@ -135,6 +142,66 @@ export const pieLegendFormatter = (series, props) => {
135
142
  });
136
143
  }
137
144
 
145
+ const scaleColumnsToMax = (cols, maxTotal) => {
146
+ const widths = cols.map((c) => parseFloat(c) || 0);
147
+ const total = widths.reduce((a, b) => a + b, 0);
148
+ if (!total || total <= maxTotal) return cols.join(" ");
149
+ const scale = maxTotal / total;
150
+ return widths
151
+ .map((w) => `${Math.max(1, Math.floor(w * scale))}px`)
152
+ .join(" ");
153
+ };
154
+
155
+ const sideGridMaxWidth = isSideAdaptive
156
+ ? Math.max(
157
+ 0,
158
+ adaptiveMaxWidth -
159
+ (icon
160
+ ? (parseFloat(icon.width) || 0) +
161
+ (parseFloat(icon.marginRight) || 0)
162
+ : 0),
163
+ )
164
+ : adaptiveMaxWidth;
165
+
166
+ const contentStyle = isSideAdaptive
167
+ ? {
168
+ display: "grid",
169
+ gridTemplateColumns: scaleColumnsToMax(columns, sideGridMaxWidth),
170
+ width: "max-content",
171
+ maxWidth: "100%",
172
+ minWidth: 0,
173
+ overflow: "hidden",
174
+ boxSizing: "border-box",
175
+ }
176
+ : isTopBottomAdaptive
177
+ ? {
178
+ display: "flex",
179
+ flexDirection: "row",
180
+ alignItems: "center",
181
+ flexWrap: "nowrap",
182
+ width: "max-content",
183
+ maxWidth: chartWidth ? `${chartWidth}px` : "100%",
184
+ minWidth: 0,
185
+ overflow: "hidden",
186
+ boxSizing: "border-box",
187
+ gap: `${valueGap}px ${percentGap}px`,
188
+ }
189
+ : {
190
+ width: `calc( 100% + ${textMarginLeft + valueGap + percentGap}px )`,
191
+ gridTemplateColumns: columns.join(" "),
192
+ overflowX: "visible",
193
+ };
194
+
195
+ const contentClassName = isTopBottomAdaptive
196
+ ? undefined
197
+ : isSideAdaptive
198
+ ? showName && showValue && showPercent
199
+ ? css.showAllStyle
200
+ : css.notShowAllStyle
201
+ : showName && showValue && showPercent
202
+ ? css.showAllStyle
203
+ : css.notShowAllStyle;
204
+
138
205
  return (
139
206
  <>
140
207
  {icon && (
@@ -142,30 +209,33 @@ export const pieLegendFormatter = (series, props) => {
142
209
  style={{
143
210
  ...icon,
144
211
  marginRight: icon.marginRight,
212
+ flexShrink: 0,
145
213
  transform: `translate(${nameX}px, ${nameY}px)`,
146
214
  }}
147
215
  />
148
216
  )}
149
- <div
150
- className={
151
- showName && showValue && showPercent
152
- ? css.showAllStyle
153
- : css.notShowAllStyle
154
- }
155
- style={{
156
- width: `calc( 100% + ${textMarginLeft + valueGap + percentGap}px )`,
157
- gridTemplateColumns: columns.join(" "),
158
- overflowX: "visible",
159
- }}
160
- >
217
+ <div className={contentClassName} style={contentStyle}>
161
218
  {showName && (
162
219
  <TextOverflow
163
220
  value={displayName}
164
- type={textOverflow}
221
+ type={nameOverflowType}
165
222
  speed={speed}
166
223
  style={{
167
- width: maxWidth,
224
+ width: isSideAdaptive
225
+ ? "100%"
226
+ : isTopBottomAdaptive
227
+ ? "auto"
228
+ : maxWidth,
229
+ maxWidth: isSideAdaptive
230
+ ? "100%"
231
+ : isTopBottomAdaptive
232
+ ? "none"
233
+ : maxWidth,
234
+ minWidth: isSideAdaptive ? 0 : undefined,
235
+ flexShrink: isTopBottomAdaptive ? 1 : undefined,
168
236
  marginLeft: textMarginLeft,
237
+ // whiteSpace: "nowrap",
238
+ overflow: "hidden",
169
239
  ...getFontStyle(nameFont),
170
240
  transform: `translate(${nameX}px, ${nameY}px)`,
171
241
  }}
@@ -177,12 +247,17 @@ export const pieLegendFormatter = (series, props) => {
177
247
  display: "flex",
178
248
  whiteSpace: "nowrap",
179
249
  ...getFontStyle(valueFont),
180
- marginLeft: valueGap,
250
+ marginLeft: isSideAdaptive || isTopBottomAdaptive ? 0 : valueGap,
251
+ flexShrink: isTopBottomAdaptive ? 0 : undefined,
181
252
  transform: `translate(${valueX}px,${valueY}px)`,
182
253
  color: valueSameColor ? _color : valueFont.color,
183
254
  alignItems: "center",
184
- justifyContent: alignToJustify(valueAlign ?? "center"),
185
- textAlign: alignToTextAlign(valueAlign),
255
+ justifyContent: alignToJustify(
256
+ isTopBottomAdaptive ? "left" : (valueAlign ?? "center"),
257
+ ),
258
+ textAlign: alignToTextAlign(
259
+ isTopBottomAdaptive ? "left" : valueAlign,
260
+ ),
186
261
  }}
187
262
  >
188
263
  <span>
@@ -207,66 +282,74 @@ export const pieLegendFormatter = (series, props) => {
207
282
  display: "flex",
208
283
  whiteSpace: "nowrap",
209
284
  ...getFontStyle(percentFont),
210
- marginLeft: percentGap,
285
+ marginLeft:
286
+ isSideAdaptive || isTopBottomAdaptive ? 0 : percentGap,
287
+ flexShrink: isTopBottomAdaptive ? 0 : undefined,
211
288
  transform: `translate(${percentX}px,${percentY}px)`,
212
289
  color: percentSameColor ? _color : percentFont.color,
213
290
  alignItems: "center",
214
- justifyContent: alignToJustify(percentAlign),
215
- textAlign: alignToTextAlign(percentAlign),
291
+ justifyContent: alignToJustify(
292
+ isTopBottomAdaptive ? "left" : percentAlign,
293
+ ),
294
+ textAlign: alignToTextAlign(
295
+ isTopBottomAdaptive ? "left" : percentAlign,
296
+ ),
216
297
  }}
217
298
  >
218
299
  {percent + "%"}
219
300
  </span>
220
301
  )}
221
- {series.fieldsData?.map(
222
- (
223
- item,
224
- index, //渲染网格布局新增的各列
225
- ) => (
302
+ {series.fieldsData?.map((item, index) => (
303
+ <span
304
+ key={fieldColumnKeys[item.fieldsColumnIndex ?? index] ?? index}
305
+ style={{
306
+ display: "flex",
307
+ boxSizing: "border-box",
308
+ width: isTopBottomAdaptive ? "auto" : "100%",
309
+ minWidth: 0,
310
+ flexShrink: isTopBottomAdaptive ? 0 : undefined,
311
+ alignItems: "center",
312
+ justifyContent: alignToJustify(
313
+ isTopBottomAdaptive ? "left" : (item.align ?? "center"),
314
+ ),
315
+ textAlign: alignToTextAlign(
316
+ isTopBottomAdaptive ? "left" : (item.align ?? "center"),
317
+ ),
318
+ overflow:
319
+ isSideAdaptive || isTopBottomAdaptive ? "hidden" : "visible",
320
+ whiteSpace: "nowrap",
321
+ ...getFontStyle(item.font),
322
+ color: item.sameColor ? _color : item.font.color,
323
+ }}
324
+ >
226
325
  <span
227
- key={fieldColumnKeys[item.fieldsColumnIndex ?? index] ?? index}
228
326
  style={{
229
- display: "flex",
230
- boxSizing: "border-box",
231
- width: "100%",
232
- minWidth: 0,
327
+ display: "inline-flex",
233
328
  alignItems: "center",
234
- justifyContent: alignToJustify(item.align ?? "center"),
235
- textAlign: alignToTextAlign(item.align ?? "center"),
236
- overflow: "visible",
237
- ...getFontStyle(item.font),
238
- color: item.sameColor ? _color : item.font.color,
329
+ flexWrap: "nowrap",
330
+ whiteSpace: "nowrap",
331
+ marginLeft: item.translate?.x ?? 0,
332
+ marginTop: item.translate?.y ?? 0,
239
333
  }}
240
334
  >
241
- <span
242
- style={{
243
- display: "inline-flex",
244
- alignItems: "center",
245
- flexWrap: "nowrap",
246
- whiteSpace: "nowrap",
247
- marginLeft: item.translate?.x ?? 0,
248
- marginTop: item.translate?.y ?? 0,
249
- }}
250
- >
251
- <span style={{ whiteSpace: "nowrap" }}>{item.value}</span>
252
- {item.suffix?.show &&
253
- item.suffix?.text != null &&
254
- String(item.suffix.text) !== "" && (
255
- <span
256
- style={{
257
- whiteSpace: "nowrap",
258
- fontSize: item.suffix.fontSize,
259
- marginLeft: item.suffix.translate?.x ?? 0,
260
- marginTop: item.suffix.translate?.y ?? 0,
261
- }}
262
- >
263
- {item.suffix.text}
264
- </span>
265
- )}
266
- </span>
335
+ <span style={{ whiteSpace: "nowrap" }}>{item.value}</span>
336
+ {item.suffix?.show &&
337
+ item.suffix?.text != null &&
338
+ String(item.suffix.text) !== "" && (
339
+ <span
340
+ style={{
341
+ whiteSpace: "nowrap",
342
+ fontSize: item.suffix.fontSize,
343
+ marginLeft: item.suffix.translate?.x ?? 0,
344
+ marginTop: item.suffix.translate?.y ?? 0,
345
+ }}
346
+ >
347
+ {item.suffix.text}
348
+ </span>
349
+ )}
267
350
  </span>
268
- ),
269
- )}
351
+ </span>
352
+ ))}
270
353
  </div>
271
354
  </>
272
355
  );
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 解析图例 layout.alignment(如 "center top"、"right top")
3
+ * 格式:{水平对齐} {位置},位置为 top | right | left | bottom
4
+ */
5
+ export const parseLegendAlignment = (alignment = "right center") => {
6
+ const [_alignment, position] = alignment.split(" ");
7
+ const isCenterTopOrBottom =
8
+ (position === "top" || position === "bottom") && _alignment === "center";
9
+ const isSidePlacement =
10
+ position === "left" ||
11
+ position === "right" ||
12
+ ((position === "top" || position === "bottom") && _alignment !== "center");
13
+ return {
14
+ alignment: _alignment,
15
+ position,
16
+ isCenterTopOrBottom,
17
+ isSidePlacement,
18
+ };
19
+ };
20
+
21
+ export const getPieAdaptiveMarginPreset = (show, alignment, presets) => {
22
+ if (!show) return presets.hidden;
23
+ const { alignment: mainAlign, position, isCenterTopOrBottom } =
24
+ parseLegendAlignment(alignment);
25
+ if (isCenterTopOrBottom) return presets[position];
26
+ if (position === "left" || mainAlign === "left") return presets.left;
27
+ return presets.right;
28
+ };