kd-lane-chart-v3 0.1.6 → 0.1.8

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/dist/esm/index.js CHANGED
@@ -2651,13 +2651,21 @@ function axisIndexOf$1(val) {
2651
2651
  return Number(n ?? 0);
2652
2652
  }
2653
2653
  function findLogTrendSeries(chart, paramId) {
2654
- const option = chart.getOption() || {};
2655
- return (option.series || []).find((s) => {
2656
- if (!s || s.type !== "line") return false;
2657
- const name = Array.isArray(s.name) ? s.name[0] : s.name;
2658
- if (name === "helper" || name === "log-grid") return false;
2659
- return String(s.paramId) === String(paramId);
2660
- }) || null;
2654
+ try {
2655
+ const list = chart.getModel().getSeries() || [];
2656
+ for (let i = 0; i < list.length; i++) {
2657
+ const sm = list[i];
2658
+ const opt = sm && sm.option || {};
2659
+ const type = sm && sm.subType || (Array.isArray(opt.type) ? opt.type[0] : opt.type);
2660
+ if (type !== "line") continue;
2661
+ const name = Array.isArray(sm.name) ? sm.name[0] : sm.name;
2662
+ if (name === "helper" || name === "log-grid" || name === "trend") continue;
2663
+ const pid = Array.isArray(opt.paramId) ? opt.paramId[0] : opt.paramId;
2664
+ if (String(pid) === String(paramId)) return opt;
2665
+ }
2666
+ } catch (e) {
2667
+ }
2668
+ return null;
2661
2669
  }
2662
2670
  function pixelToLogTrendData(chart, paramId, offsetX, offsetY, lane, lineRange, digits = 3) {
2663
2671
  if (!chart || !isLaneLogScale(lane)) return null;
@@ -2710,6 +2718,17 @@ function logTrendDataToPixel(chart, paramId, lg, depth) {
2710
2718
  const series = findLogTrendSeries(chart, paramId);
2711
2719
  const xAxisIndex = series ? axisIndexOf$1(series.xAxisIndex) : 0;
2712
2720
  const yAxisIndex = series ? axisIndexOf$1(series.yAxisIndex) : 0;
2721
+ try {
2722
+ const model = chart.getModel();
2723
+ const xAxis = model.getComponent("xAxis", xAxisIndex) && model.getComponent("xAxis", xAxisIndex).axis;
2724
+ const yAxis = model.getComponent("yAxis", yAxisIndex) && model.getComponent("yAxis", yAxisIndex).axis;
2725
+ if (xAxis && yAxis) {
2726
+ const x = xAxis.toGlobalCoord(xAxis.dataToCoord(lg, true));
2727
+ const y = yAxis.toGlobalCoord(yAxis.dataToCoord(depth, true));
2728
+ if (Number.isFinite(x) && Number.isFinite(y)) return [x, y];
2729
+ }
2730
+ } catch (e) {
2731
+ }
2713
2732
  try {
2714
2733
  const px = chart.convertToPixel({ xAxisIndex, yAxisIndex }, [lg, depth]);
2715
2734
  if (px && Number.isFinite(px[0]) && Number.isFinite(px[1])) return px;
@@ -2986,7 +3005,11 @@ const _sfc_main$8 = {
2986
3005
  let max = -Infinity;
2987
3006
  let min = Infinity;
2988
3007
  for (let i = 0; i < data.length; i++) {
2989
- const v = Number(data[i][key]);
3008
+ const raw = data[i][key];
3009
+ if (raw === null || raw === void 0 || raw === "" || raw === "null") {
3010
+ continue;
3011
+ }
3012
+ const v = Number(raw);
2990
3013
  if (!Number.isFinite(v)) continue;
2991
3014
  if (v > max) max = v;
2992
3015
  if (v < min) min = v;
@@ -3006,7 +3029,9 @@ const _sfc_main$8 = {
3006
3029
  return "--";
3007
3030
  }
3008
3031
  const raw = this.headerData[this.line.paramId];
3009
- if (raw === null || raw === void 0 || raw === "") return "--";
3032
+ if (raw === null || raw === void 0 || raw === "" || raw === "null") {
3033
+ return "--";
3034
+ }
3010
3035
  if (isLogScaleFlag(this.isLogScale)) {
3011
3036
  const lg = formatLanePointValue(raw);
3012
3037
  return lg == null ? "--" : lg;
@@ -3127,7 +3152,7 @@ function _sfc_render$8(_ctx, _cache, $props, $setup, $data, $options) {
3127
3152
  ])
3128
3153
  ], 6);
3129
3154
  }
3130
- const HeaderItem = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["render", _sfc_render$8], ["__scopeId", "data-v-466dec24"]]);
3155
+ const HeaderItem = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["render", _sfc_render$8], ["__scopeId", "data-v-c4185947"]]);
3131
3156
  const _sfc_main$7 = {
3132
3157
  name: "LegendItem",
3133
3158
  props: {
@@ -3198,7 +3223,7 @@ const _sfc_main$7 = {
3198
3223
  return "--";
3199
3224
  }
3200
3225
  const raw = this.headerData[paramId];
3201
- if (raw === null || raw === void 0 || raw === "") return "--";
3226
+ if (raw === null || raw === void 0 || raw === "" || raw === "null") return "--";
3202
3227
  if (isLogScaleFlag(this.isLogScale)) {
3203
3228
  const lg = formatLanePointValue(raw);
3204
3229
  return lg == null ? "--" : lg;
@@ -3298,7 +3323,7 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
3298
3323
  ])
3299
3324
  ], 4);
3300
3325
  }
