topo-engine 0.1.4 → 0.1.6
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 +43 -1
- package/dist/index.d.ts +5 -0
- package/dist/style.css +11 -11
- package/dist/topo-engine.cjs +189 -18
- package/dist/topo-engine.cjs.map +1 -1
- package/dist/topo-engine.js +189 -18
- package/dist/topo-engine.js.map +1 -1
- package/dist/topo-engine.umd.cjs +189 -18
- package/dist/topo-engine.umd.cjs.map +1 -1
- package/package.json +1 -1
package/dist/topo-engine.js
CHANGED
|
@@ -52,6 +52,13 @@ class TopoApi {
|
|
|
52
52
|
_assertReady() {
|
|
53
53
|
if (!this._graph) throw new Error("[TopoApi] 图实例未就绪,请等待渲染完成");
|
|
54
54
|
}
|
|
55
|
+
/** 画布中心兼容取值:G6 v5 getCanvasCenter 返回数组 [x,y,z],历史代码按 {x,y} 对象使用 */
|
|
56
|
+
_canvasCenterXY() {
|
|
57
|
+
const c = this._graph.getCanvasCenter();
|
|
58
|
+
if (Array.isArray(c)) return { x: c[0], y: c[1] };
|
|
59
|
+
if (c && typeof c === "object") return { x: c.x || 0, y: c.y || 0 };
|
|
60
|
+
return { x: 0, y: 0 };
|
|
61
|
+
}
|
|
55
62
|
/** 安全合并 style:保留现有字段,只覆盖传入字段 */
|
|
56
63
|
_safeNodeStyle(nodeId, style) {
|
|
57
64
|
try {
|
|
@@ -371,7 +378,7 @@ class TopoApi {
|
|
|
371
378
|
try {
|
|
372
379
|
this._graph.focusElement(nodeId);
|
|
373
380
|
} catch {
|
|
374
|
-
const center = this.
|
|
381
|
+
const center = this._canvasCenterXY();
|
|
375
382
|
this._graph.translateTo({ x: center.x - p.x * (zoom || this._graph.getZoom()), y: center.y - p.y * (zoom || this._graph.getZoom()) });
|
|
376
383
|
}
|
|
377
384
|
}
|
|
@@ -379,7 +386,7 @@ class TopoApi {
|
|
|
379
386
|
getViewport() {
|
|
380
387
|
this._assertReady();
|
|
381
388
|
const zoom = this._graph.getZoom();
|
|
382
|
-
const center = this.
|
|
389
|
+
const center = this._canvasCenterXY();
|
|
383
390
|
return { zoom, center };
|
|
384
391
|
}
|
|
385
392
|
// ============================================================
|
|
@@ -752,11 +759,25 @@ class TopoApi {
|
|
|
752
759
|
if (!container) return;
|
|
753
760
|
const pos = this._layout.pos.get(nodeId);
|
|
754
761
|
if (!pos) return;
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
762
|
+
let client = null;
|
|
763
|
+
try {
|
|
764
|
+
if (typeof this._graph.getClientByCanvas === "function") {
|
|
765
|
+
const r = this._graph.getClientByCanvas([pos.x, pos.y]);
|
|
766
|
+
client = Array.isArray(r) ? { x: r[0], y: r[1] } : r;
|
|
767
|
+
}
|
|
768
|
+
} catch (e2) {
|
|
769
|
+
}
|
|
770
|
+
let screenX, screenY;
|
|
771
|
+
if (client && isFinite(client.x)) {
|
|
772
|
+
screenX = client.x;
|
|
773
|
+
screenY = client.y;
|
|
774
|
+
} else {
|
|
775
|
+
const zoom = this._graph.getZoom();
|
|
776
|
+
const canvasCenter = this._canvasCenterXY();
|
|
777
|
+
const containerRect = container.getBoundingClientRect();
|
|
778
|
+
screenX = containerRect.left + canvasCenter.x + pos.x * zoom;
|
|
779
|
+
screenY = containerRect.top + canvasCenter.y + pos.y * zoom;
|
|
780
|
+
}
|
|
760
781
|
const el = document.createElement("div");
|
|
761
782
|
el.className = "topo-card";
|
|
762
783
|
el.dataset.nodeId = nodeId;
|
|
@@ -1214,6 +1235,53 @@ const SYMBOL_SPEC = {
|
|
|
1214
1235
|
function getSymbolSpec(cat) {
|
|
1215
1236
|
return SYMBOL_SPEC[cat] || SYMBOL_SPEC.other;
|
|
1216
1237
|
}
|
|
1238
|
+
function buildCustomerMap(customerData) {
|
|
1239
|
+
const map = /* @__PURE__ */ new Map();
|
|
1240
|
+
if (!Array.isArray(customerData)) return map;
|
|
1241
|
+
for (const entry of customerData) {
|
|
1242
|
+
if (entry && entry.psrId && Array.isArray(entry.consumerList)) {
|
|
1243
|
+
map.set(String(entry.psrId), entry.consumerList);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return map;
|
|
1247
|
+
}
|
|
1248
|
+
const METER_CELL_W = 26;
|
|
1249
|
+
const METER_CELL_H = 38;
|
|
1250
|
+
const METER_GAP = 4;
|
|
1251
|
+
const METER_PAD = 6;
|
|
1252
|
+
const MAX_COLS = 5;
|
|
1253
|
+
const SHOW_NAME_LIMIT = 20;
|
|
1254
|
+
function injectCustomers(model, customerMap) {
|
|
1255
|
+
for (const node of model.nodes) {
|
|
1256
|
+
if (node.cat !== "meterBox") continue;
|
|
1257
|
+
const customers = customerMap.get(node.psrId);
|
|
1258
|
+
if (!customers || customers.length === 0) continue;
|
|
1259
|
+
node._customers = customers;
|
|
1260
|
+
if (customers.length === 1) {
|
|
1261
|
+
node._meterBoxMode = "single";
|
|
1262
|
+
node._meterBoxW = 30;
|
|
1263
|
+
node._meterBoxH = 38;
|
|
1264
|
+
} else {
|
|
1265
|
+
const showName = customers.length <= SHOW_NAME_LIMIT;
|
|
1266
|
+
const cols = Math.min(customers.length, MAX_COLS);
|
|
1267
|
+
const rows = Math.ceil(customers.length / cols);
|
|
1268
|
+
const cellW = METER_CELL_W;
|
|
1269
|
+
const cellH = showName ? METER_CELL_H : METER_CELL_W;
|
|
1270
|
+
node._meterBoxMode = "grid";
|
|
1271
|
+
node._meterBoxCols = cols;
|
|
1272
|
+
node._meterBoxRows = rows;
|
|
1273
|
+
node._meterBoxShowName = showName;
|
|
1274
|
+
node._meterBoxW = METER_PAD * 2 + cols * cellW + (cols - 1) * METER_GAP;
|
|
1275
|
+
node._meterBoxH = METER_PAD * 2 + rows * cellH + (rows - 1) * METER_GAP;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
function getMeterBoxSize(node) {
|
|
1280
|
+
if (node._meterBoxW != null && node._meterBoxH != null) {
|
|
1281
|
+
return { w: node._meterBoxW, h: node._meterBoxH };
|
|
1282
|
+
}
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1217
1285
|
function stationPlans(model) {
|
|
1218
1286
|
const nodesById = new Map(model.nodes.map((n) => [n.id, n]));
|
|
1219
1287
|
const plans = [];
|
|
@@ -1265,6 +1333,10 @@ function stationInner(plan, model, band = 50, nodeMap) {
|
|
|
1265
1333
|
const nmap = nodeMap || new Map(model.nodes.map((n) => [n.id, n]));
|
|
1266
1334
|
const specOf = (id2) => {
|
|
1267
1335
|
const n = nmap.get(id2);
|
|
1336
|
+
if (n && n.cat === "meterBox") {
|
|
1337
|
+
const dim = getMeterBoxSize(n);
|
|
1338
|
+
if (dim) return dim;
|
|
1339
|
+
}
|
|
1268
1340
|
const s2 = getSymbolSpec(n ? n.cat : "other");
|
|
1269
1341
|
return { w: s2.w, h: s2.h };
|
|
1270
1342
|
};
|
|
@@ -1922,6 +1994,10 @@ function computeOrthogonalLayout(model, opts = {}) {
|
|
|
1922
1994
|
const sz = (id2) => {
|
|
1923
1995
|
const n = extNodeMap.get(id2);
|
|
1924
1996
|
if (n && n._size) return n._size;
|
|
1997
|
+
if (n && n.cat === "meterBox") {
|
|
1998
|
+
const dim = getMeterBoxSize(n);
|
|
1999
|
+
if (dim) return dim;
|
|
2000
|
+
}
|
|
1925
2001
|
const spec = getSymbolSpec(n ? n.cat : "other");
|
|
1926
2002
|
return { w: spec.w, h: spec.h };
|
|
1927
2003
|
};
|
|
@@ -2520,22 +2596,30 @@ function buildGraphData(model, layout) {
|
|
|
2520
2596
|
const nodes = model.nodes.map((n) => {
|
|
2521
2597
|
const spec = getSymbolSpec(n.cat);
|
|
2522
2598
|
const busW = layout.stationBusWidth && layout.stationBusWidth.get(n.id);
|
|
2523
|
-
const
|
|
2599
|
+
const meterBoxDim = n.cat === "meterBox" ? getMeterBoxSize(n) : null;
|
|
2600
|
+
const w = busW || (meterBoxDim ? meterBoxDim.w : spec.w);
|
|
2601
|
+
const h = meterBoxDim ? meterBoxDim.h : spec.h;
|
|
2524
2602
|
const p = layout.pos.get(n.id) || { x: 0, y: 0 };
|
|
2525
2603
|
return {
|
|
2526
2604
|
id: n.id,
|
|
2527
|
-
data: { ...n, width: w, height:
|
|
2605
|
+
data: { ...n, width: w, height: h },
|
|
2528
2606
|
style: {
|
|
2529
2607
|
x: p.x,
|
|
2530
2608
|
y: p.y,
|
|
2531
|
-
size: [w,
|
|
2609
|
+
size: [w, h],
|
|
2532
2610
|
// 自定义字段(进 attributes,供 svg-symbol 节点绘制使用)
|
|
2533
2611
|
cat: n.cat,
|
|
2534
|
-
labelText: n.cat === "bus" || n.cat === "cableTerminal" ? "" : n.shortName || "",
|
|
2612
|
+
labelText: n.cat === "bus" || n.cat === "cableTerminal" || n.cat === "consumer" ? "" : n.shortName || "",
|
|
2535
2613
|
rotate: layout.stationRotate && layout.stationRotate.get(n.id) || 0,
|
|
2536
2614
|
highlight: 0,
|
|
2537
2615
|
cursor: "pointer",
|
|
2538
|
-
_nodeId: n.id
|
|
2616
|
+
_nodeId: n.id,
|
|
2617
|
+
// 计量箱客户数据
|
|
2618
|
+
_meterBoxMode: n._meterBoxMode || null,
|
|
2619
|
+
_meterBoxCols: n._meterBoxCols || 0,
|
|
2620
|
+
_meterBoxRows: n._meterBoxRows || 0,
|
|
2621
|
+
_meterBoxShowName: n._meterBoxShowName || false,
|
|
2622
|
+
_customers: n._customers || null
|
|
2539
2623
|
}
|
|
2540
2624
|
};
|
|
2541
2625
|
});
|
|
@@ -27787,8 +27871,45 @@ function drawStationTerminal(attributes, group) {
|
|
|
27787
27871
|
group.appendChild(new Polygon({ style: { points: [[0, -5], [0, 5], [9, 0]], fill: "none", stroke: C2.red, lineWidth: 1.4 } }));
|
|
27788
27872
|
}
|
|
27789
27873
|
function drawMeterBox(attributes, group) {
|
|
27790
|
-
|
|
27791
|
-
|
|
27874
|
+
const [w, h] = attributes.size;
|
|
27875
|
+
if (attributes._meterBoxMode === "single") {
|
|
27876
|
+
group.appendChild(new Circle({ style: { cx: 0, cy: -3, r: 11, fill: "none", stroke: C2.white, lineWidth: 1.6 } }));
|
|
27877
|
+
group.appendChild(new Line({ style: { x1: 0, y1: -14, x2: 0, y2: -7, stroke: C2.white, lineWidth: 1.6 } }));
|
|
27878
|
+
const name = attributes._customers && attributes._customers[0] ? attributes._customers[0].realConsName || attributes._customers[0].consName || "" : "";
|
|
27879
|
+
if (name) {
|
|
27880
|
+
group.appendChild(new Text({ style: { text: name, x: 0, y: 12, fontSize: 8, fill: C2.white, fontFamily: FONT, textAlign: "center", textBaseline: "middle" } }));
|
|
27881
|
+
}
|
|
27882
|
+
} else if (attributes._meterBoxMode === "grid" && attributes._customers) {
|
|
27883
|
+
group.appendChild(new Rect({ style: { x: -w / 2, y: -h / 2, width: w, height: h, fill: C2.meterFill, stroke: C2.meterText, lineWidth: 0.9, radius: 2 } }));
|
|
27884
|
+
const cols = attributes._meterBoxCols || 5;
|
|
27885
|
+
const customers = attributes._customers;
|
|
27886
|
+
const showName = attributes._meterBoxShowName;
|
|
27887
|
+
const cellW = METER_CELL_W;
|
|
27888
|
+
const cellH = showName ? METER_CELL_H : METER_CELL_W;
|
|
27889
|
+
const gridW = cols * cellW + (cols - 1) * METER_GAP;
|
|
27890
|
+
const rows = attributes._meterBoxRows || Math.ceil(customers.length / cols);
|
|
27891
|
+
const gridH = rows * cellH + (rows - 1) * METER_GAP;
|
|
27892
|
+
const startX = -gridW / 2;
|
|
27893
|
+
const startY = -gridH / 2;
|
|
27894
|
+
const meterR = 9;
|
|
27895
|
+
for (let i = 0; i < customers.length; i++) {
|
|
27896
|
+
const row2 = Math.floor(i / cols);
|
|
27897
|
+
const col = i % cols;
|
|
27898
|
+
const cx = startX + col * (cellW + METER_GAP) + cellW / 2;
|
|
27899
|
+
const cy = startY + row2 * (cellH + METER_GAP) + cellW / 2;
|
|
27900
|
+
group.appendChild(new Circle({ style: { cx, cy, r: meterR, fill: "none", stroke: C2.meterText, lineWidth: 0.8 } }));
|
|
27901
|
+
group.appendChild(new Line({ style: { x1: cx, y1: cy - meterR, x2: cx, y2: cy - meterR + 4, stroke: C2.meterText, lineWidth: 0.8 } }));
|
|
27902
|
+
if (showName) {
|
|
27903
|
+
const name = customers[i].realConsName || customers[i].consName || "";
|
|
27904
|
+
if (name) {
|
|
27905
|
+
group.appendChild(new Text({ style: { text: name, x: cx, y: cy + meterR + 6, fontSize: 7, fill: C2.meterText, fontFamily: FONT, textAlign: "center", textBaseline: "middle" } }));
|
|
27906
|
+
}
|
|
27907
|
+
}
|
|
27908
|
+
}
|
|
27909
|
+
} else {
|
|
27910
|
+
group.appendChild(new Rect({ style: { x: -9.5, y: -9.5, width: 19, height: 19, fill: C2.meterFill, stroke: C2.meterText, lineWidth: 0.9 } }));
|
|
27911
|
+
text(attributes, group, "JX", 0, 0.5, 7.5, C2.meterText);
|
|
27912
|
+
}
|
|
27792
27913
|
}
|
|
27793
27914
|
function drawConsumer(attributes, group) {
|
|
27794
27915
|
group.appendChild(new Circle({ style: { cx: 0, cy: 0, r: 12.5, fill: "none", stroke: C2.white, lineWidth: 1.8 } }));
|
|
@@ -27914,8 +28035,10 @@ const _sfc_main$2 = {
|
|
|
27914
28035
|
props: {
|
|
27915
28036
|
dataUrl: { type: String, default: "" },
|
|
27916
28037
|
data: { type: Object, default: null },
|
|
28038
|
+
/** 客户数据(计量箱关联的用户列表),直接传入 JSON 数组 */
|
|
28039
|
+
customerData: { type: Array, default: null },
|
|
27917
28040
|
/** 主题:'dark'(默认,黑底 SCADA 风)| 'light'(白底);支持 v-model:theme 双向绑定 */
|
|
27918
|
-
theme: { type: String, default: "
|
|
28041
|
+
theme: { type: String, default: "light" }
|
|
27919
28042
|
},
|
|
27920
28043
|
emits: ["node-select", "loaded", "top-distances", "update:theme"],
|
|
27921
28044
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
@@ -28031,6 +28154,10 @@ const _sfc_main$2 = {
|
|
|
28031
28154
|
lastRaw.value = raw;
|
|
28032
28155
|
const theme = applyTheme();
|
|
28033
28156
|
model = parseTopo(raw);
|
|
28157
|
+
if (props.customerData && props.customerData.length) {
|
|
28158
|
+
const customerMap = buildCustomerMap(props.customerData);
|
|
28159
|
+
injectCustomers(model, customerMap);
|
|
28160
|
+
}
|
|
28034
28161
|
nodeByIdMap = new Map(model.nodes.map((n) => [n.id, n]));
|
|
28035
28162
|
title.value = String(model.title || "").replace(/\.+$/, "");
|
|
28036
28163
|
stats.value = model.stats;
|
|
@@ -28082,6 +28209,34 @@ const _sfc_main$2 = {
|
|
|
28082
28209
|
const id2 = evt.target.id;
|
|
28083
28210
|
selectNodeWithInfo(id2);
|
|
28084
28211
|
});
|
|
28212
|
+
function hitTestNodeAtCanvas(evt) {
|
|
28213
|
+
const c = evt && (evt.canvas || evt.viewport) || {};
|
|
28214
|
+
const cx = c.x, cy = c.y;
|
|
28215
|
+
if (cx == null || cy == null) return null;
|
|
28216
|
+
let best = null, bestD = Infinity;
|
|
28217
|
+
let all = [];
|
|
28218
|
+
try {
|
|
28219
|
+
all = graph.getNodeData();
|
|
28220
|
+
} catch {
|
|
28221
|
+
return null;
|
|
28222
|
+
}
|
|
28223
|
+
for (const nd of all || []) {
|
|
28224
|
+
if (nd && nd.data && nd.data.isStationFrame) continue;
|
|
28225
|
+
const st = nd && nd.style;
|
|
28226
|
+
if (!st || st.x == null || st.y == null) continue;
|
|
28227
|
+
const size = st.size || [0, 0];
|
|
28228
|
+
const w = size[0] || 0, h = size[1] || 0;
|
|
28229
|
+
if (w <= 0 || h <= 0) continue;
|
|
28230
|
+
if (cx >= st.x - w / 2 && cx <= st.x + w / 2 && cy >= st.y - h / 2 && cy <= st.y + h / 2) {
|
|
28231
|
+
const d2 = Math.abs(cx - st.x) + Math.abs(cy - st.y);
|
|
28232
|
+
if (d2 < bestD) {
|
|
28233
|
+
bestD = d2;
|
|
28234
|
+
best = nd.id;
|
|
28235
|
+
}
|
|
28236
|
+
}
|
|
28237
|
+
}
|
|
28238
|
+
return best;
|
|
28239
|
+
}
|
|
28085
28240
|
graph.on("node:pointerenter", (evt) => {
|
|
28086
28241
|
const id2 = evt.target.id;
|
|
28087
28242
|
const data3 = graph.getNodeData(id2);
|
|
@@ -28104,6 +28259,11 @@ const _sfc_main$2 = {
|
|
|
28104
28259
|
api._emit("edge:click", { edgeId: id2, source: src, target: tgt });
|
|
28105
28260
|
});
|
|
28106
28261
|
graph.on("canvas:click", (evt) => {
|
|
28262
|
+
const hitId = hitTestNodeAtCanvas(evt);
|
|
28263
|
+
if (hitId) {
|
|
28264
|
+
selectNodeWithInfo(hitId);
|
|
28265
|
+
return;
|
|
28266
|
+
}
|
|
28107
28267
|
if (selectedId) {
|
|
28108
28268
|
const data3 = graph.getNodeData(selectedId);
|
|
28109
28269
|
const existing = data3 && data3.style || {};
|
|
@@ -28116,7 +28276,8 @@ const _sfc_main$2 = {
|
|
|
28116
28276
|
});
|
|
28117
28277
|
graph.on("viewportchange", (evt) => {
|
|
28118
28278
|
const zoom = graph.getZoom();
|
|
28119
|
-
const
|
|
28279
|
+
const raw2 = graph.getCanvasCenter();
|
|
28280
|
+
const center = Array.isArray(raw2) ? { x: raw2[0], y: raw2[1] } : raw2;
|
|
28120
28281
|
api._emit("viewport:change", { zoom, center });
|
|
28121
28282
|
});
|
|
28122
28283
|
graph.render().then(() => {
|
|
@@ -28162,6 +28323,13 @@ const _sfc_main$2 = {
|
|
|
28162
28323
|
if (lastRaw.value) render(lastRaw.value);
|
|
28163
28324
|
}
|
|
28164
28325
|
);
|
|
28326
|
+
watch(
|
|
28327
|
+
() => props.customerData,
|
|
28328
|
+
() => {
|
|
28329
|
+
if (lastRaw.value) render(lastRaw.value);
|
|
28330
|
+
},
|
|
28331
|
+
{ deep: true }
|
|
28332
|
+
);
|
|
28165
28333
|
onBeforeUnmount(() => {
|
|
28166
28334
|
if (api) {
|
|
28167
28335
|
api.destroy();
|
|
@@ -28221,7 +28389,7 @@ const _sfc_main$2 = {
|
|
|
28221
28389
|
};
|
|
28222
28390
|
}
|
|
28223
28391
|
};
|
|
28224
|
-
const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-
|
|
28392
|
+
const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-b02bff98"]]);
|
|
28225
28393
|
const _hoisted_1$1 = ["data-theme"];
|
|
28226
28394
|
const _hoisted_2$1 = { class: "panel-head" };
|
|
28227
28395
|
const _hoisted_3$1 = {
|
|
@@ -28531,7 +28699,7 @@ const _sfc_main = {
|
|
|
28531
28699
|
}
|
|
28532
28700
|
};
|
|
28533
28701
|
const DistanceTop10 = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-438b6017"]]);
|
|
28534
|
-
const VERSION = "0.1.
|
|
28702
|
+
const VERSION = "0.1.6";
|
|
28535
28703
|
const DESCRIPTION = "配电台区单线图拓扑成图引擎";
|
|
28536
28704
|
export {
|
|
28537
28705
|
DESCRIPTION,
|
|
@@ -28543,11 +28711,14 @@ export {
|
|
|
28543
28711
|
TopoGraph,
|
|
28544
28712
|
VERSION,
|
|
28545
28713
|
assertOrthogonal,
|
|
28714
|
+
buildCustomerMap,
|
|
28546
28715
|
buildGraphData,
|
|
28547
28716
|
calculateTopDistances,
|
|
28548
28717
|
computeOrthogonalLayout,
|
|
28549
28718
|
edgeStyle,
|
|
28719
|
+
getMeterBoxSize,
|
|
28550
28720
|
getSymbolSpec,
|
|
28721
|
+
injectCustomers,
|
|
28551
28722
|
parseTopo
|
|
28552
28723
|
};
|
|
28553
28724
|
//# sourceMappingURL=topo-engine.js.map
|