Skip to content

Commit eca31fd

Browse files
committed
[fix] Add comments and minor refactoring
1 parent eaa1179 commit eca31fd

File tree

6 files changed

+99
-38
lines changed

6 files changed

+99
-38
lines changed

public/example_templates/netjsonmap-indoormap-overlay.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@
6363
height: 100%;
6464
flex: 1;
6565
}
66+
#indoormap-container .njg-container .sideBarHandle {
67+
top: 35px;
68+
left: 390px
69+
}
70+
#indoormap-container .njg-container .hidden .sideBarHandle {
71+
left: 35px;
72+
}
6673
</style>
6774
</head>
6875
<body>

src/js/netjsongraph.core.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ class NetJSONGraph {
1717
// if explicitly set it to somthing like L.CRS.Simple.
1818
this.config.crs = NetJSONGraphDefaultConfig.crs;
1919
this.JSONParam = this.utils.isArray(JSONParam) ? JSONParam : [JSONParam];
20-
this.utils.setupHashChangeHandler(this);
2120
}
2221

2322
/**

src/js/netjsongraph.render.js

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class NetJSONGraphRender {
9393
"click",
9494
(params) => {
9595
const clickElement = configs.onClickElement.bind(self);
96-
self.utils.setUrlFragments(self, params);
96+
self.utils.addActionToUrl(self, params);
9797
if (params.componentSubType === "graph") {
9898
return clickElement(
9999
params.dataType === "edge" ? "link" : "node",
@@ -128,6 +128,11 @@ class NetJSONGraphRender {
128128
generateGraphOption(JSONData, self) {
129129
const categories = [];
130130
const configs = self.config;
131+
// The parseUrlFragments function extracts node IDs from the URL.
132+
// We then use these IDs to proactively cache the corresponding nodes
133+
// in `this.indexedNode`. This avoids a second data traversal
134+
// for subsequent lookups and is done here while we are already
135+
// iterating over the nodes to set their ECharts properties.
131136
const fragments = self.utils.parseUrlFragments();
132137
const nodes = JSONData.nodes.map((node) => {
133138
const nodeResult = JSON.parse(JSON.stringify(node));
@@ -245,6 +250,7 @@ class NetJSONGraphRender {
245250
const linesData = [];
246251
let nodesData = [];
247252
const fragments = self.utils.parseUrlFragments();
253+
248254
nodes.forEach((node) => {
249255
if (node.properties) {
250256
// Maintain flatNodes lookup regardless of whether the node is rendered as a marker
@@ -448,6 +454,8 @@ class NetJSONGraphRender {
448454
});
449455
}
450456

457+
self.utils.setupHashChangeHandler(self);
458+
451459
self.event.emit("onLoad");
452460
self.event.emit("onReady");
453461
self.event.emit("renderArray");
@@ -490,22 +498,6 @@ class NetJSONGraphRender {
490498
// eslint-disable-next-line no-underscore-dangle
491499
self.leaflet._zoomAnimated = false;
492500

493-
try {
494-
if (self.utils && typeof self.utils.restoreBoundsFromUrl === "function") {
495-
self.utils.restoreBoundsFromUrl(self);
496-
}
497-
if (self.utils && typeof self.utils.enableBoundsUrlSync === "function") {
498-
const debounceMs = 300;
499-
const precision = 6;
500-
self._destroyUrlSync = self.utils.enableBoundsUrlSync(self, {
501-
debounceMs,
502-
precision,
503-
});
504-
}
505-
} catch (e) {
506-
console.warn("bbox URL restore/sync failed", e);
507-
}
508-
509501
self.config.geoOptions = self.utils.deepMergeObj(
510502
{
511503
pointToLayer: (feature, latlng) =>
@@ -731,6 +723,8 @@ class NetJSONGraphRender {
731723
}
732724
});
733725
}
726+
self.utils.setupHashChangeHandler(self);
727+
734728
self.event.emit("onLoad");
735729
self.event.emit("onReady");
736730
self.event.emit("renderArray");

src/js/netjsongraph.util.js

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,26 @@ class NetJSONGraphUtil {
11941194
};
11951195
}
11961196

1197+
/**
1198+
* Parse the URL hash into a mapping of map IDs to their parameters.
1199+
*
1200+
* The URL hash may contain multiple fragments separated by `;`, where each
1201+
* fragment corresponds to a single Netjsongraph.js instance on the page.
1202+
* Each fragment must include an `id` parameter identifying the map, along
1203+
* with additional parameters such as `nodeId` or `zoom`.
1204+
*
1205+
* Example:
1206+
* #id=map1&nodeId=2;id=map2&nodeId=4
1207+
*
1208+
* Result:
1209+
* {
1210+
* map1: URLSearchParams("id=map1&nodeId=2"),
1211+
* map2: URLSearchParams("id=map2&nodeId=4")
1212+
* }
1213+
*
1214+
* @returns {Object.<string, URLSearchParams>}
1215+
* An object mapping map IDs to their corresponding URLSearchParams.
1216+
*/
11971217
parseUrlFragments() {
11981218
const raw = window.location.hash.replace(/^#/, "");
11991219
const fragments = {};
@@ -1207,23 +1227,39 @@ class NetJSONGraphUtil {
12071227
return fragments;
12081228
}
12091229

1210-
generateUrlFragments(fragments) {
1211-
return Object.values(fragments)
1230+
/**
1231+
* Reverse of parseUrlFragments.
1232+
*
1233+
* Converts a fragments object (map IDs → URLSearchParams) back into
1234+
* a semicolon-delimited string suitable for the URL hash.
1235+
*
1236+
* @param {Object.<string, URLSearchParams>} fragments
1237+
* @returns {string}
1238+
*/
1239+
updateUrlFragments(fragments, state) {
1240+
const newHash = Object.values(fragments)
12121241
.map((urlParams) => urlParams.toString())
12131242
.join(";");
1243+
1244+
// We store the selected node's data to the browser's history state.
1245+
// This allows the node's information to be retrieved instantly on a back/forward
1246+
// button click without needing to re-parse the entire nodes list.
1247+
history.pushState(state, "", `#${newHash}`);
12141248
}
12151249

1216-
setUrlFragments(self, params) {
1217-
if (!self.config.bookmarkableActions.enabled || params.data.cluster) return;
1250+
addActionToUrl(self, params) {
1251+
if (!self.config.bookmarkableActions.enabled || params.data.cluster) {
1252+
return;
1253+
}
12181254
const fragments = this.parseUrlFragments();
12191255
const id = self.config.bookmarkableActions.id;
12201256
let nodeId;
12211257
self.indexedNode = self.indexedNode || {};
1222-
if (params.componentSubType === "graph") {
1258+
if (self.config.render === self.utils.graphRender) {
12231259
nodeId = params.data.id;
12241260
self.indexedNode[nodeId] = params.data;
12251261
}
1226-
if (["scatter", "effectScatter"].includes(params.componentSubType)) {
1262+
if (self.config.render === self.utils.mapRender) {
12271263
nodeId = params.data.node.id;
12281264
self.indexedNode[nodeId] = params.data.node;
12291265
}
@@ -1232,24 +1268,23 @@ class NetJSONGraphUtil {
12321268
fragments[id].set("id", id);
12331269
}
12341270
fragments[id].set("nodeId", nodeId);
1235-
const newHash = this.generateUrlFragments(fragments);
12361271
const state = self.indexedNode[nodeId];
1237-
history.pushState(state, "", `#${newHash}`);
1272+
this.updateUrlFragments(fragments, state);
12381273
}
12391274

12401275
removeUrlFragment(id) {
12411276
const fragments = this.parseUrlFragments();
12421277
if (fragments[id]) {
12431278
delete fragments[id];
12441279
}
1245-
const newHash = this.generateUrlFragments(fragments);
12461280
const state = {id};
1247-
history.pushState(state, "", `#${newHash}`);
1281+
this.updateUrlFragments(fragments, state);
12481282
}
12491283

12501284
setIndexedNodeFromUrlFragments(self, fragments, node) {
1251-
if (!self.config.bookmarkableActions.enabled || !Object.keys(fragments).length)
1285+
if (!self.config.bookmarkableActions.enabled || !Object.keys(fragments).length) {
12521286
return;
1287+
}
12531288
const id = self.config.bookmarkableActions.id;
12541289
const nodeId = fragments[id]?.get("nodeId");
12551290
if (nodeId === node.id) {
@@ -1259,11 +1294,15 @@ class NetJSONGraphUtil {
12591294
}
12601295

12611296
applyUrlFragmentState(self) {
1262-
if (!self.config.bookmarkableActions.enabled) return;
1297+
if (!self.config.bookmarkableActions.enabled) {
1298+
return;
1299+
}
12631300
const id = self.config.bookmarkableActions.id;
12641301
const fragments = self.utils.parseUrlFragments();
12651302
const nodeId = fragments[id]?.get("nodeId");
1266-
if (!self.indexedNode || !self.indexedNode[nodeId]) return;
1303+
if (!self.indexedNode || !self.indexedNode[nodeId]) {
1304+
return;
1305+
}
12671306
const node = self.indexedNode[nodeId];
12681307
const nodeType =
12691308
self.config.graphConfig.series.type || self.config.mapOptions.nodeConfig.type;

test/netjsongraph.render.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ describe("generateMapOption - node processing and dynamic styling", () => {
510510
linkEmphasisConfig: {linkStyle: {}},
511511
})),
512512
parseUrlFragments: jest.fn(),
513+
setupHashChangeHandler: jest.fn(),
513514
setIndexedNodeFromUrlFragments: jest.fn(),
514515
},
515516
};
@@ -982,6 +983,7 @@ describe("Test disableClusteringAtLevel: 0", () => {
982983
nonClusterNodes: [],
983984
nonClusterLinks: [],
984985
})),
986+
setupHashChangeHandler: jest.fn(),
985987
parseUrlFragments: jest.fn(),
986988
setIndexedNodeFromUrlFragments: jest.fn(),
987989
},
@@ -1082,6 +1084,7 @@ describe("Test leaflet zoomend handler and zoom control state", () => {
10821084
echartsSetOption: jest.fn(),
10831085
parseUrlFragments: jest.fn(),
10841086
setIndexedNodeFromUrlFragments: jest.fn(),
1087+
setupHashChangeHandler: jest.fn(),
10851088
},
10861089
event: {
10871090
emit: jest.fn(),
@@ -1225,6 +1228,7 @@ describe("mapRender – polygon overlay & moveend bbox logic", () => {
12251228
getBBoxData: jest.fn(() => Promise.resolve({nodes: [{id: "n1"}], links: []})),
12261229
parseUrlFragments: jest.fn(),
12271230
setIndexedNodeFromUrlFragments: jest.fn(),
1231+
setupHashChangeHandler: jest.fn(),
12281232
},
12291233
event: {emit: jest.fn()},
12301234
};
@@ -1283,6 +1287,9 @@ describe("graph label visibility and fallbacks", () => {
12831287
nodeSizeConfig: 10,
12841288
nodeEmphasisConfig: {nodeStyle: {}, nodeSize: 12},
12851289
})),
1290+
parseUrlFragments: jest.fn(),
1291+
setIndexedNodeFromUrlFragments: jest.fn(),
1292+
setupHashChangeHandler: jest.fn(),
12861293
},
12871294
echarts: {
12881295
getOption: jest.fn(() => ({series: [{id: "network-graph", zoom: 1}]})),
@@ -1318,6 +1325,9 @@ describe("graph label visibility and fallbacks", () => {
13181325
nodeSizeConfig: 10,
13191326
nodeEmphasisConfig: {nodeStyle: {}, nodeSize: 12},
13201327
})),
1328+
parseUrlFragments: jest.fn(),
1329+
setIndexedNodeFromUrlFragments: jest.fn(),
1330+
setupHashChangeHandler: jest.fn(),
13211331
},
13221332
echarts: {
13231333
getOption: jest
@@ -1347,6 +1357,9 @@ describe("graph label visibility and fallbacks", () => {
13471357
utils: {
13481358
generateGraphOption: jest.fn(() => ({series: []})),
13491359
echartsSetOption: jest.fn(),
1360+
parseUrlFragments: jest.fn(),
1361+
setIndexedNodeFromUrlFragments: jest.fn(),
1362+
setupHashChangeHandler: jest.fn(),
13501363
},
13511364
echarts: {
13521365
on: jest.fn((evt, cb) => {
@@ -1390,6 +1403,9 @@ describe("map series ids and name fallbacks", () => {
13901403
linkStyleConfig: {},
13911404
linkEmphasisConfig: {linkStyle: {}},
13921405
})),
1406+
parseUrlFragments: jest.fn(),
1407+
setIndexedNodeFromUrlFragments: jest.fn(),
1408+
setupHashChangeHandler: jest.fn(),
13931409
},
13941410
};
13951411

@@ -1452,6 +1468,9 @@ describe("map series ids and name fallbacks", () => {
14521468
geojsonToNetjson: jest.fn(() => ({nodes: [], links: []})),
14531469
generateMapOption: jest.fn(() => ({series: []})),
14541470
echartsSetOption: jest.fn(),
1471+
parseUrlFragments: jest.fn(),
1472+
setIndexedNodeFromUrlFragments: jest.fn(),
1473+
setupHashChangeHandler: jest.fn(),
14551474
},
14561475
event: {emit: jest.fn()},
14571476
};

test/netjsongraph.util.test.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,38 +211,41 @@ describe("Test URL fragment utilities", () => {
211211
expect(fragments.indoorMap.get("nodeId")).toBe("indoor-node");
212212
});
213213

214-
test("Test setUrlFragments adds a new fragment with nodeId and zoom", () => {
214+
test("Test addActionToUrl adds a new fragment with nodeId", () => {
215215
const self = {
216216
config: {
217217
bookmarkableActions: {enabled: true, id: "geoMap"},
218218
},
219+
utils: utils,
219220
};
220221
const params = {
221222
componentSubType: "effectScatter",
222223
data: {node: {id: "node-1"}},
223224
};
224225

225-
utils.setUrlFragments(self, params);
226+
utils.addActionToUrl(self, params);
226227

227228
const fragments = utils.parseUrlFragments();
228229
expect(fragments.geoMap).toBeDefined();
229230
expect(fragments.geoMap.get("id")).toBe("geoMap");
230231
expect(fragments.geoMap.get("nodeId")).toBe("node-1");
231232
});
232233

233-
test("Test setUrlFragments updates an existing fragment and preserves others", () => {
234+
test("Test addActionToUrl updates an existing fragment and preserves others", () => {
234235
window.location.hash = "id=graph&nodeId=node-1";
235236

236237
const self = {
237-
config: {bookmarkableActions: {enabled: true, id: "geo"}},
238+
config: {
239+
bookmarkableActions: {enabled: true, id: "geo"},
240+
},
238241
indexedNode: undefined,
242+
utils: utils,
239243
};
240244
const params = {
241-
componentSubType: "graph",
242-
data: {id: "node-2"},
245+
data:{node: {id: "node-2"}},
243246
};
244247

245-
utils.setUrlFragments(self, params);
248+
utils.addActionToUrl(self, params);
246249
const fragments = utils.parseUrlFragments();
247250

248251
expect(fragments.graph).toBeDefined();

0 commit comments

Comments
 (0)