@hsu-react/ui 0.0.8 → 0.0.9

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 (53) hide show
  1. package/es/components/Chart/Bar/index.d.ts +5 -1
  2. package/es/components/Chart/Bar/index.js +84 -9
  3. package/es/components/Chart/Line/index.d.ts +5 -1
  4. package/es/components/Chart/Line/index.js +84 -9
  5. package/es/components/Chart/_utils/autoSmooth.d.ts +2 -0
  6. package/es/components/Chart/_utils/autoSmooth.js +12 -2
  7. package/es/components/Chart/_utils/cartesian.d.ts +11 -0
  8. package/es/components/Chart/_utils/cartesian.js +19 -0
  9. package/es/components/Chart/chartUtils/chartTooltipFormatter.d.ts +2 -0
  10. package/es/components/Chart/chartUtils/chartTooltipFormatter.js +13 -3
  11. package/es/components/Chart/chartUtils/index.d.ts +5 -2
  12. package/es/components/Chart/chartUtils/index.js +3 -1
  13. package/es/components/Chart/chartUtils/useDataZoomWindow.d.ts +19 -0
  14. package/es/components/Chart/chartUtils/useDataZoomWindow.js +37 -0
  15. package/es/components/Chart/chartUtils/useLegendSelection.d.ts +14 -0
  16. package/es/components/Chart/chartUtils/useLegendSelection.js +35 -0
  17. package/es/components/Modal/index.module.scss +1 -0
  18. package/es/components/Panel/ListPanel/ListModalPanel/index.js +33 -5
  19. package/es/components/Panel/ListPanel/ListModalPanel/index.module.scss +17 -2
  20. package/es/components/Panel/ListPanel/index.module.scss +11 -12
  21. package/lib/components/Chart/Bar/index.d.ts +5 -1
  22. package/lib/components/Chart/Bar/index.js +68 -4
  23. package/lib/components/Chart/Line/index.d.ts +5 -1
  24. package/lib/components/Chart/Line/index.js +68 -4
  25. package/lib/components/Chart/_utils/autoSmooth.d.ts +2 -0
  26. package/lib/components/Chart/_utils/autoSmooth.js +11 -1
  27. package/lib/components/Chart/_utils/cartesian.d.ts +11 -0
  28. package/lib/components/Chart/_utils/cartesian.js +21 -1
  29. package/lib/components/Chart/chartUtils/chartTooltipFormatter.d.ts +2 -0
  30. package/lib/components/Chart/chartUtils/chartTooltipFormatter.js +5 -3
  31. package/lib/components/Chart/chartUtils/index.d.ts +5 -2
  32. package/lib/components/Chart/chartUtils/index.js +14 -0
  33. package/lib/components/Chart/chartUtils/useDataZoomWindow.d.ts +19 -0
  34. package/lib/components/Chart/chartUtils/useDataZoomWindow.js +31 -0
  35. package/lib/components/Chart/chartUtils/useLegendSelection.d.ts +14 -0
  36. package/lib/components/Chart/chartUtils/useLegendSelection.js +26 -0
  37. package/lib/components/Modal/index.module.scss +1 -0
  38. package/lib/components/Panel/ListPanel/ListModalPanel/index.js +21 -2
  39. package/lib/components/Panel/ListPanel/ListModalPanel/index.module.scss +17 -2
  40. package/lib/components/Panel/ListPanel/index.module.scss +11 -12
  41. package/package.json +1 -1
  42. package/src/components/Chart/Bar/index.tsx +132 -2
  43. package/src/components/Chart/Line/index.tsx +132 -2
  44. package/src/components/Chart/_utils/autoSmooth.ts +181 -164
  45. package/src/components/Chart/_utils/cartesian.ts +28 -0
  46. package/src/components/Chart/chartUtils/chartTooltipFormatter.ts +11 -2
  47. package/src/components/Chart/chartUtils/index.ts +53 -37
  48. package/src/components/Chart/chartUtils/useDataZoomWindow.ts +61 -0
  49. package/src/components/Chart/chartUtils/useLegendSelection.ts +35 -0
  50. package/src/components/Modal/index.module.scss +1 -0
  51. package/src/components/Panel/ListPanel/ListModalPanel/index.module.scss +17 -2
  52. package/src/components/Panel/ListPanel/ListModalPanel/index.tsx +261 -233
  53. package/src/components/Panel/ListPanel/index.module.scss +11 -12