3301
- const LegendItem = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["render", _sfc_render$7], ["__scopeId", "data-v-41c24d2f"]]);
3326
+ const LegendItem = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["render", _sfc_render$7], ["__scopeId", "data-v-9fa6fff3"]]);
3302
3327
  const TREND_LINE_STYLE = {
3303
3328
  stroke: "#FF9800",
3304
3329
  lineWidth: 2,
@@ -3330,7 +3355,6 @@ const TREND_MIRROR_POINT_STYLE = {
3330
3355
  lineWidth: 1
3331
3356
  };
3332
3357
  const TREND_LINE_Z = 9999;
3333
- const TREND_POINT_Z = 99999;
3334
3358
  const DEPTH_EDGE_SNAP = 0.2;
3335
3359
  const TREND_CLICK_DELAY_MS = 240;
3336
3360
  const TREND_HIT_RADIUS = 14;
@@ -3649,25 +3673,74 @@ function seriesParamId(series) {
3649
3673
  function seriesDisplayName(series) {
3650
3674
  return optionScalar(series && series.name);
3651
3675
  }
3676
+ function chartModel(chart) {
3677
+ try {
3678
+ return chart && !chart.isDisposed() ? chart.getModel() : null;
3679
+ } catch (e) {
3680
+ return null;
3681
+ }
3682
+ }
3683
+ function modelGridCount(model) {
3684
+ if (!model) return 1;
3685
+ try {
3686
+ const list = model.queryComponents({ mainType: "grid" });
3687
+ if (list && list.length) return list.length;
3688
+ } catch (e) {
3689
+ }
3690
+ return 1;
3691
+ }
3692
+ function modelAxisOption(model, mainType, index2) {
3693
+ if (!model) return {};
3694
+ try {
3695
+ const comp = model.getComponent(mainType, index2);
3696
+ return comp && comp.option || {};
3697
+ } catch (e) {
3698
+ return {};
3699
+ }
3700
+ }
3701
+ function listLineSeries(model) {
3702
+ if (!model) return [];
3703
+ try {
3704
+ const list = model.getSeries() || [];
3705
+ const out = [];
3706
+ for (let i = 0; i < list.length; i++) {
3707
+ const sm = list[i];
3708
+ const opt = sm && sm.option || {};
3709
+ const type = sm && sm.subType || seriesTypeName(opt);
3710
+ if (type !== "line") continue;
3711
+ const name = optionScalar(sm && sm.name) || seriesDisplayName(opt);
3712
+ if (name === "helper" || name === "log-grid" || name === "trend") continue;
3713
+ out.push({
3714
+ seriesIndex: sm.seriesIndex,
3715
+ xAxisIndex: axisIndexOf(opt.xAxisIndex),
3716
+ yAxisIndex: axisIndexOf(opt.yAxisIndex),
3717
+ paramId: seriesParamId(opt),
3718
+ option: opt
3719
+ });
3720
+ }
3721
+ return out;
3722
+ } catch (e) {
3723
+ return [];
3724
+ }
3725
+ }
3652
3726
  function findSeriesForDepth(chart, paramId, depth) {
3653
- const option = chart.getOption() || {};
3654
- const series = option.series || [];
3655
- const yAxes = option.yAxis || [];
3727
+ const model = chartModel(chart);
3728
+ const series = listLineSeries(model);
3656
3729
  let fallback = null;
3657
3730
  for (let i = 0; i < series.length; i++) {
3658
3731
  const s = series[i];
3659
- if (!s || seriesTypeName(s) !== "line" || seriesDisplayName(s) === "helper") continue;
3660
- if (String(seriesParamId(s)) !== String(paramId)) continue;
3732
+ if (String(s.paramId) !== String(paramId)) continue;
3661
3733
  const info = {
3662
- seriesIndex: i,
3663
- xAxisIndex: axisIndexOf(s.xAxisIndex),
3664
- yAxisIndex: axisIndexOf(s.yAxisIndex)
3734
+ seriesIndex: s.seriesIndex,
3735
+ xAxisIndex: s.xAxisIndex,
3736
+ yAxisIndex: s.yAxisIndex
3665
3737
  };
3666
3738
  fallback = info;
3667
- const yAx = yAxes[info.yAxisIndex];
3668
- if (!yAx) continue;
3669
- const min = Math.min(Number(yAx.min), Number(yAx.max));
3670
- const max = Math.max(Number(yAx.min), Number(yAx.max));
3739
+ const yAx = modelAxisOption(model, "yAxis", info.yAxisIndex);
3740
+ const minN = Number(optionScalar(yAx.min));
3741
+ const maxN = Number(optionScalar(yAx.max));
3742
+ const min = Math.min(minN, maxN);
3743
+ const max = Math.max(minN, maxN);
3671
3744
  if (Number.isFinite(depth) && depth >= min && depth <= max) return info;
3672
3745
  }
3673
3746
  return fallback || { seriesIndex: 0, xAxisIndex: 0, yAxisIndex: 0 };
@@ -3675,24 +3748,30 @@ function findSeriesForDepth(chart, paramId, depth) {
3675
3748
  function eventOffsetInChart(chart, evt) {
3676
3749
  var _a, _b;
3677
3750
  const zr = chart.getZr();
3678
- const rect = (_b = (_a = chart.getDom()) == null ? void 0 : _a.getBoundingClientRect) == null ? void 0 : _b.call(_a);
3679
- let offsetX = 0;
3680
- let offsetY = 0;
3751
+ const clamp = (x, y) => ({
3752
+ offsetX: Math.max(0, Math.min(x, zr.getWidth())),
3753
+ offsetY: Math.max(0, Math.min(y, zr.getHeight()))
3754
+ });
3755
+ if (evt && evt.event && Number.isFinite(evt.offsetX) && Number.isFinite(evt.offsetY)) {
3756
+ return clamp(evt.offsetX, evt.offsetY);
3757
+ }
3681
3758
  const native = (evt == null ? void 0 : evt.event) || evt;
3759
+ let el = null;
3760
+ try {
3761
+ el = zr.painter && zr.painter.getViewportRoot && zr.painter.getViewportRoot();
3762
+ } catch (e) {
3763
+ }
3764
+ const rect = (_b = (_a = el || chart.getDom()) == null ? void 0 : _a.getBoundingClientRect) == null ? void 0 : _b.call(_a);
3682
3765
  if (rect && Number.isFinite(native == null ? void 0 : native.clientX) && Number.isFinite(native == null ? void 0 : native.clientY)) {
3683
- offsetX = native.clientX - rect.left;
3684
- offsetY = native.clientY - rect.top;
3685
- } else if (Number.isFinite(evt == null ? void 0 : evt.offsetX) && Number.isFinite(evt == null ? void 0 : evt.offsetY)) {
3686
- offsetX = evt.offsetX;
3687
- offsetY = evt.offsetY;
3688
- }
3689
- offsetX = Math.max(0, Math.min(offsetX, zr.getWidth()));
3690
- offsetY = Math.max(0, Math.min(offsetY, zr.getHeight()));
3691
- return { offsetX, offsetY };
3766
+ return clamp(native.clientX - rect.left, native.clientY - rect.top);
3767
+ }
3768
+ if (Number.isFinite(evt == null ? void 0 : evt.offsetX) && Number.isFinite(evt == null ? void 0 : evt.offsetY)) {
3769
+ return clamp(evt.offsetX, evt.offsetY);
3770
+ }
3771
+ return clamp(0, 0);
3692
3772
  }
3693
3773
  function isPointerInAnyGrid(chart, offsetX, offsetY) {
3694
- const option = chart.getOption() || {};
3695
- const gridCount = option.grid && option.grid.length || 1;
3774
+ const gridCount = modelGridCount(chartModel(chart));
3696
3775
  for (let i = 0; i < gridCount; i++) {
3697
3776
  try {
3698
3777
  if (chart.containPixel({ gridIndex: i }, [offsetX, offsetY])) return true;
@@ -3706,22 +3785,22 @@ function axisIndexOf(val) {
3706
3785
  return Number(n ?? 0);
3707
3786
  }
3708
3787
  function pixelToData(chart, paramId, offsetX, offsetY, loose = false) {
3709
- const option = chart.getOption() || {};
3710
- const gridCount = option.grid && option.grid.length || 1;
3711
- const matchParam = (s) => s && seriesTypeName(s) === "line" && seriesDisplayName(s) !== "helper" && String(seriesParamId(s)) === String(paramId);
3788
+ const model = chartModel(chart);
3789
+ const gridCount = modelGridCount(model);
3790
+ const matched = listLineSeries(model).filter(
3791
+ (s) => String(s.paramId) === String(paramId)
3792
+ );
3712
3793
  for (let g = 0; g < gridCount; g++) {
3713
3794
  try {
3714
3795
  if (!loose && !chart.containPixel({ gridIndex: g }, [offsetX, offsetY])) continue;
3715
- const seriesList = (option.series || []).filter(matchParam);
3716
- const series = seriesList.find((s) => axisIndexOf(s.yAxisIndex) === g) || seriesList[0];
3796
+ const series = matched.find((s) => s.yAxisIndex === g) || matched[0];
3717
3797
  const finders = [];
3718
3798
  if (series) {
3719
3799
  finders.push({
3720
- xAxisIndex: axisIndexOf(series.xAxisIndex),
3721
- yAxisIndex: axisIndexOf(series.yAxisIndex)
3800
+ xAxisIndex: series.xAxisIndex,
3801
+ yAxisIndex: series.yAxisIndex
3722
3802
  });
3723
- const seriesIndex = (option.series || []).indexOf(series);
3724
- if (seriesIndex >= 0) finders.push({ seriesIndex });
3803
+ if (series.seriesIndex >= 0) finders.push({ seriesIndex: series.seriesIndex });
3725
3804
  }
3726
3805
  finders.push({ gridIndex: g });
3727
3806
  for (const finder of finders) {
@@ -3757,8 +3836,48 @@ function pixelToData(chart, paramId, offsetX, offsetY, loose = false) {
3757
3836
  }
3758
3837
  return null;
3759
3838
  }
3839
+ function axisPairToPixel(chart, xAxisIndex, yAxisIndex, value, depth) {
3840
+ try {
3841
+ const model = chartModel(chart);
3842
+ if (!model) return null;
3843
+ const xComp = model.getComponent("xAxis", xAxisIndex || 0);
3844
+ const yComp = model.getComponent("yAxis", yAxisIndex || 0);
3845
+ const xAxis = xComp && xComp.axis;
3846
+ const yAxis = yComp && yComp.axis;
3847
+ if (!xAxis || !yAxis) return null;
3848
+ const x = xAxis.toGlobalCoord(xAxis.dataToCoord(value, true));
3849
+ const y = yAxis.toGlobalCoord(yAxis.dataToCoord(depth, true));
3850
+ if (Number.isFinite(x) && Number.isFinite(y)) return [x, y];
3851
+ } catch (e) {
3852
+ }
3853
+ return null;
3854
+ }
3855
+ function axisPairFromPixel(chart, xAxisIndex, yAxisIndex, offsetX, offsetY) {
3856
+ try {
3857
+ const model = chartModel(chart);
3858
+ if (!model) return null;
3859
+ const xComp = model.getComponent("xAxis", xAxisIndex || 0);
3860
+ const yComp = model.getComponent("yAxis", yAxisIndex || 0);
3861
+ const xAxis = xComp && xComp.axis;
3862
+ const yAxis = yComp && yComp.axis;
3863
+ if (!xAxis || !yAxis) return null;
3864
+ const value = xAxis.coordToData(xAxis.toLocalCoord(offsetX), true);
3865
+ const depth = yAxis.coordToData(yAxis.toLocalCoord(offsetY), true);
3866
+ if (!Number.isFinite(value) || !Number.isFinite(depth)) return null;
3867
+ return {
3868
+ value,
3869
+ depth,
3870
+ xAxisIndex: xAxisIndex || 0,
3871
+ yAxisIndex: yAxisIndex || 0
3872
+ };
3873
+ } catch (e) {
3874
+ return null;
3875
+ }
3876
+ }
3760
3877
  function dataToPixel(chart, paramId, value, depth) {
3761
3878
  const finder = findSeriesForDepth(chart, paramId, depth);
3879
+ const live = axisPairToPixel(chart, finder.xAxisIndex, finder.yAxisIndex, value, depth);
3880
+ if (live) return live;
3762
3881
  const finders = [
3763
3882
  { xAxisIndex: finder.xAxisIndex, yAxisIndex: finder.yAxisIndex },
3764
3883
  { seriesIndex: finder.seriesIndex },
@@ -3784,14 +3903,14 @@ function snapDepthToEdge(depth, depthMin, depthMax, tol) {
3784
3903
  return Math.max(lo, Math.min(hi, d));
3785
3904
  }
3786
3905
  function getAxisRange(chart, paramId, depth) {
3787
- const option = chart.getOption() || {};
3906
+ const model = chartModel(chart);
3788
3907
  const finder = findSeriesForDepth(chart, paramId, depth);
3789
- const xAx = (option.xAxis || [])[finder.xAxisIndex] || {};
3790
- const yAx = (option.yAxis || [])[finder.yAxisIndex] || {};
3791
- const xMin = Number(xAx.min);
3792
- const xMax = Number(xAx.max);
3793
- const yMin = Number(yAx.min);
3794
- const yMax = Number(yAx.max);
3908
+ const xAx = modelAxisOption(model, "xAxis", finder.xAxisIndex);
3909
+ const yAx = modelAxisOption(model, "yAxis", finder.yAxisIndex);
3910
+ const xMin = Number(optionScalar(xAx.min));
3911
+ const xMax = Number(optionScalar(xAx.max));
3912
+ const yMin = Number(optionScalar(yAx.min));
3913
+ const yMax = Number(optionScalar(yAx.max));
3795
3914
  return {
3796
3915
  valueMin: Number.isFinite(xMin) ? xMin : 0,
3797
3916
  valueMax: Number.isFinite(xMax) ? xMax : 100,
@@ -3809,48 +3928,62 @@ class TrendLineManager {
3809
3928
  this.dragSession = null;
3810
3929
  this._panLockedByHover = false;
3811
3930
  this._raf = null;
3812
- this._zoomRaf = null;
3813
- this._zoomPending = false;
3814
- this._zoomEndTimer = null;
3815
- this._graphicIds = /* @__PURE__ */ Object.create(null);
3931
+ this._seriesIds = /* @__PURE__ */ Object.create(null);
3932
+ this._graphicCleared = /* @__PURE__ */ new WeakSet();
3816
3933
  this._ignoreClickUntil = 0;
3934
+ this._destroyed = false;
3817
3935
  }
3818
3936
  destroy() {
3937
+ this._destroyed = true;
3819
3938
  this._clearClickTimer();
3820
- this._endDrag();
3821
- Array.from(this.binds.keys()).forEach((laneId) => this.unbindChart(laneId));
3939
+ try {
3940
+ this._unbindDragWindow();
3941
+ } catch (e) {
3942
+ }
3943
+ this._panLockedByHover = false;
3944
+ Array.from(this.binds.keys()).forEach((laneId) => {
3945
+ try {
3946
+ this.unbindChart(laneId);
3947
+ } catch (e) {
3948
+ }
3949
+ });
3822
3950
  if (this._raf) {
3823
3951
  cancelAnimationFrame(this._raf);
3824
3952
  this._raf = null;
3825
3953
  }
3826
- if (this._zoomRaf) {
3827
- cancelAnimationFrame(this._zoomRaf);
3828
- this._zoomRaf = null;
3829
- }
3830
- if (this._zoomEndTimer) {
3831
- clearTimeout(this._zoomEndTimer);
3832
- this._zoomEndTimer = null;
3833
- }
3834
3954
  this.store = /* @__PURE__ */ Object.create(null);
3835
- this._graphicIds = /* @__PURE__ */ Object.create(null);
3955
+ this._seriesIds = /* @__PURE__ */ Object.create(null);
3956
+ this.binds.clear();
3957
+ this.host = null;
3958
+ }
3959
+ _alive() {
3960
+ return !this._destroyed && !!this.host;
3836
3961
  }
3837
3962
  /** 从模板 line.trendSegments 回显;已有 store 的曲线不覆盖,避免编辑中被模板刷新冲掉。8.21 */
3838
3963
  hydrateFromTemplate(template) {
3964
+ if (!this._alive()) return;
3965
+ const hasEcho = Array.isArray(this.host.lineListData) && this.host.lineListData.length;
3839
3966
  const lanes = template && template.lanes || [];
3967
+ const alive = /* @__PURE__ */ new Set();
3840
3968
  lanes.forEach((lane) => {
3841
3969
  (lane.lines || []).forEach((line) => {
3842
3970
  if (!lane.laneId || !line.paramId) return;
3843
3971
  const key = makeTrendKey(lane.laneId, line.paramId);
3972
+ alive.add(key);
3844
3973
  if (this.store[key]) return;
3845
- if (line.trendSegments && line.trendSegments.length) {
3846
- this.store[key] = { segments: this._prepareEchoSegments(line.paramId, line.trendSegments) };
3847
- } else {
3974
+ if (!hasEcho || !line.trendSegments || !line.trendSegments.length) {
3848
3975
  this.store[key] = { segments: [] };
3976
+ return;
3849
3977
  }
3978
+ this.store[key] = { segments: this._prepareEchoSegments(line.paramId, line.trendSegments) };
3850
3979
  });
3851
3980
  });
3981
+ Object.keys(this.store).forEach((key) => {
3982
+ if (!alive.has(key)) delete this.store[key];
3983
+ });
3852
3984
  }
3853
3985
  getChart(laneId) {
3986
+ if (!this._alive()) return null;
3854
3987
  const ref = this.host.getChartRefByLaneId(laneId);
3855
3988
  const chart = ref && ref.chart;
3856
3989
  if (!chart || chart.isDisposed()) return null;
@@ -3858,6 +3991,7 @@ class TrendLineManager {
3858
3991
  }
3859
3992
  /** isDraw 可编辑,或已有控制点(只读回显)的曲线。8.21 */
3860
3993
  listDrawableTargets() {
3994
+ if (!this._alive()) return [];
3861
3995
  const lanes = this.host.currentTemplate && this.host.currentTemplate.lanes || [];
3862
3996
  const list = [];
3863
3997
  lanes.forEach((lane) => {
@@ -3919,42 +4053,62 @@ class TrendLineManager {
3919
4053
  /** 捕获阶段监听 mousedown,保证拖控制点比图表其它逻辑先拿到。8.21 */
3920
4054
  bindChart(laneId, chart) {
3921
4055
  this.unbindChart(laneId);
3922
- if (!chart || chart.isDisposed()) return;
3923
- const onMouseDown = (e) => this.onMouseDown(e, laneId);
4056
+ if (!this._alive() || !chart || chart.isDisposed()) return;
3924
4057
  const onZrDown = (e) => this.onZrMouseDown(e, laneId);
3925
4058
  const onZrMove = (e) => this.onZrHover(e, laneId);
3926
4059
  const onDblClick = (e) => this.handleDblClick(e, laneId);
3927
- const dom = chart.getDom();
3928
- if (dom) dom.addEventListener("mousedown", onMouseDown, true);
3929
4060
  const zr = chart.getZr();
3930
4061
  zr.on("mousedown", onZrDown);
3931
4062
  zr.on("mousemove", onZrMove);
3932
4063
  zr.on("dblclick", onDblClick);
3933
- const onZoom = () => this.scheduleZoomRender();
4064
+ const onZoom = () => {
4065
+ if (this.host && this.host._isPanning) return;
4066
+ if (this.host && typeof this.host.startChartZoom === "function") {
4067
+ this.host.startChartZoom();
4068
+ }
4069
+ };
3934
4070
  if (typeof chart.on === "function") chart.on("datazoom", onZoom);
3935
- chart.getZr().on("mousewheel", onZoom);
3936
- if (dom) dom.addEventListener("wheel", onZoom, { passive: true });
3937
- this.binds.set(laneId, { chart, onMouseDown, onZrDown, onZrMove, onDblClick, onZoom, dom });
4071
+ this.binds.set(laneId, { chart, onZrDown, onZrMove, onDblClick, onZoom, dom: chart.getDom() });
3938
4072
  this.updateCursor(laneId);
3939
4073
  }
3940
4074
  unbindChart(laneId) {
3941
4075
  const bind = this.binds.get(laneId);
3942
4076
  if (!bind) return;
4077
+ const session = this.dragSession;
4078
+ if (session && (session.chart === bind.chart || session.target && session.target.laneId === laneId)) {
4079
+ this._unbindDragWindow();
4080
+ }
3943
4081
  if (bind.dom && bind.onMouseDown) {
3944
4082
  bind.dom.removeEventListener("mousedown", bind.onMouseDown, true);
3945
4083
  }
3946
4084
  if (bind.dom && bind.onZoom) {
3947
4085
  bind.dom.removeEventListener("wheel", bind.onZoom);
3948
4086
  }
3949
- if (bind.chart && !bind.chart.isDisposed()) {
3950
- const zr = bind.chart.getZr();
3951
- if (bind.onZrDown) zr.off("mousedown", bind.onZrDown);
3952
- if (bind.onZrMove) zr.off("mousemove", bind.onZrMove);
3953
- if (bind.onDblClick) zr.off("dblclick", bind.onDblClick);
3954
- if (bind.onZoom) {
3955
- zr.off("mousewheel", bind.onZoom);
3956
- if (typeof bind.chart.off === "function") bind.chart.off("datazoom", bind.onZoom);
4087
+ const chart = bind.chart;
4088
+ try {
4089
+ if (chart && typeof chart.getZr === "function") {
4090
+ const zr = chart.getZr();
4091
+ if (zr) {
4092
+ if (bind.onZrDown) zr.off("mousedown", bind.onZrDown);
4093
+ if (bind.onZrMove) zr.off("mousemove", bind.onZrMove);
4094
+ if (bind.onDblClick) zr.off("dblclick", bind.onDblClick);
4095
+ if (bind.onZoom) zr.off("mousewheel", bind.onZoom);
4096
+ }
4097
+ }
4098
+ if (chart && bind.onZoom && typeof chart.off === "function") {
4099
+ chart.off("datazoom", bind.onZoom);
3957
4100
  }
4101
+ } catch (e) {
4102
+ }
4103
+ delete this._seriesIds[laneId];
4104
+ if (bind) {
4105
+ bind.chart = null;
4106
+ bind.dom = null;
4107
+ bind.onZrDown = null;
4108
+ bind.onZrMove = null;
4109
+ bind.onDblClick = null;
4110
+ bind.onZoom = null;
4111
+ bind.onMouseDown = null;
3958
4112
  }
3959
4113
  this.binds.delete(laneId);
3960
4114
  }
@@ -3975,374 +4129,355 @@ class TrendLineManager {
3975
4129
  /** 图表重绘后对齐叠加层;拖拽中跳过,避免和 _onDragMove 抢 setOption。8.21 */
3976
4130
  // 只排一帧,不再连刷两次 renderAll(会闪)。8.24
3977
4131
  scheduleRender() {
3978
- if (this.dragSession) return;
4132
+ if (!this._alive() || this.dragSession) return;
3979
4133
  if (this._raf) return;
3980
4134
  this._raf = requestAnimationFrame(() => {
3981
4135
  this._raf = null;
4136
+ if (!this._alive()) return;
3982
4137
  this.renderAll();
3983
4138
  });
3984
4139
  }
3985
- /** 缩放过程用 ZR 就地改像素,结束后再整层重画,避免 replaceMerge 把线冲掉。8.22 */
4140
+ // 没有控制点就不跟缩放重画,省一轮 graphic。8.25
4141
+ hasAnyKnots() {
4142
+ const store = this.store;
4143
+ for (const key in store) {
4144
+ const segs = store[key] && store[key].segments;
4145
+ if (segs && segs.some((s) => s.knots && s.knots.length)) return true;
4146
+ }
4147
+ return false;
4148
+ }
4149
+ // 趋势线已是笛卡尔 series,dataZoom 带着走;这些入口留给旧调用方,不再像素跟帧。8.25
3986
4150
  scheduleZoomRender() {
3987
- if (this.dragSession) return;
3988
- this._zoomPending = true;
3989
- if (this._zoomEndTimer) clearTimeout(this._zoomEndTimer);
3990
- this._zoomEndTimer = setTimeout(() => {
3991
- this._zoomPending = false;
3992
- if (this._zoomRaf) {
3993
- cancelAnimationFrame(this._zoomRaf);
3994
- this._zoomRaf = null;
3995
- }
3996
- this.scheduleRender();
3997
- }, 140);
3998
- if (this._zoomRaf) return;
3999
- const tick = () => {
4000
- if (this.dragSession) {
4001
- this._zoomRaf = null;
4002
- return;
4003
- }
4004
- this._patchAllPositions();
4005
- if (this._zoomPending) {
4006
- this._zoomRaf = requestAnimationFrame(tick);
4007
- } else {
4008
- this._zoomRaf = null;
4009
- }
4010
- };
4011
- this._zoomRaf = requestAnimationFrame(tick);
4012
4151
  }
4013
- /** 缩放帧:优先 ZR setShape,元素被冲掉时才回退 renderLane。8.22 */
4152
+ beginPanFollow() {
4153
+ }
4154
+ tickPanFollow() {
4155
+ }
4156
+ endPanFollow() {
4157
+ }
4158
+ endFollowView() {
4159
+ }
4014
4160
  _patchAllPositions() {
4015
- const lanes = this.host.currentTemplate && this.host.currentTemplate.lanes || [];
4016
- lanes.forEach((lane) => this._patchLanePositions(lane.laneId));
4017
4161
  }
4018
- _patchLanePositions(laneId) {
4019
- const chart = this.getChart(laneId);
4020
- if (!chart) return;
4021
- const targets = this.listDrawableTargets().filter((t) => t.laneId === laneId);
4022
- const hasKnots = targets.some((t) => (t.segments || []).some((s) => (s.knots || []).length));
4023
- if (!hasKnots) return;
4024
- const patches = [];
4025
- targets.forEach((target) => {
4026
- patches.push(...this._buildPatches(chart, target));
4027
- });
4028
- if (!patches.length) return;
4029
- if (!this._applyPatchesViaZr(chart, patches) && !this._zoomPending) {
4030
- this.renderLane(laneId);
4162
+ _isTrendSeriesOpt(series) {
4163
+ if (!series) return false;
4164
+ const name = Array.isArray(series.name) ? series.name[0] : series.name;
4165
+ if (name === "trend") return true;
4166
+ const id = String(series.id || "");
4167
+ return id.indexOf("trend_") === 0;
4168
+ }
4169
+ _findCurveSeriesInOption(option, paramId) {
4170
+ const list = option && option.series || [];
4171
+ for (let i = 0; i < list.length; i++) {
4172
+ const s = list[i];
4173
+ if (!s || this._isTrendSeriesOpt(s)) continue;
4174
+ const type = Array.isArray(s.type) ? s.type[0] : s.type;
4175
+ if (type && type !== "line") continue;
4176
+ const name = Array.isArray(s.name) ? s.name[0] : s.name;
4177
+ if (name === "helper" || name === "log-grid" || name === "推荐值") continue;
4178
+ if (String(s.paramId) === String(paramId)) return s;
4031
4179
  }
4180
+ return null;
4032
4181
  }
4033
- _buildPatches(chart, target) {
4034
- const patches = [];
4035
- const { paramId, key, segments, line } = target;
4036
- const canMirror = this._canMirror(line, segments);
4037
- segments.forEach((seg) => {
4038
- const knots = seg.knots || [];
4039
- for (let i = 0; i < knots.length - 1; i++) {
4040
- const p1 = knots[i];
4041
- const p2 = knots[i + 1];
4042
- const px1 = this._knotPixel(chart, target, p1);
4043
- const px2 = this._knotPixel(chart, target, p2);
4044
- if (!px1 || !px2) continue;
4045
- patches.push({
4046
- id: `trend_line_${key}_${seg.id}_${i}`,
4047
- shape: { x1: px1[0], y1: px1[1], x2: px2[0], y2: px2[1] }
4048
- });
4049
- if (canMirror) {
4050
- const mx1 = this._knotPixel(chart, target, p1, "mirror");
4051
- const mx2 = this._knotPixel(chart, target, p2, "mirror");
4052
- if (mx1 && mx2) {
4053
- patches.push({
4054
- id: `trend_mirror_${key}_${seg.id}_${i}`,
4055
- shape: { x1: mx1[0], y1: mx1[1], x2: mx2[0], y2: mx2[1] }
4056
- });
4057
- }
4058
- }
4182
+ _firstKnotDepth(target) {
4183
+ const segs = target && target.segments || [];
4184
+ for (let i = 0; i < segs.length; i++) {
4185
+ const knot = (segs[i].knots || [])[0];
4186
+ if (knot && Number.isFinite(Number(knot.depth))) return Number(knot.depth);
4187
+ }
4188
+ return 0;
4189
+ }
4190
+ _curveAxisOf(chart, target) {
4191
+ const info = findSeriesForDepth(chart, target.paramId, this._firstKnotDepth(target));
4192
+ return {
4193
+ xAxisIndex: info && info.xAxisIndex != null ? info.xAxisIndex : 0,
4194
+ yAxisIndex: info && info.yAxisIndex != null ? info.yAxisIndex : 0
4195
+ };
4196
+ }
4197
+ _buildOneTrendSeries(id, data, lineStyle, pointStyle, xAxisIndex, yAxisIndex) {
4198
+ return {
4199
+ id,
4200
+ name: "trend",
4201
+ type: "line",
4202
+ data,
4203
+ xAxisIndex,
4204
+ yAxisIndex,
4205
+ clip: true,
4206
+ animation: false,
4207
+ silent: true,
4208
+ large: false,
4209
+ z: TREND_LINE_Z,
4210
+ zlevel: 0,
4211
+ symbol: "circle",
4212
+ symbolSize: 8,
4213
+ showSymbol: true,
4214
+ hoverAnimation: false,
4215
+ legendHoverLink: false,
4216
+ connectNulls: false,
4217
+ tooltip: { show: false },
4218
+ emphasis: { disabled: true },
4219
+ lineStyle: {
4220
+ color: lineStyle.stroke,
4221
+ width: lineStyle.lineWidth,
4222
+ type: lineStyle.lineDash || "solid"
4223
+ },
4224
+ itemStyle: {
4225
+ color: pointStyle.fill,
4226
+ borderColor: pointStyle.stroke,
4227
+ borderWidth: pointStyle.lineWidth
4059
4228
  }
4060
- knots.forEach((knot, i) => {
4061
- const px = this._knotPixel(chart, target, knot);
4062
- if (px) {
4063
- patches.push({
4064
- id: `trend_point_${key}_${seg.id}_${i}`,
4065
- shape: { cx: px[0], cy: px[1], r: 4 }
4066
- });
4067
- }
4068
- if (canMirror) {
4069
- const mx = this._knotPixel(chart, target, knot, "mirror");
4070
- if (mx) {
4071
- patches.push({
4072
- id: `trend_mirror_point_${key}_${seg.id}_${i}`,
4073
- shape: { cx: mx[0], cy: mx[1], r: 4 }
4074
- });
4075
- }
4076
- }
4077
- });
4078
- });
4079
- return patches;
4229
+ };
4080
4230
  }
4081
- _findGraphicZrEl(chart, id) {
4082
- let found = null;
4083
- const zr = chart.getZr();
4084
- if (!zr || !zr.storage || typeof zr.storage.traverse !== "function") return null;
4085
- zr.storage.traverse((el) => {
4086
- if (found) return;
4087
- const elId = el.id ?? el.anid ?? el.name ?? el.__ecGraphicId;
4088
- if (elId === id || String(elId) === String(id) || String(elId).endsWith(id)) {
4089
- found = el;
4231
+ _knotPoint(x, depth) {
4232
+ return { value: [x, depth], symbol: "circle", symbolSize: 8 };
4233
+ }
4234
+ _segSeriesData(knots, role) {
4235
+ const raw = [];
4236
+ for (let i = 0; i < (knots || []).length; i++) {
4237
+ const knot = knots[i];
4238
+ const x = this._knotDrawX(knot, role);
4239
+ const depth = Number(knot && knot.depth);
4240
+ if (x == null || !Number.isFinite(depth)) continue;
4241
+ raw.push([x, depth]);
4242
+ }
4243
+ if (raw.length < 2) {
4244
+ return raw.map((p) => this._knotPoint(p[0], p[1]));
4245
+ }
4246
+ const data = [];
4247
+ for (let i = 0; i < raw.length - 1; i++) {
4248
+ const a = raw[i];
4249
+ const b = raw[i + 1];
4250
+ data.push(this._knotPoint(a[0], a[1]));
4251
+ const span = Math.abs(b[1] - a[1]);
4252
+ const n = Math.min(160, Math.max(0, Math.floor(span / 4)));
4253
+ for (let k = 1; k < n; k++) {
4254
+ const t = k / n;
4255
+ data.push({
4256
+ value: [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t],
4257
+ symbol: "none"
4258
+ });
4090
4259
  }
4260
+ }
4261
+ const last = raw[raw.length - 1];
4262
+ data.push(this._knotPoint(last[0], last[1]));
4263
+ return data;
4264
+ }
4265
+ /** 曲线仍 weakFilter;趋势线点太稀,滤掉窗外控制点后穿过视窗的线会断。8.25 */
4266
+ applyDataZoomSkipTrend(option) {
4267
+ if (!option) return;
4268
+ const raw = option.dataZoom;
4269
+ if (!raw) return;
4270
+ const list = Array.isArray(raw) ? raw : [raw];
4271
+ const indexes = [];
4272
+ const series = option.series || [];
4273
+ for (let i = 0; i < series.length; i++) {
4274
+ if (!this._isTrendSeriesOpt(series[i])) indexes.push(i);
4275
+ }
4276
+ if (!indexes.length) return;
4277
+ list.forEach((dz) => {
4278
+ if (dz) dz.seriesIndex = indexes;
4091
4279
  });
4092
- return found;
4093
4280
  }
4094
- _graphicPixel(el) {
4095
- const cx = el.shape && el.shape.cx || 0;
4096
- const cy = el.shape && el.shape.cy || 0;
4097
- return [cx + (el.x || 0), cy + (el.y || 0)];
4281
+ dataZoomSeriesIndex(chart) {
4282
+ if (!chart || chart.isDisposed()) return null;
4283
+ try {
4284
+ const list = chart.getModel().getSeries() || [];
4285
+ const indexes = [];
4286
+ for (let i = 0; i < list.length; i++) {
4287
+ const sm = list[i];
4288
+ const opt = sm && sm.option || {};
4289
+ const name = Array.isArray(sm.name) ? sm.name[0] : sm.name;
4290
+ if (this._isTrendSeriesOpt({ name, id: opt.id })) continue;
4291
+ if (sm.seriesIndex != null) indexes.push(sm.seriesIndex);
4292
+ }
4293
+ return indexes.length ? indexes : null;
4294
+ } catch (e) {
4295
+ return null;
4296
+ }
4098
4297
  }
4099
- _applyKnotPixel(chart, target, knot, role, offsetX, offsetY) {
4100
- const data = this._pixelToTrend(chart, target, offsetX, offsetY, true);
4101
- if (!data) return false;
4102
- const range = this._drawValueRange(target, chart, data.depth);
4103
- let depth = snapDepthToEdge(data.depth, range.depthMin, range.depthMax, DEPTH_EDGE_SNAP);
4104
- if (data.value == null) return false;
4105
- const clickX = Math.max(range.valueMin, Math.min(range.valueMax, data.value));
4106
- const all = this._allKnots(target.key);
4107
- const idx = all.indexOf(knot);
4108
- if (idx > 0) depth = Math.max(depth, all[idx - 1].depth);
4109
- if (idx >= 0 && idx < all.length - 1) depth = Math.min(depth, all[idx + 1].depth);
4110
- knot.depth = parseFloat(depth.toFixed(3));
4111
- this._applyPointerValue(chart, target, knot, knot.depth, clickX, role);
4112
- return true;
4298
+ /** 笛卡尔 [曲线值, 井深];对数道 X 已是 lg。和曲线共用轴,dataZoom 同一帧带走。8.25 */
4299
+ _buildTrendSeries(target, xAxisIndex, yAxisIndex) {
4300
+ const list = [];
4301
+ const { key, segments, line } = target;
4302
+ const canMirror = this._canMirror(line, segments);
4303
+ const doubleLine = this._useGammaStyle(line, segments);
4304
+ const mainLineStyle = doubleLine ? TREND_DOUBLE_LINE_STYLE : TREND_LINE_STYLE;
4305
+ const mainPointStyle = doubleLine ? TREND_DOUBLE_POINT_STYLE : TREND_POINT_STYLE;
4306
+ (segments || []).forEach((seg) => {
4307
+ const knots = seg.knots || [];
4308
+ if (!knots.length) return;
4309
+ list.push(
4310
+ this._buildOneTrendSeries(
4311
+ `trend_${key}_${seg.id}`,
4312
+ this._segSeriesData(knots, "main"),
4313
+ mainLineStyle,
4314
+ mainPointStyle,
4315
+ xAxisIndex,
4316
+ yAxisIndex
4317
+ )
4318
+ );
4319
+ if (!canMirror) return;
4320
+ const mirror = this._segSeriesData(knots, "mirror");
4321
+ if (!mirror.length) return;
4322
+ list.push(
4323
+ this._buildOneTrendSeries(
4324
+ `trend_mirror_${key}_${seg.id}`,
4325
+ mirror,
4326
+ TREND_MIRROR_LINE_STYLE,
4327
+ TREND_MIRROR_POINT_STYLE,
4328
+ xAxisIndex,
4329
+ yAxisIndex
4330
+ )
4331
+ );
4332
+ });
4333
+ return list;
4113
4334
  }
4114
- /** 控制点交给 ZR 拖,不再和 dataZoom 抢 DOM mousedown。8.22 */
4115
- _bindPointInteractions(chart, laneId) {
4116
- const targets = this.listDrawableTargets().filter((t) => t.laneId === laneId && t.editable);
4335
+ /** 刷 option 时给该道补上趋势 series,所有有控制点的泳道都会走。8.25 */
4336
+ appendSeriesToOption(option, lane) {
4337
+ if (!option || !lane) return option;
4338
+ if (!Array.isArray(option.series)) option.series = [];
4339
+ option.series = option.series.filter((s) => !this._isTrendSeriesOpt(s));
4340
+ const targets = this.listDrawableTargets().filter((t) => t.laneId === lane.laneId);
4341
+ const added = [];
4117
4342
  targets.forEach((target) => {
4118
- const canMirror = this._canMirror(target.line, target.segments);
4119
- (target.segments || []).forEach((seg) => {
4120
- (seg.knots || []).forEach((knot, i) => {
4121
- this._bindOnePointEl(
4122
- chart,
4123
- target,
4124
- knot,
4125
- `trend_point_${target.key}_${seg.id}_${i}`,
4126
- "main"
4127
- );
4128
- if (canMirror && knot.mirrorValue != null && Number.isFinite(Number(knot.mirrorValue))) {
4129
- this._bindOnePointEl(
4130
- chart,
4131
- target,
4132
- knot,
4133
- `trend_mirror_point_${target.key}_${seg.id}_${i}`,
4134
- "mirror"
4135
- );
4136
- }
4137
- });
4138
- });
4343
+ const curve = this._findCurveSeriesInOption(option, target.paramId);
4344
+ if (!curve) return;
4345
+ const xAxisIndex = curve.xAxisIndex == null ? 0 : Number(curve.xAxisIndex);
4346
+ const yAxisIndex = curve.yAxisIndex == null ? 0 : Number(curve.yAxisIndex);
4347
+ added.push(...this._buildTrendSeries(target, xAxisIndex, yAxisIndex));
4139
4348
  });
4349
+ option.series.push(...added);
4350
+ this._seriesIds[lane.laneId] = added.map((s) => s.id);
4351
+ this.applyDataZoomSkipTrend(option);
4352
+ return option;
4140
4353
  }
4141
- _bindOnePointEl(chart, target, knot, id, role) {
4142
- const el = this._findGraphicZrEl(chart, id);
4143
- if (!el) return;
4144
- el.silent = false;
4145
- el.draggable = true;
4146
- el.cursor = "grab";
4147
- if (el.off && el.__trendDragBound) {
4148
- el.off("mousedown", el.__trendOnDown);
4149
- el.off("drag", el.__trendOnDrag);
4150
- el.off("dragend", el.__trendOnEnd);
4151
- }
4152
- const onDown = (e) => {
4153
- this._clearClickTimer();
4154
- this.activeKey = target.key;
4155
- this.dragSession = { chart, target, knot, role, graphic: true };
4156
- this._setPanLocked(true);
4157
- if (e && typeof e.stop === "function") e.stop();
4158
- if (e && e.event) {
4159
- e.event.preventDefault();
4160
- e.event.stopPropagation();
4161
- }
4162
- };
4163
- const onDrag = () => {
4164
- if (!this.dragSession) {
4165
- this.dragSession = { chart, target, knot, role, graphic: true };
4166
- this._setPanLocked(true);
4167
- }
4168
- const [x, y] = this._graphicPixel(el);
4169
- if (!this._applyKnotPixel(chart, target, knot, role, x, y)) return;
4170
- el.x = 0;
4171
- el.y = 0;
4172
- this._patchLanePositions(target.laneId);
4173
- };
4174
- const onEnd = () => {
4175
- if (!this.dragSession) return;
4176
- const sessionTarget = this.dragSession.target;
4177
- this.dragSession = null;
4178
- this._setPanLocked(false, true);
4179
- this._ignoreClickUntil = Date.now() + 300;
4180
- this._emitChange(sessionTarget, { redraw: false });
4181
- };
4182
- el.__trendOnDown = onDown;
4183
- el.__trendOnDrag = onDrag;
4184
- el.__trendOnEnd = onEnd;
4185
- el.__trendDragBound = true;
4186
- el.on("mousedown", onDown);
4187
- el.on("drag", onDrag);
4188
- el.on("dragend", onEnd);
4189
- }
4190
- _applyPatchesViaZr(chart, patches) {
4191
- if (!patches.length) return false;
4192
- let applied = 0;
4193
- patches.forEach(({ id, shape }) => {
4194
- const el = this._findGraphicZrEl(chart, id);
4195
- if (!el || typeof el.setShape !== "function") return;
4196
- el.setShape(shape);
4197
- applied += 1;
4198
- });
4199
- if (applied > 0 && chart.getZr() && typeof chart.getZr().refreshImmediately === "function") {
4200
- chart.getZr().refreshImmediately();
4354
+ _clearLeftoverGraphic(chart) {
4355
+ if (!chart || chart.isDisposed() || this._graphicCleared.has(chart)) return;
4356
+ this._graphicCleared.add(chart);
4357
+ try {
4358
+ chart.setOption({ graphic: [] }, { replaceMerge: ["graphic"], silent: true, lazyUpdate: false });
4359
+ } catch (e) {
4201
4360
  }
4202
- return applied > 0;
4203
- }
4204
- // 只 merge 新图形,不动已有线。8.24
4205
- _addGraphicElements(chart, elements) {
4206
- if (!elements.length) return;
4207
- chart.setOption(
4208
- {
4209
- graphic: elements.map((el) => ({ ...el, $action: "merge" }))
4210
- },
4211
- { silent: true, lazyUpdate: false }
4212
- );
4213
- }
4214
- // 按 id 删除,不 replaceMerge 整层 graphic。8.24
4215
- _removeGraphicIds(chart, ids) {
4216
- if (!ids.length) return;
4217
- chart.setOption(
4218
- {
4219
- graphic: ids.map((id) => ({ id, $action: "remove" }))
4220
- },
4221
- { silent: true, lazyUpdate: false }
4222
- );
4223
4361
  }
4224
4362
  renderAll(forceReplace = false) {
4225
4363
  const lanes = this.host.currentTemplate && this.host.currentTemplate.lanes || [];
4226
4364
  lanes.forEach((lane) => this.renderLane(lane.laneId, forceReplace));
4227
4365
  }
4228
- /** 已有线只改 ZR 形状;新点/新线段单独 merge 进去,绝不整层 graphic 替换。8.24 */
4366
+ /** 用笛卡尔 series 画趋势线;平移/缩放交给 dataZoom,不跟像素。8.25 */
4229
4367
  renderLane(laneId, forceReplace = false) {
4368
+ if (!this._alive()) return;
4230
4369
  const chart = this.getChart(laneId);
4231
4370
  if (!chart) return;
4371
+ this._clearLeftoverGraphic(chart);
4232
4372
  const targets = this.listDrawableTargets().filter((t) => t.laneId === laneId);
4233
- const elements = [];
4373
+ const next = [];
4234
4374
  targets.forEach((target) => {
4235
- elements.push(...this._buildElements(chart, target));
4375
+ const axis = this._curveAxisOf(chart, target);
4376
+ next.push(...this._buildTrendSeries(target, axis.xAxisIndex, axis.yAxisIndex));
4236
4377
  });
4237
- const hasKnots = targets.some((t) => (t.segments || []).some((s) => (s.knots || []).length));
4238
- if (hasKnots && !elements.length) return;
4239
- const nextIds = elements.map((el) => el.id);
4240
- const prevIds = this._graphicIds[laneId] || [];
4241
- const prevSet = new Set(prevIds);
4378
+ const nextIds = next.map((s) => s.id);
4242
4379
  const nextSet = new Set(nextIds);
4243
- const added = elements.filter((el) => !prevSet.has(el.id));
4244
- const kept = elements.filter((el) => prevSet.has(el.id));
4245
- const removedIds = prevIds.filter((id) => !nextSet.has(id));
4246
- if (forceReplace) {
4247
- this._removeGraphicIds(chart, prevIds);
4248
- this._addGraphicElements(chart, elements);
4249
- } else {
4250
- if (kept.length) {
4251
- this._applyPatchesViaZr(
4252
- chart,
4253
- kept.map((el) => ({ id: el.id, shape: el.shape }))
4254
- );
4380
+ const liveIds = this._chartTrendSeriesIds(chart);
4381
+ const prevIds = [.../* @__PURE__ */ new Set([...this._seriesIds[laneId] || [], ...liveIds])];
4382
+ const replace = !!(forceReplace && !this.dragSession);
4383
+ const patch = next.map((s) => ({
4384
+ ...s,
4385
+ data: s.data || [],
4386
+ $action: replace ? "replace" : "merge"
4387
+ }));
4388
+ prevIds.forEach((id) => {
4389
+ if (!nextSet.has(id)) patch.push({ id, $action: "remove" });
4390
+ });
4391
+ if (patch.length) {
4392
+ try {
4393
+ chart.setOption({ series: patch }, { silent: true, lazyUpdate: false });
4394
+ } catch (e) {
4255
4395
  }
4256
- if (removedIds.length) this._removeGraphicIds(chart, removedIds);
4257
- if (added.length) this._addGraphicElements(chart, added);
4258
4396
  }
4259
- this._graphicIds[laneId] = nextIds;
4260
- this._bindPointInteractions(chart, laneId);
4261
- this.updateCursor(laneId);
4262
- }
4263
- _buildElements(chart, target) {
4264
- const elements = [];
4265
- const { paramId, key, editable, segments, line } = target;
4266
- const canMirror = this._canMirror(line, segments);
4267
- const doubleLine = this._useGammaStyle(line, segments);
4268
- const mainLineStyle = doubleLine ? TREND_DOUBLE_LINE_STYLE : TREND_LINE_STYLE;
4269
- const mainPointStyle = doubleLine ? TREND_DOUBLE_POINT_STYLE : TREND_POINT_STYLE;
4270
- segments.forEach((seg) => {
4271
- const knots = seg.knots || [];
4272
- for (let i = 0; i < knots.length - 1; i++) {
4273
- const p1 = knots[i];
4274
- const p2 = knots[i + 1];
4275
- const px1 = this._knotPixel(chart, target, p1);
4276
- const px2 = this._knotPixel(chart, target, p2);
4277
- if (!px1 || !px2) continue;
4278
- elements.push({
4279
- type: "line",
4280
- id: `trend_line_${key}_${seg.id}_${i}`,
4281
- shape: { x1: px1[0], y1: px1[1], x2: px2[0], y2: px2[1] },
4282
- style: { ...mainLineStyle },
4283
- z: TREND_LINE_Z,
4284
- silent: true
4285
- });
4286
- if (canMirror) {
4287
- const mx1 = this._knotPixel(chart, target, p1, "mirror");
4288
- const mx2 = this._knotPixel(chart, target, p2, "mirror");
4289
- if (mx1 && mx2) {
4290
- elements.push({
4291
- type: "line",
4292
- id: `trend_mirror_${key}_${seg.id}_${i}`,
4293
- shape: { x1: mx1[0], y1: mx1[1], x2: mx2[0], y2: mx2[1] },
4294
- style: { ...TREND_MIRROR_LINE_STYLE },
4295
- z: TREND_LINE_Z - 1,
4296
- silent: true
4297
- });
4397
+ this._seriesIds[laneId] = nextIds;
4398
+ if (!this.dragSession) this._syncCachedOption(laneId);
4399
+ if (this.dragSession) {
4400
+ this.updateCursor(laneId);
4401
+ return;
4402
+ }
4403
+ const idsChanged = nextIds.length !== prevIds.length || nextIds.some((id, i) => id !== prevIds[i]);
4404
+ if (idsChanged) {
4405
+ const skip = this.dataZoomSeriesIndex(chart);
4406
+ if (skip && skip.length) {
4407
+ try {
4408
+ const dz = chart.getModel().getComponent("dataZoom", 0);
4409
+ const opt = dz && dz.option;
4410
+ const next2 = { seriesIndex: skip };
4411
+ if (opt) {
4412
+ if (opt.start != null) next2.start = opt.start;
4413
+ if (opt.end != null) next2.end = opt.end;
4414
+ if (opt.startValue != null) next2.startValue = opt.startValue;
4415
+ if (opt.endValue != null) next2.endValue = opt.endValue;
4298
4416
  }
4417
+ chart.setOption({ dataZoom: [next2] }, { silent: true, lazyUpdate: true });
4418
+ } catch (e) {
4299
4419
  }
4300
4420
  }
4301
- knots.forEach((knot, i) => {
4302
- const px = this._knotPixel(chart, target, knot);
4303
- if (!px) return;
4304
- elements.push({
4305
- type: "circle",
4306
- id: `trend_point_${key}_${seg.id}_${i}`,
4307
- shape: { cx: px[0], cy: px[1], r: 4 },
4308
- style: { ...mainPointStyle },
4309
- z: TREND_POINT_Z,
4310
- zlevel: 10,
4311
- silent: !editable,
4312
- draggable: !!editable,
4313
- cursor: editable ? "grab" : "default"
4314
- });
4315
- if (canMirror) {
4316
- const mx = this._knotPixel(chart, target, knot, "mirror");
4317
- if (mx) {
4318
- elements.push({
4319
- type: "circle",
4320
- id: `trend_mirror_point_${key}_${seg.id}_${i}`,
4321
- shape: { cx: mx[0], cy: mx[1], r: 4 },
4322
- style: { ...TREND_MIRROR_POINT_STYLE },
4323
- z: TREND_POINT_Z,
4324
- zlevel: 10,
4325
- silent: !editable,
4326
- draggable: !!editable,
4327
- cursor: editable ? "grab" : "default"
4328
- });
4329
- }
4330
- }
4331
- });
4332
- });
4333
- return elements;
4421
+ }
4422
+ this.updateCursor(laneId);
4423
+ }
4424
+ /** 把当前 store 写回 allOptions,避免随后 notMerge 用旧趋势 series 把点盖回去。8.25 */
4425
+ _syncCachedOption(laneId) {
4426
+ const host = this.host;
4427
+ if (!host || typeof host.getOptionsByLineId !== "function") return null;
4428
+ const option = host.getOptionsByLineId(laneId);
4429
+ if (!option || !Array.isArray(option.series)) return null;
4430
+ const lanes = host.currentTemplate && host.currentTemplate.lanes || [];
4431
+ const lane = lanes.find((l) => l.laneId === laneId);
4432
+ if (!lane) return null;
4433
+ this.appendSeriesToOption(option, lane);
4434
+ return option;
4435
+ }
4436
+ /** 删点后走整份 option 替换,ECharts 才会丢掉缩短后的旧 data。8.25 */
4437
+ _flushLane(laneId) {
4438
+ const option = this._syncCachedOption(laneId);
4439
+ if (option && this.host && typeof this.host.flushLaneChartOption === "function") {
4440
+ option.__kdTrendSynced = true;
4441
+ try {
4442
+ this.host.flushLaneChartOption(laneId, option);
4443
+ } finally {
4444
+ delete option.__kdTrendSynced;
4445
+ }
4446
+ this.updateCursor(laneId);
4447
+ return;
4448
+ }
4449
+ this.renderLane(laneId, true);
4450
+ }
4451
+ _chartTrendSeriesIds(chart) {
4452
+ if (!chart || chart.isDisposed()) return [];
4453
+ try {
4454
+ const list = chart.getModel().getSeries() || [];
4455
+ const ids = [];
4456
+ for (let i = 0; i < list.length; i++) {
4457
+ const sm = list[i];
4458
+ const opt = sm && sm.option || {};
4459
+ const name = Array.isArray(sm.name) ? sm.name[0] : sm.name;
4460
+ const id = opt.id || sm.id;
4461
+ if (this._isTrendSeriesOpt({ name, id }) && id) ids.push(id);
4462
+ }
4463
+ return ids;
4464
+ } catch (e) {
4465
+ return [];
4466
+ }
4334
4467
  }
4335
4468
  _findKnotAtPointer(chart, target, offsetX, offsetY, radius = TREND_HIT_RADIUS) {
4336
4469
  let best = null;
4337
4470
  let bestDist = radius;
4338
4471
  const canMirror = this._canMirror(target.line, target.segments);
4339
4472
  for (const seg of target.segments || []) {
4340
- for (const knot of seg.knots || []) {
4473
+ const knots = seg.knots || [];
4474
+ for (let i = 0; i < knots.length; i++) {
4475
+ const knot = knots[i];
4341
4476
  const px = this._knotPixel(chart, target, knot);
4342
4477
  if (px) {
4343
4478
  const dist = Math.hypot(px[0] - offsetX, px[1] - offsetY);
4344
4479
  if (dist <= bestDist) {
4345
- best = { knot, seg, role: "main" };
4480
+ best = { knot, seg, knotIndex: i, role: "main" };
4346
4481
  bestDist = dist;
4347
4482
  }
4348
4483
  }
@@ -4351,7 +4486,7 @@ class TrendLineManager {
4351
4486
  if (!mx) continue;
4352
4487
  const mirrorDist = Math.hypot(mx[0] - offsetX, mx[1] - offsetY);
4353
4488
  if (mirrorDist <= bestDist) {
4354
- best = { knot, seg, role: "mirror" };
4489
+ best = { knot, seg, knotIndex: i, role: "mirror" };
4355
4490
  bestDist = mirrorDist;
4356
4491
  }
4357
4492
  }
@@ -4360,6 +4495,11 @@ class TrendLineManager {
4360
4495
  }
4361
4496
  /** 单击空白处加点;延迟执行以便和双击断段区分。命中已有点则不加点。返回 true 表示已消费本次点击。8.21 */
4362
4497
  handleClick(e, laneId) {
4498
+ if (!this._alive()) return false;
4499
+ if (this.dragSession) {
4500
+ this._endDrag(true);
4501
+ return true;
4502
+ }
4363
4503
  const target = this.getTargetByLane(laneId);
4364
4504
  if (!target || !target.editable) return false;
4365
4505
  if (Date.now() < this._ignoreClickUntil) return true;
@@ -4372,6 +4512,7 @@ class TrendLineManager {
4372
4512
  this._clearClickTimer();
4373
4513
  this.clickTimer = setTimeout(() => {
4374
4514
  this.clickTimer = null;
4515
+ if (!this._alive()) return;
4375
4516
  this._addPoint(chart, target, offsetX, offsetY);
4376
4517
  }, TREND_CLICK_DELAY_MS);
4377
4518
  return true;
@@ -4393,7 +4534,7 @@ class TrendLineManager {
4393
4534
  }
4394
4535
  return true;
4395
4536
  }
4396
- /** 右键命中控制点时弹出「删除控制点」;先结束可能误触发的拖拽并重绘,避免线段被 replaceMerge 丢掉。8.21 */
4537
+ /** 右键命中控制点时弹出「删除控制点」。打开菜单时不要 setOption,否则会吞掉菜单点击。8.21 */
4397
4538
  handleContextMenu(e, laneId) {
4398
4539
  const target = this.getTargetByLane(laneId);
4399
4540
  if (!target || !target.editable) return false;
@@ -4407,14 +4548,26 @@ class TrendLineManager {
4407
4548
  this.host.showTrendKnotContextMenu(e, {
4408
4549
  key: target.key,
4409
4550
  knot: hit.knot,
4551
+ segId: hit.seg && hit.seg.id,
4552
+ knotIndex: hit.knotIndex,
4553
+ depth: hit.knot.depth,
4554
+ value: hit.knot.value,
4555
+ plotValue: hit.knot.plotValue,
4410
4556
  laneId
4411
4557
  });
4412
- this.renderLane(laneId);
4413
4558
  return true;
4414
4559
  }
4415
4560
  /** 指针靠近控制点时先停地应力平移,按下时才来得及拖点。8.22 */
4416
4561
  onZrHover(e, laneId) {
4417
- if (this.dragSession) return;
4562
+ if (!this._alive()) return;
4563
+ if (this.dragSession) {
4564
+ e.__ecRoamConsumed = true;
4565
+ if (typeof e.stop === "function") e.stop();
4566
+ return;
4567
+ }
4568
+ if (this.host && typeof this.host.isChartBusy === "function" && this.host.isChartBusy()) {
4569
+ return;
4570
+ }
4418
4571
  const chart = this.getChart(laneId);
4419
4572
  const target = this.getTargetByLane(laneId);
4420
4573
  if (!chart || !target || !target.editable) {
@@ -4434,38 +4587,32 @@ class TrendLineManager {
4434
4587
  this.host.setInsideDataZoomPanEnabled(!locked);
4435
4588
  }
4436
4589
  }
4437
- /** zr 里拦住 dataZoom 的按下,否则图会跟着点一起拖。8.22 */
4590
+ /** 左键拖点与右键删点同一套命中(zr offset + _findKnotAtPointer)。8.25 */
4438
4591
  onZrMouseDown(e, laneId) {
4592
+ if (!this._alive()) return;
4439
4593
  if (e.which != null && e.which !== 1 && e.event && e.event.button !== 0) return;
4594
+ if (this.dragSession) {
4595
+ e.__ecRoamConsumed = true;
4596
+ if (typeof e.stop === "function") e.stop();
4597
+ this._endDrag(true);
4598
+ return;
4599
+ }
4440
4600
  const target = this.getTargetByLane(laneId);
4441
4601
  const chart = this.getChart(laneId);
4442
- if (!target || !target.editable || !chart || this.dragSession) return;
4602
+ if (!target || !target.editable || !chart) return;
4443
4603
  const { offsetX, offsetY } = eventOffsetInChart(chart, e);
4444
- if (!this._findKnotAtPointer(chart, target, offsetX, offsetY)) return;
4604
+ const hit = this._findKnotAtPointer(chart, target, offsetX, offsetY);
4605
+ if (!hit) return;
4606
+ e.__ecRoamConsumed = true;
4445
4607
  if (typeof e.stop === "function") e.stop();
4446
4608
  e.cancelBubble = true;
4447
4609
  if (e.event) {
4448
4610
  e.event.preventDefault();
4449
4611
  e.event.stopPropagation();
4450
4612
  }
4451
- }
4452
- /** 仅左键拖拽控制点。右键 mousedown 会先于 contextmenu,绝不能当拖拽处理。8.21 */
4453
- onMouseDown(nativeEvent, laneId) {
4454
- if (nativeEvent.button !== 0) return;
4455
- const target = this.getTargetByLane(laneId);
4456
- if (!target || !target.editable || this.dragSession) return;
4457
- const chart = this.getChart(laneId);
4458
- if (!chart) return;
4459
- const { offsetX, offsetY } = eventOffsetInChart(chart, nativeEvent);
4460
- const hit = this._findKnotAtPointer(chart, target, offsetX, offsetY);
4461
- if (!hit) return;
4462
- nativeEvent.stopPropagation();
4463
- nativeEvent.stopImmediatePropagation();
4464
- nativeEvent.preventDefault();
4465
4613
  this._clearClickTimer();
4466
4614
  this.activeKey = target.key;
4467
- this._setPanLocked(true);
4468
- this._startDrag(chart, target, hit.knot, nativeEvent, hit.role);
4615
+ this._startDrag(chart, target, hit.knot, e, hit.role);
4469
4616
  }
4470
4617
  /** 沿深度增大方向追加控制点;曲线值限制在该线 min/max 内。8.21 */
4471
4618
  _addPoint(chart, target, offsetX, offsetY) {
@@ -4561,6 +4708,7 @@ class TrendLineManager {
4561
4708
  }
4562
4709
  /** 清空全部趋势线控制点并重绘。8.22 */
4563
4710
  resetAllTrendLines() {
4711
+ if (this.dragSession) this._endDrag(false);
4564
4712
  const prev = this.listDrawableTargets().filter(
4565
4713
  (t) => (t.segments || []).some((s) => (s.knots || []).length)
4566
4714
  );
@@ -4574,25 +4722,39 @@ class TrendLineManager {
4574
4722
  segments: []
4575
4723
  });
4576
4724
  });
4577
- this.renderAll(true);
4725
+ this._flushTrendLanes(prev);
4578
4726
  }
4579
4727
  /** 清空指定曲线的控制点;不传 key 则清当前编辑曲线。8.21 */
4580
4728
  resetTrendLine(key) {
4581
4729
  const target = key ? this.listDrawableTargets().find((t) => t.key === key) : this.getActiveTarget() || this.listDrawableTargets().find((t) => t.editable);
4582
4730
  if (!target) return;
4731
+ if (this.dragSession) this._endDrag(false);
4583
4732
  this.store[target.key] = { segments: [] };
4584
- this.renderLane(target.laneId, true);
4733
+ this._flushLane(target.laneId);
4585
4734
  this._emitChange(target, { redraw: false });
4586
4735
  }
4736
+ /** 把 store 已空的泳道整份 option 刷进图,线段才会从画面上消失。8.25 */
4737
+ _flushTrendLanes(prev) {
4738
+ const laneIds = /* @__PURE__ */ new Set();
4739
+ (prev || []).forEach((t) => t.laneId && laneIds.add(t.laneId));
4740
+ Object.keys(this._seriesIds).forEach((id) => laneIds.add(id));
4741
+ this.binds.forEach((_, laneId) => {
4742
+ const chart = this.getChart(laneId);
4743
+ if (chart && this._chartTrendSeriesIds(chart).length) laneIds.add(laneId);
4744
+ });
4745
+ if (!laneIds.size) {
4746
+ this.renderAll(true);
4747
+ return;
4748
+ }
4749
+ laneIds.forEach((laneId) => this._flushLane(laneId));
4750
+ }
4587
4751
  /** 删除一个控制点。段内只剩 0~1 个点时整段丢掉(画不出线)。8.21 */
4588
4752
  deleteKnot(payload) {
4589
- if (!payload || !payload.key || !payload.knot) return;
4753
+ if (!payload || !payload.key) return;
4590
4754
  const bucket = this.store[payload.key];
4591
4755
  if (!bucket) return;
4592
4756
  bucket.segments = bucket.segments.map((seg) => {
4593
- const idx = seg.knots.findIndex(
4594
- (k) => k === payload.knot || Math.abs(k.depth - payload.knot.depth) < 0.05 && Math.abs(k.value - payload.knot.value) < 0.01
4595
- );
4757
+ const idx = this._findKnotIndex(seg, payload);
4596
4758
  if (idx < 0) return seg;
4597
4759
  const knots = seg.knots.filter((_, i) => i !== idx);
4598
4760
  if (knots.length <= 1) return { ...seg, knots: [] };
@@ -4600,9 +4762,32 @@ class TrendLineManager {
4600
4762
  }).filter((s) => s.knots.length > 0);
4601
4763
  const target = this.listDrawableTargets().find((t) => t.key === payload.key);
4602
4764
  const laneId = target && target.laneId || payload.laneId;
4603
- if (laneId) this.renderLane(laneId, true);
4765
+ if (laneId) this._flushLane(laneId);
4604
4766
  if (target) this._emitChange(target, { redraw: false });
4605
4767
  }
4768
+ _findKnotIndex(seg, payload) {
4769
+ const knots = seg && seg.knots || [];
4770
+ if (!knots.length || !payload) return -1;
4771
+ if (payload.knot) {
4772
+ const byRef = knots.indexOf(payload.knot);
4773
+ if (byRef >= 0) return byRef;
4774
+ }
4775
+ if (payload.segId && seg.id !== payload.segId) return -1;
4776
+ if (Number.isInteger(payload.knotIndex) && payload.knotIndex >= 0 && payload.knotIndex < knots.length) {
4777
+ return payload.knotIndex;
4778
+ }
4779
+ const depth = payload.knot != null ? payload.knot.depth : payload.depth;
4780
+ const value = payload.knot != null ? payload.knot.value : payload.value;
4781
+ const plotValue = payload.knot != null ? payload.knot.plotValue : payload.plotValue;
4782
+ return knots.findIndex((k) => {
4783
+ if (depth == null || Math.abs(Number(k.depth) - Number(depth)) >= 0.05) return false;
4784
+ if (value != null && Math.abs(Number(k.value) - Number(value)) < 0.01) return true;
4785
+ if (plotValue != null && k.plotValue != null && Math.abs(Number(k.plotValue) - Number(plotValue)) < 0.01) {
4786
+ return true;
4787
+ }
4788
+ return value == null;
4789
+ });
4790
+ }
4606
4791
  /** 返回 geosteering trendLinePoint 格式的线段数组;8137 带上 isDouble / calcType。8.22 */
4607
4792
  getTrendLineData(mainId = "demo") {
4608
4793
  const grouped = {};
@@ -4626,12 +4811,14 @@ class TrendLineManager {
4626
4811
  }
4627
4812
  const segs = this._prepareEchoSegments(paramId, segments);
4628
4813
  this.store[key] = { segments: segs };
4629
- if (found.line) found.line.trendSegments = segs;
4630
4814
  this.renderLane(found.laneId);
4631
4815
  }
4632
4816
  /** 把 geosteering 线段数组灌进对应曲线并画出叠加层。8.21 */
4633
4817
  setTrendLineList(lineList, overwrite = true) {
4634
- if (!Array.isArray(lineList) || !lineList.length) return;
4818
+ if (!Array.isArray(lineList) || !lineList.length) {
4819
+ this.resetAllTrendLines();
4820
+ return;
4821
+ }
4635
4822
  const grouped = fromTrendLineSavePayload(lineList);
4636
4823
  Object.entries(grouped).forEach(([paramId, segments]) => {
4637
4824
  this.setTrendLineData(paramId, segments, overwrite);
@@ -4667,33 +4854,58 @@ class TrendLineManager {
4667
4854
  const last = target.segments[target.segments.length - 1];
4668
4855
  return !!(last && !last._closed && last.knots && last.knots.length);
4669
4856
  }
4670
- /** 拖拽开始:在 window 上听 mousemove/mouseup,避免鼠标移出图表丢事件。8.21 */
4857
+ /** 拖拽开始:按下不刷图,避免 setOption 把这次按住掐掉。8.25 */
4671
4858
  _startDrag(chart, target, knot, nativeEvent, role = "main") {
4859
+ this._unbindDragWindow();
4672
4860
  const onMove = (ev) => this._onDragMove(ev);
4673
- const onUp = () => this._endDrag(true);
4861
+ const onUp = (ev) => {
4862
+ if (ev && ev.button != null && ev.button !== 0) return;
4863
+ this._endDrag(true);
4864
+ };
4865
+ const startStamp = nativeEvent && nativeEvent.timeStamp;
4866
+ const onClickEnd = (ev) => {
4867
+ if (ev && ev.button != null && ev.button !== 0) return;
4868
+ if (startStamp != null && ev.timeStamp === startStamp) return;
4869
+ this._endDrag(true);
4870
+ };
4871
+ const { offsetX, offsetY } = eventOffsetInChart(chart, nativeEvent);
4872
+ const px = this._knotPixel(chart, target, knot, role);
4674
4873
  this.dragSession = {
4675
4874
  chart,
4676
4875
  target,
4677
4876
  knot,
4678
4877
  role,
4679
4878
  onMove,
4680
- onUp
4879
+ onUp,
4880
+ onClickEnd,
4881
+ grabDX: px ? px[0] - offsetX : 0,
4882
+ grabDY: px ? px[1] - offsetY : 0
4681
4883
  };
4682
- if (this.host && typeof this.host.setInsideDataZoomPanEnabled === "function") {
4683
- this.host.setInsideDataZoomPanEnabled(false);
4684
- }
4685
4884
  window.addEventListener("mousemove", onMove);
4686
4885
  window.addEventListener("mouseup", onUp);
4886
+ window.addEventListener("mousedown", onClickEnd, true);
4687
4887
  this.updateCursor(target.laneId);
4688
- this._onDragMove(nativeEvent);
4689
4888
  }
4690
- /** 拖拽时夹在相邻点之间,不能越过前后控制点。8.21 */
4889
+ _unbindDragWindow() {
4890
+ const session = this.dragSession;
4891
+ if (!session) return;
4892
+ window.removeEventListener("mousemove", session.onMove);
4893
+ window.removeEventListener("mouseup", session.onUp);
4894
+ if (session.onClickEnd) {
4895
+ window.removeEventListener("mousedown", session.onClickEnd, true);
4896
+ }
4897
+ this.dragSession = null;
4898
+ }
4899
+ /** 拖拽时点跟着鼠标走;夹在相邻点之间,不能越过前后控制点。8.21 */
4691
4900
  _onDragMove(nativeEvent) {
4901
+ if (!this._alive()) return;
4692
4902
  const session = this.dragSession;
4693
4903
  if (!session) return;
4694
4904
  const { chart, target, knot, role } = session;
4695
- const { offsetX, offsetY } = eventOffsetInChart(chart, nativeEvent);
4696
- const data = this._pixelToTrend(chart, target, offsetX, offsetY, true);
4905
+ const pos = eventOffsetInChart(chart, nativeEvent);
4906
+ const offsetX = pos.offsetX + (session.grabDX || 0);
4907
+ const offsetY = pos.offsetY + (session.grabDY || 0);
4908
+ const data = this._pixelToDragTrend(chart, target, offsetX, offsetY);
4697
4909
  if (!data) return;
4698
4910
  const range = this._drawValueRange(target, chart, data.depth);
4699
4911
  let depth = snapDepthToEdge(data.depth, range.depthMin, range.depthMax, DEPTH_EDGE_SNAP);
@@ -4707,19 +4919,33 @@ class TrendLineManager {
4707
4919
  this._applyPointerValue(chart, target, knot, knot.depth, clickX, role);
4708
4920
  this.renderLane(target.laneId);
4709
4921
  }
4922
+ /** 拖点用轴 scale 反算,和画点同一套,避免点落在鼠标旁边。8.25 */
4923
+ _pixelToDragTrend(chart, target, offsetX, offsetY) {
4924
+ const finder = findSeriesForDepth(chart, target.paramId, this._firstKnotDepth(target));
4925
+ const fromAxis = axisPairFromPixel(
4926
+ chart,
4927
+ finder.xAxisIndex,
4928
+ finder.yAxisIndex,
4929
+ offsetX,
4930
+ offsetY
4931
+ );
4932
+ if (fromAxis) return fromAxis;
4933
+ return this._pixelToTrend(chart, target, offsetX, offsetY, true);
4934
+ }
4710
4935
  /** emit=false 只结束拖拽不抛事件(右键菜单用)。8.21 */
4711
4936
  _endDrag(emit) {
4712
4937
  const session = this.dragSession;
4713
4938
  if (!session) return;
4714
- window.removeEventListener("mousemove", session.onMove);
4715
- window.removeEventListener("mouseup", session.onUp);
4716
- this.dragSession = null;
4939
+ this._unbindDragWindow();
4940
+ if (!this._alive()) return;
4717
4941
  this._panLockedByHover = false;
4718
4942
  if (this.host && typeof this.host.setInsideDataZoomPanEnabled === "function") {
4719
4943
  this.host.setInsideDataZoomPanEnabled(true);
4720
4944
  }
4721
4945
  this._ignoreClickUntil = Date.now() + 300;
4722
4946
  this.updateCursor(session.target.laneId);
4947
+ this.renderLane(session.target.laneId, true);
4948
+ this._syncCachedOption(session.target.laneId);
4723
4949
  if (emit) this._emitChange(session.target, { redraw: false });
4724
4950
  }
4725
4951
  _clearClickTimer() {
@@ -4731,6 +4957,7 @@ class TrendLineManager {
4731
4957
  /** 抛 trend-change(拖拽过程中不抛,只在松开时抛);不写回响应式模板。8.22 */
4732
4958
  // redraw=false:本地已画过,避免再 scheduleRender 把线闪掉。8.24
4733
4959
  _emitChange(target, { redraw = true } = {}) {
4960
+ if (!this._alive() || !target) return;
4734
4961
  const segments = cloneSegments(this.store[target.key] && this.store[target.key].segments || []);
4735
4962
  this.host.$emit("trend-change", {
4736
4963
  paramId: target.paramId,
@@ -4742,8 +4969,12 @@ class TrendLineManager {
4742
4969
  /** 等 Vue / 图表 setOption 完成后再画叠加层。8.22 */
4743
4970
  // 只 nextTick 一次,不再叠 rAF 连刷。8.24
4744
4971
  _scheduleRenderAfterVue() {
4745
- if (this.host && typeof this.host.$nextTick === "function") {
4746
- this.host.$nextTick(() => this.scheduleRender());
4972
+ if (!this._alive()) return;
4973
+ if (typeof this.host.$nextTick === "function") {
4974
+ this.host.$nextTick(() => {
4975
+ if (!this._alive()) return;
4976
+ this.scheduleRender();
4977
+ });
4747
4978
  } else {
4748
4979
  this.scheduleRender();
4749
4980
  }
@@ -4860,6 +5091,15 @@ class TrendLineManager {
4860
5091
  return pixelToData(chart, target.paramId, offsetX, offsetY, loose);
4861
5092
  }
4862
5093
  _trendToPixel(chart, target, value, depth) {
5094
+ const finder = findSeriesForDepth(chart, target.paramId, depth);
5095
+ const live = axisPairToPixel(
5096
+ chart,
5097
+ finder.xAxisIndex,
5098
+ finder.yAxisIndex,
5099
+ value,
5100
+ depth
5101
+ );
5102
+ if (live) return live;
4863
5103
  const lane = this._laneById(target.laneId);
4864
5104
  if (isLaneLogScale(lane)) {
4865
5105
  return logTrendDataToPixel(chart, target.paramId, value, depth);
@@ -5087,11 +5327,16 @@ const _sfc_main$6 = {
5087
5327
  type: Boolean,
5088
5328
  default: true
5089
5329
  },
5090
- // 当前选中的泳道ID(用于在头部显示红色边框)
5330
+ // 当前选中的泳道ID(最后一次点选,兼容旧 emit)
5091
5331
  activeLaneId: {
5092
5332
  type: [String, Number],
5093
5333
  default: null
5094
5334
  },
5335
+ // 地应力可同时选中多道。8.25
5336
+ activeLaneIds: {
5337
+ type: Array,
5338
+ default: () => []
5339
+ },
5095
5340
  // 是否为地应力模式(只有为 true 时才启用泳道选中功能)
5096
5341
  isGeomechanical: {
5097
5342
  type: Boolean,
@@ -5164,6 +5409,10 @@ const _sfc_main$6 = {
5164
5409
  clearTimeout(this.resizeTimer);
5165
5410
  this.resizeTimer = null;
5166
5411
  }
5412
+ if (this._curveConfigCloseTimer) {
5413
+ clearTimeout(this._curveConfigCloseTimer);
5414
+ this._curveConfigCloseTimer = 0;
5415
+ }
5167
5416
  if (this.resizeObserver) {
5168
5417
  this.resizeObserver.disconnect();
5169
5418
  this.resizeObserver = null;
@@ -5192,6 +5441,7 @@ const _sfc_main$6 = {
5192
5441
  return;
5193
5442
  }
5194
5443
  this.$nextTick(() => {
5444
+ if (this._isUnmounted) return;
5195
5445
  const containerRef = this.$refs.containerRef;
5196
5446
  if (!containerRef) {
5197
5447
  return;
@@ -5571,10 +5821,11 @@ const _sfc_main$6 = {
5571
5821
  isLaneTrendDraw(lane) {
5572
5822
  return !!(lane && (lane.lines || []).some((line) => isLineDrawable(line)));
5573
5823
  },
5574
- // 获取泳道头部样式:地应力选中道,或 isDraw 所在道,红色边框
5824
+ // 获取泳道头部样式:地应力已选中的道都画红框。8.25
5575
5825
  getLaneHeaderStyle(lane) {
5576
5826
  const base = this.headerStyle;
5577
- const isActive = this.isGeomechanical && this.activeLaneId != null && lane && lane.laneId === this.activeLaneId || this.isLaneTrendDraw(lane);
5827
+ const ids = this.activeLaneIds || [];
5828
+ const isActive = this.isGeomechanical ? !!(lane && ids.some((id) => id === lane.laneId)) : this.isLaneTrendDraw(lane);
5578
5829
  if (!isActive) return base;
5579
5830
  return {
5580
5831
  ...base,
@@ -5587,7 +5838,10 @@ const _sfc_main$6 = {
5587
5838
  },
5588
5839
  closeCurveConfigDialog() {
5589
5840
  this.curveConfigClosing = true;
5590
- setTimeout(() => {
5841
+ if (this._curveConfigCloseTimer) clearTimeout(this._curveConfigCloseTimer);
5842
+ this._curveConfigCloseTimer = setTimeout(() => {
5843
+ this._curveConfigCloseTimer = 0;
5844
+ if (this._isUnmounted) return;
5591
5845
  this.curveConfigVisible = false;
5592
5846
  this.curveConfigClosing = false;
5593
5847
  }, 300);
@@ -6595,7 +6849,7 @@ function _sfc_render$6(_ctx, _cache, $props, $setup, $data, $options) {
6595
6849
  }, 8, ["modelValue", "onClose"])
6596
6850
  ], 544);
6597
6851
  }
6598
- const KdLaneContainerComponent = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["render", _sfc_render$6], ["__scopeId", "data-v-9d96e270"]]);
6852
+ const KdLaneContainerComponent = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["render", _sfc_render$6], ["__scopeId", "data-v-814fbe51"]]);
6599
6853
  const SLIDER_GROUP_ID = "depthSliderGroup";
6600
6854
  function pad2(n) {
6601
6855
  return n < 10 ? "0" + n : "" + n;
@@ -7456,8 +7710,14 @@ const _sfc_main$5 = {
7456
7710
  ensureVisible() {
7457
7711
  const rows = this.resolvedRows;
7458
7712
  if (!rows.length) return;
7459
- const option = this.chart.getOption();
7460
- const dz = option.dataZoom && option.dataZoom[0] ? option.dataZoom[0] : null;
7713
+ const dz = (() => {
7714
+ try {
7715
+ const m = this.chart.getModel().getComponent("dataZoom", 0);
7716
+ return m && m.option;
7717
+ } catch (e) {
7718
+ return null;
7719
+ }
7720
+ })();
7461
7721
  if (!dz) return;
7462
7722
  const total = rows.length;
7463
7723
  const start = dz.start ?? 0;
@@ -7913,7 +8173,7 @@ function _sfc_render$5(_ctx, _cache, $props, $setup, $data, $options) {
7913
8173
  ], 512)
7914
8174
  ], 512);
7915
8175
  }
7916
- const DepthTimeChart = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["render", _sfc_render$5], ["__scopeId", "data-v-9991a24b"]]);
8176
+ const DepthTimeChart = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["render", _sfc_render$5], ["__scopeId", "data-v-300010d5"]]);
7917
8177
  const curveExportIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAA2CAYAAACMRWrdAAAH+ElEQVRo3u1ZTWxU1xX+zpuxsRvbWLEBx6mFIcEOTWkChYZ00abdgNRKDVJRi1pVTvHYRCy6qsKilbLMoot0YRV7jGRVipSGLpAapV1UqRsSKKi0iAK1cRxIY4gJGNvEP2PPvPN1ce/7sz1vxsbTRJWvNPZ77/68c+4595zvOw9Ya2ttrZWySb6Ow4ePbpaE2/aZC0hOpNPdv17uvGS+DsfR5rqqzMtfb/mYpAhA8yKxF7aREO8ZCTF7RX/fRBjqQ2SNxXNNn3c9M5/E21eabgBYPcUAoK5qFt95+jogQtDal4SRx5ee9o+AntD0nYGEGHXJhc5CowhsV9AvQpC4+2mlvH2laUWWjlWMANSTmOaBEZlUK7kg2OHoXEb6fQUFBAGSYq2DxR7gr8iVunCsYkYAaye1LwRACAhS4Pullc9orhRxHNBIbcaSIg5A164jECrtYuKpJyICKmnfvWK9irCY0riL3T2NKMjIOSMhAoE4pGpo9wEhCQ25oQZC0z++YubZGzyAXvGKCUAIoSoiDmi2WBjspBFbBFS1ziNKqj1DDDbIuiTsCkHgEIAKEQdUNxxMCFVKaVwRANUeL5ciDhjdxpBrGnPRWNQ7jcG9hiOm9QCliJhdspthn3kvkRKdseFPauWl158rKu8t6JcC+VIKrCkPmv/yKjY09O/3WlpaajNZ5zNGELoGo9ba/yUI/umLL7YmcjwWfpZOH38hfJ9Kdb4EyBOlFZF30+nun69a8Ejk0LChZrrtW9s/IAC8cX6HAHghmsBl/7e/9ME366umJQyOF4LbhdfhZx7OFCwG19Nz5XjrUusNAKunGABUV8xh1+ZbAoBvnN+x5Jhtm+5i64Z7DGN6WpgoQpICkbAOhox4ORsWUNODVhQLsYTjMxV461JrCSCVwWtLgtxQAhdVH9xa+BieY4BK1Eq0KnkYw6rovdWCZH2ASF8QeQRIYmnlPPRrhQ8PIumxnRC8ChRkwFYkglKi1i2RYkY+iQs/1JB5VMUHyZ4itKiSFB+EGQWFHswKzl/g1D7+XH3FyIUccJFFGcBHQwS4RJAgNFjHWMbCegkzJMv9PABaStoiZIHlrQ+64egHgGqCwOLzFXiwUKChMkLWdcR1HSQcMplwA0dfddpCD5XnN5kSomavSZp7n9AwoD9hET2eNj5Tiau3GjB0e4OM3q/B9FxZBkBGRJLliVzVww/NSKkshkIGM/Q44q5+RCTF5yXhNjb1EN659rhcGmmcUMrrFL5Z5jgXe9PHb3pjfnL0aN38ZNmXKdxdIleMj04MOcyCSlNkW7y+M8Nb0T/QMpF1E686jr7am+6eXGrd33Z1jQH4q/2VgGiSsfRIlUIb1gih2IDil99sMHA1wVMXn5IrNxv/lkzg+729gXVK0QoVc2iSL2Njh7oe7aWJjH6yDpz09xd2yuDtTX/KZTMHetN9mVKD4AIskiAIt0A+IUgloWpU8YpMqhAl8d77j8ngaEN/bn7uQF9fvFKp1JF97e3tXyypYgzXcWNs5inkxX5V+qWM0cn1OH2tZYKaaCuklF3vCXGSp9vajjSX1mLWEnGQiwz/N9DBuz89tA05dV4+caLrw2KFmqr6tDmxjmc7Oo4+UxqLGYELWiyIgLQuSAHIjydrZPCTR25ns3PdyxFqvO4eLu260JBz5vtTqSP7SmAxPwnHAw/rjuFzpgQGRhsJoK84F1yQ6+ru4p+7/l6RTWZPtbd3fm/VFVMvGsQgD4aCBmwgoUKG72wUunhzpe40UTuGc197tyLzhdlThq2vLh+DUiQuz6lf3wzm5Sgcm65xh4cHzoXHt3d2/oB0KmJeujec/GcqZ3D+6bPYfXHvK4dTnVUn0t2/XKUEHU64+YZ4Xy58CoL7MxXiqtzo7+/PRU2M4xOb79Sqox6nBD2GQJNe7tdO2gK6gFDMVszg7M53sPPqnl+0d3Q25+bnUoXcu2DtXjWe8all0I6lJ/ZzEefcpIhwaqk5/9l9HdlkDlQXqgpVQlXB0DXU5wAAFPPl8zj35BnsvvrMjzdNbKxva2uLzYlOIawY4ckxA1UhrkIIv5aQf45IiDlLLA71xxNQx8X57WfwUd3I/rKydX88ePBg1YrRvRYB7tX7lmvLMyBQWZYBIQ3xErOIwmDkAykq5yuxfmY9ALx78uTJqZVFRQJUQ8pixzBA+V6doyKZkcrkXMOhQx31+TVbSPVkiSHBs6rZajx7+Ruonqk+li4QRAqVBiK0JF9UND+/NEjAgOKGmnHJZBv2AXgtPGfHH3bmtxWBm00f4f1tgxEF6ybqsevynkwyW9aW7u3+3QNixUDwuDzm1eiMWwY5bWvdCAD8MPJCYXMy69SW5fkls4ljjkbFqr+3EXv+9exUWa78+d4ilCqGtsDVRfWKJdIBfEoqtqZIAFsevoV1Zdn9HR0dj/X09AwDQE9Pz2Q8uu+MRLpHbzVh++COUXGd76Z7f3Nh1ZBHAeARKXhSTTVKbZQsT2b5VOO1BCG/Wgny2Hr9cTw58JUbyCX39i5DqYIWG5+txl+GvhpLof8x0ioDt5sXebB39HPqAJDnU6kjP0qnj79W1JcSCrYNtqLpwy2XEw72p3u6ls228yqWzc4OilS2XbuzJe9nGQFeGZl4pK+YTzrqaK5YoRpHHoW4iT9PTowdiAvphd75uWqpVOfPIHguOz93aCWsYBkl7v9tU02eGh6+0rUIY661tbbWPlftv9DKUDsQUF/KAAAAAElFTkSuQmCC";
7918
8178
  const depthSlotIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAA2CAYAAACMRWrdAAAFnUlEQVRo3u1ZTWxUVRT+zptOp51pZZpOaWlGCgIFFIRgiCsNJi6MbFyyMRqgPygmJhrXNbJgQQQTSf9mGhaEuGkCK/9iMkhAoxAoJCIBTZEWO0Ch03ZsZzpzPxf3venr0JoZ7LzOkJ7Mz3v3vfvuO/ec7zvn3Assy7IUhUihB9h74MBGI8099jblkq/6OjtvFHLcskIrZii1KVFZ3zEW2AEFYc39S1KRiF4BUNqKAUCiciVGVr/BFAWVk0OoSEQLPqbhlM/TYYyVOYVkIUUc1M5w0lxOquaMYiJOEPBSWIyAOAszZzAGcdgRHWNFghDSQasZTllMtH7ylJEHtameOlZ0Ojo7mXmIw4oVhBX3t7YdFYofAKiwJlstUXKwpaX9LZNWpm/dvPFBJBJJlQDdy/SUf/07sYadUIRMe2ppUX501at8UPPi6wrCVdHzqI7/+cViK1UwV2Ta1eWJD6cnA1sQq9+J+Ip10PQhjK1oxr2VL3PUv1W8U8MJqPThksFYOHz8tjEzddI/9CMIzYRCiGYR7ZYN939CWTp5OhQKRUuKPETUIf/dCwKmrcxD/5NiqBSCIz8gnebhQo1fMMV6enr+cM2Mf+OPXhKQYo9hDdFzUp4cP93X1z1QknQvNA7X3vkeAkUb77Mheh5KyZGSjWO9vZ1ny6ceRKpHr4mFrZqH11AxHb0YDnedL+kALcCXtX+fy6QfwTtfCxQOlXzZcvPm72c2bMAtX+zWOkVIZXzoSijUfaYoFGtpbTsHSNCW+k2Gerq25tI3Eomkmps3H2q4892JtOEBRbryymJa2i6L6CwGABQ5FO7tfmWRLCbB4e0H18x4/JR0AmsvHZkguSXXl4vH4wOfHz02CvzDAx9/djHU3Zlz3/b299b98vwn1akyL8uTY9hx/ejiumKqws9UeY1IetqKsp5c+/p8Pqysq+t3u90TgUAA+fQFgOmKWqZclQAphcGYCJ+0Btm9+81T/68ykLwWF3JXjBRrVeZJJBgMThZ1PeZ0XWWbVAhyd8WcFdPJrCxFMZyZTiuhXlTFBBSQsiQWM6dTCkIeIoQImMfDF00tMb1FCmCxDNU6uuw564jMEwRGXhYDl8gTTQgULI7Neryrv7+/2SF0uSxXLGASLKQYmPI1es9cuH4qC9vZh/PAP7tBSBNFgPYIZo2HykYBXHknBnkGaJAuD/566SOCEEUCYpCk0HxJQodxmqGBBGjjaWUeZt2n+ypo7jWXEJR1TT9Y8KSZx7597zcZhlpjnacNNdbXbZbvIpxT4pMZIuFjFtEvlsGlxWqKIqAo61kwUzTqPU+xlnvMiZpdBNIK20fY29a2zaWM2axfGYPh8PHb8yomrvS7SV+yY6YqASPpQsVDbwTAawDgiQ1KmXsUFGtKYR0DJDQIRO+qMONSYk22FV0tyiZpntC0GoUwiDl9xTI4QIo7OT7LekqOxXwPdyVdM/AmfPAmfR0APl3QFR+tH0V02136Rqrw3Lcbreaf6387OYgiEAWMWMfXmgZw75koNw+9gE3DW3LAmJmWiWBXS2u7uVGCohAB0NLavkd7DCkLJEMLkkd81QQH3r4IpikUkiSgIAqk/sz9AgCVdiwqitlGRQWLAax2ivZdKgqhn23vk7kP5lWTPZTKjDPnes4BWgzR9xMCYxa0NFGdsaoZXAQi9nOdfmnwiBV/aO7+yQIxQfuITtnEgl0mOTD/ZJ5uMm/RUTY/sxOVj7xo/PVZ2MBsiz8UW2zK8JW1vWe7D8w6N3/tHKe3BZkV4Gy5r/VS2VH6atNlxLxjecQxQlwJF6pGqq4AxocoSlHH3Kny7cT8rviYYr6RKtSjEe7JchAyFurtPFuMau1vaR9bfb+JgYk6BMbr/lsxoUR80eoOX7TaKhcGUaRCkROrH6yN2N8dy7IsxSf/Avx35qhCwuXeAAAAAElFTkSuQmCC";
7919
8179
  const refreshIcon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAA2CAYAAACMRWrdAAAJgklEQVRo3u1aaVRURxbOj/yYHzknc04E3JClwY1RxyW4EBmXMC5xdIwh4MkEkO5+rxskioqAMJIIykQEFdmCERoaxA0XUIlBwCUmIWJwy5hojGbIDBkzkahRQObcO/dW92PR0BBFWh3eOd+pevXqVd3v3Vu37q3uZ57puXqu/79LluWpTyUxraRDrVaOnTRp0rNPHbHEmNeJnK4iIEDn+FQR++8nA6Eo/Y8YpJdqJUl65akh1vTxQLhLOF/ojuFL/Nk0EwICAn7zxJBgU9PI8hwSPFyS5PVkftlMrOEjV2w84QqM6+VDYeOqeWyanzzWpqnR60cLEpLuypK3FuCm2Hm4I3k6lGROwSMGTzya4wn1x12xgUg1fORCcBXg50F6bR0RnPd4mZhWN01DDiFyqR/uTfOCb4pHEAEXuHPMBVuXltq+2jsKwkPZNHWbrG6abD6kndKoZW/iCeMEvHXUFW4fdUESFBQo91w2455non5MBXXlgyBuxXzUSPJJb2/v56ylpTfIs93anzEFbla44s9HVdAGR1R4i6Dc325+5oI//0L7jQoXLEzxAlmWatgCrERKjo1c4geX9g0HEr4ZNysEmTb1m0fub1Pqyn3NQTd8N9oHSFMlOp2un3UcBDmH+BW+UPvBIBLOGW6Uq/BGuTNyyfckLPxUroIzO0ZDwYZpsPav3ric1k5IsEZA6W/q6wyV+e64OCSwSZJ0UVaLSDSSLiYucj5c+9CVCDnfh/+UukBx+mSMWOIn1gn3V1N8qFYHO9Bm/Dy7+5/KnAWul6kgP2k6kPZr1Gqdh9UcBUcJyxf749XiISSYE9S1xmFnrN4+EgQhra6Y+o5tb4Pmd68WDcHYSF+l7/NW9H4BvSkyr/28YCReP+wEP5Y6ibLOjMJNU5CfqyX9Kx1FHscNY8kk1fWkzaVW36fI/jdnvTsTfyx1ZFJgKh3hOtW3b3gZtZJ8gc2tMyEVaenSgnY02t051IhQiiK+L6E19KGjwA+HnJDLgxkvIUcZGo3GrlNrVKvbaVXTu8dhbN6VPBl/OOQI1z5wFCXjy91DQafT1pOgqicuGqe19Vs9CX+lyJVIOcC/SxyRyFHpgEkxczkyX/REphmSpPdfGz0PiQh8zzjogLWEqvwRwGulMzEd9+FIgj+CRtIHc93qsSDtRcb96ROI0ACobYXU2Fkdaos3WzLjKI0UVOcbloRzYgpwzso89KF6oBxSx8+sRpAdQ3XBUPzXgQGg4LtiBwgJUqMlh8ECU/pS4hu+ETwza8A9px7H5DTCKEMj/J4wOuMaeIdtRO7T7eS8g4KeC9Zr8LviAfjP/fbA4PpnxmEUVejOdRBLpvtGpqBH7m0Yl1sP7rkNMCanwUQsuxGGZzXi77Y0wKvhqfSBdJu713FI0uCI0L9ATTETasH+tPEcrBotbQ8LFoahZ+5N8Mi9g4JYTguxEURsWPZdcNtC5DJvgn9wGAYG6gd1GzGO31aFv441Rf2hpshe4B9UL0iaxBqLtxT5v7q6ECbm3QEPYz2MNzaYNGaox1HZDcimOIw05pZ1F4ZsaYLpbxeCpfEeQWqi/0NchDd+u68/COztjwQwrpvCgsRY2ISLZyZXCWITiFiLxhphpHmNCY2ZiY1PPC3SlXbH0/C5CZ2X/AIC9Q+gaQ573g7zgat7+sFVInR1bz+4sqc/bkvypIhcn2AhAS2dkXIGJhrvIBMbf88aazZFM7GxSed566iwlCatj/kTFqWMg30p40TJWLbIDx8oNFOr1S788hUi9s3ufkoJB9Je5FRjjwVi2bMTykymSGusmZihAUe10VgjDtlyFybFHwNLDkSjlXeWpI8Bnr+1LGGL38QHinp4HyJH0HRxl70gdLmwH17e3RdOGgcCbwPtCiLLPm+EJaCyxsYZW2usAUfcY4qvkdu3dDpFAXbNSeMgIYMix8Wd9sCyPXBiym792BY3uLSrL35NuFzYFxhL3/Jr15PxZBztz9xQiR7KGrvPFBsFMc+1n/J6PdfeXsZriK3ma9P8IEqa/zjJ1NGW0+ExwNZ1E5lYK/SBzNUvixPc9he8frRaXlg/bWMVmyK2dveKKU5cV4Xch/tacGAJ76+Zipd29kWeV5EhL4HXuW7TQ2XN0ct8SfV94OKOPvgVgeuVBhe27zoOki1tF2yyPrRReyV9hh4ZNTgh7VucvK4SX1ueLPIyS6Q4udXTIerJXGdQ5lWwcpkPPtRJljneq63MceHBBb7c3gcZqavEeUV6RwEwuWs1bwFMhE1Uy3Vq6yiU4rEz4rya51VwImsgm2HtQ4divGetj5mJF7b1JkK9QcHpPHsMDfFH3mcexfkKr+Oz+f2b57tgRlqsF6opCOiSnIzN7uMsFf69oDcTBC4JUJE5mJPNW115ykTnih46WVt/+D03mseO5zLPZ0faUiE/62zG3hmtLV0T+Wc8v9UOv6AJvthK4JImPbBpODK5rji9ZU1RYnvrQMrwtnOZ54uLmItdeggkXLhWrt6e6A7n823hXL4dnsuzw/P5dkACwKG0oSazJC/6ILbP5/TsZUND/JpKUt1QjE/gsRVsW+dODkeu7vI0h/ct1kxp+mA4l2cLZ4nY2TxbFkDUP80eAGujZvEXreUk1JLHbG3mWlkO53cSo2dCZbY9jWfbPD6XfH8obQiwCT6y8xU2t0UL/ZuOZtIRdq4tnjGyALZAJZMU9bIMV0iMnmFaCxQDcuQuPCP9CMigs0d/zgD4WZBe08R9y9JdxVhiHGPb8Y68p8JFwf5Nj/yHCq1WO4/JlaQMhtMkxOlcG6jOtcHTRNQEvrfFKkMfKE4eBu/HTcSk6BkQHzFbgIlsprZ9G4fhKUNv0b/5XTGeLVab7w+lDgImFRio7Z4fBPnrsUby176In+fYkCA2IMocKg02yGjT1qqPeEZtAq36KPdKncfuKqf0az2YihdzfOQsPJLhwAKzkAKnBLleojyV3Ytgum+BDbQ8Nz8zv1uR4YRx4XOEo+jWzPr+ozU5nLWXHDMVK9IHkHn1gqqsXlglCJHwDIOp5DbRbjC1NdfpWXmaI25Y6UW/AWjFCdZj8ScXjus4YKWvXPfOsrmQ+zd3KEt3gJNMMEsQEPUWvCDaSlOd0BA/Ft4JmwscBFBeFjNfkno9jifHHBvOUZv++nBlYdACjAz1wTURszAhajomrJiOq8NnY9QSbwzWB4pzf+7L7zxRf0PyCw5+gdN3TiT5VJnBLp/bOrPP9Vw9V8/Vc3X79T8J1TZaWjb6HwAAAABJRU5ErkJggg==";
@@ -9173,27 +9433,106 @@ const _sfc_main$3 = {
9173
9433
  _saveDataZoomState() {
9174
9434
  if (!this.chart || this.chart.isDisposed()) return null;
9175
9435
  try {
9176
- const option = this.chart.getOption();
9177
- if (option && option.dataZoom && option.dataZoom.length > 0) {
9178
- return option.dataZoom.map((dz) => ({
9179
- start: dz.start,
9180
- end: dz.end,
9181
- startValue: dz.startValue,
9182
- endValue: dz.endValue
9183
- }));
9184
- }
9436
+ const model = this.chart.getModel();
9437
+ const list = model.queryComponents({ mainType: "dataZoom" }) || [];
9438
+ if (!list.length) return null;
9439
+ return list.map((m) => {
9440
+ const opt = m.option || {};
9441
+ return {
9442
+ start: opt.start,
9443
+ end: opt.end,
9444
+ startValue: opt.startValue,
9445
+ endValue: opt.endValue
9446
+ };
9447
+ });
9185
9448
  } catch (e) {
9449
+ return null;
9186
9450
  }
9187
- return null;
9188
9451
  },
9189
9452
  // notMerge 刷 option 时把当前趋势线 graphic 带回去,避免先消失再画。8.24
9190
9453
  _preserveGraphic(options) {
9191
9454
  if (!options || options.graphic || !this.chart || this.chart.isDisposed()) return;
9192
9455
  try {
9193
- const current = this.chart.getOption();
9194
- if (current && current.graphic && current.graphic.length) {
9195
- options.graphic = current.graphic;
9456
+ const model = this.chart.getModel();
9457
+ const list = model.queryComponents({ mainType: "graphic" }) || [];
9458
+ if (!list.length) return;
9459
+ const graphic = list.map((m) => m.option).filter((item) => {
9460
+ if (!item || !(item.id || item.elements || item.type)) return false;
9461
+ const id = String(item.id || "");
9462
+ return id.indexOf("trend_") !== 0;
9463
+ });
9464
+ if (graphic.length) options.graphic = graphic;
9465
+ } catch (e) {
9466
+ }
9467
+ },
9468
+ // notMerge 时把笛卡尔趋势 series 带回去,避免先消失再画。8.25
9469
+ // 只拷绘制字段,禁止展开 model.option(会带上坐标系等内部引用,图表拆不掉)。8.25
9470
+ _copyTrendSeriesOption(opt, id) {
9471
+ if (!opt) return null;
9472
+ const ls = opt.lineStyle || {};
9473
+ const is = opt.itemStyle || {};
9474
+ return {
9475
+ id: opt.id || id,
9476
+ name: "trend",
9477
+ type: "line",
9478
+ data: Array.isArray(opt.data) ? opt.data.slice() : [],
9479
+ xAxisIndex: opt.xAxisIndex,
9480
+ yAxisIndex: opt.yAxisIndex,
9481
+ clip: opt.clip,
9482
+ animation: false,
9483
+ silent: true,
9484
+ large: false,
9485
+ z: opt.z,
9486
+ zlevel: 0,
9487
+ symbol: opt.symbol,
9488
+ symbolSize: opt.symbolSize,
9489
+ showSymbol: opt.showSymbol,
9490
+ hoverAnimation: false,
9491
+ legendHoverLink: false,
9492
+ connectNulls: false,
9493
+ tooltip: { show: false },
9494
+ emphasis: { disabled: true },
9495
+ lineStyle: {
9496
+ color: ls.color,
9497
+ width: ls.width,
9498
+ type: ls.type
9499
+ },
9500
+ itemStyle: {
9501
+ color: is.color,
9502
+ borderColor: is.borderColor,
9503
+ borderWidth: is.borderWidth
9504
+ }
9505
+ };
9506
+ },
9507
+ _preserveTrendSeries(options) {
9508
+ if (!options || !this.chart || this.chart.isDisposed()) return;
9509
+ if (options.__kdTrendSynced) return;
9510
+ const incoming = Array.isArray(options.series) ? options.series : [];
9511
+ const hasTrend = incoming.some((s) => {
9512
+ if (!s) return false;
9513
+ const name = Array.isArray(s.name) ? s.name[0] : s.name;
9514
+ if (name === "trend") return true;
9515
+ const id = String(s.id || "");
9516
+ return id.indexOf("trend_") === 0;
9517
+ });
9518
+ if (hasTrend) return;
9519
+ try {
9520
+ const list = this.chart.getModel().getSeries() || [];
9521
+ const trends = [];
9522
+ const seen = /* @__PURE__ */ new Set();
9523
+ for (let i = 0; i < list.length; i++) {
9524
+ const sm = list[i];
9525
+ const opt = sm && sm.option || {};
9526
+ const name = Array.isArray(sm.name) ? sm.name[0] : sm.name;
9527
+ const id = String(opt.id || sm.id || "");
9528
+ if (name !== "trend" && id.indexOf("trend_") !== 0) continue;
9529
+ if (!id || seen.has(id)) continue;
9530
+ seen.add(id);
9531
+ const copied = this._copyTrendSeriesOption(opt, id);
9532
+ if (copied) trends.push(copied);
9196
9533
  }
9534
+ if (!trends.length) return;
9535
+ options.series = incoming.concat(trends);
9197
9536
  } catch (e) {
9198
9537
  }
9199
9538
  },
@@ -9246,6 +9585,7 @@ const _sfc_main$3 = {
9246
9585
  const next = this.getOptionsWithTheme(options, skipClone);
9247
9586
  this._applySavedDataZoomToOptions(next, savedDataZoom);
9248
9587
  this._preserveGraphic(next);
9588
+ this._preserveTrendSeries(next);
9249
9589
  this.delegateMethod("setOption", next, {
9250
9590
  notMerge: !!notMerge,
9251
9591
  lazyUpdate: !!lazyUpdate
@@ -9412,6 +9752,7 @@ const _sfc_main$3 = {
9412
9752
  const next = this.getOptionsWithTheme(val);
9413
9753
  this._applySavedDataZoomToOptions(next, savedDataZoom);
9414
9754
  this._preserveGraphic(next);
9755
+ this._preserveTrendSeries(next);
9415
9756
  this.chart.setOption(next, {
9416
9757
  notMerge: this.skipClone || val !== oldVal
9417
9758
  });
@@ -9507,17 +9848,35 @@ const _hoisted_1$3 = { class: "echarts" };
9507
9848
  function _sfc_render$3(_ctx, _cache, $props, $setup, $data, $options) {
9508
9849
  return openBlock(), createElementBlock("div", _hoisted_1$3);
9509
9850
  }
9510
- const resizeEcharts = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["render", _sfc_render$3], ["__scopeId", "data-v-8c9f410f"]]);
9851
+ const resizeEcharts = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["render", _sfc_render$3], ["__scopeId", "data-v-5b44c8a6"]]);
9511
9852
  const DRAW_POINT_MIN = 600;
9512
9853
  const DRAW_POINT_MAX = 1800;
9513
9854
  function getSeriesDrawThreshold(laneHeightPx) {
9514
9855
  const h = Number(laneHeightPx) || 400;
9515
9856
  return Math.max(DRAW_POINT_MIN, Math.min(DRAW_POINT_MAX, Math.round(h * 2)));
9516
9857
  }
9858
+ function isMissingCurveValue(raw) {
9859
+ if (raw === null || raw === void 0 || raw === "" || raw === "null") {
9860
+ return true;
9861
+ }
9862
+ const n = Number(raw);
9863
+ return !Number.isFinite(n);
9864
+ }
9865
+ function pointX(pt) {
9866
+ if (Array.isArray(pt)) return pt[0];
9867
+ if (pt && Array.isArray(pt.value)) return pt.value[0];
9868
+ return null;
9869
+ }
9870
+ function pointYRaw(pt) {
9871
+ if (Array.isArray(pt)) return pt[1];
9872
+ if (pt && Array.isArray(pt.value)) return pt.value[1];
9873
+ return null;
9874
+ }
9517
9875
  function getPointXY(pt) {
9518
- if (Array.isArray(pt)) return [Number(pt[0]), Number(pt[1])];
9519
- if (pt && Array.isArray(pt.value)) return [Number(pt.value[0]), Number(pt.value[1])];
9520
- return [NaN, NaN];
9876
+ const x = pointX(pt);
9877
+ const y = pointYRaw(pt);
9878
+ if (isMissingCurveValue(x)) return [NaN, Number(y)];
9879
+ return [Number(x), Number(y)];
9521
9880
  }
9522
9881
  function lttb(data, threshold) {
9523
9882
  const n = data && data.length || 0;
@@ -9550,8 +9909,37 @@ function lttb(data, threshold) {
9550
9909
  return sampled;
9551
9910
  }
9552
9911
  function downsampleSeriesForDraw(data, threshold) {
9553
- if (!data || data.length <= threshold) return data || [];
9554
- return lttb(data, threshold);
9912
+ if (!data || !data.length) return data || [];
9913
+ if (data.length <= threshold) return data;
9914
+ const segments = [];
9915
+ let cur = [];
9916
+ for (let i = 0; i < data.length; i++) {
9917
+ if (isMissingCurveValue(pointX(data[i]))) {
9918
+ if (cur.length) {
9919
+ segments.push(cur);
9920
+ cur = [];
9921
+ }
9922
+ } else {
9923
+ cur.push(data[i]);
9924
+ }
9925
+ }
9926
+ if (cur.length) segments.push(cur);
9927
+ if (segments.length === 1 && segments[0].length === data.length) {
9928
+ return lttb(data, threshold);
9929
+ }
9930
+ if (!segments.length) return data;
9931
+ const totalValid = segments.reduce((n, s) => n + s.length, 0);
9932
+ const out = [];
9933
+ for (let s = 0; s < segments.length; s++) {
9934
+ const seg = segments[s];
9935
+ const share = Math.max(2, Math.round(seg.length / totalValid * threshold));
9936
+ const sampled = seg.length <= share ? seg : lttb(seg, Math.min(share, seg.length));
9937
+ if (out.length) {
9938
+ out.push([null, pointYRaw(sampled[0])]);
9939
+ }
9940
+ for (let i = 0; i < sampled.length; i++) out.push(sampled[i]);
9941
+ }
9942
+ return out;
9555
9943
  }
9556
9944
  function lowerBound(rows, key, target) {
9557
9945
  let lo = 0;
@@ -9841,6 +10229,7 @@ const _sfc_main$2 = {
9841
10229
  selectedParamId: null,
9842
10230
  // 当前选中的泳道ID(只有该泳道显示缺失点、区块、区块点)
9843
10231
  activeLaneId: null,
10232
+ activeLaneIds: [],
9844
10233
  currentMarkAreaIndex: -1,
9845
10234
  // 每个泳道的选中点对数组,每对包含两个点形成区块,与 markArea 通过 markAreaId 关联
9846
10235
  laneSelectedPointPairs: {},
@@ -9868,16 +10257,36 @@ const _sfc_main$2 = {
9868
10257
  this._pointerMetaGen = 0;
9869
10258
  this._lastTipKey = /* @__PURE__ */ new WeakMap();
9870
10259
  this._lastAxisPx = /* @__PURE__ */ new WeakMap();
10260
+ this._laneHasValidPoint = /* @__PURE__ */ Object.create(null);
9871
10261
  this._pendingPointer = null;
9872
10262
  this._pointerRaf = 0;
9873
10263
  this._lastHeaderDataIndex = -1;
10264
+ this._tooltipLaneId = null;
10265
+ this._lastTooltipAxisLabel = "";
10266
+ this._isPanning = false;
10267
+ this._isZooming = false;
10268
+ this._zoomIdleTimer = 0;
10269
+ this._geoClickTimer = 0;
10270
+ this._laneZrHandlers = /* @__PURE__ */ Object.create(null);
10271
+ this._connectDetached = false;
10272
+ this._savedConnectGroups = null;
10273
+ this._tipHtmlBucket = "";
10274
+ this._tipHtmlMap = /* @__PURE__ */ Object.create(null);
10275
+ this._onWindowMouseUp = () => this.endChartPan();
9874
10276
  this._flushPointerSync = () => {
9875
10277
  this._pointerRaf = 0;
9876
10278
  const pending = this._pendingPointer;
9877
10279
  if (!pending || !this.pointerInChartArea) return;
10280
+ const { chart, x, y, clientY } = pending;
10281
+ if (!chart || chart.isDisposed()) {
10282
+ this._pendingPointer = null;
10283
+ return;
10284
+ }
10285
+ if (this.isChartBusy()) {
10286
+ this.updateCrosshairLine(chart, y);
10287
+ return;
10288
+ }
9878
10289
  this._pendingPointer = null;
9879
- const { chart, x, y } = pending;
9880
- if (!chart || chart.isDisposed()) return;
9881
10290
  this.syncAxisPointerAcrossCharts(chart, x, y);
9882
10291
  };
9883
10292
  this.debounceJudgeEdge = debounce(() => {
@@ -9895,13 +10304,30 @@ const _sfc_main$2 = {
9895
10304
  ) && this.currentToolBarConfig.showDateTime == true;
9896
10305
  window.addEventListener("keydown", this.handleKeyDown);
9897
10306
  window.addEventListener("keyup", this.handleKeyUp);
10307
+ window.addEventListener("mouseup", this._onWindowMouseUp);
9898
10308
  this.showTooltip = this.formCache.displayType == "float";
9899
10309
  },
9900
10310
  beforeUnmount() {
9901
10311
  this._isUnmounted = true;
9902
10312
  window.removeEventListener("keydown", this.handleKeyDown);
9903
10313
  window.removeEventListener("keyup", this.handleKeyUp);
9904
- if (this.trendLineManager) this.trendLineManager.destroy();
10314
+ window.removeEventListener("mouseup", this._onWindowMouseUp);
10315
+ if (this._zoomIdleTimer) {
10316
+ clearTimeout(this._zoomIdleTimer);
10317
+ this._zoomIdleTimer = 0;
10318
+ }
10319
+ if (this._geoClickTimer) {
10320
+ clearTimeout(this._geoClickTimer);
10321
+ this._geoClickTimer = 0;
10322
+ }
10323
+ Object.keys(this._laneZrHandlers || {}).forEach((laneId) => {
10324
+ this._unbindLaneZrEvents(laneId);
10325
+ });
10326
+ this._laneZrHandlers = /* @__PURE__ */ Object.create(null);
10327
+ if (this.trendLineManager) {
10328
+ this.trendLineManager.destroy();
10329
+ this.trendLineManager = null;
10330
+ }
9905
10331
  if (this._pointerRaf) {
9906
10332
  cancelAnimationFrame(this._pointerRaf);
9907
10333
  this._pointerRaf = 0;
@@ -9919,8 +10345,10 @@ const _sfc_main$2 = {
9919
10345
  this.scheduleConnectAllCharts.cancel();
9920
10346
  }
9921
10347
  this._pendingPointer = null;
10348
+ this.attachChartConnect();
9922
10349
  this.unbindChartPointerSync();
9923
10350
  echarts.disConnect(this.group);
10351
+ this.chartRefMap = /* @__PURE__ */ Object.create(null);
9924
10352
  this.clearEcharts();
9925
10353
  if (this.resizeObserver) {
9926
10354
  this.resizeObserver.disconnect();
@@ -9940,6 +10368,7 @@ const _sfc_main$2 = {
9940
10368
  async laneWidthChange(widthMap) {
9941
10369
  this.laneWidthMap = widthMap || {};
9942
10370
  await this.$nextTick();
10371
+ if (this._isUnmounted) return;
9943
10372
  this.updateLanesChartOption();
9944
10373
  },
9945
10374
  // 曲线上方对数刻度:边控关则只藏尺。8.22对数轴
@@ -9953,7 +10382,7 @@ const _sfc_main$2 = {
9953
10382
  chartLaneKey(lane) {
9954
10383
  return `${lane && lane.laneId}-${isLaneLogScale(lane) ? "log" : "lin"}`;
9955
10384
  },
9956
- // 整道 10 条竖线,样式跟普通泳道 splitLine 同一套。8.22对数轴
10385
+ // 对数竖格:整道 10 条同一虚线,和线性纸格一致。8.25
9957
10386
  getLogPaperGridSeries(lane, xAxisIndex, yAxisIndex) {
9958
10387
  const range = this.getLaneLogPlotRange(lane);
9959
10388
  const color = getCssVariable("--kd-lane-container-item-line-color");
@@ -9989,59 +10418,92 @@ const _sfc_main$2 = {
9989
10418
  },
9990
10419
  getLaneChartContentStyle(laneId) {
9991
10420
  const width = this.laneWidthMap && this.laneWidthMap[laneId];
9992
- const gap = this.currentHeaderStyle.itemGap || 0;
9993
10421
  const style = {};
9994
10422
  if (width && Number(width) > 0) {
9995
10423
  Object.assign(style, {
9996
10424
  width: `${width}px`,
9997
- padding: `0 ${gap}px`,
10425
+ padding: "0",
9998
10426
  height: "100%"
9999
10427
  });
10000
10428
  } else {
10001
10429
  Object.assign(style, {
10002
10430
  width: "100%",
10003
10431
  height: "100%",
10004
- padding: `0 ${gap}px`,
10432
+ padding: "0",
10005
10433
  flex: "1"
10006
10434
  });
10007
10435
  }
10008
- const isDrawLane = this.isLaneTrendDrawById(laneId);
10009
- if (this.isGeomechanical || isDrawLane) {
10010
- const isActive = this.activeLaneId === laneId || isDrawLane;
10436
+ if (this.isGeomechanical) {
10437
+ style.boxSizing = "border-box";
10438
+ style.border = this.isLaneSelected(laneId) ? "1px solid #F86C59" : "1px solid transparent";
10439
+ } else if (this.isLaneTrendDrawById(laneId)) {
10011
10440
  style.boxSizing = "border-box";
10012
- style.border = isActive ? "1px solid #F86C59" : "1px solid transparent";
10441
+ style.border = "1px solid #F86C59";
10013
10442
  }
10014
10443
  return style;
10015
10444
  },
10445
+ // 按引用摘掉 click/dblclick,避免 zr.off(类型) 误拆趋势线 dblclick。8.25
10446
+ _unbindLaneZrEvents(laneId) {
10447
+ const rec = this._laneZrHandlers && this._laneZrHandlers[laneId];
10448
+ if (!rec) return;
10449
+ try {
10450
+ const chart = rec.chart;
10451
+ if (chart && typeof chart.getZr === "function" && !chart.isDisposed()) {
10452
+ const zr = chart.getZr();
10453
+ if (zr) {
10454
+ if (rec.onContext) zr.off("contextmenu", rec.onContext);
10455
+ if (rec.onClick) zr.off("click", rec.onClick);
10456
+ if (rec.onDbl) zr.off("dblclick", rec.onDbl);
10457
+ }
10458
+ }
10459
+ } catch (e) {
10460
+ }
10461
+ rec.chart = null;
10462
+ rec.onContext = null;
10463
+ rec.onClick = null;
10464
+ rec.onDbl = null;
10465
+ delete this._laneZrHandlers[laneId];
10466
+ },
10467
+ _bindLaneZrEvents(laneId, chart) {
10468
+ this._unbindLaneZrEvents(laneId);
10469
+ if (!chart || chart.isDisposed()) return;
10470
+ let zr;
10471
+ try {
10472
+ zr = chart.getZr();
10473
+ } catch (e) {
10474
+ return;
10475
+ }
10476
+ if (!zr) return;
10477
+ const onContext = (e) => this.handleChartRightClick(e, laneId);
10478
+ const onClick = (e) => this.handleChartClick(e, laneId);
10479
+ const onDbl = (e) => this.handleChartDblClick(e, laneId);
10480
+ zr.on("contextmenu", onContext);
10481
+ zr.on("click", onClick);
10482
+ zr.on("dblclick", onDbl);
10483
+ this._laneZrHandlers[laneId] = { chart, onContext, onClick, onDbl };
10484
+ },
10016
10485
  setChartRef(laneId, el) {
10017
10486
  if (el) {
10018
- if (this.chartRefMap[laneId] && this.chartRefMap[laneId].chart) {
10019
- this.chartRefMap[laneId].chart.getZr().off("contextmenu");
10020
- this.chartRefMap[laneId].chart.getZr().off("click");
10487
+ const prev = this.chartRefMap[laneId];
10488
+ if (prev && prev.chart && prev.chart !== el.chart) {
10489
+ this._unbindLaneZrEvents(laneId);
10021
10490
  }
10022
10491
  this.chartRefMap[laneId] = el;
10023
- el.chart && el.chart.getZr().on("contextmenu", (e) => this.handleChartRightClick(e, laneId));
10024
- el.chart && el.chart.getZr().on("click", (e) => this.handleChartClick(e, laneId));
10492
+ if (el.chart) this._bindLaneZrEvents(laneId, el.chart);
10025
10493
  if (el.chart && this.trendLineManager) {
10026
10494
  this.trendLineManager.bindChart(laneId, el.chart);
10027
10495
  }
10028
10496
  } else if (this.chartRefMap[laneId]) {
10029
10497
  if (this.trendLineManager) this.trendLineManager.unbindChart(laneId);
10030
- this.chartRefMap[laneId].chart && this.chartRefMap[laneId].chart.getZr().off("contextmenu");
10031
- this.chartRefMap[laneId].chart && this.chartRefMap[laneId].chart.getZr().off("click");
10498
+ this._unbindLaneZrEvents(laneId);
10032
10499
  delete this.chartRefMap[laneId];
10033
10500
  }
10034
10501
  },
10035
10502
  handleChartInited(laneId, chart) {
10036
- if (this.chartRefMap[laneId]) {
10037
- this.chartRefMap[laneId].chart.getZr().off("contextmenu");
10038
- this.chartRefMap[laneId].chart.getZr().off("click");
10039
- }
10040
- chart.getZr().on("contextmenu", (e) => this.handleChartRightClick(e, laneId));
10041
- chart.getZr().on("click", (e) => this.handleChartClick(e, laneId));
10503
+ this._bindLaneZrEvents(laneId, chart);
10042
10504
  if (this.trendLineManager) {
10043
10505
  this.trendLineManager.bindChart(laneId, chart);
10044
- this.applyLineListData();
10506
+ if ((this.lineListData || []).length) this.applyLineListData();
10045
10507
  this.trendLineManager.scheduleRender();
10046
10508
  }
10047
10509
  this.scheduleConnectAllCharts();
@@ -10052,9 +10514,25 @@ const _sfc_main$2 = {
10052
10514
  if (!this.isGeomechanical) return;
10053
10515
  this.getConnectedCharts().forEach((chart) => {
10054
10516
  if (!chart || chart.isDisposed()) return;
10055
- chart.setOption({
10056
- dataZoom: [this.buildInsideDataZoom(!!enabled)]
10057
- });
10517
+ const laneId = this.findLaneIdByChart(chart);
10518
+ const next = this.buildInsideDataZoom(
10519
+ !!enabled,
10520
+ this.dataZoomFilterModeForLane(this.findLaneById(laneId))
10521
+ );
10522
+ try {
10523
+ const dz = chart.getModel().getComponent("dataZoom", 0);
10524
+ const opt = dz && dz.option;
10525
+ if (opt) {
10526
+ if (opt.start != null) next.start = opt.start;
10527
+ if (opt.end != null) next.end = opt.end;
10528
+ if (opt.startValue != null) next.startValue = opt.startValue;
10529
+ if (opt.endValue != null) next.endValue = opt.endValue;
10530
+ }
10531
+ const skip = this.trendLineManager && this.trendLineManager.dataZoomSeriesIndex(chart);
10532
+ if (skip && skip.length) next.seriesIndex = skip;
10533
+ } catch (e) {
10534
+ }
10535
+ chart.setOption({ dataZoom: [next] }, { silent: true, lazyUpdate: false });
10058
10536
  });
10059
10537
  },
10060
10538
  getConnectedCharts() {
@@ -10066,7 +10544,7 @@ const _sfc_main$2 = {
10066
10544
  // 趋势线控制点右键菜单(删除控制点) 8.21
10067
10545
  showTrendKnotContextMenu(e, payload) {
10068
10546
  this.currentContextMenuType = "trendKnot";
10069
- this.currentPointInfo = payload;
10547
+ this.currentPointInfo = markRaw(payload);
10070
10548
  this.currentContextMenuItems = [{ label: "删除控制点", action: "deleteTrendKnot" }];
10071
10549
  this.calculateContextMenuPosition(e);
10072
10550
  this.contextMenuVisible = true;
@@ -10099,6 +10577,85 @@ const _sfc_main$2 = {
10099
10577
  resetAllTrendLines() {
10100
10578
  if (this.trendLineManager) this.trendLineManager.resetAllTrendLines();
10101
10579
  },
10580
+ isLaneSelected(laneId) {
10581
+ return laneId != null && (this.activeLaneIds || []).some((id) => id === laneId);
10582
+ },
10583
+ addLaneSelection(laneId) {
10584
+ if (laneId == null) return false;
10585
+ this.activeLaneId = laneId;
10586
+ if (this.isLaneSelected(laneId)) return false;
10587
+ this.activeLaneIds = this.activeLaneIds.concat([laneId]);
10588
+ return true;
10589
+ },
10590
+ removeLaneSelection(laneId) {
10591
+ if (!this.isLaneSelected(laneId)) return;
10592
+ this.activeLaneIds = (this.activeLaneIds || []).filter((id) => id !== laneId);
10593
+ if (this.activeLaneId === laneId) {
10594
+ this.activeLaneId = this.activeLaneIds.length ? this.activeLaneIds[this.activeLaneIds.length - 1] : null;
10595
+ }
10596
+ },
10597
+ // 退出编辑:清掉全部选中红框,不挂新监听。8.25
10598
+ clearActiveLane() {
10599
+ this._clearGeoClickTimer();
10600
+ this._geoSelectClickAt = 0;
10601
+ this._geoSelectLaneId = null;
10602
+ this._lastBlankClickTime = null;
10603
+ this._lastBlankClickLaneId = null;
10604
+ const ids = (this.activeLaneIds || []).slice();
10605
+ if (this.selectedPoints.length > 0 && this.selectedLaneId) {
10606
+ if (this.laneSelectedPointPairs[this.selectedLaneId]) {
10607
+ this.laneSelectedPointPairs[this.selectedLaneId].pop();
10608
+ }
10609
+ this.selectedPoints = [];
10610
+ this.selectedLaneId = null;
10611
+ this.selectedParamId = null;
10612
+ }
10613
+ this.activeLaneIds = [];
10614
+ this.activeLaneId = null;
10615
+ if (!ids.length) return;
10616
+ this.updateLanesChartOption().then(() => {
10617
+ if (this._isUnmounted) return;
10618
+ ids.forEach((id) => this.showSelectedPointScatter(id));
10619
+ });
10620
+ },
10621
+ // 切趋势线:只保留当前曲线所在道;单击其它道仍可再选中。8.25
10622
+ selectLaneByParamId(paramId) {
10623
+ if (!this.isGeomechanical) return;
10624
+ if (paramId == null || paramId === "") {
10625
+ this.clearActiveLane();
10626
+ return;
10627
+ }
10628
+ const lanes = this.currentTemplate && this.currentTemplate.lanes || [];
10629
+ let laneId = null;
10630
+ for (let i = 0; i < lanes.length; i++) {
10631
+ const lane = lanes[i];
10632
+ if ((lane.lines || []).some((line) => String(line.paramId) === String(paramId))) {
10633
+ laneId = lane.laneId;
10634
+ break;
10635
+ }
10636
+ }
10637
+ if (!laneId) return;
10638
+ const prevIds = (this.activeLaneIds || []).slice();
10639
+ if (prevIds.length === 1 && prevIds[0] === laneId) {
10640
+ this.activeLaneId = laneId;
10641
+ return;
10642
+ }
10643
+ if (this.selectedPoints.length > 0 && this.selectedLaneId && this.selectedLaneId !== laneId) {
10644
+ if (this.laneSelectedPointPairs[this.selectedLaneId]) {
10645
+ this.laneSelectedPointPairs[this.selectedLaneId].pop();
10646
+ }
10647
+ this.selectedPoints = [];
10648
+ this.selectedLaneId = null;
10649
+ this.selectedParamId = null;
10650
+ }
10651
+ this.activeLaneIds = [laneId];
10652
+ this.activeLaneId = laneId;
10653
+ this.updateLanesChartOption().then(() => {
10654
+ if (this._isUnmounted) return;
10655
+ prevIds.forEach((id) => this.showSelectedPointScatter(id));
10656
+ this.showSelectedPointScatter(laneId);
10657
+ });
10658
+ },
10102
10659
  // 根据 paramId 判断曲线是否可见(isUsed === '1')
10103
10660
  isLineVisibleByParamId(paramId) {
10104
10661
  const lanes = this.currentTemplate && this.currentTemplate.lanes || [];
@@ -10130,6 +10687,35 @@ const _sfc_main$2 = {
10130
10687
  }
10131
10688
  return 1;
10132
10689
  },
10690
+ // 只读 option 引用,禁止 getOption() 深拷贝整图 series。8.25
10691
+ readChartOptionRefs(chart) {
10692
+ const empty = { grid: [], yAxis: [], xAxis: [], series: [] };
10693
+ if (!chart || chart.isDisposed()) return empty;
10694
+ try {
10695
+ const model = chart.getModel();
10696
+ const collect = (mainType) => {
10697
+ const list = model.queryComponents({ mainType }) || [];
10698
+ const out = [];
10699
+ for (let i = 0; i < list.length; i++) {
10700
+ out.push(list[i].option || {});
10701
+ }
10702
+ return out;
10703
+ };
10704
+ const seriesModels = model.getSeries() || [];
10705
+ const series = [];
10706
+ for (let i = 0; i < seriesModels.length; i++) {
10707
+ series.push(seriesModels[i].option || {});
10708
+ }
10709
+ return {
10710
+ grid: collect("grid"),
10711
+ yAxis: collect("yAxis"),
10712
+ xAxis: collect("xAxis"),
10713
+ series
10714
+ };
10715
+ } catch (e) {
10716
+ return empty;
10717
+ }
10718
+ },
10133
10719
  readGridBounds(chart, gridIndex, width) {
10134
10720
  try {
10135
10721
  const gridModel = chart.getModel().getComponent("grid", gridIndex);
@@ -10173,14 +10759,22 @@ const _sfc_main$2 = {
10173
10759
  bottom: chart.getHeight && chart.getHeight() || 0
10174
10760
  };
10175
10761
  },
10762
+ seriesDataHasValidX(data) {
10763
+ if (!data || !data.length) return false;
10764
+ for (let i = 0; i < data.length; i++) {
10765
+ if (this.isValidTooltipX(this.pointXY(data[i]).x)) return true;
10766
+ }
10767
+ return false;
10768
+ },
10176
10769
  collectTooltipSeries(chart, seriesByGrid) {
10770
+ let hasAnyValidPlotPoint = false;
10177
10771
  try {
10178
10772
  const seriesList = chart.getModel().getSeries();
10179
10773
  for (let s = 0; s < seriesList.length; s++) {
10180
10774
  const sm = seriesList[s];
10181
10775
  const option = sm.option || {};
10182
10776
  const name = this.unwrapEchartsValue(sm.name);
10183
- if (name === "helper" || name === "log-grid") continue;
10777
+ if (name === "helper" || name === "log-grid" || name === "trend") continue;
10184
10778
  const type = sm.subType || this.unwrapEchartsValue(option.type);
10185
10779
  if (type && type !== "line") continue;
10186
10780
  const tip = option.tooltip;
@@ -10196,9 +10790,13 @@ const _sfc_main$2 = {
10196
10790
  seriesIndex: sm.seriesIndex,
10197
10791
  data
10198
10792
  });
10793
+ if (!hasAnyValidPlotPoint) {
10794
+ hasAnyValidPlotPoint = this.seriesDataHasValidX(data);
10795
+ }
10199
10796
  }
10200
10797
  } catch (e) {
10201
10798
  }
10799
+ return hasAnyValidPlotPoint;
10202
10800
  },
10203
10801
  ensurePointerMeta(chart) {
10204
10802
  const width = chart.getWidth && chart.getWidth() || 0;
@@ -10214,14 +10812,18 @@ const _sfc_main$2 = {
10214
10812
  bounds[i] = this.readGridBounds(chart, i, width);
10215
10813
  seriesByGrid[i] = [];
10216
10814
  }
10217
- this.collectTooltipSeries(chart, seriesByGrid);
10815
+ const hasAnyValidPlotPoint = this.collectTooltipSeries(
10816
+ chart,
10817
+ seriesByGrid
10818
+ );
10218
10819
  meta = {
10219
10820
  gen: this._pointerMetaGen,
10220
10821
  width,
10221
10822
  height,
10222
10823
  gridCount,
10223
10824
  bounds,
10224
- seriesByGrid
10825
+ seriesByGrid,
10826
+ hasAnyValidPlotPoint
10225
10827
  };
10226
10828
  this._pointerMeta.set(chart, meta);
10227
10829
  return meta;
@@ -10288,18 +10890,32 @@ const _sfc_main$2 = {
10288
10890
  const item = Array.isArray(tip) ? tip[0] : tip;
10289
10891
  return !!(item && item.show === false);
10290
10892
  },
10291
- // 对数轴曲线 X 用 lg(x);轴是线性 lg 量程,允许负数。不改原始行。8.24
10893
+ // 对数轴曲线 X 用 lg(x);缺数保持 null,不能 Number(null) 成 0。8.25
10292
10894
  plotX(raw, isLogScale) {
10895
+ if (isMissingCurveValue(raw)) return null;
10293
10896
  return isLogScale ? toLogPlotX(raw) : raw;
10294
10897
  },
10295
10898
  plotSeries(data, isLogScale) {
10296
- return isLogScale ? mapSeriesDataXToLog(data) : data;
10899
+ if (!data || !data.length) return data || [];
10900
+ if (isLogScale) return mapSeriesDataXToLog(data);
10901
+ return data.map((item) => {
10902
+ if (Array.isArray(item)) {
10903
+ if (!isMissingCurveValue(item[0])) return item;
10904
+ return [null, item[1], item[2]];
10905
+ }
10906
+ if (item && Array.isArray(item.value)) {
10907
+ if (!isMissingCurveValue(item.value[0])) return item;
10908
+ return {
10909
+ ...item,
10910
+ value: [null, item.value[1]]
10911
+ };
10912
+ }
10913
+ return item;
10914
+ });
10297
10915
  },
10298
10916
  // null / '' 不能当有数:Number(null) === 0,会误打到空的第一条线。8.22
10299
10917
  isValidTooltipX(raw) {
10300
- if (raw === null || raw === void 0 || raw === "") return false;
10301
- const n = Number(raw);
10302
- return Number.isFinite(n);
10918
+ return !isMissingCurveValue(raw);
10303
10919
  },
10304
10920
  pointY(pt) {
10305
10921
  if (Array.isArray(pt)) return Number(pt[1]);
@@ -10319,6 +10935,7 @@ const _sfc_main$2 = {
10319
10935
  if (!series || !Array.isArray(series.data) || !series.data.length) return false;
10320
10936
  if (this.seriesDisplayName(series) === "helper") return false;
10321
10937
  if (this.seriesDisplayName(series) === "log-grid") return false;
10938
+ if (this.seriesDisplayName(series) === "trend") return false;
10322
10939
  const type = this.seriesTypeName(series);
10323
10940
  if (type && type !== "line") return false;
10324
10941
  if (this.seriesTooltipHidden(series)) return false;
@@ -10380,6 +10997,73 @@ const _sfc_main$2 = {
10380
10997
  }
10381
10998
  return best;
10382
10999
  },
11000
+ findLaneIdByChart(chart) {
11001
+ const map = this.chartRefMap || {};
11002
+ for (const laneId in map) {
11003
+ const ref = map[laneId];
11004
+ if (ref && ref.chart === chart) return laneId;
11005
+ }
11006
+ return null;
11007
+ },
11008
+ findLaneById(laneId) {
11009
+ if (laneId == null) return null;
11010
+ const lanes = this.currentTemplate && this.currentTemplate.lanes || [];
11011
+ for (let i = 0; i < lanes.length; i++) {
11012
+ if (lanes[i] && lanes[i].laneId == laneId) return lanes[i];
11013
+ }
11014
+ return null;
11015
+ },
11016
+ isMissingTooltipValue(raw) {
11017
+ if (raw === null || raw === void 0 || raw === "" || raw === "null") {
11018
+ return true;
11019
+ }
11020
+ const n = Number(raw);
11021
+ return !Number.isFinite(n);
11022
+ },
11023
+ // 泳道内任一曲线在任意井深有过有效点,才允许出浮窗。8.25
11024
+ laneHasAnyValidPlotPoint(chart, laneId) {
11025
+ if (laneId != null && this._laneHasValidPoint && this._laneHasValidPoint[laneId]) {
11026
+ return true;
11027
+ }
11028
+ const meta = this.ensurePointerMeta(chart);
11029
+ if (meta && typeof meta.hasAnyValidPlotPoint === "boolean") {
11030
+ return meta.hasAnyValidPlotPoint;
11031
+ }
11032
+ const grids = meta && meta.seriesByGrid || [];
11033
+ for (let g = 0; g < grids.length; g++) {
11034
+ const list = grids[g] || [];
11035
+ for (let s = 0; s < list.length; s++) {
11036
+ if (this.seriesDataHasValidX(list[s] && list[s].data)) return true;
11037
+ }
11038
+ }
11039
+ return false;
11040
+ },
11041
+ findHelperTooltipAnchor(chart, yValue, gridIndex) {
11042
+ try {
11043
+ const seriesList = chart.getModel().getSeries();
11044
+ const gi = this.clampGridIndex(
11045
+ this.ensurePointerMeta(chart).gridCount,
11046
+ gridIndex
11047
+ );
11048
+ for (let s = 0; s < seriesList.length; s++) {
11049
+ const sm = seriesList[s];
11050
+ const name = this.unwrapEchartsValue(sm.name);
11051
+ if (name !== "helper") continue;
11052
+ const option = sm.option || {};
11053
+ let yAxisIndex = this.unwrapEchartsValue(option.yAxisIndex);
11054
+ yAxisIndex = yAxisIndex == null || yAxisIndex === "" ? 0 : Number(yAxisIndex);
11055
+ if (!Number.isInteger(yAxisIndex) || yAxisIndex < 0) yAxisIndex = 0;
11056
+ if (yAxisIndex !== gi) continue;
11057
+ const data = option.data;
11058
+ if (!data || !data.length) continue;
11059
+ let dataIndex = this.findNearestDataIndexByY(data, yValue);
11060
+ if (dataIndex < 0) dataIndex = 0;
11061
+ return { seriesIndex: sm.seriesIndex, dataIndex };
11062
+ }
11063
+ } catch (e) {
11064
+ }
11065
+ return null;
11066
+ },
10383
11067
  onChartAreaMouseMove(event) {
10384
11068
  const charts = this.getConnectedCharts();
10385
11069
  for (let i = 0; i < charts.length; i++) {
@@ -10406,8 +11090,8 @@ const _sfc_main$2 = {
10406
11090
  el.style.transform = `translate3d(0, ${top}px, 0)`;
10407
11091
  if (!this.crosshairVisible) this.crosshairVisible = true;
10408
11092
  },
10409
- schedulePointerSync(chart, x, y) {
10410
- this._pendingPointer = { chart, x, y };
11093
+ schedulePointerSync(chart, x, y, clientY) {
11094
+ this._pendingPointer = { chart, x, y, clientY };
10411
11095
  if (this._pointerRaf) return;
10412
11096
  this._pointerRaf = requestAnimationFrame(this._flushPointerSync);
10413
11097
  },
@@ -10424,51 +11108,56 @@ const _sfc_main$2 = {
10424
11108
  this.showSyncedLaneTooltips(hit.yValue, offsetY, hit.gridIndex);
10425
11109
  },
10426
11110
  showSyncedLaneTooltips(yValue, fallbackY, gridIndex) {
10427
- const charts = this.getConnectedCharts();
11111
+ if (this.isChartBusy() || this._showingTips) return;
11112
+ this._showingTips = true;
10428
11113
  const needTip = this.showTooltip;
10429
11114
  const needHeader = this.formCache.displayType === "header";
10430
- for (let c = 0; c < charts.length; c++) {
10431
- const chart = charts[c];
10432
- if (!chart || chart.isDisposed()) continue;
10433
- const meta = this.ensurePointerMeta(chart);
10434
- const targetGridIndex = this.clampGridIndex(meta.gridCount, gridIndex);
10435
- const pos = this.getPixelFromYValue(chart, yValue, targetGridIndex);
10436
- const x = pos && Number.isFinite(pos[0]) ? pos[0] : (meta.width || 80) / 2;
10437
- const y = pos && Number.isFinite(pos[1]) ? pos[1] : fallbackY;
10438
- const axisKey = `${targetGridIndex}:${y | 0}`;
10439
- const group = chart.group;
10440
- chart.group = null;
10441
- if (this._lastAxisPx.get(chart) !== axisKey) {
10442
- this._lastAxisPx.set(chart, axisKey);
10443
- chart.dispatchAction({
10444
- type: "updateAxisPointer",
10445
- currTrigger: "mousemove",
10446
- x,
10447
- y,
10448
- axesInfo: [
10449
- {
10450
- axisDim: "y",
10451
- axisIndex: targetGridIndex,
10452
- value: yValue
10453
- }
10454
- ]
10455
- });
10456
- }
10457
- if (needTip || needHeader) {
10458
- const nearest = this.findNearestTooltipPoint(
10459
- chart,
10460
- yValue,
10461
- targetGridIndex
10462
- );
10463
- if (needHeader && nearest && nearest.rowData && nearest.dataIndex !== this._lastHeaderDataIndex) {
10464
- this._lastHeaderDataIndex = nearest.dataIndex;
10465
- this.headerData = nearest.rowData;
11115
+ const row = this.findRowByAxisValue(yValue);
11116
+ this._lastTooltipAxisLabel = this.formatAxisPointerYLabel(yValue, row);
11117
+ if (needHeader && row && row !== this.headerData) {
11118
+ this.headerData = markRaw(row);
11119
+ }
11120
+ const charts = this.getConnectedCharts();
11121
+ this.detachChartConnect();
11122
+ try {
11123
+ for (let c = 0; c < charts.length; c++) {
11124
+ const chart = charts[c];
11125
+ if (!chart || chart.isDisposed()) continue;
11126
+ const laneId = this.findLaneIdByChart(chart);
11127
+ this._tooltipLaneId = laneId;
11128
+ const lane = this.findLaneById(laneId);
11129
+ const meta = this.ensurePointerMeta(chart);
11130
+ const targetGridIndex = this.clampGridIndex(meta.gridCount, gridIndex);
11131
+ const pos = this.getPixelFromYValue(chart, yValue, targetGridIndex);
11132
+ const x = pos && Number.isFinite(pos[0]) ? pos[0] : (meta.width || 80) / 2;
11133
+ const y = pos && Number.isFinite(pos[1]) ? pos[1] : fallbackY;
11134
+ const axisKey = `${targetGridIndex}:${y | 0}`;
11135
+ const pinAxis = () => {
11136
+ chart.dispatchAction({
11137
+ type: "updateAxisPointer",
11138
+ currTrigger: "mousemove",
11139
+ x,
11140
+ y,
11141
+ axesInfo: [
11142
+ {
11143
+ axisDim: "y",
11144
+ axisIndex: targetGridIndex,
11145
+ value: yValue
11146
+ }
11147
+ ]
11148
+ });
11149
+ };
11150
+ if (this._lastAxisPx.get(chart) !== axisKey) {
11151
+ this._lastAxisPx.set(chart, axisKey);
11152
+ pinAxis();
10466
11153
  }
10467
- if (needTip) {
10468
- const tipKey = nearest ? `${nearest.seriesIndex}:${nearest.dataIndex}` : "";
11154
+ const canShowTip = needTip && lane && lane.lines && lane.lines.length > 0 && this.laneHasAnyValidPlotPoint(chart, laneId);
11155
+ if (canShowTip) {
11156
+ const tipKey = `${axisKey}:${Number(yValue).toFixed(2)}`;
10469
11157
  if (this._lastTipKey.get(chart) !== tipKey) {
10470
- this._lastTipKey.set(chart, tipKey);
11158
+ const nearest = this.findHelperTooltipAnchor(chart, yValue, targetGridIndex) || this.findNearestTooltipPoint(chart, yValue, targetGridIndex);
10471
11159
  if (nearest) {
11160
+ this._lastTipKey.set(chart, tipKey);
10472
11161
  chart.dispatchAction({
10473
11162
  type: "showTip",
10474
11163
  seriesIndex: nearest.seriesIndex,
@@ -10476,13 +11165,16 @@ const _sfc_main$2 = {
10476
11165
  x,
10477
11166
  y
10478
11167
  });
10479
- } else {
10480
- chart.dispatchAction({ type: "hideTip" });
10481
11168
  }
10482
11169
  }
11170
+ } else if (this._lastTipKey.get(chart) != null) {
11171
+ this._lastTipKey.delete(chart);
11172
+ chart.dispatchAction({ type: "hideTip" });
10483
11173
  }
11174
+ pinAxis();
10484
11175
  }
10485
- chart.group = group;
11176
+ } finally {
11177
+ this._showingTips = false;
10486
11178
  }
10487
11179
  },
10488
11180
  isLeavingToTooltip(event) {
@@ -10491,6 +11183,81 @@ const _sfc_main$2 = {
10491
11183
  if (next.nodeType !== 1) return false;
10492
11184
  return !!(next.classList && next.classList.contains("echarts-tooltip") || next.closest && next.closest(".echarts-tooltip"));
10493
11185
  },
11186
+ isChartBusy() {
11187
+ return !!(this._isPanning || this._isZooming);
11188
+ },
11189
+ detachChartConnect() {
11190
+ if (this._connectDetached) return;
11191
+ const charts = this.getConnectedCharts();
11192
+ this._savedConnectGroups = [];
11193
+ for (let i = 0; i < charts.length; i++) {
11194
+ const chart = charts[i];
11195
+ if (!chart || chart.isDisposed()) continue;
11196
+ this._savedConnectGroups.push({ chart, group: chart.group });
11197
+ chart.group = null;
11198
+ }
11199
+ this._connectDetached = true;
11200
+ },
11201
+ attachChartConnect() {
11202
+ if (!this._connectDetached) return;
11203
+ const saved = this._savedConnectGroups || [];
11204
+ for (let i = 0; i < saved.length; i++) {
11205
+ const item = saved[i];
11206
+ if (!item || !item.chart || item.chart.isDisposed()) continue;
11207
+ item.chart.group = item.group;
11208
+ }
11209
+ this._savedConnectGroups = null;
11210
+ this._connectDetached = false;
11211
+ },
11212
+ hideLaneTooltipsForPan() {
11213
+ this._lastTipKey = /* @__PURE__ */ new WeakMap();
11214
+ this._lastAxisPx = /* @__PURE__ */ new WeakMap();
11215
+ this.getConnectedCharts().forEach((chart) => {
11216
+ if (!chart || chart.isDisposed()) return;
11217
+ chart.dispatchAction({ type: "hideTip" });
11218
+ chart.dispatchAction({
11219
+ type: "updateAxisPointer",
11220
+ currTrigger: "leave"
11221
+ });
11222
+ });
11223
+ },
11224
+ restorePointerAfterInteract() {
11225
+ if (this.isChartBusy()) return;
11226
+ const pending = this._pendingPointer;
11227
+ if (!pending || !this.pointerInChartArea) return;
11228
+ const { chart, x, y } = pending;
11229
+ if (!chart || chart.isDisposed()) return;
11230
+ this.syncAxisPointerAcrossCharts(chart, x, y);
11231
+ },
11232
+ startChartPan() {
11233
+ if (!this.isGeomechanical || this._isPanning) return;
11234
+ if (this._chartPanLocked) return;
11235
+ this._isPanning = true;
11236
+ this.attachChartConnect();
11237
+ this.hideLaneTooltipsForPan();
11238
+ },
11239
+ endChartPan() {
11240
+ if (!this._isPanning) return;
11241
+ this._isPanning = false;
11242
+ this.restorePointerAfterInteract();
11243
+ },
11244
+ startChartZoom() {
11245
+ if (!this.isGeomechanical) return;
11246
+ this.attachChartConnect();
11247
+ if (!this._isZooming) {
11248
+ this._isZooming = true;
11249
+ if (!this._isPanning) this.hideLaneTooltipsForPan();
11250
+ }
11251
+ if (this._zoomIdleTimer) clearTimeout(this._zoomIdleTimer);
11252
+ this._zoomIdleTimer = setTimeout(() => this.endChartZoom(), 160);
11253
+ },
11254
+ endChartZoom() {
11255
+ this._zoomIdleTimer = 0;
11256
+ if (!this._isZooming) return;
11257
+ this._isZooming = false;
11258
+ if (!this._isPanning && this.trendLineManager) this.trendLineManager.endFollowView();
11259
+ this.restorePointerAfterInteract();
11260
+ },
10494
11261
  hideAllLaneTooltips(event) {
10495
11262
  if (this.isLeavingToTooltip(event)) return;
10496
11263
  const next = event && event.relatedTarget;
@@ -10498,8 +11265,10 @@ const _sfc_main$2 = {
10498
11265
  if (wrap && next && wrap.contains(next)) return;
10499
11266
  this.pointerInChartArea = false;
10500
11267
  this._lastTooltipY = null;
11268
+ this._lastTooltipAxisLabel = "";
10501
11269
  this._lastTooltipGridIndex = 0;
10502
11270
  this._lastHeaderDataIndex = -1;
11271
+ this._tooltipLaneId = null;
10503
11272
  this._lastTipKey = /* @__PURE__ */ new WeakMap();
10504
11273
  this._lastAxisPx = /* @__PURE__ */ new WeakMap();
10505
11274
  this.crosshairVisible = false;
@@ -10514,6 +11283,7 @@ const _sfc_main$2 = {
10514
11283
  currTrigger: "leave"
10515
11284
  });
10516
11285
  });
11286
+ this.attachChartConnect();
10517
11287
  },
10518
11288
  hideAxisPointerAcrossCharts(event) {
10519
11289
  if (this.isLeavingToTooltip(event)) return;
@@ -10532,9 +11302,10 @@ const _sfc_main$2 = {
10532
11302
  this._pendingPointer = null;
10533
11303
  if (!this._pointerSyncHandlers || this._pointerSyncHandlers.length === 0)
10534
11304
  return;
10535
- this._pointerSyncHandlers.forEach(({ zr, onMove, onOut }) => {
11305
+ this._pointerSyncHandlers.forEach(({ zr, onMove, onDown, onOut }) => {
10536
11306
  if (zr) {
10537
11307
  zr.off("mousemove", onMove);
11308
+ if (onDown) zr.off("mousedown", onDown);
10538
11309
  if (onOut) zr.off("globalout", onOut);
10539
11310
  }
10540
11311
  });
@@ -10547,31 +11318,27 @@ const _sfc_main$2 = {
10547
11318
  const chart = item == null ? void 0 : item.chart;
10548
11319
  if (!chart || chart.isDisposed()) return;
10549
11320
  const zr = chart.getZr();
11321
+ const onDown = (e) => {
11322
+ const native = e.event || e;
11323
+ if (native.button != null && native.button !== 0) return;
11324
+ this.startChartPan();
11325
+ };
10550
11326
  const onMove = (e) => {
10551
11327
  var _a, _b;
11328
+ const native = e.event || e;
11329
+ if (this.isGeomechanical && native.buttons === 1 && !this._isPanning) {
11330
+ this.startChartPan();
11331
+ }
10552
11332
  this.schedulePointerSync(
10553
11333
  chart,
10554
11334
  e.offsetX ?? ((_a = e.event) == null ? void 0 : _a.offsetX),
10555
- e.offsetY ?? ((_b = e.event) == null ? void 0 : _b.offsetY)
11335
+ e.offsetY ?? ((_b = e.event) == null ? void 0 : _b.offsetY),
11336
+ native.clientY
10556
11337
  );
10557
11338
  };
10558
- const onOut = () => {
10559
- if (!this.pointerInChartArea || !this.showTooltip) return;
10560
- if (this._lastTooltipY == null) return;
10561
- this.$nextTick(() => {
10562
- if (!this.pointerInChartArea || this._lastTooltipY == null) return;
10563
- this._lastTipKey = /* @__PURE__ */ new WeakMap();
10564
- this._lastAxisPx = /* @__PURE__ */ new WeakMap();
10565
- this.showSyncedLaneTooltips(
10566
- this._lastTooltipY,
10567
- this._lastTooltipOffsetY,
10568
- this._lastTooltipGridIndex
10569
- );
10570
- });
10571
- };
11339
+ zr.on("mousedown", onDown);
10572
11340
  zr.on("mousemove", onMove);
10573
- zr.on("globalout", onOut);
10574
- this._pointerSyncHandlers.push({ zr, onMove, onOut });
11341
+ this._pointerSyncHandlers.push({ zr, onMove, onDown });
10575
11342
  });
10576
11343
  },
10577
11344
  // 联动所有泳道图表的十字准线与 tooltip(贯穿整个泳道区域)
@@ -10584,6 +11351,9 @@ const _sfc_main$2 = {
10584
11351
  if (this._isUnmounted) return;
10585
11352
  const charts = this.getConnectedCharts();
10586
11353
  if (charts.length === 0) return;
11354
+ const keepDetached = this._connectDetached || this.pointerInChartArea && !this.isChartBusy();
11355
+ this._connectDetached = false;
11356
+ this._savedConnectGroups = null;
10587
11357
  charts.forEach((chart) => {
10588
11358
  chart.group = this.group;
10589
11359
  });
@@ -10592,6 +11362,7 @@ const _sfc_main$2 = {
10592
11362
  echarts.connect(this.group);
10593
11363
  }
10594
11364
  this.bindChartPointerSync();
11365
+ if (keepDetached) this.detachChartConnect();
10595
11366
  });
10596
11367
  });
10597
11368
  },
@@ -10602,11 +11373,13 @@ const _sfc_main$2 = {
10602
11373
  if (this.trendLineManager) this.trendLineManager.hydrateFromTemplate(template);
10603
11374
  this.applyLineListData();
10604
11375
  await this.$nextTick();
11376
+ if (this._isUnmounted) return;
10605
11377
  this.updateLanesChartOption();
10606
11378
  },
10607
11379
  // 线段变化
10608
11380
  async lineChange() {
10609
11381
  await this.$nextTick();
11382
+ if (this._isUnmounted) return;
10610
11383
  this.updateLanesChartOption();
10611
11384
  },
10612
11385
  // 初始化窗口大小范围以及windowSize默认值
@@ -10687,7 +11460,12 @@ const _sfc_main$2 = {
10687
11460
  getXAxisRangeByHeader(lineInfo, validData = []) {
10688
11461
  let headerMin = 0;
10689
11462
  let headerMax = 1;
10690
- const numericValues = (validData || []).map((item) => Number(item[0])).filter((v) => !Number.isNaN(v) && v !== null && v !== void 0);
11463
+ const numericValues = (validData || []).reduce((acc, item) => {
11464
+ const raw = Array.isArray(item) ? item[0] : item && item.value ? item.value[0] : item;
11465
+ if (isMissingCurveValue(raw)) return acc;
11466
+ acc.push(Number(raw));
11467
+ return acc;
11468
+ }, []);
10691
11469
  if (lineInfo && Object.prototype.hasOwnProperty.call(lineInfo, "min")) {
10692
11470
  headerMin = Number(lineInfo.min);
10693
11471
  } else if (numericValues.length > 0) {
@@ -10700,10 +11478,10 @@ const _sfc_main$2 = {
10700
11478
  }
10701
11479
  const min = headerMin;
10702
11480
  const max = headerMax;
10703
- this.lineRange[lineInfo.lineId] = {
10704
- min,
10705
- max
10706
- };
11481
+ const prev = this.lineRange[lineInfo.lineId];
11482
+ if (!prev || prev.min !== min || prev.max !== max) {
11483
+ this.lineRange[lineInfo.lineId] = { min, max };
11484
+ }
10707
11485
  return { min, max };
10708
11486
  },
10709
11487
  // 判断滚动是否到达边界
@@ -10719,7 +11497,7 @@ const _sfc_main$2 = {
10719
11497
  currentData: this.visibleData[0],
10720
11498
  direction: "top"
10721
11499
  };
10722
- if (now - this.latestTopInfo.timestamp > oneMinute || JSON.stringify(data) !== JSON.stringify(this.latestTopInfo.data)) {
11500
+ if (now - this.latestTopInfo.timestamp > oneMinute || !this.isSameScrollEdgePayload(data, this.latestTopInfo.data)) {
10723
11501
  this.latestTopInfo = { data, timestamp: now };
10724
11502
  this.$emit("chart-scroll", data);
10725
11503
  }
@@ -10735,13 +11513,19 @@ const _sfc_main$2 = {
10735
11513
  currentData: this.visibleData[this.visibleData.length - 1],
10736
11514
  direction: "bottom"
10737
11515
  };
10738
- if (now - this.latestBottomInfo.timestamp > oneMinute || JSON.stringify(data) !== JSON.stringify(this.latestBottomInfo.data)) {
11516
+ if (now - this.latestBottomInfo.timestamp > oneMinute || !this.isSameScrollEdgePayload(data, this.latestBottomInfo.data)) {
10739
11517
  this.latestBottomInfo = { data, timestamp: now };
10740
11518
  this.$emit("chart-scroll", data);
10741
11519
  }
10742
11520
  }
10743
11521
  },
10744
- // 更新所有泳道的图表选项
11522
+ // 触顶/触底只比轴值,不要 JSON.stringify 整行。8.25
11523
+ isSameScrollEdgePayload(next, prev) {
11524
+ if (!next || !prev) return false;
11525
+ if (next.direction !== prev.direction) return false;
11526
+ const key = this.currentToolBarConfig.axisTypeList && this.currentToolBarConfig.axisTypeList[this.formCache.axisType] || this.formCache.axisType;
11527
+ return next.currentData && prev.currentData && next.currentData[key] === prev.currentData[key];
11528
+ },
10745
11529
  updateOptionsByLineId(lineId, option) {
10746
11530
  let index2 = this.allOptions.findIndex((lane) => lane.lineId == lineId);
10747
11531
  if (Object.prototype.hasOwnProperty.call(option, "xAxis") && option.xAxis.length === 0) {
@@ -10770,10 +11554,10 @@ const _sfc_main$2 = {
10770
11554
  ref.mergeOptions(option, true, false, true);
10771
11555
  },
10772
11556
  // 判断指定泳道是否应该显示tooltip内容
10773
- // 地应力模式下:如果没有选中泳道,所有泳道正常显示;如果有选中泳道,只有选中的泳道显示
11557
+ // 地应力:无选中时所有泳道显示;有选中时已选中的多道都显示。8.25
10774
11558
  shouldShowTooltipContent(laneId) {
10775
11559
  if (!this.showTooltip) return false;
10776
- if (this.isGeomechanical && this.activeLaneId && laneId !== this.activeLaneId) {
11560
+ if (this.isGeomechanical && (this.activeLaneIds || []).length && !this.isLaneSelected(laneId)) {
10777
11561
  return false;
10778
11562
  }
10779
11563
  return true;
@@ -10789,7 +11573,6 @@ const _sfc_main$2 = {
10789
11573
  const chart = chartRef.chart;
10790
11574
  chart.setOption({
10791
11575
  tooltip: {
10792
- ...chart.getOption().tooltip,
10793
11576
  showContent: this.shouldShowTooltipContent(lane.laneId)
10794
11577
  }
10795
11578
  });
@@ -10820,6 +11603,19 @@ const _sfc_main$2 = {
10820
11603
  return { min, max };
10821
11604
  },
10822
11605
  /** 井深/时间轴:没有有效轴值时不画 0、1 占位刻度。不改行数据。8.24 */
11606
+ isDepthOrTimeLane(lane) {
11607
+ if (!lane) return false;
11608
+ const axisTypeList = this.currentToolBarConfig.axisTypeList || {
11609
+ depth: "depth",
11610
+ time: "time"
11611
+ };
11612
+ return Object.prototype.hasOwnProperty.call(axisTypeList, "depth") && axisTypeList.depth == lane.laneKey || Object.prototype.hasOwnProperty.call(axisTypeList, "time") && axisTypeList.time == lane.laneKey;
11613
+ },
11614
+ dataZoomFilterModeForLane(lane) {
11615
+ if (!this.isGeomechanical) return "none";
11616
+ if (this.isDepthOrTimeLane(lane)) return "none";
11617
+ return "weakFilter";
11618
+ },
10823
11619
  shouldShowDepthAxisLabel(rows, axisKey) {
10824
11620
  if (!rows || !rows.length) return false;
10825
11621
  for (let i = 0; i < rows.length; i++) {
@@ -10827,6 +11623,56 @@ const _sfc_main$2 = {
10827
11623
  }
10828
11624
  return false;
10829
11625
  },
11626
+ getDepthTimeLabelWidth(lane) {
11627
+ const mapped = Number(this.laneWidthMap && this.laneWidthMap[lane.laneId]);
11628
+ if (Number.isFinite(mapped) && mapped > 8) return mapped;
11629
+ const w = Number(lane && lane.width);
11630
+ if (Number.isFinite(w) && w > 8) return w;
11631
+ return 80;
11632
+ },
11633
+ // 井深/时间刻度与桌面 kd-curve-v3 一致:居中、按数据步长取间隔。8.25
11634
+ buildDepthTimeAxisLabel(lane, group, isTimeLane, dataGroups, groupIndex, groupMin, groupMax, axisKey) {
11635
+ const clientWidth = this.getDepthTimeLabelWidth(lane);
11636
+ const richWidth = Math.max(clientWidth - 6, 24);
11637
+ return {
11638
+ show: this.shouldShowDepthAxisLabel(group, axisKey),
11639
+ width: clientWidth,
11640
+ hideOverlap: true,
11641
+ height: 14,
11642
+ verticalAlign: "middle",
11643
+ color: getCssVariable("--default-text-color"),
11644
+ overflow: "truncate",
11645
+ rich: {
11646
+ center: {
11647
+ align: "center",
11648
+ fontSize: 12,
11649
+ verticalAlign: "middle",
11650
+ width: richWidth,
11651
+ height: 14,
11652
+ textBaseline: "middle",
11653
+ lineHeight: 14
11654
+ },
11655
+ center2: {
11656
+ align: "center",
11657
+ fontSize: 12,
11658
+ backgroundColor: "red",
11659
+ verticalAlign: "middle",
11660
+ width: richWidth,
11661
+ height: 14,
11662
+ textBaseline: "middle",
11663
+ lineHeight: 14
11664
+ }
11665
+ },
11666
+ formatter: this.getYAxisLabelFormatter(
11667
+ isTimeLane,
11668
+ dataGroups,
11669
+ groupIndex,
11670
+ groupMin,
11671
+ groupMax,
11672
+ axisKey
11673
+ )
11674
+ };
11675
+ },
10830
11676
  getGroupAxisExtents(groups, axisKey) {
10831
11677
  const extents = [];
10832
11678
  let totalRange = 0;
@@ -10838,6 +11684,64 @@ const _sfc_main$2 = {
10838
11684
  }
10839
11685
  return { extents, totalRange };
10840
11686
  },
11687
+ // 成图纸格:横竖同一虚线;竖向 5 等分,横向间距跟小格同宽(方格)。8.25
11688
+ getPaperXSplitCount() {
11689
+ return 5;
11690
+ },
11691
+ getChartPlotHeightPx() {
11692
+ const charts = typeof this.getConnectedCharts === "function" ? this.getConnectedCharts() : [];
11693
+ for (let i = 0; i < charts.length; i++) {
11694
+ const chart = charts[i];
11695
+ if (!chart || chart.isDisposed()) continue;
11696
+ const h = chart.getHeight && chart.getHeight();
11697
+ if (h > 40) return h;
11698
+ }
11699
+ const wrap = this.$refs.chartAreaWrap;
11700
+ if (wrap && wrap.clientHeight > 40) return wrap.clientHeight;
11701
+ return 400;
11702
+ },
11703
+ getTypicalCurveLaneWidthPx() {
11704
+ const map = this.laneWidthMap || {};
11705
+ const lanes = this.currentTemplate && this.currentTemplate.lanes || [];
11706
+ const widths = [];
11707
+ for (let i = 0; i < lanes.length; i++) {
11708
+ const lane = lanes[i];
11709
+ if (!lane || !lane.lines || !lane.lines.length) continue;
11710
+ const w = Number(map[lane.laneId]);
11711
+ if (Number.isFinite(w) && w > 60) widths.push(w);
11712
+ }
11713
+ if (!widths.length) return 200;
11714
+ widths.sort((a, b) => a - b);
11715
+ return widths[Math.floor(widths.length / 2)];
11716
+ },
11717
+ getYGridSplitCount() {
11718
+ const xSplits = this.getPaperXSplitCount();
11719
+ const h = this.getChartPlotHeightPx();
11720
+ const w = this.getTypicalCurveLaneWidthPx();
11721
+ const cell = Math.max(6, w / xSplits);
11722
+ return Math.max(8, Math.min(48, Math.round(h / cell)));
11723
+ },
11724
+ // 井深与横向虚线共用:按列高固定条数,缩放后仍铺满且一一对应。8.25
11725
+ getDepthLabelSplitCount(heightPx) {
11726
+ const h = Math.max(80, Number(heightPx) || this.getChartPlotHeightPx());
11727
+ return Math.max(8, Math.min(24, Math.floor(h / 18)));
11728
+ },
11729
+ getPaperGridAxisStyle(show = true, splitNumber = 5) {
11730
+ const color = getCssVariable("--kd-lane-container-item-line-color");
11731
+ const visible = !!show;
11732
+ return {
11733
+ splitNumber,
11734
+ minorTick: { show: false },
11735
+ minorSplitLine: { show: false },
11736
+ splitLine: {
11737
+ show: visible,
11738
+ showMinLine: false,
11739
+ showMaxLine: false,
11740
+ interval: 0,
11741
+ lineStyle: { color, type: "dashed", width: 1 }
11742
+ }
11743
+ };
11744
+ },
10841
11745
  computeUnifiedAxisInterval(groups, axisKey) {
10842
11746
  let total = 0;
10843
11747
  let min = Infinity;
@@ -10850,10 +11754,34 @@ const _sfc_main$2 = {
10850
11754
  if (e.max > max) max = e.max;
10851
11755
  }
10852
11756
  const range = max - min;
11757
+ const splits = this.getYGridSplitCount();
10853
11758
  if (!Number.isFinite(range) || range <= 0) return 5;
10854
11759
  if (this.isGeomechanical || total > 4e3) {
10855
- return range / 10;
11760
+ return range / splits;
11761
+ }
11762
+ let minStep = Infinity;
11763
+ for (let g = 0; g < groups.length; g++) {
11764
+ const group = groups[g];
11765
+ let prev = NaN;
11766
+ for (let i = 0; i < group.length; i++) {
11767
+ const v = Number(group[i][axisKey]);
11768
+ if (!Number.isFinite(v)) continue;
11769
+ if (Number.isFinite(prev)) {
11770
+ const step = Math.abs(v - prev);
11771
+ if (step > 0 && step < minStep) minStep = step;
11772
+ }
11773
+ prev = v;
11774
+ }
10856
11775
  }
11776
+ if (!Number.isFinite(minStep) || minStep <= 0) return range / 10;
11777
+ const intervalCount = Math.max(1, Math.min(12, Math.ceil(range / minStep)));
11778
+ let unified = Math.ceil(range / Math.min(12, intervalCount));
11779
+ return Math.max(minStep, Math.ceil(unified / minStep) * minStep);
11780
+ },
11781
+ // 与桌面 kd-curve-v3 井深轴相同:按最小数据步长、最多 12 段。8.25
11782
+ computeDepthTimeAxisInterval(groups, axisKey) {
11783
+ let min = Infinity;
11784
+ let max = -Infinity;
10857
11785
  let minStep = Infinity;
10858
11786
  for (let g = 0; g < groups.length; g++) {
10859
11787
  const group = groups[g];
@@ -10861,6 +11789,8 @@ const _sfc_main$2 = {
10861
11789
  for (let i = 0; i < group.length; i++) {
10862
11790
  const v = Number(group[i][axisKey]);
10863
11791
  if (!Number.isFinite(v)) continue;
11792
+ if (v < min) min = v;
11793
+ if (v > max) max = v;
10864
11794
  if (Number.isFinite(prev)) {
10865
11795
  const step = Math.abs(v - prev);
10866
11796
  if (step > 0 && step < minStep) minStep = step;
@@ -10868,24 +11798,66 @@ const _sfc_main$2 = {
10868
11798
  prev = v;
10869
11799
  }
10870
11800
  }
11801
+ const range = max - min;
11802
+ if (!Number.isFinite(range) || range <= 0) return 5;
10871
11803
  if (!Number.isFinite(minStep) || minStep <= 0) return range / 10;
10872
11804
  const intervalCount = Math.max(1, Math.min(12, Math.ceil(range / minStep)));
10873
11805
  let unified = Math.ceil(range / Math.min(12, intervalCount));
10874
11806
  return Math.max(minStep, Math.ceil(unified / minStep) * minStep);
10875
11807
  },
10876
- buildInsideDataZoom(moveOnMouseMove) {
11808
+ buildInsideDataZoom(moveOnMouseMove, filterMode) {
10877
11809
  const pan = moveOnMouseMove === void 0 ? this.isGeomechanical && !this._chartPanLocked : !!moveOnMouseMove;
11810
+ const mode = filterMode == null ? this.isGeomechanical ? "weakFilter" : "none" : filterMode;
10878
11811
  return {
10879
11812
  type: "inside",
10880
11813
  orient: "vertical",
10881
- filterMode: "none",
11814
+ // 视窗外的点不画;x 轴仍用表头 min/max,不会跟着被滤掉的点跑偏。8.25
11815
+ filterMode: mode,
10882
11816
  disabled: !this.isGeomechanical,
10883
11817
  zoomOnMouseWheel: true,
10884
11818
  moveOnMouseMove: pan,
10885
11819
  moveOnMouseWheel: false,
10886
- throttle: this.isGeomechanical ? 50 : 100
11820
+ throttle: this.isGeomechanical ? 16 : 100
11821
+ // 地应力跟手;过大 throttle 会拖影。8.25
10887
11822
  };
10888
11823
  },
11824
+ splitPlotDataByGaps(data) {
11825
+ if (!data || !data.length) return [data || []];
11826
+ const segs = [];
11827
+ let cur = [];
11828
+ for (let i = 0; i < data.length; i++) {
11829
+ const pt = data[i];
11830
+ const x = Array.isArray(pt) ? pt[0] : pt && pt.value && pt.value[0];
11831
+ if (isMissingCurveValue(x)) {
11832
+ if (cur.length) {
11833
+ segs.push(cur);
11834
+ cur = [];
11835
+ }
11836
+ } else {
11837
+ cur.push(pt);
11838
+ }
11839
+ }
11840
+ if (cur.length) segs.push(cur);
11841
+ return segs.length ? segs : [[]];
11842
+ },
11843
+ pushGeoLineSeries(option, baseSeries, seriesSource) {
11844
+ const segs = this.splitPlotDataByGaps(seriesSource);
11845
+ const canSplit = this.isGeomechanical && segs.length > 1 && segs.length <= 24;
11846
+ const chunks = canSplit ? segs : [seriesSource];
11847
+ for (let i = 0; i < chunks.length; i++) {
11848
+ const chunk = chunks[i];
11849
+ const series = i === 0 ? Object.assign(baseSeries, { data: chunk }) : {
11850
+ ...baseSeries,
11851
+ data: chunk,
11852
+ markLine: void 0,
11853
+ markArea: void 0,
11854
+ areaStyle: void 0,
11855
+ tooltip: { show: false }
11856
+ };
11857
+ this.applyGeoLineDrawOption(series, chunk && chunk.length || 0);
11858
+ option.series.push(series);
11859
+ }
11860
+ },
10889
11861
  applyGeoLineDrawOption(series, pointCount) {
10890
11862
  if (!this.isGeomechanical || !series) return series;
10891
11863
  series.clip = true;
@@ -10900,11 +11872,16 @@ const _sfc_main$2 = {
10900
11872
  itemStyle: { opacity: 1 }
10901
11873
  };
10902
11874
  series.select = { disabled: true };
11875
+ series.connectNulls = false;
10903
11876
  const area = series.areaStyle;
10904
11877
  const filled = area && Number(area.opacity) > 0;
10905
- if (pointCount >= 2e3 && !filled) {
11878
+ const hasGap = Array.isArray(series.data) && series.data.some((pt) => {
11879
+ const x = Array.isArray(pt) ? pt[0] : pt && pt.value && pt.value[0];
11880
+ return isMissingCurveValue(x);
11881
+ });
11882
+ if (pointCount >= 400 && !filled && !hasGap) {
10906
11883
  series.large = true;
10907
- series.largeThreshold = 2e3;
11884
+ series.largeThreshold = 400;
10908
11885
  series.symbol = "none";
10909
11886
  }
10910
11887
  return series;
@@ -10922,10 +11899,30 @@ const _sfc_main$2 = {
10922
11899
  toDrawSeriesData(lane, rawPoints) {
10923
11900
  return this.plotSeriesForDraw(rawPoints, isLaneLogScale(lane));
10924
11901
  },
11902
+ getAxisDataKey() {
11903
+ return this.currentToolBarConfig.axisTypeList && this.currentToolBarConfig.axisTypeList[this.formCache.axisType] || (this.formCache.axisType === "time" ? "time" : "depth");
11904
+ },
10925
11905
  findRowByAxisValue(axisValue) {
10926
- const key = this.currentToolBarConfig.axisTypeList && this.currentToolBarConfig.axisTypeList[this.formCache.axisType] || (this.formCache.axisType === "time" ? "time" : "depth");
11906
+ const key = this.getAxisDataKey();
10927
11907
  return findNearestRow(this.visibleData, key, axisValue) || findNearestRow(this.realTimeData, key, axisValue);
10928
11908
  },
11909
+ // 十字线标签用最近一条井深/时间,不要用像素反算的长小数。8.25
11910
+ formatAxisPointerYLabel(axisValue, row) {
11911
+ try {
11912
+ const hit = row || this.findRowByAxisValue(axisValue);
11913
+ if (this.formCache.axisType == "time") {
11914
+ const t = hit && (hit.time != null ? hit.time : hit.timestamp);
11915
+ return this.timestampToYMDHMS(t != null ? t : axisValue);
11916
+ }
11917
+ const key = this.getAxisDataKey();
11918
+ const raw = hit && hit[key] != null ? hit[key] : axisValue;
11919
+ const n = Number(raw);
11920
+ if (!Number.isFinite(n)) return "";
11921
+ return String(Number(n.toFixed(3)));
11922
+ } catch (e) {
11923
+ return "";
11924
+ }
11925
+ },
10929
11926
  readChartGridCount(option) {
10930
11927
  const grid = option && option.grid;
10931
11928
  if (Array.isArray(grid)) return grid.length;
@@ -10986,7 +11983,7 @@ const _sfc_main$2 = {
10986
11983
  const ref = this.getChartRefByLaneId(lanes[i].laneId);
10987
11984
  const chart = ref && ref.chart;
10988
11985
  if (!chart || chart.isDisposed()) continue;
10989
- const gridCount = this.readChartGridCount(chart.getOption() || {});
11986
+ const gridCount = this.readGridCount(chart);
10990
11987
  if (!gridCount || gridCount !== groups.length) {
10991
11988
  if (!this.isUpdatingChart) this.updateLanesChartOption();
10992
11989
  return;
@@ -10997,19 +11994,27 @@ const _sfc_main$2 = {
10997
11994
  const ref = this.getChartRefByLaneId(lane.laneId);
10998
11995
  const chart = ref && ref.chart;
10999
11996
  if (!chart || chart.isDisposed()) return;
11000
- const option = chart.getOption() || {};
11997
+ let seriesList = [];
11998
+ try {
11999
+ seriesList = chart.getModel().getSeries() || [];
12000
+ } catch (e) {
12001
+ return;
12002
+ }
11001
12003
  const isLog = isLaneLogScale(lane);
11002
12004
  const yAxis = groups.map((g) => ({ min: g.min, max: g.max }));
11003
- const series = (option.series || []).map((s) => {
11004
- const gi = this.clampGridIndex(groups.length, this.seriesYAxisIndex(s));
12005
+ const series = seriesList.map((sm) => {
12006
+ const opt = sm && sm.option || {};
12007
+ const gi = this.clampGridIndex(groups.length, this.seriesYAxisIndex(opt));
11005
12008
  const group = groups[gi];
11006
- const name = Array.isArray(s.name) ? s.name[0] : s.name;
12009
+ const name = this.unwrapEchartsValue(sm && sm.name);
11007
12010
  if (name === "helper") {
11008
- const x = s.data && s.data[0] && s.data[0][0];
12011
+ const data2 = opt.data;
12012
+ const x = data2 && data2[0] && data2[0][0];
11009
12013
  return { data: [[x, group.min], [x, group.max]] };
11010
12014
  }
11011
- if (name === "log-grid" || s.type !== "line") return {};
11012
- const paramId = Array.isArray(s.paramId) ? s.paramId[0] : s.paramId;
12015
+ const type = sm.subType || this.unwrapEchartsValue(opt.type);
12016
+ if (name === "log-grid" || name === "trend" || type !== "line") return {};
12017
+ const paramId = this.unwrapEchartsValue(opt.paramId);
11013
12018
  if (paramId == null) return {};
11014
12019
  const cacheKey = `${gi}:${paramId}:${isLog ? 1 : 0}`;
11015
12020
  let data = groupSeriesCache.get(cacheKey);
@@ -11040,7 +12045,6 @@ const _sfc_main$2 = {
11040
12045
  } catch (e) {
11041
12046
  }
11042
12047
  });
11043
- if (this.trendLineManager) this.trendLineManager.scheduleZoomRender();
11044
12048
  },
11045
12049
  // 处理键盘按下事件
11046
12050
  handleKeyDown(e) {
@@ -11108,8 +12112,10 @@ const _sfc_main$2 = {
11108
12112
  },
11109
12113
  // 处理鼠标滚轮事件
11110
12114
  handleWheel(e) {
11111
- if (this.trendLineManager) this.trendLineManager.scheduleZoomRender();
11112
- if (this.isGeomechanical) return;
12115
+ if (this.isGeomechanical) {
12116
+ this.startChartZoom();
12117
+ return;
12118
+ }
11113
12119
  e.preventDefault();
11114
12120
  const dataLength = this.realTimeData && this.realTimeData.length || 0;
11115
12121
  this.wheelDelta = Math.sign(e.deltaY);
@@ -11180,7 +12186,7 @@ const _sfc_main$2 = {
11180
12186
  let markLineYValues = [];
11181
12187
  const firstChart = this.getConnectedCharts()[0];
11182
12188
  if (firstChart) {
11183
- const chartOption = firstChart.getOption();
12189
+ const chartOption = this.readChartOptionRefs(firstChart);
11184
12190
  if (chartOption && chartOption.series) {
11185
12191
  chartOption.series.forEach((series) => {
11186
12192
  if (series.markLine && series.markLine.data) {
@@ -11377,28 +12383,29 @@ const _sfc_main$2 = {
11377
12383
  const newItems = newData.filter(
11378
12384
  (item) => !existingKeys.has(item[axisKey])
11379
12385
  );
11380
- this.realTimeData = [...this.realTimeData, ...newItems];
12386
+ const merged = [...this.realTimeData, ...newItems];
11381
12387
  if (axisType === "depth") {
11382
- if (this.realTimeData[0] && this.realTimeData[0].timestamp) {
11383
- this.realTimeData.sort((a, b) => {
12388
+ if (merged[0] && merged[0].timestamp) {
12389
+ merged.sort((a, b) => {
11384
12390
  const timestampA = this.toMillisecondTimestamp(a.timestamp);
11385
12391
  const timestampB = this.toMillisecondTimestamp(b.timestamp);
11386
12392
  return timestampA - timestampB;
11387
12393
  });
11388
12394
  } else {
11389
- this.realTimeData.sort((a, b) => {
12395
+ merged.sort((a, b) => {
11390
12396
  const valA = Number(a[axisKey]);
11391
12397
  const valB = Number(b[axisKey]);
11392
12398
  return valA - valB;
11393
12399
  });
11394
12400
  }
11395
12401
  } else {
11396
- this.realTimeData.sort((a, b) => {
12402
+ merged.sort((a, b) => {
11397
12403
  const valA = Number(a[axisKey]);
11398
12404
  const valB = Number(b[axisKey]);
11399
12405
  return valA - valB;
11400
12406
  });
11401
12407
  }
12408
+ this.realTimeData = markRaw(merged);
11402
12409
  let targetStartValue = Number(startValue);
11403
12410
  if (axisType !== "depth") {
11404
12411
  const str = String(targetStartValue);
@@ -11525,16 +12532,8 @@ const _sfc_main$2 = {
11525
12532
  type: "value",
11526
12533
  inverse: true,
11527
12534
  boundaryGap: false,
11528
- scale: true,
11529
- splitLine: {
11530
- show: true,
11531
- showMinLine: false,
11532
- showMaxLine: false,
11533
- lineStyle: {
11534
- color: getCssVariable("--kd-lane-container-item-line-color"),
11535
- type: "dashed"
11536
- }
11537
- },
12535
+ scale: false,
12536
+ ...this.getPaperGridAxisStyle(true, this.getYGridSplitCount()),
11538
12537
  axisLine: {
11539
12538
  show: false,
11540
12539
  onZero: false
@@ -11577,24 +12576,7 @@ const _sfc_main$2 = {
11577
12576
  backgroundColor: `${getCssVariable("--kd-lane-container-border-color")}99`,
11578
12577
  color: `${getCssVariable("--default-text-color")}`,
11579
12578
  show: true,
11580
- formatter: (params) => {
11581
- var _a;
11582
- try {
11583
- if (this.formCache.displayType == "header") {
11584
- const firstItem = this.getFirstDataIndexItem(
11585
- params && params.seriesData
11586
- );
11587
- this.headerData = ((_a = firstItem == null ? void 0 : firstItem.data) == null ? void 0 : _a.rowData) || this.findRowByAxisValue(params && params.value) || {};
11588
- }
11589
- if (params == null || params.value == null) return "";
11590
- if (this.formCache.axisType == "time") {
11591
- return this.timestampToYMDHMS(params.value);
11592
- }
11593
- return String(params.value);
11594
- } catch (e) {
11595
- return "";
11596
- }
11597
- }
12579
+ formatter: () => this._lastTooltipAxisLabel || ""
11598
12580
  }
11599
12581
  },
11600
12582
  confine: true,
@@ -11603,65 +12585,7 @@ const _sfc_main$2 = {
11603
12585
  textStyle: {
11604
12586
  color: getCssVariable("--default-text-color")
11605
12587
  },
11606
- formatter: (series) => {
11607
- if (!series || !Array.isArray(series)) return "";
11608
- const uniqueMap = /* @__PURE__ */ new Map();
11609
- for (let i = 0; i < series.length; i++) {
11610
- const current = series[i];
11611
- if (!current || current.seriesName === "helper") continue;
11612
- const existingItem = uniqueMap.get(current.seriesName);
11613
- if (!existingItem) {
11614
- uniqueMap.set(current.seriesName, current);
11615
- continue;
11616
- }
11617
- const existingValue = Array.isArray(existingItem.value) ? existingItem.value[0] : existingItem.value;
11618
- const currentValue = Array.isArray(current.value) ? current.value[0] : current.value;
11619
- const existingHasValue = existingValue !== void 0 && existingValue !== null && existingValue !== "";
11620
- const currentHasValue = currentValue !== void 0 && currentValue !== null && currentValue !== "";
11621
- if (!existingHasValue && currentHasValue) {
11622
- continue;
11623
- }
11624
- if (existingHasValue && !currentHasValue) {
11625
- uniqueMap.set(current.seriesName, current);
11626
- } else if (existingHasValue && currentHasValue) {
11627
- if (Number(currentValue) > Number(existingValue)) {
11628
- uniqueMap.set(current.seriesName, current);
11629
- }
11630
- } else {
11631
- uniqueMap.set(current.seriesName, current);
11632
- }
11633
- }
11634
- const uniqueSeries = [];
11635
- uniqueMap.forEach((item) => uniqueSeries.push(item));
11636
- if (this.tooltipFormatter && typeof this.tooltipFormatter === "function") {
11637
- let mousePositionData = null;
11638
- if (uniqueSeries.length > 0) {
11639
- const firstItem = this.getFirstDataIndexItem(uniqueSeries);
11640
- const tip = firstItem || uniqueSeries[0];
11641
- const axisVal = Array.isArray(tip && tip.value) ? tip.value[1] : tip && tip.axisValue;
11642
- mousePositionData = firstItem && firstItem.data && firstItem.data.rowData || this.findRowByAxisValue(
11643
- Number.isFinite(Number(axisVal)) ? axisVal : this._lastTooltipY
11644
- ) || {};
11645
- } else if (this._lastTooltipY != null) {
11646
- mousePositionData = this.findRowByAxisValue(this._lastTooltipY) || {};
11647
- }
11648
- return this.tooltipFormatter({
11649
- series: uniqueSeries,
11650
- mousePositionData
11651
- });
11652
- }
11653
- let html = "<div>";
11654
- for (let i = 0; i < uniqueSeries.length; i++) {
11655
- const params = uniqueSeries[i];
11656
- const { seriesName, value, color } = params;
11657
- let xValue = value;
11658
- if (Array.isArray(value)) xValue = value[0];
11659
- const text = xValue == void 0 || xValue == null ? `${seriesName}:--` : `${seriesName}:${typeof +xValue === "number" ? Number(Number(xValue).toFixed(3)) : xValue}`;
11660
- html += `<div style="display:flex;align-items:center"><div style="height:10px;width:10px;font-size:10px;border-radius:8px;background-color:${color}"></div><span style="margin-left:8px;font-size:12px">${text}</span></div>`;
11661
- }
11662
- html += "</div>";
11663
- return html;
11664
- }
12588
+ formatter: () => this.renderLaneTooltipHtml(this._tooltipLaneId)
11665
12589
  }
11666
12590
  };
11667
12591
  return option;
@@ -11705,6 +12629,7 @@ const _sfc_main$2 = {
11705
12629
  },
11706
12630
  // 更新泳道图表选项
11707
12631
  async updateLanesChartOption(transferData) {
12632
+ if (this._isUnmounted) return;
11708
12633
  if (this.isUpdatingChart) {
11709
12634
  this.pendingChartUpdate = true;
11710
12635
  return;
@@ -11731,6 +12656,7 @@ const _sfc_main$2 = {
11731
12656
  dataIndexMap.set(String(realTimeData[i][axisKey]), realTimeData[i]);
11732
12657
  }
11733
12658
  try {
12659
+ this._laneHasValidPoint = /* @__PURE__ */ Object.create(null);
11734
12660
  for (let laneIndex = 0; laneIndex < lanes.length; laneIndex++) {
11735
12661
  const lane = lanes[laneIndex];
11736
12662
  const axisTypeList = this.currentToolBarConfig.axisTypeList || {
@@ -11740,11 +12666,15 @@ const _sfc_main$2 = {
11740
12666
  const isDepthLane = Object.prototype.hasOwnProperty.call(axisTypeList, "depth") && axisTypeList.depth == lane.laneKey;
11741
12667
  const isTimeLane = Object.prototype.hasOwnProperty.call(axisTypeList, "time") && axisTypeList.time == lane.laneKey;
11742
12668
  const option = this.getDefaultLaneChartOption();
12669
+ if (isDepthLane || isTimeLane) {
12670
+ if (option.dataZoom && option.dataZoom[0]) {
12671
+ option.dataZoom[0].filterMode = "none";
12672
+ }
12673
+ }
11743
12674
  let hasValidLineData = false;
11744
12675
  const seriesDataCache = {};
11745
12676
  if (lane && lane.lines && lane.lines.length > 0) {
11746
12677
  for (const lineInfo of lane.lines) {
11747
- if (lineInfo.isUsed !== "1") continue;
11748
12678
  const seriesData = this.generateSeriesDataCached(
11749
12679
  realTimeData,
11750
12680
  lineInfo,
@@ -11753,9 +12683,7 @@ const _sfc_main$2 = {
11753
12683
  );
11754
12684
  seriesDataCache[lineInfo.lineId] = { full: seriesData };
11755
12685
  if (!hasValidLineData) {
11756
- hasValidLineData = seriesData.some(
11757
- (point) => point[0] !== null && point[0] !== void 0 && point[0] !== ""
11758
- );
12686
+ hasValidLineData = this.seriesDataHasValidX(seriesData);
11759
12687
  }
11760
12688
  if (lineInfo.recommendParamId) {
11761
12689
  const recommendLineInfo = {
@@ -11772,13 +12700,14 @@ const _sfc_main$2 = {
11772
12700
  full: recommendSeriesData
11773
12701
  };
11774
12702
  if (!hasValidLineData) {
11775
- hasValidLineData = recommendSeriesData.some(
11776
- (point) => point[0] !== null && point[0] !== void 0 && point[0] !== ""
12703
+ hasValidLineData = this.seriesDataHasValidX(
12704
+ recommendSeriesData
11777
12705
  );
11778
12706
  }
11779
12707
  }
11780
12708
  }
11781
12709
  }
12710
+ this._laneHasValidPoint[lane.laneId] = hasValidLineData;
11782
12711
  if (dataGroups.length > 0) {
11783
12712
  option.yAxis = [];
11784
12713
  option.series = [];
@@ -11808,9 +12737,12 @@ const _sfc_main$2 = {
11808
12737
  right: 2,
11809
12738
  height: `${groupHeight}%`,
11810
12739
  show: false,
11811
- containLabel: true
12740
+ containLabel: !!(isDepthLane || isTimeLane)
12741
+ // 井深列给刻度留宽,和桌面版一致。8.25
11812
12742
  });
11813
12743
  currentTop += groupHeight;
12744
+ const groupPlotH = this.getChartPlotHeightPx() * (groupHeight / 100);
12745
+ const ySplitCount = this.isGeomechanical ? this.getDepthLabelSplitCount(groupPlotH) : this.getYGridSplitCount();
11814
12746
  option.yAxis.push({
11815
12747
  gridIndex,
11816
12748
  type: "value",
@@ -11818,17 +12750,11 @@ const _sfc_main$2 = {
11818
12750
  max: groupMax,
11819
12751
  inverse: true,
11820
12752
  boundaryGap: false,
11821
- splitLine: {
11822
- show: true,
11823
- showMinLine: false,
11824
- showMaxLine: false,
11825
- lineStyle: {
11826
- color: getCssVariable(
11827
- "--kd-lane-container-item-line-color"
11828
- ),
11829
- type: "dashed"
11830
- }
11831
- },
12753
+ scale: false,
12754
+ ...this.getPaperGridAxisStyle(
12755
+ !isDepthLane && !isTimeLane,
12756
+ ySplitCount
12757
+ ),
11832
12758
  axisLine: { show: false, onZero: false },
11833
12759
  axisTick: { show: false },
11834
12760
  axisLabel: {
@@ -11838,13 +12764,16 @@ const _sfc_main$2 = {
11838
12764
  },
11839
12765
  axisPointer: { show: hasValidLineData }
11840
12766
  });
11841
- if (dataGroups.length > 1) {
11842
- option.yAxis[gridIndex].interval = unifiedInterval;
12767
+ if (this.isGeomechanical) {
12768
+ option.yAxis[gridIndex].splitNumber = ySplitCount;
12769
+ option.yAxis[gridIndex].minInterval = 1;
12770
+ option.yAxis[gridIndex].alignTicks = true;
12771
+ } else if (isDepthLane || isTimeLane) {
12772
+ option.yAxis[gridIndex].interval = this.computeDepthTimeAxisInterval(dataGroups, axisKey);
11843
12773
  } else {
11844
- option.yAxis[gridIndex].splitNumber = 15;
12774
+ option.yAxis[gridIndex].interval = unifiedInterval;
11845
12775
  }
11846
12776
  if (isDepthLane || isTimeLane) {
11847
- const clientWidth = this.laneWidthMap && this.laneWidthMap[lane.laneId];
11848
12777
  option.xAxis.push({
11849
12778
  gridIndex,
11850
12779
  type: "value",
@@ -11859,53 +12788,33 @@ const _sfc_main$2 = {
11859
12788
  type: "line",
11860
12789
  xAxisIndex: option.xAxis.length - 1,
11861
12790
  yAxisIndex: gridIndex,
12791
+ name: "helper",
11862
12792
  nameIndex: groupIndex,
11863
- data: []
12793
+ data: [
12794
+ [0, groupMin],
12795
+ [0, groupMax]
12796
+ ],
12797
+ symbol: "none",
12798
+ silent: true,
12799
+ tooltip: { show: false },
12800
+ lineStyle: { opacity: 0 }
11864
12801
  });
11865
12802
  Object.assign(option.yAxis[gridIndex], {
11866
12803
  splitLine: { show: false },
12804
+ minorSplitLine: { show: false },
11867
12805
  axisPointer: { show: false },
11868
12806
  alignTicks: true,
11869
12807
  boundaryGap: false,
11870
- axisLabel: {
11871
- show: this.shouldShowDepthAxisLabel(group, axisKey),
11872
- // 无井深值不显示 0/1。8.24
11873
- width: clientWidth,
11874
- hideOverlap: true,
11875
- height: 14,
11876
- verticalAlign: "middle",
11877
- color: getCssVariable("--default-text-color"),
11878
- overflow: "truncate",
11879
- rich: {
11880
- center: {
11881
- align: "center",
11882
- fontSize: 12,
11883
- verticalAlign: "middle",
11884
- width: clientWidth - 6,
11885
- height: 14,
11886
- textBaseline: "middle",
11887
- lineHeight: 14
11888
- },
11889
- center2: {
11890
- align: "center",
11891
- fontSize: 12,
11892
- backgroundColor: "red",
11893
- verticalAlign: "middle",
11894
- width: clientWidth - 6,
11895
- height: 14,
11896
- textBaseline: "middle",
11897
- lineHeight: 14
11898
- }
11899
- },
11900
- formatter: this.getYAxisLabelFormatter(
11901
- isTimeLane,
11902
- dataGroups,
11903
- groupIndex,
11904
- groupMin,
11905
- groupMax,
11906
- axisKey
11907
- )
11908
- }
12808
+ axisLabel: this.buildDepthTimeAxisLabel(
12809
+ lane,
12810
+ group,
12811
+ isTimeLane,
12812
+ dataGroups,
12813
+ groupIndex,
12814
+ groupMin,
12815
+ groupMax,
12816
+ axisKey
12817
+ )
11909
12818
  });
11910
12819
  option.backgroundColor = "transparent";
11911
12820
  delete option.tooltip;
@@ -11962,22 +12871,16 @@ const _sfc_main$2 = {
11962
12871
  }),
11963
12872
  show: !isDepthLane
11964
12873
  } : {
11965
- interval: (max - min) / 5,
11966
- splitNumber: 5,
12874
+ interval: (max - min) / this.getPaperXSplitCount(),
11967
12875
  gridIndex,
11968
12876
  show: !isDepthLane,
11969
12877
  type: "value",
11970
12878
  min,
11971
12879
  max,
11972
- splitLine: {
11973
- show: !isDepthLane,
11974
- showMaxLine: false,
11975
- showMinLine: false,
11976
- lineStyle: {
11977
- color: splitColor,
11978
- type: "dashed"
11979
- }
11980
- },
12880
+ ...this.getPaperGridAxisStyle(
12881
+ !isDepthLane && index2 === 0,
12882
+ this.getPaperXSplitCount()
12883
+ ),
11981
12884
  axisLabel: { show: false },
11982
12885
  axisLine: { show: false },
11983
12886
  axisTick: { show: false }
@@ -12001,6 +12904,7 @@ const _sfc_main$2 = {
12001
12904
  type: lineInfo.lineType
12002
12905
  },
12003
12906
  clip: true,
12907
+ connectNulls: false,
12004
12908
  z: lineInfo.lineSort
12005
12909
  };
12006
12910
  if (lane.lines.length - 1 == index2) {
@@ -12039,12 +12943,11 @@ const _sfc_main$2 = {
12039
12943
  max,
12040
12944
  isLogScale
12041
12945
  );
12042
- this.applyGeoLineDrawOption(baseSeries, seriesSource.length);
12043
- option.series.push(baseSeries);
12946
+ this.pushGeoLineSeries(option, baseSeries, seriesSource);
12044
12947
  if (rangeAreaSeries) {
12045
12948
  option.series.push(rangeAreaSeries);
12046
12949
  }
12047
- if (lineInfo.isUsed === "1" && this.isGeomechanical && lane.laneId === this.activeLaneId) {
12950
+ if (lineInfo.isUsed === "1" && this.isGeomechanical && this.isLaneSelected(lane.laneId)) {
12048
12951
  const boundaryPoints = this.findMissingDataBoundaryPoints(
12049
12952
  this.plotSeries(groupSeriesData, isLogScale)
12050
12953
  );
@@ -12098,13 +13001,10 @@ const _sfc_main$2 = {
12098
13001
  type: "dashed"
12099
13002
  },
12100
13003
  clip: true,
13004
+ connectNulls: false,
12101
13005
  z: lineInfo.lineSort
12102
13006
  };
12103
- this.applyGeoLineDrawOption(
12104
- recommendSeries,
12105
- recommendSource.length
12106
- );
12107
- option.series.push(recommendSeries);
13007
+ this.pushGeoLineSeries(option, recommendSeries, recommendSource);
12108
13008
  }
12109
13009
  const effectScatterPoint = this.latestLineDataByParamCode[lineInfo.paramId];
12110
13010
  if (effectScatterPoint && lineInfo.latestDataTips) {
@@ -12159,25 +13059,27 @@ const _sfc_main$2 = {
12159
13059
  }).filter(
12160
13060
  (v) => v !== void 0 && v !== null && !Number.isNaN(v)
12161
13061
  );
13062
+ let helperX = 0;
12162
13063
  if (rangeMins.length > 0) {
12163
- const min = Math.max(...rangeMins);
12164
- const helperX = isLogScale ? Math.log10(this.getLaneLogPlotRange(lane).min) : min;
12165
- option.series.unshift({
12166
- name: "helper",
12167
- type: "line",
12168
- xAxisIndex: option.xAxis.length - 1,
12169
- yAxisIndex: gridIndex,
12170
- data: [
12171
- [helperX, groupMin],
12172
- [helperX, groupMax]
12173
- ],
12174
- lineStyle: { opacity: 0 },
12175
- symbol: "none",
12176
- silent: true,
12177
- // 禁用鼠标事件
12178
- tooltip: { show: false }
12179
- });
13064
+ helperX = isLogScale ? Math.log10(this.getLaneLogPlotRange(lane).min) : Math.max(...rangeMins);
13065
+ } else if (isLogScale) {
13066
+ helperX = Math.log10(this.getLaneLogPlotRange(lane).min);
12180
13067
  }
13068
+ if (!Number.isFinite(helperX)) helperX = 0;
13069
+ option.series.unshift({
13070
+ name: "helper",
13071
+ type: "line",
13072
+ xAxisIndex: option.xAxis.length - 1,
13073
+ yAxisIndex: gridIndex,
13074
+ data: [
13075
+ [helperX, groupMin],
13076
+ [helperX, groupMax]
13077
+ ],
13078
+ lineStyle: { opacity: 0 },
13079
+ symbol: "none",
13080
+ silent: true,
13081
+ tooltip: { show: true }
13082
+ });
12181
13083
  if (isLogScale) {
12182
13084
  option.series.unshift(
12183
13085
  this.getLogPaperGridSeries(
@@ -12191,19 +13093,24 @@ const _sfc_main$2 = {
12191
13093
  }
12192
13094
  }
12193
13095
  if (option.tooltip && lane && lane.lines && lane.lines.length) {
12194
- option.tooltip.showContent = this.shouldShowTooltipContent(
12195
- lane.laneId
12196
- );
13096
+ const laneId = lane.laneId;
13097
+ option.tooltip.showContent = this.shouldShowTooltipContent(laneId);
12197
13098
  option.tooltip.triggerOn = "none";
12198
13099
  option.tooltip.enterable = false;
12199
13100
  option.tooltip.extraCssText = "pointer-events: none !important;";
13101
+ option.tooltip.formatter = () => this.renderLaneTooltipHtml(laneId);
12200
13102
  }
13103
+ if (this.trendLineManager) this.trendLineManager.appendSeriesToOption(option, lane);
12201
13104
  this.updateOptionsByLineId(lane.laneId, option);
12202
13105
  }
12203
13106
  } catch (error) {
12204
13107
  console.error("updateLanesChartOption error:", error);
12205
13108
  } finally {
12206
13109
  this.isUpdatingChart = false;
13110
+ if (this._isUnmounted) {
13111
+ this.pendingChartUpdate = false;
13112
+ return;
13113
+ }
12207
13114
  this.scheduleConnectAllCharts();
12208
13115
  const lanesToRefresh = new Set(
12209
13116
  Object.keys(this.laneSelectedPointPairs || {})
@@ -12211,6 +13118,7 @@ const _sfc_main$2 = {
12211
13118
  if (this.selectedLaneId) lanesToRefresh.add(this.selectedLaneId);
12212
13119
  if (lanesToRefresh.size > 0) {
12213
13120
  this.$nextTick(() => {
13121
+ if (this._isUnmounted) return;
12214
13122
  lanesToRefresh.forEach((lid) => {
12215
13123
  this.showSelectedPointScatter(lid);
12216
13124
  });
@@ -12219,13 +13127,10 @@ const _sfc_main$2 = {
12219
13127
  if (this.pendingChartUpdate) {
12220
13128
  this.pendingChartUpdate = false;
12221
13129
  this.$nextTick(() => {
13130
+ if (this._isUnmounted) return;
12222
13131
  this.updateLanesChartOption(transferData);
12223
13132
  });
12224
13133
  }
12225
- if (this.trendLineManager) {
12226
- this.trendLineManager.scheduleZoomRender();
12227
- this.$nextTick(() => this.trendLineManager.scheduleZoomRender());
12228
- }
12229
13134
  }
12230
13135
  },
12231
13136
  /**
@@ -12270,6 +13175,108 @@ const _sfc_main$2 = {
12270
13175
  output.push(data[dataLength - 1]);
12271
13176
  return output;
12272
13177
  },
13178
+ formatTooltipXValue(raw) {
13179
+ if (this.isMissingTooltipValue(raw)) return "--";
13180
+ return Number(Number(raw).toFixed(3));
13181
+ },
13182
+ getLineThemeColor(lineInfo) {
13183
+ if (!lineInfo) return "";
13184
+ if (lineInfo.themeConfig && lineInfo.themeConfig[this.nowTheme]) {
13185
+ if (lineInfo.isGradient && this.linearGradientLineColor && this.linearGradientLineColor[this.nowTheme] && this.linearGradientLineColor[this.nowTheme][lineInfo.paramId]) {
13186
+ return this.linearGradientLineColor[this.nowTheme][lineInfo.paramId];
13187
+ }
13188
+ return lineInfo.themeConfig[this.nowTheme].lineColor;
13189
+ }
13190
+ return lineInfo.lineColor || "";
13191
+ },
13192
+ getTooltipLineName(lineInfo, isRecommend) {
13193
+ if (isRecommend) return "推荐值";
13194
+ if (lineInfo && lineInfo.recommendParamId) return "实际值";
13195
+ const param = (this.params || []).find(
13196
+ (p) => p.paramId == (lineInfo && lineInfo.paramId)
13197
+ );
13198
+ if (param && param.paramName) {
13199
+ return `${param.paramName}${param.paramUnit ? `(${param.paramUnit})` : ""}`;
13200
+ }
13201
+ return lineInfo && (lineInfo.paramName || String(lineInfo.paramId)) || "";
13202
+ },
13203
+ readLineValueFromRow(row, paramId) {
13204
+ if (!row || paramId == null) return null;
13205
+ if (paramId in row) return row[paramId];
13206
+ const strId = String(paramId);
13207
+ if (strId in row) return row[strId];
13208
+ const currentParam = (this.params || []).find((p) => p.paramId == paramId);
13209
+ if (currentParam && currentParam.paramCode) {
13210
+ const codes = String(currentParam.paramCode).split(",");
13211
+ for (let i = 0; i < codes.length; i++) {
13212
+ const code = codes[i];
13213
+ if (code && code in row) return row[code];
13214
+ }
13215
+ }
13216
+ return null;
13217
+ },
13218
+ formatTooltipLineDisplayValue(raw, isLogScale) {
13219
+ if (this.isMissingTooltipValue(raw)) return "--";
13220
+ if (isLogScale) {
13221
+ const lg = formatLanePointValue(raw);
13222
+ return lg == null ? "--" : lg;
13223
+ }
13224
+ return this.formatTooltipXValue(raw);
13225
+ },
13226
+ buildLaneTooltipSeries(lane, row, yValue) {
13227
+ const lines = lane && lane.lines || [];
13228
+ const isLogScale = isLaneLogScale(lane);
13229
+ const items = [];
13230
+ for (let i = 0; i < lines.length; i++) {
13231
+ const lineInfo = lines[i];
13232
+ const raw = this.readLineValueFromRow(row, lineInfo.paramId);
13233
+ items.push({
13234
+ seriesName: this.getTooltipLineName(lineInfo, false),
13235
+ color: this.getLineThemeColor(lineInfo),
13236
+ value: [this.formatTooltipLineDisplayValue(raw, isLogScale), yValue],
13237
+ paramId: lineInfo.paramId,
13238
+ lineId: lineInfo.lineId
13239
+ });
13240
+ if (lineInfo.recommendParamId) {
13241
+ const recRaw = this.readLineValueFromRow(
13242
+ row,
13243
+ lineInfo.recommendParamId
13244
+ );
13245
+ items.push({
13246
+ seriesName: this.getTooltipLineName(lineInfo, true),
13247
+ color: this.getLineThemeColor(lineInfo),
13248
+ value: [
13249
+ this.formatTooltipLineDisplayValue(recRaw, isLogScale),
13250
+ yValue
13251
+ ],
13252
+ paramId: lineInfo.recommendParamId,
13253
+ lineId: lineInfo.lineId
13254
+ });
13255
+ }
13256
+ }
13257
+ return items;
13258
+ },
13259
+ renderLaneTooltipHtml(laneId) {
13260
+ const yValue = this._lastTooltipY;
13261
+ const lane = this.findLaneById(laneId);
13262
+ const row = yValue != null ? this.findRowByAxisValue(yValue) || {} : {};
13263
+ const displaySeries = this.buildLaneTooltipSeries(lane, row, yValue);
13264
+ if (this.tooltipFormatter && typeof this.tooltipFormatter === "function") {
13265
+ return this.tooltipFormatter({
13266
+ series: displaySeries,
13267
+ mousePositionData: row
13268
+ });
13269
+ }
13270
+ let html = "<div>";
13271
+ for (let i = 0; i < displaySeries.length; i++) {
13272
+ const params = displaySeries[i];
13273
+ const { seriesName, value, color } = params;
13274
+ const xValue = Array.isArray(value) ? value[0] : value;
13275
+ html += `<div style="display:flex;align-items:center"><div style="height:10px;width:10px;font-size:10px;border-radius:8px;background-color:${color}"></div><span style="margin-left:8px;font-size:12px">${seriesName}:${xValue}</span></div>`;
13276
+ }
13277
+ html += "</div>";
13278
+ return html;
13279
+ },
12273
13280
  // 生成泳道图表数据
12274
13281
  generateSeriesData(data, lineInfo, axisKey) {
12275
13282
  return data.map((item) => {
@@ -12290,6 +13297,7 @@ const _sfc_main$2 = {
12290
13297
  }
12291
13298
  }
12292
13299
  }
13300
+ if (isMissingCurveValue(value)) value = null;
12293
13301
  return [value, +item[axisKey], item.timestamp];
12294
13302
  });
12295
13303
  },
@@ -12308,6 +13316,7 @@ const _sfc_main$2 = {
12308
13316
  }
12309
13317
  }
12310
13318
  }
13319
+ if (isMissingCurveValue(value)) value = null;
12311
13320
  return [value, +item[axisKey], item.timestamp];
12312
13321
  });
12313
13322
  },
@@ -12326,6 +13335,7 @@ const _sfc_main$2 = {
12326
13335
  }
12327
13336
  }
12328
13337
  }
13338
+ if (isMissingCurveValue(value)) value = null;
12329
13339
  return {
12330
13340
  value: [value, +item[axisKey]],
12331
13341
  rowData: item
@@ -12413,85 +13423,172 @@ const _sfc_main$2 = {
12413
13423
  }
12414
13424
  return boundaryPoints;
12415
13425
  },
12416
- async handleChartClick(e, laneId) {
12417
- var _a, _b, _c;
12418
- const chartRef = this.getChartRefByLaneId(laneId);
12419
- if (!chartRef || !chartRef.chart) return;
12420
- if (this.trendLineManager && this.trendLineManager.handleClick(e, laneId)) {
12421
- return;
12422
- }
12423
- if (!this.isGeomechanical) return;
12424
- const wasActiveLane = this.activeLaneId === laneId;
12425
- if (this.activeLaneId !== laneId) {
12426
- const prevActiveLaneId = this.activeLaneId;
12427
- this.activeLaneId = laneId;
12428
- await this.updateLanesChartOption();
12429
- if (prevActiveLaneId) {
12430
- this.showSelectedPointScatter(prevActiveLaneId);
13426
+ // 只把点击附近的点转成像素。全量 convertToPixel 会堵住主线程,双击 500ms 对不上。8.25
13427
+ findClosestGeoLineHit(chart, option, pointInPixel, pixelThreshold, laneId) {
13428
+ var _a;
13429
+ const threshold = pixelThreshold || 10;
13430
+ const px = pointInPixel[0];
13431
+ const py = pointInPixel[1];
13432
+ const seriesList = option && option.series || [];
13433
+ let closest = null;
13434
+ let closestDistance = threshold;
13435
+ for (let i = 0; i < seriesList.length; i++) {
13436
+ const series2 = seriesList[i];
13437
+ if (!series2 || series2.type !== "line") continue;
13438
+ const name = Array.isArray(series2.name) ? series2.name[0] : series2.name;
13439
+ if (name === "helper" || name === "log-grid" || name === "trend") continue;
13440
+ const id = String(series2.id || "");
13441
+ if (id.indexOf("trend_") === 0) continue;
13442
+ let clickData;
13443
+ try {
13444
+ clickData = chart.convertFromPixel({ seriesIndex: i }, [px, py]);
13445
+ } catch (err) {
13446
+ continue;
12431
13447
  }
12432
- }
12433
- const chart = chartRef.chart;
12434
- const option = chart.getOption();
12435
- const pointInPixel = [
12436
- e.offsetX || ((_a = e.event) == null ? void 0 : _a.offsetX),
12437
- e.offsetY || ((_b = e.event) == null ? void 0 : _b.offsetY)
12438
- ];
12439
- let clickedPoint = null;
12440
- let closestPoint = null;
12441
- let closestDistance = 10;
12442
- const axisKey = this.currentToolBarConfig.axisTypeList && this.currentToolBarConfig.axisTypeList[this.formCache.axisType] || (this.formCache.axisType === "time" ? "time" : "depth");
12443
- for (let i = 0; i < option.series.length; i++) {
12444
- const series = option.series[i];
12445
- if (series.name === "helper" || series.name === "log-grid") continue;
12446
- if (series.type !== "line") continue;
12447
- const data = series.data || [];
13448
+ if (!clickData || clickData.length < 2) continue;
13449
+ let yTol = Infinity;
13450
+ try {
13451
+ const offset = chart.convertFromPixel({ seriesIndex: i }, [px, py + threshold]);
13452
+ if (offset && offset.length >= 2) {
13453
+ const span = Math.abs(offset[1] - clickData[1]);
13454
+ if (Number.isFinite(span) && span > 0) yTol = span * 1.25;
13455
+ }
13456
+ } catch (err) {
13457
+ yTol = Infinity;
13458
+ }
13459
+ const data = series2.data || [];
12448
13460
  for (let j = 0; j < data.length; j++) {
12449
- const item = data[j];
12450
- if (!item) continue;
12451
- let pointValue;
12452
- pointValue = Array.isArray(item) ? item : item.value || [];
12453
- if (!pointValue || !Array.isArray(pointValue) || pointValue.length < 2)
12454
- continue;
12455
- const point = chart.convertToPixel({ seriesIndex: i }, pointValue);
13461
+ const item2 = data[j];
13462
+ if (!item2) continue;
13463
+ const pointValue2 = Array.isArray(item2) ? item2 : item2.value || [];
13464
+ if (!pointValue2 || pointValue2.length < 2) continue;
13465
+ if (Number.isFinite(yTol) && Math.abs(pointValue2[1] - clickData[1]) > yTol) continue;
13466
+ const point = chart.convertToPixel({ seriesIndex: i }, pointValue2);
12456
13467
  if (!point) continue;
12457
- const distance = Math.sqrt(
12458
- Math.pow(point[0] - pointInPixel[0], 2) + Math.pow(point[1] - pointInPixel[1], 2)
12459
- );
13468
+ const distance = Math.hypot(point[0] - px, point[1] - py);
12460
13469
  if (distance < closestDistance) {
12461
13470
  closestDistance = distance;
12462
- const axisValue = pointValue[1];
12463
- const originalData = this.realTimeData.find(
12464
- (d) => d[axisKey] == axisValue
12465
- );
12466
- const paramId = item.paramId || series.paramId;
12467
- const lane = (this.currentTemplate && this.currentTemplate.lanes || []).find(
12468
- (l) => l.laneId === laneId
12469
- );
12470
- const originalX = (item.rowData && item.rowData[paramId]) ?? (originalData && originalData[paramId]);
12471
- option.xAxis && option.xAxis[series.xAxisIndex ?? 0] || {};
12472
- const useOriginal = originalX != null && Number(originalX) > 0;
12473
- const drawX = isLaneLogScale(lane) ? toLaneDrawX(
12474
- useOriginal ? originalX : pointValue[0],
12475
- lane
12476
- ) : pointValue[0];
12477
- closestPoint = {
12478
- ...item,
12479
- value: [drawX, pointValue[1], pointValue[2]],
12480
- seriesIndex: i,
12481
- dataIndex: j,
12482
- paramId,
12483
- lineId: item.lineId || series.lineId,
12484
- color: series.color,
12485
- laneId,
12486
- depth: (originalData == null ? void 0 : originalData.depth) || null,
12487
- timestamp: ((_c = originalData == null ? void 0 : originalData.timestamp) == null ? void 0 : _c.toString()) || null
12488
- };
13471
+ closest = { item: item2, series: series2, seriesIndex: i, dataIndex: j, pointValue: pointValue2, distance };
12489
13472
  }
12490
13473
  }
12491
13474
  }
12492
- if (closestPoint && closestDistance < 10) {
12493
- clickedPoint = closestPoint;
13475
+ if (!closest) return null;
13476
+ const axisKey = this.currentToolBarConfig.axisTypeList && this.currentToolBarConfig.axisTypeList[this.formCache.axisType] || (this.formCache.axisType === "time" ? "time" : "depth");
13477
+ const { item, series, seriesIndex, dataIndex, pointValue } = closest;
13478
+ const originalData = item && item.rowData || (this.realTimeData || []).find((d) => d[axisKey] == pointValue[1]);
13479
+ const paramId = item && item.paramId || series.paramId;
13480
+ const lane = (this.currentTemplate && this.currentTemplate.lanes || []).find(
13481
+ (l) => l.laneId === laneId
13482
+ );
13483
+ const originalX = (item.rowData && item.rowData[paramId]) ?? (originalData && originalData[paramId]);
13484
+ option.xAxis && option.xAxis[series.xAxisIndex ?? 0] || {};
13485
+ const useOriginal = originalX != null && Number(originalX) > 0;
13486
+ const drawX = isLaneLogScale(lane) ? toLaneDrawX(
13487
+ useOriginal ? originalX : pointValue[0],
13488
+ lane
13489
+ ) : pointValue[0];
13490
+ return {
13491
+ ...item,
13492
+ value: [drawX, pointValue[1], pointValue[2]],
13493
+ seriesIndex,
13494
+ dataIndex,
13495
+ paramId,
13496
+ lineId: item && item.lineId || series.lineId,
13497
+ color: series.color,
13498
+ laneId,
13499
+ depth: (originalData == null ? void 0 : originalData.depth) || null,
13500
+ timestamp: ((_a = originalData == null ? void 0 : originalData.timestamp) == null ? void 0 : _a.toString()) || null
13501
+ };
13502
+ },
13503
+ _clearGeoClickTimer() {
13504
+ if (this._geoClickTimer) {
13505
+ clearTimeout(this._geoClickTimer);
13506
+ this._geoClickTimer = 0;
13507
+ }
13508
+ },
13509
+ // 地应力:双击已选中泳道,只取消这一道,其它选中保留。8.25
13510
+ handleChartDblClick(e, laneId) {
13511
+ if (this._isUnmounted || !this.isGeomechanical) return;
13512
+ this._clearGeoClickTimer();
13513
+ if (!this.isLaneSelected(laneId)) return;
13514
+ if (this._geoSelectClickAt && this._geoSelectLaneId === laneId && Date.now() - this._geoSelectClickAt < 500) {
13515
+ return;
13516
+ }
13517
+ this.deselectGeoLane(laneId);
13518
+ },
13519
+ async deselectGeoLane(laneId) {
13520
+ const id = laneId != null ? laneId : this.activeLaneId;
13521
+ this._lastBlankClickTime = null;
13522
+ this._lastBlankClickLaneId = null;
13523
+ if (!id || !this.isLaneSelected(id)) return;
13524
+ if (this.selectedPoints.length > 0 && this.selectedLaneId === id) {
13525
+ if (this.laneSelectedPointPairs[this.selectedLaneId]) {
13526
+ this.laneSelectedPointPairs[this.selectedLaneId].pop();
13527
+ }
13528
+ this.selectedPoints = [];
13529
+ this.selectedLaneId = null;
13530
+ this.selectedParamId = null;
13531
+ }
13532
+ this.removeLaneSelection(id);
13533
+ await this.updateLanesChartOption();
13534
+ if (this._isUnmounted) return;
13535
+ this.showSelectedPointScatter(id);
13536
+ },
13537
+ async handleChartClick(e, laneId) {
13538
+ var _a, _b, _c, _d;
13539
+ if (this._isUnmounted) return;
13540
+ const clickAt = Date.now();
13541
+ const chartRef = this.getChartRefByLaneId(laneId);
13542
+ if (!chartRef || !chartRef.chart) return;
13543
+ const wasActiveLane = this.isLaneSelected(laneId);
13544
+ const trendConsumed = this.trendLineManager && this.trendLineManager.handleClick(e, laneId);
13545
+ if (!this.isGeomechanical) return;
13546
+ if (!wasActiveLane) {
13547
+ this._geoSelectClickAt = clickAt;
13548
+ this._geoSelectLaneId = laneId;
13549
+ this.addLaneSelection(laneId);
13550
+ await this.updateLanesChartOption();
13551
+ if (this._isUnmounted) return;
13552
+ this.showSelectedPointScatter(laneId);
13553
+ }
13554
+ if (trendConsumed) return;
13555
+ if (wasActiveLane) {
13556
+ this._clearGeoClickTimer();
13557
+ const offsetX = e.offsetX || ((_a = e.event) == null ? void 0 : _a.offsetX);
13558
+ const offsetY = e.offsetY || ((_b = e.event) == null ? void 0 : _b.offsetY);
13559
+ this._geoClickTimer = setTimeout(() => {
13560
+ this._geoClickTimer = 0;
13561
+ if (this._isUnmounted) return;
13562
+ this.processGeoPointOrBlank(laneId, offsetX, offsetY, clickAt, true);
13563
+ }, 280);
13564
+ return;
12494
13565
  }
13566
+ const pointInPixel = [
13567
+ e.offsetX || ((_c = e.event) == null ? void 0 : _c.offsetX),
13568
+ e.offsetY || ((_d = e.event) == null ? void 0 : _d.offsetY)
13569
+ ];
13570
+ await this.processGeoPointOrBlank(
13571
+ laneId,
13572
+ pointInPixel[0],
13573
+ pointInPixel[1],
13574
+ clickAt,
13575
+ false
13576
+ );
13577
+ },
13578
+ async processGeoPointOrBlank(laneId, offsetX, offsetY, clickAt, wasActiveLane) {
13579
+ if (this._isUnmounted) return;
13580
+ const chartRef = this.getChartRefByLaneId(laneId);
13581
+ if (!chartRef || !chartRef.chart) return;
13582
+ const chart = chartRef.chart;
13583
+ const option = this.readChartOptionRefs(chart);
13584
+ const closestPoint = this.findClosestGeoLineHit(
13585
+ chart,
13586
+ option,
13587
+ [offsetX, offsetY],
13588
+ 10,
13589
+ laneId
13590
+ );
13591
+ const clickedPoint = closestPoint || null;
12495
13592
  if (clickedPoint) {
12496
13593
  this._lastBlankClickTime = null;
12497
13594
  this._lastBlankClickLaneId = null;
@@ -12550,18 +13647,19 @@ const _sfc_main$2 = {
12550
13647
  this.showSelectedPointScatter(laneId);
12551
13648
  }
12552
13649
  if (wasActiveLane) {
12553
- const now = Date.now();
13650
+ const now = clickAt;
12554
13651
  if (this._lastBlankClickLaneId === laneId && this._lastBlankClickTime && now - this._lastBlankClickTime < 500) {
12555
13652
  this._lastBlankClickTime = null;
12556
13653
  this._lastBlankClickLaneId = null;
12557
- if (this.selectedPoints.length > 0 && this.selectedLaneId) {
13654
+ if (this.selectedPoints.length > 0 && this.selectedLaneId === laneId) {
12558
13655
  this.laneSelectedPointPairs[this.selectedLaneId].pop();
12559
13656
  this.selectedPoints = [];
12560
13657
  this.selectedLaneId = null;
12561
13658
  this.selectedParamId = null;
12562
13659
  }
12563
- this.activeLaneId = null;
13660
+ this.removeLaneSelection(laneId);
12564
13661
  await this.updateLanesChartOption();
13662
+ if (this._isUnmounted) return;
12565
13663
  } else {
12566
13664
  this._lastBlankClickTime = now;
12567
13665
  this._lastBlankClickLaneId = laneId;
@@ -12572,12 +13670,13 @@ const _sfc_main$2 = {
12572
13670
  }
12573
13671
  }
12574
13672
  await this.$nextTick();
13673
+ if (this._isUnmounted) return;
12575
13674
  this.showSelectedPointScatter(laneId);
12576
13675
  },
12577
13676
  // 收集某泳道所有需要展示 scatter 的点:已完成区块的两个点 + 进行中的第一个点
12578
- // 仅在地应力模式下的当前激活泳道才收集并显示
13677
+ // 地应力已选中的泳道都收集并显示。8.25
12579
13678
  collectScatterPointsForLane(laneId) {
12580
- if (!this.isGeomechanical || laneId !== this.activeLaneId) return [];
13679
+ if (!this.isGeomechanical || !this.isLaneSelected(laneId)) return [];
12581
13680
  const points = [];
12582
13681
  const pairs = this.laneSelectedPointPairs[laneId] || [];
12583
13682
  pairs.forEach((pair) => {
@@ -12603,10 +13702,10 @@ const _sfc_main$2 = {
12603
13702
  const filteredPoints = points.filter((point) => {
12604
13703
  if (this.formCache.axisType === "depth" && point.timestamp !== void 0 && point.timestamp !== null) {
12605
13704
  const pointY = point.value[1];
12606
- let findItem = this.visibleData.find(
13705
+ const findItem = this.visibleData.find(
12607
13706
  (d) => d[axisKey] == pointY && d.timestamp == point.timestamp
12608
- ) || {};
12609
- if (JSON.stringify(findItem) == "{}") {
13707
+ );
13708
+ if (!findItem) {
12610
13709
  return false;
12611
13710
  }
12612
13711
  return true;
@@ -12624,7 +13723,7 @@ const _sfc_main$2 = {
12624
13723
  borderWidth: 2
12625
13724
  }
12626
13725
  }));
12627
- const option = chart.getOption();
13726
+ const option = this.readChartOptionRefs(chart);
12628
13727
  const existingIndex = option.series.findIndex(
12629
13728
  (s) => s.id === "selectedPointScatter"
12630
13729
  );
@@ -12706,22 +13805,16 @@ const _sfc_main$2 = {
12706
13805
  return;
12707
13806
  }
12708
13807
  if (!this.isGeomechanical) return;
12709
- if (this.activeLaneId !== laneId) {
12710
- const prevActiveLaneId = this.activeLaneId;
12711
- this.activeLaneId = laneId;
13808
+ if (!this.isLaneSelected(laneId)) {
13809
+ this.addLaneSelection(laneId);
12712
13810
  this.updateLanesChartOption();
12713
- if (prevActiveLaneId) {
12714
- this.$nextTick(() => {
12715
- this.showSelectedPointScatter(prevActiveLaneId);
12716
- });
12717
- }
12718
13811
  }
12719
13812
  const chart = chartRef.chart;
12720
13813
  const pointInPixel = [
12721
13814
  e.offsetX || ((_a = e.event) == null ? void 0 : _a.offsetX),
12722
13815
  e.offsetY || ((_b = e.event) == null ? void 0 : _b.offsetY)
12723
13816
  ];
12724
- const option = chart.getOption();
13817
+ const option = this.readChartOptionRefs(chart);
12725
13818
  let clickedPoint = null;
12726
13819
  let minDistance = 15;
12727
13820
  for (let i = 0; i < option.series.length; i++) {
@@ -12869,7 +13962,6 @@ const _sfc_main$2 = {
12869
13962
  findMarkAreaByPoint(point, laneId) {
12870
13963
  const chartRef = this.getChartRefByLaneId(laneId);
12871
13964
  if (!chartRef || !chartRef.chart) return -1;
12872
- chartRef.chart.getOption();
12873
13965
  const yValue = point.value[1];
12874
13966
  for (let i = 0; i < this.markAreaLineData.length; i++) {
12875
13967
  const area = this.markAreaLineData[i];
@@ -13330,7 +14422,7 @@ const _sfc_main$2 = {
13330
14422
  for (let i = 0; i < currentMarkAreaData.length; i++) {
13331
14423
  const areaItem = currentMarkAreaData[i];
13332
14424
  if (!areaItem.data || areaItem.data.length < 2) continue;
13333
- if (lineInfo.laneId !== this.activeLaneId && areaItem.type === "markArea")
14425
+ if (!this.isLaneSelected(lineInfo.laneId) && areaItem.type === "markArea")
13334
14426
  continue;
13335
14427
  const hasTimestamp = areaItem.data[0].timestamp;
13336
14428
  const sortedData = hasTimestamp ? [...areaItem.data].sort(
@@ -13589,7 +14681,7 @@ const _sfc_main$2 = {
13589
14681
  const chartRef = this.getChartRefByLaneId(lane.laneId);
13590
14682
  if (!chartRef || !chartRef.chart) continue;
13591
14683
  const chart = chartRef.chart;
13592
- const currentOption = chart.getOption();
14684
+ const currentOption = this.readChartOptionRefs(chart);
13593
14685
  const axisTypeList = this.currentToolBarConfig.axisTypeList || {
13594
14686
  depth: "depth",
13595
14687
  time: "time"
@@ -13626,13 +14718,15 @@ const _sfc_main$2 = {
13626
14718
  right: 2,
13627
14719
  height: `${groupHeight}%`,
13628
14720
  show: false,
13629
- containLabel: false
14721
+ containLabel: !!isDepthLane
13630
14722
  };
13631
14723
  const currentGrid = currentOption.grid && currentOption.grid[gridIndex];
13632
14724
  if (!currentGrid || currentGrid.top !== gridItem.top || currentGrid.height !== gridItem.height) {
13633
14725
  needUpdate.grid = true;
13634
14726
  }
13635
14727
  newGrid.push(gridItem);
14728
+ const groupPlotH = this.getChartPlotHeightPx() * (groupHeight / 100);
14729
+ const ySplitCount = this.isGeomechanical ? this.getDepthLabelSplitCount(groupPlotH) : this.getYGridSplitCount();
13636
14730
  const yAxisItem = {
13637
14731
  gridIndex,
13638
14732
  type: "value",
@@ -13640,16 +14734,39 @@ const _sfc_main$2 = {
13640
14734
  max: groupMax,
13641
14735
  inverse: true,
13642
14736
  boundaryGap: false,
13643
- interval: unifiedInterval
14737
+ scale: false,
14738
+ ...this.getPaperGridAxisStyle(
14739
+ !isDepthLane,
14740
+ ySplitCount
14741
+ )
13644
14742
  };
14743
+ if (this.isGeomechanical) {
14744
+ yAxisItem.splitNumber = ySplitCount;
14745
+ yAxisItem.minInterval = 1;
14746
+ yAxisItem.alignTicks = true;
14747
+ } else if (isDepthLane) {
14748
+ yAxisItem.interval = this.computeDepthTimeAxisInterval(
14749
+ dataGroups,
14750
+ axisKey
14751
+ );
14752
+ } else {
14753
+ yAxisItem.interval = unifiedInterval;
14754
+ }
13645
14755
  if (isDepthLane) {
13646
- yAxisItem.axisLabel = {
13647
- show: this.shouldShowDepthAxisLabel(group, axisKey)
13648
- // 无井深值不显示 0/1。8.24
13649
- };
14756
+ yAxisItem.alignTicks = true;
14757
+ yAxisItem.axisLabel = this.buildDepthTimeAxisLabel(
14758
+ lane,
14759
+ group,
14760
+ false,
14761
+ dataGroups,
14762
+ groupIndex,
14763
+ groupMin,
14764
+ groupMax,
14765
+ axisKey
14766
+ );
13650
14767
  }
13651
14768
  const currentYAxis = currentOption.yAxis && currentOption.yAxis[gridIndex];
13652
- if (!currentYAxis || currentYAxis.min !== groupMin || currentYAxis.max !== groupMax || currentYAxis.interval !== unifiedInterval) {
14769
+ if (!currentYAxis || currentYAxis.min !== groupMin || currentYAxis.max !== groupMax) {
13653
14770
  needUpdate.yAxis = true;
13654
14771
  }
13655
14772
  newYAxis.push(yAxisItem);
@@ -13759,7 +14876,7 @@ const _sfc_main$2 = {
13759
14876
  }
13760
14877
  }
13761
14878
  seriesIndex++;
13762
- if (lineInfo.isUsed === "1" && this.isGeomechanical && lane.laneId === this.activeLaneId) {
14879
+ if (lineInfo.isUsed === "1" && this.isGeomechanical && this.isLaneSelected(lane.laneId)) {
13763
14880
  const boundaryPoints = this.findMissingDataBoundaryPoints(
13764
14881
  this.plotSeries(seriesData, isLogScale)
13765
14882
  );
@@ -13842,10 +14959,6 @@ const _sfc_main$2 = {
13842
14959
  } finally {
13843
14960
  this.isUpdatingChart = false;
13844
14961
  this.scheduleConnectAllCharts();
13845
- if (this.trendLineManager) {
13846
- this.trendLineManager.scheduleZoomRender();
13847
- this.$nextTick(() => this.trendLineManager.scheduleZoomRender());
13848
- }
13849
14962
  }
13850
14963
  },
13851
14964
  // 清空所有图表实例
@@ -13871,6 +14984,7 @@ const _sfc_main$2 = {
13871
14984
  this.markAreaLineData = [];
13872
14985
  this.laneSelectedPointPairs = {};
13873
14986
  this.activeLaneId = null;
14987
+ this.activeLaneIds = [];
13874
14988
  this.lineRange = {};
13875
14989
  this.pendingRequestData = null;
13876
14990
  this.initialRealTimeData = {};
@@ -13901,7 +15015,7 @@ const _sfc_main$2 = {
13901
15015
  deduplicateByUniqueKey(existingData, newData, uniqueKey, options = {}) {
13902
15016
  const { sortOrder = "asc", isSort = true } = options;
13903
15017
  const newDataArr = Array.isArray(newData) ? newData : [newData];
13904
- let resultData = JSON.parse(JSON.stringify(existingData));
15018
+ const resultData = Array.isArray(existingData) ? existingData.slice() : [];
13905
15019
  const existingKeys = new Set(resultData.map((item) => item[uniqueKey]));
13906
15020
  newDataArr.forEach((newItem) => {
13907
15021
  if (!newItem || !newItem[uniqueKey]) return;
@@ -14361,7 +15475,8 @@ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
14361
15475
  visibleData: $data.visibleData,
14362
15476
  headerData: $data.headerData,
14363
15477
  lineRange: $data.lineRange,
14364
- activeLaneId: $data.activeLaneId
15478
+ activeLaneId: $data.activeLaneId,
15479
+ activeLaneIds: $data.activeLaneIds
14365
15480
  }), createSlots({ _: 2 }, [
14366
15481
  renderList($props.headerSlotName, (lane) => {
14367
15482
  return {
@@ -14461,7 +15576,7 @@ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
14461
15576
  ])
14462
15577
  };
14463
15578
  })
14464
- ]), 1040, ["onTemplateChange", "onLineChange", "onLaneWidthChange", "onParamsChange", "visibleData", "headerData", "lineRange", "activeLaneId"])
15579
+ ]), 1040, ["onTemplateChange", "onLineChange", "onLaneWidthChange", "onParamsChange", "visibleData", "headerData", "lineRange", "activeLaneId", "activeLaneIds"])
14465
15580
  ], 512),
14466
15581
  withDirectives(createElementVNode("div", _hoisted_2$2, null, 512), [
14467
15582
  [vShow, $data.crosshairVisible]
@@ -14504,7 +15619,7 @@ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
14504
15619
  ], 40, _hoisted_3$1)) : (openBlock(), createElementBlock("div", {
14505
15620
  key: 1,
14506
15621
  class: "context-menu-item",
14507
- onClick: ($event) => $options.handleContextMenuItemClick(item)
15622
+ onClick: withModifiers(($event) => $options.handleContextMenuItemClick(item), ["stop"])
14508
15623
  }, [
14509
15624
  createElementVNode("span", null, toDisplayString(item.label), 1)
14510
15625
  ], 8, _hoisted_5))
@@ -14516,7 +15631,7 @@ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
14516
15631
  ])
14517
15632
  ], 544);
14518
15633
  }
14519
- const chartContainer = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["render", _sfc_render$2], ["__scopeId", "data-v-aace7d52"]]);
15634
+ const chartContainer = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["render", _sfc_render$2], ["__scopeId", "data-v-9a816c60"]]);
14520
15635
  const _sfc_main$1 = {
14521
15636
  name: "ParameterPanel",
14522
15637
  props: {
@@ -15662,6 +16777,12 @@ const _sfc_main = {
15662
16777
  this.$refs.chartContainer.resetAllTrendLines();
15663
16778
  }
15664
16779
  },
16780
+ // 退出编辑时清掉泳道选中。8.25
16781
+ clearActiveLane() {
16782
+ if (this.$refs.chartContainer && this.$refs.chartContainer.clearActiveLane) {
16783
+ this.$refs.chartContainer.clearActiveLane();
16784
+ }
16785
+ },
15665
16786
  // 新增:更新form数据缓存
15666
16787
  updateForm(content) {
15667
16788
  let key = cloneDeep(content.key);
@@ -16031,7 +17152,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
16031
17152
  ])])) : createCommentVNode("", true)
16032
17153
  ]);
16033
17154
  }
16034
- const FrameLayoutComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-76b2334b"]]);
17155
+ const FrameLayoutComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-51f78d28"]]);
16035
17156
  KdLaneContainerComponent.install = function(app) {
16036
17157
  app.component(KdLaneContainerComponent.name, KdLaneContainerComponent);
16037
17158
  };