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