@easyv/charts 1.7.37 → 1.8.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 (61) hide show
  1. package/.babelrc +8 -8
  2. package/CHANGELOG.md +18 -18
  3. package/commitlint.config.js +1 -1
  4. package/lib/components/Background.js +2 -2
  5. package/lib/components/Band.js +2 -2
  6. package/lib/components/Brush.js +2 -2
  7. package/lib/components/Chart.js +2 -2
  8. package/lib/components/ChartContainer.js +2 -2
  9. package/lib/components/ConicalGradient.js +21 -21
  10. package/lib/components/ExtentData.js +2 -2
  11. package/lib/components/Indicator.js +2 -2
  12. package/lib/components/Label.js +2 -2
  13. package/lib/components/Legend.js +5 -8
  14. package/lib/components/Lighter.js +2 -2
  15. package/lib/components/Line.js +2 -2
  16. package/lib/components/LinearGradient.js +2 -2
  17. package/lib/components/PieChart.js +26 -4
  18. package/lib/components/StereoBar.js +2 -2
  19. package/lib/css/index.module.css +39 -39
  20. package/lib/css/piechart.module.css +26 -26
  21. package/lib/hooks/useAnimateData.js +6 -6
  22. package/lib/hooks/useFilterData.js +5 -5
  23. package/lib/hooks/useStackData.js +5 -5
  24. package/lib/hooks/useTooltip.js +11 -11
  25. package/package.json +54 -54
  26. package/src/components/Background.tsx +61 -61
  27. package/src/components/Band.tsx +334 -334
  28. package/src/components/Brush.js +159 -159
  29. package/src/components/Chart.js +157 -157
  30. package/src/components/ChartContainer.tsx +71 -71
  31. package/src/components/ConicalGradient.js +258 -258
  32. package/src/components/Control.jsx +242 -242
  33. package/src/components/ExtentData.js +18 -18
  34. package/src/components/Indicator.js +61 -61
  35. package/src/components/Label.js +262 -262
  36. package/src/components/Legend.js +286 -289
  37. package/src/components/Lighter.jsx +173 -173
  38. package/src/components/Line.js +153 -153
  39. package/src/components/LinearGradient.js +29 -29
  40. package/src/components/PieChart.js +19 -5
  41. package/src/components/PieTooltip.jsx +160 -160
  42. package/src/components/SplitText.tsx +70 -70
  43. package/src/components/StereoBar.tsx +307 -307
  44. package/src/components/index.js +61 -61
  45. package/src/context/index.js +2 -2
  46. package/src/css/index.module.css +39 -39
  47. package/src/css/piechart.module.css +26 -26
  48. package/src/element/ConicGradient.jsx +55 -55
  49. package/src/element/Line.tsx +33 -33
  50. package/src/element/index.ts +3 -3
  51. package/src/formatter/index.js +1 -1
  52. package/src/formatter/legend.js +122 -122
  53. package/src/hooks/index.js +20 -20
  54. package/src/hooks/useAnimateData.ts +68 -68
  55. package/src/hooks/useFilterData.js +77 -77
  56. package/src/hooks/useStackData.js +140 -140
  57. package/src/hooks/useTooltip.ts +103 -103
  58. package/src/index.js +6 -6
  59. package/src/types/index.d.ts +68 -68
  60. package/src/utils/index.js +812 -812
  61. package/tsconfig.json +23 -23
