topo-engine 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/README.md +41 -1
- package/dist/index.d.ts +62 -44
- package/dist/style.css +11 -11
- package/dist/topo-engine.cjs +1355 -213
- package/dist/topo-engine.cjs.map +1 -1
- package/dist/topo-engine.js +1355 -213
- package/dist/topo-engine.js.map +1 -1
- package/dist/topo-engine.umd.cjs +1355 -213
- package/dist/topo-engine.umd.cjs.map +1 -1
- package/docs/API.md +225 -31
- package/package.json +1 -1
package/dist/topo-engine.js
CHANGED
|
@@ -25024,8 +25024,8 @@ function injectCustomers(model, customerMap) {
|
|
|
25024
25024
|
node._customers = customers;
|
|
25025
25025
|
if (customers.length === 1) {
|
|
25026
25026
|
node._meterBoxMode = "single";
|
|
25027
|
-
node._meterBoxW =
|
|
25028
|
-
node._meterBoxH =
|
|
25027
|
+
node._meterBoxW = 22;
|
|
25028
|
+
node._meterBoxH = 30;
|
|
25029
25029
|
} else {
|
|
25030
25030
|
const showName = customers.length <= SHOW_NAME_LIMIT;
|
|
25031
25031
|
const cols = Math.min(customers.length, MAX_COLS);
|
|
@@ -25095,32 +25095,48 @@ class TopoApi {
|
|
|
25095
25095
|
this._topoLines = /* @__PURE__ */ new Map();
|
|
25096
25096
|
this._cards = /* @__PURE__ */ new Map();
|
|
25097
25097
|
this._animations = /* @__PURE__ */ new Map();
|
|
25098
|
+
this._glowNodes = /* @__PURE__ */ new Map();
|
|
25099
|
+
this._glowRaf = null;
|
|
25098
25100
|
this._nodeLabels = /* @__PURE__ */ new Map();
|
|
25099
25101
|
this._originalNodeStyles = /* @__PURE__ */ new Map();
|
|
25100
25102
|
this._originalEdgeStyles = /* @__PURE__ */ new Map();
|
|
25103
|
+
this._nodeColorOverrides = /* @__PURE__ */ new Map();
|
|
25104
|
+
this._custColorOverrides = /* @__PURE__ */ new Map();
|
|
25105
|
+
this._cellFx = /* @__PURE__ */ new Map();
|
|
25101
25106
|
this._bindConnGraphEvents();
|
|
25102
25107
|
}
|
|
25103
25108
|
/** 数据刷新(图重新渲染后调用) */
|
|
25104
25109
|
update(graph, model, layout, nodeByIdMap) {
|
|
25110
|
+
const colorSnapshot = {
|
|
25111
|
+
node: new Map(this._nodeColorOverrides),
|
|
25112
|
+
cust: new Map(this._custColorOverrides)
|
|
25113
|
+
};
|
|
25105
25114
|
this.destroy();
|
|
25106
25115
|
this._graph = graph;
|
|
25107
25116
|
this._model = model;
|
|
25108
25117
|
this._layout = layout;
|
|
25109
25118
|
this._nodeByIdMap = nodeByIdMap;
|
|
25110
25119
|
this._selectedId = null;
|
|
25120
|
+
this._nodeColorOverrides = colorSnapshot.node;
|
|
25121
|
+
this._custColorOverrides = colorSnapshot.cust;
|
|
25111
25122
|
this._bindConnGraphEvents();
|
|
25123
|
+
this._applyColorOverrides();
|
|
25112
25124
|
}
|
|
25113
25125
|
/** 销毁,释放所有资源 */
|
|
25114
25126
|
destroy() {
|
|
25115
25127
|
this._removeAllConnectionsInternal();
|
|
25116
25128
|
this._unbindConnGraphEvents();
|
|
25117
25129
|
this._removeAllAnimationsInternal();
|
|
25130
|
+
this._removeAllGlowsInternal();
|
|
25118
25131
|
this._removeAllTopoLinesInternal();
|
|
25119
25132
|
this._hideAllCardsInternal();
|
|
25120
25133
|
this._listeners.clear();
|
|
25121
25134
|
this._originalNodeStyles.clear();
|
|
25122
25135
|
this._originalEdgeStyles.clear();
|
|
25123
25136
|
this._nodeLabels.clear();
|
|
25137
|
+
this._nodeColorOverrides.clear();
|
|
25138
|
+
this._custColorOverrides.clear();
|
|
25139
|
+
this._cellFx.clear();
|
|
25124
25140
|
this._graph = null;
|
|
25125
25141
|
this._model = null;
|
|
25126
25142
|
this._layout = null;
|
|
@@ -25238,11 +25254,163 @@ class TopoApi {
|
|
|
25238
25254
|
}
|
|
25239
25255
|
}
|
|
25240
25256
|
// ============================================================
|
|
25257
|
+
// 统一目标解析(id / psrId / assetNo)
|
|
25258
|
+
//
|
|
25259
|
+
// 全库约定:凡入参语义为「节点/设备/用户」的方法,一律接受三种引用:
|
|
25260
|
+
// - 节点内部 id 或 psrId(设备编码) → 普通节点(变压器/开关/导线点/计量箱…)
|
|
25261
|
+
// - 客户 assetNo(资产编号,兼容 consNo/consId)→ 计量箱内该户「电表户」
|
|
25262
|
+
// 解析结果:
|
|
25263
|
+
// { kind:'node', id, node } 普通节点
|
|
25264
|
+
// { kind:'customer', boxId, index, boxNode, customer } 箱内电表户
|
|
25265
|
+
// ============================================================
|
|
25266
|
+
/** 统一解析 ref(内部实现;对外用 resolveRef) */
|
|
25267
|
+
_resolveTarget(ref2) {
|
|
25268
|
+
if (ref2 == null) return null;
|
|
25269
|
+
const e2 = this._parseEndpoint(ref2);
|
|
25270
|
+
if (!e2) return null;
|
|
25271
|
+
if (e2.kind === "node") {
|
|
25272
|
+
return { kind: "node", id: e2.id, node: this._nodeByIdMap.get(e2.id) || null };
|
|
25273
|
+
}
|
|
25274
|
+
const boxNode = this._nodeByIdMap.get(e2.boxId) || null;
|
|
25275
|
+
const customers = boxNode && Array.isArray(boxNode._customers) ? boxNode._customers : [];
|
|
25276
|
+
return {
|
|
25277
|
+
kind: "customer",
|
|
25278
|
+
boxId: e2.boxId,
|
|
25279
|
+
index: e2.index,
|
|
25280
|
+
boxNode,
|
|
25281
|
+
customer: customers[e2.index] || null,
|
|
25282
|
+
customers
|
|
25283
|
+
};
|
|
25284
|
+
}
|
|
25285
|
+
/** 解析到“节点 id”层:customer 统一落到其所在计量箱节点 */
|
|
25286
|
+
_targetNodeId(ref2) {
|
|
25287
|
+
const t = this._resolveTarget(ref2);
|
|
25288
|
+
return t ? t.kind === "node" ? t.id : t.boxId : null;
|
|
25289
|
+
}
|
|
25290
|
+
/** 解析到“图 id”层:普通节点=节点 id;电表户=`${boxId}#cust${index}`(唯一键) */
|
|
25291
|
+
_targetGraphKey(ref2) {
|
|
25292
|
+
const t = this._resolveTarget(ref2);
|
|
25293
|
+
if (!t) return null;
|
|
25294
|
+
return t.kind === "node" ? t.id : `${t.boxId}#cust${t.index}`;
|
|
25295
|
+
}
|
|
25296
|
+
/** 目标中心的世界坐标(节点中心 / 户表位中心) */
|
|
25297
|
+
_targetWorld(ref2) {
|
|
25298
|
+
const e2 = this._parseEndpoint(ref2);
|
|
25299
|
+
if (!e2) return null;
|
|
25300
|
+
if (e2.kind === "customer") return this._endpointWorld(e2);
|
|
25301
|
+
const p = this._layout.pos.get(e2.id);
|
|
25302
|
+
return p ? { x: p.x, y: p.y } : null;
|
|
25303
|
+
}
|
|
25304
|
+
// ---- 箱内电表户特效(_cellFx)读写 ----
|
|
25305
|
+
_cellFxKey(boxId, index) {
|
|
25306
|
+
return `${boxId}#cust${index}`;
|
|
25307
|
+
}
|
|
25308
|
+
/** 取某户特效 descriptor(无则新建空对象,不落库) */
|
|
25309
|
+
_cellFxGet(boxId, index) {
|
|
25310
|
+
const key = this._cellFxKey(boxId, index);
|
|
25311
|
+
let d2 = this._cellFx.get(key);
|
|
25312
|
+
if (!d2) {
|
|
25313
|
+
d2 = {};
|
|
25314
|
+
this._cellFx.set(key, d2);
|
|
25315
|
+
}
|
|
25316
|
+
return d2;
|
|
25317
|
+
}
|
|
25318
|
+
/** 删除某户特效键(若因此无任何特效则一并清除该箱 style 上的 _cellFx) */
|
|
25319
|
+
_cellFxDrop(boxId, index) {
|
|
25320
|
+
const key = this._cellFxKey(boxId, index);
|
|
25321
|
+
this._cellFx.delete(key);
|
|
25322
|
+
}
|
|
25323
|
+
/** 把某只计量箱的 _cellFx 映射刷成 style(无特效则移除 style 字段) */
|
|
25324
|
+
_flushCellFx(boxId) {
|
|
25325
|
+
if (!this._graph) return;
|
|
25326
|
+
const out = {};
|
|
25327
|
+
let any = false;
|
|
25328
|
+
for (const [key, d2] of this._cellFx) {
|
|
25329
|
+
if (!key.startsWith(`${boxId}#cust`)) continue;
|
|
25330
|
+
const idx = Number(key.slice(key.indexOf("#cust") + 5));
|
|
25331
|
+
if (!d2 || !d2.hl && !d2.note && !d2.glow && !d2.pulse) {
|
|
25332
|
+
this._cellFx.delete(key);
|
|
25333
|
+
continue;
|
|
25334
|
+
}
|
|
25335
|
+
out[idx] = d2;
|
|
25336
|
+
any = true;
|
|
25337
|
+
}
|
|
25338
|
+
const cur = this._safeNodeStyle(boxId, {});
|
|
25339
|
+
this._graph.updateNodeData([{ id: boxId, style: { ...cur, _cellFx: any ? out : void 0 } }]);
|
|
25340
|
+
this._graph.render();
|
|
25341
|
+
}
|
|
25342
|
+
/** 清理失效的户特效记录(箱/户不存在时自动丢弃),返回受影响箱列表 */
|
|
25343
|
+
_pruneCellFx() {
|
|
25344
|
+
const touched = /* @__PURE__ */ new Set();
|
|
25345
|
+
for (const [key] of this._cellFx) {
|
|
25346
|
+
const sep = key.indexOf("#cust");
|
|
25347
|
+
if (sep < 0) {
|
|
25348
|
+
this._cellFx.delete(key);
|
|
25349
|
+
continue;
|
|
25350
|
+
}
|
|
25351
|
+
const boxId = key.slice(0, sep);
|
|
25352
|
+
const idx = Number(key.slice(sep + 5));
|
|
25353
|
+
const box2 = this._nodeByIdMap.get(boxId);
|
|
25354
|
+
if (!box2 || !Array.isArray(box2._customers) || idx < 0 || idx >= box2._customers.length) {
|
|
25355
|
+
this._cellFx.delete(key);
|
|
25356
|
+
touched.add(boxId);
|
|
25357
|
+
}
|
|
25358
|
+
}
|
|
25359
|
+
return touched;
|
|
25360
|
+
}
|
|
25361
|
+
// ============================================================
|
|
25241
25362
|
// 一、查询 API
|
|
25242
25363
|
// ============================================================
|
|
25243
|
-
/**
|
|
25364
|
+
/** 获取单个节点(ref = 节点 id/psrId/资产编号 assetNo)
|
|
25365
|
+
* 命中电表户时返回其所在计量箱节点,并附带 customer/customerIndex 等客户信息 */
|
|
25244
25366
|
getNode(nodeId) {
|
|
25245
|
-
|
|
25367
|
+
const t = this._resolveTarget(nodeId);
|
|
25368
|
+
if (!t) return null;
|
|
25369
|
+
if (t.kind === "node") return t.node || null;
|
|
25370
|
+
if (!t.boxNode) return null;
|
|
25371
|
+
const box2 = t.boxNode;
|
|
25372
|
+
return {
|
|
25373
|
+
...box2,
|
|
25374
|
+
targetKind: "customer",
|
|
25375
|
+
meterBoxId: t.boxId,
|
|
25376
|
+
customer: t.customer,
|
|
25377
|
+
customerIndex: t.index,
|
|
25378
|
+
customerCount: t.customers.length
|
|
25379
|
+
};
|
|
25380
|
+
}
|
|
25381
|
+
/**
|
|
25382
|
+
* 解析任意引用 → 标准目标描述(调试 / 业务分支判断用)。
|
|
25383
|
+
* @param {string} ref 节点 id / psrId / 资产编号(assetNo,兼容 consNo/consId)
|
|
25384
|
+
* @returns {object|null}
|
|
25385
|
+
* kind='node' → { kind, id, node }
|
|
25386
|
+
* kind='customer' → { kind, boxId, boxNode, index, customer, key, assetNo, name }
|
|
25387
|
+
*/
|
|
25388
|
+
resolveRef(ref2) {
|
|
25389
|
+
const t = this._resolveTarget(ref2);
|
|
25390
|
+
if (!t) return null;
|
|
25391
|
+
if (t.kind === "node") {
|
|
25392
|
+
return { kind: "node", id: t.id, node: t.node };
|
|
25393
|
+
}
|
|
25394
|
+
const c = t.customer || {};
|
|
25395
|
+
return {
|
|
25396
|
+
kind: "customer",
|
|
25397
|
+
boxId: t.boxId,
|
|
25398
|
+
boxNode: t.boxNode,
|
|
25399
|
+
index: t.index,
|
|
25400
|
+
customer: t.customer,
|
|
25401
|
+
key: this._cellFxKey(t.boxId, t.index),
|
|
25402
|
+
assetNo: c.assetNo != null ? c.assetNo : c.consNo != null ? c.consNo : c.consId,
|
|
25403
|
+
name: c.realConsName || c.consName || ""
|
|
25404
|
+
};
|
|
25405
|
+
}
|
|
25406
|
+
/**
|
|
25407
|
+
* 按资产编号 / 客户编号取客户档案包装(也可传所在箱的 psrId 时返回 null)。
|
|
25408
|
+
* @returns {{ node, customer, index, count, boxId } | null}
|
|
25409
|
+
*/
|
|
25410
|
+
getCustomer(ref2) {
|
|
25411
|
+
const t = this._resolveTarget(ref2);
|
|
25412
|
+
if (!t || t.kind !== "customer") return null;
|
|
25413
|
+
return { node: t.boxNode, customer: t.customer, index: t.index, count: t.customers.length, boxId: t.boxId };
|
|
25246
25414
|
}
|
|
25247
25415
|
/** 获取所有节点(只读副本) */
|
|
25248
25416
|
getAllNodes() {
|
|
@@ -25274,15 +25442,17 @@ class TopoApi {
|
|
|
25274
25442
|
getSelectedId() {
|
|
25275
25443
|
return this._selectedId;
|
|
25276
25444
|
}
|
|
25277
|
-
/**
|
|
25445
|
+
/** 获取上下游邻居(ref 可传 节点id/psrId/资产编号;电表户按所在计量箱参与拓扑) */
|
|
25278
25446
|
getNeighbors(nodeId) {
|
|
25447
|
+
const id2 = this._targetNodeId(nodeId);
|
|
25448
|
+
if (id2 == null) return { upstream: [], downstream: [] };
|
|
25279
25449
|
const upstream = [];
|
|
25280
25450
|
const downstream = [];
|
|
25281
25451
|
for (const e2 of this._model.edges) {
|
|
25282
|
-
if (e2.target ===
|
|
25452
|
+
if (e2.target === id2) {
|
|
25283
25453
|
const p = this._nodeByIdMap.get(e2.source);
|
|
25284
25454
|
if (p) upstream.push(p);
|
|
25285
|
-
} else if (e2.source ===
|
|
25455
|
+
} else if (e2.source === id2) {
|
|
25286
25456
|
const c = this._nodeByIdMap.get(e2.target);
|
|
25287
25457
|
if (c) downstream.push(c);
|
|
25288
25458
|
}
|
|
@@ -25291,11 +25461,13 @@ class TopoApi {
|
|
|
25291
25461
|
}
|
|
25292
25462
|
/**
|
|
25293
25463
|
* 沿拓扑流向取节点链
|
|
25294
|
-
* @param {string}
|
|
25464
|
+
* @param {string} ref - 节点 id / psrId / 资产编号(电表户按所在箱)
|
|
25295
25465
|
* @param {'upstream'|'downstream'} direction
|
|
25296
25466
|
*/
|
|
25297
|
-
getStreamNodes(
|
|
25298
|
-
|
|
25467
|
+
getStreamNodes(ref2, direction2) {
|
|
25468
|
+
const id2 = this._targetNodeId(ref2);
|
|
25469
|
+
if (id2 == null) return [];
|
|
25470
|
+
return this._bfs(id2, direction2).nodes;
|
|
25299
25471
|
}
|
|
25300
25472
|
/** 获取主干链节点 */
|
|
25301
25473
|
getTrunkNodes() {
|
|
@@ -25333,11 +25505,13 @@ class TopoApi {
|
|
|
25333
25505
|
for (const k of kids) w += this._subtreeWeight(k, childrenMap, visited);
|
|
25334
25506
|
return w;
|
|
25335
25507
|
}
|
|
25336
|
-
/**
|
|
25337
|
-
getSubtreeNodes(
|
|
25508
|
+
/** 获取子树所有节点(ref 可传 节点id/psrId/资产编号) */
|
|
25509
|
+
getSubtreeNodes(ref2) {
|
|
25510
|
+
const id2 = this._targetNodeId(ref2);
|
|
25511
|
+
if (id2 == null) return [];
|
|
25338
25512
|
const result = [];
|
|
25339
25513
|
const visited = /* @__PURE__ */ new Set();
|
|
25340
|
-
const queue = [
|
|
25514
|
+
const queue = [id2];
|
|
25341
25515
|
while (queue.length) {
|
|
25342
25516
|
const cur = queue.shift();
|
|
25343
25517
|
if (visited.has(cur)) continue;
|
|
@@ -25350,8 +25524,11 @@ class TopoApi {
|
|
|
25350
25524
|
}
|
|
25351
25525
|
return result;
|
|
25352
25526
|
}
|
|
25353
|
-
/** 获取两节点间路径(BFS
|
|
25354
|
-
getPath(
|
|
25527
|
+
/** 获取两节点间路径(BFS;端点可传 id/psrId/assetNo,电表户按所在箱) */
|
|
25528
|
+
getPath(fromRef, toRef) {
|
|
25529
|
+
const fromId = this._targetNodeId(fromRef);
|
|
25530
|
+
const toId = this._targetNodeId(toRef);
|
|
25531
|
+
if (fromId == null || toId == null) return null;
|
|
25355
25532
|
const parent = /* @__PURE__ */ new Map([[fromId, null]]);
|
|
25356
25533
|
const queue = [fromId];
|
|
25357
25534
|
while (queue.length) {
|
|
@@ -25378,17 +25555,19 @@ class TopoApi {
|
|
|
25378
25555
|
}
|
|
25379
25556
|
return path;
|
|
25380
25557
|
}
|
|
25381
|
-
/**
|
|
25382
|
-
getEdge(
|
|
25383
|
-
return this._findEdgeData(
|
|
25558
|
+
/** 获取指定边(端点可传 id/psrId/assetNo,电表户按所在箱) */
|
|
25559
|
+
getEdge(srcRef, tgtRef) {
|
|
25560
|
+
return this._findEdgeData(this._targetNodeId(srcRef), this._targetNodeId(tgtRef));
|
|
25384
25561
|
}
|
|
25385
25562
|
/** 获取所有边 */
|
|
25386
25563
|
getEdges() {
|
|
25387
25564
|
return [...this._model.edges];
|
|
25388
25565
|
}
|
|
25389
|
-
/**
|
|
25390
|
-
getStreamEdges(
|
|
25391
|
-
|
|
25566
|
+
/** 沿流向取边(ref 可传 节点id/psrId/资产编号) */
|
|
25567
|
+
getStreamEdges(ref2, direction2) {
|
|
25568
|
+
const id2 = this._targetNodeId(ref2);
|
|
25569
|
+
if (id2 == null) return [];
|
|
25570
|
+
return this._bfs(id2, direction2).edges;
|
|
25392
25571
|
}
|
|
25393
25572
|
/** 获取主干边 */
|
|
25394
25573
|
getMainEdge() {
|
|
@@ -25402,14 +25581,15 @@ class TopoApi {
|
|
|
25402
25581
|
return true;
|
|
25403
25582
|
});
|
|
25404
25583
|
}
|
|
25405
|
-
/**
|
|
25406
|
-
getNodePosition(
|
|
25407
|
-
const
|
|
25408
|
-
return
|
|
25584
|
+
/** 获取节点 / 户表位中心坐标(assetNo → 该户表位中心;节点 id/psrId → 节点中心) */
|
|
25585
|
+
getNodePosition(ref2) {
|
|
25586
|
+
const world = this._targetWorld(ref2);
|
|
25587
|
+
return world ? { x: world.x, y: world.y } : null;
|
|
25409
25588
|
}
|
|
25410
|
-
/**
|
|
25411
|
-
getEdgePath(
|
|
25412
|
-
const
|
|
25589
|
+
/** 获取边折线点序列(端点可传 id/psrId/assetNo) */
|
|
25590
|
+
getEdgePath(srcRef, tgtRef) {
|
|
25591
|
+
const key = this._edgeKey(this._targetNodeId(srcRef), this._targetNodeId(tgtRef));
|
|
25592
|
+
const cps = this._layout.edgeCP.get(key);
|
|
25413
25593
|
return cps ? cps.map(([x, y]) => [x, y]) : null;
|
|
25414
25594
|
}
|
|
25415
25595
|
/** 获取图整体边界 */
|
|
@@ -25449,17 +25629,29 @@ class TopoApi {
|
|
|
25449
25629
|
const current = this._graph.getZoom();
|
|
25450
25630
|
this._graph.zoomTo(current / (1 + step2));
|
|
25451
25631
|
}
|
|
25452
|
-
/**
|
|
25453
|
-
locateNode(
|
|
25632
|
+
/** 定位节点到视口中央(ref 可传 id/psrId/资产编号;电表户聚焦其表位) */
|
|
25633
|
+
locateNode(ref2, zoom) {
|
|
25454
25634
|
this._assertReady();
|
|
25455
|
-
const
|
|
25456
|
-
if (!
|
|
25635
|
+
const t = this._resolveTarget(ref2);
|
|
25636
|
+
if (!t) return;
|
|
25637
|
+
const world = this._targetWorld(ref2);
|
|
25638
|
+
if (!world) return;
|
|
25457
25639
|
if (zoom) this._graph.zoomTo(zoom);
|
|
25640
|
+
if (t.kind === "node") {
|
|
25641
|
+
try {
|
|
25642
|
+
this._graph.focusElement(t.id);
|
|
25643
|
+
return;
|
|
25644
|
+
} catch {
|
|
25645
|
+
}
|
|
25646
|
+
}
|
|
25458
25647
|
try {
|
|
25459
|
-
this._graph.
|
|
25460
|
-
|
|
25461
|
-
const
|
|
25462
|
-
|
|
25648
|
+
const z2 = zoom || this._graph.getZoom();
|
|
25649
|
+
const [vx, vy] = this._graph.getViewportCenter();
|
|
25650
|
+
const dx = (vx - world.x) * z2;
|
|
25651
|
+
const dy = (vy - world.y) * z2;
|
|
25652
|
+
this._graph.translateBy([dx, dy]);
|
|
25653
|
+
} catch (e2) {
|
|
25654
|
+
console.warn("[TopoApi] locateNode 平移失败:", e2 && e2.message);
|
|
25463
25655
|
}
|
|
25464
25656
|
}
|
|
25465
25657
|
/** 获取当前视口状态 */
|
|
@@ -25472,25 +25664,34 @@ class TopoApi {
|
|
|
25472
25664
|
// ============================================================
|
|
25473
25665
|
// 三、节点样式
|
|
25474
25666
|
// ============================================================
|
|
25475
|
-
/**
|
|
25476
|
-
setNodeStyle(
|
|
25667
|
+
/** 设置单个节点样式(ref = 节点id/psrId;传 assetNo 时作用于其所在计量箱节点) */
|
|
25668
|
+
setNodeStyle(ref2, style) {
|
|
25477
25669
|
this._assertReady();
|
|
25478
|
-
this.
|
|
25479
|
-
|
|
25670
|
+
const id2 = this._targetNodeId(ref2);
|
|
25671
|
+
if (id2 == null) {
|
|
25672
|
+
console.warn(`[TopoApi] setNodeStyle: 无法解析目标 ${ref2}`);
|
|
25673
|
+
return;
|
|
25674
|
+
}
|
|
25675
|
+
this._snapshotNodeStyle(id2);
|
|
25676
|
+
this._updateNodes([{ id: id2, style }]);
|
|
25480
25677
|
}
|
|
25481
|
-
/**
|
|
25482
|
-
batchSetNodeStyle(
|
|
25678
|
+
/** 批量设置节点样式(每一项可为 id/psrId/assetNo) */
|
|
25679
|
+
batchSetNodeStyle(refs, style) {
|
|
25483
25680
|
this._assertReady();
|
|
25484
|
-
|
|
25485
|
-
|
|
25681
|
+
const ids = refs.map((r) => this._targetNodeId(r)).filter((v) => v != null);
|
|
25682
|
+
if (!ids.length) return;
|
|
25683
|
+
for (const id2 of ids) this._snapshotNodeStyle(id2);
|
|
25684
|
+
this._updateNodes(ids.map((id2) => ({ id: id2, style })));
|
|
25486
25685
|
}
|
|
25487
|
-
/**
|
|
25488
|
-
resetNodeStyle(
|
|
25686
|
+
/** 重置单个节点样式(ref = 节点id/psrId/assetNo) */
|
|
25687
|
+
resetNodeStyle(ref2) {
|
|
25489
25688
|
this._assertReady();
|
|
25490
|
-
const
|
|
25689
|
+
const id2 = this._targetNodeId(ref2);
|
|
25690
|
+
if (id2 == null) return;
|
|
25691
|
+
const orig = this._originalNodeStyles.get(id2);
|
|
25491
25692
|
if (orig) {
|
|
25492
|
-
this._updateNodes([{ id:
|
|
25493
|
-
this._originalNodeStyles.delete(
|
|
25693
|
+
this._updateNodes([{ id: id2, style: orig }]);
|
|
25694
|
+
this._originalNodeStyles.delete(id2);
|
|
25494
25695
|
}
|
|
25495
25696
|
}
|
|
25496
25697
|
/** 重置所有节点样式 */
|
|
@@ -25504,30 +25705,269 @@ class TopoApi {
|
|
|
25504
25705
|
this._originalNodeStyles.clear();
|
|
25505
25706
|
}
|
|
25506
25707
|
// ============================================================
|
|
25708
|
+
// 三·五、节点 / 箱内电表户染色(支持 psrId / 资产编号 / 节点 id)
|
|
25709
|
+
//
|
|
25710
|
+
// 普通节点(变压器/开关/熔丝/导线点/用户接入点…)→ 图元“整图单色换装”,
|
|
25711
|
+
// 主体/引线/描边换成指定色,白字与浅色细节保留可读;
|
|
25712
|
+
// 箱内电表户(计量箱 grid/single 里的每只电表)→ 该户电表图标与户名染色。
|
|
25713
|
+
// 引用规则与 addConnection 完全一致:
|
|
25714
|
+
// 节点:节点内部 id 或 psrId(设备编码);电表户:客户档案 assetNo(资产编号,
|
|
25715
|
+
// 兼容 consNo / consId)。染色记录随数据重渲染(主题切换/换数据)自动保留重放。
|
|
25716
|
+
// ============================================================
|
|
25717
|
+
/**
|
|
25718
|
+
* 把当前记录的染色覆盖(节点 / 电表户)重新写进图 style(update 后调用)。
|
|
25719
|
+
* 仅做 updateNodeData 合并,不主动 render —— 由随后的一次 render 统一生效。
|
|
25720
|
+
* 顺带清理指向已不存在节点 / 表位的过期记录(换数据后自动丢弃)。
|
|
25721
|
+
*/
|
|
25722
|
+
_applyColorOverrides() {
|
|
25723
|
+
if (!this._graph) return;
|
|
25724
|
+
const updates = [];
|
|
25725
|
+
if (this._nodeColorOverrides.size) {
|
|
25726
|
+
const stale = [];
|
|
25727
|
+
for (const [id2, color2] of this._nodeColorOverrides) {
|
|
25728
|
+
if (!this._graph.getNodeData(id2)) {
|
|
25729
|
+
stale.push(id2);
|
|
25730
|
+
continue;
|
|
25731
|
+
}
|
|
25732
|
+
updates.push({ id: id2, style: { color: color2 || "" } });
|
|
25733
|
+
}
|
|
25734
|
+
for (const id2 of stale) this._nodeColorOverrides.delete(id2);
|
|
25735
|
+
}
|
|
25736
|
+
if (this._custColorOverrides.size) {
|
|
25737
|
+
const byBox = /* @__PURE__ */ new Map();
|
|
25738
|
+
const stale = [];
|
|
25739
|
+
for (const [key, color2] of this._custColorOverrides) {
|
|
25740
|
+
const sep = key.indexOf("#cust");
|
|
25741
|
+
if (sep < 0) {
|
|
25742
|
+
stale.push(key);
|
|
25743
|
+
continue;
|
|
25744
|
+
}
|
|
25745
|
+
const boxId = key.slice(0, sep);
|
|
25746
|
+
if (!this._graph.getNodeData(boxId)) {
|
|
25747
|
+
stale.push(key);
|
|
25748
|
+
continue;
|
|
25749
|
+
}
|
|
25750
|
+
if (!byBox.has(boxId)) byBox.set(boxId, /* @__PURE__ */ new Map());
|
|
25751
|
+
byBox.get(boxId).set(Number(key.slice(sep + 5)), color2);
|
|
25752
|
+
}
|
|
25753
|
+
for (const [boxId, cellMap] of byBox) {
|
|
25754
|
+
const cur = this._safeNodeStyle(boxId, {});
|
|
25755
|
+
const customers = cur._customers;
|
|
25756
|
+
if (!Array.isArray(customers) || !customers.length) continue;
|
|
25757
|
+
const arr = new Array(customers.length).fill(null);
|
|
25758
|
+
let any = false;
|
|
25759
|
+
for (const [idx, color2] of cellMap) {
|
|
25760
|
+
if (idx >= 0 && idx < arr.length && color2) {
|
|
25761
|
+
arr[idx] = color2;
|
|
25762
|
+
any = true;
|
|
25763
|
+
}
|
|
25764
|
+
}
|
|
25765
|
+
updates.push({ id: boxId, style: { _cellColors: any ? arr : null } });
|
|
25766
|
+
}
|
|
25767
|
+
for (const key of stale) this._custColorOverrides.delete(key);
|
|
25768
|
+
}
|
|
25769
|
+
if (updates.length) {
|
|
25770
|
+
try {
|
|
25771
|
+
this._graph.updateNodeData(updates);
|
|
25772
|
+
} catch (e2) {
|
|
25773
|
+
console.error("[TopoApi] _applyColorOverrides error:", e2);
|
|
25774
|
+
}
|
|
25775
|
+
}
|
|
25776
|
+
}
|
|
25777
|
+
/**
|
|
25778
|
+
* 写入某个解析端点的染色(setNodeColor / batchSetNodeColor 共用)。
|
|
25779
|
+
* @param {{kind:'node',id:string}|{kind:'customer',boxId:string,index:number,node:object}} e
|
|
25780
|
+
* @param {string|null} color 颜色;null/''/undefined = 清除该处染色
|
|
25781
|
+
*/
|
|
25782
|
+
_writeColorEndpoint(e2, color2) {
|
|
25783
|
+
if (!this._graph) return;
|
|
25784
|
+
color2 = color2 == null ? null : String(color2).trim() || null;
|
|
25785
|
+
if (e2.kind === "node") {
|
|
25786
|
+
const id2 = e2.id;
|
|
25787
|
+
if (color2) this._nodeColorOverrides.set(id2, color2);
|
|
25788
|
+
else this._nodeColorOverrides.delete(id2);
|
|
25789
|
+
this._updateNodes([{ id: id2, style: { color: color2 || "" } }]);
|
|
25790
|
+
return;
|
|
25791
|
+
}
|
|
25792
|
+
const key = `${e2.boxId}#cust${e2.index}`;
|
|
25793
|
+
if (color2) this._custColorOverrides.set(key, color2);
|
|
25794
|
+
else this._custColorOverrides.delete(key);
|
|
25795
|
+
const cur = this._safeNodeStyle(e2.boxId, {});
|
|
25796
|
+
const customers = cur._customers;
|
|
25797
|
+
if (!Array.isArray(customers) || !customers.length) return;
|
|
25798
|
+
const arr = new Array(customers.length).fill(null);
|
|
25799
|
+
let any = false;
|
|
25800
|
+
for (const [k, c] of this._custColorOverrides) {
|
|
25801
|
+
if (!k.startsWith(`${e2.boxId}#cust`)) continue;
|
|
25802
|
+
const idx = Number(k.slice(k.indexOf("#cust") + 5));
|
|
25803
|
+
if (idx >= 0 && idx < arr.length && c) {
|
|
25804
|
+
arr[idx] = c;
|
|
25805
|
+
any = true;
|
|
25806
|
+
}
|
|
25807
|
+
}
|
|
25808
|
+
this._updateNodes([{ id: e2.boxId, style: { _cellColors: any ? arr : null } }]);
|
|
25809
|
+
}
|
|
25810
|
+
/**
|
|
25811
|
+
* 给节点 / 箱内电表户设置颜色。
|
|
25812
|
+
* @param {string} ref 节点内部 id / 节点 psrId(普通节点、计量箱本体)
|
|
25813
|
+
* 或 客户资产编号 assetNo(兼容 consNo/consId,箱内电表户)
|
|
25814
|
+
* @param {string} [color] 颜色('#RRGGBB' / 'red' 等);传 null/''/undefined 清除该处染色
|
|
25815
|
+
* @returns {boolean} 是否命中并设置成功
|
|
25816
|
+
*/
|
|
25817
|
+
setNodeColor(ref2, color2) {
|
|
25818
|
+
this._assertReady();
|
|
25819
|
+
const e2 = this._parseEndpoint(ref2);
|
|
25820
|
+
if (!e2) {
|
|
25821
|
+
console.warn(`[TopoApi] setNodeColor: 无法解析目标 ${ref2}(普通节点请用 psrId/节点id,电表户请用 assetNo/consNo/consId)`);
|
|
25822
|
+
return false;
|
|
25823
|
+
}
|
|
25824
|
+
this._writeColorEndpoint(e2, color2);
|
|
25825
|
+
return true;
|
|
25826
|
+
}
|
|
25827
|
+
/**
|
|
25828
|
+
* 批量设置颜色。三种入参形式:
|
|
25829
|
+
* 1) 数组:[ ['8ff4…', '#FF0000'], ['4230…资产编号', '#00FF00'], … ]
|
|
25830
|
+
* 2) 数组:[{ ref / psrId / assetNo / nodeId, color? }](color 缺省用第 2 参)
|
|
25831
|
+
* 3) 对象:{ 'psrId或assetNo': '#FF0000', … }
|
|
25832
|
+
* @param {*} entries
|
|
25833
|
+
* @param {string} [color] 未在单项里给色时的兜底颜色
|
|
25834
|
+
* @returns {{total:number, ok:number, failed:Array<{ref:string, reason:string}>}}
|
|
25835
|
+
*/
|
|
25836
|
+
batchSetNodeColor(entries, color2) {
|
|
25837
|
+
this._assertReady();
|
|
25838
|
+
const failed = [];
|
|
25839
|
+
let total = 0;
|
|
25840
|
+
const applyRef = (ref2, c) => {
|
|
25841
|
+
total++;
|
|
25842
|
+
const e2 = this._parseEndpoint(ref2);
|
|
25843
|
+
if (!e2) {
|
|
25844
|
+
failed.push({ ref: String(ref2), reason: "未命中任何节点/电表户" });
|
|
25845
|
+
return;
|
|
25846
|
+
}
|
|
25847
|
+
this._writeColorEndpoint(e2, c);
|
|
25848
|
+
};
|
|
25849
|
+
if (Array.isArray(entries)) {
|
|
25850
|
+
for (const item of entries) {
|
|
25851
|
+
if (Array.isArray(item)) {
|
|
25852
|
+
applyRef(item[0], item[1] != null ? item[1] : color2);
|
|
25853
|
+
} else if (item && typeof item === "object") {
|
|
25854
|
+
const ref2 = item.ref != null ? item.ref : item.psrId != null ? item.psrId : item.assetNo != null ? item.assetNo : item.nodeId;
|
|
25855
|
+
if (ref2 == null) {
|
|
25856
|
+
total++;
|
|
25857
|
+
failed.push({ ref: JSON.stringify(item), reason: "缺少 ref/psrId/assetNo/nodeId" });
|
|
25858
|
+
continue;
|
|
25859
|
+
}
|
|
25860
|
+
applyRef(ref2, item.color != null ? item.color : color2);
|
|
25861
|
+
}
|
|
25862
|
+
}
|
|
25863
|
+
} else if (entries && typeof entries === "object") {
|
|
25864
|
+
for (const [ref2, c] of Object.entries(entries)) applyRef(ref2, c);
|
|
25865
|
+
}
|
|
25866
|
+
return { total, ok: total - failed.length, failed };
|
|
25867
|
+
}
|
|
25868
|
+
/**
|
|
25869
|
+
* 清除某处染色(返回节点 / 电表户到主题默认配色)。
|
|
25870
|
+
* @param {string} ref 同 setNodeColor 的引用
|
|
25871
|
+
*/
|
|
25872
|
+
resetNodeColor(ref2) {
|
|
25873
|
+
this._assertReady();
|
|
25874
|
+
const e2 = this._parseEndpoint(ref2);
|
|
25875
|
+
if (!e2) {
|
|
25876
|
+
console.warn(`[TopoApi] resetNodeColor: 无法解析目标 ${ref2}`);
|
|
25877
|
+
return false;
|
|
25878
|
+
}
|
|
25879
|
+
this._writeColorEndpoint(e2, null);
|
|
25880
|
+
return true;
|
|
25881
|
+
}
|
|
25882
|
+
/** 清除全部染色(所有节点与电表户回到主题默认配色) */
|
|
25883
|
+
resetAllNodeColors() {
|
|
25884
|
+
this._assertReady();
|
|
25885
|
+
const nodeUpdates = [];
|
|
25886
|
+
for (const [id2] of this._nodeColorOverrides) {
|
|
25887
|
+
if (this._graph.getNodeData(id2)) nodeUpdates.push({ id: id2, style: { color: "" } });
|
|
25888
|
+
}
|
|
25889
|
+
const boxes = /* @__PURE__ */ new Set();
|
|
25890
|
+
for (const [key] of this._custColorOverrides) {
|
|
25891
|
+
const sep = key.indexOf("#cust");
|
|
25892
|
+
if (sep >= 0) boxes.add(key.slice(0, sep));
|
|
25893
|
+
}
|
|
25894
|
+
for (const boxId of boxes) {
|
|
25895
|
+
if (this._graph.getNodeData(boxId)) {
|
|
25896
|
+
nodeUpdates.push({ id: boxId, style: { _cellColors: null } });
|
|
25897
|
+
}
|
|
25898
|
+
}
|
|
25899
|
+
if (nodeUpdates.length) this._updateNodes(nodeUpdates);
|
|
25900
|
+
this._nodeColorOverrides.clear();
|
|
25901
|
+
this._custColorOverrides.clear();
|
|
25902
|
+
}
|
|
25903
|
+
/**
|
|
25904
|
+
* 查询某处当前生效的染色。
|
|
25905
|
+
* @param {string} ref 同 setNodeColor 的引用
|
|
25906
|
+
* @returns {{ kind: 'node'|'customer', id: string, index?: number, color: string|null } | null}
|
|
25907
|
+
*/
|
|
25908
|
+
getNodeColor(ref2) {
|
|
25909
|
+
this._assertReady();
|
|
25910
|
+
const e2 = this._parseEndpoint(ref2);
|
|
25911
|
+
if (!e2) return null;
|
|
25912
|
+
if (e2.kind === "node") {
|
|
25913
|
+
const color3 = this._nodeColorOverrides.get(e2.id);
|
|
25914
|
+
if (color3) return { kind: "node", id: e2.id, color: color3 };
|
|
25915
|
+
const cur2 = this._safeNodeStyle(e2.id, {});
|
|
25916
|
+
return { kind: "node", id: e2.id, color: cur2.color || null };
|
|
25917
|
+
}
|
|
25918
|
+
const key = `${e2.boxId}#cust${e2.index}`;
|
|
25919
|
+
const color2 = this._custColorOverrides.get(key);
|
|
25920
|
+
if (color2) return { kind: "customer", id: key, boxId: e2.boxId, index: e2.index, color: color2 };
|
|
25921
|
+
const cur = this._safeNodeStyle(e2.boxId, {});
|
|
25922
|
+
const arr = cur._cellColors;
|
|
25923
|
+
return {
|
|
25924
|
+
kind: "customer",
|
|
25925
|
+
id: key,
|
|
25926
|
+
boxId: e2.boxId,
|
|
25927
|
+
index: e2.index,
|
|
25928
|
+
color: Array.isArray(arr) && arr[e2.index] || null
|
|
25929
|
+
};
|
|
25930
|
+
}
|
|
25931
|
+
// ============================================================
|
|
25507
25932
|
// 四、边样式
|
|
25508
25933
|
// ============================================================
|
|
25509
|
-
/**
|
|
25510
|
-
|
|
25934
|
+
/** 解析边端点 → 节点 id(customer → 所在计量箱;失败返回 null) */
|
|
25935
|
+
_edgeEndpoints(srcRef, tgtRef) {
|
|
25936
|
+
const a2 = this._targetNodeId(srcRef);
|
|
25937
|
+
const b = this._targetNodeId(tgtRef);
|
|
25938
|
+
return a2 != null && b != null ? [a2, b] : null;
|
|
25939
|
+
}
|
|
25940
|
+
/** 设置边样式(端点可为节点 id/psrId/资产编号 assetNo) */
|
|
25941
|
+
setEdgeStyle(srcRef, tgtRef, style) {
|
|
25511
25942
|
this._assertReady();
|
|
25512
|
-
const
|
|
25943
|
+
const ep = this._edgeEndpoints(srcRef, tgtRef);
|
|
25944
|
+
if (!ep) {
|
|
25945
|
+
console.warn(`[TopoApi] setEdgeStyle: 无法解析端点 ${srcRef} / ${tgtRef}`);
|
|
25946
|
+
return;
|
|
25947
|
+
}
|
|
25948
|
+
const edgeId = this._edgeKey(ep[0], ep[1]);
|
|
25513
25949
|
this._snapshotEdgeStyle(edgeId);
|
|
25514
25950
|
this._updateEdges([{ id: edgeId, style }]);
|
|
25515
25951
|
}
|
|
25516
|
-
/**
|
|
25517
|
-
batchSetEdgeStyle(
|
|
25952
|
+
/** 批量设置边样式(每个端对可为 id/psrId/assetNo) */
|
|
25953
|
+
batchSetEdgeStyle(edgeRefs, style) {
|
|
25518
25954
|
this._assertReady();
|
|
25519
25955
|
const updates = [];
|
|
25520
|
-
for (const [src, tgt] of
|
|
25521
|
-
const
|
|
25956
|
+
for (const [src, tgt] of edgeRefs) {
|
|
25957
|
+
const ep = this._edgeEndpoints(src, tgt);
|
|
25958
|
+
if (!ep) continue;
|
|
25959
|
+
const eid = this._edgeKey(ep[0], ep[1]);
|
|
25522
25960
|
this._snapshotEdgeStyle(eid);
|
|
25523
25961
|
updates.push({ id: eid, style });
|
|
25524
25962
|
}
|
|
25525
25963
|
if (updates.length) this._updateEdges(updates);
|
|
25526
25964
|
}
|
|
25527
|
-
/**
|
|
25528
|
-
resetEdgeStyle(
|
|
25965
|
+
/** 重置边样式(端点可为节点 id/psrId/资产编号 assetNo) */
|
|
25966
|
+
resetEdgeStyle(srcRef, tgtRef) {
|
|
25529
25967
|
this._assertReady();
|
|
25530
|
-
const
|
|
25968
|
+
const ep = this._edgeEndpoints(srcRef, tgtRef);
|
|
25969
|
+
if (!ep) return;
|
|
25970
|
+
const edgeId = this._edgeKey(ep[0], ep[1]);
|
|
25531
25971
|
const orig = this._originalEdgeStyles.get(edgeId);
|
|
25532
25972
|
if (orig) {
|
|
25533
25973
|
this._updateEdges([{ id: edgeId, style: orig }]);
|
|
@@ -25538,54 +25978,92 @@ class TopoApi {
|
|
|
25538
25978
|
// 五、高亮与标注
|
|
25539
25979
|
// ============================================================
|
|
25540
25980
|
/**
|
|
25541
|
-
* 高亮节点
|
|
25542
|
-
* @param {string}
|
|
25981
|
+
* 高亮节点 / 箱内电表户
|
|
25982
|
+
* @param {string} ref - 节点 id / psrId / 资产编号(电表户 → 该户表位高亮环 + 可选文字)
|
|
25543
25983
|
* @param {object} [opts] - { color, label, labelColor, labelSize, borderColor, borderStyle, borderWidth }
|
|
25544
25984
|
*/
|
|
25545
|
-
highlightNode(
|
|
25985
|
+
highlightNode(ref2, opts = {}) {
|
|
25546
25986
|
this._assertReady();
|
|
25547
|
-
this.
|
|
25548
|
-
|
|
25549
|
-
|
|
25987
|
+
const t = this._resolveTarget(ref2);
|
|
25988
|
+
if (!t) {
|
|
25989
|
+
console.warn(`[TopoApi] highlightNode: 无法解析目标 ${ref2}`);
|
|
25990
|
+
return;
|
|
25991
|
+
}
|
|
25992
|
+
if (t.kind === "node") {
|
|
25993
|
+
this._snapshotNodeStyle(t.id);
|
|
25994
|
+
const style = {
|
|
25995
|
+
highlight: 1
|
|
25996
|
+
};
|
|
25997
|
+
if (opts.borderColor) style.highlightColor = opts.borderColor;
|
|
25998
|
+
if (opts.borderWidth) style.highlightWidth = opts.borderWidth;
|
|
25999
|
+
if (opts.borderStyle === "solid") style.highlightDash = [];
|
|
26000
|
+
if (opts.label) {
|
|
26001
|
+
style.annotationText = opts.label;
|
|
26002
|
+
style.annotationColor = opts.labelColor || "#FFFFFF";
|
|
26003
|
+
style.annotationSize = opts.labelSize || 12;
|
|
26004
|
+
}
|
|
26005
|
+
this._updateNodes([{ id: t.id, style }]);
|
|
26006
|
+
return;
|
|
26007
|
+
}
|
|
26008
|
+
const d2 = this._cellFxGet(t.boxId, t.index);
|
|
26009
|
+
d2.hl = {
|
|
26010
|
+
color: opts.borderColor || opts.color || "#FFD700",
|
|
26011
|
+
width: opts.borderWidth || 2,
|
|
26012
|
+
dash: opts.borderStyle === "solid" ? [] : [4, 3]
|
|
25550
26013
|
};
|
|
25551
|
-
if (opts.borderColor) style.highlightColor = opts.borderColor;
|
|
25552
|
-
if (opts.borderWidth) style.highlightWidth = opts.borderWidth;
|
|
25553
|
-
if (opts.borderStyle === "solid") style.highlightDash = [];
|
|
25554
26014
|
if (opts.label) {
|
|
25555
|
-
|
|
25556
|
-
|
|
25557
|
-
|
|
26015
|
+
d2.note = {
|
|
26016
|
+
text: String(opts.label),
|
|
26017
|
+
color: opts.labelColor || "#FFD700",
|
|
26018
|
+
size: Math.min(opts.labelSize || 8, 9),
|
|
26019
|
+
position: "top",
|
|
26020
|
+
mark: "hl"
|
|
26021
|
+
};
|
|
25558
26022
|
}
|
|
25559
|
-
this.
|
|
26023
|
+
this._flushCellFx(t.boxId);
|
|
25560
26024
|
}
|
|
25561
|
-
/**
|
|
25562
|
-
unhighlightNode(
|
|
26025
|
+
/** 取消节点 / 箱内电表户高亮(ref 可传 id/psrId/assetNo) */
|
|
26026
|
+
unhighlightNode(ref2) {
|
|
25563
26027
|
this._assertReady();
|
|
25564
|
-
this.
|
|
25565
|
-
|
|
25566
|
-
|
|
25567
|
-
|
|
25568
|
-
|
|
25569
|
-
|
|
25570
|
-
|
|
25571
|
-
|
|
25572
|
-
|
|
25573
|
-
|
|
26028
|
+
const t = this._resolveTarget(ref2);
|
|
26029
|
+
if (!t) return;
|
|
26030
|
+
if (t.kind === "node") {
|
|
26031
|
+
this._updateNodes([{
|
|
26032
|
+
id: t.id,
|
|
26033
|
+
style: {
|
|
26034
|
+
highlight: 0,
|
|
26035
|
+
annotationText: "",
|
|
26036
|
+
annotationColor: "",
|
|
26037
|
+
annotationSize: 0
|
|
26038
|
+
}
|
|
26039
|
+
}]);
|
|
26040
|
+
this._originalNodeStyles.delete(t.id);
|
|
26041
|
+
return;
|
|
26042
|
+
}
|
|
26043
|
+
const d2 = this._cellFx.get(this._cellFxKey(t.boxId, t.index));
|
|
26044
|
+
if (!d2) return;
|
|
26045
|
+
delete d2.hl;
|
|
26046
|
+
if (d2.note && d2.note.mark === "hl") delete d2.note;
|
|
26047
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) this._cellFx.delete(this._cellFxKey(t.boxId, t.index));
|
|
26048
|
+
this._flushCellFx(t.boxId);
|
|
25574
26049
|
}
|
|
25575
|
-
/**
|
|
25576
|
-
highlightPath(
|
|
26050
|
+
/** 高亮路径(节点与边;节点可为 id/psrId,箱内户可用 assetNo) */
|
|
26051
|
+
highlightPath(refs, opts = {}) {
|
|
25577
26052
|
this._assertReady();
|
|
25578
|
-
for (const id2 of
|
|
26053
|
+
for (const id2 of refs) {
|
|
25579
26054
|
this.highlightNode(id2, opts);
|
|
25580
26055
|
}
|
|
25581
26056
|
const edgeColor = opts.edgeColor || opts.color || "#00C8FF";
|
|
25582
|
-
for (let i = 0; i + 1 <
|
|
25583
|
-
const
|
|
26057
|
+
for (let i = 0; i + 1 < refs.length; i++) {
|
|
26058
|
+
const a2 = this._targetNodeId(refs[i]);
|
|
26059
|
+
const b = this._targetNodeId(refs[i + 1]);
|
|
26060
|
+
if (a2 == null || b == null || a2 === b) continue;
|
|
26061
|
+
const eid = this._edgeKey(a2, b);
|
|
25584
26062
|
this._snapshotEdgeStyle(eid);
|
|
25585
26063
|
this._updateEdges([{ id: eid, style: { overrideStroke: edgeColor, overrideLineWidth: 2.5 } }]);
|
|
25586
26064
|
}
|
|
25587
26065
|
}
|
|
25588
|
-
/**
|
|
26066
|
+
/** 取消所有高亮(含箱内电表户高亮环) */
|
|
25589
26067
|
unhighlightAll() {
|
|
25590
26068
|
this._assertReady();
|
|
25591
26069
|
const nodeUpdates = [];
|
|
@@ -25603,11 +26081,26 @@ class TopoApi {
|
|
|
25603
26081
|
}
|
|
25604
26082
|
if (edgeUpdates.length) this._updateEdges(edgeUpdates);
|
|
25605
26083
|
this._originalEdgeStyles.clear();
|
|
26084
|
+
const boxes = /* @__PURE__ */ new Set();
|
|
26085
|
+
for (const [key, d2] of this._cellFx) {
|
|
26086
|
+
const hasHl = !!d2.hl;
|
|
26087
|
+
if (d2.note && d2.note.mark === "hl") delete d2.note;
|
|
26088
|
+
delete d2.hl;
|
|
26089
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) {
|
|
26090
|
+
this._cellFx.delete(key);
|
|
26091
|
+
} else if (hasHl) {
|
|
26092
|
+
const sep = key.indexOf("#cust");
|
|
26093
|
+
boxes.add(sep > 0 ? key.slice(0, sep) : null);
|
|
26094
|
+
}
|
|
26095
|
+
}
|
|
26096
|
+
for (const boxId of boxes) {
|
|
26097
|
+
if (boxId != null) this._flushCellFx(boxId);
|
|
26098
|
+
}
|
|
25606
26099
|
}
|
|
25607
|
-
/**
|
|
25608
|
-
dimOthers(
|
|
26100
|
+
/** 非指定节点变暗(keepIds 内每一项可为 节点id/psrId/assetNo) */
|
|
26101
|
+
dimOthers(keepRefs, opacity2 = 0.15) {
|
|
25609
26102
|
this._assertReady();
|
|
25610
|
-
const keepSet = new Set(
|
|
26103
|
+
const keepSet = new Set(keepRefs.map((r) => this._targetNodeId(r)).filter((v) => v != null));
|
|
25611
26104
|
const updates = [];
|
|
25612
26105
|
for (const n of this._model.nodes) {
|
|
25613
26106
|
if (!keepSet.has(n.id)) {
|
|
@@ -25621,13 +26114,34 @@ class TopoApi {
|
|
|
25621
26114
|
// 六、动画
|
|
25622
26115
|
// ============================================================
|
|
25623
26116
|
/**
|
|
25624
|
-
* 节点脉动动画(G6 v5: updateNodeData + draw
|
|
26117
|
+
* 节点脉动动画(G6 v5: updateNodeData + draw 方式)。
|
|
26118
|
+
* ref = 节点 id/psrId → 图元透明度呼吸;assetNo → 该户电表格淡入淡出。
|
|
26119
|
+
* opts: { duration, min, max }(min/max 为透明度下限/上限,默认 0.3/1)
|
|
25625
26120
|
*/
|
|
25626
|
-
setNodePulse(
|
|
26121
|
+
setNodePulse(ref2, opts = {}) {
|
|
25627
26122
|
this._assertReady();
|
|
26123
|
+
const t = this._resolveTarget(ref2);
|
|
26124
|
+
if (!t) {
|
|
26125
|
+
console.warn(`[TopoApi] setNodePulse: 无法解析目标 ${ref2}(节点用 id/psrId,电表户用 assetNo)`);
|
|
26126
|
+
return;
|
|
26127
|
+
}
|
|
26128
|
+
if (t.kind === "customer") {
|
|
26129
|
+
this.removeAnimation(ref2);
|
|
26130
|
+
const d2 = this._cellFxGet(t.boxId, t.index);
|
|
26131
|
+
d2.pulse = {
|
|
26132
|
+
duration: opts.duration || 1500,
|
|
26133
|
+
min: opts.min != null ? opts.min : 0.3,
|
|
26134
|
+
max: opts.max != null ? opts.max : 1,
|
|
26135
|
+
_start: performance.now(),
|
|
26136
|
+
opacity: 1
|
|
26137
|
+
};
|
|
26138
|
+
this._flushCellFx(t.boxId);
|
|
26139
|
+
this._ensureFxLoop();
|
|
26140
|
+
return;
|
|
26141
|
+
}
|
|
26142
|
+
const nodeId = t.id;
|
|
25628
26143
|
this.removeAnimation(nodeId);
|
|
25629
26144
|
const duration2 = opts.duration || 1500;
|
|
25630
|
-
this._safeNodeStyle(nodeId, {});
|
|
25631
26145
|
let start = null;
|
|
25632
26146
|
const animate = (ts) => {
|
|
25633
26147
|
if (!this._graph || !this._animations.has(nodeId)) return;
|
|
@@ -25711,8 +26225,19 @@ class TopoApi {
|
|
|
25711
26225
|
const raf2 = requestAnimationFrame(animate);
|
|
25712
26226
|
this._animations.set(edgeId, raf2);
|
|
25713
26227
|
}
|
|
25714
|
-
/**
|
|
26228
|
+
/** 移除指定元素动画(ref = 节点 id/psrId → 脉动/边流动;assetNo → 该户脉动+光晕) */
|
|
25715
26229
|
removeAnimation(targetId) {
|
|
26230
|
+
const t = targetId != null ? this._resolveTarget(String(targetId)) : null;
|
|
26231
|
+
if (t && t.kind === "customer") {
|
|
26232
|
+
const key = this._cellFxKey(t.boxId, t.index);
|
|
26233
|
+
const d2 = this._cellFx.get(key);
|
|
26234
|
+
if (!d2) return;
|
|
26235
|
+
delete d2.pulse;
|
|
26236
|
+
delete d2.glow;
|
|
26237
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) this._cellFx.delete(key);
|
|
26238
|
+
this._flushCellFx(t.boxId);
|
|
26239
|
+
return;
|
|
26240
|
+
}
|
|
25716
26241
|
const raf2 = this._animations.get(targetId);
|
|
25717
26242
|
if (raf2) {
|
|
25718
26243
|
cancelAnimationFrame(raf2);
|
|
@@ -25735,9 +26260,11 @@ class TopoApi {
|
|
|
25735
26260
|
console.error("[TopoApi] removeAnimation update error:", e2);
|
|
25736
26261
|
}
|
|
25737
26262
|
}
|
|
25738
|
-
/**
|
|
26263
|
+
/** 移除所有动画(脉动/流动/光晕/户脉动户光晕) */
|
|
25739
26264
|
removeAllAnimations() {
|
|
25740
26265
|
this._removeAllAnimationsInternal();
|
|
26266
|
+
this._removeAllGlowsInternal();
|
|
26267
|
+
this._removeAllCellPulses();
|
|
25741
26268
|
}
|
|
25742
26269
|
_removeAllAnimationsInternal() {
|
|
25743
26270
|
for (const [id2, raf2] of this._animations) {
|
|
@@ -25761,12 +26288,270 @@ class TopoApi {
|
|
|
25761
26288
|
}
|
|
25762
26289
|
this._animations.clear();
|
|
25763
26290
|
}
|
|
26291
|
+
/** 清除全部箱内电表户的脉动(保留光晕/高亮/文字) */
|
|
26292
|
+
_removeAllCellPulses() {
|
|
26293
|
+
if (this._glowRaf) {
|
|
26294
|
+
cancelAnimationFrame(this._glowRaf);
|
|
26295
|
+
this._glowRaf = null;
|
|
26296
|
+
}
|
|
26297
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
26298
|
+
for (const [key, d2] of this._cellFx) {
|
|
26299
|
+
if (!d2.pulse) continue;
|
|
26300
|
+
delete d2.pulse;
|
|
26301
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) {
|
|
26302
|
+
this._cellFx.delete(key);
|
|
26303
|
+
} else {
|
|
26304
|
+
const sep = key.indexOf("#cust");
|
|
26305
|
+
if (sep > 0) dirty.add(key.slice(0, sep));
|
|
26306
|
+
}
|
|
26307
|
+
}
|
|
26308
|
+
if (!this._graph) return;
|
|
26309
|
+
for (const boxId of dirty) this._flushCellFx(boxId);
|
|
26310
|
+
if (this._hasFramedFx()) this._ensureFxLoop();
|
|
26311
|
+
}
|
|
26312
|
+
// ------------------------------------------------------------
|
|
26313
|
+
// 光晕 / 脉动 动画 —— 节点 & 箱内电表户
|
|
26314
|
+
// setNodeGlow(ref, { color, spread/radius, duration, opacity })
|
|
26315
|
+
// ref = 节点 id/psrId → 图元光环;assetNo → 该户表位光环
|
|
26316
|
+
// setNodePulse(ref, opts) → 节点透明度呼吸;assetNo → 该户电表格淡入淡出
|
|
26317
|
+
// ------------------------------------------------------------
|
|
26318
|
+
/** 是否有需要逐帧推进的动画(节点光晕 / 户光晕 / 户脉动) */
|
|
26319
|
+
_hasFramedFx() {
|
|
26320
|
+
if (this._glowNodes.size) return true;
|
|
26321
|
+
for (const d2 of this._cellFx.values()) {
|
|
26322
|
+
if (d2.glow && !d2.glow.done || d2.pulse && !d2.pulse.done) return true;
|
|
26323
|
+
}
|
|
26324
|
+
return false;
|
|
26325
|
+
}
|
|
26326
|
+
/** 启动统一动画循环(节点光晕 + 箱内户光晕/脉动,每帧一次批量写入) */
|
|
26327
|
+
_ensureFxLoop() {
|
|
26328
|
+
if (this._glowRaf) return;
|
|
26329
|
+
const loop = (ts) => {
|
|
26330
|
+
if (!this._graph) {
|
|
26331
|
+
this._glowRaf = null;
|
|
26332
|
+
return;
|
|
26333
|
+
}
|
|
26334
|
+
const updates = [];
|
|
26335
|
+
if (this._glowNodes.size) {
|
|
26336
|
+
for (const [id2, s2] of this._glowNodes) {
|
|
26337
|
+
if (!this._graph.getNodeData(id2)) {
|
|
26338
|
+
this._glowNodes.delete(id2);
|
|
26339
|
+
continue;
|
|
26340
|
+
}
|
|
26341
|
+
s2.phase = (ts - s2.start) % s2.duration / s2.duration;
|
|
26342
|
+
updates.push({ id: id2, style: { _glowT: s2.phase } });
|
|
26343
|
+
}
|
|
26344
|
+
}
|
|
26345
|
+
const dirtyBoxes = /* @__PURE__ */ new Set();
|
|
26346
|
+
if (this._cellFx.size) {
|
|
26347
|
+
const stale = [];
|
|
26348
|
+
for (const [key, d2] of this._cellFx) {
|
|
26349
|
+
if (!d2.glow && !d2.pulse) continue;
|
|
26350
|
+
const sep = key.indexOf("#cust");
|
|
26351
|
+
if (sep < 0) {
|
|
26352
|
+
stale.push(key);
|
|
26353
|
+
continue;
|
|
26354
|
+
}
|
|
26355
|
+
const boxId = key.slice(0, sep);
|
|
26356
|
+
const idx = Number(key.slice(sep + 5));
|
|
26357
|
+
const box2 = this._nodeByIdMap.get(boxId);
|
|
26358
|
+
if (!box2 || !Array.isArray(box2._customers) || idx < 0 || idx >= box2._customers.length) {
|
|
26359
|
+
stale.push(key);
|
|
26360
|
+
continue;
|
|
26361
|
+
}
|
|
26362
|
+
if (d2.glow) d2.glow.t = (ts - d2.glow._start) % d2.glow.duration / d2.glow.duration;
|
|
26363
|
+
if (d2.pulse) {
|
|
26364
|
+
d2.pulse.opacity = d2.pulse.min + (d2.pulse.max - d2.pulse.min) * (0.5 + 0.5 * Math.sin((ts - d2.pulse._start) % d2.pulse.duration / d2.pulse.duration * Math.PI * 2));
|
|
26365
|
+
}
|
|
26366
|
+
dirtyBoxes.add(boxId);
|
|
26367
|
+
}
|
|
26368
|
+
for (const k of stale) this._cellFx.delete(k);
|
|
26369
|
+
}
|
|
26370
|
+
if (dirtyBoxes.size) {
|
|
26371
|
+
const perBox = /* @__PURE__ */ new Map();
|
|
26372
|
+
for (const [key, d2] of this._cellFx) {
|
|
26373
|
+
const sep = key.indexOf("#cust");
|
|
26374
|
+
if (sep < 0) continue;
|
|
26375
|
+
const boxId = key.slice(0, sep);
|
|
26376
|
+
if (!dirtyBoxes.has(boxId)) continue;
|
|
26377
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) continue;
|
|
26378
|
+
if (!perBox.has(boxId)) perBox.set(boxId, {});
|
|
26379
|
+
perBox.get(boxId)[Number(key.slice(sep + 5))] = d2;
|
|
26380
|
+
}
|
|
26381
|
+
for (const [boxId, obj] of perBox) {
|
|
26382
|
+
const cur = this._safeNodeStyle(boxId, {});
|
|
26383
|
+
updates.push({ id: boxId, style: { ...cur, _cellFx: obj } });
|
|
26384
|
+
}
|
|
26385
|
+
}
|
|
26386
|
+
if (updates.length) {
|
|
26387
|
+
try {
|
|
26388
|
+
this._graph.updateNodeData(updates);
|
|
26389
|
+
this._graph.render();
|
|
26390
|
+
} catch {
|
|
26391
|
+
}
|
|
26392
|
+
}
|
|
26393
|
+
if (!this._hasFramedFx()) {
|
|
26394
|
+
this._glowRaf = null;
|
|
26395
|
+
return;
|
|
26396
|
+
}
|
|
26397
|
+
this._glowRaf = requestAnimationFrame(loop);
|
|
26398
|
+
};
|
|
26399
|
+
this._glowRaf = requestAnimationFrame(loop);
|
|
26400
|
+
}
|
|
26401
|
+
/** 清除某节点 style 上的光晕帧字段(光晕环不再绘制) */
|
|
26402
|
+
_clearGlowStyle(nodeId) {
|
|
26403
|
+
try {
|
|
26404
|
+
const cur = this._safeNodeStyle(nodeId, {});
|
|
26405
|
+
this._graph.updateNodeData([{
|
|
26406
|
+
id: nodeId,
|
|
26407
|
+
style: {
|
|
26408
|
+
...cur,
|
|
26409
|
+
_glowColor: void 0,
|
|
26410
|
+
_glowSpread: void 0,
|
|
26411
|
+
_glowOpacity: void 0,
|
|
26412
|
+
_glowDuration: void 0,
|
|
26413
|
+
_glowT: void 0
|
|
26414
|
+
}
|
|
26415
|
+
}]);
|
|
26416
|
+
} catch {
|
|
26417
|
+
}
|
|
26418
|
+
}
|
|
26419
|
+
/**
|
|
26420
|
+
* 节点 / 箱内电表户光晕(发光环)动画。
|
|
26421
|
+
* @param {string} ref 节点 id / psrId(图元光环)或资产编号 assetNo(该户表位光环)
|
|
26422
|
+
* @param {object} [opts] - { color, spread/radius, duration, opacity }
|
|
26423
|
+
* @returns {boolean} 是否命中并启动
|
|
26424
|
+
*/
|
|
26425
|
+
setNodeGlow(ref2, opts = {}) {
|
|
26426
|
+
this._assertReady();
|
|
26427
|
+
const t = this._resolveTarget(ref2);
|
|
26428
|
+
if (!t) {
|
|
26429
|
+
console.warn(`[TopoApi] setNodeGlow: 无法解析目标 ${ref2}(节点用 id/psrId,电表户用 assetNo)`);
|
|
26430
|
+
return false;
|
|
26431
|
+
}
|
|
26432
|
+
const cfg = {
|
|
26433
|
+
color: opts.color || "#00C8FF",
|
|
26434
|
+
spread: opts.spread != null ? opts.spread : opts.radius != null ? opts.radius : 14,
|
|
26435
|
+
opacity: opts.opacity != null ? opts.opacity : 0.9,
|
|
26436
|
+
duration: opts.duration || 1600,
|
|
26437
|
+
_start: performance.now(),
|
|
26438
|
+
t: 0
|
|
26439
|
+
};
|
|
26440
|
+
if (t.kind === "node") {
|
|
26441
|
+
this._glowNodes.set(t.id, cfg);
|
|
26442
|
+
this._updateNodes([{
|
|
26443
|
+
id: t.id,
|
|
26444
|
+
style: {
|
|
26445
|
+
_glowColor: cfg.color,
|
|
26446
|
+
_glowSpread: cfg.spread,
|
|
26447
|
+
_glowOpacity: cfg.opacity,
|
|
26448
|
+
_glowDuration: cfg.duration,
|
|
26449
|
+
_glowT: 0
|
|
26450
|
+
}
|
|
26451
|
+
}]);
|
|
26452
|
+
} else {
|
|
26453
|
+
const d2 = this._cellFxGet(t.boxId, t.index);
|
|
26454
|
+
d2.glow = cfg;
|
|
26455
|
+
this._flushCellFx(t.boxId);
|
|
26456
|
+
}
|
|
26457
|
+
this._ensureFxLoop();
|
|
26458
|
+
return true;
|
|
26459
|
+
}
|
|
26460
|
+
/**
|
|
26461
|
+
* 停止节点 / 箱内电表户的光晕动画。
|
|
26462
|
+
* @param {string} ref 节点 id / psrId / 资产编号
|
|
26463
|
+
*/
|
|
26464
|
+
removeNodeGlow(ref2) {
|
|
26465
|
+
this._assertReady();
|
|
26466
|
+
const t = this._resolveTarget(ref2);
|
|
26467
|
+
if (!t) return false;
|
|
26468
|
+
if (t.kind === "node") {
|
|
26469
|
+
if (this._glowNodes.delete(t.id)) this._clearGlowStyle(t.id);
|
|
26470
|
+
return true;
|
|
26471
|
+
}
|
|
26472
|
+
const d2 = this._cellFx.get(this._cellFxKey(t.boxId, t.index));
|
|
26473
|
+
if (!d2) return false;
|
|
26474
|
+
delete d2.glow;
|
|
26475
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) this._cellFx.delete(this._cellFxKey(t.boxId, t.index));
|
|
26476
|
+
this._flushCellFx(t.boxId);
|
|
26477
|
+
return true;
|
|
26478
|
+
}
|
|
26479
|
+
/** 停止全部光晕动画(节点 + 箱内电表户) */
|
|
26480
|
+
removeAllNodeGlows() {
|
|
26481
|
+
this._removeAllGlowsInternal();
|
|
26482
|
+
if (this._hasFramedFx()) this._ensureFxLoop();
|
|
26483
|
+
}
|
|
26484
|
+
_removeAllGlowsInternal() {
|
|
26485
|
+
if (this._glowRaf) {
|
|
26486
|
+
cancelAnimationFrame(this._glowRaf);
|
|
26487
|
+
this._glowRaf = null;
|
|
26488
|
+
}
|
|
26489
|
+
const updates = [];
|
|
26490
|
+
if (this._graph && this._glowNodes.size) {
|
|
26491
|
+
for (const [id2] of this._glowNodes) {
|
|
26492
|
+
if (!this._graph.getNodeData(id2)) continue;
|
|
26493
|
+
updates.push({
|
|
26494
|
+
id: id2,
|
|
26495
|
+
style: {
|
|
26496
|
+
_glowColor: void 0,
|
|
26497
|
+
_glowSpread: void 0,
|
|
26498
|
+
_glowOpacity: void 0,
|
|
26499
|
+
_glowDuration: void 0,
|
|
26500
|
+
_glowT: void 0
|
|
26501
|
+
}
|
|
26502
|
+
});
|
|
26503
|
+
}
|
|
26504
|
+
}
|
|
26505
|
+
this._glowNodes.clear();
|
|
26506
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
26507
|
+
for (const [key, d2] of this._cellFx) {
|
|
26508
|
+
if (!d2.glow) continue;
|
|
26509
|
+
delete d2.glow;
|
|
26510
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) {
|
|
26511
|
+
this._cellFx.delete(key);
|
|
26512
|
+
} else {
|
|
26513
|
+
const sep = key.indexOf("#cust");
|
|
26514
|
+
if (sep > 0) dirty.add(key.slice(0, sep));
|
|
26515
|
+
}
|
|
26516
|
+
}
|
|
26517
|
+
if (this._graph) {
|
|
26518
|
+
if (updates.length) {
|
|
26519
|
+
try {
|
|
26520
|
+
this._graph.updateNodeData(updates);
|
|
26521
|
+
this._graph.render();
|
|
26522
|
+
} catch {
|
|
26523
|
+
}
|
|
26524
|
+
}
|
|
26525
|
+
for (const boxId of dirty) this._flushCellFx(boxId);
|
|
26526
|
+
}
|
|
26527
|
+
}
|
|
25764
26528
|
// ============================================================
|
|
25765
26529
|
// 七、文字标注
|
|
25766
26530
|
// ============================================================
|
|
25767
|
-
/**
|
|
25768
|
-
|
|
26531
|
+
/**
|
|
26532
|
+
* 设置附加文字(ref = 节点 id/psrId → 节点旁标注;assetNo → 该户表位上方小标注)
|
|
26533
|
+
* opts: { color, fontSize, position: 'top'|'bottom' }
|
|
26534
|
+
*/
|
|
26535
|
+
setText(ref2, text2, opts = {}) {
|
|
25769
26536
|
this._assertReady();
|
|
26537
|
+
const t = this._resolveTarget(ref2);
|
|
26538
|
+
if (!t) {
|
|
26539
|
+
console.warn(`[TopoApi] setText: 无法解析目标 ${ref2}`);
|
|
26540
|
+
return;
|
|
26541
|
+
}
|
|
26542
|
+
if (t.kind === "customer") {
|
|
26543
|
+
const d2 = this._cellFxGet(t.boxId, t.index);
|
|
26544
|
+
d2.note = {
|
|
26545
|
+
text: String(text2),
|
|
26546
|
+
color: opts.color || "#FFD700",
|
|
26547
|
+
size: Math.min(opts.fontSize || 8, 9),
|
|
26548
|
+
position: opts.position === "bottom" ? "bottom" : "top",
|
|
26549
|
+
mark: "text"
|
|
26550
|
+
};
|
|
26551
|
+
this._flushCellFx(t.boxId);
|
|
26552
|
+
return;
|
|
26553
|
+
}
|
|
26554
|
+
const nodeId = t.id;
|
|
25770
26555
|
this._snapshotNodeStyle(nodeId);
|
|
25771
26556
|
this._nodeLabels.set(nodeId, { text: text2, opts });
|
|
25772
26557
|
this._updateNodes([{
|
|
@@ -25780,16 +26565,28 @@ class TopoApi {
|
|
|
25780
26565
|
}
|
|
25781
26566
|
}]);
|
|
25782
26567
|
}
|
|
25783
|
-
/**
|
|
25784
|
-
removeText(
|
|
26568
|
+
/** 移除附加文字(ref = 节点 id/psrId/资产编号) */
|
|
26569
|
+
removeText(ref2) {
|
|
25785
26570
|
this._assertReady();
|
|
26571
|
+
const t = this._resolveTarget(ref2);
|
|
26572
|
+
if (!t) return;
|
|
26573
|
+
if (t.kind === "customer") {
|
|
26574
|
+
const key = this._cellFxKey(t.boxId, t.index);
|
|
26575
|
+
const d2 = this._cellFx.get(key);
|
|
26576
|
+
if (!d2) return;
|
|
26577
|
+
delete d2.note;
|
|
26578
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) this._cellFx.delete(key);
|
|
26579
|
+
this._flushCellFx(t.boxId);
|
|
26580
|
+
return;
|
|
26581
|
+
}
|
|
26582
|
+
const nodeId = t.id;
|
|
25786
26583
|
this._nodeLabels.delete(nodeId);
|
|
25787
26584
|
this._updateNodes([{
|
|
25788
26585
|
id: nodeId,
|
|
25789
26586
|
style: { annotationText: "", annotationColor: "", annotationSize: 0 }
|
|
25790
26587
|
}]);
|
|
25791
26588
|
}
|
|
25792
|
-
/**
|
|
26589
|
+
/** 移除所有附加文字(含箱内电表户标注) */
|
|
25793
26590
|
removeAllTexts() {
|
|
25794
26591
|
this._assertReady();
|
|
25795
26592
|
const updates = [];
|
|
@@ -25798,6 +26595,18 @@ class TopoApi {
|
|
|
25798
26595
|
}
|
|
25799
26596
|
if (updates.length) this._updateNodes(updates);
|
|
25800
26597
|
this._nodeLabels.clear();
|
|
26598
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
26599
|
+
for (const [key, d2] of this._cellFx) {
|
|
26600
|
+
if (!d2.note) continue;
|
|
26601
|
+
delete d2.note;
|
|
26602
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) {
|
|
26603
|
+
this._cellFx.delete(key);
|
|
26604
|
+
} else {
|
|
26605
|
+
const sep = key.indexOf("#cust");
|
|
26606
|
+
if (sep > 0) dirty.add(key.slice(0, sep));
|
|
26607
|
+
}
|
|
26608
|
+
}
|
|
26609
|
+
for (const boxId of dirty) this._flushCellFx(boxId);
|
|
25801
26610
|
}
|
|
25802
26611
|
/** 按设备类型设字号 */
|
|
25803
26612
|
setDeviceText(cat, fontSize2) {
|
|
@@ -25827,22 +26636,30 @@ class TopoApi {
|
|
|
25827
26636
|
// 八、卡片盒
|
|
25828
26637
|
// ============================================================
|
|
25829
26638
|
/**
|
|
25830
|
-
*
|
|
25831
|
-
* @param {string}
|
|
25832
|
-
* @param {object} content - { title, fields, buttons, closable, width }
|
|
26639
|
+
* 在节点 / 箱内电表户旁弹卡片。
|
|
26640
|
+
* @param {string} ref 节点 id / psrId / 资产编号(assetNo → 卡片定位在该户表位旁)
|
|
26641
|
+
* @param {object} [content] - { title, fields, buttons, closable, width };
|
|
26642
|
+
* 缺省自动按「设备 / 客户档案」生成字段
|
|
25833
26643
|
*/
|
|
25834
|
-
showCard(
|
|
26644
|
+
showCard(ref2, content) {
|
|
25835
26645
|
var _a, _b, _c, _d, _e, _f;
|
|
25836
26646
|
this._assertReady();
|
|
25837
|
-
this.
|
|
26647
|
+
const t = this._resolveTarget(ref2);
|
|
26648
|
+
if (!t) {
|
|
26649
|
+
console.warn(`[TopoApi] showCard: 无法解析目标 ${ref2}(节点用 id/psrId,电表户用 assetNo)`);
|
|
26650
|
+
return;
|
|
26651
|
+
}
|
|
26652
|
+
const isCustomer = t.kind === "customer";
|
|
26653
|
+
const cardKey = isCustomer ? this._cellFxKey(t.boxId, t.index) : t.id;
|
|
26654
|
+
this.hideCard(cardKey);
|
|
25838
26655
|
const container = ((_b = (_a = this._graph).getContainer) == null ? void 0 : _b.call(_a)) || ((_f = (_e = (_d = (_c = this._graph).getCanvas) == null ? void 0 : _d.call(_c)) == null ? void 0 : _e.getContainer) == null ? void 0 : _f.call(_e));
|
|
25839
26656
|
if (!container) return;
|
|
25840
|
-
const
|
|
25841
|
-
if (!
|
|
26657
|
+
const world = this._targetWorld(ref2);
|
|
26658
|
+
if (!world) return;
|
|
25842
26659
|
let client = null;
|
|
25843
26660
|
try {
|
|
25844
26661
|
if (typeof this._graph.getClientByCanvas === "function") {
|
|
25845
|
-
const r = this._graph.getClientByCanvas([
|
|
26662
|
+
const r = this._graph.getClientByCanvas([world.x, world.y]);
|
|
25846
26663
|
client = Array.isArray(r) ? { x: r[0], y: r[1] } : r;
|
|
25847
26664
|
}
|
|
25848
26665
|
} catch (e2) {
|
|
@@ -25855,12 +26672,37 @@ class TopoApi {
|
|
|
25855
26672
|
const zoom = this._graph.getZoom();
|
|
25856
26673
|
const canvasCenter = this._canvasCenterXY();
|
|
25857
26674
|
const containerRect = container.getBoundingClientRect();
|
|
25858
|
-
screenX = containerRect.left + canvasCenter.x +
|
|
25859
|
-
screenY = containerRect.top + canvasCenter.y +
|
|
26675
|
+
screenX = containerRect.left + canvasCenter.x + world.x * zoom;
|
|
26676
|
+
screenY = containerRect.top + canvasCenter.y + world.y * zoom;
|
|
26677
|
+
}
|
|
26678
|
+
content = content || {};
|
|
26679
|
+
let cTitle = content.title;
|
|
26680
|
+
let cFields = content.fields;
|
|
26681
|
+
if (!cTitle || !Array.isArray(cFields) || !cFields.length) {
|
|
26682
|
+
if (isCustomer) {
|
|
26683
|
+
const c = t.customer || {};
|
|
26684
|
+
cTitle = cTitle || c.realConsName || c.consName || `表位 ${t.index + 1}`;
|
|
26685
|
+
cFields = [
|
|
26686
|
+
["户名", c.realConsName || c.consName],
|
|
26687
|
+
["资产编号", c.assetNo],
|
|
26688
|
+
["客户编号", c.consId],
|
|
26689
|
+
["户号", c.consNo],
|
|
26690
|
+
["电表编号", c.meterId]
|
|
26691
|
+
].filter(([, v]) => v != null && v !== "").map(([label, value]) => ({ label, value }));
|
|
26692
|
+
} else {
|
|
26693
|
+
const node = t.node || {};
|
|
26694
|
+
cTitle = cTitle || node.name || "设备信息";
|
|
26695
|
+
cFields = [
|
|
26696
|
+
["类型", node.catLabel],
|
|
26697
|
+
["设备名称", node.name],
|
|
26698
|
+
["PSR编号", node.psrId],
|
|
26699
|
+
["状态", node.status === "0" ? "正常" : node.status]
|
|
26700
|
+
].filter(([, v]) => v != null && v !== "").map(([label, value]) => ({ label, value }));
|
|
26701
|
+
}
|
|
25860
26702
|
}
|
|
25861
26703
|
const el = document.createElement("div");
|
|
25862
26704
|
el.className = "topo-card";
|
|
25863
|
-
el.dataset.nodeId =
|
|
26705
|
+
el.dataset.nodeId = cardKey;
|
|
25864
26706
|
el.style.cssText = `
|
|
25865
26707
|
position: fixed; left: ${screenX + 30}px; top: ${screenY - 20}px;
|
|
25866
26708
|
width: ${content.width || 240}px; z-index: 1000;
|
|
@@ -25870,22 +26712,22 @@ class TopoApi {
|
|
|
25870
26712
|
`;
|
|
25871
26713
|
const head = document.createElement("div");
|
|
25872
26714
|
head.style.cssText = "display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid #2c2c30;";
|
|
25873
|
-
head.innerHTML = `<span style="font-weight:600;color:#fff;">${
|
|
26715
|
+
head.innerHTML = `<span style="font-weight:600;color:#fff;">${String(cTitle || "").replace(/</g, "<")}</span>`;
|
|
25874
26716
|
if (content.closable !== false) {
|
|
25875
26717
|
const closeBtn = document.createElement("button");
|
|
25876
26718
|
closeBtn.textContent = "×";
|
|
25877
26719
|
closeBtn.style.cssText = "border:none;background:none;color:#888;font-size:16px;cursor:pointer;";
|
|
25878
|
-
closeBtn.onclick = () => this.hideCard(
|
|
26720
|
+
closeBtn.onclick = () => this.hideCard(cardKey);
|
|
25879
26721
|
head.appendChild(closeBtn);
|
|
25880
26722
|
}
|
|
25881
26723
|
el.appendChild(head);
|
|
25882
|
-
if (
|
|
26724
|
+
if (cFields && cFields.length) {
|
|
25883
26725
|
const body = document.createElement("div");
|
|
25884
26726
|
body.style.cssText = "padding:8px 12px;";
|
|
25885
|
-
for (const f2 of
|
|
26727
|
+
for (const f2 of cFields) {
|
|
25886
26728
|
const row2 = document.createElement("div");
|
|
25887
26729
|
row2.style.cssText = "display:flex;gap:8px;margin-bottom:4px;";
|
|
25888
|
-
row2.innerHTML = `<span style="color:#8a8a96;flex:none;width:56px;">${f2.label}</span><span style="color:#eee;">${f2.value}</span>`;
|
|
26730
|
+
row2.innerHTML = `<span style="color:#8a8a96;flex:none;width:56px;">${String(f2.label || "").replace(/</g, "<")}</span><span style="color:#eee;">${String(f2.value != null ? f2.value : "").replace(/</g, "<")}</span>`;
|
|
25889
26731
|
body.appendChild(row2);
|
|
25890
26732
|
}
|
|
25891
26733
|
el.appendChild(body);
|
|
@@ -25899,21 +26741,34 @@ class TopoApi {
|
|
|
25899
26741
|
b.style.cssText = btn.type === "primary" ? "padding:3px 10px;border:1px solid #00c8ff;border-radius:4px;background:#00c8ff;color:#000;cursor:pointer;font-size:12px;" : "padding:3px 10px;border:1px solid #3a3a42;border-radius:4px;background:#18181c;color:#ddd;cursor:pointer;font-size:12px;";
|
|
25900
26742
|
b.onclick = () => {
|
|
25901
26743
|
if (btn.onClick) btn.onClick();
|
|
25902
|
-
|
|
26744
|
+
if (isCustomer) {
|
|
26745
|
+
this._emit("card:button", {
|
|
26746
|
+
nodeId: t.boxId,
|
|
26747
|
+
boxId: t.boxId,
|
|
26748
|
+
index: t.index,
|
|
26749
|
+
customer: t.customer,
|
|
26750
|
+
cardKey,
|
|
26751
|
+
buttonIndex: idx
|
|
26752
|
+
});
|
|
26753
|
+
} else {
|
|
26754
|
+
this._emit("card:button", { nodeId: t.id, buttonIndex: idx });
|
|
26755
|
+
}
|
|
25903
26756
|
};
|
|
25904
26757
|
foot.appendChild(b);
|
|
25905
26758
|
});
|
|
25906
26759
|
el.appendChild(foot);
|
|
25907
26760
|
}
|
|
25908
26761
|
document.body.appendChild(el);
|
|
25909
|
-
this._cards.set(
|
|
26762
|
+
this._cards.set(cardKey, el);
|
|
25910
26763
|
}
|
|
25911
|
-
/**
|
|
25912
|
-
hideCard(
|
|
25913
|
-
const
|
|
26764
|
+
/** 关闭指定节点 / 电表户旁的卡片(ref 可传 id/psrId/assetNo) */
|
|
26765
|
+
hideCard(ref2) {
|
|
26766
|
+
const t = this._resolveTarget(ref2);
|
|
26767
|
+
const key = t ? t.kind === "node" ? t.id : this._cellFxKey(t.boxId, t.index) : String(ref2);
|
|
26768
|
+
const el = this._cards.get(key);
|
|
25914
26769
|
if (el) {
|
|
25915
26770
|
el.remove();
|
|
25916
|
-
this._cards.delete(
|
|
26771
|
+
this._cards.delete(key);
|
|
25917
26772
|
}
|
|
25918
26773
|
}
|
|
25919
26774
|
/** 关闭所有卡片 */
|
|
@@ -26247,12 +27102,17 @@ class TopoApi {
|
|
|
26247
27102
|
}
|
|
26248
27103
|
/**
|
|
26249
27104
|
* 添加拓扑高亮线
|
|
26250
|
-
* @param {string[]}
|
|
27105
|
+
* @param {string[]} refs - 节点路径(每点可为 id/psrId;assetNo 落到所在计量箱)
|
|
26251
27106
|
* @param {object} [opts] - { color, width, id }
|
|
26252
27107
|
* @returns {string} 线 ID
|
|
26253
27108
|
*/
|
|
26254
|
-
addTopoLine(
|
|
27109
|
+
addTopoLine(refs, opts = {}) {
|
|
26255
27110
|
this._assertReady();
|
|
27111
|
+
const nodeIds = refs.map((n) => this._targetNodeId(n)).filter((v) => v != null);
|
|
27112
|
+
if (nodeIds.length < 2) {
|
|
27113
|
+
console.warn("[TopoApi] addTopoLine: 有效节点不足 2 个,无法画线");
|
|
27114
|
+
return opts.id || `topo-${Date.now()}`;
|
|
27115
|
+
}
|
|
26256
27116
|
const lineId = opts.id || `topo-${Date.now()}`;
|
|
26257
27117
|
const color2 = opts.color || "#00C8FF";
|
|
26258
27118
|
const width = opts.width || 3;
|
|
@@ -27899,6 +28759,9 @@ function buildGraphData(model, layout) {
|
|
|
27899
28759
|
size: [w, h],
|
|
27900
28760
|
// 自定义字段(进 attributes,供 svg-symbol 节点绘制使用)
|
|
27901
28761
|
cat: n.cat,
|
|
28762
|
+
// labelText:节点标签文字(canvasNode 用固定每行 N 字换行绘制)。
|
|
28763
|
+
// 禁用 G6 内置 label(label:false),避免其单行画出长名不换行。
|
|
28764
|
+
label: false,
|
|
27902
28765
|
labelText: n.cat === "bus" || n.cat === "cableTerminal" || n.cat === "consumer" ? "" : n.shortName || "",
|
|
27903
28766
|
rotate: layout.stationRotate && layout.stationRotate.get(n.id) || 0,
|
|
27904
28767
|
highlight: 0,
|
|
@@ -28035,21 +28898,56 @@ function setNodePalette(themeName) {
|
|
|
28035
28898
|
if (key in C2) C2[key] = t[key];
|
|
28036
28899
|
}
|
|
28037
28900
|
}
|
|
28901
|
+
function nodeColor(attributes) {
|
|
28902
|
+
return attributes && attributes.color || null;
|
|
28903
|
+
}
|
|
28904
|
+
function cellColorAt(attributes, i) {
|
|
28905
|
+
const arr = attributes && attributes._cellColors;
|
|
28906
|
+
return Array.isArray(arr) ? arr[i] || null : null;
|
|
28907
|
+
}
|
|
28908
|
+
function localPalette(attributes) {
|
|
28909
|
+
const c = nodeColor(attributes);
|
|
28910
|
+
if (!c) return C2;
|
|
28911
|
+
return {
|
|
28912
|
+
...C2,
|
|
28913
|
+
red: c,
|
|
28914
|
+
orange: c,
|
|
28915
|
+
cyan: c,
|
|
28916
|
+
gold: c,
|
|
28917
|
+
trunkLine: c,
|
|
28918
|
+
dotFill: withAlpha(c, 0.18)
|
|
28919
|
+
};
|
|
28920
|
+
}
|
|
28921
|
+
function withAlpha(color2, a2) {
|
|
28922
|
+
if (typeof color2 !== "string") return color2;
|
|
28923
|
+
const m = color2.match(/^#([0-9a-f]{6}|[0-9a-f]{3})$/i);
|
|
28924
|
+
if (!m) return color2;
|
|
28925
|
+
let hex2 = m[1];
|
|
28926
|
+
if (hex2.length === 3) hex2 = hex2.split("").map((h) => h + h).join("");
|
|
28927
|
+
const n = parseInt(hex2, 16);
|
|
28928
|
+
const r = n >> 16 & 255, g = n >> 8 & 255, b = n & 255;
|
|
28929
|
+
return `rgba(${r},${g},${b},${a2})`;
|
|
28930
|
+
}
|
|
28931
|
+
const LW = 1.8;
|
|
28932
|
+
const LW_HI = 2.2;
|
|
28038
28933
|
function drawHighlight(attributes, group) {
|
|
28039
28934
|
if (!attributes.highlight) return;
|
|
28040
28935
|
const [w, h] = attributes.size;
|
|
28936
|
+
const color2 = attributes.highlightColor || C2.gold;
|
|
28937
|
+
const lw = attributes.highlightWidth || 2;
|
|
28938
|
+
const dash = attributes.highlightDash || [5, 4];
|
|
28041
28939
|
group.appendChild(
|
|
28042
28940
|
new Rect({
|
|
28043
28941
|
style: {
|
|
28044
|
-
x: -w / 2
|
|
28045
|
-
y: -h / 2
|
|
28046
|
-
width: w
|
|
28047
|
-
height: h
|
|
28048
|
-
radius:
|
|
28942
|
+
x: -w / 2 - 3,
|
|
28943
|
+
y: -h / 2 - 3,
|
|
28944
|
+
width: w + 6,
|
|
28945
|
+
height: h + 6,
|
|
28946
|
+
radius: 6,
|
|
28049
28947
|
fill: "none",
|
|
28050
|
-
stroke:
|
|
28051
|
-
lineWidth:
|
|
28052
|
-
lineDash:
|
|
28948
|
+
stroke: color2,
|
|
28949
|
+
lineWidth: lw,
|
|
28950
|
+
lineDash: dash
|
|
28053
28951
|
}
|
|
28054
28952
|
})
|
|
28055
28953
|
);
|
|
@@ -28061,15 +28959,16 @@ function drawAnnotation(attributes, group) {
|
|
|
28061
28959
|
const pos = attributes.annotationPos || "top";
|
|
28062
28960
|
const offset = attributes.annotationOffset || 4;
|
|
28063
28961
|
const y = pos === "top" ? -h / 2 - offset - 6 : h / 2 + offset + 6;
|
|
28064
|
-
_drawWrappedText(group, label, 0, y, attributes.annotationSize || 12, attributes.annotationColor || C2.white, w + 10, 1.25);
|
|
28962
|
+
_drawWrappedText(group, label, 0, y, attributes.annotationSize || 12, attributes.annotationColor || C2.white, w + 10, 1.25, 600);
|
|
28065
28963
|
}
|
|
28066
28964
|
function drawLabelUnder(attributes, group) {
|
|
28067
28965
|
const label = attributes.labelText || "";
|
|
28068
28966
|
if (!label) return;
|
|
28069
28967
|
const [w, h] = attributes.size;
|
|
28070
|
-
|
|
28968
|
+
const x0 = w / 2 + 6;
|
|
28969
|
+
_drawWrappedTextFixed(group, label, x0, -h / 2, 8, C2.white, 4, 1.3, 500, "left");
|
|
28071
28970
|
}
|
|
28072
|
-
function text(attributes, group, str2, x, y, size, fill) {
|
|
28971
|
+
function text(attributes, group, str2, x, y, size, fill, weight = 400) {
|
|
28073
28972
|
if (!str2) return;
|
|
28074
28973
|
group.appendChild(
|
|
28075
28974
|
new Text({
|
|
@@ -28079,6 +28978,7 @@ function text(attributes, group, str2, x, y, size, fill) {
|
|
|
28079
28978
|
y,
|
|
28080
28979
|
fontSize: size,
|
|
28081
28980
|
fill,
|
|
28981
|
+
fontWeight: weight,
|
|
28082
28982
|
fontFamily: FONT,
|
|
28083
28983
|
textAlign: "center",
|
|
28084
28984
|
textBaseline: "middle"
|
|
@@ -28109,7 +29009,44 @@ function _wrapLines(str2, fontSize2, maxWidth) {
|
|
|
28109
29009
|
if (line) lines.push(line);
|
|
28110
29010
|
return lines;
|
|
28111
29011
|
}
|
|
28112
|
-
function
|
|
29012
|
+
function _wrapLinesFixed(str2, chars2) {
|
|
29013
|
+
if (!str2) return [];
|
|
29014
|
+
const lines = [];
|
|
29015
|
+
let line = "";
|
|
29016
|
+
for (const ch of str2) {
|
|
29017
|
+
line += ch;
|
|
29018
|
+
if (line.length >= chars2) {
|
|
29019
|
+
lines.push(line);
|
|
29020
|
+
line = "";
|
|
29021
|
+
}
|
|
29022
|
+
}
|
|
29023
|
+
if (line) lines.push(line);
|
|
29024
|
+
return lines;
|
|
29025
|
+
}
|
|
29026
|
+
function _drawWrappedTextFixed(group, str2, x, y, fontSize2, fill, chars2, lineGap = 1.3, weight = 400, align = "center") {
|
|
29027
|
+
if (!str2) return;
|
|
29028
|
+
const lines = _wrapLinesFixed(str2, chars2);
|
|
29029
|
+
if (!lines.length) return;
|
|
29030
|
+
const lineHeight2 = fontSize2 * lineGap;
|
|
29031
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29032
|
+
group.appendChild(
|
|
29033
|
+
new Text({
|
|
29034
|
+
style: {
|
|
29035
|
+
text: lines[i],
|
|
29036
|
+
x,
|
|
29037
|
+
y: y + lineHeight2 * i,
|
|
29038
|
+
fontSize: fontSize2,
|
|
29039
|
+
fill,
|
|
29040
|
+
fontWeight: weight,
|
|
29041
|
+
fontFamily: FONT,
|
|
29042
|
+
textAlign: align === "left" ? "left" : "center",
|
|
29043
|
+
textBaseline: "middle"
|
|
29044
|
+
}
|
|
29045
|
+
})
|
|
29046
|
+
);
|
|
29047
|
+
}
|
|
29048
|
+
}
|
|
29049
|
+
function _drawWrappedText(group, str2, x, y, fontSize2, fill, maxWidth, lineGap = 1.3, weight = 400) {
|
|
28113
29050
|
if (!str2) return;
|
|
28114
29051
|
const lines = _wrapLines(str2, fontSize2, maxWidth);
|
|
28115
29052
|
if (!lines.length) return;
|
|
@@ -28124,6 +29061,7 @@ function _drawWrappedText(group, str2, x, y, fontSize2, fill, maxWidth, lineGap
|
|
|
28124
29061
|
y: y - totalH / 2 + lineHeight2 * (i + 0.5),
|
|
28125
29062
|
fontSize: fontSize2,
|
|
28126
29063
|
fill,
|
|
29064
|
+
fontWeight: weight,
|
|
28127
29065
|
fontFamily: FONT,
|
|
28128
29066
|
textAlign: "center",
|
|
28129
29067
|
textBaseline: "middle"
|
|
@@ -28133,77 +29071,172 @@ function _drawWrappedText(group, str2, x, y, fontSize2, fill, maxWidth, lineGap
|
|
|
28133
29071
|
}
|
|
28134
29072
|
}
|
|
28135
29073
|
function drawTransformer(attributes, group) {
|
|
28136
|
-
const
|
|
28137
|
-
|
|
28138
|
-
group.appendChild(new
|
|
28139
|
-
group.appendChild(new Circle({ style: { cx:
|
|
28140
|
-
group.appendChild(new
|
|
29074
|
+
const P = localPalette(attributes);
|
|
29075
|
+
const lw = LW;
|
|
29076
|
+
group.appendChild(new Line({ style: { x1: -26, y1: 0, x2: -14, y2: 0, stroke: P.trunkLine, lineWidth: lw, lineCap: "round" } }));
|
|
29077
|
+
group.appendChild(new Circle({ style: { cx: -4.5, cy: 0, r: 10, fill: P.orange, stroke: P.trunkLine, lineWidth: 1.4 } }));
|
|
29078
|
+
group.appendChild(new Circle({ style: { cx: -7.2, cy: -3.2, r: 3.2, fill: withAlpha(C2.white, 0.25), stroke: "none" } }));
|
|
29079
|
+
group.appendChild(new Circle({ style: { cx: 10, cy: 0, r: 10, fill: withAlpha(P.red, 0.06), stroke: P.red, lineWidth: LW_HI } }));
|
|
29080
|
+
group.appendChild(new Line({ style: { x1: 20, y1: 0, x2: 30, y2: 0, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
28141
29081
|
}
|
|
28142
29082
|
function drawFuse(attributes, group) {
|
|
28143
|
-
const
|
|
28144
|
-
|
|
28145
|
-
group.appendChild(new
|
|
28146
|
-
group.appendChild(new
|
|
28147
|
-
group.appendChild(new Rect({ style: { x: -
|
|
28148
|
-
group.appendChild(new
|
|
28149
|
-
group.appendChild(new
|
|
29083
|
+
const P = localPalette(attributes);
|
|
29084
|
+
const lw = LW;
|
|
29085
|
+
group.appendChild(new Line({ style: { x1: -28, y1: 0, x2: -19, y2: 0, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
29086
|
+
group.appendChild(new Line({ style: { x1: 19, y1: 0, x2: 28, y2: 0, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
29087
|
+
group.appendChild(new Rect({ style: { x: -19, y: -7.5, width: 38, height: 15, radius: 2, fill: withAlpha(P.red, 0.05), stroke: P.red, lineWidth: lw } }));
|
|
29088
|
+
group.appendChild(new Circle({ style: { cx: -13, cy: 0, r: 3.4, fill: P.red, stroke: "none" } }));
|
|
29089
|
+
group.appendChild(new Rect({ style: { x: -8.2, y: -2.4, width: 5, height: 4.8, radius: 0.6, fill: P.red } }));
|
|
29090
|
+
group.appendChild(new Rect({ style: { x: -1.4, y: -5.4, width: 13, height: 10.8, radius: 0.8, fill: P.red } }));
|
|
29091
|
+
group.appendChild(new Rect({ style: { x: 5, y: -1.2, width: 8, height: 2.4, fill: C2.white, opacity: 0.55 } }));
|
|
28150
29092
|
}
|
|
28151
29093
|
function drawBreaker(attributes, group) {
|
|
28152
|
-
const
|
|
28153
|
-
|
|
28154
|
-
|
|
28155
|
-
|
|
28156
|
-
|
|
28157
|
-
|
|
29094
|
+
const P = localPalette(attributes);
|
|
29095
|
+
const lw = LW;
|
|
29096
|
+
const [nw] = attributes.size;
|
|
29097
|
+
const bw = 42;
|
|
29098
|
+
const bh = 20, r = 4;
|
|
29099
|
+
const bx = -bw / 2, by = -bh / 2;
|
|
29100
|
+
const xEdge = (nw || 60) / 2;
|
|
29101
|
+
const xBox = bw / 2;
|
|
29102
|
+
if (xEdge > xBox) {
|
|
29103
|
+
group.appendChild(new Line({ style: { x1: -xEdge, y1: 0, x2: -xBox, y2: 0, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
29104
|
+
group.appendChild(new Line({ style: { x1: xBox, y1: 0, x2: xEdge, y2: 0, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
29105
|
+
}
|
|
29106
|
+
group.appendChild(new Rect({ style: { x: bx, y: by, width: bw, height: bh, radius: r, fill: P.red, stroke: withAlpha(C2.white, 0.22), lineWidth: 1 } }));
|
|
29107
|
+
if (attributes.labelText) {
|
|
29108
|
+
_drawWrappedText(group, attributes.labelText, 0, 0.6, 11.5, "#FFFFFF", bw - 8, 1.15, 700);
|
|
29109
|
+
}
|
|
28158
29110
|
}
|
|
28159
29111
|
function drawCableTerminal(attributes, group) {
|
|
28160
|
-
|
|
29112
|
+
const P = localPalette(attributes);
|
|
29113
|
+
const lw = 1.6;
|
|
29114
|
+
group.appendChild(new Line({ style: { x1: -11, y1: 0, x2: -5.5, y2: 0, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
29115
|
+
group.appendChild(new Polygon({
|
|
29116
|
+
style: {
|
|
29117
|
+
points: [[-5, -9.5], [11, 0], [-5, 9.5]],
|
|
29118
|
+
fill: P.red,
|
|
29119
|
+
stroke: withAlpha(C2.white, 0.3),
|
|
29120
|
+
lineWidth: 1
|
|
29121
|
+
}
|
|
29122
|
+
}));
|
|
28161
29123
|
}
|
|
28162
29124
|
function drawStationTerminal(attributes, group) {
|
|
28163
|
-
|
|
28164
|
-
|
|
29125
|
+
const P = localPalette(attributes);
|
|
29126
|
+
const lw = LW;
|
|
29127
|
+
group.appendChild(new Line({ style: { x1: -12, y1: 0, x2: -4, y2: 0, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
29128
|
+
group.appendChild(new Polygon({
|
|
29129
|
+
style: {
|
|
29130
|
+
points: [[0, -6], [0, 6], [10, 0]],
|
|
29131
|
+
fill: withAlpha(P.red, 0.08),
|
|
29132
|
+
stroke: P.red,
|
|
29133
|
+
lineWidth: lw
|
|
29134
|
+
}
|
|
29135
|
+
}));
|
|
29136
|
+
}
|
|
29137
|
+
function drawMeterFace(group, cx, cy, meterW, meterH, lineColor, markCell, cellIdx) {
|
|
29138
|
+
const r = Math.max(2, meterW * 0.16);
|
|
29139
|
+
const body = new Rect({ style: { x: cx - meterW / 2, y: cy - meterH / 2, width: meterW, height: meterH, radius: r, fill: withAlpha(lineColor, 0.06), stroke: lineColor, lineWidth: 1.2, cursor: "pointer" } });
|
|
29140
|
+
if (markCell) markCell(body, cellIdx);
|
|
29141
|
+
group.appendChild(body);
|
|
29142
|
+
const winW = meterW * 0.66, winH = Math.max(2.4, meterH * 0.17);
|
|
29143
|
+
const win = new Rect({ style: { x: cx - winW / 2, y: cy - meterH * 0.31, width: winW, height: winH, radius: winH / 2, fill: lineColor, opacity: 0.5, stroke: "none" } });
|
|
29144
|
+
if (markCell) markCell(win, cellIdx);
|
|
29145
|
+
group.appendChild(win);
|
|
29146
|
+
const dot2 = new Circle({ style: { cx, cy: cy + meterH * 0.12, r: 1.6, fill: lineColor, opacity: 0.85, stroke: "none" } });
|
|
29147
|
+
if (markCell) markCell(dot2, cellIdx);
|
|
29148
|
+
group.appendChild(dot2);
|
|
29149
|
+
}
|
|
29150
|
+
function cellFxAt(attributes, i) {
|
|
29151
|
+
const fx = attributes && attributes._cellFx;
|
|
29152
|
+
return fx && typeof fx === "object" ? fx[i] || null : null;
|
|
29153
|
+
}
|
|
29154
|
+
function drawCellGlowRings(group, cx, cy, baseR, g, t) {
|
|
29155
|
+
const color2 = g.color || "#00C8FF";
|
|
29156
|
+
const spread = g.spread || 10;
|
|
29157
|
+
const maxOp = g.opacity == null ? 0.8 : g.opacity;
|
|
29158
|
+
const ease2 = 1 - (1 - t) * (1 - t);
|
|
29159
|
+
const alpha = maxOp * Math.pow(1 - t, 1.4);
|
|
29160
|
+
if (alpha <= 0.02) return;
|
|
29161
|
+
const r = baseR + spread * ease2;
|
|
29162
|
+
const lw = Math.max(1, 3.2 * (1 - t) + 0.6);
|
|
29163
|
+
group.appendChild(new Circle({ style: { cx, cy, r, stroke: color2, lineWidth: lw, strokeOpacity: alpha, fill: "none", lineCap: "round" } }));
|
|
29164
|
+
group.appendChild(new Circle({ style: { cx, cy, r: r + spread * 0.6, stroke: color2, lineWidth: Math.max(0.6, lw * 0.55), strokeOpacity: alpha * 0.35, fill: "none", lineCap: "round" } }));
|
|
29165
|
+
}
|
|
29166
|
+
function drawCellHighlight(group, cx, cy, baseR, hl) {
|
|
29167
|
+
group.appendChild(new Circle({
|
|
29168
|
+
style: {
|
|
29169
|
+
cx,
|
|
29170
|
+
cy,
|
|
29171
|
+
r: baseR + 3,
|
|
29172
|
+
stroke: hl.color || "#FFD700",
|
|
29173
|
+
lineWidth: hl.width || 2,
|
|
29174
|
+
lineDash: hl.dash || [4, 3],
|
|
29175
|
+
fill: "none"
|
|
29176
|
+
}
|
|
29177
|
+
}));
|
|
29178
|
+
}
|
|
29179
|
+
function drawCellNote(group, cx, cy, baseR, note) {
|
|
29180
|
+
const size = note.size || 7;
|
|
29181
|
+
const color2 = note.color || C2.gold;
|
|
29182
|
+
const top = !note.position || note.position === "top";
|
|
29183
|
+
const y = top ? cy - baseR - 5 - size / 2 : cy + baseR + 6 + size / 2;
|
|
29184
|
+
_drawWrappedTextFixed(group, note.text, cx, y, size, color2, 6, 1.15, 600, "center");
|
|
28165
29185
|
}
|
|
28166
29186
|
function drawMeterBox(attributes, group) {
|
|
28167
29187
|
const [w, h] = attributes.size;
|
|
29188
|
+
const boxColor = nodeColor(attributes);
|
|
28168
29189
|
if (attributes._meterBoxMode === "single") {
|
|
28169
29190
|
const nodeId = attributes._nodeId || "";
|
|
28170
29191
|
const sel = attributes._custSel === 0;
|
|
28171
29192
|
const hover = attributes._custHover === 0;
|
|
28172
|
-
const
|
|
29193
|
+
const baseColor = cellColorAt(attributes, 0) || boxColor || C2.white;
|
|
29194
|
+
const lineColor = sel ? C2.gold : hover ? C2.cyan : baseColor;
|
|
29195
|
+
const fx = cellFxAt(attributes, 0);
|
|
29196
|
+
const cellGroup = new Group({});
|
|
28173
29197
|
const markCell = (shape) => {
|
|
28174
29198
|
if (!nodeId) return;
|
|
28175
29199
|
shape.__custBox = nodeId;
|
|
28176
29200
|
shape.__custIndex = 0;
|
|
28177
29201
|
};
|
|
29202
|
+
const meterW = 20;
|
|
29203
|
+
const meterH = 26;
|
|
29204
|
+
const cx = 0;
|
|
29205
|
+
const cy = -1;
|
|
28178
29206
|
if (sel || hover) {
|
|
28179
|
-
const ring = new
|
|
29207
|
+
const ring = new Rect({
|
|
28180
29208
|
style: {
|
|
28181
|
-
|
|
28182
|
-
|
|
28183
|
-
|
|
28184
|
-
|
|
28185
|
-
|
|
29209
|
+
x: cx - meterW / 2 - 4,
|
|
29210
|
+
y: cy - meterH / 2 - 4,
|
|
29211
|
+
width: meterW + 8,
|
|
29212
|
+
height: meterH + 8,
|
|
29213
|
+
radius: 5,
|
|
29214
|
+
fill: "none",
|
|
28186
29215
|
stroke: sel ? C2.gold : C2.cyan,
|
|
28187
|
-
lineWidth: sel ?
|
|
29216
|
+
lineWidth: sel ? 2 : 1.3
|
|
28188
29217
|
}
|
|
28189
29218
|
});
|
|
28190
29219
|
markCell(ring);
|
|
28191
|
-
|
|
28192
|
-
}
|
|
28193
|
-
|
|
28194
|
-
|
|
28195
|
-
|
|
28196
|
-
|
|
28197
|
-
|
|
28198
|
-
|
|
29220
|
+
cellGroup.appendChild(ring);
|
|
29221
|
+
}
|
|
29222
|
+
drawMeterFace(cellGroup, cx, cy, meterW, meterH, lineColor, markCell, 0);
|
|
29223
|
+
if (fx) {
|
|
29224
|
+
const baseR = Math.max(12, meterH / 2 + 2);
|
|
29225
|
+
if (fx.hl) drawCellHighlight(cellGroup, cx, cy, baseR, fx.hl);
|
|
29226
|
+
if (fx.note && fx.note.text) drawCellNote(cellGroup, cx, cy, baseR, fx.note);
|
|
29227
|
+
if (fx.glow) drawCellGlowRings(cellGroup, cx, cy, baseR, fx.glow, fx.glow.t || 0);
|
|
29228
|
+
}
|
|
28199
29229
|
const name = attributes._customers && attributes._customers[0] ? attributes._customers[0].realConsName || attributes._customers[0].consName || "" : "";
|
|
28200
29230
|
if (name) {
|
|
28201
|
-
|
|
28202
|
-
|
|
28203
|
-
|
|
29231
|
+
_drawWrappedTextFixed(cellGroup, name, cx, meterH / 2 + 7, 7, lineColor, 4, 1.15, 500);
|
|
29232
|
+
}
|
|
29233
|
+
if (fx && fx.pulse && fx.pulse.opacity != null && fx.pulse.opacity < 1) {
|
|
29234
|
+
cellGroup.style.opacity = fx.pulse.opacity;
|
|
28204
29235
|
}
|
|
29236
|
+
group.appendChild(cellGroup);
|
|
28205
29237
|
} else if (attributes._meterBoxMode === "grid" && attributes._customers) {
|
|
28206
|
-
|
|
29238
|
+
const frameColor = boxColor || C2.meterText;
|
|
29239
|
+
group.appendChild(new Rect({ style: { x: -w / 2, y: -h / 2, width: w, height: h, fill: withAlpha(frameColor, 0.04), stroke: frameColor, lineWidth: 1, radius: 4 } }));
|
|
28207
29240
|
const cols = attributes._meterBoxCols || 5;
|
|
28208
29241
|
const customers = attributes._customers;
|
|
28209
29242
|
const showName = attributes._meterBoxShowName;
|
|
@@ -28214,11 +29247,12 @@ function drawMeterBox(attributes, group) {
|
|
|
28214
29247
|
const gridH = rows * cellH + (rows - 1) * METER_GAP;
|
|
28215
29248
|
const startX = -gridW / 2;
|
|
28216
29249
|
const startY = -gridH / 2;
|
|
28217
|
-
const
|
|
29250
|
+
const meterW = Math.min(13, cellW * 0.5);
|
|
29251
|
+
const meterH = meterW * 1.5;
|
|
29252
|
+
const hitR = Math.max(9, meterH * 0.4);
|
|
28218
29253
|
const custSel = attributes._custSel == null ? -1 : attributes._custSel;
|
|
28219
29254
|
const custHover = attributes._custHover == null ? -1 : attributes._custHover;
|
|
28220
29255
|
const nodeId = attributes._nodeId || "";
|
|
28221
|
-
const hitR = meterR + 2.5;
|
|
28222
29256
|
const markCell = (shape, i) => {
|
|
28223
29257
|
if (!nodeId) return;
|
|
28224
29258
|
shape.__custBox = nodeId;
|
|
@@ -28231,9 +29265,12 @@ function drawMeterBox(attributes, group) {
|
|
|
28231
29265
|
const cy = startY + row2 * (cellH + METER_GAP) + cellW / 2;
|
|
28232
29266
|
const sel = i === custSel;
|
|
28233
29267
|
const hover = i === custHover;
|
|
28234
|
-
const
|
|
29268
|
+
const baseColor = cellColorAt(attributes, i) || C2.meterText;
|
|
29269
|
+
const lineColor = sel ? C2.gold : hover ? C2.cyan : baseColor;
|
|
29270
|
+
const fx = cellFxAt(attributes, i);
|
|
29271
|
+
const cellGroup = new Group({});
|
|
28235
29272
|
if (sel || hover) {
|
|
28236
|
-
|
|
29273
|
+
cellGroup.appendChild(new Circle({
|
|
28237
29274
|
style: {
|
|
28238
29275
|
cx,
|
|
28239
29276
|
cy,
|
|
@@ -28241,49 +29278,83 @@ function drawMeterBox(attributes, group) {
|
|
|
28241
29278
|
fill: sel ? C2.gold : C2.cyan,
|
|
28242
29279
|
fillOpacity: sel ? 0.3 : 0.18,
|
|
28243
29280
|
stroke: sel ? C2.gold : C2.cyan,
|
|
28244
|
-
lineWidth: sel ? 1.
|
|
29281
|
+
lineWidth: sel ? 1.7 : 1.1
|
|
28245
29282
|
}
|
|
28246
29283
|
}));
|
|
28247
29284
|
}
|
|
28248
|
-
|
|
28249
|
-
|
|
29285
|
+
drawMeterFace(cellGroup, cx, cy, meterW, meterH, lineColor, markCell, i);
|
|
29286
|
+
if (fx) {
|
|
29287
|
+
if (fx.hl) drawCellHighlight(cellGroup, cx, cy, hitR, fx.hl);
|
|
29288
|
+
if (fx.note && fx.note.text) drawCellNote(cellGroup, cx, cy, hitR, fx.note);
|
|
29289
|
+
if (fx.glow) drawCellGlowRings(cellGroup, cx, cy, hitR, fx.glow, fx.glow.t || 0);
|
|
29290
|
+
}
|
|
29291
|
+
if (showName) {
|
|
29292
|
+
const name = customers[i].realConsName || customers[i].consName || "";
|
|
29293
|
+
if (name) {
|
|
29294
|
+
_drawWrappedTextFixed(cellGroup, name, cx, cy + meterH / 2 + 6.5, 6.5, lineColor, 4, 1.15, 500);
|
|
29295
|
+
}
|
|
29296
|
+
}
|
|
28250
29297
|
const hit = new Circle({ style: { cx, cy, r: hitR, fill: "rgba(255,255,255,0.01)", stroke: "none", cursor: "pointer" } });
|
|
28251
29298
|
markCell(hit, i);
|
|
28252
|
-
|
|
29299
|
+
cellGroup.appendChild(hit);
|
|
28253
29300
|
if (showName) {
|
|
28254
29301
|
const name = customers[i].realConsName || customers[i].consName || "";
|
|
28255
29302
|
if (name) {
|
|
28256
|
-
const
|
|
28257
|
-
|
|
28258
|
-
|
|
29303
|
+
const hitName = new Circle({
|
|
29304
|
+
style: {
|
|
29305
|
+
cx,
|
|
29306
|
+
cy: cy + meterH / 2 + 6.5 + 4,
|
|
29307
|
+
r: Math.max(8, Math.min(14, Math.ceil(name.length / 4) * 4 + 4)),
|
|
29308
|
+
fill: "rgba(255,255,255,0.01)",
|
|
29309
|
+
stroke: "none",
|
|
29310
|
+
cursor: "pointer"
|
|
29311
|
+
}
|
|
29312
|
+
});
|
|
29313
|
+
markCell(hitName, i);
|
|
29314
|
+
cellGroup.appendChild(hitName);
|
|
28259
29315
|
}
|
|
28260
29316
|
}
|
|
29317
|
+
if (fx && fx.pulse && fx.pulse.opacity != null && fx.pulse.opacity < 1) {
|
|
29318
|
+
cellGroup.style.opacity = fx.pulse.opacity;
|
|
29319
|
+
}
|
|
29320
|
+
group.appendChild(cellGroup);
|
|
28261
29321
|
}
|
|
28262
29322
|
} else {
|
|
28263
|
-
|
|
28264
|
-
|
|
29323
|
+
const s2 = Math.min(w, h) || 21;
|
|
29324
|
+
const ink = boxColor || C2.meterText;
|
|
29325
|
+
group.appendChild(new Rect({ style: { x: -s2 / 2, y: -s2 / 2, width: s2, height: s2, radius: Math.max(2, s2 * 0.16), fill: C2.meterFill, stroke: ink, lineWidth: 1.4 } }));
|
|
29326
|
+
group.appendChild(new Rect({ style: { x: -s2 / 2 + 2, y: -s2 / 2 + 2, width: s2 - 4, height: 2, radius: 1, fill: withAlpha(ink, 0.35), stroke: "none" } }));
|
|
29327
|
+
text(attributes, group, "JX", 0, s2 * 0.06, Math.max(8, s2 * 0.4), ink, 700);
|
|
28265
29328
|
}
|
|
28266
29329
|
}
|
|
28267
29330
|
function drawConsumer(attributes, group) {
|
|
28268
|
-
|
|
28269
|
-
|
|
29331
|
+
const ink = nodeColor(attributes) || C2.white;
|
|
29332
|
+
group.appendChild(new Circle({ style: { cx: 0, cy: 0, r: 13, fill: "none", stroke: ink, lineWidth: LW_HI } }));
|
|
29333
|
+
text(attributes, group, "J", 0, 0.8, 14.5, ink, 700);
|
|
28270
29334
|
}
|
|
28271
29335
|
function drawBus(attributes, group) {
|
|
28272
29336
|
const [w] = attributes.size;
|
|
28273
|
-
|
|
29337
|
+
const ink = nodeColor(attributes) || C2.white;
|
|
29338
|
+
const hh = Math.max(4.5, Math.min(6, w / 10));
|
|
29339
|
+
group.appendChild(new Rect({ style: { x: -w / 2, y: -hh / 2, width: w, height: hh, radius: hh / 2, fill: withAlpha(ink, 0.5), stroke: ink, lineWidth: 1.2 } }));
|
|
29340
|
+
group.appendChild(new Rect({ style: { x: -w / 2 + 1, y: -0.7, width: w - 2, height: 1.4, fill: withAlpha(C2.canvasBg || "#000", 0.18), stroke: "none" } }));
|
|
28274
29341
|
}
|
|
28275
29342
|
function drawLineDot(attributes, group) {
|
|
28276
|
-
|
|
29343
|
+
const P = localPalette(attributes);
|
|
29344
|
+
group.appendChild(new Circle({ style: { cx: 0, cy: 0, r: 4.6, fill: P.dotFill, stroke: P.cyan, lineWidth: 1.6 } }));
|
|
29345
|
+
group.appendChild(new Circle({ style: { cx: 0, cy: 0, r: 1.5, fill: P.cyan, stroke: "none" } }));
|
|
28277
29346
|
}
|
|
28278
29347
|
function drawJunction(attributes, group) {
|
|
28279
|
-
const
|
|
28280
|
-
|
|
28281
|
-
group.appendChild(new Line({ style: { x1:
|
|
28282
|
-
group.appendChild(new Line({ style: { x1:
|
|
28283
|
-
group.appendChild(new Line({ style: { x1: 3.
|
|
29348
|
+
const P = localPalette(attributes);
|
|
29349
|
+
const lw = LW;
|
|
29350
|
+
group.appendChild(new Line({ style: { x1: -5.5, y1: 0, x2: 5.5, y2: 0, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
29351
|
+
group.appendChild(new Line({ style: { x1: 0, y1: -5.5, x2: 0, y2: 5.5, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
29352
|
+
group.appendChild(new Line({ style: { x1: -3.9, y1: -3.9, x2: 3.9, y2: 3.9, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
29353
|
+
group.appendChild(new Line({ style: { x1: 3.9, y1: -3.9, x2: -3.9, y2: 3.9, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
|
|
28284
29354
|
}
|
|
28285
29355
|
function drawUnknown(attributes, group) {
|
|
28286
|
-
group.appendChild(new Circle({ style: { cx: 0, cy: 0, r:
|
|
29356
|
+
group.appendChild(new Circle({ style: { cx: 0, cy: 0, r: 9, fill: C2.unknownFill, stroke: C2.unknownStroke, lineWidth: 1.4 } }));
|
|
29357
|
+
text(attributes, group, "?", 0, 0.8, 13, C2.unknownStroke, 700);
|
|
28287
29358
|
}
|
|
28288
29359
|
function drawStation(attributes, group) {
|
|
28289
29360
|
const [w, h] = attributes.size;
|
|
@@ -28294,16 +29365,30 @@ function drawStation(attributes, group) {
|
|
|
28294
29365
|
y: -h / 2,
|
|
28295
29366
|
width: w,
|
|
28296
29367
|
height: h,
|
|
28297
|
-
radius:
|
|
29368
|
+
radius: 5,
|
|
28298
29369
|
fill: C2.stationFill,
|
|
28299
29370
|
stroke: C2.white,
|
|
28300
|
-
lineWidth: 1.
|
|
29371
|
+
lineWidth: 1.5,
|
|
29372
|
+
opacity: 0.95
|
|
29373
|
+
}
|
|
29374
|
+
})
|
|
29375
|
+
);
|
|
29376
|
+
group.appendChild(
|
|
29377
|
+
new Rect({
|
|
29378
|
+
style: {
|
|
29379
|
+
x: -w / 2 + 2,
|
|
29380
|
+
y: -h / 2 + 2,
|
|
29381
|
+
width: w - 4,
|
|
29382
|
+
height: 2.4,
|
|
29383
|
+
radius: 1.2,
|
|
29384
|
+
fill: withAlpha(C2.white, 0.28),
|
|
29385
|
+
stroke: "none",
|
|
28301
29386
|
opacity: 0.9
|
|
28302
29387
|
}
|
|
28303
29388
|
})
|
|
28304
29389
|
);
|
|
28305
29390
|
if (attributes._stationTitle) {
|
|
28306
|
-
_drawWrappedText(group, attributes._stationTitle, 0, -h / 2 +
|
|
29391
|
+
_drawWrappedText(group, attributes._stationTitle, 0, -h / 2 + 15, 12, C2.stationText, w - 12, 1.35, 600);
|
|
28307
29392
|
}
|
|
28308
29393
|
}
|
|
28309
29394
|
const DRAWERS = {
|
|
@@ -28320,8 +29405,36 @@ const DRAWERS = {
|
|
|
28320
29405
|
station: drawStation,
|
|
28321
29406
|
other: drawUnknown
|
|
28322
29407
|
};
|
|
28323
|
-
const LABEL_UNDER = /* @__PURE__ */ new Set(["transformer", "fuse"]);
|
|
28324
29408
|
const _pulseState = /* @__PURE__ */ new Map();
|
|
29409
|
+
function drawGlow(attributes, group) {
|
|
29410
|
+
const color2 = attributes._glowColor;
|
|
29411
|
+
if (!color2) return;
|
|
29412
|
+
const t = attributes._glowT == null ? 0 : attributes._glowT;
|
|
29413
|
+
const [w, h] = attributes.size || [40, 40];
|
|
29414
|
+
const baseR = Math.max(w, h) / 2 + 3;
|
|
29415
|
+
const spread = attributes._glowSpread || 14;
|
|
29416
|
+
const maxOp = attributes._glowOpacity == null ? 0.9 : attributes._glowOpacity;
|
|
29417
|
+
const ease2 = 1 - (1 - t) * (1 - t);
|
|
29418
|
+
const alpha = maxOp * Math.pow(1 - t, 1.4);
|
|
29419
|
+
if (alpha <= 0.02) return;
|
|
29420
|
+
const r = baseR + spread * ease2;
|
|
29421
|
+
const lw = Math.max(1.2, 4.4 * (1 - t) + 0.8);
|
|
29422
|
+
group.appendChild(new Circle({
|
|
29423
|
+
style: { cx: 0, cy: 0, r, stroke: color2, lineWidth: lw, strokeOpacity: alpha, fill: "none", lineCap: "round" }
|
|
29424
|
+
}));
|
|
29425
|
+
group.appendChild(new Circle({
|
|
29426
|
+
style: {
|
|
29427
|
+
cx: 0,
|
|
29428
|
+
cy: 0,
|
|
29429
|
+
r: r + spread * 0.6,
|
|
29430
|
+
stroke: color2,
|
|
29431
|
+
lineWidth: Math.max(0.8, lw * 0.55),
|
|
29432
|
+
strokeOpacity: alpha * 0.35,
|
|
29433
|
+
fill: "none",
|
|
29434
|
+
lineCap: "round"
|
|
29435
|
+
}
|
|
29436
|
+
}));
|
|
29437
|
+
}
|
|
28325
29438
|
class SvgSymbolNode extends BaseNode {
|
|
28326
29439
|
drawKeyShape(attributes, container) {
|
|
28327
29440
|
const [w, h] = attributes.size;
|
|
@@ -28352,9 +29465,12 @@ class SvgSymbolNode extends BaseNode {
|
|
|
28352
29465
|
if (rot) key.appendChild(target);
|
|
28353
29466
|
const draw = DRAWERS[attributes.cat] || drawUnknown;
|
|
28354
29467
|
draw(attributes, target);
|
|
29468
|
+
drawGlow(attributes, target);
|
|
28355
29469
|
drawHighlight(attributes, target);
|
|
28356
29470
|
drawAnnotation(attributes, target);
|
|
28357
|
-
if (
|
|
29471
|
+
if (attributes.labelText && !attributes._stationTitle && attributes.cat !== "bus" && attributes.cat !== "cableTerminal" && attributes.cat !== "consumer" && attributes.cat !== "switch" && attributes.cat !== "meterBox") {
|
|
29472
|
+
drawLabelUnder(attributes, target);
|
|
29473
|
+
}
|
|
28358
29474
|
const pulse = _pulseState.get(attributes._nodeId || "");
|
|
28359
29475
|
if (pulse) {
|
|
28360
29476
|
const items = rot ? [target] : key.children || [];
|
|
@@ -28541,6 +29657,32 @@ const _sfc_main$2 = {
|
|
|
28541
29657
|
emit("node-select", { node, upstream, downstream });
|
|
28542
29658
|
if (api) api._emit("node:click", { nodeId: id2, node });
|
|
28543
29659
|
}
|
|
29660
|
+
function selectNodeRef(ref2) {
|
|
29661
|
+
if (!api || !graph || ref2 == null) return;
|
|
29662
|
+
const t = api._resolveTarget(ref2);
|
|
29663
|
+
if (!t) {
|
|
29664
|
+
selectNodeWithInfo(String(ref2));
|
|
29665
|
+
return;
|
|
29666
|
+
}
|
|
29667
|
+
if (t.kind === "customer") {
|
|
29668
|
+
selectCustomerWithInfo(t.boxId, t.index);
|
|
29669
|
+
} else {
|
|
29670
|
+
selectNodeWithInfo(t.id);
|
|
29671
|
+
}
|
|
29672
|
+
}
|
|
29673
|
+
function selectCustomerRef(nodeRef, index) {
|
|
29674
|
+
if (!api || !graph || nodeRef == null) return;
|
|
29675
|
+
const t = api._resolveTarget(nodeRef);
|
|
29676
|
+
if (!t) {
|
|
29677
|
+
selectNodeWithInfo(String(nodeRef));
|
|
29678
|
+
return;
|
|
29679
|
+
}
|
|
29680
|
+
if (t.kind === "customer") {
|
|
29681
|
+
selectCustomerWithInfo(t.boxId, t.index);
|
|
29682
|
+
return;
|
|
29683
|
+
}
|
|
29684
|
+
selectCustomerWithInfo(t.id, index);
|
|
29685
|
+
}
|
|
28544
29686
|
function routeNodeTap(id2) {
|
|
28545
29687
|
if (!graph || id2 == null) return;
|
|
28546
29688
|
const customers = customersOf(id2);
|
|
@@ -28853,9 +29995,9 @@ const _sfc_main$2 = {
|
|
|
28853
29995
|
}, {}),
|
|
28854
29996
|
// 保留原有快捷方法
|
|
28855
29997
|
resetView,
|
|
28856
|
-
selectNode:
|
|
28857
|
-
/** 选中计量箱内第 index
|
|
28858
|
-
selectCustomer:
|
|
29998
|
+
selectNode: selectNodeRef,
|
|
29999
|
+
/** 选中计量箱内第 index 个用户(nodeRef=箱节点id/psrId;传 assetNo 时自动定位该户) */
|
|
30000
|
+
selectCustomer: selectCustomerRef,
|
|
28859
30001
|
/** 当前选中的“箱内用户”信息({ nodeId, index, customer, node } | null) */
|
|
28860
30002
|
getSelectedCustomer,
|
|
28861
30003
|
exportPng,
|
|
@@ -28894,7 +30036,7 @@ const _sfc_main$2 = {
|
|
|
28894
30036
|
};
|
|
28895
30037
|
}
|
|
28896
30038
|
};
|
|
28897
|
-
const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-
|
|
30039
|
+
const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-d3774ed5"]]);
|
|
28898
30040
|
const _hoisted_1$1 = ["data-theme"];
|
|
28899
30041
|
const _hoisted_2$1 = { class: "panel-head" };
|
|
28900
30042
|
const _hoisted_3$1 = {
|
|
@@ -29267,7 +30409,7 @@ const _sfc_main = {
|
|
|
29267
30409
|
}
|
|
29268
30410
|
};
|
|
29269
30411
|
const DistanceTop10 = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-438b6017"]]);
|
|
29270
|
-
const VERSION = "0.1.
|
|
30412
|
+
const VERSION = "0.1.9";
|
|
29271
30413
|
const DESCRIPTION = "配电台区单线图拓扑成图引擎";
|
|
29272
30414
|
export {
|
|
29273
30415
|
DESCRIPTION,
|