@@ -0,0 +1,35 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ 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) { _defineProperty(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; }
4
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
5
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
6
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
7
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
8
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
9
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
10
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
11
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
12
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
13
+ import { useCallback, useState } from "react";
14
+ /**
15
+ * 图例选中状态管理:配合 Chart.Bar / Chart.Line 的 onLegendSelectChanged,
16
+ * 在调用方按"当前可见系列"重算 Y 轴 min/max/interval(calculateAxisConfig),
17
+ * 实现图例隐藏某一项后坐标轴自动重算。
18
+ */
19
+ export function useLegendSelection() {
20
+ var _useState = useState({}),
21
+ _useState2 = _slicedToArray(_useState, 2),
22
+ legendSelected = _useState2[0],
23
+ setLegendSelected = _useState2[1];
24
+ var onLegendSelectChanged = useCallback(function (selected) {
25
+ setLegendSelected(_objectSpread({}, selected));
26
+ }, []);
27
+ var isSeriesVisible = useCallback(function (name) {
28
+ return name == null || legendSelected[name] !== false;
29
+ }, [legendSelected]);
30
+ return {
31
+ legendSelected: legendSelected,
32
+ onLegendSelectChanged: onLegendSelectChanged,
33
+ isSeriesVisible: isSeriesVisible
34
+ };
35
+ }
@@ -62,6 +62,7 @@
62
62
  align-items: center;
63
63
  justify-content: flex-end;
64
64
  column-gap: 10px;
65
+ margin-left: auto; // title 为空时仍保持靠右
65
66
  }
66
67
  }
67
68
  }
@@ -1,6 +1,10 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
2
  var _excluded = ["modalClassName", "className", "searchProps", "searchMode", "tableProps", "hasPermi", "extraContent"],
3
3
  _excluded2 = ["title", "buttonGroup", "className", "columns", "tabBarProps", "tableTools", "tableContainerClassName", "tips", "otherTool"];
4
+ function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
5
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
6
+ function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
7
+ function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
4
8
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5
9
  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) { _defineProperty(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; }
6
10
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
@@ -17,6 +21,7 @@ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) r
17
21
  import React, { useEffect, useMemo, useState } from "react";
18
22
  import Search from "../../../Search";
19
23
  import Table from "../../../Table";
24
+ import { FullscreenExitOutlined, FullscreenOutlined } from "@ant-design/icons";
20
25
  import classNames from "classnames";
21
26
  import { cloneDeep } from "lodash";
22
27
  import styles from "./index.module.scss";
@@ -28,6 +33,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
28
33
  import { jsxs as _jsxs } from "react/jsx-runtime";
29
34
  import { Fragment as _Fragment } from "react/jsx-runtime";