@@ -1,289 +1,286 @@
1
- /**
2
- * 图例
3
- */
4
- import React, { memo, useCallback, useState, useEffect, useRef } from 'react';
5
- import { getIcon, sortPie } from '../utils';
6
- import TextOverflow from './TextOverflow';
7
-
8
- const defaultFont = {
9
- fontStyle: 'normal',
10
- fontWeight: 'normal',
11
- };
12
-
13
- export default memo(
14
- ({
15
- series,
16
- height,
17
- config,
18
- config: {
19
- show,
20
- order = '',
21
- interactive,
22
- LegendType,//类型
23
- maxWidth,
24
- textOverflow,
25
- speed,
26
- layout: {
27
- alignment = 'right center',
28
- gridTemplateColumns,
29
- gridGap: { gridColumnGap, gridRowGap },
30
- translate: { x, y },
31
- },
32
- loop = {},
33
- font: { italic, bold, ...font } = defaultFont,
34
- unselect: { opacity = 1 } = {},
35
- },
36
- filterData,
37
- formatter,
38
- judge
39
- }) => {
40
- if (!show) return null;
41
-
42
- const ref_container = useRef(null); // 滚动容器
43
- const ref_scrollTop = useRef(0); // 当前滚动距离
44
-
45
- const [scrollStep, setScrollStep] = useState(0); // 行高度
46
-
47
- // 初始化行高
48
- useEffect(() => {
49
- if (ref_container.current) {
50
- const rowHeight = ref_container.current.querySelector('li').clientHeight + gridRowGap;
51
- setScrollStep(rowHeight);
52
- }
53
- }, [gridRowGap]);
54
-
55
- // 启动自动滚动定时器
56
- useEffect(() => {
57
- if (!loop.show) return;
58
-
59
- ref_scrollTop.current = 0;
60
-
61
- const timer = setInterval(() => {
62
- handleAutoScroll();
63
- }, loop.interval * 1000); // 每隔3秒滚动一次
64
-
65
- // 清除定时器
66
- return () => clearInterval(timer);
67
- }, [scrollStep, loop.show, loop.interval])
68
-
69
- const handleAutoScroll = () => {
70
- const table = ref_container.current;
71
- if (!table) return;
72
-
73
- // 如果已经滚动到了底部,则返回顶部
74
- if (ref_scrollTop.current + table.clientHeight >= table.scrollHeight) {
75
- ref_scrollTop.current = 0;
76
- } else {
77
- // 否则,滚动一行的高度
78
- ref_scrollTop.current += scrollStep;
79
- }
80
-
81
- table.scrollTo({ top: ref_scrollTop.current, behavior: 'smooth' });
82
- };
83
-
84
- const _series = sortPie(series, order);
85
- const [_alignment, position] = alignment.split(' ');
86
- const length = _series.length;
87
-
88
- const onClick = useCallback(
89
- (e) => {
90
- const { dataset } = e.currentTarget;
91
- const { name } = dataset;
92
- filterData && interactive && filterData(name);
93
- },
94
- [interactive, filterData]
95
- );
96
-
97
- if (judge == 0) {
98
- _series.forEach((d) => {
99
- d.percent=0
100
- })
101
- }
102
-
103
- // 计算最大宽度
104
- const valueFont = config.value?.font || {};
105
- const percentFont = config.percent?.font || {};
106
- const valueSuffix = config.value?.suffix?.text || '';
107
- const valueSplitConfig = config.value?.splitConfig || {};
108
- const getValueStr = (item) => {
109
- let valueStr = (item.data?.y ?? item.value ?? '') + (valueSuffix || '');
110
- if (valueSplitConfig.show && valueSplitConfig.separator) {
111
- valueStr = valueStr.toString().replace(/\B(?=(\d{3})+(?!\d))/g, valueSplitConfig.separator);
112
- }
113
- return valueStr;
114
- };
115
- const valueMaxWidth = Math.max(
116
- ..._series.map(item => parseFloat(getCanvasTextWidth(getValueStr(item), valueFont.letterSpacing || 0, `${valueFont.fontSize || 12}px ${valueFont.fontFamily || 'Arial'}`)))
117
- );
118
- const percentMaxWidth = Math.max(
119
- ..._series.map(item => {
120
- const percentStr = (item.percent !== undefined ? item.percent : item.data?.percent) + '%';
121
- return parseFloat(getCanvasTextWidth(percentStr, percentFont.letterSpacing || 0, `${percentFont.fontSize || 12}px ${percentFont.fontFamily || 'Arial'}`));
122
- })
123
- );
124
- const nameMaxWidth = config.name?.maxWidth || 80;
125
-
126
- return (
127
- LegendType=="FixedWidth"?
128
- <div
129
- className='__easyv-legend-wrapper'
130
- style={{
131
- position: 'absolute',
132
- display: 'flex',
133
- ...getPosition(position, _alignment, x, y),
134
- height: loop.show ? height : 'auto',
135
- overflowY: loop.show ? 'scroll' : 'auto',
136
- pointerEvents:"none"//鼠标穿透
137
- }}
138
- ref={ref_container}
139
- >
140
- <ul
141
- style={{
142
- display: 'grid',
143
- gridGap: gridRowGap + 'px ' + gridColumnGap + 'px',
144
- gridTemplateColumns: `${nameMaxWidth}px ${valueMaxWidth}px ${percentMaxWidth}px`,
145
- }}
146
- >
147
- {_series.map((series, index) => {
148
- const { type, name, displayName, icon, selected } = series;
149
- const _icon = getIcon(type, icon, series?.config?.line?.type);
150
- return (
151
- <li
152
- key={index}
153
- onClick={onClick}
154
- data-name={name}
155
- style={{
156
- display: 'flex',
157
- opacity: selected === false ? opacity / 100 : 1,
158
- alignItems: 'center',
159
- cursor: "pointer",
160
- gap: _icon.gap,
161
- }}
162
- >
163
- {formatter ? (
164
- formatter(series, { ...config, valueMaxWidth, percentMaxWidth, nameMaxWidth })
165
- ) : (
166
- <>
167
- <span style={_icon} />
168
- <TextOverflow type={textOverflow} value={displayName || name} style={{
169
- ...font,
170
- width:maxWidth,
171
- fontStyle: italic ? 'italic' : 'normal',
172
- fontWeight: bold ? 'bold' : 'normal',
173
- }} speed={speed}></TextOverflow>
174
-
175
- </>
176
- )}
177
- </li>
178
- );
179
- })}
180
- </ul>
181
- </div>:<div
182
- className='__easyv-legend-wrapper'
183
- style={{
184
- display:'flex',
185
- flexWrap:"wrap",
186
- alignContent:alignment.split(" ")[0]=="center"&&(alignment.split(" ")[1]=="left"||alignment.split(" ")[1]=="right")?alignment.split(" ")[1]=="left"?"flex-start":"flex-end":alignment.split(" ")[0]=="left"?"flex-start":alignment.split(" ")[0]=="center"?"center":"flex-end",
187
- flexDirection: "column",
188
- position: 'absolute',
189
- ...getPosition(position, _alignment, x, y),
190
- height: loop.show ? height : 'auto',
191
- overflowY: loop.show ? 'scroll' : 'auto',
192
- width:"100%",
193
- pointerEvents:"none"
194
- }}
195
- ref={ref_container}
196
- >
197
-
198
- {[...Array(Math.ceil(series.length/gridTemplateColumns))].map((_,indexs)=>(
199
- <ul
200
- key={indexs}
201
- style={{
202
- display: 'flex',
203
- width: 'fit-content',
204
- justifyContent:alignment.split(" ")[0]=="left"?"flex-start":alignment.split(" ")[0]=="center"?"center":"flex-end",
205
- marginBottom:"0px",
206
- gap:`${gridRowGap}px ${gridColumnGap}px`,
207
- marginBottom:gridRowGap+"px"
208
- }}
209
- >
210
- {_series.map((series, index) => {
211
- if(Math.floor(index/gridTemplateColumns)==indexs){
212
- const { type, name, displayName, icon, selected } = series;
213
- const _icon = getIcon(type, icon, series?.config?.line?.type);
214
- return (
215
- <li
216
- key={index}
217
- onClick={onClick}
218
- data-name={name}
219
- style={{
220
- display: 'flex',
221
- opacity: selected === false ? opacity / 100 : 1,
222
- alignItems: 'center',
223
- cursor: "pointer",
224
- gap: _icon.gap,
225
- }}
226
- >
227
- {formatter ? (
228
- formatter(series, { ...config, valueMaxWidth, percentMaxWidth, nameMaxWidth })
229
- ) : (
230
- <>
231
- <span style={_icon} />
232
- <TextOverflow ShowType={LegendType} type="ellipsis" value={displayName || name} style={{
233
- ...font,
234
- fontStyle: italic ? 'italic' : 'normal',
235
- fontWeight: bold ? 'bold' : 'normal',
236
- minWidth:getCanvasTextWidth(displayName.substring(0,5) || name.substring(0,5),font.letterSpacing,`${font.fontSize}px ${font.fontFamily}` )
237
- }} speed={speed}></TextOverflow>
238
-
239
- </>
240
- )}
241
- </li>
242
- );
243
- }
244
- })}
245
- </ul>
246
- ))
247
- }
248
- </div>
249
- );
250
- }
251
- );
252
-
253
- const getPosition = (position, alignment, x = 0, y = 0) => {
254
- switch (position) {
255
- case 'top':
256
- return {
257
- left: alignment == 'left' ? 5 : alignment == 'center' ? '50%' : '',
258
- right: alignment == 'right' ? 10 : '',
259
- top: 5,
260
- transform: `translate3d(calc(${alignment == 'center' ? '-50%' : '0px'} + ${x}px), ${y}px, 0px)`
261
- };
262
- case 'right':
263
- return {
264
- top: '50%',
265
- right: 10,
266
- transform: `translate3d(${x}px, calc(-50% + ${y}px), 0px)`
267
- };
268
- case 'left':
269
- return {
270
- top: '50%',
271
- left: 5,
272
- transform: `translate3d(${x}px, calc(-50% + ${y}px), 0px)`
273
- };
274
- default: // bottom
275
- return {
276
- left: alignment == 'left' ? 5 : alignment == 'center' ? '50%' : '',
277
- right: alignment == 'right' ? 10 : '',
278
- bottom: 5,
279
- transform: `translate3d(calc(${alignment == 'center' ? '-50%' : '0px'} + ${x}px), ${y}px, 0px)`
280
- };
281
- }
282
- };
283
- const getCanvasTextWidth=(text,letterSpacing, font = '16px Arial')=>{
284
- const canvas = document.createElement('canvas');
285
- const ctx = canvas.getContext('2d');
286
- ctx.font = font;
287
- return ctx.measureText(text).width+(text.length)*letterSpacing+"px";
288
- // return ctx.measureText(text).width+(text.length-1)*letterSpacing+"px";//-1有bug
289
- }
1
+ /**
2
+ * 图例
3
+ */
4
+ import React, { memo, useCallback, useState, useEffect, useRef } from 'react';
5
+ import { getIcon, sortPie } from '../utils';
6
+ import TextOverflow from './TextOverflow';
7
+
8
+ const defaultFont = {
9
+ fontStyle: 'normal',
10
+ fontWeight: 'normal',
11
+ };
12
+
13
+ export default memo(
14
+ ({
15
+ series,
16
+ height,
17
+ config,
18
+ config: {
19
+ show,
20
+ order = '',
21
+ interactive,
22
+ LegendType,//类型
23
+ maxWidth,
24
+ textOverflow,
25
+ speed,
26
+ layout: {
27
+ alignment = 'right center',
28
+ gridTemplateColumns,
29
+ gridGap: { gridColumnGap, gridRowGap },
30
+ translate: { x, y },
31
+ },
32
+ loop = {},
33
+ font: { italic, bold, ...font } = defaultFont,
34
+ unselect: { opacity = 1 } = {},
35
+ },
36
+ filterData,
37
+ formatter,
38
+ judge
39
+ }) => {
40
+ if (!show) return null;
41
+
42
+ const ref_container = useRef(null); // 滚动容器
43
+ const ref_scrollTop = useRef(0); // 当前滚动距离
44
+
45
+ const [scrollStep, setScrollStep] = useState(0); // 行高度
46
+
47
+ // 初始化行高
48
+ useEffect(() => {
49
+ if (ref_container.current) {
50
+ const rowHeight = ref_container.current.querySelector('li').clientHeight + gridRowGap;
51
+ setScrollStep(rowHeight);
52
+ }
53
+ }, [gridRowGap]);
54
+
55
+ // 启动自动滚动定时器
56
+ useEffect(() => {
57
+ if (!loop.show) return;
58
+
59
+ ref_scrollTop.current = 0;
60
+
61
+ const timer = setInterval(() => {
62
+ handleAutoScroll();
63
+ }, loop.interval * 1000); // 每隔3秒滚动一次
64
+
65
+ // 清除定时器
66
+ return () => clearInterval(timer);
67
+ }, [scrollStep, loop.show, loop.interval])
68
+
69
+ const handleAutoScroll = () => {
70
+ const table = ref_container.current;
71
+ if (!table) return;
72
+
73
+ // 如果已经滚动到了底部,则返回顶部
74
+ if (ref_scrollTop.current + table.clientHeight >= table.scrollHeight) {
75
+ ref_scrollTop.current = 0;
76
+ } else {
77
+ // 否则,滚动一行的高度
78
+ ref_scrollTop.current += scrollStep;
79
+ }
80
+
81
+ table.scrollTo({ top: ref_scrollTop.current, behavior: 'smooth' });
82
+ };
83
+
84
+ const _series = sortPie(series, order);
85
+ const [_alignment, position] = alignment.split(' ');
86
+ const length = _series.length;
87
+
88
+ const onClick = useCallback(
89
+ (e) => {
90
+ const { dataset } = e.currentTarget;
91
+ const { name } = dataset;
92
+ filterData && interactive && filterData(name);
93
+ },
94
+ [interactive, filterData]
95
+ );
96
+
97
+ if (judge == 0) {
98
+ _series.forEach((d) => {
99
+ d.percent=0
100
+ })
101
+ }
102
+
103
+ // 计算最大宽度
104
+ const valueFont = config.value?.font || {};
105
+ const percentFont = config.percent?.font || {};
106
+ const valueSuffix = config.value?.suffix?.text || '';
107
+ const valueSplitConfig = config.value?.splitConfig || {};
108
+ const getValueStr = (item) => {
109
+ let valueStr = (item.data?.y ?? item.value ?? '') + (valueSuffix || '');
110
+ if (valueSplitConfig.show && valueSplitConfig.separator) {
111
+ valueStr = valueStr.toString().replace(/\B(?=(\d{3})+(?!\d))/g, valueSplitConfig.separator);
112
+ }
113
+ return valueStr;
114
+ };
115
+ const valueMaxWidth = Math.max(
116
+ ..._series.map(item => parseFloat(getCanvasTextWidth(getValueStr(item), valueFont.letterSpacing || 0, `${valueFont.fontSize || 12}px ${valueFont.fontFamily || 'Arial'}`)))
117
+ );
118
+ const percentMaxWidth = Math.max(
119
+ ..._series.map(item => {
120
+ const percentStr = (item.percent !== undefined ? item.percent : item.data?.percent) + '%';
121
+ return parseFloat(getCanvasTextWidth(percentStr, percentFont.letterSpacing || 0, `${percentFont.fontSize || 12}px ${percentFont.fontFamily || 'Arial'}`));
122
+ })
123
+ );
124
+ const nameMaxWidth = config.name?.maxWidth || 80;
125
+
126
+ return (
127
+ LegendType=="FixedWidth"?
128
+ <div
129
+ className='__easyv-legend-wrapper'
130
+ style={{
131
+ position: 'absolute',
132
+ display: 'flex',
133
+ ...getPosition(position, _alignment, x, y),
134
+ height: loop.show ? height : 'auto',
135
+ overflowY: loop.show ? 'scroll' : 'auto'
136
+ }}
137
+ ref={ref_container}
138
+ >
139
+ <ul
140
+ style={{
141
+ display: 'grid',
142
+ gridGap: gridRowGap + 'px ' + gridColumnGap + 'px',
143
+ gridTemplateColumns: `${nameMaxWidth}px ${valueMaxWidth}px ${percentMaxWidth}px`,
144
+ }}
145
+ >
146
+ {_series.map((series, index) => {
147
+ const { type, name, displayName, icon, selected } = series;
148
+ const _icon = getIcon(type, icon, series?.config?.line?.type);
149
+ return (
150
+ <li
151
+ key={index}
152
+ onClick={onClick}
153
+ data-name={name}
154
+ style={{
155
+ display: 'flex',
156
+ opacity: selected === false ? opacity / 100 : 1,
157
+ alignItems: 'center',
158
+ cursor: "pointer",
159
+ gap: _icon.gap,
160
+ }}
161
+ >
162
+ {formatter ? (
163
+ formatter(series, { ...config, valueMaxWidth, percentMaxWidth, nameMaxWidth })
164
+ ) : (
165
+ <>
166
+ <span style={_icon} />
167
+ <TextOverflow type={textOverflow} value={displayName || name} style={{
168
+ ...font,
169
+ width:maxWidth,
170
+ fontStyle: italic ? 'italic' : 'normal',
171
+ fontWeight: bold ? 'bold' : 'normal',
172
+ }} speed={speed}></TextOverflow>
173
+
174
+ </>
175
+ )}
176
+ </li>
177
+ );
178
+ })}
179
+ </ul>
180
+ </div>:<div
181
+ className='__easyv-legend-wrapper'
182
+ style={{
183
+ display:'flex',
184
+ flexWrap:"wrap",
185
+ alignContent:alignment.split(" ")[0]=="center"&&(alignment.split(" ")[1]=="left"||alignment.split(" ")[1]=="right")?alignment.split(" ")[1]=="left"?"flex-start":"flex-end":alignment.split(" ")[0]=="left"?"flex-start":alignment.split(" ")[0]=="center"?"center":"flex-end",
186
+ flexDirection: "column",
187
+ position: 'absolute',
188
+ ...getPosition(position, _alignment, x, y),
189
+ height: loop.show ? height : 'auto',
190
+ overflowY: loop.show ? 'scroll' : 'auto'
191
+ }}
192
+ ref={ref_container}
193
+ >
194
+
195
+ {[...Array(Math.ceil(series.length/gridTemplateColumns))].map((_,indexs)=>(
196
+ <ul
197
+ key={indexs}
198
+ style={{
199
+ display: 'flex',
200
+ width: 'fit-content',
201
+ justifyContent:alignment.split(" ")[0]=="left"?"flex-start":alignment.split(" ")[0]=="center"?"center":"flex-end",
202
+ marginBottom:"0px",
203
+ gap:`${gridRowGap}px ${gridColumnGap}px`,
204
+ marginBottom:gridRowGap+"px"
205
+ }}
206
+ >
207
+ {_series.map((series, index) => {
208
+ if(Math.floor(index/gridTemplateColumns)==indexs){
209
+ const { type, name, displayName, icon, selected } = series;
210
+ const _icon = getIcon(type, icon, series?.config?.line?.type);
211
+ return (
212
+ <li
213
+ key={index}
214
+ onClick={onClick}
215
+ data-name={name}
216
+ style={{
217
+ display: 'flex',
218
+ opacity: selected === false ? opacity / 100 : 1,
219
+ alignItems: 'center',
220
+ cursor: "pointer",
221
+ gap: _icon.gap,
222
+ }}
223
+ >
224
+ {formatter ? (
225
+ formatter(series, { ...config, valueMaxWidth, percentMaxWidth, nameMaxWidth })
226
+ ) : (
227
+ <>
228
+ <span style={_icon} />
229
+ <TextOverflow ShowType={LegendType} type="ellipsis" value={displayName || name} style={{
230
+ ...font,
231
+ fontStyle: italic ? 'italic' : 'normal',
232
+ fontWeight: bold ? 'bold' : 'normal',
233
+ minWidth:getCanvasTextWidth(displayName?(displayName.substring(0,5) || name.substring(0,5)):"",font.letterSpacing,`${font.fontSize}px ${font.fontFamily}` )
234
+ }} speed={speed}></TextOverflow>
235
+
236
+ </>
237
+ )}
238
+ </li>
239
+ );
240
+ }
241
+ })}
242
+ </ul>
243
+ ))
244
+ }
245
+ </div>
246
+ );
247
+ }
248
+ );
249
+
250
+ const getPosition = (position, alignment, x = 0, y = 0) => {
251
+ switch (position) {
252
+ case 'top':
253
+ return {
254
+ left: alignment == 'left' ? 5 : alignment == 'center' ? '50%' : '',
255
+ right: alignment == 'right' ? 10 : '',
256
+ top: 5,
257
+ transform: `translate3d(calc(${alignment == 'center' ? '-50%' : '0px'} + ${x}px), ${y}px, 0px)`
258
+ };
259
+ case 'right':
260
+ return {
261
+ top: '50%',
262
+ right: 10,
263
+ transform: `translate3d(${x}px, calc(-50% + ${y}px), 0px)`
264
+ };
265
+ case 'left':
266
+ return {
267
+ top: '50%',
268
+ left: 5,
269
+ transform: `translate3d(${x}px, calc(-50% + ${y}px), 0px)`
270
+ };
271
+ default: // bottom
272
+ return {
273
+ left: alignment == 'left' ? 5 : alignment == 'center' ? '50%' : '',
274
+ right: alignment == 'right' ? 10 : '',
275
+ bottom: 5,
276
+ transform: `translate3d(calc(${alignment == 'center' ? '-50%' : '0px'} + ${x}px), ${y}px, 0px)`
277
+ };
278
+ }
279
+ };
280
+ const getCanvasTextWidth=(text,letterSpacing, font = '16px Arial')=>{
281
+ const canvas = document.createElement('canvas');
282
+ const ctx = canvas.getContext('2d');
283
+ ctx.font = font;
284
+ return ctx.measureText(text).width+(text.length)*letterSpacing+"px";
285
+ // return ctx.measureText(text).width+(text.length-1)*letterSpacing+"px";//-1有bug
286
+ }