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

Add multiple edges simultaneously #88

Open
fsoupimenta opened this issue Apr 9, 2024 · 11 comments
Open

Add multiple edges simultaneously #88

fsoupimenta opened this issue Apr 9, 2024 · 11 comments

Comments

@fsoupimenta
Copy link

fsoupimenta commented Apr 9, 2024

Hi guys, I hope you're well.

I work on a project called GraphFilter, where we use your library (we've even opened some issues here). Recently, a user asked us about the possibility of adding multiple edges simultaneously. I'd like to know if it's possible to implement this functionality.

We even tried to implement something similar, replacing the _on_key_press method, so that we could select all the desired vertices by dragging the left mouse button, and then press the button to add a new node, but we got the following traceback:

Traceback (most recent call last):
  File "C:\Users\Fernando Pimenta\Documents\Github\GraphFilter\venv\lib\site-packages\matplotlib\cbook\__init__.py", line 307, in process
    func(*args, **kwargs)
  File "C:\Users\Fernando Pimenta\Documents\Github\GraphFilter\source\view\project\docks\visualize_graph_dock.py", line 116, in _on_key_press
    self._add_node(event)
  File "C:\Users\Fernando Pimenta\Documents\Github\GraphFilter\venv\lib\site-packages\netgraph\_interactive_variants.py", line 227, in _add_node
    node_properties = self._extract_node_properties(self._selected_artists[-1])
  File "C:\Users\Fernando Pimenta\Documents\Github\GraphFilter\venv\lib\site-packages\netgraph\_interactive_variants.py", line 178, in _extract_node_properties
    radius    = node_artist.radius,
AttributeError: 'EdgeArtist' object has no attribute 'radius'.

We are willing to contribute if you find it interesting.

Thank you in advance for all your support!

@paulbrodersen
Copy link
Owner

Could you describe the intended behaviour in excruciating detail for me? As in:

  1. The user selects multiple nodes with the window selector or by clicking on multiple node artists while holding Ctrl-C.
  1. The user presses ...

Etc.

@paulbrodersen
Copy link
Owner

so that we could select all the desired vertices by dragging the left mouse button, and then press the button to add a new node

Is this a different functionality you were working on, or did you mean to say "... to add a new edge".

@paulbrodersen
Copy link
Owner

I haven't heard from you in a while, so I am assuming that the issue got resolved on your end. Feel free to re-open if that is not the case.

@fsoupimenta
Copy link
Author

Hi! Sorry for my absence, I've had some unexpected events.

The behavior I want is as follows :

  1. The user selects the desired vertices (either by dragging the mouse cursor or by clicking on each one individually while holding down control).

  2. The user clicks the insert a new vertex button (insert or +)

  3. A new vertex is added which is already adjacent to all the other vertices selected previously.

I'll leave a gif below with a demonstration of something similar:
ezgif-3-83e0b22f33

Would it be possible to add this functionality?

@paulbrodersen
Copy link
Owner

Welcome back!

Possible? Sure. But it seems you already got it covered (or how else did you make the gif?).

@paulbrodersen paulbrodersen reopened this May 7, 2024
@paulbrodersen
Copy link
Owner

I made some changes to the dev branch to make the implementation of the desired feature more straightforward.
Now the desired functionality boils down to the following class:

import matplotlib.pyplot as plt

from netgraph._interactive_graph_classes import MutableGraph
from netgraph._artists import NodeArtist


class GFGraph(MutableGraph):
    """Extends :py:class:`MutableGraph` to support the addition of edges to newly created nodes.

    - Double clicking on two nodes successively will create an edge between them.
    - Pressing 'insert' or '+' will add a new node to the graph.
    - Pressing 'delete' or '-' will remove selected nodes and edges.
    - Pressing '@' will reverse the direction of selected edges.

    When adding a new node, the properties of the last selected node will be used to style the node artist.
    Ditto for edges. If no node or edge has been previously selected the first created node or edge artist will be used.

    When adding a new node while other nodes are currently selected,
    edges from the selected nodes to the new node will be added as well.
    """

    def _on_key_press(self, event):

        if event.key in ('insert', '+'):
            node = self._add_node(event)

            # --------------------
            # custom code
            new_edges = []
            for artist in self._selected_artists:
                if isinstance(artist, NodeArtist):
                    source = self._reverse_node_artists[artist]
                    new_edges.append(self._add_edge(source, node))

            if new_edges:
                self.edge_layout.get()
                self._update_edge_artists()

            self.fig.canvas.draw_idle()
            # --------------------

        else:
            super()._on_key_press(event)


if __name__ == "__main__":

    fig, ax = plt.subplots()
    instance = GFGraph([(0, 1)], node_labels=True, ax=ax)
    plt.show()

I haven't made this behaviour the default for MutableGraph (yet), as it potentially conflicts with the way that new nodes are styled: new nodes are given the same type and properties as the last node that the user clicked on. The new feature can conflict with this behaviour, if:

  1. the source nodes are selected through Ctrl+click, and
  2. the source nodes do not have the desired type or properties.

Currently, the only work-around is to 1) click on a node with the desired type and properties, and then 2) select the source nodes with a rectangle selector (which avoids clicking on them).