30
35
  var ListModalPanel = function ListModalPanel(props) {
36
+ var _modalConfig$width, _modalConfig$titleBut;
31
37
  var modalClassName = props.modalClassName,
32
38
  className = props.className,
33
39
  searchProps = props.searchProps,
@@ -60,6 +66,15 @@ var ListModalPanel = function ListModalPanel(props) {
60
66
  _useState4 = _slicedToArray(_useState3, 2),
61
67
  showColumnMgt = _useState4[0],
62
68
  setShowColumnMgt = _useState4[1];
69
+ var _useState5 = useState(false),
70
+ _useState6 = _slicedToArray(_useState5, 2),
71
+ isFullscreen = _useState6[0],
72
+ setIsFullscreen = _useState6[1];
73
+ useEffect(function () {
74
+ if (modalConfig.open) {
75
+ setIsFullscreen(false);
76
+ }
77
+ }, [modalConfig.open]);
63
78
  useEffect(function () {
64
79
  if (columnMgt && columns !== null && columns !== void 0 && columns.length) {
65
80
  var _columns2 = columns === null || columns === void 0 ? void 0 : columns.map(function (col) {
@@ -117,10 +132,22 @@ var ListModalPanel = function ListModalPanel(props) {
117
132
  return null;
118
133
  }
119
134
  return /*#__PURE__*/_jsxs(_Fragment, {
120
- children: [/*#__PURE__*/_jsx(Modal, _objectSpread(_objectSpread({
121
- width: 1400,
122
- className: classNames(modalClassName)
123
- }, modalConfig), {}, {
135
+ children: [/*#__PURE__*/_jsx(Modal, _objectSpread(_objectSpread(_objectSpread({}, modalConfig), {}, {
136
+ width: isFullscreen ? "100vw" : (_modalConfig$width = modalConfig.width) !== null && _modalConfig$width !== void 0 ? _modalConfig$width : 1400
137
+ }, isFullscreen ? {
138
+ full: true
139
+ } : {}), {}, {
140
+ className: classNames(styles.modal, modalClassName, _defineProperty({}, styles.fullscreen, isFullscreen)),
141
+ titleButtonGroup: [{
142
+ type: "text",
143
+ icon: isFullscreen ? /*#__PURE__*/_jsx(FullscreenExitOutlined, {}) : /*#__PURE__*/_jsx(FullscreenOutlined, {}),
144
+ title: isFullscreen ? "退出全屏" : "全屏",
145
+ onClick: function onClick() {
146
+ return setIsFullscreen(function (v) {
147
+ return !v;
148
+ });
149
+ }
150
+ }].concat(_toConsumableArray((_modalConfig$titleBut = modalConfig.titleButtonGroup) !== null && _modalConfig$titleBut !== void 0 ? _modalConfig$titleBut : [])),
124
151
  children: /*#__PURE__*/_jsxs("div", {
125
152
  className: classNames(styles.ListModalPanel, className),
126
153
  children: [extraContent, SearchComponent, /*#__PURE__*/_jsxs("div", {
@@ -139,7 +166,8 @@ var ListModalPanel = function ListModalPanel(props) {
139
166
  }, _objectSpread(_objectSpread({
140
167
  scroll: true,
141
168
  serialNumberColumn: true,
142
- bordered: true
169
+ bordered: true,
170
+ fillPanel: true
143
171
  }, tableConfig), {}, {
144
172
  columns: _columns
145
173
  })))]
@@ -1,10 +1,25 @@
1
+ .modal {
2
+ // 定高让内部“height: 100%”链路可解析(antd Modal 默认内容自适应,内层设置 height/max-height 无效)
3
+ height: 700px;
4
+
5
+ &.fullscreen {
6
+ top: 0;
7
+
8
+ width: 100vw !important;
9
+ max-width: 100vw !important;
10
+ height: 100vh !important;
11
+
12
+ margin: 0;
13
+ padding-bottom: 0;
14
+ }
15
+ }
16
+
1
17
  .ListModalPanel {
2
18
  display: flex;
3
19
  flex-direction: column;
4
20
  row-gap: 10px;
5
21
 
6
- height: max-content;
7
- max-height: 70vh;
22
+ height: 100%;
8
23
 
9
24
  .search {
10
25
  padding: 0px;
@@ -87,7 +87,7 @@
87
87
 
88
88
  width: 100%;
89
89
  min-height: 0px;
90
- padding: 14px 16px;
90
+ padding: 10px;
91
91
 
92
92
  background: var(--cf-surface);
93
93
 
@@ -102,23 +102,22 @@
102
102
  align-items: center;
103
103
  justify-content: space-between;
104
104
  column-gap: 10px;
105
+ }
105
106
 
106
- padding: 10px;
107
+ // 页面级页签:无底色容器,页签拆成独立圆角块、块间留空;
108
+ // margin/radius 需 !important 压过 TabBar 内部的连体分段样式(负 margin + 首尾切角)
109
+ .headerTabBar {
110
+ column-gap: 4px;
107
111
 
108
- background: var(--cf-subtle);
112
+ > div {
113
+ margin-left: 0 !important;
114
+ border-radius: 6px !important;
109
115
 
110
- border-radius: 8px 8px 0px 0px;
111
- border: 1px solid var(--cf-border);
116
+ background: var(--cf-surface);
117
+ }
112
118
  }
113
119
 
114
120
  &.hasHeaderTabBar {
115
- .search {
116
- padding: 10px;
117
- margin-top: -10px;
118
-
119
- border-radius: 0px 0px 8px 8px;
120
- }
121
-
122
121
  &:not(:has(.search)) {
123
122
  .headerTabBarContainer {
124
123
  border-radius: 8px;
@@ -1,7 +1,7 @@
1
1
  import * as echarts from "echarts";
2
2
  import { ChartCommonProps, SeriesDataType } from "..";
3
3
  import React from "react";
4
- import { ChartScrollConfig } from "../_utils/cartesian";
4
+ import { ChartScrollConfig, DataZoomIndexWindow } from "../_utils/cartesian";
5
5
  export interface ChartBarProps extends ChartCommonProps {
6
6
  chartTitle?: string;
7
7
  xAxisData?: Array<string>;
@@ -9,6 +9,10 @@ export interface ChartBarProps extends ChartCommonProps {
9
9
  legendData?: string[];
10
10
  scrollConfig?: ChartScrollConfig;
11
11
  onClick?: (event: echarts.ECElementEvent) => void;
12
+ /** 图例点选变化回调,参数为当前各系列选中状态(配合 legend.selected 可实现坐标轴重算) */
13
+ onLegendSelectChanged?: (selected: Record<string, boolean>) => void;
14
+ /** dataZoom 可见窗口变化回调(滑块/滚轮/自动滚动/初始同步均触发),参数为当前可见的 x 轴索引区间(配合 useDataZoomWindow 可按展示部分重算坐标轴) */
15
+ onDataZoomWindowChanged?: (window: DataZoomIndexWindow) => void;
12
16
  /** 是否启用图例自动滚动,默认 false */
13
17
  enableLegendAutoScroll?: boolean;
14
18
  /** 图例每页可见数量,默认 8 */
@@ -34,6 +34,8 @@ const ChartBar = props => {
34
34
  insideDataZoom,
35
35
  sliderDataZoom,
36
36
  onClick,
37
+ onLegendSelectChanged,
38
+ onDataZoomWindowChanged,
37
39
  enableLegendAutoScroll = false,
38
40
  legendVisibleCount = 8,
39
41
  legendScrollInterval = 1500,
@@ -42,6 +44,7 @@ const ChartBar = props => {
42
44
  const chartRef = (0, _react.useRef)(null);
43
45
  const chartInstanceRef = (0, _react.useRef)(null);
44
46
  const resizeObserverRef = (0, _react.useRef)(null);
47
+ const prevXAxisKeyRef = (0, _react.useRef)(null);
45
48
  const legendScrollRef = (0, _react.useRef)(null);
46
49
  const normalizedScroll = (0, _react.useMemo)(() => (0, _cartesian.resolveScrollConfig)(scrollConfig), [scrollConfig]);
47
50
 
@@ -198,8 +201,37 @@ const ChartBar = props => {
198
201
  chartInstanceRef.current = chart;
199
202
  }
200
203
 
201
- // 设置配置
204
+ // 设置配置:notMerge 会把 dataZoom 窗口重置回初始位置,
205
+ // x 轴数据未变化时(如坐标轴重算/图例点选触发的重渲染)先记录当前窗口,设置后恢复
206
+ const xAxisKey = xAxisData?.join("\u0001") ?? "";
207
+ const prevZoomRanges = prevXAxisKeyRef.current === xAxisKey ? chart.getOption()?.dataZoom : undefined;
208
+ prevXAxisKeyRef.current = xAxisKey;
202
209
  chart.setOption(chartOption, true);
210
+ const nextZooms = chartOption.dataZoom;
211
+ if (prevZoomRanges?.length && Array.isArray(nextZooms) && nextZooms.length === prevZoomRanges.length) {
212
+ chart.setOption({
213
+ dataZoom: prevZoomRanges.map(z => ({
214
+ start: z.start,
215
+ end: z.end
216
+ }))
217
+ });
218
+ }
219
+
220
+ // 向调用方同步当前可见窗口(初始渲染与恢复后),用于按展示部分重算坐标轴
221
+ if (onDataZoomWindowChanged) {
222
+ const zooms = chart.getOption()?.dataZoom;
223
+ const zoom = zooms?.[0];
224
+ const totalLen = xAxisData?.length ?? 0;
225
+ if (typeof zoom?.start === "number" && typeof zoom?.end === "number") {
226
+ onDataZoomWindowChanged((0, _cartesian.percentWindowToIndexWindow)(zoom.start, zoom.end, totalLen));
227
+ } else {
228
+ // 无 dataZoom(如数据量不足未启用滚动)时窗口即全量,同步一次避免残留旧窗口
229
+ onDataZoomWindowChanged({
230
+ startIndex: 0,
231
+ endIndex: Math.max(0, totalLen - 1)
232
+ });
233
+ }
234
+ }
203
235
 
204
236
  // 添加 resize 监听
205
237
  window.addEventListener("resize", handleResize);
@@ -215,6 +247,31 @@ const ChartBar = props => {
215
247
  chartInstanceRef.current?.on("click", onClick);
216
248
  }
217
249
 
250
+ // 图例点选事件
251
+ const handleLegendSelectChanged = params => {
252
+ onLegendSelectChanged?.(params.selected);
253
+ };
254
+ if (onLegendSelectChanged) {
255
+ chartInstanceRef.current?.on("legendselectchanged", handleLegendSelectChanged);
256
+ }
257
+
258
+ // dataZoom 原生交互(slider 拖动 / inside 缩放)→ 同步可见窗口
259
+ const handleDataZoom = params => {
260
+ const raw = params;
261
+ const info = raw?.batch?.[0] ?? raw;
262
+ if (typeof info?.startValue === "number" && typeof info?.endValue === "number") {
263
+ onDataZoomWindowChanged?.({
264
+ startIndex: Math.max(0, Math.round(info.startValue)),
265
+ endIndex: Math.max(0, Math.round(info.endValue))
266
+ });
267
+ } else if (typeof info?.start === "number" && typeof info?.end === "number") {
268
+ onDataZoomWindowChanged?.((0, _cartesian.percentWindowToIndexWindow)(info.start, info.end, xAxisData?.length ?? 0));
269
+ }
270
+ };
271
+ if (onDataZoomWindowChanged) {
272
+ chartInstanceRef.current?.on("datazoom", handleDataZoom);
273
+ }
274
+
218
275
  // 图例自动滚动(参考 Pie 组件)
219
276
  const opt = chartOption;
220
277
  const legendDataLen = Array.isArray(opt.legend) ? opt.legend[0]?.data?.length : opt.legend?.data?.length;
@@ -239,12 +296,18 @@ const ChartBar = props => {
239
296
  if (onClick) {
240
297
  chartInstanceRef.current?.off("click", onClick);
241
298
  }
299
+ if (onLegendSelectChanged) {
300
+ chartInstanceRef.current?.off("legendselectchanged", handleLegendSelectChanged);
301
+ }
302
+ if (onDataZoomWindowChanged) {
303
+ chartInstanceRef.current?.off("datazoom", handleDataZoom);
304
+ }
242
305
  if (legendScrollRef.current) {
243
306
  legendScrollRef.current.dispose();
244
307
  legendScrollRef.current = null;
245
308
  }
246
309
  };
247
- }, [chartOption, handleResize, onClick, enableLegendAutoScroll, legendVisibleCount, legendScrollInterval]);
310
+ }, [chartOption, handleResize, onClick, onLegendSelectChanged, onDataZoomWindowChanged, xAxisData, enableLegendAutoScroll, legendVisibleCount, legendScrollInterval]);
248
311
 
249
312
  // 处理自动播放
250
313
  (0, _react.useEffect)(() => {
@@ -260,10 +323,11 @@ const ChartBar = props => {
260
323
  interval: normalizedScroll.interval,
261
324
  startIndex: normalizedScroll.startIndex,
262
325
  autoPlay: normalizedScroll.autoScroll,
263
- enableWheelScroll
326
+ enableWheelScroll,
327
+ onWindowChange: onDataZoomWindowChanged ? (startPercent, endPercent) => onDataZoomWindowChanged((0, _cartesian.percentWindowToIndexWindow)(startPercent, endPercent, xAxisData.length)) : undefined
264
328
  });
265
329
  return dispose;
266
- }, [dataZoom, normalizedScroll, sliderDataZoom, xAxisData]);
330
+ }, [dataZoom, normalizedScroll, sliderDataZoom, xAxisData, onDataZoomWindowChanged]);
267
331
 
268
332
  // 组件卸载时清理资源
269
333
  (0, _react.useEffect)(() => {
@@ -1,7 +1,7 @@
1
1
  import * as echarts from "echarts";
2
2
  import { ChartCommonProps, SeriesDataType } from "..";
3
3
  import React from "react";
4
- import { ChartScrollConfig } from "../_utils/cartesian";
4
+ import { ChartScrollConfig, DataZoomIndexWindow } from "../_utils/cartesian";
5
5
  export interface ChartLineProps extends ChartCommonProps {
6
6
  chartTitle?: string;
7
7
  xAxisData?: Array<string>;
@@ -10,6 +10,10 @@ export interface ChartLineProps extends ChartCommonProps {
10
10
  scrollConfig?: ChartScrollConfig;
11
11
  yAxisNameOffsets?: number[];
12
12
  onClick?: (event: echarts.ECElementEvent) => void;
13
+ /** 图例点选变化回调,参数为当前各系列选中状态(配合 legend.selected 可实现坐标轴重算) */
14
+ onLegendSelectChanged?: (selected: Record<string, boolean>) => void;
15
+ /** dataZoom 可见窗口变化回调(滑块/滚轮/自动滚动/初始同步均触发),参数为当前可见的 x 轴索引区间(配合 useDataZoomWindow 可按展示部分重算坐标轴) */
16
+ onDataZoomWindowChanged?: (window: DataZoomIndexWindow) => void;
13
17
  /** 是否启用图例自动滚动,默认 false */
14
18
  enableLegendAutoScroll?: boolean;
15
19
  /** 图例每页可见数量,默认 8 */
@@ -34,6 +34,8 @@ const ChartLine = props => {
34
34
  insideDataZoom,
35
35
  sliderDataZoom,
36
36
  onClick,
37
+ onLegendSelectChanged,
38
+ onDataZoomWindowChanged,
37
39
  enableLegendAutoScroll = false,
38
40
  legendVisibleCount = 8,
39
41
  legendScrollInterval = 1500,
@@ -42,6 +44,7 @@ const ChartLine = props => {
42
44
  const chartRef = (0, _react.useRef)(null);
43
45
  const chartInstanceRef = (0, _react.useRef)(null);
44
46
  const resizeObserverRef = (0, _react.useRef)(null);
47
+ const prevXAxisKeyRef = (0, _react.useRef)(null);
45
48
  const legendScrollRef = (0, _react.useRef)(null);
46
49
  const normalizedScroll = (0, _react.useMemo)(() => (0, _cartesian.resolveScrollConfig)(scrollConfig), [scrollConfig]);
47
50
 
@@ -194,8 +197,37 @@ const ChartLine = props => {
194
197
  chartInstanceRef.current = chart;
195
198
  }
196
199
 
197
- // 设置配置
200
+ // 设置配置:notMerge 会把 dataZoom 窗口重置回初始位置,
201
+ // x 轴数据未变化时(如坐标轴重算/图例点选触发的重渲染)先记录当前窗口,设置后恢复
202
+ const xAxisKey = xAxisData?.join("\u0001") ?? "";
203
+ const prevZoomRanges = prevXAxisKeyRef.current === xAxisKey ? chart.getOption()?.dataZoom : undefined;
204
+ prevXAxisKeyRef.current = xAxisKey;
198
205
  chart.setOption(chartOption, true);
206
+ const nextZooms = chartOption.dataZoom;
207
+ if (prevZoomRanges?.length && Array.isArray(nextZooms) && nextZooms.length === prevZoomRanges.length) {
208
+ chart.setOption({
209
+ dataZoom: prevZoomRanges.map(z => ({
210
+ start: z.start,
211
+ end: z.end
212
+ }))
213
+ });
214
+ }
215
+
216
+ // 向调用方同步当前可见窗口(初始渲染与恢复后),用于按展示部分重算坐标轴
217
+ if (onDataZoomWindowChanged) {
218
+ const zooms = chart.getOption()?.dataZoom;
219
+ const zoom = zooms?.[0];
220
+ const totalLen = xAxisData?.length ?? 0;
221
+ if (typeof zoom?.start === "number" && typeof zoom?.end === "number") {
222
+ onDataZoomWindowChanged((0, _cartesian.percentWindowToIndexWindow)(zoom.start, zoom.end, totalLen));
223
+ } else {
224
+ // 无 dataZoom(如数据量不足未启用滚动)时窗口即全量,同步一次避免残留旧窗口
225
+ onDataZoomWindowChanged({
226
+ startIndex: 0,
227
+ endIndex: Math.max(0, totalLen - 1)
228
+ });
229
+ }
230
+ }
199
231
 
200
232
  // 添加 resize 监听
201
233
  window.addEventListener("resize", handleResize);
@@ -211,6 +243,31 @@ const ChartLine = props => {
211
243
  chartInstanceRef.current?.on("click", onClick);
212
244
  }
213
245
 
246
+ // 图例点选事件
247
+ const handleLegendSelectChanged = params => {
248
+ onLegendSelectChanged?.(params.selected);
249
+ };
250
+ if (onLegendSelectChanged) {
251
+ chartInstanceRef.current?.on("legendselectchanged", handleLegendSelectChanged);
252
+ }
253
+
254
+ // dataZoom 原生交互(slider 拖动 / inside 缩放)→ 同步可见窗口
255
+ const handleDataZoom = params => {
256
+ const raw = params;
257
+ const info = raw?.batch?.[0] ?? raw;
258
+ if (typeof info?.startValue === "number" && typeof info?.endValue === "number") {
259
+ onDataZoomWindowChanged?.({
260
+ startIndex: Math.max(0, Math.round(info.startValue)),
261
+ endIndex: Math.max(0, Math.round(info.endValue))
262
+ });
263
+ } else if (typeof info?.start === "number" && typeof info?.end === "number") {
264
+ onDataZoomWindowChanged?.((0, _cartesian.percentWindowToIndexWindow)(info.start, info.end, xAxisData?.length ?? 0));
265
+ }
266
+ };
267
+ if (onDataZoomWindowChanged) {
268
+ chartInstanceRef.current?.on("datazoom", handleDataZoom);
269
+ }
270
+
214
271
  // 图例自动滚动(与 Pie/Bar 一致)
215
272
  const opt = chartOption;
216
273
  const legendDataLen = Array.isArray(opt.legend) ? opt.legend[0]?.data?.length : opt.legend?.data?.length;
@@ -235,12 +292,18 @@ const ChartLine = props => {
235
292
  if (onClick) {
236
293
  chartInstanceRef.current?.off("click", onClick);
237
294
  }
295
+ if (onLegendSelectChanged) {
296
+ chartInstanceRef.current?.off("legendselectchanged", handleLegendSelectChanged);
297
+ }
298
+ if (onDataZoomWindowChanged) {
299
+ chartInstanceRef.current?.off("datazoom", handleDataZoom);
300
+ }
238
301
  if (legendScrollRef.current) {
239
302
  legendScrollRef.current.dispose();
240
303
  legendScrollRef.current = null;
241
304
  }
242
305
  };
243
- }, [chartOption, handleResize, onClick, enableLegendAutoScroll, legendVisibleCount, legendScrollInterval]);
306
+ }, [chartOption, handleResize, onClick, onLegendSelectChanged, onDataZoomWindowChanged, xAxisData, enableLegendAutoScroll, legendVisibleCount, legendScrollInterval]);
244
307
 
245
308
  // 处理自动播放
246
309
  (0, _react.useEffect)(() => {
@@ -256,10 +319,11 @@ const ChartLine = props => {
256
319
  interval: normalizedScroll.interval,
257
320
  startIndex: normalizedScroll.startIndex,
258
321
  autoPlay: normalizedScroll.autoScroll,
259
- enableWheelScroll
322
+ enableWheelScroll,
323
+ onWindowChange: onDataZoomWindowChanged ? (startPercent, endPercent) => onDataZoomWindowChanged((0, _cartesian.percentWindowToIndexWindow)(startPercent, endPercent, xAxisData.length)) : undefined
260
324
  });
261
325
  return dispose;
262
- }, [dataZoom, normalizedScroll, sliderDataZoom, xAxisData]);
326
+ }, [dataZoom, normalizedScroll, sliderDataZoom, xAxisData, onDataZoomWindowChanged]);
263
327
 
264
328
  // 组件卸载时清理资源
265
329
  (0, _react.useEffect)(() => {
@@ -7,6 +7,8 @@ export interface AutoScrollByItemProps {
7
7
  startIndex?: number;
8
8
  autoPlay?: boolean;
9
9
  enableWheelScroll?: boolean;
10
+ /** 窗口变化回调(创建同步、自动滚动、滚轮、resize 均触发),参数为当前窗口 start/end 百分比 */
11
+ onWindowChange?: (startPercent: number, endPercent: number) => void;
10
12
  }
11
13
  export declare function autoScrollByItem(props: AutoScrollByItemProps): {
12
14
  pause: () => void;
@@ -12,7 +12,8 @@ function autoScrollByItem(props) {
12
12
  interval = 2000,
13
13
  startIndex = 0,
14
14
  autoPlay = true,
15
- enableWheelScroll = true
15
+ enableWheelScroll = true,
16
+ onWindowChange
16
17
  } = props;
17
18
  const total = xAxisData?.length ?? 0;
18
19
  if (total <= 0) {
@@ -28,6 +29,14 @@ function autoScrollByItem(props) {
28
29
  const initialStartPercent = total > 0 ? initialStartIndex / total * 100 : 0;
29
30
  let start = initialStartPercent;
30
31
  let end = Math.min(initialStartPercent + windowSizePercent, 100);
32
+
33
+ // 图表已有 dataZoom 窗口时(组件重渲染重建滚动器)从当前位置继续,
34
+ // 避免图例点选、坐标轴重算等触发的重渲染把已滚动位置重置回初始窗口
35
+ const currentZoom = chart.getOption()?.dataZoom?.[0];
36
+ if (typeof currentZoom?.start === "number" && typeof currentZoom?.end === "number") {
37
+ start = currentZoom.start;
38
+ end = currentZoom.end;
39
+ }
31
40
  const syncDataZoomRange = () => {
32
41
  const option = chart.getOption();
33
42
  const currentDataZoom = option?.dataZoom;
@@ -39,6 +48,7 @@ function autoScrollByItem(props) {
39
48
  end
40
49
  }))
41
50
  });
51
+ onWindowChange?.(start, end);
42
52
  };
43
53
  syncDataZoomRange();
44
54
  let timer = null;
@@ -18,6 +18,17 @@ export interface NormalizedScrollConfig {
18
18
  sliderVisible?: boolean;
19
19
  wheelModeWhenSliderHidden: "scroll" | "none";
20
20
  }
21
+ /** dataZoom 当前可见窗口对应的 x 轴索引区间(闭区间) */
22
+ export interface DataZoomIndexWindow {
23
+ startIndex: number;
24
+ endIndex: number;
25
+ }
26
+ /**
27
+ * 把 dataZoom 的 start/end 百分比换算为 x 轴索引窗口。
28
+ * 与本组件族写入 dataZoom 的百分比约定一致(index / total * 100),
29
+ * 对 echarts 原生交互产生的百分比做外扩取整,边界最多多含一项,不影响坐标轴量级。
30
+ */
31
+ export declare const percentWindowToIndexWindow: (startPercent: number, endPercent: number, total: number) => DataZoomIndexWindow;
21
32
  export declare const createDefaultCategoryXAxis: (xAxisData?: Array<string>) => ChartOptionType;
22
33
  export declare const createDefaultValueYAxis: (mode?: "bar" | "line") => ChartOptionType;
23
34
  export declare const buildCartesianSeriesOptions: (mode: "bar" | "line", seriesData?: SeriesDataType, series?: echarts.SeriesOption | echarts.SeriesOption[]) => echarts.SeriesOption[];
@@ -3,7 +3,27 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.resolveScrollConfig = exports.getSliderShow = exports.createDefaultValueYAxis = exports.createDefaultCategoryXAxis = exports.buildCartesianSeriesOptions = void 0;
6
+ exports.resolveScrollConfig = exports.percentWindowToIndexWindow = exports.getSliderShow = exports.createDefaultValueYAxis = exports.createDefaultCategoryXAxis = exports.buildCartesianSeriesOptions = void 0;
7
+ /** dataZoom 当前可见窗口对应的 x 轴索引区间(闭区间) */
8
+
9
+ /**
10
+ * 把 dataZoom 的 start/end 百分比换算为 x 轴索引窗口。
11
+ * 与本组件族写入 dataZoom 的百分比约定一致(index / total * 100),
12
+ * 对 echarts 原生交互产生的百分比做外扩取整,边界最多多含一项,不影响坐标轴量级。
13
+ */
14
+ const percentWindowToIndexWindow = (startPercent, endPercent, total) => {
15
+ if (total <= 0) return {
16
+ startIndex: 0,
17
+ endIndex: 0
18
+ };
19
+ const startIndex = Math.max(0, Math.min(total - 1, Math.floor(startPercent / 100 * total)));
20
+ const endIndex = Math.max(startIndex, Math.min(total - 1, Math.ceil(endPercent / 100 * total) - 1));
21
+ return {
22
+ startIndex,
23
+ endIndex
24
+ };
25
+ };
26
+ exports.percentWindowToIndexWindow = percentWindowToIndexWindow;
7
27
  const createDefaultCategoryXAxis = xAxisData => ({
8
28
  type: "category",
9
29
  boundaryGap: true,
@@ -18,6 +18,8 @@ export interface ChartTooltipFormatterProps {
18
18
  hideMarker?: boolean;
19
19
  textStyle?: Record<string, unknown>;
20
20
  unit?: string;
21
+ /** 多系列时按数值从大到小排序展示(大的在上) */
22
+ sortDescending?: boolean;
21
23
  }
22
24
  declare const chartTooltipFormatter: (props: ChartTooltipFormatterProps) => string;
23
25
  export default chartTooltipFormatter;
@@ -14,7 +14,8 @@ const chartTooltipFormatter = props => {
14
14
  color: "#666",
15
15
  fontWeight: 400
16
16
  },
17
- unit
17
+ unit,
18
+ sortDescending
18
19
  } = props;
19
20
  if (!Array.isArray(params)) {
20
21
  return `
@@ -43,10 +44,11 @@ const chartTooltipFormatter = props => {
43
44
  </div>
44
45
  `;
45
46
  }
46
- const items = params?.map((item, idx) => {
47
+ const sortedParams = sortDescending ? [...params].sort((a, b) => (Number(b.value) || 0) - (Number(a.value) || 0)) : params;
48
+ const items = sortedParams?.map((item, idx) => {
47
49
  const value = item.value ?? "";
48
50
  return `
49
- <div style="margin: 0 0 ${idx === params.length - 1 ? "0" : "10px"} 0;line-height:1;">
51
+ <div style="margin: 0 0 ${idx === sortedParams.length - 1 ? "0" : "10px"} 0;line-height:1;">
50
52
  <div style="margin: 0px 0 0;line-height:1;">
51
53
  ${hideMarker ? "" : `${item?.marker}`}
52
54
  <span style="font-size:${textStyle?.fontSize}px;color:${textStyle?.color};font-weight:${textStyle?.fontWeight};margin-left:2px">${item?.seriesName}</span>
@@ -2,6 +2,9 @@ import chartTooltipFormatter from "./chartTooltipFormatter";
2
2
  import { TooltipParams, ChartTooltipFormatterProps } from "./chartTooltipFormatter";
3
3
  import { autoScrollLegend, AutoScrollLegendProps } from "../_utils/autoScrollLegend";
4
4
  import { calculateAxisConfig } from "./calculateAxisConfig";
5
+ import { useLegendSelection, LegendSelectionResult } from "./useLegendSelection";
6
+ import { useDataZoomWindow, DataZoomWindowOptions, DataZoomWindowResult } from "./useDataZoomWindow";
7
+ import { DataZoomIndexWindow } from "../_utils/cartesian";
5
8
  import { MB_PER_GB, MB_PER_TB, trimAxisNumber, chineseMagnitudeDivisor, formatChineseMagnitudeTick, mbDisplayTier, formatMbAxisTick } from "./yAxisDisplayUnit";
6
- export { chartTooltipFormatter, autoScrollLegend, calculateAxisConfig, MB_PER_GB, MB_PER_TB, trimAxisNumber, chineseMagnitudeDivisor, formatChineseMagnitudeTick, mbDisplayTier, formatMbAxisTick, };
7
- export type { TooltipParams, ChartTooltipFormatterProps, AutoScrollLegendProps, };
9
+ export { chartTooltipFormatter, autoScrollLegend, calculateAxisConfig, useLegendSelection, useDataZoomWindow, MB_PER_GB, MB_PER_TB, trimAxisNumber, chineseMagnitudeDivisor, formatChineseMagnitudeTick, mbDisplayTier, formatMbAxisTick, };
10
+ export type { TooltipParams, ChartTooltipFormatterProps, AutoScrollLegendProps, LegendSelectionResult, DataZoomWindowOptions, DataZoomWindowResult, DataZoomIndexWindow, };
@@ -63,8 +63,22 @@ Object.defineProperty(exports, "trimAxisNumber", {
63
63
  return _yAxisDisplayUnit.trimAxisNumber;
64
64
  }
65
65
  });
66
+ Object.defineProperty(exports, "useDataZoomWindow", {
67
+ enumerable: true,
68
+ get: function () {
69
+ return _useDataZoomWindow.useDataZoomWindow;
70
+ }
71
+ });
72
+ Object.defineProperty(exports, "useLegendSelection", {
73
+ enumerable: true,
74
+ get: function () {
75
+ return _useLegendSelection.useLegendSelection;
76
+ }
77
+ });
66
78
  var _chartTooltipFormatter = _interopRequireDefault(require("./chartTooltipFormatter"));
67
79
  var _autoScrollLegend = require("../_utils/autoScrollLegend");
68
80
  var _calculateAxisConfig = require("./calculateAxisConfig");
81
+ var _useLegendSelection = require("./useLegendSelection");
82
+ var _useDataZoomWindow = require("./useDataZoomWindow");
69
83
  var _yAxisDisplayUnit = require("./yAxisDisplayUnit");
70
84
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }