Skip to content

Commit 591ed33

Browse files
committed
[feature] Implement for graph, geoMap and indoorMap
1 parent 45f5006 commit 591ed33

File tree

8 files changed

+99
-83
lines changed

8 files changed

+99
-83
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,23 @@ NetJSON format used internally is based on [networkgraph](http://netjson.org/rfc
335335

336336
You can customize the style of GeoJSON features using `style` property. The list of all available properties can be found in the [Leaflet documentation](https://leafletjs.com/reference.html#geojson).
337337

338+
- `hashParams`
339+
340+
Configuration for adding hash parameters to the URL when a node is clicked.
341+
342+
```JS
343+
hashParams:{
344+
show: boolean,
345+
type: string
346+
}
347+
```
348+
349+
You can enable or disable adding hash parameters by setting show to true or false. When enabled, the following parameters are added to the URL:
350+
1. type – A prefix used to uniquely identify the map node.
351+
2. nodeId – The ID of the selected node.
352+
3. zoom – The current zoom level of the map.
353+
**Note: Zoom is only applied when type is set to `geoMap`**
354+
338355
- `onInit`
339356

340357
The callback function executed on initialization of `NetJSONGraph` instance.

public/example_templates/netjsongraph.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
// `graph` render defaultly.
6868
// We can add data gradually by array.
6969
const graph = new NetJSONGraph("../assets/data/netjsonmap.json", {
70+
hashParams: {
71+
show: true,
72+
type: "basicUsage",
73+
},
7074
linkCategories: [
7175
{
7276
name: "down",

public/example_templates/netjsonmap-indoormap.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
render: "map",
4848
crs: L.CRS.Simple,
4949
// set map initial state.
50+
hashParams: {
51+
show: true,
52+
type: "indoorMap"
53+
},
5054
mapOptions: {
5155
center: [48.577, 18.539],
5256
zoom: 0,

public/example_templates/netjsonmap.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@
7272
const map = new NetJSONGraph("../assets/data/netjsonmap.json", {
7373
render: "map",
7474
// set map initial state.
75+
hashParams: {
76+
show: true,
77+
type: "GeoMap"
78+
},
7579
mapOptions: {
7680
center: [46.86764405052012, 19.675998687744144],
7781
zoom: 5,

src/js/netjsongraph.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ const NetJSONGraphDefaultConfig = {
284284
},
285285
],
286286
linkCategories: [],
287+
hashParams: {
288+
show: false,
289+
type: null,
290+
},
287291

288292
/**
289293
* @function

src/js/netjsongraph.core.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ class NetJSONGraph {
6868
this.config.onRender.call(this);
6969
this.event.once("onReady", this.config.onReady.bind(this));
7070
this.event.once("onLoad", this.config.onLoad.bind(this));
71+
this.event.once("applyHashState", () => {
72+
this.utils.applyHashState.call(this, this);
73+
});
7174
this.utils.paginatedDataParse
7275
.call(this, JSONParam)
7376
.then((JSONData) => {
@@ -128,7 +131,9 @@ class NetJSONGraph {
128131

129132
if (resParam.length) {
130133
const renderArray = function _renderArray() {
131-
resParam.map((file) => this.utils.JSONDataUpdate.call(this, file, false));
134+
resParam.map((file) =>
135+
this.utils.JSONDataUpdate.call(this, file, false),
136+
);
132137
};
133138
this.JSONParam = [JSONParam];
134139
this.event.once("renderArray", renderArray.bind(this));

src/js/netjsongraph.render.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class NetJSONGraphRender {
9393
"click",
9494
(params) => {
9595
const clickElement = configs.onClickElement.bind(self);
96+
self.utils.setHashParams(self, params);
9697
if (params.componentSubType === "graph") {
9798
return clickElement(
9899
params.dataType === "edge" ? "link" : "node",
@@ -127,6 +128,7 @@ class NetJSONGraphRender {
127128
generateGraphOption(JSONData, self) {
128129
const categories = [];
129130
const configs = self.config;
131+
const params = self.utils.parseHashParams();
130132
const nodes = JSONData.nodes.map((node) => {
131133
const nodeResult = JSON.parse(JSON.stringify(node));
132134
const {nodeStyleConfig, nodeSizeConfig, nodeEmphasisConfig} =
@@ -142,6 +144,7 @@ class NetJSONGraphRender {
142144
// Preserve original NetJSON node for sidebar use
143145
/* eslint-disable no-underscore-dangle */
144146
nodeResult._source = JSON.parse(JSON.stringify(node));
147+
self.utils.getSelectedNodeFromHashParams(self, params, node);
145148

146149
return nodeResult;
147150
});
@@ -201,7 +204,7 @@ class NetJSONGraphRender {
201204
const flatNodes = JSONData.flatNodes || {};
202205
const linesData = [];
203206
let nodesData = [];
204-
207+
const hashparams = self.utils.parseHashParams();
205208
nodes.forEach((node) => {
206209
if (node.properties) {
207210
// Maintain flatNodes lookup regardless of whether the node is rendered as a marker
@@ -242,6 +245,7 @@ class NetJSONGraphRender {
242245
});
243246
}
244247
}
248+
self.utils.getSelectedNodeFromHashParams(self, hashparams, node);
245249
});
246250
links.forEach((link) => {
247251
if (!flatNodes[link.source]) {
@@ -370,12 +374,11 @@ class NetJSONGraphRender {
370374
*/
371375
graphRender(JSONData, self) {
372376
self.utils.echartsSetOption(self.utils.generateGraphOption(JSONData, self), self);
373-
374377
window.onresize = () => {
375378
self.echarts.resize();
376379
};
377-
378380
self.event.emit("onLoad");
381+
self.event.emit("applyHashState");
379382
self.event.emit("onReady");
380383
self.event.emit("renderArray");
381384
}
@@ -652,8 +655,8 @@ class NetJSONGraphRender {
652655
}
653656
});
654657
}
655-
656658
self.event.emit("onLoad");
659+
self.event.emit("applyHashState");
657660
self.event.emit("onReady");
658661
self.event.emit("renderArray");
659662
}

src/js/netjsongraph.util.js

Lines changed: 53 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,92 +1165,67 @@ class NetJSONGraphUtil {
11651165
};
11661166
}
11671167

1168-
parseUrlBounds() {
1169-
const params = new URLSearchParams(window.location.search);
1170-
const getFloat = (k) => {
1171-
const v = params.get(k);
1172-
return v == null ? null : parseFloat(v);
1173-
};
1174-
const swLat = getFloat('swLat');
1175-
const swLng = getFloat('swLng');
1176-
const neLat = getFloat('neLat');
1177-
const neLng = getFloat('neLng');
1178-
return {
1179-
swLat, swLng, neLat, neLng,
1180-
hasBounds:
1181-
swLat !== null &&
1182-
swLng !== null &&
1183-
neLat !== null &&
1184-
neLng !== null,
1185-
};
1168+
/**
1169+
* Current implementation of hash params covers three scenarios:
1170+
* 1. For graph visualizations (e.g. network topology), triggers only a click event on the node.
1171+
* 2. For geographic maps, first stores the clicked node separately in a diffrent key selectedNode
1172+
* to avoid extra traversal, then sets the map view to the node's [lat, lng] as center and that zoom level.
1173+
* 3. For indoor maps, only triggers the click event without altering the map view. Any zoom applied via
1174+
* parameters is overridden because setting the image overlay redefines the map’s center and zoom level.
1175+
*/
1176+
parseHashParams() {
1177+
const raw = window.location.hash.replace(/^#/, "");
1178+
return new URLSearchParams(raw);
11861179
}
11871180

1188-
writeBoundsToUrl(self, { precision = 6 } = {}) {
1189-
if (!self || !self.leaflet) return;
1190-
try {
1191-
const b = self.leaflet.getBounds();
1192-
if (!b || !b.isValid()) return;
1193-
1194-
const sw = b.getSouthWest();
1195-
const ne = b.getNorthEast();
1196-
const url = new URL(window.location.href);
1197-
const params = url.searchParams;
1198-
1199-
params.set('swLat', (+sw.lat).toFixed(precision));
1200-
params.set('swLng', (+sw.lng).toFixed(precision));
1201-
params.set('neLat', (+ne.lat).toFixed(precision));
1202-
params.set('neLng', (+ne.lng).toFixed(precision));
1203-
1204-
url.search = params.toString();
1205-
window.history.replaceState({}, '', url.toString());
1206-
} catch (err) {
1207-
console.warn('writeBoundsToUrl error', err);
1181+
setHashParams(self, params) {
1182+
if (!self.config.hashParams.show) return;
1183+
const {type} = self.config.hashParams;
1184+
let nodeId;
1185+
let zoom = null;
1186+
if (params.componentSubType === "graph") {
1187+
nodeId = params.data.id;
1188+
}
1189+
// scatter, effectScatter signifies the instance of leaflet map
1190+
// graph signifies the instance of echarts graph
1191+
if (["scatter", "effectScatter"].includes(params.componentSubType)) {
1192+
nodeId = params.data.node.id;
1193+
zoom = self.leaflet.getZoom();
12081194
}
1195+
const hashParams = new URLSearchParams();
1196+
hashParams.set("type", type);
1197+
hashParams.set("nodeId", nodeId);
1198+
// Only adding zoom for geoMap as graph does not have much zoom levels and for indoor
1199+
// map zoom is redefined after after adding the image overlay in indoor map
1200+
if (zoom != null && type === "geoMap") hashParams.set("zoom", zoom);
1201+
window.location.hash = hashParams.toString();
12091202
}
12101203

1211-
restoreBoundsFromUrl(self) {
1212-
if (!self || !self.leaflet) return;
1213-
try {
1214-
const b = this.parseUrlBounds();
1215-
if (!b.hasBounds) return;
1216-
this._suspendUrlWrites = true;
1217-
self.leaflet.whenReady(() => {
1218-
if (typeof self.leaflet.invalidateSize === 'function') {
1219-
self.leaflet.invalidateSize();
1220-
}
1221-
self.leaflet.fitBounds(
1222-
L.latLngBounds([b.swLat, b.swLng], [b.neLat, b.neLng]),
1223-
{ animate: false },
1224-
);
1225-
setTimeout(() => {
1226-
this._suspendUrlWrites = false;
1227-
}, 50);
1228-
});
1229-
} catch (e) {
1230-
console.warn('restoreBoundsFromUrl failed', e);
1204+
// Getting the node from params and saving it as diffrent key so that it
1205+
// can be retrived afterwadrs without looping over the data.
1206+
getSelectedNodeFromHashParams(self, params, node) {
1207+
if (!self.config.hashParams.show) return;
1208+
const nodeId = params.get("nodeId");
1209+
const type = params.get("type");
1210+
const zoom = params.get("zoom");
1211+
if (nodeId === node.id) {
1212+
self.data.selectedNode = node;
1213+
self.data.selectedNode.type = type;
1214+
if (zoom != null) self.data.selectedNode.zoom = Number(zoom);
12311215
}
12321216
}
12331217

1234-
enableBoundsUrlSync(self, { debounceMs = 300, precision = 6 } = {}) {
1235-
if (!self || !self.leaflet) return () => {};
1236-
let timer = null;
1237-
const write = () => {
1238-
if (this._suspendUrlWrites) return;
1239-
this.writeBoundsToUrl(self, { precision });
1240-
};
1241-
const debounced = () => {
1242-
clearTimeout(timer);
1243-
timer = setTimeout(write, debounceMs);
1244-
};
1245-
1246-
self.leaflet.on('moveend', debounced);
1247-
self.leaflet.on('zoomend', debounced);
1248-
1249-
return () => {
1250-
self.leaflet.off('moveend', debounced);
1251-
self.leaflet.off('zoomend', debounced);
1252-
clearTimeout(timer);
1253-
};
1218+
applyHashState(self) {
1219+
console.log(self);
1220+
if (!self.config.hashParams.show) return;
1221+
const node = self.data.selectedNode;
1222+
if (!node) return;
1223+
const nodeType = self.config.graphConfig.series.type || self.config.mapOptions.nodeConfig.type;
1224+
const {location, zoom} = node;
1225+
if (["scatter", "effectScatter"].includes(nodeType) && zoom != null) {
1226+
self.leaflet.setView([location.lat, location.lng], zoom);
1227+
}
1228+
self.config.onClickElement.call(self, "node", node);
12541229
}
12551230
}
12561231

0 commit comments

Comments
 (0)