kd-lane-chart-v3 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -2659,7 +2659,7 @@ function findLogTrendSeries(chart, paramId) {
2659
2659
  const type = sm && sm.subType || (Array.isArray(opt.type) ? opt.type[0] : opt.type);
2660
2660
  if (type !== "line") continue;
2661
2661
  const name = Array.isArray(sm.name) ? sm.name[0] : sm.name;
2662
- if (name === "helper" || name === "log-grid") continue;
2662
+ if (name === "helper" || name === "log-grid" || name === "trend") continue;
2663
2663
  const pid = Array.isArray(opt.paramId) ? opt.paramId[0] : opt.paramId;
2664
2664
  if (String(pid) === String(paramId)) return opt;
2665
2665
  }
@@ -2718,6 +2718,17 @@ function logTrendDataToPixel(chart, paramId, lg, depth) {
2718
2718
  const series = findLogTrendSeries(chart, paramId);
2719
2719
  const xAxisIndex = series ? axisIndexOf$1(series.xAxisIndex) : 0;
2720
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
+ }
2721
2732
  try {
2722
2733
  const px = chart.convertToPixel({ xAxisIndex, yAxisIndex }, [lg, depth]);
2723
2734
  if (px && Number.isFinite(px[0]) && Number.isFinite(px[1])) return px;
@@ -3344,7 +3355,6 @@ const TREND_MIRROR_POINT_STYLE = {
3344
3355
  lineWidth: 1
3345
3356
  };
3346
3357
  const TREND_LINE_Z = 9999;
3347
- const TREND_POINT_Z = 99999;
3348
3358
  const DEPTH_EDGE_SNAP = 0.2;
3349
3359
  const TREND_CLICK_DELAY_MS = 240;
3350
3360
  const TREND_HIT_RADIUS = 14;
@@ -3371,16 +3381,42 @@ function mapGammaLineApiType(lithType, lineRole) {
3371
3381
  return mainType === "grShale" ? "grClean" : "grShale";
3372
3382
  }
3373
3383
  function resolveTrendParamId(type) {
3374
- const t = String(type || "").trim();
3384
+ let t = String(type || "").trim();
3385
+ if (t.startsWith(":")) t = t.slice(1);
3375
3386
  if (t === "grShale" || t === "grClean") return "8137";
3376
3387
  return t;
3377
3388
  }
3389
+ function inferCalcTypeFromSegments(segments) {
3390
+ const segs = segments || [];
3391
+ if (!segs.some((s) => (s.knots || []).length)) return null;
3392
+ const hasMirror = segs.some((s) => {
3393
+ if (s.lithType === "sandstone") return true;
3394
+ return (s.knots || []).some((k) => k.mirrorValue != null && Number.isFinite(Number(k.mirrorValue)));
3395
+ });
3396
+ return hasMirror ? "moxing" : "hand";
3397
+ }
3398
+ function inferTrendSaveMeta(line, segments) {
3399
+ const segs = segments || [];
3400
+ const hasKnots = segs.some((s) => (s.knots || []).length > 0);
3401
+ if (!hasKnots) return { isDouble: false, calcType: void 0 };
3402
+ const inferredCalc = inferCalcTypeFromSegments(segs);
3403
+ const calcType = line && line.calcType || inferredCalc;
3404
+ const hasMirror = inferredCalc === "moxing";
3405
+ const is8137 = line && String(line.paramId) === "8137";
3406
+ const isDouble = isLineDouble(line) || hasMirror || is8137 && !!calcType;
3407
+ return { isDouble: !!isDouble, calcType };
3408
+ }
3378
3409
  function newSegmentId() {
3379
3410
  return `seg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
3380
3411
  }
3381
3412
  function makeTrendKey(laneId, paramId) {
3382
3413
  return `${laneId}::${paramId}`;
3383
3414
  }
3415
+ function paramIdFromTrendKey(key) {
3416
+ const sep = String(key || "").lastIndexOf("::");
3417
+ if (sep < 0) return String(key || "");
3418
+ return key.slice(sep + 2);
3419
+ }
3384
3420
  function cloneSegments(segments) {
3385
3421
  return (segments || []).map((seg) => {
3386
3422
  const next = {
@@ -3699,7 +3735,7 @@ function listLineSeries(model) {
3699
3735
  const type = sm && sm.subType || seriesTypeName(opt);
3700
3736
  if (type !== "line") continue;
3701
3737
  const name = optionScalar(sm && sm.name) || seriesDisplayName(opt);
3702
- if (name === "helper" || name === "log-grid") continue;
3738
+ if (name === "helper" || name === "log-grid" || name === "trend") continue;
3703
3739
  out.push({
3704
3740
  seriesIndex: sm.seriesIndex,
3705
3741
  xAxisIndex: axisIndexOf(opt.xAxisIndex),
@@ -3738,20 +3774,27 @@ function findSeriesForDepth(chart, paramId, depth) {
3738
3774
  function eventOffsetInChart(chart, evt) {
3739
3775
  var _a, _b;
3740
3776
  const zr = chart.getZr();
3741
- const rect = (_b = (_a = chart.getDom()) == null ? void 0 : _a.getBoundingClientRect) == null ? void 0 : _b.call(_a);
3742
- let offsetX = 0;
3743
- let offsetY = 0;
3777
+ const clamp = (x, y) => ({
3778
+ offsetX: Math.max(0, Math.min(x, zr.getWidth())),
3779
+ offsetY: Math.max(0, Math.min(y, zr.getHeight()))
3780
+ });
3781
+ if (evt && evt.event && Number.isFinite(evt.offsetX) && Number.isFinite(evt.offsetY)) {
3782
+ return clamp(evt.offsetX, evt.offsetY);
3783
+ }
3744
3784
  const native = (evt == null ? void 0 : evt.event) || evt;
3785
+ let el = null;
3786
+ try {
3787
+ el = zr.painter && zr.painter.getViewportRoot && zr.painter.getViewportRoot();
3788
+ } catch (e) {
3789
+ }
3790
+ const rect = (_b = (_a = el || chart.getDom()) == null ? void 0 : _a.getBoundingClientRect) == null ? void 0 : _b.call(_a);
3745
3791
  if (rect && Number.isFinite(native == null ? void 0 : native.clientX) && Number.isFinite(native == null ? void 0 : native.clientY)) {
3746
- offsetX = native.clientX - rect.left;
3747
- offsetY = native.clientY - rect.top;
3748
- } else if (Number.isFinite(evt == null ? void 0 : evt.offsetX) && Number.isFinite(evt == null ? void 0 : evt.offsetY)) {
3749
- offsetX = evt.offsetX;
3750
- offsetY = evt.offsetY;
3792
+ return clamp(native.clientX - rect.left, native.clientY - rect.top);
3793
+ }
3794
+ if (Number.isFinite(evt == null ? void 0 : evt.offsetX) && Number.isFinite(evt == null ? void 0 : evt.offsetY)) {
3795
+ return clamp(evt.offsetX, evt.offsetY);
3751
3796
  }
3752
- offsetX = Math.max(0, Math.min(offsetX, zr.getWidth()));
3753
- offsetY = Math.max(0, Math.min(offsetY, zr.getHeight()));
3754
- return { offsetX, offsetY };
3797
+ return clamp(0, 0);
3755
3798
  }
3756
3799
  function isPointerInAnyGrid(chart, offsetX, offsetY) {
3757
3800
  const gridCount = modelGridCount(chartModel(chart));
@@ -3819,8 +3862,48 @@ function pixelToData(chart, paramId, offsetX, offsetY, loose = false) {
3819
3862
  }
3820
3863
  return null;
3821
3864
  }
3865
+ function axisPairToPixel(chart, xAxisIndex, yAxisIndex, value, depth) {
3866
+ try {
3867
+ const model = chartModel(chart);
3868
+ if (!model) return null;
3869
+ const xComp = model.getComponent("xAxis", xAxisIndex || 0);
3870
+ const yComp = model.getComponent("yAxis", yAxisIndex || 0);
3871
+ const xAxis = xComp && xComp.axis;
3872
+ const yAxis = yComp && yComp.axis;
3873
+ if (!xAxis || !yAxis) return null;
3874
+ const x = xAxis.toGlobalCoord(xAxis.dataToCoord(value, true));
3875
+ const y = yAxis.toGlobalCoord(yAxis.dataToCoord(depth, true));
3876
+ if (Number.isFinite(x) && Number.isFinite(y)) return [x, y];
3877
+ } catch (e) {
3878
+ }
3879
+ return null;
3880
+ }
3881
+ function axisPairFromPixel(chart, xAxisIndex, yAxisIndex, offsetX, offsetY) {
3882
+ try {
3883
+ const model = chartModel(chart);
3884
+ if (!model) return null;
3885
+ const xComp = model.getComponent("xAxis", xAxisIndex || 0);
3886
+ const yComp = model.getComponent("yAxis", yAxisIndex || 0);
3887
+ const xAxis = xComp && xComp.axis;
3888
+ const yAxis = yComp && yComp.axis;
3889
+ if (!xAxis || !yAxis) return null;
3890
+ const value = xAxis.coordToData(xAxis.toLocalCoord(offsetX), true);
3891
+ const depth = yAxis.coordToData(yAxis.toLocalCoord(offsetY), true);
3892
+ if (!Number.isFinite(value) || !Number.isFinite(depth)) return null;
3893
+ return {
3894
+ value,
3895
+ depth,
3896
+ xAxisIndex: xAxisIndex || 0,
3897
+ yAxisIndex: yAxisIndex || 0
3898
+ };
3899
+ } catch (e) {
3900
+ return null;
3901
+ }
3902
+ }
3822
3903
  function dataToPixel(chart, paramId, value, depth) {
3823
3904
  const finder = findSeriesForDepth(chart, paramId, depth);
3905
+ const live = axisPairToPixel(chart, finder.xAxisIndex, finder.yAxisIndex, value, depth);
3906
+ if (live) return live;
3824
3907
  const finders = [
3825
3908
  { xAxisIndex: finder.xAxisIndex, yAxisIndex: finder.yAxisIndex },
3826
3909
  { seriesIndex: finder.seriesIndex },
@@ -3871,49 +3954,104 @@ class TrendLineManager {
3871
3954
  this.dragSession = null;
3872
3955
  this._panLockedByHover = false;
3873
3956
  this._raf = null;
3874
- this._zoomRaf = null;
3875
- this._zoomPending = false;
3876
- this._zoomEndTimer = null;
3877
- this._graphicIds = /* @__PURE__ */ Object.create(null);
3878
- this._zrElCache = /* @__PURE__ */ new WeakMap();
3957
+ this._seriesIds = /* @__PURE__ */ Object.create(null);
3958
+ this._graphicCleared = /* @__PURE__ */ new WeakSet();
3879
3959
  this._ignoreClickUntil = 0;
3960
+ this._destroyed = false;
3880
3961
  }
3881
3962
  destroy() {
3963
+ this._destroyed = true;
3882
3964
  this._clearClickTimer();
3883
- this._endDrag();
3884
- Array.from(this.binds.keys()).forEach((laneId) => this.unbindChart(laneId));
3965
+ try {
3966
+ this._unbindDragWindow();
3967
+ } catch (e) {
3968
+ }
3969
+ this._panLockedByHover = false;
3970
+ Array.from(this.binds.keys()).forEach((laneId) => {
3971
+ try {
3972
+ this.unbindChart(laneId);
3973
+ } catch (e) {
3974
+ }
3975
+ });
3885
3976
  if (this._raf) {
3886
3977
  cancelAnimationFrame(this._raf);
3887
3978
  this._raf = null;
3888
3979
  }
3889
- if (this._zoomRaf) {
3890
- cancelAnimationFrame(this._zoomRaf);
3891
- this._zoomRaf = null;
3892
- }
3893
- if (this._zoomEndTimer) {
3894
- clearTimeout(this._zoomEndTimer);
3895
- this._zoomEndTimer = null;
3896
- }
3897
3980
  this.store = /* @__PURE__ */ Object.create(null);
3898
- this._graphicIds = /* @__PURE__ */ Object.create(null);
3981
+ this._seriesIds = /* @__PURE__ */ Object.create(null);
3982
+ this.binds.clear();
3983
+ this.host = null;
3984
+ }
3985
+ _alive() {
3986
+ return !this._destroyed && !!this.host;
3899
3987
  }
3900
3988
  /** 从模板 line.trendSegments 回显;已有 store 的曲线不覆盖,避免编辑中被模板刷新冲掉。8.21 */
3901
3989
  hydrateFromTemplate(template) {
3990
+ if (!this._alive()) return null;
3991
+ const hasEcho = Array.isArray(this.host.lineListData) && this.host.lineListData.length;
3902
3992
  const lanes = template && template.lanes || [];
3993
+ const alive = /* @__PURE__ */ new Set();
3994
+ const lanesToRefresh = /* @__PURE__ */ new Set();
3903
3995
  lanes.forEach((lane) => {
3904
3996
  (lane.lines || []).forEach((line) => {
3905
3997
  if (!lane.laneId || !line.paramId) return;
3906
3998
  const key = makeTrendKey(lane.laneId, line.paramId);
3999
+ alive.add(key);
4000
+ const bucket = this.store[key];
4001
+ if (bucket && this._hasTrendKnots(bucket.segments)) return;
4002
+ const fromLaneId = this._tryMigrateTrendStore(line.paramId, key);
4003
+ if (fromLaneId) {
4004
+ lanesToRefresh.add(fromLaneId);
4005
+ lanesToRefresh.add(lane.laneId);
4006
+ return;
4007
+ }
3907
4008
  if (this.store[key]) return;
3908
- if (line.trendSegments && line.trendSegments.length) {
3909
- this.store[key] = { segments: this._prepareEchoSegments(line.paramId, line.trendSegments) };
3910
- } else {
4009
+ if (!hasEcho || !line.trendSegments || !line.trendSegments.length) {
3911
4010
  this.store[key] = { segments: [] };
4011
+ return;
3912
4012
  }
4013
+ this.store[key] = { segments: this._prepareEchoSegments(line.paramId, line.trendSegments) };
3913
4014
  });
3914
4015
  });
4016
+ Object.keys(this.store).forEach((key) => {
4017
+ if (!alive.has(key)) {
4018
+ const laneId = key.split("::")[0];
4019
+ if (laneId && this._hasTrendKnots(this.store[key] && this.store[key].segments)) {
4020
+ lanesToRefresh.add(laneId);
4021
+ }
4022
+ delete this.store[key];
4023
+ }
4024
+ });
4025
+ if (lanesToRefresh.size) {
4026
+ return lanesToRefresh;
4027
+ }
4028
+ return null;
4029
+ }
4030
+ /** 段内是否已有控制点(可画线)。8.26 */
4031
+ _hasTrendKnots(segments) {
4032
+ return (segments || []).some((s) => (s.knots || []).length > 0);
4033
+ }
4034
+ /**
4035
+ * 曲线表头跨泳道拖拽:把旧 laneId::paramId 的 store 迁到新泳道。
4036
+ * @returns {string|null} 迁出方 laneId,无迁移则 null
4037
+ */
4038
+ _tryMigrateTrendStore(paramId, newKey) {
4039
+ const existing = this.store[newKey];
4040
+ if (existing && this._hasTrendKnots(existing.segments)) return null;
4041
+ const pid = String(paramId);
4042
+ const suffix = `::${pid}`;
4043
+ for (const oldKey of Object.keys(this.store)) {
4044
+ if (oldKey === newKey || !oldKey.endsWith(suffix)) continue;
4045
+ const bucket = this.store[oldKey];
4046
+ if (!bucket || !this._hasTrendKnots(bucket.segments)) continue;
4047
+ this.store[newKey] = bucket;
4048
+ if (this.activeKey === oldKey) this.activeKey = newKey;
4049
+ return oldKey.split("::")[0] || null;
4050
+ }
4051
+ return null;
3915
4052
  }
3916
4053
  getChart(laneId) {
4054
+ if (!this._alive()) return null;
3917
4055
  const ref = this.host.getChartRefByLaneId(laneId);
3918
4056
  const chart = ref && ref.chart;
3919
4057
  if (!chart || chart.isDisposed()) return null;
@@ -3921,6 +4059,7 @@ class TrendLineManager {
3921
4059
  }
3922
4060
  /** isDraw 可编辑,或已有控制点(只读回显)的曲线。8.21 */
3923
4061
  listDrawableTargets() {
4062
+ if (!this._alive()) return [];
3924
4063
  const lanes = this.host.currentTemplate && this.host.currentTemplate.lanes || [];
3925
4064
  const list = [];
3926
4065
  lanes.forEach((lane) => {
@@ -3943,7 +4082,9 @@ class TrendLineManager {
3943
4082
  }
3944
4083
  /** 只认当前 isDraw 曲线。8107 和 8011 不在同一泳道,切到 8011 后不能还按 8107 的 activeKey 消费点击。8.21 */
3945
4084
  getTargetByLane(laneId) {
3946
- const targets = this.listDrawableTargets().filter((t) => t.laneId === laneId);
4085
+ const targets = this.listDrawableTargets().filter(
4086
+ (t) => String(t.laneId) === String(laneId)
4087
+ );
3947
4088
  if (!targets.length) return null;
3948
4089
  if (this.activeKey) {
3949
4090
  const active = targets.find((t) => t.key === this.activeKey && t.editable);
@@ -3982,45 +4123,62 @@ class TrendLineManager {
3982
4123
  /** 捕获阶段监听 mousedown,保证拖控制点比图表其它逻辑先拿到。8.21 */
3983
4124
  bindChart(laneId, chart) {
3984
4125
  this.unbindChart(laneId);
3985
- if (!chart || chart.isDisposed()) return;
3986
- const onMouseDown = (e) => this.onMouseDown(e, laneId);
4126
+ if (!this._alive() || !chart || chart.isDisposed()) return;
3987
4127
  const onZrDown = (e) => this.onZrMouseDown(e, laneId);
3988
4128
  const onZrMove = (e) => this.onZrHover(e, laneId);
3989
4129
  const onDblClick = (e) => this.handleDblClick(e, laneId);
3990
- const dom = chart.getDom();
3991
- if (dom) dom.addEventListener("mousedown", onMouseDown, true);
3992
4130
  const zr = chart.getZr();
3993
4131
  zr.on("mousedown", onZrDown);
3994
4132
  zr.on("mousemove", onZrMove);
3995
4133
  zr.on("dblclick", onDblClick);
3996
4134
  const onZoom = () => {
4135
+ if (this.host && this.host._isPanning) return;
3997
4136
  if (this.host && typeof this.host.startChartZoom === "function") {
3998
4137
  this.host.startChartZoom();
3999
4138
  }
4000
- this.scheduleZoomRender();
4001
4139
  };
4002
4140
  if (typeof chart.on === "function") chart.on("datazoom", onZoom);
4003
- this.binds.set(laneId, { chart, onMouseDown, onZrDown, onZrMove, onDblClick, onZoom, dom });
4141
+ this.binds.set(laneId, { chart, onZrDown, onZrMove, onDblClick, onZoom, dom: chart.getDom() });
4004
4142
  this.updateCursor(laneId);
4005
4143
  }
4006
4144
  unbindChart(laneId) {
4007
4145
  const bind = this.binds.get(laneId);
4008
4146
  if (!bind) return;
4147
+ const session = this.dragSession;
4148
+ if (session && (session.chart === bind.chart || session.target && session.target.laneId === laneId)) {
4149
+ this._unbindDragWindow();
4150
+ }
4009
4151
  if (bind.dom && bind.onMouseDown) {
4010
4152
  bind.dom.removeEventListener("mousedown", bind.onMouseDown, true);
4011
4153
  }
4012
4154
  if (bind.dom && bind.onZoom) {
4013
4155
  bind.dom.removeEventListener("wheel", bind.onZoom);
4014
4156
  }
4015
- if (bind.chart && !bind.chart.isDisposed()) {
4016
- const zr = bind.chart.getZr();
4017
- if (bind.onZrDown) zr.off("mousedown", bind.onZrDown);
4018
- if (bind.onZrMove) zr.off("mousemove", bind.onZrMove);
4019
- if (bind.onDblClick) zr.off("dblclick", bind.onDblClick);
4020
- if (bind.onZoom) {
4021
- zr.off("mousewheel", bind.onZoom);
4022
- if (typeof bind.chart.off === "function") bind.chart.off("datazoom", bind.onZoom);
4157
+ const chart = bind.chart;
4158
+ try {
4159
+ if (chart && typeof chart.getZr === "function") {
4160
+ const zr = chart.getZr();
4161
+ if (zr) {
4162
+ if (bind.onZrDown) zr.off("mousedown", bind.onZrDown);
4163
+ if (bind.onZrMove) zr.off("mousemove", bind.onZrMove);
4164
+ if (bind.onDblClick) zr.off("dblclick", bind.onDblClick);
4165
+ if (bind.onZoom) zr.off("mousewheel", bind.onZoom);
4166
+ }
4023
4167
  }
4168
+ if (chart && bind.onZoom && typeof chart.off === "function") {
4169
+ chart.off("datazoom", bind.onZoom);
4170
+ }
4171
+ } catch (e) {
4172
+ }
4173
+ delete this._seriesIds[laneId];
4174
+ if (bind) {
4175
+ bind.chart = null;
4176
+ bind.dom = null;
4177
+ bind.onZrDown = null;
4178
+ bind.onZrMove = null;
4179
+ bind.onDblClick = null;
4180
+ bind.onZoom = null;
4181
+ bind.onMouseDown = null;
4024
4182
  }
4025
4183
  this.binds.delete(laneId);
4026
4184
  }
@@ -4041,10 +4199,11 @@ class TrendLineManager {
4041
4199
  /** 图表重绘后对齐叠加层;拖拽中跳过,避免和 _onDragMove 抢 setOption。8.21 */
4042
4200
  // 只排一帧,不再连刷两次 renderAll(会闪)。8.24
4043
4201
  scheduleRender() {
4044
- if (this.dragSession) return;
4202
+ if (!this._alive() || this.dragSession) return;
4045
4203
  if (this._raf) return;
4046
4204
  this._raf = requestAnimationFrame(() => {
4047
4205
  this._raf = null;
4206
+ if (!this._alive()) return;
4048
4207
  this.renderAll();
4049
4208
  });
4050
4209
  }
@@ -4057,385 +4216,342 @@ class TrendLineManager {
4057
4216
  }
4058
4217
  return false;
4059
4218
  }
4060
- /** 缩放过程用 ZR 就地改像素,结束后再整层重画,避免 replaceMerge 把线冲掉。8.22 */
4219
+ // 趋势线已是笛卡尔 series,dataZoom 带着走;这些入口留给旧调用方,不再像素跟帧。8.25
4061
4220
  scheduleZoomRender() {
4062
- if (this.dragSession || !this.hasAnyKnots()) return;
4063
- this._zoomPending = true;
4064
- if (this._zoomEndTimer) clearTimeout(this._zoomEndTimer);
4065
- this._zoomEndTimer = setTimeout(() => {
4066
- this._zoomPending = false;
4067
- if (this._zoomRaf) {
4068
- cancelAnimationFrame(this._zoomRaf);
4069
- this._zoomRaf = null;
4070
- }
4071
- this.scheduleRender();
4072
- }, 140);
4073
- if (this._zoomRaf) return;
4074
- const tick = () => {
4075
- if (this.dragSession) {
4076
- this._zoomRaf = null;
4077
- return;
4078
- }
4079
- this._patchAllPositions();
4080
- if (this._zoomPending) {
4081
- this._zoomRaf = requestAnimationFrame(tick);
4082
- } else {
4083
- this._zoomRaf = null;
4084
- }
4085
- };
4086
- this._zoomRaf = requestAnimationFrame(tick);
4087
4221
  }
4088
- /** 缩放帧:优先 ZR setShape,元素被冲掉时才回退 renderLane。8.22 */
4089
- _patchAllPositions() {
4090
- const lanes = this.host.currentTemplate && this.host.currentTemplate.lanes || [];
4091
- lanes.forEach((lane) => this._patchLanePositions(lane.laneId));
4222
+ beginPanFollow() {
4092
4223
  }
4093
- _patchLanePositions(laneId) {
4094
- const chart = this.getChart(laneId);
4095
- if (!chart) return;
4096
- const targets = this.listDrawableTargets().filter((t) => t.laneId === laneId);
4097
- const hasKnots = targets.some((t) => (t.segments || []).some((s) => (s.knots || []).length));
4098
- if (!hasKnots) return;
4099
- const patches = [];
4100
- targets.forEach((target) => {
4101
- patches.push(...this._buildPatches(chart, target));
4102
- });
4103
- if (!patches.length) return;
4104
- if (!this._applyPatchesViaZr(chart, patches) && !this._zoomPending) {
4105
- this.renderLane(laneId);
4106
- }
4224
+ tickPanFollow() {
4107
4225
  }
4108
- _buildPatches(chart, target) {
4109
- const patches = [];
4110
- const { paramId, key, segments, line } = target;
4111
- const canMirror = this._canMirror(line, segments);
4112
- segments.forEach((seg) => {
4113
- const knots = seg.knots || [];
4114
- for (let i = 0; i < knots.length - 1; i++) {
4115
- const p1 = knots[i];
4116
- const p2 = knots[i + 1];
4117
- const px1 = this._knotPixel(chart, target, p1);
4118
- const px2 = this._knotPixel(chart, target, p2);
4119
- if (!px1 || !px2) continue;
4120
- patches.push({
4121
- id: `trend_line_${key}_${seg.id}_${i}`,
4122
- shape: { x1: px1[0], y1: px1[1], x2: px2[0], y2: px2[1] }
4123
- });
4124
- if (canMirror) {
4125
- const mx1 = this._knotPixel(chart, target, p1, "mirror");
4126
- const mx2 = this._knotPixel(chart, target, p2, "mirror");
4127
- if (mx1 && mx2) {
4128
- patches.push({
4129
- id: `trend_mirror_${key}_${seg.id}_${i}`,
4130
- shape: { x1: mx1[0], y1: mx1[1], x2: mx2[0], y2: mx2[1] }
4131
- });
4132
- }
4133
- }
4134
- }
4135
- knots.forEach((knot, i) => {
4136
- const px = this._knotPixel(chart, target, knot);
4137
- if (px) {
4138
- patches.push({
4139
- id: `trend_point_${key}_${seg.id}_${i}`,
4140
- shape: { cx: px[0], cy: px[1], r: 4 }
4141
- });
4142
- }
4143
- if (canMirror) {
4144
- const mx = this._knotPixel(chart, target, knot, "mirror");
4145
- if (mx) {
4146
- patches.push({
4147
- id: `trend_mirror_point_${key}_${seg.id}_${i}`,
4148
- shape: { cx: mx[0], cy: mx[1], r: 4 }
4149
- });
4150
- }
4151
- }
4152
- });
4153
- });
4154
- return patches;
4226
+ endPanFollow() {
4155
4227
  }
4156
- _invalidateZrElCache(chart) {
4157
- if (chart && this._zrElCache) this._zrElCache.delete(chart);
4228
+ endFollowView() {
4158
4229
  }
4159
- _indexGraphicZrEls(chart, map) {
4160
- const zr = chart.getZr();
4161
- if (!zr || !zr.storage || typeof zr.storage.traverse !== "function") return;
4162
- zr.storage.traverse((el) => {
4163
- const elId = el.id ?? el.anid ?? el.name ?? el.__ecGraphicId;
4164
- if (elId == null) return;
4165
- map.set(String(elId), el);
4166
- });
4230
+ _patchAllPositions() {
4167
4231
  }
4168
- _findGraphicZrEl(chart, id) {
4169
- if (!chart || id == null) return null;
4170
- let map = this._zrElCache.get(chart);
4171
- if (!map) {
4172
- map = /* @__PURE__ */ new Map();
4173
- this._zrElCache.set(chart, map);
4174
- this._indexGraphicZrEls(chart, map);
4175
- }
4176
- const key = String(id);
4177
- if (map.has(key)) return map.get(key);
4178
- map.clear();
4179
- this._indexGraphicZrEls(chart, map);
4180
- if (map.has(key)) return map.get(key);
4181
- for (const [elId, el] of map) {
4182
- if (elId.endsWith(key)) return el;
4232
+ _isTrendSeriesOpt(series) {
4233
+ if (!series) return false;
4234
+ const name = Array.isArray(series.name) ? series.name[0] : series.name;
4235
+ if (name === "trend") return true;
4236
+ const id = String(series.id || "");
4237
+ return id.indexOf("trend_") === 0;
4238
+ }
4239
+ _findCurveSeriesInOption(option, paramId) {
4240
+ const list = option && option.series || [];
4241
+ for (let i = 0; i < list.length; i++) {
4242
+ const s = list[i];
4243
+ if (!s || this._isTrendSeriesOpt(s)) continue;
4244
+ const type = Array.isArray(s.type) ? s.type[0] : s.type;
4245
+ if (type && type !== "line") continue;
4246
+ const name = Array.isArray(s.name) ? s.name[0] : s.name;
4247
+ if (name === "helper" || name === "log-grid" || name === "推荐值") continue;
4248
+ if (String(s.paramId) === String(paramId)) return s;
4183
4249
  }
4184
4250
  return null;
4185
4251
  }
4186
- _graphicPixel(el) {
4187
- const cx = el.shape && el.shape.cx || 0;
4188
- const cy = el.shape && el.shape.cy || 0;
4189
- return [cx + (el.x || 0), cy + (el.y || 0)];
4252
+ _firstKnotDepth(target) {
4253
+ const segs = target && target.segments || [];
4254
+ for (let i = 0; i < segs.length; i++) {
4255
+ const knot = (segs[i].knots || [])[0];
4256
+ if (knot && Number.isFinite(Number(knot.depth))) return Number(knot.depth);
4257
+ }
4258
+ return 0;
4190
4259
  }
4191
- _applyKnotPixel(chart, target, knot, role, offsetX, offsetY) {
4192
- const data = this._pixelToTrend(chart, target, offsetX, offsetY, true);
4193
- if (!data) return false;
4194
- const range = this._drawValueRange(target, chart, data.depth);
4195
- let depth = snapDepthToEdge(data.depth, range.depthMin, range.depthMax, DEPTH_EDGE_SNAP);
4196
- if (data.value == null) return false;
4197
- const clickX = Math.max(range.valueMin, Math.min(range.valueMax, data.value));
4198
- const all = this._allKnots(target.key);
4199
- const idx = all.indexOf(knot);
4200
- if (idx > 0) depth = Math.max(depth, all[idx - 1].depth);
4201
- if (idx >= 0 && idx < all.length - 1) depth = Math.min(depth, all[idx + 1].depth);
4202
- knot.depth = parseFloat(depth.toFixed(3));
4203
- this._applyPointerValue(chart, target, knot, knot.depth, clickX, role);
4204
- return true;
4260
+ _curveAxisOf(chart, target) {
4261
+ const info = findSeriesForDepth(chart, target.paramId, this._firstKnotDepth(target));
4262
+ return {
4263
+ xAxisIndex: info && info.xAxisIndex != null ? info.xAxisIndex : 0,
4264
+ yAxisIndex: info && info.yAxisIndex != null ? info.yAxisIndex : 0
4265
+ };
4205
4266
  }
4206
- /** 控制点交给 ZR 拖,不再和 dataZoom 抢 DOM mousedown。8.22 */
4207
- _bindPointInteractions(chart, laneId) {
4208
- const targets = this.listDrawableTargets().filter((t) => t.laneId === laneId && t.editable);
4209
- targets.forEach((target) => {
4210
- const canMirror = this._canMirror(target.line, target.segments);
4211
- (target.segments || []).forEach((seg) => {
4212
- (seg.knots || []).forEach((knot, i) => {
4213
- this._bindOnePointEl(
4214
- chart,
4215
- target,
4216
- knot,
4217
- `trend_point_${target.key}_${seg.id}_${i}`,
4218
- "main"
4219
- );
4220
- if (canMirror && knot.mirrorValue != null && Number.isFinite(Number(knot.mirrorValue))) {
4221
- this._bindOnePointEl(
4222
- chart,
4223
- target,
4224
- knot,
4225
- `trend_mirror_point_${target.key}_${seg.id}_${i}`,
4226
- "mirror"
4227
- );
4228
- }
4267
+ _buildOneTrendSeries(id, data, lineStyle, pointStyle, xAxisIndex, yAxisIndex) {
4268
+ return {
4269
+ id,
4270
+ name: "trend",
4271
+ type: "line",
4272
+ data,
4273
+ xAxisIndex,
4274
+ yAxisIndex,
4275
+ clip: true,
4276
+ animation: false,
4277
+ silent: true,
4278
+ large: false,
4279
+ z: TREND_LINE_Z,
4280
+ zlevel: 0,
4281
+ symbol: "circle",
4282
+ symbolSize: 8,
4283
+ showSymbol: true,
4284
+ hoverAnimation: false,
4285
+ legendHoverLink: false,
4286
+ connectNulls: false,
4287
+ tooltip: { show: false },
4288
+ emphasis: { disabled: true },
4289
+ lineStyle: {
4290
+ color: lineStyle.stroke,
4291
+ width: lineStyle.lineWidth,
4292
+ type: lineStyle.lineDash || "solid"
4293
+ },
4294
+ itemStyle: {
4295
+ color: pointStyle.fill,
4296
+ borderColor: pointStyle.stroke,
4297
+ borderWidth: pointStyle.lineWidth
4298
+ }
4299
+ };
4300
+ }
4301
+ _knotPoint(x, depth) {
4302
+ return { value: [x, depth], symbol: "circle", symbolSize: 8 };
4303
+ }
4304
+ _segSeriesData(knots, role) {
4305
+ const raw = [];
4306
+ for (let i = 0; i < (knots || []).length; i++) {
4307
+ const knot = knots[i];
4308
+ const x = this._knotDrawX(knot, role);
4309
+ const depth = Number(knot && knot.depth);
4310
+ if (x == null || !Number.isFinite(depth)) continue;
4311
+ raw.push([x, depth]);
4312
+ }
4313
+ if (raw.length < 2) {
4314
+ return raw.map((p) => this._knotPoint(p[0], p[1]));
4315
+ }
4316
+ const data = [];
4317
+ for (let i = 0; i < raw.length - 1; i++) {
4318
+ const a = raw[i];
4319
+ const b = raw[i + 1];
4320
+ data.push(this._knotPoint(a[0], a[1]));
4321
+ const span = Math.abs(b[1] - a[1]);
4322
+ const n = Math.min(160, Math.max(0, Math.floor(span / 4)));
4323
+ for (let k = 1; k < n; k++) {
4324
+ const t = k / n;
4325
+ data.push({
4326
+ value: [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t],
4327
+ symbol: "none"
4229
4328
  });
4230
- });
4329
+ }
4330
+ }
4331
+ const last = raw[raw.length - 1];
4332
+ data.push(this._knotPoint(last[0], last[1]));
4333
+ return data;
4334
+ }
4335
+ /** 曲线仍 weakFilter;趋势线点太稀,滤掉窗外控制点后穿过视窗的线会断。8.25 */
4336
+ applyDataZoomSkipTrend(option) {
4337
+ if (!option) return;
4338
+ const raw = option.dataZoom;
4339
+ if (!raw) return;
4340
+ const list = Array.isArray(raw) ? raw : [raw];
4341
+ const indexes = [];
4342
+ const series = option.series || [];
4343
+ for (let i = 0; i < series.length; i++) {
4344
+ if (!this._isTrendSeriesOpt(series[i])) indexes.push(i);
4345
+ }
4346
+ if (!indexes.length) return;
4347
+ list.forEach((dz) => {
4348
+ if (dz) dz.seriesIndex = indexes;
4231
4349
  });
4232
4350
  }
4233
- _bindOnePointEl(chart, target, knot, id, role) {
4234
- const el = this._findGraphicZrEl(chart, id);
4235
- if (!el) return;
4236
- el.silent = false;
4237
- el.draggable = true;
4238
- el.cursor = "grab";
4239
- if (el.off && el.__trendDragBound) {
4240
- el.off("mousedown", el.__trendOnDown);
4241
- el.off("drag", el.__trendOnDrag);
4242
- el.off("dragend", el.__trendOnEnd);
4351
+ dataZoomSeriesIndex(chart) {
4352
+ if (!chart || chart.isDisposed()) return null;
4353
+ try {
4354
+ const list = chart.getModel().getSeries() || [];
4355
+ const indexes = [];
4356
+ for (let i = 0; i < list.length; i++) {
4357
+ const sm = list[i];
4358
+ const opt = sm && sm.option || {};
4359
+ const name = Array.isArray(sm.name) ? sm.name[0] : sm.name;
4360
+ if (this._isTrendSeriesOpt({ name, id: opt.id })) continue;
4361
+ if (sm.seriesIndex != null) indexes.push(sm.seriesIndex);
4362
+ }
4363
+ return indexes.length ? indexes : null;
4364
+ } catch (e) {
4365
+ return null;
4243
4366
  }
4244
- const onDown = (e) => {
4245
- this._clearClickTimer();
4246
- this.activeKey = target.key;
4247
- this.dragSession = { chart, target, knot, role, graphic: true };
4248
- this._setPanLocked(true);
4249
- if (e && typeof e.stop === "function") e.stop();
4250
- if (e && e.event) {
4251
- e.event.preventDefault();
4252
- e.event.stopPropagation();
4253
- }
4254
- };
4255
- const onDrag = () => {
4256
- if (!this.dragSession) {
4257
- this.dragSession = { chart, target, knot, role, graphic: true };
4258
- this._setPanLocked(true);
4259
- }
4260
- const [x, y] = this._graphicPixel(el);
4261
- if (!this._applyKnotPixel(chart, target, knot, role, x, y)) return;
4262
- el.x = 0;
4263
- el.y = 0;
4264
- this._patchLanePositions(target.laneId);
4265
- };
4266
- const onEnd = () => {
4267
- if (!this.dragSession) return;
4268
- const sessionTarget = this.dragSession.target;
4269
- this.dragSession = null;
4270
- this._setPanLocked(false, true);
4271
- this._ignoreClickUntil = Date.now() + 300;
4272
- this._emitChange(sessionTarget, { redraw: false });
4273
- };
4274
- el.__trendOnDown = onDown;
4275
- el.__trendOnDrag = onDrag;
4276
- el.__trendOnEnd = onEnd;
4277
- el.__trendDragBound = true;
4278
- el.on("mousedown", onDown);
4279
- el.on("drag", onDrag);
4280
- el.on("dragend", onEnd);
4281
4367
  }
4282
- _applyPatchesViaZr(chart, patches) {
4283
- if (!patches.length) return false;
4284
- let applied = 0;
4285
- patches.forEach(({ id, shape }) => {
4286
- const el = this._findGraphicZrEl(chart, id);
4287
- if (!el || typeof el.setShape !== "function") return;
4288
- el.setShape(shape);
4289
- applied += 1;
4368
+ /** 笛卡尔 [曲线值, 井深];对数道 X 已是 lg。和曲线共用轴,dataZoom 同一帧带走。8.25 */
4369
+ _buildTrendSeries(target, xAxisIndex, yAxisIndex) {
4370
+ const list = [];
4371
+ const { key, segments, line } = target;
4372
+ const canMirror = this._canMirror(line, segments);
4373
+ const doubleLine = this._useGammaStyle(line, segments);
4374
+ const mainLineStyle = doubleLine ? TREND_DOUBLE_LINE_STYLE : TREND_LINE_STYLE;
4375
+ const mainPointStyle = doubleLine ? TREND_DOUBLE_POINT_STYLE : TREND_POINT_STYLE;
4376
+ (segments || []).forEach((seg) => {
4377
+ const knots = seg.knots || [];
4378
+ if (!knots.length) return;
4379
+ list.push(
4380
+ this._buildOneTrendSeries(
4381
+ `trend_${key}_${seg.id}`,
4382
+ this._segSeriesData(knots, "main"),
4383
+ mainLineStyle,
4384
+ mainPointStyle,
4385
+ xAxisIndex,
4386
+ yAxisIndex
4387
+ )
4388
+ );
4389
+ if (!canMirror) return;
4390
+ const mirror = this._segSeriesData(knots, "mirror");
4391
+ if (!mirror.length) return;
4392
+ list.push(
4393
+ this._buildOneTrendSeries(
4394
+ `trend_mirror_${key}_${seg.id}`,
4395
+ mirror,
4396
+ TREND_MIRROR_LINE_STYLE,
4397
+ TREND_MIRROR_POINT_STYLE,
4398
+ xAxisIndex,
4399
+ yAxisIndex
4400
+ )
4401
+ );
4290
4402
  });
4291
- const zr = chart.getZr && chart.getZr();
4292
- if (applied > 0 && zr && typeof zr.refresh === "function") zr.refresh();
4293
- return applied > 0;
4403
+ return list;
4294
4404
  }
4295
- // 只 merge 新图形,不动已有线。8.24
4296
- _addGraphicElements(chart, elements) {
4297
- if (!elements.length) return;
4298
- this._invalidateZrElCache(chart);
4299
- chart.setOption(
4300
- {
4301
- graphic: elements.map((el) => ({ ...el, $action: "merge" }))
4302
- },
4303
- { silent: true, lazyUpdate: false }
4405
+ /** 刷 option 时给该道补上趋势 series,所有有控制点的泳道都会走。8.25 */
4406
+ appendSeriesToOption(option, lane) {
4407
+ if (!option || !lane) return option;
4408
+ if (!Array.isArray(option.series)) option.series = [];
4409
+ option.series = option.series.filter((s) => !this._isTrendSeriesOpt(s));
4410
+ const targets = this.listDrawableTargets().filter(
4411
+ (t) => String(t.laneId) === String(lane.laneId)
4304
4412
  );
4413
+ const added = [];
4414
+ targets.forEach((target) => {
4415
+ const curve = this._findCurveSeriesInOption(option, target.paramId);
4416
+ if (!curve) return;
4417
+ const xAxisIndex = curve.xAxisIndex == null ? 0 : Number(curve.xAxisIndex);
4418
+ const yAxisIndex = curve.yAxisIndex == null ? 0 : Number(curve.yAxisIndex);
4419
+ added.push(...this._buildTrendSeries(target, xAxisIndex, yAxisIndex));
4420
+ });
4421
+ option.series.push(...added);
4422
+ this._seriesIds[lane.laneId] = added.map((s) => s.id);
4423
+ this.applyDataZoomSkipTrend(option);
4424
+ return option;
4305
4425
  }
4306
- // 按 id 删除,不 replaceMerge 整层 graphic。8.24
4307
- _removeGraphicIds(chart, ids) {
4308
- if (!ids.length) return;
4309
- this._invalidateZrElCache(chart);
4310
- chart.setOption(
4311
- {
4312
- graphic: ids.map((id) => ({ id, $action: "remove" }))
4313
- },
4314
- { silent: true, lazyUpdate: false }
4315
- );
4426
+ _clearLeftoverGraphic(chart) {
4427
+ if (!chart || chart.isDisposed() || this._graphicCleared.has(chart)) return;
4428
+ this._graphicCleared.add(chart);
4429
+ try {
4430
+ chart.setOption({ graphic: [] }, { replaceMerge: ["graphic"], silent: true, lazyUpdate: false });
4431
+ } catch (e) {
4432
+ }
4316
4433
  }
4317
4434
  renderAll(forceReplace = false) {
4318
4435
  const lanes = this.host.currentTemplate && this.host.currentTemplate.lanes || [];
4319
4436
  lanes.forEach((lane) => this.renderLane(lane.laneId, forceReplace));
4320
4437
  }
4321
- /** 已有线只改 ZR 形状;新点/新线段单独 merge 进去,绝不整层 graphic 替换。8.24 */
4438
+ /** 用笛卡尔 series 画趋势线;平移/缩放交给 dataZoom,不跟像素。8.25 */
4322
4439
  renderLane(laneId, forceReplace = false) {
4440
+ if (!this._alive()) return;
4323
4441
  const chart = this.getChart(laneId);
4324
4442
  if (!chart) return;
4325
- const targets = this.listDrawableTargets().filter((t) => t.laneId === laneId);
4326
- const elements = [];
4443
+ this._clearLeftoverGraphic(chart);
4444
+ const targets = this.listDrawableTargets().filter(
4445
+ (t) => String(t.laneId) === String(laneId)
4446
+ );
4447
+ const next = [];
4327
4448
  targets.forEach((target) => {
4328
- elements.push(...this._buildElements(chart, target));
4449
+ const axis = this._curveAxisOf(chart, target);
4450
+ next.push(...this._buildTrendSeries(target, axis.xAxisIndex, axis.yAxisIndex));
4329
4451
  });
4330
- const hasKnots = targets.some((t) => (t.segments || []).some((s) => (s.knots || []).length));
4331
- if (hasKnots && !elements.length) return;
4332
- const nextIds = elements.map((el) => el.id);
4333
- const prevIds = this._graphicIds[laneId] || [];
4334
- const prevSet = new Set(prevIds);
4452
+ const nextIds = next.map((s) => s.id);
4335
4453
  const nextSet = new Set(nextIds);
4336
- const added = elements.filter((el) => !prevSet.has(el.id));
4337
- const kept = elements.filter((el) => prevSet.has(el.id));
4338
- const removedIds = prevIds.filter((id) => !nextSet.has(id));
4339
- if (forceReplace) {
4340
- this._removeGraphicIds(chart, prevIds);
4341
- this._addGraphicElements(chart, elements);
4342
- } else {
4343
- if (kept.length) {
4344
- this._applyPatchesViaZr(
4345
- chart,
4346
- kept.map((el) => ({ id: el.id, shape: el.shape }))
4347
- );
4454
+ const liveIds = this._chartTrendSeriesIds(chart);
4455
+ const prevIds = [.../* @__PURE__ */ new Set([...this._seriesIds[laneId] || [], ...liveIds])];
4456
+ const replace = !!(forceReplace && !this.dragSession);
4457
+ const patch = next.map((s) => ({
4458
+ ...s,
4459
+ data: s.data || [],
4460
+ $action: replace ? "replace" : "merge"
4461
+ }));
4462
+ prevIds.forEach((id) => {
4463
+ if (!nextSet.has(id)) patch.push({ id, $action: "remove" });
4464
+ });
4465
+ if (patch.length) {
4466
+ try {
4467
+ chart.setOption({ series: patch }, { silent: true, lazyUpdate: false });
4468
+ } catch (e) {
4348
4469
  }
4349
- if (removedIds.length) this._removeGraphicIds(chart, removedIds);
4350
- if (added.length) this._addGraphicElements(chart, added);
4351
4470
  }
4352
- this._graphicIds[laneId] = nextIds;
4353
- this._bindPointInteractions(chart, laneId);
4354
- this.updateCursor(laneId);
4355
- }
4356
- _buildElements(chart, target) {
4357
- const elements = [];
4358
- const { paramId, key, editable, segments, line } = target;
4359
- const canMirror = this._canMirror(line, segments);
4360
- const doubleLine = this._useGammaStyle(line, segments);
4361
- const mainLineStyle = doubleLine ? TREND_DOUBLE_LINE_STYLE : TREND_LINE_STYLE;
4362
- const mainPointStyle = doubleLine ? TREND_DOUBLE_POINT_STYLE : TREND_POINT_STYLE;
4363
- segments.forEach((seg) => {
4364
- const knots = seg.knots || [];
4365
- for (let i = 0; i < knots.length - 1; i++) {
4366
- const p1 = knots[i];
4367
- const p2 = knots[i + 1];
4368
- const px1 = this._knotPixel(chart, target, p1);
4369
- const px2 = this._knotPixel(chart, target, p2);
4370
- if (!px1 || !px2) continue;
4371
- elements.push({
4372
- type: "line",
4373
- id: `trend_line_${key}_${seg.id}_${i}`,
4374
- shape: { x1: px1[0], y1: px1[1], x2: px2[0], y2: px2[1] },
4375
- style: { ...mainLineStyle },
4376
- z: TREND_LINE_Z,
4377
- silent: true
4378
- });
4379
- if (canMirror) {
4380
- const mx1 = this._knotPixel(chart, target, p1, "mirror");
4381
- const mx2 = this._knotPixel(chart, target, p2, "mirror");
4382
- if (mx1 && mx2) {
4383
- elements.push({
4384
- type: "line",
4385
- id: `trend_mirror_${key}_${seg.id}_${i}`,
4386
- shape: { x1: mx1[0], y1: mx1[1], x2: mx2[0], y2: mx2[1] },
4387
- style: { ...TREND_MIRROR_LINE_STYLE },
4388
- z: TREND_LINE_Z - 1,
4389
- silent: true
4390
- });
4471
+ this._seriesIds[laneId] = nextIds;
4472
+ if (!this.dragSession) this._syncCachedOption(laneId);
4473
+ if (this.dragSession) {
4474
+ this.updateCursor(laneId);
4475
+ return;
4476
+ }
4477
+ const idsChanged = nextIds.length !== prevIds.length || nextIds.some((id, i) => id !== prevIds[i]);
4478
+ if (idsChanged) {
4479
+ const skip = this.dataZoomSeriesIndex(chart);
4480
+ if (skip && skip.length) {
4481
+ try {
4482
+ const dz = chart.getModel().getComponent("dataZoom", 0);
4483
+ const opt = dz && dz.option;
4484
+ const next2 = { seriesIndex: skip };
4485
+ if (opt) {
4486
+ if (opt.start != null) next2.start = opt.start;
4487
+ if (opt.end != null) next2.end = opt.end;
4488
+ if (opt.startValue != null) next2.startValue = opt.startValue;
4489
+ if (opt.endValue != null) next2.endValue = opt.endValue;
4391
4490
  }
4491
+ chart.setOption({ dataZoom: [next2] }, { silent: true, lazyUpdate: true });
4492
+ } catch (e) {
4392
4493
  }
4393
4494
  }
4394
- knots.forEach((knot, i) => {
4395
- const px = this._knotPixel(chart, target, knot);
4396
- if (!px) return;
4397
- elements.push({
4398
- type: "circle",
4399
- id: `trend_point_${key}_${seg.id}_${i}`,
4400
- shape: { cx: px[0], cy: px[1], r: 4 },
4401
- style: { ...mainPointStyle },
4402
- z: TREND_POINT_Z,
4403
- zlevel: 10,
4404
- silent: !editable,
4405
- draggable: !!editable,
4406
- cursor: editable ? "grab" : "default"
4407
- });
4408
- if (canMirror) {
4409
- const mx = this._knotPixel(chart, target, knot, "mirror");
4410
- if (mx) {
4411
- elements.push({
4412
- type: "circle",
4413
- id: `trend_mirror_point_${key}_${seg.id}_${i}`,
4414
- shape: { cx: mx[0], cy: mx[1], r: 4 },
4415
- style: { ...TREND_MIRROR_POINT_STYLE },
4416
- z: TREND_POINT_Z,
4417
- zlevel: 10,
4418
- silent: !editable,
4419
- draggable: !!editable,
4420
- cursor: editable ? "grab" : "default"
4421
- });
4422
- }
4423
- }
4424
- });
4425
- });
4426
- return elements;
4495
+ }
4496
+ this.updateCursor(laneId);
4497
+ }
4498
+ /** 把当前 store 写回 allOptions,避免随后 notMerge 用旧趋势 series 把点盖回去。8.25 */
4499
+ _syncCachedOption(laneId) {
4500
+ const host = this.host;
4501
+ if (!host || typeof host.getOptionsByLineId !== "function") return null;
4502
+ const option = host.getOptionsByLineId(laneId);
4503
+ if (!option || !Array.isArray(option.series)) return null;
4504
+ const lanes = host.currentTemplate && host.currentTemplate.lanes || [];
4505
+ const lane = lanes.find((l) => l.laneId === laneId);
4506
+ if (!lane) return null;
4507
+ this.appendSeriesToOption(option, lane);
4508
+ return option;
4509
+ }
4510
+ /** 删点后走整份 option 替换,ECharts 才会丢掉缩短后的旧 data。8.25 */
4511
+ _flushLane(laneId) {
4512
+ const option = this._syncCachedOption(laneId);
4513
+ if (option && this.host && typeof this.host.flushLaneChartOption === "function") {
4514
+ option.__kdTrendSynced = true;
4515
+ try {
4516
+ this.host.flushLaneChartOption(laneId, option);
4517
+ } finally {
4518
+ delete option.__kdTrendSynced;
4519
+ }
4520
+ this.updateCursor(laneId);
4521
+ return;
4522
+ }
4523
+ this.renderLane(laneId, true);
4524
+ }
4525
+ _chartTrendSeriesIds(chart) {
4526
+ if (!chart || chart.isDisposed()) return [];
4527
+ try {
4528
+ const list = chart.getModel().getSeries() || [];
4529
+ const ids = [];
4530
+ for (let i = 0; i < list.length; i++) {
4531
+ const sm = list[i];
4532
+ const opt = sm && sm.option || {};
4533
+ const name = Array.isArray(sm.name) ? sm.name[0] : sm.name;
4534
+ const id = opt.id || sm.id;
4535
+ if (this._isTrendSeriesOpt({ name, id }) && id) ids.push(id);
4536
+ }
4537
+ return ids;
4538
+ } catch (e) {
4539
+ return [];
4540
+ }
4427
4541
  }
4428
4542
  _findKnotAtPointer(chart, target, offsetX, offsetY, radius = TREND_HIT_RADIUS) {
4429
4543
  let best = null;
4430
4544
  let bestDist = radius;
4431
4545
  const canMirror = this._canMirror(target.line, target.segments);
4432
4546
  for (const seg of target.segments || []) {
4433
- for (const knot of seg.knots || []) {
4547
+ const knots = seg.knots || [];
4548
+ for (let i = 0; i < knots.length; i++) {
4549
+ const knot = knots[i];
4434
4550
  const px = this._knotPixel(chart, target, knot);
4435
4551
  if (px) {
4436
4552
  const dist = Math.hypot(px[0] - offsetX, px[1] - offsetY);
4437
4553
  if (dist <= bestDist) {
4438
- best = { knot, seg, role: "main" };
4554
+ best = { knot, seg, knotIndex: i, role: "main" };
4439
4555
  bestDist = dist;
4440
4556
  }
4441
4557
  }
@@ -4444,7 +4560,7 @@ class TrendLineManager {
4444
4560
  if (!mx) continue;
4445
4561
  const mirrorDist = Math.hypot(mx[0] - offsetX, mx[1] - offsetY);
4446
4562
  if (mirrorDist <= bestDist) {
4447
- best = { knot, seg, role: "mirror" };
4563
+ best = { knot, seg, knotIndex: i, role: "mirror" };
4448
4564
  bestDist = mirrorDist;
4449
4565
  }
4450
4566
  }
@@ -4453,6 +4569,11 @@ class TrendLineManager {
4453
4569
  }
4454
4570
  /** 单击空白处加点;延迟执行以便和双击断段区分。命中已有点则不加点。返回 true 表示已消费本次点击。8.21 */
4455
4571
  handleClick(e, laneId) {
4572
+ if (!this._alive()) return false;
4573
+ if (this.dragSession) {
4574
+ this._endDrag(true);
4575
+ return true;
4576
+ }
4456
4577
  const target = this.getTargetByLane(laneId);
4457
4578
  if (!target || !target.editable) return false;
4458
4579
  if (Date.now() < this._ignoreClickUntil) return true;
@@ -4465,6 +4586,7 @@ class TrendLineManager {
4465
4586
  this._clearClickTimer();
4466
4587
  this.clickTimer = setTimeout(() => {
4467
4588
  this.clickTimer = null;
4589
+ if (!this._alive()) return;
4468
4590
  this._addPoint(chart, target, offsetX, offsetY);
4469
4591
  }, TREND_CLICK_DELAY_MS);
4470
4592
  return true;
@@ -4486,7 +4608,7 @@ class TrendLineManager {
4486
4608
  }
4487
4609
  return true;
4488
4610
  }
4489
- /** 右键命中控制点时弹出「删除控制点」;先结束可能误触发的拖拽并重绘,避免线段被 replaceMerge 丢掉。8.21 */
4611
+ /** 右键命中控制点时弹出「删除控制点」。打开菜单时不要 setOption,否则会吞掉菜单点击。8.21 */
4490
4612
  handleContextMenu(e, laneId) {
4491
4613
  const target = this.getTargetByLane(laneId);
4492
4614
  if (!target || !target.editable) return false;
@@ -4500,14 +4622,23 @@ class TrendLineManager {
4500
4622
  this.host.showTrendKnotContextMenu(e, {
4501
4623
  key: target.key,
4502
4624
  knot: hit.knot,
4625
+ segId: hit.seg && hit.seg.id,
4626
+ knotIndex: hit.knotIndex,
4627
+ depth: hit.knot.depth,
4628
+ value: hit.knot.value,
4629
+ plotValue: hit.knot.plotValue,
4503
4630
  laneId
4504
4631
  });
4505
- this.renderLane(laneId);
4506
4632
  return true;
4507
4633
  }
4508
4634
  /** 指针靠近控制点时先停地应力平移,按下时才来得及拖点。8.22 */
4509
4635
  onZrHover(e, laneId) {
4510
- if (this.dragSession) return;
4636
+ if (!this._alive()) return;
4637
+ if (this.dragSession) {
4638
+ e.__ecRoamConsumed = true;
4639
+ if (typeof e.stop === "function") e.stop();
4640
+ return;
4641
+ }
4511
4642
  if (this.host && typeof this.host.isChartBusy === "function" && this.host.isChartBusy()) {
4512
4643
  return;
4513
4644
  }
@@ -4530,38 +4661,32 @@ class TrendLineManager {
4530
4661
  this.host.setInsideDataZoomPanEnabled(!locked);
4531
4662
  }
4532
4663
  }
4533
- /** zr 里拦住 dataZoom 的按下,否则图会跟着点一起拖。8.22 */
4664
+ /** 左键拖点与右键删点同一套命中(zr offset + _findKnotAtPointer)。8.25 */
4534
4665
  onZrMouseDown(e, laneId) {
4666
+ if (!this._alive()) return;
4535
4667
  if (e.which != null && e.which !== 1 && e.event && e.event.button !== 0) return;
4668
+ if (this.dragSession) {
4669
+ e.__ecRoamConsumed = true;
4670
+ if (typeof e.stop === "function") e.stop();
4671
+ this._endDrag(true);
4672
+ return;
4673
+ }
4536
4674
  const target = this.getTargetByLane(laneId);
4537
4675
  const chart = this.getChart(laneId);
4538
- if (!target || !target.editable || !chart || this.dragSession) return;
4676
+ if (!target || !target.editable || !chart) return;
4539
4677
  const { offsetX, offsetY } = eventOffsetInChart(chart, e);
4540
- if (!this._findKnotAtPointer(chart, target, offsetX, offsetY)) return;
4678
+ const hit = this._findKnotAtPointer(chart, target, offsetX, offsetY);
4679
+ if (!hit) return;
4680
+ e.__ecRoamConsumed = true;
4541
4681
  if (typeof e.stop === "function") e.stop();
4542
4682
  e.cancelBubble = true;
4543
4683
  if (e.event) {
4544
4684
  e.event.preventDefault();
4545
4685
  e.event.stopPropagation();
4546
4686
  }
4547
- }
4548
- /** 仅左键拖拽控制点。右键 mousedown 会先于 contextmenu,绝不能当拖拽处理。8.21 */
4549
- onMouseDown(nativeEvent, laneId) {
4550
- if (nativeEvent.button !== 0) return;
4551
- const target = this.getTargetByLane(laneId);
4552
- if (!target || !target.editable || this.dragSession) return;
4553
- const chart = this.getChart(laneId);
4554
- if (!chart) return;
4555
- const { offsetX, offsetY } = eventOffsetInChart(chart, nativeEvent);
4556
- const hit = this._findKnotAtPointer(chart, target, offsetX, offsetY);
4557
- if (!hit) return;
4558
- nativeEvent.stopPropagation();
4559
- nativeEvent.stopImmediatePropagation();
4560
- nativeEvent.preventDefault();
4561
4687
  this._clearClickTimer();
4562
4688
  this.activeKey = target.key;
4563
- this._setPanLocked(true);
4564
- this._startDrag(chart, target, hit.knot, nativeEvent, hit.role);
4689
+ this._startDrag(chart, target, hit.knot, e, hit.role);
4565
4690
  }
4566
4691
  /** 沿深度增大方向追加控制点;曲线值限制在该线 min/max 内。8.21 */
4567
4692
  _addPoint(chart, target, offsetX, offsetY) {
@@ -4657,6 +4782,7 @@ class TrendLineManager {
4657
4782
  }
4658
4783
  /** 清空全部趋势线控制点并重绘。8.22 */
4659
4784
  resetAllTrendLines() {
4785
+ if (this.dragSession) this._endDrag(false);
4660
4786
  const prev = this.listDrawableTargets().filter(
4661
4787
  (t) => (t.segments || []).some((s) => (s.knots || []).length)
4662
4788
  );
@@ -4670,25 +4796,39 @@ class TrendLineManager {
4670
4796
  segments: []
4671
4797
  });
4672
4798
  });
4673
- this.renderAll(true);
4799
+ this._flushTrendLanes(prev);
4674
4800
  }
4675
4801
  /** 清空指定曲线的控制点;不传 key 则清当前编辑曲线。8.21 */
4676
4802
  resetTrendLine(key) {
4677
4803
  const target = key ? this.listDrawableTargets().find((t) => t.key === key) : this.getActiveTarget() || this.listDrawableTargets().find((t) => t.editable);
4678
4804
  if (!target) return;
4805
+ if (this.dragSession) this._endDrag(false);
4679
4806
  this.store[target.key] = { segments: [] };
4680
- this.renderLane(target.laneId, true);
4807
+ this._flushLane(target.laneId);
4681
4808
  this._emitChange(target, { redraw: false });
4682
4809
  }
4810
+ /** 把 store 已空的泳道整份 option 刷进图,线段才会从画面上消失。8.25 */
4811
+ _flushTrendLanes(prev) {
4812
+ const laneIds = /* @__PURE__ */ new Set();
4813
+ (prev || []).forEach((t) => t.laneId && laneIds.add(t.laneId));
4814
+ Object.keys(this._seriesIds).forEach((id) => laneIds.add(id));
4815
+ this.binds.forEach((_, laneId) => {
4816
+ const chart = this.getChart(laneId);
4817
+ if (chart && this._chartTrendSeriesIds(chart).length) laneIds.add(laneId);
4818
+ });
4819
+ if (!laneIds.size) {
4820
+ this.renderAll(true);
4821
+ return;
4822
+ }
4823
+ laneIds.forEach((laneId) => this._flushLane(laneId));
4824
+ }
4683
4825
  /** 删除一个控制点。段内只剩 0~1 个点时整段丢掉(画不出线)。8.21 */
4684
4826
  deleteKnot(payload) {
4685
- if (!payload || !payload.key || !payload.knot) return;
4827
+ if (!payload || !payload.key) return;
4686
4828
  const bucket = this.store[payload.key];
4687
4829
  if (!bucket) return;
4688
4830
  bucket.segments = bucket.segments.map((seg) => {
4689
- const idx = seg.knots.findIndex(
4690
- (k) => k === payload.knot || Math.abs(k.depth - payload.knot.depth) < 0.05 && Math.abs(k.value - payload.knot.value) < 0.01
4691
- );
4831
+ const idx = this._findKnotIndex(seg, payload);
4692
4832
  if (idx < 0) return seg;
4693
4833
  const knots = seg.knots.filter((_, i) => i !== idx);
4694
4834
  if (knots.length <= 1) return { ...seg, knots: [] };
@@ -4696,19 +4836,46 @@ class TrendLineManager {
4696
4836
  }).filter((s) => s.knots.length > 0);
4697
4837
  const target = this.listDrawableTargets().find((t) => t.key === payload.key);
4698
4838
  const laneId = target && target.laneId || payload.laneId;
4699
- if (laneId) this.renderLane(laneId, true);
4839
+ if (laneId) this._flushLane(laneId);
4700
4840
  if (target) this._emitChange(target, { redraw: false });
4701
4841
  }
4842
+ _findKnotIndex(seg, payload) {
4843
+ const knots = seg && seg.knots || [];
4844
+ if (!knots.length || !payload) return -1;
4845
+ if (payload.knot) {
4846
+ const byRef = knots.indexOf(payload.knot);
4847
+ if (byRef >= 0) return byRef;
4848
+ }
4849
+ if (payload.segId && seg.id !== payload.segId) return -1;
4850
+ if (Number.isInteger(payload.knotIndex) && payload.knotIndex >= 0 && payload.knotIndex < knots.length) {
4851
+ return payload.knotIndex;
4852
+ }
4853
+ const depth = payload.knot != null ? payload.knot.depth : payload.depth;
4854
+ const value = payload.knot != null ? payload.knot.value : payload.value;
4855
+ const plotValue = payload.knot != null ? payload.knot.plotValue : payload.plotValue;
4856
+ return knots.findIndex((k) => {
4857
+ if (depth == null || Math.abs(Number(k.depth) - Number(depth)) >= 0.05) return false;
4858
+ if (value != null && Math.abs(Number(k.value) - Number(value)) < 0.01) return true;
4859
+ if (plotValue != null && k.plotValue != null && Math.abs(Number(k.plotValue) - Number(plotValue)) < 0.01) {
4860
+ return true;
4861
+ }
4862
+ return value == null;
4863
+ });
4864
+ }
4702
4865
  /** 返回 geosteering trendLinePoint 格式的线段数组;8137 带上 isDouble / calcType。8.22 */
4703
4866
  getTrendLineData(mainId = "demo") {
4704
4867
  const grouped = {};
4705
4868
  const meta = {};
4869
+ const linesByParam = {};
4706
4870
  this.listDrawableTargets().forEach((t) => {
4707
- grouped[t.paramId] = cloneSegments(t.segments);
4708
- meta[t.paramId] = {
4709
- isDouble: isLineDouble(t.line),
4710
- calcType: t.line && t.line.calcType
4711
- };
4871
+ linesByParam[t.paramId] = t.line;
4872
+ });
4873
+ Object.keys(this.store).forEach((key) => {
4874
+ const segments = this.store[key] && this.store[key].segments;
4875
+ if (!this._hasTrendKnots(segments)) return;
4876
+ const paramId = paramIdFromTrendKey(key);
4877
+ grouped[paramId] = cloneSegments(segments);
4878
+ meta[paramId] = inferTrendSaveMeta(linesByParam[paramId], segments);
4712
4879
  });
4713
4880
  return toTrendLineSavePayload(grouped, mainId, meta);
4714
4881
  }
@@ -4722,12 +4889,15 @@ class TrendLineManager {
4722
4889
  }
4723
4890
  const segs = this._prepareEchoSegments(paramId, segments);
4724
4891
  this.store[key] = { segments: segs };
4725
- if (found.line) found.line.trendSegments = segs;
4726
4892
  this.renderLane(found.laneId);
4727
4893
  }
4728
4894
  /** 把 geosteering 线段数组灌进对应曲线并画出叠加层。8.21 */
4729
4895
  setTrendLineList(lineList, overwrite = true) {
4730
- if (!Array.isArray(lineList) || !lineList.length) return;
4896
+ if (!Array.isArray(lineList) || !lineList.length) {
4897
+ if (!overwrite) return;
4898
+ this.resetAllTrendLines();
4899
+ return;
4900
+ }
4731
4901
  const grouped = fromTrendLineSavePayload(lineList);
4732
4902
  Object.entries(grouped).forEach(([paramId, segments]) => {
4733
4903
  this.setTrendLineData(paramId, segments, overwrite);
@@ -4763,33 +4933,58 @@ class TrendLineManager {
4763
4933
  const last = target.segments[target.segments.length - 1];
4764
4934
  return !!(last && !last._closed && last.knots && last.knots.length);
4765
4935
  }
4766
- /** 拖拽开始:在 window 上听 mousemove/mouseup,避免鼠标移出图表丢事件。8.21 */
4936
+ /** 拖拽开始:按下不刷图,避免 setOption 把这次按住掐掉。8.25 */
4767
4937
  _startDrag(chart, target, knot, nativeEvent, role = "main") {
4938
+ this._unbindDragWindow();
4768
4939
  const onMove = (ev) => this._onDragMove(ev);
4769
- const onUp = () => this._endDrag(true);
4940
+ const onUp = (ev) => {
4941
+ if (ev && ev.button != null && ev.button !== 0) return;
4942
+ this._endDrag(true);
4943
+ };
4944
+ const startStamp = nativeEvent && nativeEvent.timeStamp;
4945
+ const onClickEnd = (ev) => {
4946
+ if (ev && ev.button != null && ev.button !== 0) return;
4947
+ if (startStamp != null && ev.timeStamp === startStamp) return;
4948
+ this._endDrag(true);
4949
+ };
4950
+ const { offsetX, offsetY } = eventOffsetInChart(chart, nativeEvent);
4951
+ const px = this._knotPixel(chart, target, knot, role);
4770
4952
  this.dragSession = {
4771
4953
  chart,
4772
4954
  target,
4773
4955
  knot,
4774
4956
  role,
4775
4957
  onMove,
4776
- onUp
4958
+ onUp,
4959
+ onClickEnd,
4960
+ grabDX: px ? px[0] - offsetX : 0,
4961
+ grabDY: px ? px[1] - offsetY : 0
4777
4962
  };
4778
- if (this.host && typeof this.host.setInsideDataZoomPanEnabled === "function") {
4779
- this.host.setInsideDataZoomPanEnabled(false);
4780
- }
4781
4963
  window.addEventListener("mousemove", onMove);
4782
4964
  window.addEventListener("mouseup", onUp);
4965
+ window.addEventListener("mousedown", onClickEnd, true);
4783
4966
  this.updateCursor(target.laneId);
4784
- this._onDragMove(nativeEvent);
4785
4967
  }
4786
- /** 拖拽时夹在相邻点之间,不能越过前后控制点。8.21 */
4968
+ _unbindDragWindow() {
4969
+ const session = this.dragSession;
4970
+ if (!session) return;
4971
+ window.removeEventListener("mousemove", session.onMove);
4972
+ window.removeEventListener("mouseup", session.onUp);
4973
+ if (session.onClickEnd) {
4974
+ window.removeEventListener("mousedown", session.onClickEnd, true);
4975
+ }
4976
+ this.dragSession = null;
4977
+ }
4978
+ /** 拖拽时点跟着鼠标走;夹在相邻点之间,不能越过前后控制点。8.21 */
4787
4979
  _onDragMove(nativeEvent) {
4980
+ if (!this._alive()) return;
4788
4981
  const session = this.dragSession;
4789
4982
  if (!session) return;
4790
4983
  const { chart, target, knot, role } = session;
4791
- const { offsetX, offsetY } = eventOffsetInChart(chart, nativeEvent);
4792
- const data = this._pixelToTrend(chart, target, offsetX, offsetY, true);
4984
+ const pos = eventOffsetInChart(chart, nativeEvent);
4985
+ const offsetX = pos.offsetX + (session.grabDX || 0);
4986
+ const offsetY = pos.offsetY + (session.grabDY || 0);
4987
+ const data = this._pixelToDragTrend(chart, target, offsetX, offsetY);
4793
4988
  if (!data) return;
4794
4989
  const range = this._drawValueRange(target, chart, data.depth);
4795
4990
  let depth = snapDepthToEdge(data.depth, range.depthMin, range.depthMax, DEPTH_EDGE_SNAP);
@@ -4803,19 +4998,33 @@ class TrendLineManager {
4803
4998
  this._applyPointerValue(chart, target, knot, knot.depth, clickX, role);
4804
4999
  this.renderLane(target.laneId);
4805
5000
  }
5001
+ /** 拖点用轴 scale 反算,和画点同一套,避免点落在鼠标旁边。8.25 */
5002
+ _pixelToDragTrend(chart, target, offsetX, offsetY) {
5003
+ const finder = findSeriesForDepth(chart, target.paramId, this._firstKnotDepth(target));
5004
+ const fromAxis = axisPairFromPixel(
5005
+ chart,
5006
+ finder.xAxisIndex,
5007
+ finder.yAxisIndex,
5008
+ offsetX,
5009
+ offsetY
5010
+ );
5011
+ if (fromAxis) return fromAxis;
5012
+ return this._pixelToTrend(chart, target, offsetX, offsetY, true);
5013
+ }
4806
5014
  /** emit=false 只结束拖拽不抛事件(右键菜单用)。8.21 */
4807
5015
  _endDrag(emit) {
4808
5016
  const session = this.dragSession;
4809
5017
  if (!session) return;
4810
- window.removeEventListener("mousemove", session.onMove);
4811
- window.removeEventListener("mouseup", session.onUp);
4812
- this.dragSession = null;
5018
+ this._unbindDragWindow();
5019
+ if (!this._alive()) return;
4813
5020
  this._panLockedByHover = false;
4814
5021
  if (this.host && typeof this.host.setInsideDataZoomPanEnabled === "function") {
4815
5022
  this.host.setInsideDataZoomPanEnabled(true);
4816
5023
  }
4817
5024
  this._ignoreClickUntil = Date.now() + 300;
4818
5025
  this.updateCursor(session.target.laneId);
5026
+ this.renderLane(session.target.laneId, true);
5027
+ this._syncCachedOption(session.target.laneId);
4819
5028
  if (emit) this._emitChange(session.target, { redraw: false });
4820
5029
  }
4821
5030
  _clearClickTimer() {
@@ -4827,6 +5036,7 @@ class TrendLineManager {
4827
5036
  /** 抛 trend-change(拖拽过程中不抛,只在松开时抛);不写回响应式模板。8.22 */
4828
5037
  // redraw=false:本地已画过,避免再 scheduleRender 把线闪掉。8.24
4829
5038
  _emitChange(target, { redraw = true } = {}) {
5039
+ if (!this._alive() || !target) return;
4830
5040
  const segments = cloneSegments(this.store[target.key] && this.store[target.key].segments || []);
4831
5041
  this.host.$emit("trend-change", {
4832
5042
  paramId: target.paramId,
@@ -4838,8 +5048,12 @@ class TrendLineManager {
4838
5048
  /** 等 Vue / 图表 setOption 完成后再画叠加层。8.22 */
4839
5049
  // 只 nextTick 一次,不再叠 rAF 连刷。8.24
4840
5050
  _scheduleRenderAfterVue() {
4841
- if (this.host && typeof this.host.$nextTick === "function") {
4842
- this.host.$nextTick(() => this.scheduleRender());
5051
+ if (!this._alive()) return;
5052
+ if (typeof this.host.$nextTick === "function") {
5053
+ this.host.$nextTick(() => {
5054
+ if (!this._alive()) return;
5055
+ this.scheduleRender();
5056
+ });
4843
5057
  } else {
4844
5058
  this.scheduleRender();
4845
5059
  }
@@ -4956,6 +5170,15 @@ class TrendLineManager {
4956
5170
  return pixelToData(chart, target.paramId, offsetX, offsetY, loose);
4957
5171
  }
4958
5172
  _trendToPixel(chart, target, value, depth) {
5173
+ const finder = findSeriesForDepth(chart, target.paramId, depth);
5174
+ const live = axisPairToPixel(
5175
+ chart,
5176
+ finder.xAxisIndex,
5177
+ finder.yAxisIndex,
5178
+ value,
5179
+ depth
5180
+ );
5181
+ if (live) return live;
4959
5182
  const lane = this._laneById(target.laneId);
4960
5183
  if (isLaneLogScale(lane)) {
4961
5184
  return logTrendDataToPixel(chart, target.paramId, value, depth);
@@ -5183,11 +5406,16 @@ const _sfc_main$6 = {
5183
5406
  type: Boolean,
5184
5407
  default: true
5185
5408
  },
5186
- // 当前选中的泳道ID(用于在头部显示红色边框)
5409
+ // 当前选中的泳道ID(最后一次点选,兼容旧 emit)
5187
5410
  activeLaneId: {
5188
5411
  type: [String, Number],
5189
5412
  default: null
5190
5413
  },
5414
+ // 地应力可同时选中多道。8.25
5415
+ activeLaneIds: {
5416
+ type: Array,
5417
+ default: () => []
5418
+ },
5191
5419
  // 是否为地应力模式(只有为 true 时才启用泳道选中功能)
5192
5420
  isGeomechanical: {
5193
5421
  type: Boolean,
@@ -5260,6 +5488,10 @@ const _sfc_main$6 = {
5260
5488
  clearTimeout(this.resizeTimer);
5261
5489
  this.resizeTimer = null;
5262
5490
  }
5491
+ if (this._curveConfigCloseTimer) {
5492
+ clearTimeout(this._curveConfigCloseTimer);
5493
+ this._curveConfigCloseTimer = 0;
5494
+ }
5263
5495
  if (this.resizeObserver) {
5264
5496
  this.resizeObserver.disconnect();
5265
5497
  this.resizeObserver = null;
@@ -5288,6 +5520,7 @@ const _sfc_main$6 = {
5288
5520
  return;
5289
5521
  }
5290
5522
  this.$nextTick(() => {
5523
+ if (this._isUnmounted) return;
5291
5524
  const containerRef = this.$refs.containerRef;
5292
5525
  if (!containerRef) {
5293
5526
  return;
@@ -5667,10 +5900,11 @@ const _sfc_main$6 = {
5667
5900
  isLaneTrendDraw(lane) {
5668
5901
  return !!(lane && (lane.lines || []).some((line) => isLineDrawable(line)));
5669
5902
  },
5670
- // 获取泳道头部样式:地应力选中道,或 isDraw 所在道,红色边框
5903
+ // 获取泳道头部样式:地应力已选中的道都画红框。8.25
5671
5904
  getLaneHeaderStyle(lane) {
5672
5905
  const base = this.headerStyle;
5673
- const isActive = this.isGeomechanical && this.activeLaneId != null && lane && lane.laneId === this.activeLaneId || this.isLaneTrendDraw(lane);
5906
+ const ids = this.activeLaneIds || [];
5907
+ const isActive = this.isGeomechanical ? !!(lane && ids.some((id) => id === lane.laneId)) : this.isLaneTrendDraw(lane);
5674
5908
  if (!isActive) return base;
5675
5909
  return {
5676
5910
  ...base,
@@ -5683,7 +5917,10 @@ const _sfc_main$6 = {
5683
5917
  },
5684
5918
  closeCurveConfigDialog() {
5685
5919
  this.curveConfigClosing = true;
5686
- setTimeout(() => {
5920
+ if (this._curveConfigCloseTimer) clearTimeout(this._curveConfigCloseTimer);
5921
+ this._curveConfigCloseTimer = setTimeout(() => {
5922
+ this._curveConfigCloseTimer = 0;
5923
+ if (this._isUnmounted) return;
5687
5924
  this.curveConfigVisible = false;
5688
5925
  this.curveConfigClosing = false;
5689
5926
  }, 300);
@@ -6691,7 +6928,7 @@ function _sfc_render$6(_ctx, _cache, $props, $setup, $data, $options) {
6691
6928
  }, 8, ["modelValue", "onClose"])
6692
6929
  ], 544);
6693
6930
  }
6694
- const KdLaneContainerComponent = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["render", _sfc_render$6], ["__scopeId", "data-v-9d96e270"]]);
6931
+ const KdLaneContainerComponent = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["render", _sfc_render$6], ["__scopeId", "data-v-814fbe51"]]);
6695
6932
  const SLIDER_GROUP_ID = "depthSliderGroup";
6696
6933
  function pad2(n) {
6697
6934
  return n < 10 ? "0" + n : "" + n;
@@ -9298,11 +9535,86 @@ const _sfc_main$3 = {
9298
9535
  const model = this.chart.getModel();
9299
9536
  const list = model.queryComponents({ mainType: "graphic" }) || [];
9300
9537
  if (!list.length) return;
9301
- const graphic = list.map((m) => m.option).filter((item) => item && (item.id || item.elements || item.type));
9538
+ const graphic = list.map((m) => m.option).filter((item) => {
9539
+ if (!item || !(item.id || item.elements || item.type)) return false;
9540
+ const id = String(item.id || "");
9541
+ return id.indexOf("trend_") !== 0;
9542
+ });
9302
9543
  if (graphic.length) options.graphic = graphic;
9303
9544
  } catch (e) {
9304
9545
  }
9305
9546
  },
9547
+ // notMerge 时把笛卡尔趋势 series 带回去,避免先消失再画。8.25
9548
+ // 只拷绘制字段,禁止展开 model.option(会带上坐标系等内部引用,图表拆不掉)。8.25
9549
+ _copyTrendSeriesOption(opt, id) {
9550
+ if (!opt) return null;
9551
+ const ls = opt.lineStyle || {};
9552
+ const is = opt.itemStyle || {};
9553
+ return {
9554
+ id: opt.id || id,
9555
+ name: "trend",
9556
+ type: "line",
9557
+ data: Array.isArray(opt.data) ? opt.data.slice() : [],
9558
+ xAxisIndex: opt.xAxisIndex,
9559
+ yAxisIndex: opt.yAxisIndex,
9560
+ clip: opt.clip,
9561
+ animation: false,
9562
+ silent: true,
9563
+ large: false,
9564
+ z: opt.z,
9565
+ zlevel: 0,
9566
+ symbol: opt.symbol,
9567
+ symbolSize: opt.symbolSize,
9568
+ showSymbol: opt.showSymbol,
9569
+ hoverAnimation: false,
9570
+ legendHoverLink: false,
9571
+ connectNulls: false,
9572
+ tooltip: { show: false },
9573
+ emphasis: { disabled: true },
9574
+ lineStyle: {
9575
+ color: ls.color,
9576
+ width: ls.width,
9577
+ type: ls.type
9578
+ },
9579
+ itemStyle: {
9580
+ color: is.color,
9581
+ borderColor: is.borderColor,
9582
+ borderWidth: is.borderWidth
9583
+ }
9584
+ };
9585
+ },
9586
+ _preserveTrendSeries(options) {
9587
+ if (!options || !this.chart || this.chart.isDisposed()) return;
9588
+ if (options.__kdTrendSynced) return;
9589
+ const incoming = Array.isArray(options.series) ? options.series : [];
9590
+ const hasTrend = incoming.some((s) => {
9591
+ if (!s) return false;
9592
+ const name = Array.isArray(s.name) ? s.name[0] : s.name;
9593
+ if (name === "trend") return true;
9594
+ const id = String(s.id || "");
9595
+ return id.indexOf("trend_") === 0;
9596
+ });
9597
+ if (hasTrend) return;
9598
+ try {
9599
+ const list = this.chart.getModel().getSeries() || [];
9600
+ const trends = [];
9601
+ const seen = /* @__PURE__ */ new Set();
9602
+ for (let i = 0; i < list.length; i++) {
9603
+ const sm = list[i];
9604
+ const opt = sm && sm.option || {};
9605
+ const name = Array.isArray(sm.name) ? sm.name[0] : sm.name;
9606
+ const id = String(opt.id || sm.id || "");
9607
+ if (name !== "trend" && id.indexOf("trend_") !== 0) continue;
9608
+ if (!id || seen.has(id)) continue;
9609
+ seen.add(id);
9610
+ const copied = this._copyTrendSeriesOption(opt, id);
9611
+ if (copied) trends.push(copied);
9612
+ }
9613
+ if (!trends.length) return;
9614
+ options.series = incoming.concat(trends);
9615
+ } catch (e) {
9616
+ }
9617
+ },
9306
9618
  // dataZoom 写进即将下发的 option,不再二次 setOption 打断 Chrome 平移。8.24
9307
9619
  _applySavedDataZoomToOptions(options, savedState) {
9308
9620
  if (!savedState || !options) return;
@@ -9352,6 +9664,7 @@ const _sfc_main$3 = {
9352
9664
  const next = this.getOptionsWithTheme(options, skipClone);
9353
9665
  this._applySavedDataZoomToOptions(next, savedDataZoom);
9354
9666
  this._preserveGraphic(next);
9667
+ this._preserveTrendSeries(next);
9355
9668
  this.delegateMethod("setOption", next, {
9356
9669
  notMerge: !!notMerge,
9357
9670
  lazyUpdate: !!lazyUpdate
@@ -9518,6 +9831,7 @@ const _sfc_main$3 = {
9518
9831
  const next = this.getOptionsWithTheme(val);
9519
9832
  this._applySavedDataZoomToOptions(next, savedDataZoom);
9520
9833
  this._preserveGraphic(next);
9834
+ this._preserveTrendSeries(next);
9521
9835
  this.chart.setOption(next, {
9522
9836
  notMerge: this.skipClone || val !== oldVal
9523
9837
  });
@@ -9613,7 +9927,7 @@ const _hoisted_1$3 = { class: "echarts" };
9613
9927
  function _sfc_render$3(_ctx, _cache, $props, $setup, $data, $options) {
9614
9928
  return openBlock(), createElementBlock("div", _hoisted_1$3);
9615
9929
  }
9616
- const resizeEcharts = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["render", _sfc_render$3], ["__scopeId", "data-v-c1cce4a1"]]);
9930
+ const resizeEcharts = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["render", _sfc_render$3], ["__scopeId", "data-v-5b44c8a6"]]);
9617
9931
  const DRAW_POINT_MIN = 600;
9618
9932
  const DRAW_POINT_MAX = 1800;
9619
9933
  function getSeriesDrawThreshold(laneHeightPx) {
@@ -9994,6 +10308,7 @@ const _sfc_main$2 = {
9994
10308
  selectedParamId: null,
9995
10309
  // 当前选中的泳道ID(只有该泳道显示缺失点、区块、区块点)
9996
10310
  activeLaneId: null,
10311
+ activeLaneIds: [],
9997
10312
  currentMarkAreaIndex: -1,
9998
10313
  // 每个泳道的选中点对数组,每对包含两个点形成区块,与 markArea 通过 markAreaId 关联
9999
10314
  laneSelectedPointPairs: {},
@@ -10030,6 +10345,8 @@ const _sfc_main$2 = {
10030
10345
  this._isPanning = false;
10031
10346
  this._isZooming = false;
10032
10347
  this._zoomIdleTimer = 0;
10348
+ this._geoClickTimer = 0;
10349
+ this._laneZrHandlers = /* @__PURE__ */ Object.create(null);
10033
10350
  this._connectDetached = false;
10034
10351
  this._savedConnectGroups = null;
10035
10352
  this._tipHtmlBucket = "";
@@ -10039,7 +10356,7 @@ const _sfc_main$2 = {
10039
10356
  this._pointerRaf = 0;
10040
10357
  const pending = this._pendingPointer;
10041
10358
  if (!pending || !this.pointerInChartArea) return;
10042
- const { chart, x, y } = pending;
10359
+ const { chart, x, y, clientY } = pending;
10043
10360
  if (!chart || chart.isDisposed()) {
10044
10361
  this._pendingPointer = null;
10045
10362
  return;
@@ -10078,7 +10395,18 @@ const _sfc_main$2 = {
10078
10395
  clearTimeout(this._zoomIdleTimer);
10079
10396
  this._zoomIdleTimer = 0;
10080
10397
  }
10081
- if (this.trendLineManager) this.trendLineManager.destroy();
10398
+ if (this._geoClickTimer) {
10399
+ clearTimeout(this._geoClickTimer);
10400
+ this._geoClickTimer = 0;
10401
+ }
10402
+ Object.keys(this._laneZrHandlers || {}).forEach((laneId) => {
10403
+ this._unbindLaneZrEvents(laneId);
10404
+ });
10405
+ this._laneZrHandlers = /* @__PURE__ */ Object.create(null);
10406
+ if (this.trendLineManager) {
10407
+ this.trendLineManager.destroy();
10408
+ this.trendLineManager = null;
10409
+ }
10082
10410
  if (this._pointerRaf) {
10083
10411
  cancelAnimationFrame(this._pointerRaf);
10084
10412
  this._pointerRaf = 0;
@@ -10099,6 +10427,7 @@ const _sfc_main$2 = {
10099
10427
  this.attachChartConnect();
10100
10428
  this.unbindChartPointerSync();
10101
10429
  echarts.disConnect(this.group);
10430
+ this.chartRefMap = /* @__PURE__ */ Object.create(null);
10102
10431
  this.clearEcharts();
10103
10432
  if (this.resizeObserver) {
10104
10433
  this.resizeObserver.disconnect();
@@ -10118,6 +10447,7 @@ const _sfc_main$2 = {
10118
10447
  async laneWidthChange(widthMap) {
10119
10448
  this.laneWidthMap = widthMap || {};
10120
10449
  await this.$nextTick();
10450
+ if (this._isUnmounted) return;
10121
10451
  this.updateLanesChartOption();
10122
10452
  },
10123
10453
  // 曲线上方对数刻度:边控关则只藏尺。8.22对数轴
@@ -10182,43 +10512,77 @@ const _sfc_main$2 = {
10182
10512
  flex: "1"
10183
10513
  });
10184
10514
  }
10185
- const isDrawLane = this.isLaneTrendDrawById(laneId);
10186
- if (this.isGeomechanical || isDrawLane) {
10187
- const isActive = this.activeLaneId === laneId || isDrawLane;
10515
+ if (this.isGeomechanical) {
10188
10516
  style.boxSizing = "border-box";
10189
- style.border = isActive ? "1px solid #F86C59" : "1px solid transparent";
10517
+ style.border = this.isLaneSelected(laneId) ? "1px solid #F86C59" : "1px solid transparent";
10518
+ } else if (this.isLaneTrendDrawById(laneId)) {
10519
+ style.boxSizing = "border-box";
10520
+ style.border = "1px solid #F86C59";
10190
10521
  }
10191
10522
  return style;
10192
10523
  },
10524
+ // 按引用摘掉 click/dblclick,避免 zr.off(类型) 误拆趋势线 dblclick。8.25
10525
+ _unbindLaneZrEvents(laneId) {
10526
+ const rec = this._laneZrHandlers && this._laneZrHandlers[laneId];
10527
+ if (!rec) return;
10528
+ try {
10529
+ const chart = rec.chart;
10530
+ if (chart && typeof chart.getZr === "function" && !chart.isDisposed()) {
10531
+ const zr = chart.getZr();
10532
+ if (zr) {
10533
+ if (rec.onContext) zr.off("contextmenu", rec.onContext);
10534
+ if (rec.onClick) zr.off("click", rec.onClick);
10535
+ if (rec.onDbl) zr.off("dblclick", rec.onDbl);
10536
+ }
10537
+ }
10538
+ } catch (e) {
10539
+ }
10540
+ rec.chart = null;
10541
+ rec.onContext = null;
10542
+ rec.onClick = null;
10543
+ rec.onDbl = null;
10544
+ delete this._laneZrHandlers[laneId];
10545
+ },
10546
+ _bindLaneZrEvents(laneId, chart) {
10547
+ this._unbindLaneZrEvents(laneId);
10548
+ if (!chart || chart.isDisposed()) return;
10549
+ let zr;
10550
+ try {
10551
+ zr = chart.getZr();
10552
+ } catch (e) {
10553
+ return;
10554
+ }
10555
+ if (!zr) return;
10556
+ const onContext = (e) => this.handleChartRightClick(e, laneId);
10557
+ const onClick = (e) => this.handleChartClick(e, laneId);
10558
+ const onDbl = (e) => this.handleChartDblClick(e, laneId);
10559
+ zr.on("contextmenu", onContext);
10560
+ zr.on("click", onClick);
10561
+ zr.on("dblclick", onDbl);
10562
+ this._laneZrHandlers[laneId] = { chart, onContext, onClick, onDbl };
10563
+ },
10193
10564
  setChartRef(laneId, el) {
10194
10565
  if (el) {
10195
- if (this.chartRefMap[laneId] && this.chartRefMap[laneId].chart) {
10196
- this.chartRefMap[laneId].chart.getZr().off("contextmenu");
10197
- this.chartRefMap[laneId].chart.getZr().off("click");
10566
+ const prev = this.chartRefMap[laneId];
10567
+ if (prev && prev.chart && prev.chart !== el.chart) {
10568
+ this._unbindLaneZrEvents(laneId);
10198
10569
  }
10199
10570
  this.chartRefMap[laneId] = el;
10200
- el.chart && el.chart.getZr().on("contextmenu", (e) => this.handleChartRightClick(e, laneId));
10201
- el.chart && el.chart.getZr().on("click", (e) => this.handleChartClick(e, laneId));
10571
+ if (el.chart) this._bindLaneZrEvents(laneId, el.chart);
10202
10572
  if (el.chart && this.trendLineManager) {
10203
10573
  this.trendLineManager.bindChart(laneId, el.chart);
10204
10574
  }
10205
10575
  } else if (this.chartRefMap[laneId]) {
10206
10576
  if (this.trendLineManager) this.trendLineManager.unbindChart(laneId);
10207
- this.chartRefMap[laneId].chart && this.chartRefMap[laneId].chart.getZr().off("contextmenu");
10208
- this.chartRefMap[laneId].chart && this.chartRefMap[laneId].chart.getZr().off("click");
10577
+ this._unbindLaneZrEvents(laneId);
10209
10578
  delete this.chartRefMap[laneId];
10210
10579
  }
10211
10580
  },
10212
10581
  handleChartInited(laneId, chart) {
10213
- if (this.chartRefMap[laneId]) {
10214
- this.chartRefMap[laneId].chart.getZr().off("contextmenu");
10215
- this.chartRefMap[laneId].chart.getZr().off("click");
10216
- }
10217
- chart.getZr().on("contextmenu", (e) => this.handleChartRightClick(e, laneId));
10218
- chart.getZr().on("click", (e) => this.handleChartClick(e, laneId));
10582
+ this._bindLaneZrEvents(laneId, chart);
10219
10583
  if (this.trendLineManager) {
10220
10584
  this.trendLineManager.bindChart(laneId, chart);
10221
- this.applyLineListData();
10585
+ if ((this.lineListData || []).length) this.applyLineListData();
10222
10586
  this.trendLineManager.scheduleRender();
10223
10587
  }
10224
10588
  this.scheduleConnectAllCharts();
@@ -10230,14 +10594,24 @@ const _sfc_main$2 = {
10230
10594
  this.getConnectedCharts().forEach((chart) => {
10231
10595
  if (!chart || chart.isDisposed()) return;
10232
10596
  const laneId = this.findLaneIdByChart(chart);
10233
- chart.setOption({
10234
- dataZoom: [
10235
- this.buildInsideDataZoom(
10236
- !!enabled,
10237
- this.dataZoomFilterModeForLane(this.findLaneById(laneId))
10238
- )
10239
- ]
10240
- });
10597
+ const next = this.buildInsideDataZoom(
10598
+ !!enabled,
10599
+ this.dataZoomFilterModeForLane(this.findLaneById(laneId))
10600
+ );
10601
+ try {
10602
+ const dz = chart.getModel().getComponent("dataZoom", 0);
10603
+ const opt = dz && dz.option;
10604
+ if (opt) {
10605
+ if (opt.start != null) next.start = opt.start;
10606
+ if (opt.end != null) next.end = opt.end;
10607
+ if (opt.startValue != null) next.startValue = opt.startValue;
10608
+ if (opt.endValue != null) next.endValue = opt.endValue;
10609
+ }
10610
+ const skip = this.trendLineManager && this.trendLineManager.dataZoomSeriesIndex(chart);
10611
+ if (skip && skip.length) next.seriesIndex = skip;
10612
+ } catch (e) {
10613
+ }
10614
+ chart.setOption({ dataZoom: [next] }, { silent: true, lazyUpdate: false });
10241
10615
  });
10242
10616
  },
10243
10617
  getConnectedCharts() {
@@ -10249,7 +10623,7 @@ const _sfc_main$2 = {
10249
10623
  // 趋势线控制点右键菜单(删除控制点) 8.21
10250
10624
  showTrendKnotContextMenu(e, payload) {
10251
10625
  this.currentContextMenuType = "trendKnot";
10252
- this.currentPointInfo = payload;
10626
+ this.currentPointInfo = markRaw(payload);
10253
10627
  this.currentContextMenuItems = [{ label: "删除控制点", action: "deleteTrendKnot" }];
10254
10628
  this.calculateContextMenuPosition(e);
10255
10629
  this.contextMenuVisible = true;
@@ -10282,6 +10656,85 @@ const _sfc_main$2 = {
10282
10656
  resetAllTrendLines() {
10283
10657
  if (this.trendLineManager) this.trendLineManager.resetAllTrendLines();
10284
10658
  },
10659
+ isLaneSelected(laneId) {
10660
+ return laneId != null && (this.activeLaneIds || []).some((id) => id === laneId);
10661
+ },
10662
+ addLaneSelection(laneId) {
10663
+ if (laneId == null) return false;
10664
+ this.activeLaneId = laneId;
10665
+ if (this.isLaneSelected(laneId)) return false;
10666
+ this.activeLaneIds = this.activeLaneIds.concat([laneId]);
10667
+ return true;
10668
+ },
10669
+ removeLaneSelection(laneId) {
10670
+ if (!this.isLaneSelected(laneId)) return;
10671
+ this.activeLaneIds = (this.activeLaneIds || []).filter((id) => id !== laneId);
10672
+ if (this.activeLaneId === laneId) {
10673
+ this.activeLaneId = this.activeLaneIds.length ? this.activeLaneIds[this.activeLaneIds.length - 1] : null;
10674
+ }
10675
+ },
10676
+ // 退出编辑:清掉全部选中红框,不挂新监听。8.25
10677
+ clearActiveLane() {
10678
+ this._clearGeoClickTimer();
10679
+ this._geoSelectClickAt = 0;
10680
+ this._geoSelectLaneId = null;
10681
+ this._lastBlankClickTime = null;
10682
+ this._lastBlankClickLaneId = null;
10683
+ const ids = (this.activeLaneIds || []).slice();
10684
+ if (this.selectedPoints.length > 0 && this.selectedLaneId) {
10685
+ if (this.laneSelectedPointPairs[this.selectedLaneId]) {
10686
+ this.laneSelectedPointPairs[this.selectedLaneId].pop();
10687
+ }
10688
+ this.selectedPoints = [];
10689
+ this.selectedLaneId = null;
10690
+ this.selectedParamId = null;
10691
+ }
10692
+ this.activeLaneIds = [];
10693
+ this.activeLaneId = null;
10694
+ if (!ids.length) return;
10695
+ this.updateLanesChartOption().then(() => {
10696
+ if (this._isUnmounted) return;
10697
+ ids.forEach((id) => this.showSelectedPointScatter(id));
10698
+ });
10699
+ },
10700
+ // 切趋势线:只保留当前曲线所在道;单击其它道仍可再选中。8.25
10701
+ selectLaneByParamId(paramId) {
10702
+ if (!this.isGeomechanical) return;
10703
+ if (paramId == null || paramId === "") {
10704
+ this.clearActiveLane();
10705
+ return;
10706
+ }
10707
+ const lanes = this.currentTemplate && this.currentTemplate.lanes || [];
10708
+ let laneId = null;
10709
+ for (let i = 0; i < lanes.length; i++) {
10710
+ const lane = lanes[i];
10711
+ if ((lane.lines || []).some((line) => String(line.paramId) === String(paramId))) {
10712
+ laneId = lane.laneId;
10713
+ break;
10714
+ }
10715
+ }
10716
+ if (!laneId) return;
10717
+ const prevIds = (this.activeLaneIds || []).slice();
10718
+ if (prevIds.length === 1 && prevIds[0] === laneId) {
10719
+ this.activeLaneId = laneId;
10720
+ return;
10721
+ }
10722
+ if (this.selectedPoints.length > 0 && this.selectedLaneId && this.selectedLaneId !== laneId) {
10723
+ if (this.laneSelectedPointPairs[this.selectedLaneId]) {
10724
+ this.laneSelectedPointPairs[this.selectedLaneId].pop();
10725
+ }
10726
+ this.selectedPoints = [];
10727
+ this.selectedLaneId = null;
10728
+ this.selectedParamId = null;
10729
+ }
10730
+ this.activeLaneIds = [laneId];
10731
+ this.activeLaneId = laneId;
10732
+ this.updateLanesChartOption().then(() => {
10733
+ if (this._isUnmounted) return;
10734
+ prevIds.forEach((id) => this.showSelectedPointScatter(id));
10735
+ this.showSelectedPointScatter(laneId);
10736
+ });
10737
+ },
10285
10738
  // 根据 paramId 判断曲线是否可见(isUsed === '1')
10286
10739
  isLineVisibleByParamId(paramId) {
10287
10740
  const lanes = this.currentTemplate && this.currentTemplate.lanes || [];
@@ -10400,7 +10853,7 @@ const _sfc_main$2 = {
10400
10853
  const sm = seriesList[s];
10401
10854
  const option = sm.option || {};
10402
10855
  const name = this.unwrapEchartsValue(sm.name);
10403
- if (name === "helper" || name === "log-grid") continue;
10856
+ if (name === "helper" || name === "log-grid" || name === "trend") continue;
10404
10857
  const type = sm.subType || this.unwrapEchartsValue(option.type);
10405
10858
  if (type && type !== "line") continue;
10406
10859
  const tip = option.tooltip;
@@ -10561,6 +11014,7 @@ const _sfc_main$2 = {
10561
11014
  if (!series || !Array.isArray(series.data) || !series.data.length) return false;
10562
11015
  if (this.seriesDisplayName(series) === "helper") return false;
10563
11016
  if (this.seriesDisplayName(series) === "log-grid") return false;
11017
+ if (this.seriesDisplayName(series) === "trend") return false;
10564
11018
  const type = this.seriesTypeName(series);
10565
11019
  if (type && type !== "line") return false;
10566
11020
  if (this.seriesTooltipHidden(series)) return false;
@@ -10715,8 +11169,8 @@ const _sfc_main$2 = {
10715
11169
  el.style.transform = `translate3d(0, ${top}px, 0)`;
10716
11170
  if (!this.crosshairVisible) this.crosshairVisible = true;
10717
11171
  },
10718
- schedulePointerSync(chart, x, y) {
10719
- this._pendingPointer = { chart, x, y };
11172
+ schedulePointerSync(chart, x, y, clientY) {
11173
+ this._pendingPointer = { chart, x, y, clientY };
10720
11174
  if (this._pointerRaf) return;
10721
11175
  this._pointerRaf = requestAnimationFrame(this._flushPointerSync);
10722
11176
  },
@@ -10856,6 +11310,7 @@ const _sfc_main$2 = {
10856
11310
  },
10857
11311
  startChartPan() {
10858
11312
  if (!this.isGeomechanical || this._isPanning) return;
11313
+ if (this._chartPanLocked) return;
10859
11314
  this._isPanning = true;
10860
11315
  this.attachChartConnect();
10861
11316
  this.hideLaneTooltipsForPan();
@@ -10879,6 +11334,7 @@ const _sfc_main$2 = {
10879
11334
  this._zoomIdleTimer = 0;
10880
11335
  if (!this._isZooming) return;
10881
11336
  this._isZooming = false;
11337
+ if (!this._isPanning && this.trendLineManager) this.trendLineManager.endFollowView();
10882
11338
  this.restorePointerAfterInteract();
10883
11339
  },
10884
11340
  hideAllLaneTooltips(event) {
@@ -10955,7 +11411,8 @@ const _sfc_main$2 = {
10955
11411
  this.schedulePointerSync(
10956
11412
  chart,
10957
11413
  e.offsetX ?? ((_a = e.event) == null ? void 0 : _a.offsetX),
10958
- e.offsetY ?? ((_b = e.event) == null ? void 0 : _b.offsetY)
11414
+ e.offsetY ?? ((_b = e.event) == null ? void 0 : _b.offsetY),
11415
+ native.clientY
10959
11416
  );
10960
11417
  };
10961
11418
  zr.on("mousedown", onDown);
@@ -10992,14 +11449,22 @@ const _sfc_main$2 = {
10992
11449
  async templateChange(template) {
10993
11450
  this.currentTemplate = template;
10994
11451
  this.getLatestLineDataByParamCode();
10995
- if (this.trendLineManager) this.trendLineManager.hydrateFromTemplate(template);
10996
- this.applyLineListData();
11452
+ let migratedLanes = null;
11453
+ if (this.trendLineManager) {
11454
+ migratedLanes = this.trendLineManager.hydrateFromTemplate(template);
11455
+ }
11456
+ if ((this.lineListData || []).length) this.applyLineListData();
10997
11457
  await this.$nextTick();
10998
- this.updateLanesChartOption();
11458
+ if (this._isUnmounted) return;
11459
+ await this.updateLanesChartOption();
11460
+ if (this.trendLineManager && migratedLanes && migratedLanes.size) {
11461
+ migratedLanes.forEach((laneId) => this.trendLineManager._flushLane(laneId));
11462
+ }
10999
11463
  },
11000
11464
  // 线段变化
11001
11465
  async lineChange() {
11002
11466
  await this.$nextTick();
11467
+ if (this._isUnmounted) return;
11003
11468
  this.updateLanesChartOption();
11004
11469
  },
11005
11470
  // 初始化窗口大小范围以及windowSize默认值
@@ -11155,6 +11620,7 @@ const _sfc_main$2 = {
11155
11620
  option.yAxis = [{}];
11156
11621
  }
11157
11622
  const cachedOption = markRaw(option);
11623
+ if (this.trendLineManager) cachedOption.__kdTrendSynced = true;
11158
11624
  if (index2 != -1) {
11159
11625
  this.allOptions.splice(index2, 1, { lineId, option: cachedOption });
11160
11626
  } else {
@@ -11174,10 +11640,10 @@ const _sfc_main$2 = {
11174
11640
  ref.mergeOptions(option, true, false, true);
11175
11641
  },
11176
11642
  // 判断指定泳道是否应该显示tooltip内容
11177
- // 地应力模式下:如果没有选中泳道,所有泳道正常显示;如果有选中泳道,只有选中的泳道显示
11643
+ // 地应力:无选中时所有泳道显示;有选中时已选中的多道都显示。8.25
11178
11644
  shouldShowTooltipContent(laneId) {
11179
11645
  if (!this.showTooltip) return false;
11180
- if (this.isGeomechanical && this.activeLaneId && laneId !== this.activeLaneId) {
11646
+ if (this.isGeomechanical && (this.activeLaneIds || []).length && !this.isLaneSelected(laneId)) {
11181
11647
  return false;
11182
11648
  }
11183
11649
  return true;
@@ -11633,7 +12099,7 @@ const _sfc_main$2 = {
11633
12099
  return { data: [[x, group.min], [x, group.max]] };
11634
12100
  }
11635
12101
  const type = sm.subType || this.unwrapEchartsValue(opt.type);
11636
- if (name === "log-grid" || type !== "line") return {};
12102
+ if (name === "log-grid" || name === "trend" || type !== "line") return {};
11637
12103
  const paramId = this.unwrapEchartsValue(opt.paramId);
11638
12104
  if (paramId == null) return {};
11639
12105
  const cacheKey = `${gi}:${paramId}:${isLog ? 1 : 0}`;
@@ -11665,7 +12131,6 @@ const _sfc_main$2 = {
11665
12131
  } catch (e) {
11666
12132
  }
11667
12133
  });
11668
- if (this.trendLineManager) this.trendLineManager.scheduleZoomRender();
11669
12134
  },
11670
12135
  // 处理键盘按下事件
11671
12136
  handleKeyDown(e) {
@@ -12250,6 +12715,7 @@ const _sfc_main$2 = {
12250
12715
  },
12251
12716
  // 更新泳道图表选项
12252
12717
  async updateLanesChartOption(transferData) {
12718
+ if (this._isUnmounted) return;
12253
12719
  if (this.isUpdatingChart) {
12254
12720
  this.pendingChartUpdate = true;
12255
12721
  return;
@@ -12567,7 +13033,7 @@ const _sfc_main$2 = {
12567
13033
  if (rangeAreaSeries) {
12568
13034
  option.series.push(rangeAreaSeries);
12569
13035
  }
12570
- if (lineInfo.isUsed === "1" && this.isGeomechanical && lane.laneId === this.activeLaneId) {
13036
+ if (lineInfo.isUsed === "1" && this.isGeomechanical && this.isLaneSelected(lane.laneId)) {
12571
13037
  const boundaryPoints = this.findMissingDataBoundaryPoints(
12572
13038
  this.plotSeries(groupSeriesData, isLogScale)
12573
13039
  );
@@ -12720,12 +13186,17 @@ const _sfc_main$2 = {
12720
13186
  option.tooltip.extraCssText = "pointer-events: none !important;";
12721
13187
  option.tooltip.formatter = () => this.renderLaneTooltipHtml(laneId);
12722
13188
  }
13189
+ if (this.trendLineManager) this.trendLineManager.appendSeriesToOption(option, lane);
12723
13190
  this.updateOptionsByLineId(lane.laneId, option);
12724
13191
  }
12725
13192
  } catch (error) {
12726
13193
  console.error("updateLanesChartOption error:", error);
12727
13194
  } finally {
12728
13195
  this.isUpdatingChart = false;
13196
+ if (this._isUnmounted) {
13197
+ this.pendingChartUpdate = false;
13198
+ return;
13199
+ }
12729
13200
  this.scheduleConnectAllCharts();
12730
13201
  const lanesToRefresh = new Set(
12731
13202
  Object.keys(this.laneSelectedPointPairs || {})
@@ -12733,6 +13204,7 @@ const _sfc_main$2 = {
12733
13204
  if (this.selectedLaneId) lanesToRefresh.add(this.selectedLaneId);
12734
13205
  if (lanesToRefresh.size > 0) {
12735
13206
  this.$nextTick(() => {
13207
+ if (this._isUnmounted) return;
12736
13208
  lanesToRefresh.forEach((lid) => {
12737
13209
  this.showSelectedPointScatter(lid);
12738
13210
  });
@@ -12741,13 +13213,10 @@ const _sfc_main$2 = {
12741
13213
  if (this.pendingChartUpdate) {
12742
13214
  this.pendingChartUpdate = false;
12743
13215
  this.$nextTick(() => {
13216
+ if (this._isUnmounted) return;
12744
13217
  this.updateLanesChartOption(transferData);
12745
13218
  });
12746
13219
  }
12747
- if (this.trendLineManager) {
12748
- this.trendLineManager.scheduleZoomRender();
12749
- this.$nextTick(() => this.trendLineManager.scheduleZoomRender());
12750
- }
12751
13220
  }
12752
13221
  },
12753
13222
  /**
@@ -13040,85 +13509,172 @@ const _sfc_main$2 = {
13040
13509
  }
13041
13510
  return boundaryPoints;
13042
13511
  },
13043
- async handleChartClick(e, laneId) {
13044
- var _a, _b, _c;
13045
- const chartRef = this.getChartRefByLaneId(laneId);
13046
- if (!chartRef || !chartRef.chart) return;
13047
- if (this.trendLineManager && this.trendLineManager.handleClick(e, laneId)) {
13048
- return;
13049
- }
13050
- if (!this.isGeomechanical) return;
13051
- const wasActiveLane = this.activeLaneId === laneId;
13052
- if (this.activeLaneId !== laneId) {
13053
- const prevActiveLaneId = this.activeLaneId;
13054
- this.activeLaneId = laneId;
13055
- await this.updateLanesChartOption();
13056
- if (prevActiveLaneId) {
13057
- this.showSelectedPointScatter(prevActiveLaneId);
13512
+ // 只把点击附近的点转成像素。全量 convertToPixel 会堵住主线程,双击 500ms 对不上。8.25
13513
+ findClosestGeoLineHit(chart, option, pointInPixel, pixelThreshold, laneId) {
13514
+ var _a;
13515
+ const threshold = pixelThreshold || 10;
13516
+ const px = pointInPixel[0];
13517
+ const py = pointInPixel[1];
13518
+ const seriesList = option && option.series || [];
13519
+ let closest = null;
13520
+ let closestDistance = threshold;
13521
+ for (let i = 0; i < seriesList.length; i++) {
13522
+ const series2 = seriesList[i];
13523
+ if (!series2 || series2.type !== "line") continue;
13524
+ const name = Array.isArray(series2.name) ? series2.name[0] : series2.name;
13525
+ if (name === "helper" || name === "log-grid" || name === "trend") continue;
13526
+ const id = String(series2.id || "");
13527
+ if (id.indexOf("trend_") === 0) continue;
13528
+ let clickData;
13529
+ try {
13530
+ clickData = chart.convertFromPixel({ seriesIndex: i }, [px, py]);
13531
+ } catch (err) {
13532
+ continue;
13058
13533
  }
13059
- }
13060
- const chart = chartRef.chart;
13061
- const option = this.readChartOptionRefs(chart);
13062
- const pointInPixel = [
13063
- e.offsetX || ((_a = e.event) == null ? void 0 : _a.offsetX),
13064
- e.offsetY || ((_b = e.event) == null ? void 0 : _b.offsetY)
13065
- ];
13066
- let clickedPoint = null;
13067
- let closestPoint = null;
13068
- let closestDistance = 10;
13069
- const axisKey = this.currentToolBarConfig.axisTypeList && this.currentToolBarConfig.axisTypeList[this.formCache.axisType] || (this.formCache.axisType === "time" ? "time" : "depth");
13070
- for (let i = 0; i < option.series.length; i++) {
13071
- const series = option.series[i];
13072
- if (series.name === "helper" || series.name === "log-grid") continue;
13073
- if (series.type !== "line") continue;
13074
- const data = series.data || [];
13534
+ if (!clickData || clickData.length < 2) continue;
13535
+ let yTol = Infinity;
13536
+ try {
13537
+ const offset = chart.convertFromPixel({ seriesIndex: i }, [px, py + threshold]);
13538
+ if (offset && offset.length >= 2) {
13539
+ const span = Math.abs(offset[1] - clickData[1]);
13540
+ if (Number.isFinite(span) && span > 0) yTol = span * 1.25;
13541
+ }
13542
+ } catch (err) {
13543
+ yTol = Infinity;
13544
+ }
13545
+ const data = series2.data || [];
13075
13546
  for (let j = 0; j < data.length; j++) {
13076
- const item = data[j];
13077
- if (!item) continue;
13078
- let pointValue;
13079
- pointValue = Array.isArray(item) ? item : item.value || [];
13080
- if (!pointValue || !Array.isArray(pointValue) || pointValue.length < 2)
13081
- continue;
13082
- const point = chart.convertToPixel({ seriesIndex: i }, pointValue);
13547
+ const item2 = data[j];
13548
+ if (!item2) continue;
13549
+ const pointValue2 = Array.isArray(item2) ? item2 : item2.value || [];
13550
+ if (!pointValue2 || pointValue2.length < 2) continue;
13551
+ if (Number.isFinite(yTol) && Math.abs(pointValue2[1] - clickData[1]) > yTol) continue;
13552
+ const point = chart.convertToPixel({ seriesIndex: i }, pointValue2);
13083
13553
  if (!point) continue;
13084
- const distance = Math.sqrt(
13085
- Math.pow(point[0] - pointInPixel[0], 2) + Math.pow(point[1] - pointInPixel[1], 2)
13086
- );
13554
+ const distance = Math.hypot(point[0] - px, point[1] - py);
13087
13555
  if (distance < closestDistance) {
13088
13556
  closestDistance = distance;
13089
- const axisValue = pointValue[1];
13090
- const originalData = this.realTimeData.find(
13091
- (d) => d[axisKey] == axisValue
13092
- );
13093
- const paramId = item.paramId || series.paramId;
13094
- const lane = (this.currentTemplate && this.currentTemplate.lanes || []).find(
13095
- (l) => l.laneId === laneId
13096
- );
13097
- const originalX = (item.rowData && item.rowData[paramId]) ?? (originalData && originalData[paramId]);
13098
- option.xAxis && option.xAxis[series.xAxisIndex ?? 0] || {};
13099
- const useOriginal = originalX != null && Number(originalX) > 0;
13100
- const drawX = isLaneLogScale(lane) ? toLaneDrawX(
13101
- useOriginal ? originalX : pointValue[0],
13102
- lane
13103
- ) : pointValue[0];
13104
- closestPoint = {
13105
- ...item,
13106
- value: [drawX, pointValue[1], pointValue[2]],
13107
- seriesIndex: i,
13108
- dataIndex: j,
13109
- paramId,
13110
- lineId: item.lineId || series.lineId,
13111
- color: series.color,
13112
- laneId,
13113
- depth: (originalData == null ? void 0 : originalData.depth) || null,
13114
- timestamp: ((_c = originalData == null ? void 0 : originalData.timestamp) == null ? void 0 : _c.toString()) || null
13115
- };
13557
+ closest = { item: item2, series: series2, seriesIndex: i, dataIndex: j, pointValue: pointValue2, distance };
13116
13558
  }
13117
13559
  }
13118
13560
  }
13119
- if (closestPoint && closestDistance < 10) {
13120
- clickedPoint = closestPoint;
13561
+ if (!closest) return null;
13562
+ const axisKey = this.currentToolBarConfig.axisTypeList && this.currentToolBarConfig.axisTypeList[this.formCache.axisType] || (this.formCache.axisType === "time" ? "time" : "depth");
13563
+ const { item, series, seriesIndex, dataIndex, pointValue } = closest;
13564
+ const originalData = item && item.rowData || (this.realTimeData || []).find((d) => d[axisKey] == pointValue[1]);
13565
+ const paramId = item && item.paramId || series.paramId;
13566
+ const lane = (this.currentTemplate && this.currentTemplate.lanes || []).find(
13567
+ (l) => l.laneId === laneId
13568
+ );
13569
+ const originalX = (item.rowData && item.rowData[paramId]) ?? (originalData && originalData[paramId]);
13570
+ option.xAxis && option.xAxis[series.xAxisIndex ?? 0] || {};
13571
+ const useOriginal = originalX != null && Number(originalX) > 0;
13572
+ const drawX = isLaneLogScale(lane) ? toLaneDrawX(
13573
+ useOriginal ? originalX : pointValue[0],
13574
+ lane
13575
+ ) : pointValue[0];
13576
+ return {
13577
+ ...item,
13578
+ value: [drawX, pointValue[1], pointValue[2]],
13579
+ seriesIndex,
13580
+ dataIndex,
13581
+ paramId,
13582
+ lineId: item && item.lineId || series.lineId,
13583
+ color: series.color,
13584
+ laneId,
13585
+ depth: (originalData == null ? void 0 : originalData.depth) || null,
13586
+ timestamp: ((_a = originalData == null ? void 0 : originalData.timestamp) == null ? void 0 : _a.toString()) || null
13587
+ };
13588
+ },
13589
+ _clearGeoClickTimer() {
13590
+ if (this._geoClickTimer) {
13591
+ clearTimeout(this._geoClickTimer);
13592
+ this._geoClickTimer = 0;
13593
+ }
13594
+ },
13595
+ // 地应力:双击已选中泳道,只取消这一道,其它选中保留。8.25
13596
+ handleChartDblClick(e, laneId) {
13597
+ if (this._isUnmounted || !this.isGeomechanical) return;
13598
+ this._clearGeoClickTimer();
13599
+ if (!this.isLaneSelected(laneId)) return;
13600
+ if (this._geoSelectClickAt && this._geoSelectLaneId === laneId && Date.now() - this._geoSelectClickAt < 500) {
13601
+ return;
13602
+ }
13603
+ this.deselectGeoLane(laneId);
13604
+ },
13605
+ async deselectGeoLane(laneId) {
13606
+ const id = laneId != null ? laneId : this.activeLaneId;
13607
+ this._lastBlankClickTime = null;
13608
+ this._lastBlankClickLaneId = null;
13609
+ if (!id || !this.isLaneSelected(id)) return;
13610
+ if (this.selectedPoints.length > 0 && this.selectedLaneId === id) {
13611
+ if (this.laneSelectedPointPairs[this.selectedLaneId]) {
13612
+ this.laneSelectedPointPairs[this.selectedLaneId].pop();
13613
+ }
13614
+ this.selectedPoints = [];
13615
+ this.selectedLaneId = null;
13616
+ this.selectedParamId = null;
13121
13617
  }
13618
+ this.removeLaneSelection(id);
13619
+ await this.updateLanesChartOption();
13620
+ if (this._isUnmounted) return;
13621
+ this.showSelectedPointScatter(id);
13622
+ },
13623
+ async handleChartClick(e, laneId) {
13624
+ var _a, _b, _c, _d;
13625
+ if (this._isUnmounted) return;
13626
+ const clickAt = Date.now();
13627
+ const chartRef = this.getChartRefByLaneId(laneId);
13628
+ if (!chartRef || !chartRef.chart) return;
13629
+ const wasActiveLane = this.isLaneSelected(laneId);
13630
+ const trendConsumed = this.trendLineManager && this.trendLineManager.handleClick(e, laneId);
13631
+ if (!this.isGeomechanical) return;
13632
+ if (!wasActiveLane) {
13633
+ this._geoSelectClickAt = clickAt;
13634
+ this._geoSelectLaneId = laneId;
13635
+ this.addLaneSelection(laneId);
13636
+ await this.updateLanesChartOption();
13637
+ if (this._isUnmounted) return;
13638
+ this.showSelectedPointScatter(laneId);
13639
+ }
13640
+ if (trendConsumed) return;
13641
+ if (wasActiveLane) {
13642
+ this._clearGeoClickTimer();
13643
+ const offsetX = e.offsetX || ((_a = e.event) == null ? void 0 : _a.offsetX);
13644
+ const offsetY = e.offsetY || ((_b = e.event) == null ? void 0 : _b.offsetY);
13645
+ this._geoClickTimer = setTimeout(() => {
13646
+ this._geoClickTimer = 0;
13647
+ if (this._isUnmounted) return;
13648
+ this.processGeoPointOrBlank(laneId, offsetX, offsetY, clickAt, true);
13649
+ }, 280);
13650
+ return;
13651
+ }
13652
+ const pointInPixel = [
13653
+ e.offsetX || ((_c = e.event) == null ? void 0 : _c.offsetX),
13654
+ e.offsetY || ((_d = e.event) == null ? void 0 : _d.offsetY)
13655
+ ];
13656
+ await this.processGeoPointOrBlank(
13657
+ laneId,
13658
+ pointInPixel[0],
13659
+ pointInPixel[1],
13660
+ clickAt,
13661
+ false
13662
+ );
13663
+ },
13664
+ async processGeoPointOrBlank(laneId, offsetX, offsetY, clickAt, wasActiveLane) {
13665
+ if (this._isUnmounted) return;
13666
+ const chartRef = this.getChartRefByLaneId(laneId);
13667
+ if (!chartRef || !chartRef.chart) return;
13668
+ const chart = chartRef.chart;
13669
+ const option = this.readChartOptionRefs(chart);
13670
+ const closestPoint = this.findClosestGeoLineHit(
13671
+ chart,
13672
+ option,
13673
+ [offsetX, offsetY],
13674
+ 10,
13675
+ laneId
13676
+ );
13677
+ const clickedPoint = closestPoint || null;
13122
13678
  if (clickedPoint) {
13123
13679
  this._lastBlankClickTime = null;
13124
13680
  this._lastBlankClickLaneId = null;
@@ -13177,18 +13733,19 @@ const _sfc_main$2 = {
13177
13733
  this.showSelectedPointScatter(laneId);
13178
13734
  }
13179
13735
  if (wasActiveLane) {
13180
- const now = Date.now();
13736
+ const now = clickAt;
13181
13737
  if (this._lastBlankClickLaneId === laneId && this._lastBlankClickTime && now - this._lastBlankClickTime < 500) {
13182
13738
  this._lastBlankClickTime = null;
13183
13739
  this._lastBlankClickLaneId = null;
13184
- if (this.selectedPoints.length > 0 && this.selectedLaneId) {
13740
+ if (this.selectedPoints.length > 0 && this.selectedLaneId === laneId) {
13185
13741
  this.laneSelectedPointPairs[this.selectedLaneId].pop();
13186
13742
  this.selectedPoints = [];
13187
13743
  this.selectedLaneId = null;
13188
13744
  this.selectedParamId = null;
13189
13745
  }
13190
- this.activeLaneId = null;
13746
+ this.removeLaneSelection(laneId);
13191
13747
  await this.updateLanesChartOption();
13748
+ if (this._isUnmounted) return;
13192
13749
  } else {
13193
13750
  this._lastBlankClickTime = now;
13194
13751
  this._lastBlankClickLaneId = laneId;
@@ -13199,12 +13756,13 @@ const _sfc_main$2 = {
13199
13756
  }
13200
13757
  }
13201
13758
  await this.$nextTick();
13759
+ if (this._isUnmounted) return;
13202
13760
  this.showSelectedPointScatter(laneId);
13203
13761
  },
13204
13762
  // 收集某泳道所有需要展示 scatter 的点:已完成区块的两个点 + 进行中的第一个点
13205
- // 仅在地应力模式下的当前激活泳道才收集并显示
13763
+ // 地应力已选中的泳道都收集并显示。8.25
13206
13764
  collectScatterPointsForLane(laneId) {
13207
- if (!this.isGeomechanical || laneId !== this.activeLaneId) return [];
13765
+ if (!this.isGeomechanical || !this.isLaneSelected(laneId)) return [];
13208
13766
  const points = [];
13209
13767
  const pairs = this.laneSelectedPointPairs[laneId] || [];
13210
13768
  pairs.forEach((pair) => {
@@ -13333,15 +13891,9 @@ const _sfc_main$2 = {
13333
13891
  return;
13334
13892
  }
13335
13893
  if (!this.isGeomechanical) return;
13336
- if (this.activeLaneId !== laneId) {
13337
- const prevActiveLaneId = this.activeLaneId;
13338
- this.activeLaneId = laneId;
13894
+ if (!this.isLaneSelected(laneId)) {
13895
+ this.addLaneSelection(laneId);
13339
13896
  this.updateLanesChartOption();
13340
- if (prevActiveLaneId) {
13341
- this.$nextTick(() => {
13342
- this.showSelectedPointScatter(prevActiveLaneId);
13343
- });
13344
- }
13345
13897
  }
13346
13898
  const chart = chartRef.chart;
13347
13899
  const pointInPixel = [
@@ -13956,7 +14508,7 @@ const _sfc_main$2 = {
13956
14508
  for (let i = 0; i < currentMarkAreaData.length; i++) {
13957
14509
  const areaItem = currentMarkAreaData[i];
13958
14510
  if (!areaItem.data || areaItem.data.length < 2) continue;
13959
- if (lineInfo.laneId !== this.activeLaneId && areaItem.type === "markArea")
14511
+ if (!this.isLaneSelected(lineInfo.laneId) && areaItem.type === "markArea")
13960
14512
  continue;
13961
14513
  const hasTimestamp = areaItem.data[0].timestamp;
13962
14514
  const sortedData = hasTimestamp ? [...areaItem.data].sort(
@@ -14410,7 +14962,7 @@ const _sfc_main$2 = {
14410
14962
  }
14411
14963
  }
14412
14964
  seriesIndex++;
14413
- if (lineInfo.isUsed === "1" && this.isGeomechanical && lane.laneId === this.activeLaneId) {
14965
+ if (lineInfo.isUsed === "1" && this.isGeomechanical && this.isLaneSelected(lane.laneId)) {
14414
14966
  const boundaryPoints = this.findMissingDataBoundaryPoints(
14415
14967
  this.plotSeries(seriesData, isLogScale)
14416
14968
  );
@@ -14493,10 +15045,6 @@ const _sfc_main$2 = {
14493
15045
  } finally {
14494
15046
  this.isUpdatingChart = false;
14495
15047
  this.scheduleConnectAllCharts();
14496
- if (this.trendLineManager) {
14497
- this.trendLineManager.scheduleZoomRender();
14498
- this.$nextTick(() => this.trendLineManager.scheduleZoomRender());
14499
- }
14500
15048
  }
14501
15049
  },
14502
15050
  // 清空所有图表实例
@@ -14522,6 +15070,7 @@ const _sfc_main$2 = {
14522
15070
  this.markAreaLineData = [];
14523
15071
  this.laneSelectedPointPairs = {};
14524
15072
  this.activeLaneId = null;
15073
+ this.activeLaneIds = [];
14525
15074
  this.lineRange = {};
14526
15075
  this.pendingRequestData = null;
14527
15076
  this.initialRealTimeData = {};
@@ -15012,7 +15561,8 @@ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
15012
15561
  visibleData: $data.visibleData,
15013
15562
  headerData: $data.headerData,
15014
15563
  lineRange: $data.lineRange,
15015
- activeLaneId: $data.activeLaneId
15564
+ activeLaneId: $data.activeLaneId,
15565
+ activeLaneIds: $data.activeLaneIds
15016
15566
  }), createSlots({ _: 2 }, [
15017
15567
  renderList($props.headerSlotName, (lane) => {
15018
15568
  return {
@@ -15112,7 +15662,7 @@ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
15112
15662
  ])
15113
15663
  };
15114
15664
  })
15115
- ]), 1040, ["onTemplateChange", "onLineChange", "onLaneWidthChange", "onParamsChange", "visibleData", "headerData", "lineRange", "activeLaneId"])
15665
+ ]), 1040, ["onTemplateChange", "onLineChange", "onLaneWidthChange", "onParamsChange", "visibleData", "headerData", "lineRange", "activeLaneId", "activeLaneIds"])
15116
15666
  ], 512),
15117
15667
  withDirectives(createElementVNode("div", _hoisted_2$2, null, 512), [
15118
15668
  [vShow, $data.crosshairVisible]
@@ -15155,7 +15705,7 @@ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
15155
15705
  ], 40, _hoisted_3$1)) : (openBlock(), createElementBlock("div", {
15156
15706
  key: 1,
15157
15707
  class: "context-menu-item",
15158
- onClick: ($event) => $options.handleContextMenuItemClick(item)
15708
+ onClick: withModifiers(($event) => $options.handleContextMenuItemClick(item), ["stop"])
15159
15709
  }, [
15160
15710
  createElementVNode("span", null, toDisplayString(item.label), 1)
15161
15711
  ], 8, _hoisted_5))
@@ -15167,7 +15717,7 @@ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
15167
15717
  ])
15168
15718
  ], 544);
15169
15719
  }
15170
- const chartContainer = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["render", _sfc_render$2], ["__scopeId", "data-v-22f7e692"]]);
15720
+ const chartContainer = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["render", _sfc_render$2], ["__scopeId", "data-v-ec0aa0bf"]]);
15171
15721
  const _sfc_main$1 = {
15172
15722
  name: "ParameterPanel",
15173
15723
  props: {
@@ -16313,6 +16863,12 @@ const _sfc_main = {
16313
16863
  this.$refs.chartContainer.resetAllTrendLines();
16314
16864
  }
16315
16865
  },
16866
+ // 退出编辑时清掉泳道选中。8.25
16867
+ clearActiveLane() {
16868
+ if (this.$refs.chartContainer && this.$refs.chartContainer.clearActiveLane) {
16869
+ this.$refs.chartContainer.clearActiveLane();
16870
+ }
16871
+ },
16316
16872
  // 新增:更新form数据缓存
16317
16873
  updateForm(content) {
16318
16874
  let key = cloneDeep(content.key);
@@ -16682,7 +17238,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
16682
17238
  ])])) : createCommentVNode("", true)
16683
17239
  ]);
16684
17240
  }
16685
- const FrameLayoutComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-76b2334b"]]);
17241
+ const FrameLayoutComponent = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-51f78d28"]]);
16686
17242
  KdLaneContainerComponent.install = function(app) {
16687
17243
  app.component(KdLaneContainerComponent.name, KdLaneContainerComponent);
16688
17244
  };