Skip to content

Conversation

@dee077
Copy link
Member

@dee077 dee077 commented Aug 17, 2025

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Reference to Existing Issue

Closes #238 .

Description of Changes

  • Update URL query params on map interactions (zoom, move, bounds change)
  • Add mode in URL to differentiate indoor map and geo-map
  • Open floorplan overlay when in indoor map mode

Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in our weekly meeting, the result doesn't seem to match the specs described in #238.

Please read again the specs and ask any question if not clear.

@dee077 dee077 force-pushed the feature/238-update-url-on-map-actions branch from 6fa8c24 to 591ed33 Compare September 7, 2025 13:19
@dee077 dee077 marked this pull request as ready for review September 7, 2025 20:53
@dee077 dee077 force-pushed the feature/238-update-url-on-map-actions branch 2 times, most recently from 4c6fdfb to 751d759 Compare September 11, 2025 22:25
@dee077
Copy link
Member Author

dee077 commented Sep 11, 2025

Screencast.from.2025-09-12.02-47-39.webm

@dee077 dee077 moved this from In progress to In review in [GSoC25] General Map: Indoor, Mobile, Linkable URLs Sep 12, 2025
@dee077 dee077 force-pushed the feature/238-update-url-on-map-actions branch from 0b32122 to 5bfbddc Compare September 12, 2025 16:11
Copy link
Member

@pandafy pandafy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work @Deepanshu! This is going to be a very user feature in the library. 💪🏼

I found a corner case while going back through the history of the browser using the back button. I expected the map to update when the URL fragment updates, but that is not the case here.

I also explored about enabling this feature by default. If ID is not provided in the config, we can generate a hash based on the HTML element. This can have side-effects, so let's think before going ahead in this direction.

Maybe, auto-generating ID is a bad idea. If the hash will change if the user changed the HTML element. This will render old URLs useless.

diff --git a/src/js/netjsongraph.config.js b/src/js/netjsongraph.config.js
index b4fcc67..444d813 100644
--- a/src/js/netjsongraph.config.js
+++ b/src/js/netjsongraph.config.js
@@ -285,7 +285,7 @@ const NetJSONGraphDefaultConfig = {
   ],
   linkCategories: [],
   urlFragments: {
-    show: false,
+    show: true,
     id: null,
   },
 
diff --git a/src/js/netjsongraph.core.js b/src/js/netjsongraph.core.js
index 1d448d1..1758e0b 100644
--- a/src/js/netjsongraph.core.js
+++ b/src/js/netjsongraph.core.js
@@ -57,6 +57,19 @@ class NetJSONGraph {
       console.error("Can't change el again!");
     }
 
+    if (!this.config.urlFragments.id) {
+      // The config does not specify a an ID, we assign one by creating
+      // a hash of the HTML element using FNV-1a algorithm
+      let str = this.el.outerHTML
+      let hash = 0x811c9dc5; // FNV offset basis
+      for (let i = 0; i < str.length; i++) {
+        hash ^= str.charCodeAt(i);
+        hash = (hash * 0x01000193) >>> 0; // FNV prime
+
+      }
+      this.config.urlFragments.id = hash.toString();
+    }
+
     return this.config;
   }

@pandafy
Copy link
Member

pandafy commented Sep 12, 2025

I also explored about enabling this feature by default. If ID is not provided in the config, we can generate a hash based on the HTML element. This can have side-effects, so let's think before going ahead in this direction

Maybe, this is a bad idea. What if the user updates the HTML element? Then, the old URLs will no longer work.

@dee077 dee077 force-pushed the feature/238-update-url-on-map-actions branch from 32eb321 to 0883e25 Compare September 17, 2025 16:19
Copy link
Member

@pandafy pandafy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work @dee077! 👏🏼 👏🏼 👏🏼

It works as expected.

We need to add a lot of comments in the code to explain our design decision. Otherwise, we will forget about why we chose to implement the feature this way and then search for the details in issues.

@dee077 dee077 force-pushed the feature/238-update-url-on-map-actions branch 6 times, most recently from c076184 to d65b3ac Compare September 22, 2025 09:20
@dee077 dee077 force-pushed the feature/238-update-url-on-map-actions branch 3 times, most recently from 9c90edb to 463ed3b Compare October 12, 2025 21:35
@dee077
Copy link
Member Author

dee077 commented Oct 12, 2025

Updates

  • Added a new object in the netjsongraph instance this.nodeIndex, which contains the key as node.id or sourceNodeId-targetNodeId in case of a link, so that it can be looked afterwards by this.data.nodes[index] or this.data.links[index].
  • Updated the tests with these new changes and added new unit and browser tests for case of links.
  • Did not add browser tests for adding nodes to url on click on the node or link, as we currently don't have resilient logic to trigger the click event of the node or link in selenium tests. Moving the mouse programmatically and triggering a click will cause unexpected failure on different window sizes.
  • Adding the value for nodeIndex as index of the array present this.data.nodes or this.data.nodes was a bad idea, as it cannot be sure the array will be in the same sequence every time. On monitoring found that the index is getting changed on readering a url in a new tab.
  • So in netjsongraph.core.js we are already traversing the nodes and links to set the this.data, so I created a shallow copy of it in this.nodeLinkIndex as key of node id or source~target for further looks without need for a traversal.
  • Used ~ as a separator in case of setting link key in dict as source~target, uuid can have - and give unexpected results

@dee077 dee077 force-pushed the feature/238-update-url-on-map-actions branch from d730eef to 1c0bbda Compare October 12, 2025 22:06
@dee077 dee077 force-pushed the feature/238-update-url-on-map-actions branch from 39c68f4 to 6866b9b Compare October 13, 2025 09:48
Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good @dee077, I have a few comments.

The left side in the nested indoor map overlayed on the geo map doesn't look right:

Image

White background on the white background of the image doesn't work well from the point of view of UX, something like this would work slightly better (feel free to improve if you want):

#netjson-indoormap .njg-sideBar {
    background: #f4f4f4;
    border-right: 1px solid rgba(0, 0, 0, 0.2);
}

I will do more testing and reviewing tomorrow.

Copy link
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far it's behaving pretty well, we just need to do some polishing.

Here's something weird which I found:

  1. I keep clicking on nodes and some times the zoom changes. Is this on purpose?
  2. Some times the zoom changes also when going back, but most of the time it doesn't, this looks incosistent
  3. When openinig a URL fragment on a new tab, the map doesn't really zoom in much

See:

Image

What do you think about the following and can it be done?

  1. When the map is already open and the user is clicking around or going back and forth in the history, we shouldn't interfere with the zoom level has set (if they zoomed up to that level it means they're happy with that)
  2. When the map opens with a fragment we can zoom in at a specific configurable level (eg: urlFragmentZoomLevel), which by default can fallback to showLabelsAtZoomLevel, so we have a reasonable default for this but can be tweaked if needed.

