Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any way to select only node/edge which is clicked #68

Open
parmar-abhinav opened this issue Jul 16, 2023 · 5 comments
Open

Any way to select only node/edge which is clicked #68

parmar-abhinav opened this issue Jul 16, 2023 · 5 comments

Comments

@parmar-abhinav
Copy link

When any node is selected then its adjacent edges/nodes also gets selected. passing isDefaultSelectEnabled to false in strategy does not select any node/edge.

any way to select only the node/edge which is clicked ?

@tonilastre
Copy link
Contributor

tonilastre commented Jul 17, 2023

If you are checking the Orb from release/1.0.0 branch it can be done using the CLICK events and node.state + node.clearState. The same can be done for the default hover.

const orb = new OrbView(container, {
  strategy: {
    isDefaultSelectEnabled: false,
  },
});

let selectedNodeId = null;
orb.events.on(OrbEventType.MOUSE_CLICK, (event) => {
  // Clear the previously selected node
  if (selectedNodeId !== null) {
    const selectedNode = orb.data.getNodeById(selectedNodeId);
    selectedNode?.clearState();
    selectedNodeId = null;
  }

  // isNode can be imported from Orb namespace
  if (isNode(event.subject)) {
    event.subject.state = GraphObjectState.SELECTED;
    selectedNodeId = event.subject.id;
  }

  orb.render();
});

Btw I've used MOUSE_CLICK event instead of NODE_CLICK event because MOUSE_CLICK can give out a canvas click, e.g. to unselect any selected one. You can also go through and get the latest selected nodes and unselect them using:

orb.data.getSelectedNodes().forEach(node => node.clearState());

Where you don't need to remember the last selected node. The iteration to slower though because it needs to go through all the nodes to find the selected ones.

Just FYI, we plan to change the API a bit to add RxJS so all these setters (like state on the node) will be set<XYZ> where you won't need to call orb.render() for orb to know that there is a change in the data and the render is needed.

@parmar-abhinav
Copy link
Author

Based on the provided approach, it seems that the code snippet effectively addresses the requirement to select only the clicked node or edge. However, it could be beneficial to have a more flexible solution that allows for passing an argument to control which nodes and edges are selected upon clicking. This could provide greater customization options based on specific use cases.

For example, introducing a parameter like selectClickedOnly to the OrbView constructor or selection strategy could offer more control over the selection behavior. When selectClickedOnly is set to true, only the clicked node or edge would be selected, and adjacent elements would remain unselected. On the other hand, when set to false (or omitted), the current behavior of selecting adjacent elements along with the clicked node or edge would be maintained.

By introducing this parameter, users of the Orb library could easily tailor the selection behavior to suit their specific requirements without having to modify the event handling logic each time. It would enhance the flexibility and usability of the library in different scenarios.

@tonilastre
Copy link
Contributor

I totally agree about providing greater customization based on specific use cases. I think the selectClickedOnly would just add a bit to the default strategy, but it wouldn't solve the general problem: How to enable users of the Orb API to be able to do any kind of select/hover flows. E.g.:

  • Select only the clicked node
  • Select the clicked node, but also select the adjacent nodes
  • Select the path between two clicked nodes
  • Upon clicking, select the clicked node on the first click, and then select 1 hop more for each new click on the same node - more like spread clicking

There are so many cases and we can't cover them all with flags, so my thinking is in this direction:

  • The ORB should have a structure to keep nodes/edges by their state (selected, hovered, or even custom user ones - that is the reason why the state can be any number) - this is for easier and faster lookup, currently, you need to iterate through all nodes/edges to get the selected/hovered one which is not an optimal way
  • The state change of the node/edge goes through setState which will be listened to by ORB and ORB components (renderer). ORB will react to it by updating the internal structure for faster lookup, while the renderer should do the throttling for the render and simulate calls - this requires integration of RxJS which we have in plan and which will happen on the release/1.0.0 branch

With just these above two changes, the default strategy will be questionable how much sense it has. There will be two paths:

  1. If you want custom select/hover logic, you disable the default via settings and implement yours in the event listeners. (like the example above)

  2. If you want custom select/hover logic, you will implement a custom callback for each select/hover settings function - I am talking about the functions from the DefaultEventStrategy class: https://github.com/memgraph/orb/blob/main/src/models/strategy.ts - these will be exposed out in the settings.

Additionally, regardless of the two paths (event listener or callback implementation), an update on the setState for nodes and edges can be done with optional options, e.g.

// Select the node
node.setState({ state: SELECTED });

// Select the node if not selected, otherwise unselect it
node.setState({ state: SELECTED, options: { isToggle: true }});

// Select the node, but unselect any other node that is currently selected (aka only the single node will be selected)
node.setState({ state: SELECTED, options: { isSingle: true }}};

What do you think?

@parmar-abhinav
Copy link
Author

Thank you for your valuable insights and suggestions! I fully understand the need for greater customization in select/hover flows and the limitations of a simple flag-based approach.

Based on your suggestions, I agree that implementing a structured storage mechanism and introducing the setState method would be beneficial for enabling advanced customization.

I will work on making these changes into the library as per your recommendations.

@parmar-abhinav
Copy link
Author

I am thinking of a class with below interface to store nodes and edges id by their state. The StateStorage class will provide methods to update and retrieve nodes and edges with specific states, ensuring easy and quick access. Additionally, I am planning to integrate this class with the graph.ts to ensure that any changes to the state of nodes or edges will be observed by graph.ts which will update the StateStorage object present.

// Define the interface for StateStorage
export interface IStateStorage<N extends INodeBase, E extends IEdgeBase> {
  // key defines the state and set contains the node/edge id that belongs to particular state
  nodeStateMap: Map<number, Set<number>>;
  edgeStateMap: Map<number, Set<number>>;
  
   // update the map, to add particular node/edge to a given state
  updateState(graphObject: INode<N, E> | IEdge<N, E>, state: number): void;
   // remove node/edge from the map
  clearState(graphObject: INode<N, E> | IEdge<N, E>): void;
   // clear both nodes and edge state maps
  clearAllState(): void;
  // returns node id's belonging to given state
  getNodesWithState(state: number): Set<number>;
  // returns edge id's belonging to given state
  getEdgesWithState(state: number): Set<number>;
}

@tonilastre Kindly review the approach and provide your valuable feedback. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants