@easyv/charts 1.10.2 → 1.10.4
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.
- package/lib/components/Axis.js +2 -2
- package/lib/hooks/useAxes.js +7 -1
- package/lib/hooks/useCarouselAxisX.js +191 -44
- package/package.json +1 -1
- package/src/components/Axis.tsx +1 -1
- package/src/hooks/useAxes.js +44 -32
- package/src/hooks/useCarouselAxisX.js +318 -181
package/lib/components/Axis.js
CHANGED
|
@@ -261,8 +261,8 @@ var Label = function Label(_ref5) {
|
|
|
261
261
|
var _style = style && ((0, _typeof2["default"])(style) == "object" ? style : style(_label));
|
|
262
262
|
//x轴显示
|
|
263
263
|
return /*#__PURE__*/_react["default"].createElement("foreignObject", {
|
|
264
|
-
width: "100%"
|
|
265
|
-
|
|
264
|
+
width: "100%",
|
|
265
|
+
height: "1px" //设置一个高度,让foreignObject正常显示内部内容,又不至于因为太高发生遮挡
|
|
266
266
|
// x={x}
|
|
267
267
|
// y={y}
|
|
268
268
|
,
|
package/lib/hooks/useAxes.js
CHANGED
|
@@ -113,6 +113,12 @@ var _default = exports["default"] = function _default(_ref) {
|
|
|
113
113
|
cPercent = controlInfo.cPercent;
|
|
114
114
|
var width = chartWidth;
|
|
115
115
|
var height = chartHeight - cHeight;
|
|
116
|
+
var axesMemoKey = (0, _react.useMemo)(function () {
|
|
117
|
+
return JSON.stringify(axes);
|
|
118
|
+
}, [axes]);
|
|
119
|
+
var controlInfoMemoKey = (0, _react.useMemo)(function () {
|
|
120
|
+
return JSON.stringify(controlInfo);
|
|
121
|
+
}, [controlInfo]);
|
|
116
122
|
var _axes = (0, _react.useMemo)(function () {
|
|
117
123
|
var tmp = new Map();
|
|
118
124
|
var xAxisPositions = new Set(); //用Set去重,去掉重复的x轴
|
|
@@ -409,6 +415,6 @@ var _default = exports["default"] = function _default(_ref) {
|
|
|
409
415
|
return JSON.parse(d);
|
|
410
416
|
}));
|
|
411
417
|
return tmp;
|
|
412
|
-
}, [axes, controlInfo]);
|
|
418
|
+
}, [axesMemoKey, controlInfoMemoKey, axes, controlInfo]);
|
|
413
419
|
return _axes;
|
|
414
420
|
};
|
|
@@ -5,8 +5,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports["default"] = void 0;
|
|
8
|
-
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
8
|
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
9
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
10
10
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
11
11
|
var _react = require("react");
|
|
12
12
|
var _popmotion = require("popmotion");
|
|
@@ -14,14 +14,14 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
|
|
|
14
14
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
15
15
|
var initialState = {
|
|
16
16
|
currentIndex: null,
|
|
17
|
-
flag: false
|
|
17
|
+
flag: false // 首次加载标识:true-首次加载不执行动画,false-可执行轮播
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* x轴滚动逻辑
|
|
22
|
-
* @param {Object} axis x轴配置项
|
|
23
|
-
* @param {Object} config x轴轮播动画的配置项
|
|
24
|
-
* @returns {Map}
|
|
20
|
+
/**
|
|
21
|
+
* x轴滚动逻辑
|
|
22
|
+
* @param {Object} axis x轴配置项
|
|
23
|
+
* @param {Object} config x轴轮播动画的配置项
|
|
24
|
+
* @returns {Map} 返回更新后的x轴配置(ticks/scaler/range 变更)
|
|
25
25
|
*/
|
|
26
26
|
var _default = exports["default"] = function _default(axis, config, isHover, controlInfo, active) {
|
|
27
27
|
var show = config.show,
|
|
@@ -30,7 +30,6 @@ var _default = exports["default"] = function _default(axis, config, isHover, con
|
|
|
30
30
|
hover = config.hover;
|
|
31
31
|
var isC = controlInfo.isC,
|
|
32
32
|
cPercent = controlInfo.cPercent;
|
|
33
|
-
var time = duration + interval;
|
|
34
33
|
var count = axis.tickCount,
|
|
35
34
|
allTicks = axis.allTicks,
|
|
36
35
|
scaler = axis.scaler,
|
|
@@ -44,6 +43,9 @@ var _default = exports["default"] = function _default(axis, config, isHover, con
|
|
|
44
43
|
var scale = isC ? cPercent : 1;
|
|
45
44
|
var _start = start / scale;
|
|
46
45
|
var _end = end / scale;
|
|
46
|
+
var canCarousel = (0, _react.useMemo)(function () {
|
|
47
|
+
return show && active && tickLength > tickCount;
|
|
48
|
+
}, [show, active, tickLength, tickCount]);
|
|
47
49
|
var _useState = (0, _react.useState)({
|
|
48
50
|
scaler: scaler,
|
|
49
51
|
step: step,
|
|
@@ -56,8 +58,69 @@ var _default = exports["default"] = function _default(axis, config, isHover, con
|
|
|
56
58
|
_useState4 = (0, _slicedToArray2["default"])(_useState3, 2),
|
|
57
59
|
status = _useState4[0],
|
|
58
60
|
setStatus = _useState4[1];
|
|
61
|
+
var _useState5 = (0, _react.useState)(true),
|
|
62
|
+
_useState6 = (0, _slicedToArray2["default"])(_useState5, 2),
|
|
63
|
+
ready = _useState6[0],
|
|
64
|
+
setReady = _useState6[1]; // true 才允许开启下一轮动画(用于 interval 等待)
|
|
65
|
+
var _useState7 = (0, _react.useState)(0),
|
|
66
|
+
_useState8 = (0, _slicedToArray2["default"])(_useState7, 2),
|
|
67
|
+
wakeToken = _useState8[0],
|
|
68
|
+
setWakeToken = _useState8[1];
|
|
69
|
+
var hoverRef = (0, _react.useRef)(isHover);
|
|
70
|
+
var prevHoverRef = (0, _react.useRef)(isHover);
|
|
71
|
+
var intervalTimerRef = (0, _react.useRef)(null);
|
|
72
|
+
var isAnimatingRef = (0, _react.useRef)(false);
|
|
73
|
+
var latestRef = (0, _react.useRef)({
|
|
74
|
+
allTicks: allTicks,
|
|
75
|
+
scaler: scaler,
|
|
76
|
+
tickCount: tickCount,
|
|
77
|
+
tickLength: tickLength,
|
|
78
|
+
lengthWithoutPaddingOuter: lengthWithoutPaddingOuter,
|
|
79
|
+
_start: _start,
|
|
80
|
+
_end: _end,
|
|
81
|
+
interval: interval,
|
|
82
|
+
duration: duration,
|
|
83
|
+
hover: hover
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 避免 hover 触发 render 时 effect 误重启动画:用 ref 持有最新输入
|
|
87
|
+
(0, _react.useEffect)(function () {
|
|
88
|
+
latestRef.current = {
|
|
89
|
+
allTicks: allTicks,
|
|
90
|
+
scaler: scaler,
|
|
91
|
+
tickCount: tickCount,
|
|
92
|
+
tickLength: tickLength,
|
|
93
|
+
lengthWithoutPaddingOuter: lengthWithoutPaddingOuter,
|
|
94
|
+
_start: _start,
|
|
95
|
+
_end: _end,
|
|
96
|
+
interval: interval,
|
|
97
|
+
duration: duration,
|
|
98
|
+
hover: hover
|
|
99
|
+
};
|
|
100
|
+
}, [allTicks, scaler, tickCount, tickLength, lengthWithoutPaddingOuter, _start, _end, interval, duration, hover]);
|
|
101
|
+
|
|
102
|
+
// hover 变化不打断正在进行的动画:只更新引用、并在需要时取消「下一轮」定时器
|
|
103
|
+
(0, _react.useEffect)(function () {
|
|
104
|
+
var prev = prevHoverRef.current;
|
|
105
|
+
prevHoverRef.current = isHover;
|
|
106
|
+
hoverRef.current = isHover;
|
|
107
|
+
if (latestRef.current.hover && isHover && intervalTimerRef.current) {
|
|
108
|
+
clearTimeout(intervalTimerRef.current);
|
|
109
|
+
intervalTimerRef.current = null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 仅在 hover 从 true -> false 时唤醒轮播(避免在非 hover 状态下反复唤醒导致 interval 失效)
|
|
113
|
+
if (latestRef.current.hover && prev === true && isHover === false && canCarousel && status.currentIndex !== null && !isAnimatingRef.current) {
|
|
114
|
+
setReady(true);
|
|
115
|
+
setWakeToken(function (v) {
|
|
116
|
+
return v + 1;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}, [isHover, canCarousel, status.currentIndex]);
|
|
120
|
+
|
|
121
|
+
// 初始化:仅在基础条件变化时设置初始索引
|
|
59
122
|
(0, _react.useEffect)(function () {
|
|
60
|
-
if (show &&
|
|
123
|
+
if (show && tickLength > tickCount) {
|
|
61
124
|
setStatus({
|
|
62
125
|
currentIndex: 0,
|
|
63
126
|
flag: true
|
|
@@ -65,95 +128,179 @@ var _default = exports["default"] = function _default(axis, config, isHover, con
|
|
|
65
128
|
} else {
|
|
66
129
|
setStatus(initialState);
|
|
67
130
|
}
|
|
68
|
-
}, [show,
|
|
69
|
-
(0, _react.useEffect)(function () {
|
|
70
|
-
var handler;
|
|
71
|
-
if (!(hover && isHover) && show && time && tickLength > tickCount && active) {
|
|
72
|
-
handler = setInterval(function () {
|
|
73
|
-
setStatus(function (_ref) {
|
|
74
|
-
var currentIndex = _ref.currentIndex;
|
|
75
|
-
var tmp = +currentIndex + 1;
|
|
76
|
-
return {
|
|
77
|
-
currentIndex: tmp >= tickLength ? 0 : tmp,
|
|
78
|
-
flag: false
|
|
79
|
-
};
|
|
80
|
-
});
|
|
81
|
-
}, time * 1000);
|
|
82
|
-
}
|
|
83
|
-
return function () {
|
|
84
|
-
handler && clearInterval(handler);
|
|
85
|
-
};
|
|
86
|
-
}, [show, time, tickCount, tickLength, hover, isHover, active]);
|
|
131
|
+
}, [show, tickCount, tickLength]);
|
|
87
132
|
(0, _react.useEffect)(function () {
|
|
88
133
|
var animation;
|
|
89
134
|
var currentIndex = status.currentIndex,
|
|
90
135
|
flag = status.flag;
|
|
91
|
-
|
|
92
|
-
|
|
136
|
+
var latest = latestRef.current;
|
|
137
|
+
|
|
138
|
+
// 执行条件:显示、非hover暂停、激活、有足够的轮播项
|
|
139
|
+
var canRun = canCarousel && !(latest.hover && hoverRef.current);
|
|
140
|
+
if (currentIndex !== null && canRun) {
|
|
93
141
|
if (flag) {
|
|
94
|
-
|
|
142
|
+
// 首次加载:仅初始化视图,不执行动画
|
|
143
|
+
var _step = latest.lengthWithoutPaddingOuter / latest.tickCount;
|
|
144
|
+
var _ticks = latest.allTicks.slice(currentIndex, latest.tickCount);
|
|
95
145
|
setState({
|
|
96
146
|
step: _step,
|
|
97
147
|
ticks: _ticks,
|
|
98
|
-
scaler: scaler.copy().domain(_ticks).range([_start, _end])
|
|
148
|
+
scaler: latest.scaler.copy().domain(_ticks).range([latest._start, latest._end])
|
|
99
149
|
});
|
|
150
|
+
|
|
151
|
+
// 首次加载完成后,启动第一轮动画(等待interval后执行)
|
|
152
|
+
if (latest.interval >= 0) {
|
|
153
|
+
intervalTimerRef.current = setTimeout(function () {
|
|
154
|
+
setStatus(function (prev) {
|
|
155
|
+
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
156
|
+
flag: false
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}, latest.interval * 1000);
|
|
160
|
+
} else {
|
|
161
|
+
setStatus(function (prev) {
|
|
162
|
+
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
163
|
+
flag: false
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
100
167
|
} else {
|
|
168
|
+
// 非首次加载:执行动画 + 后续逻辑
|
|
169
|
+
if (!ready) return;
|
|
170
|
+
var _step2 = latest.lengthWithoutPaddingOuter / latest.tickCount;
|
|
101
171
|
animation = (0, _popmotion.animate)({
|
|
102
172
|
from: 0,
|
|
103
173
|
to: -1,
|
|
104
|
-
duration: duration * 1000,
|
|
174
|
+
duration: latest.duration * 1000,
|
|
105
175
|
ease: _popmotion.linear,
|
|
106
176
|
onPlay: function onPlay() {
|
|
177
|
+
isAnimatingRef.current = true;
|
|
178
|
+
// 动画开始:更新ticks和scaler初始范围
|
|
179
|
+
// 这里需要保证动画是「当前项滚出,下一项滚入」
|
|
180
|
+
// 因此后续窗口从 currentIndex + 1 开始,避免首项重复导致视觉上「从第一项滚到第一项」的闪烁
|
|
107
181
|
setState(function (axis) {
|
|
108
182
|
var ticks = axis.ticks,
|
|
109
183
|
scaler = axis.scaler;
|
|
110
184
|
var _ticks2 = (0, _slicedToArray2["default"])(ticks, 1),
|
|
111
185
|
tick = _ticks2[0];
|
|
112
|
-
var _ticks = [tick].concat((0, _toConsumableArray2["default"])(getTicks(allTicks, currentIndex, tickCount)));
|
|
186
|
+
var _ticks = [tick].concat((0, _toConsumableArray2["default"])(getTicks(latest.allTicks, currentIndex + 1, latest.tickCount)));
|
|
113
187
|
return _objectSpread(_objectSpread({}, axis), {}, {
|
|
114
188
|
ticks: _ticks,
|
|
115
|
-
scaler: scaler.copy().range([_start, _end +
|
|
189
|
+
scaler: scaler.copy().range([latest._start, latest._end + _step2]).domain(_ticks)
|
|
116
190
|
});
|
|
117
191
|
});
|
|
118
192
|
},
|
|
119
193
|
onUpdate: function onUpdate(v) {
|
|
194
|
+
// 动画过程:实时更新scaler范围(实现滚动效果)
|
|
120
195
|
setState(function (axis) {
|
|
121
196
|
var scaler = axis.scaler,
|
|
122
197
|
step = axis.step;
|
|
123
198
|
return _objectSpread(_objectSpread({}, axis), {}, {
|
|
124
|
-
scaler: scaler.copy().range([_start + step * v, _end + step + step * v])
|
|
199
|
+
scaler: scaler.copy().range([latest._start + step * v, latest._end + step + step * v])
|
|
125
200
|
});
|
|
126
201
|
});
|
|
127
202
|
},
|
|
128
203
|
onComplete: function onComplete() {
|
|
204
|
+
isAnimatingRef.current = false;
|
|
205
|
+
// 本轮结束后进入等待态,避免立即开启下一轮(保证 interval 生效)
|
|
206
|
+
setReady(false);
|
|
207
|
+
// 动画完成:重置ticks和scaler
|
|
129
208
|
setState(function (axis) {
|
|
130
209
|
var scaler = axis.scaler,
|
|
131
210
|
ticks = axis.ticks;
|
|
132
211
|
var _ticks = ticks.slice(1, ticks.length);
|
|
133
212
|
return _objectSpread(_objectSpread({}, axis), {}, {
|
|
134
213
|
ticks: _ticks,
|
|
135
|
-
scaler: scaler.copy().range([_start, _end]).domain(_ticks)
|
|
214
|
+
scaler: scaler.copy().range([latest._start, latest._end]).domain(_ticks)
|
|
136
215
|
});
|
|
137
216
|
});
|
|
217
|
+
var nextIndex = (currentIndex + 1) % latest.tickLength;
|
|
218
|
+
|
|
219
|
+
// 无论是否 hover,都先把 currentIndex 同步到下一屏起点
|
|
220
|
+
// 否则在 interval 暂停期触发 hover/离开,会用旧索引重启,视觉上“回滚一格”
|
|
221
|
+
setStatus(function (prev) {
|
|
222
|
+
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
223
|
+
currentIndex: nextIndex
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// hover 触发时:让当前动画跑完后暂停(不再排下一轮)
|
|
228
|
+
if (latest.hover && hoverRef.current) return;
|
|
229
|
+
|
|
230
|
+
// 非hover:动画完成后,等待interval时长,再触发下一轮动画
|
|
231
|
+
intervalTimerRef.current = setTimeout(function () {
|
|
232
|
+
setReady(true);
|
|
233
|
+
setWakeToken(function (v) {
|
|
234
|
+
return v + 1;
|
|
235
|
+
});
|
|
236
|
+
}, latest.interval * 1000);
|
|
138
237
|
}
|
|
139
238
|
});
|
|
140
239
|
}
|
|
141
|
-
}
|
|
142
|
-
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 清理副作用:停止动画、清除定时器
|
|
243
|
+
return function () {
|
|
244
|
+
if (animation) {
|
|
245
|
+
animation.stop();
|
|
246
|
+
isAnimatingRef.current = false;
|
|
247
|
+
}
|
|
248
|
+
if (intervalTimerRef.current) {
|
|
249
|
+
clearTimeout(intervalTimerRef.current);
|
|
250
|
+
intervalTimerRef.current = null;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}, [canCarousel, status.currentIndex, status.flag, wakeToken, ready]);
|
|
254
|
+
|
|
255
|
+
// 重置逻辑:当无有效索引时恢复初始状态
|
|
256
|
+
(0, _react.useEffect)(function () {
|
|
257
|
+
if (status.currentIndex === null) {
|
|
258
|
+
var _ticks = scaler.type === "linear" ? scaler.domain() : allTicks;
|
|
143
259
|
setState({
|
|
144
260
|
step: step,
|
|
145
|
-
scaler: scaler.copy().domain(
|
|
261
|
+
scaler: scaler.copy().domain(_ticks).range([_start, _end]),
|
|
146
262
|
ticks: ticks
|
|
147
263
|
});
|
|
148
264
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
265
|
+
}, [status.currentIndex, scaler, _start, _end, step, ticks, allTicks]);
|
|
266
|
+
|
|
267
|
+
// 当 x 轴可见项数量(count)发生变化时,安全地重置轮播节奏
|
|
268
|
+
(0, _react.useEffect)(function () {
|
|
269
|
+
// 清掉当前的 interval 计时器,避免用旧配置继续排队
|
|
270
|
+
if (intervalTimerRef.current) {
|
|
271
|
+
clearTimeout(intervalTimerRef.current);
|
|
272
|
+
intervalTimerRef.current = null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 重置动画节奏,让配置变更后「立即」按最新的 tickCount/tickLength 重新初始化
|
|
276
|
+
// 不能简单 setStatus(initialState),否则会覆盖上方的初始化 effect(先开轮播再改数量时会被清成 null,导致展示全部且不轮播)
|
|
277
|
+
isAnimatingRef.current = false;
|
|
278
|
+
setReady(true);
|
|
279
|
+
setStatus(function () {
|
|
280
|
+
// 与初始化 effect 保持一致:只要满足条件就从 0 开始重新初始化
|
|
281
|
+
if (show && active && tickLength > tickCount) {
|
|
282
|
+
return {
|
|
283
|
+
currentIndex: 0,
|
|
284
|
+
flag: true
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return initialState;
|
|
288
|
+
});
|
|
289
|
+
setWakeToken(function (v) {
|
|
290
|
+
return v + 1;
|
|
291
|
+
});
|
|
292
|
+
}, [count, show, active, tickLength, tickCount]);
|
|
153
293
|
return _objectSpread(_objectSpread(_objectSpread({}, axis), state), {}, {
|
|
154
294
|
controlEnd: _end
|
|
155
295
|
});
|
|
156
296
|
};
|
|
297
|
+
/**
|
|
298
|
+
* 获取指定索引范围的ticks(支持循环)
|
|
299
|
+
* @param {Array} ticks 所有ticks
|
|
300
|
+
* @param {number} currentIndex 当前起始索引
|
|
301
|
+
* @param {number} length 需要的长度
|
|
302
|
+
* @returns {Array} 目标ticks数组
|
|
303
|
+
*/
|
|
157
304
|
var getTicks = function getTicks(ticks, currentIndex, length) {
|
|
158
305
|
var _currentIndex = +currentIndex;
|
|
159
306
|
var ticksLength = ticks.length;
|
package/package.json
CHANGED
package/src/components/Axis.tsx
CHANGED
package/src/hooks/useAxes.js
CHANGED
|
@@ -20,19 +20,19 @@ const getNewDomain = (
|
|
|
20
20
|
step,
|
|
21
21
|
extent = {},
|
|
22
22
|
numericalRangeModel = "value",
|
|
23
|
-
percentageExtent = { max: 100, min: 100 }
|
|
23
|
+
percentageExtent = { max: 100, min: 100 },
|
|
24
24
|
) => {
|
|
25
25
|
let { min: defaultBottom = "", max: defaultTop = "" } = extent;
|
|
26
26
|
const { min: defaultPercentageMin = 100, max: defaultPercentageMax = 100 } =
|
|
27
27
|
percentageExtent;
|
|
28
28
|
let bottom = defaultBottom,
|
|
29
29
|
top = defaultTop;
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
let newDomain = [];
|
|
32
32
|
//能进入这个函数,说明extent中min和max至少有一个是缺失的,如果max存在,意味着纵轴上限被固定
|
|
33
33
|
let min = domain[0],
|
|
34
34
|
max = domain[1];
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
let minCount = Math.pow(10, getCount(min)),
|
|
37
37
|
maxCount = Math.pow(10, getCount(max));
|
|
38
38
|
//轴标签,范围模式,百分比模式
|
|
@@ -47,9 +47,15 @@ const getNewDomain = (
|
|
|
47
47
|
newDomain[0] =
|
|
48
48
|
bottom != "" ? bottom : Math.floor(domain[0] / minCount) * minCount;
|
|
49
49
|
newDomain[1] =
|
|
50
|
-
top != ""
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
top != ""
|
|
51
|
+
? top
|
|
52
|
+
: maxCount == 1
|
|
53
|
+
? domain[1]
|
|
54
|
+
: Math.ceil(domain[1] / maxCount) * maxCount;
|
|
55
|
+
if (newDomain[0] > 0 && newDomain[0] < 10 && defaultBottom === "")
|
|
56
|
+
newDomain[0] = 0;
|
|
57
|
+
if (newDomain[1] < 0 && newDomain[1] > -10 && defaultTop === "")
|
|
58
|
+
newDomain[1] = 0;
|
|
53
59
|
break;
|
|
54
60
|
case "step":
|
|
55
61
|
if (defaultTop != "") {
|
|
@@ -65,8 +71,8 @@ const getNewDomain = (
|
|
|
65
71
|
}
|
|
66
72
|
break;
|
|
67
73
|
}
|
|
68
|
-
if(newDomain[0]==newDomain[1]){
|
|
69
|
-
newDomain[1]+=1;
|
|
74
|
+
if (newDomain[0] == newDomain[1]) {
|
|
75
|
+
newDomain[1] += 1;
|
|
70
76
|
}
|
|
71
77
|
return newDomain;
|
|
72
78
|
};
|
|
@@ -103,10 +109,15 @@ export default ({
|
|
|
103
109
|
const { isC, cHeight, cWidth, cPercent } = controlInfo;
|
|
104
110
|
const width = chartWidth;
|
|
105
111
|
const height = chartHeight - cHeight;
|
|
112
|
+
const axesMemoKey = useMemo(() => JSON.stringify(axes), [axes]);
|
|
113
|
+
const controlInfoMemoKey = useMemo(
|
|
114
|
+
() => JSON.stringify(controlInfo),
|
|
115
|
+
[controlInfo],
|
|
116
|
+
);
|
|
106
117
|
|
|
107
118
|
const _axes = useMemo(() => {
|
|
108
119
|
const tmp = new Map();
|
|
109
|
-
const xAxisPositions = new Set();
|
|
120
|
+
const xAxisPositions = new Set(); //用Set去重,去掉重复的x轴
|
|
110
121
|
axes.forEach((item) => {
|
|
111
122
|
const {
|
|
112
123
|
config: {
|
|
@@ -154,14 +165,14 @@ export default ({
|
|
|
154
165
|
orientation === "top" || orientation === "bottom"
|
|
155
166
|
? "horizontal"
|
|
156
167
|
: orientation === "left" || orientation === "right"
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
? "vertical"
|
|
169
|
+
: "";
|
|
159
170
|
const length =
|
|
160
171
|
direction === "horizontal"
|
|
161
172
|
? width
|
|
162
173
|
: direction === "vertical"
|
|
163
|
-
|
|
164
|
-
|
|
174
|
+
? height
|
|
175
|
+
: 0;
|
|
165
176
|
|
|
166
177
|
const _paddingOuter = paddingOuter * length;
|
|
167
178
|
const start = _paddingOuter / 2;
|
|
@@ -202,8 +213,8 @@ export default ({
|
|
|
202
213
|
return ticks
|
|
203
214
|
? ticks
|
|
204
215
|
: scaler.ticks
|
|
205
|
-
|
|
206
|
-
|
|
216
|
+
? scaler.ticks(tickCount)
|
|
217
|
+
: scaler.domain();
|
|
207
218
|
}
|
|
208
219
|
|
|
209
220
|
/**
|
|
@@ -228,7 +239,7 @@ export default ({
|
|
|
228
239
|
mode,
|
|
229
240
|
newDomain,
|
|
230
241
|
tickCount,
|
|
231
|
-
step
|
|
242
|
+
step,
|
|
232
243
|
) {
|
|
233
244
|
let _ticks = allTicks;
|
|
234
245
|
if (type === "ordinal") {
|
|
@@ -279,7 +290,7 @@ export default ({
|
|
|
279
290
|
if (_isClipAxis) {
|
|
280
291
|
clipAxisDomain.forEach((domain, index) => {
|
|
281
292
|
clipAxisTickCount.push(
|
|
282
|
-
getTickCount(domain, clipAxisCount[index], decimal)
|
|
293
|
+
getTickCount(domain, clipAxisCount[index], decimal),
|
|
283
294
|
);
|
|
284
295
|
});
|
|
285
296
|
|
|
@@ -304,14 +315,14 @@ export default ({
|
|
|
304
315
|
start,
|
|
305
316
|
end,
|
|
306
317
|
clipPosition,
|
|
307
|
-
clipMargin
|
|
318
|
+
clipMargin,
|
|
308
319
|
);
|
|
309
320
|
let newClipAxisDomain = [];
|
|
310
321
|
//如果非自适应模式,计算新的domain,传入scaler,适配强制步长或者数量强制
|
|
311
322
|
if (!isNaN(domain[1]) && !auto) {
|
|
312
323
|
clipAxisDomain.forEach((domain, index) => {
|
|
313
324
|
newClipAxisDomain.push(
|
|
314
|
-
getNewDomain(domain, mode, clipAxisStep[index])
|
|
325
|
+
getNewDomain(domain, mode, clipAxisStep[index]),
|
|
315
326
|
);
|
|
316
327
|
});
|
|
317
328
|
} else {
|
|
@@ -322,14 +333,14 @@ export default ({
|
|
|
322
333
|
let clipAxisScaler = [];
|
|
323
334
|
newClipAxisDomain.forEach((domain, index) => {
|
|
324
335
|
clipAxisScaler.push(
|
|
325
|
-
setScaler(scales, type, domain, clipAxisRange[index])
|
|
336
|
+
setScaler(scales, type, domain, clipAxisRange[index]),
|
|
326
337
|
);
|
|
327
338
|
});
|
|
328
339
|
//clipAxisAllTicks作用是使用scaler.ticks方法,来计算标签集合
|
|
329
340
|
let clipAxisAllTicks = [];
|
|
330
341
|
clipAxisScaler.forEach((scaler, index) => {
|
|
331
342
|
clipAxisAllTicks.push(
|
|
332
|
-
getAllTicks(scaler, ticks, clipAxisTickCount[index])
|
|
343
|
+
getAllTicks(scaler, ticks, clipAxisTickCount[index]),
|
|
333
344
|
);
|
|
334
345
|
});
|
|
335
346
|
|
|
@@ -345,8 +356,8 @@ export default ({
|
|
|
345
356
|
mode,
|
|
346
357
|
newClipAxisDomain[index],
|
|
347
358
|
clipAxisTickCount[index],
|
|
348
|
-
clipAxisStep[index]
|
|
349
|
-
)
|
|
359
|
+
clipAxisStep[index],
|
|
360
|
+
),
|
|
350
361
|
);
|
|
351
362
|
});
|
|
352
363
|
let lengthWithoutPaddingOuter = length - _paddingOuter;
|
|
@@ -378,8 +389,8 @@ export default ({
|
|
|
378
389
|
direction === "horizontal"
|
|
379
390
|
? [start, end]
|
|
380
391
|
: direction === "vertical"
|
|
381
|
-
|
|
382
|
-
|
|
392
|
+
? [end, start]
|
|
393
|
+
: [0, 0];
|
|
383
394
|
if (reverse) range = [range[1], range[0]];
|
|
384
395
|
let newDomain = domain;
|
|
385
396
|
const fixedDomain = extent && extent.min != "" && extent.max != ""; //判断配置项中是否强制了最大最小值,如果已经被强制了,就不计算newDomain
|
|
@@ -390,7 +401,7 @@ export default ({
|
|
|
390
401
|
_step,
|
|
391
402
|
extent,
|
|
392
403
|
numericalRangeModel,
|
|
393
|
-
percentageExtent
|
|
404
|
+
percentageExtent,
|
|
394
405
|
);
|
|
395
406
|
}
|
|
396
407
|
let scaler = scales[type]().domain(newDomain).range(range);
|
|
@@ -400,8 +411,8 @@ export default ({
|
|
|
400
411
|
const allTicks = ticks
|
|
401
412
|
? ticks
|
|
402
413
|
: scaler.ticks
|
|
403
|
-
|
|
404
|
-
|
|
414
|
+
? scaler.ticks(tickCount)
|
|
415
|
+
: scaler.domain();
|
|
405
416
|
let _ticks = allTicks;
|
|
406
417
|
|
|
407
418
|
if (type === "ordinal") {
|
|
@@ -464,8 +475,8 @@ export default ({
|
|
|
464
475
|
direction_ === "horizontal"
|
|
465
476
|
? [start_, end_]
|
|
466
477
|
: direction_ === "vertical"
|
|
467
|
-
|
|
468
|
-
|
|
478
|
+
? [end_, start_]
|
|
479
|
+
: [0, 0];
|
|
469
480
|
scaler = scales[type]().domain(newDomain).range(range);
|
|
470
481
|
scaler.type = type;
|
|
471
482
|
const controlOuter = len - outer;
|
|
@@ -491,8 +502,9 @@ export default ({
|
|
|
491
502
|
});
|
|
492
503
|
}
|
|
493
504
|
});
|
|
494
|
-
tmp.get("x") &&
|
|
505
|
+
tmp.get("x") &&
|
|
506
|
+
(tmp.get("x").positions = [...xAxisPositions].map((d) => JSON.parse(d)));
|
|
495
507
|
return tmp;
|
|
496
|
-
}, [axes, controlInfo]);
|
|
508
|
+
}, [axesMemoKey, controlInfoMemoKey, axes, controlInfo]);
|
|
497
509
|
return _axes;
|
|
498
510
|
};
|
|
@@ -1,181 +1,318 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { animate, linear } from "popmotion";
|
|
3
|
-
|
|
4
|
-
const initialState = {
|
|
5
|
-
currentIndex: null,
|
|
6
|
-
flag: false,
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* x轴滚动逻辑
|
|
11
|
-
* @param {Object} axis x轴配置项
|
|
12
|
-
* @param {Object} config x轴轮播动画的配置项
|
|
13
|
-
* @returns {Map}
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const {
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { animate, linear } from "popmotion";
|
|
3
|
+
|
|
4
|
+
const initialState = {
|
|
5
|
+
currentIndex: null,
|
|
6
|
+
flag: false, // 首次加载标识:true-首次加载不执行动画,false-可执行轮播
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* x轴滚动逻辑
|
|
11
|
+
* @param {Object} axis x轴配置项
|
|
12
|
+
* @param {Object} config x轴轮播动画的配置项
|
|
13
|
+
* @returns {Map} 返回更新后的x轴配置(ticks/scaler/range 变更)
|
|
14
|
+
*/
|
|
15
|
+
export default (axis, config, isHover, controlInfo, active) => {
|
|
16
|
+
const { show, interval, duration, hover } = config;
|
|
17
|
+
const { isC, cPercent } = controlInfo;
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
tickCount: count,
|
|
21
|
+
allTicks,
|
|
22
|
+
scaler,
|
|
23
|
+
start,
|
|
24
|
+
end,
|
|
25
|
+
step,
|
|
26
|
+
ticks,
|
|
27
|
+
lengthWithoutPaddingOuter,
|
|
28
|
+
} = axis;
|
|
29
|
+
const tickLength = ticks.length;
|
|
30
|
+
const tickCount = isC ? allTicks.length : count;
|
|
31
|
+
|
|
32
|
+
const scale = isC ? cPercent : 1;
|
|
33
|
+
const _start = start / scale;
|
|
34
|
+
const _end = end / scale;
|
|
35
|
+
const canCarousel = useMemo(
|
|
36
|
+
() => show && active && tickLength > tickCount,
|
|
37
|
+
[show, active, tickLength, tickCount]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const [state, setState] = useState({
|
|
41
|
+
scaler,
|
|
42
|
+
step,
|
|
43
|
+
ticks,
|
|
44
|
+
});
|
|
45
|
+
const [status, setStatus] = useState(initialState);
|
|
46
|
+
const [ready, setReady] = useState(true); // true 才允许开启下一轮动画(用于 interval 等待)
|
|
47
|
+
const [wakeToken, setWakeToken] = useState(0);
|
|
48
|
+
const hoverRef = useRef(isHover);
|
|
49
|
+
const prevHoverRef = useRef(isHover);
|
|
50
|
+
const intervalTimerRef = useRef(null);
|
|
51
|
+
const isAnimatingRef = useRef(false);
|
|
52
|
+
const latestRef = useRef({
|
|
53
|
+
allTicks,
|
|
54
|
+
scaler,
|
|
55
|
+
tickCount,
|
|
56
|
+
tickLength,
|
|
57
|
+
lengthWithoutPaddingOuter,
|
|
58
|
+
_start,
|
|
59
|
+
_end,
|
|
60
|
+
interval,
|
|
61
|
+
duration,
|
|
62
|
+
hover,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// 避免 hover 触发 render 时 effect 误重启动画:用 ref 持有最新输入
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
latestRef.current = {
|
|
68
|
+
allTicks,
|
|
69
|
+
scaler,
|
|
70
|
+
tickCount,
|
|
71
|
+
tickLength,
|
|
72
|
+
lengthWithoutPaddingOuter,
|
|
73
|
+
_start,
|
|
74
|
+
_end,
|
|
75
|
+
interval,
|
|
76
|
+
duration,
|
|
77
|
+
hover,
|
|
78
|
+
};
|
|
79
|
+
}, [
|
|
80
|
+
allTicks,
|
|
81
|
+
scaler,
|
|
82
|
+
tickCount,
|
|
83
|
+
tickLength,
|
|
84
|
+
lengthWithoutPaddingOuter,
|
|
85
|
+
_start,
|
|
86
|
+
_end,
|
|
87
|
+
interval,
|
|
88
|
+
duration,
|
|
89
|
+
hover,
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
// hover 变化不打断正在进行的动画:只更新引用、并在需要时取消「下一轮」定时器
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const prev = prevHoverRef.current;
|
|
95
|
+
prevHoverRef.current = isHover;
|
|
96
|
+
hoverRef.current = isHover;
|
|
97
|
+
if (latestRef.current.hover && isHover && intervalTimerRef.current) {
|
|
98
|
+
clearTimeout(intervalTimerRef.current);
|
|
99
|
+
intervalTimerRef.current = null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 仅在 hover 从 true -> false 时唤醒轮播(避免在非 hover 状态下反复唤醒导致 interval 失效)
|
|
103
|
+
if (
|
|
104
|
+
latestRef.current.hover &&
|
|
105
|
+
prev === true &&
|
|
106
|
+
isHover === false &&
|
|
107
|
+
canCarousel &&
|
|
108
|
+
status.currentIndex !== null &&
|
|
109
|
+
!isAnimatingRef.current
|
|
110
|
+
) {
|
|
111
|
+
setReady(true);
|
|
112
|
+
setWakeToken((v) => v + 1);
|
|
113
|
+
}
|
|
114
|
+
}, [isHover, canCarousel, status.currentIndex]);
|
|
115
|
+
|
|
116
|
+
// 初始化:仅在基础条件变化时设置初始索引
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (show && tickLength > tickCount) {
|
|
119
|
+
setStatus({
|
|
120
|
+
currentIndex: 0,
|
|
121
|
+
flag: true,
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
setStatus(initialState);
|
|
125
|
+
}
|
|
126
|
+
}, [show, tickCount, tickLength]);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
let animation;
|
|
129
|
+
const { currentIndex, flag } = status;
|
|
130
|
+
const latest = latestRef.current;
|
|
131
|
+
|
|
132
|
+
// 执行条件:显示、非hover暂停、激活、有足够的轮播项
|
|
133
|
+
const canRun = canCarousel && !(latest.hover && hoverRef.current);
|
|
134
|
+
|
|
135
|
+
if (currentIndex !== null && canRun) {
|
|
136
|
+
if (flag) {
|
|
137
|
+
// 首次加载:仅初始化视图,不执行动画
|
|
138
|
+
const step = latest.lengthWithoutPaddingOuter / latest.tickCount;
|
|
139
|
+
const _ticks = latest.allTicks.slice(currentIndex, latest.tickCount);
|
|
140
|
+
setState({
|
|
141
|
+
step,
|
|
142
|
+
ticks: _ticks,
|
|
143
|
+
scaler: latest.scaler
|
|
144
|
+
.copy()
|
|
145
|
+
.domain(_ticks)
|
|
146
|
+
.range([latest._start, latest._end]),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// 首次加载完成后,启动第一轮动画(等待interval后执行)
|
|
150
|
+
if (latest.interval >= 0) {
|
|
151
|
+
intervalTimerRef.current = setTimeout(() => {
|
|
152
|
+
setStatus((prev) => ({ ...prev, flag: false }));
|
|
153
|
+
}, latest.interval * 1000);
|
|
154
|
+
} else {
|
|
155
|
+
setStatus((prev) => ({ ...prev, flag: false }));
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
// 非首次加载:执行动画 + 后续逻辑
|
|
159
|
+
if (!ready) return;
|
|
160
|
+
const step = latest.lengthWithoutPaddingOuter / latest.tickCount;
|
|
161
|
+
|
|
162
|
+
animation = animate({
|
|
163
|
+
from: 0,
|
|
164
|
+
to: -1,
|
|
165
|
+
duration: latest.duration * 1000,
|
|
166
|
+
ease: linear,
|
|
167
|
+
onPlay: () => {
|
|
168
|
+
isAnimatingRef.current = true;
|
|
169
|
+
// 动画开始:更新ticks和scaler初始范围
|
|
170
|
+
// 这里需要保证动画是「当前项滚出,下一项滚入」
|
|
171
|
+
// 因此后续窗口从 currentIndex + 1 开始,避免首项重复导致视觉上「从第一项滚到第一项」的闪烁
|
|
172
|
+
setState((axis) => {
|
|
173
|
+
const { ticks, scaler } = axis;
|
|
174
|
+
const [tick] = ticks;
|
|
175
|
+
const _ticks = [
|
|
176
|
+
tick,
|
|
177
|
+
...getTicks(
|
|
178
|
+
latest.allTicks,
|
|
179
|
+
currentIndex + 1,
|
|
180
|
+
latest.tickCount
|
|
181
|
+
),
|
|
182
|
+
];
|
|
183
|
+
return {
|
|
184
|
+
...axis,
|
|
185
|
+
ticks: _ticks,
|
|
186
|
+
scaler: scaler
|
|
187
|
+
.copy()
|
|
188
|
+
.range([latest._start, latest._end + step])
|
|
189
|
+
.domain(_ticks),
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
onUpdate: (v) => {
|
|
194
|
+
// 动画过程:实时更新scaler范围(实现滚动效果)
|
|
195
|
+
setState((axis) => {
|
|
196
|
+
const { scaler, step } = axis;
|
|
197
|
+
return {
|
|
198
|
+
...axis,
|
|
199
|
+
scaler: scaler
|
|
200
|
+
.copy()
|
|
201
|
+
.range([
|
|
202
|
+
latest._start + step * v,
|
|
203
|
+
latest._end + step + step * v,
|
|
204
|
+
]),
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
onComplete: () => {
|
|
209
|
+
isAnimatingRef.current = false;
|
|
210
|
+
// 本轮结束后进入等待态,避免立即开启下一轮(保证 interval 生效)
|
|
211
|
+
setReady(false);
|
|
212
|
+
// 动画完成:重置ticks和scaler
|
|
213
|
+
setState((axis) => {
|
|
214
|
+
const { scaler, ticks } = axis;
|
|
215
|
+
const _ticks = ticks.slice(1, ticks.length);
|
|
216
|
+
return {
|
|
217
|
+
...axis,
|
|
218
|
+
ticks: _ticks,
|
|
219
|
+
scaler: scaler
|
|
220
|
+
.copy()
|
|
221
|
+
.range([latest._start, latest._end])
|
|
222
|
+
.domain(_ticks),
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const nextIndex = (currentIndex + 1) % latest.tickLength;
|
|
227
|
+
|
|
228
|
+
// 无论是否 hover,都先把 currentIndex 同步到下一屏起点
|
|
229
|
+
// 否则在 interval 暂停期触发 hover/离开,会用旧索引重启,视觉上“回滚一格”
|
|
230
|
+
setStatus((prev) => ({ ...prev, currentIndex: nextIndex }));
|
|
231
|
+
|
|
232
|
+
// hover 触发时:让当前动画跑完后暂停(不再排下一轮)
|
|
233
|
+
if (latest.hover && hoverRef.current) return;
|
|
234
|
+
|
|
235
|
+
// 非hover:动画完成后,等待interval时长,再触发下一轮动画
|
|
236
|
+
intervalTimerRef.current = setTimeout(() => {
|
|
237
|
+
setReady(true);
|
|
238
|
+
setWakeToken((v) => v + 1);
|
|
239
|
+
}, latest.interval * 1000);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 清理副作用:停止动画、清除定时器
|
|
246
|
+
return () => {
|
|
247
|
+
if (animation) {
|
|
248
|
+
animation.stop();
|
|
249
|
+
isAnimatingRef.current = false;
|
|
250
|
+
}
|
|
251
|
+
if (intervalTimerRef.current) {
|
|
252
|
+
clearTimeout(intervalTimerRef.current);
|
|
253
|
+
intervalTimerRef.current = null;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}, [canCarousel, status.currentIndex, status.flag, wakeToken, ready]);
|
|
257
|
+
|
|
258
|
+
// 重置逻辑:当无有效索引时恢复初始状态
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (status.currentIndex === null) {
|
|
261
|
+
const _ticks = scaler.type === "linear" ? scaler.domain() : allTicks;
|
|
262
|
+
setState({
|
|
263
|
+
step,
|
|
264
|
+
scaler: scaler.copy().domain(_ticks).range([_start, _end]),
|
|
265
|
+
ticks,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}, [status.currentIndex, scaler, _start, _end, step, ticks, allTicks]);
|
|
269
|
+
|
|
270
|
+
// 当 x 轴可见项数量(count)发生变化时,安全地重置轮播节奏
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
// 清掉当前的 interval 计时器,避免用旧配置继续排队
|
|
273
|
+
if (intervalTimerRef.current) {
|
|
274
|
+
clearTimeout(intervalTimerRef.current);
|
|
275
|
+
intervalTimerRef.current = null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 重置动画节奏,让配置变更后「立即」按最新的 tickCount/tickLength 重新初始化
|
|
279
|
+
// 不能简单 setStatus(initialState),否则会覆盖上方的初始化 effect(先开轮播再改数量时会被清成 null,导致展示全部且不轮播)
|
|
280
|
+
isAnimatingRef.current = false;
|
|
281
|
+
setReady(true);
|
|
282
|
+
setStatus(() => {
|
|
283
|
+
// 与初始化 effect 保持一致:只要满足条件就从 0 开始重新初始化
|
|
284
|
+
if (show && active && tickLength > tickCount) {
|
|
285
|
+
return { currentIndex: 0, flag: true };
|
|
286
|
+
}
|
|
287
|
+
return initialState;
|
|
288
|
+
});
|
|
289
|
+
setWakeToken((v) => v + 1);
|
|
290
|
+
}, [count, show, active, tickLength, tickCount]);
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
...axis,
|
|
294
|
+
...state,
|
|
295
|
+
controlEnd: _end,
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 获取指定索引范围的ticks(支持循环)
|
|
301
|
+
* @param {Array} ticks 所有ticks
|
|
302
|
+
* @param {number} currentIndex 当前起始索引
|
|
303
|
+
* @param {number} length 需要的长度
|
|
304
|
+
* @returns {Array} 目标ticks数组
|
|
305
|
+
*/
|
|
306
|
+
const getTicks = (ticks, currentIndex, length) => {
|
|
307
|
+
const _currentIndex = +currentIndex;
|
|
308
|
+
const ticksLength = ticks.length;
|
|
309
|
+
if (ticksLength <= length) return ticks;
|
|
310
|
+
|
|
311
|
+
const _end = _currentIndex + length;
|
|
312
|
+
if (ticksLength < _end) {
|
|
313
|
+
const prev = ticks.slice(_currentIndex, ticksLength);
|
|
314
|
+
const next = ticks.slice(0, _end - ticksLength);
|
|
315
|
+
return [...prev, ...next];
|
|
316
|
+
}
|
|
317
|
+
return ticks.slice(_currentIndex, _end);
|
|
318
|
+
};
|