```

You can enable or disable adding url fragments by setting enabled to true or false. When enabled, the following parameters are added to the URL:
1. id – A prefix used to uniquely identify the map.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this an arbitrary identifier or what?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is an arbitrary identifier

1. id – A prefix used to uniquely identify the map.
2. nodeId – The id of the selected node.
When a URL containing these fragments is opened, the click event associated with the given nodeId is triggered, and if the map is based on Leaflet, the view will automatically center on that node.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this whole docs section is not user friendly and needs to be improved.

  1. Explain what the feature does in general terms
  2. Be specific, eg: link a few examples and suggest to click on nodes and link while observing the URL bar, then try to go back and forward with the browser history or copy and paste a URL in a new browser tab/window.
  3. State whether this feature is enabled by default or not (I think it's not right?)
  4. Show an example of how to enable it, be specific in what id means (it's the ID of the HTML container of the netjsongraph.js target right?)

Please do this yourself: do not ask LLM to generate this for you, but once you have a good draft feel free to ask LLMs to improve grammar to ensure correct use of the English language which is consistent with the rest of the text in this document.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

},
bookmarkableActions:{
enabled: true,
id: "indoorMap",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to speecify an ID when we already have an HTML id in el?
My question is not rethoric, I am not sure el MUST be an HTML id, I don't remember right now, can you please check? If el MUST be an HTML id, then we do not need to ask the user to specify the ID, it could be optional, which makes the setup easier. Let's keep in mind we must strive to make our work easy to use which is a common pain points across new openwisp features added in gsoc projects.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In openwisp-monitoring we need to open the floorplan overlay before applying the state from url fragments.
So we make the indoor map id as locationId:floor so that we can open the overlay using these values for a specific location and floor, and then the state can be applied.

@dee077 dee077 force-pushed the feature/238-update-url-on-map-actions branch from 6866b9b to 0ef49db Compare October 27, 2025 10:20
Copy link
Member

@pandafy pandafy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work Deepanshu! I just found this minor adjustments to make before we merge.

Can you also rebase it on the current master?

dee077 added 20 commits November 8, 2025 02:18
Implemented query param updates on node click, zoom and interaction with map. Visiting a URL with query params of map bounds to auto-focuses the node.

Fixes #238
Fixed CRS conflict in indoor map example and added a new parameter 'zoomLevel' in bookmarkableActions. Added support for link interactions, fix tests and update docs.

Fixes #397
@dee077 dee077 force-pushed the feature/238-update-url-on-map-actions branch from 0ef49db to ba4f267 Compare November 7, 2025 21:14
@dee077 dee077 requested review from nemesifier and pandafy November 7, 2025 21:19
@dee077
Copy link
Member Author

dee077 commented Nov 7, 2025

Feel free to update the Readme if you want some additional details to mention or want to remove something.

Comment on lines +468 to +496
- `bookmarkableActions`

Configuration for adding url fragments when a node is clicked.

```javascript
bookmarkableActions: {
enabled: boolean,
id: string,
zoomLevel: number
}
```

**Note: This feature is disabled by default.**

You can enable or disable adding url fragments by setting enabled to true or false. When enabled, the following parameters are added to the URL:
1. id – A prefix used to uniquely identify the map.
2. nodeId – The id of the selected node.
3. zoomLevel – The zoom level applied when restoring the state from the URL fragments.

This feature allows you to create shareable and restorable map or graph states using URL fragments. When this feature is enabled, the URL updates automatically whenever you click a node or a link in your NetJSONGraph visualization. This makes it easy to share a specific view, restore it later, or navigate between different states using the browser’s back and forward buttons.

This feature works across all ECharts graphs, as well as Leaflet-based maps including geographic and indoor floorplan maps and it supports multiple maps or graphs on the same page. The id parameter is used to uniquely identify which visualization the URL fragment belongs to (for example: `#map1-node=device-1;#map2-node=device-2` ).

For nodes, the behavior depends on the type of visualization in Leaflet maps, clicking a node updates the URL and on apllying the state from url it automatically centers the map on that node, in addition to triggering its click event. In ECharts graphs, only triggers the click event for the node.

For links, the URL fragment uses the format `source~target` as the `nodeId`. Opening such a URL restores the initial map or graph view and triggers the corresponding link click event.

If you need to manually remove the URL fragment, you can call the built-in utility method: `netjsongraphInstance.utils.removeUrlFragment('id');` where id is `bookmarkableActions.id`.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ @dee077 don't commit these changes yet. I want to get a review from @nemesifier first before integrating it to the readme.

What do you think about changing the section with following:

  • bookmarkableActions

    Enables shareable URLs that capture the current NetJSONGraph state.

    When a graph element is clicked, the browser URL updates with a fragment identifying the selected element.
    This makes it easy to share a specific view, restore it later, or navigate between different states using the browser’s back and forward buttons.

    The following examples demonstrate how selected nodes can be shared across different types of maps:

    bookmarkableActions: {
      // Enables or disables the feature (default: false)
      enabled: boolean,
    
      // Arbitrary unique identifier for the NetJSONGraph instance.
      id: string,
    
      // Zoom level to apply when restoring from the URL fragment.
      zoomLevel: number
    }

    The URL fragment format depends on the selected element:

    • Node: #<id>-node=<nodeId>
    • Link: #<id>-node=<source>~<target>

    Where:

    • <id> is the value of bookmarkableActions.id, identifying each NetJSONGraph instance.
    • <nodeId> is the ID of the selected node.
    • <source> and <target> are the IDs of the nodes connected by the selected link.

    When multiple visualizations are present, fragments are separated by semicolons:

    #map1-node=device-1;#map2-node=device-2
    

    Note: When restoring a view from a URL, the corresponding element is automatically clicked. Due to technical limitations, only Geographic maps also zoom in to the selected node during this process.

Copy link
Member

@pandafy pandafy Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The links currently don't work, but will work once the PR is merged.

I am not in favour of sharing the structure of URL fragments in the README. I just added it here for completeness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

[feature] Make actions bookmarkable

4 participants