netjsongraph.js icon indicating copy to clipboard operation
netjsongraph.js copied to clipboard

[feature] Make actions bookmarkable

Open pandafy opened this issue 1 year ago • 1 comments

Clicking on a node/link to view it's details (action) should update the query parameters of the page's URL with the object's ID (or some other identifiable information).

When visiting this URL, the map should automatically focus the said node/link. It shall also open the node's/links's details if they are available.

It shall also work with geo-maps which works with co-ordinates.

We shall also add the user's actions to the browser's history using history.push.

We shall make it extendable so it possible to add more data in the query params which can trigger some operation.

Screenshot from 2024-02-12 15-36-01

pandafy avatar Feb 12 '24 10:02 pandafy

Approach:

  • We have 3 prefixes: geo map, indoor map, and network topology . And two things to do:

1. Add fragments to the URL

If not already present, fragments (nodeId) can be added from the onClickElement handler from config. https://github.com/openwisp/netjsongraph.js/blob/dcb23b325cf8a6b09fb8a0e27c65329d9c8fb13f/src/js/netjsongraph.config.js#L324-L345

2. Render using the fragments.

While traversing through the data inside the prepared data handler, we get the data of that particular nodeId and keep it in a separate object so that it can be retrieved afterwards without the need for full traversal. https://github.com/openwisp/netjsongraph.js/blob/dcb23b325cf8a6b09fb8a0e27c65329d9c8fb13f/src/js/netjsongraph.config.js#L298-L311

After that, we can create an event handler, AfterRender in config.js, and emit it just like onReady. https://github.com/openwisp/netjsongraph.js/blob/dcb23b325cf8a6b09fb8a0e27c65329d9c8fb13f/src/js/netjsongraph.core.js#L65-L71

That way, we can configure our custom use case based on the prefix inside the afterRender.

dee077 avatar May 14 '25 17:05 dee077

TLDR

We discussed the following topics in the call:

  • Managing URL fragments for multiple maps on one page
  • Approach for handling geographic map vs. indoor floorplan maps in openwisp-monitoring
  • Deciding how to include locationId (chose encoding it in mapId)
  • Logic for loading floorplans from bookmarked URLs
  • Need to clear fragments when switching/closing floorplans
  • Bug with popup rendering before floorplan loads

Implementation Details

How to handle URL for multiple maps on a single page?

  • Each Netjsongraph.js object has an id

  • The netjsongraph.js object will look for it's id in the URL fragment and it will only perform the operation defined for it's ID

  • The Netjsongraph.js is aware only of itself.

  • From the perspective of netjsongraph.js, we can have unlimited number of maps on the page. And each map will add it's own fragment to the URL. Example: #id=map1&nodeId=2&zoom=4;id=map2&nodeId=4&zoom=4;id=map3&nodeId=6&zoom=4;id=map6&nodeId=7&zoom=4

  • From the perspective of openwisp-monitoring, we only have two maps on the page. Example: #id=owGeoMap&nodeId=<locationId>&zoom=4;id=<locationId:floorNumber>&nodeId=4&zoom=2; Notes: The order of the fragment does not matter, the URL could be id=<locationId:floorNumber>&nodeId=4&zoom=2;#id=owGeoMap&nodeId=<locationId>&zoom=4;

In openwisp-monitoring, we require the locationId to fetch the indoor coordinates:

  • We thought of two options

    1. Add JS logic in openwisp-monitoring which read the locationId from the fragment of the owGeoMap map.

    2. Include the locationId in the mapId of indoor map, i.e. the mapId would be <locationId:floorNumber>.

  • @pandafy and @dee077 concluded that option 2 is better as it makes the netjsongraph object self-reliant.

  • When the user opens a bookmarked URL, initially only the geographic map will render. We will add JS logic in floorplan.js which will do the following tasks:

    • This function is executed after $(document).ready()
    • Any fragment whose id is not owGeoMap will be considered as fragment for the floorplan overlay
    • We can generate the indoor-coordinates URL from the info present in the id (it contains both locationId and floor number) using the getIndoorCoordinatesUrl function. Then using the openFloorplan function, we can open the desired floorplan overlay. openFloorplan function only requires the URL to open the correct floor.
    • After this, the NetjsonGraph object will take over to open popup for the bookmarked node.
  • Avoiding bugs: We need to clear the fragment for floorplan node in the following scenarios:

    • When the user changes the floor
    • When the user closes the floorplan overlay

    Do so will ensure that we don't have fragments for two different floors.

When opening the floorplan overlay using the bookmarkable URL, the pop-up appears before floorplan and nodes are rendered

Bug
  • For optimization reasons, we avoid to iterate over the data again to find the co-ordinates of the bookmarked node (we only have nodeId).

  • Instead, we store the co-ordinates of the bookmarked node in an internal attribute of the netJSONGraph object when the library iterates over the data in prepareData. Thus, avoiding the need to iterate over all the data again.

  • In the floorplan.js, we convert co-ordinates of the node from CRS.Simple to EPSG:3857. In the current implementation, we are only updating the echarts.series[0].data.

  • Thus, the co-ordinates saved in prepareData gets outdated and the pop-up renders at wrong place.

Solution

  • Conversion of co-ordinates in floorplan.js is a special case and would not be required after we fix the real issue in netjsongraph.js

  • As a temporary fix, we convert the following logic in to scoped function and use that function to convert the co-ordinates of bookmarked node in floorplan.js

mapOptions.series[0].data.forEach((data) => {
  const node = data.node;
  const px = Number(node.coordinates.lng);
  const py = -Number(node.coordinates.lat);
  const nodeProjected = L.point(topLeft.x + px, topLeft.y + py);
  // This requrires an map instance to unproject coordinates so it cann't be done in prepareData
  const nodeLatLng = map.unproject(nodeProjected, zoom);
  node.properties.location = nodeLatLng;
  data.value = [nodeLatLng.lng, nodeLatLng.lat];
});
  • The click operation should be performed after onReady.

PS: @dee077 I changed the id for openwisp-monitoring geographic map from dashboardGeo to owGeoMap since we will also have a dedicated page for the map.

PSS: We used arbitrary delimited (;) for separating data for different maps. We should double check and use only url safe character.

pandafy avatar Sep 08 '25 20:09 pandafy

Thoughts for later

  • In openwisp-monitoring, when we close the floorplan overlay, ensure all the netjsongraph.js object related to floors of the location are deleted. The goal is to ensure that the zombie objects are not listening for any events.

pandafy avatar Sep 08 '25 21:09 pandafy