I will see if I can come up with a decent solution. In the meantime, please leave the issue open lest I forget about.

@fsoupimenta
Copy link
Author

Welcome back!

Possible? Sure. But it seems you already got it covered (or how else did you make the gif?).

In our software we have a class that inherits from EditableGraph, and to get the behavior that the gif demonstrates we have overridden the on_key_press method as follows:

class ResizableGraph(EditableGraph):
    ...
    def _on_key_press(self, event):
        ...
        if event.key == '+' or event.key == '=':
            self._add_node(event)
            for selected_artist in self._selected_artists:
                node = self.find_node(selected_artist)
                if node:
                    self._add_edge((self.nodes[-1], node))
                    self._update_edges([(self.nodes[-1], node)])
                super()._on_key_press(event)
        ...

However, I believe that the implementation was not done in the best way and we had the problem I reported in the first comment of this issue when the vertices are selected using the rectangle selector instead of ctrl + click.

@paulbrodersen
Copy link
Owner

In our software we have a class that inherits from EditableGraph

I haven't tested If the approach above works seamlessly if you inherit from EditableGraph instead of MutableGraph.
Are you using any of the features of EditableGraph (i.e. interactive node & edge labeling)?

@fsoupimenta
Copy link
Author

In our software we have a class that inherits from EditableGraph

I haven't tested If the approach above works seamlessly if you inherit from EditableGraph instead of MutableGraph. Are you using any of the features of EditableGraph (i.e. interactive node & edge labeling)?

Yes, we use the features of the EditableGraph . You even helped us implement this class that inherits from EditableGraph a while ago, in the issue #46.

@paulbrodersen
Copy link
Owner

Thanks for the reminder! ;-)

Have you tested the solution above if all features still work when you inherit from ResizableGraph or EditableGraph?
I don't know for sure which features you care about so I can't do it for you.

@fsoupimenta
Copy link
Author

Sorry for my disappearance again 😅

We tested it and only had one problem:
When we select the nodes that we want by the rectangle, some edges are also selected because they are in the middle of the path. And then, when inserting a new node, nothing happens and we get the following error in the console:

  File “C:\Users\Fernando Pimenta\Documents\Github\GraphFilter\venv\lib\site-packages\matplotlib\cbook\__init__.py”, line 307, in process
    func(*args, **kwargs)
  File “C:\Users\Fernando Pimenta\Documents\Github\GraphFilter\source\view\project\docks\visualize_graph_dock.py”, line 116, in _on_key_press
    self._add_node(event)
  File “C:\Users\Fernando Pimenta\Documents\Github\GraphFilter\venv\lib\site-packages\netgraph\_interactive_variants.py”, line 227, in _add_node
    node_properties = self._extract_node_properties(self._selected_artists[-1])
  File “C:\Users\Fernando Pimenta\Documents\Github\GraphFilter\venv\lib\site-packages\netgraph\_interactive_variants.py”, line 178, in _extract_node_properties
    radius = node_artist.radius,
AttributeError: 'EdgeArtist' object has no attribute 'radius'

Debugging a bit, we found that the problem is in this part of the code:

def _add_node(self, event):
    # --------------------
    if self._selected_artists:
        node_properties = self._extract_node_properties(self._selected_artists[-1])
    else:
        node_properties = self._last_selected_node_properties

Our theory is that it tries to take the properties of the last node that was selected as you mentioned earlier, but since in this case the last artist selected was an edge, it tries to assign edge properties to a node.

But otherwise, everything worked as it should.

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

No branches or pull requests

2 participants