WestHealth / pyvis

Python package for creating and visualizing interactive network graphs.
http://pyvis.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
1k stars 169 forks source link

Filtering nodes #73

Open lazarospa opened 4 years ago

lazarospa commented 4 years ago

Is there a way to select a node and visualize only the nodes that are connected with the selected one ?

pyem20 commented 3 years ago

Hi @lazarospa, did you find a way to do this? I saw a R counterpart "visOptions(highlightNearest = TRUE, nodesIdSelection = TRUE)", but, as it appears, there is none for Python - I could be wrong.

pyem20 commented 3 years ago

Hi @boludo00, wondering if you can help.

boludo00 commented 3 years ago

Do you mean something like in this example?

https://visjs.github.io/vis-network/examples/network/exampleApplications/neighbourhoodHighlight.html

If so, then additional JavaScript would need to be supplied in the template.html that pyvis uses for rendering your graph. Inspecting the above example's source code, something similar would like:

      var network;
      var allNodes;
      var highlightActive = false;

      var nodesDataset = new vis.DataSet(nodes); // these come from WorldCup2014.js
      var edgesDataset = new vis.DataSet(edges); // these come from WorldCup2014.js

      function redrawAll() {
        var container = document.getElementById("mynetwork");
        var options = {
          nodes: {
            shape: "dot",
            scaling: {
              min: 10,
              max: 30,
              label: {
                min: 8,
                max: 30,
                drawThreshold: 12,
                maxVisible: 20,
              },
            },
            font: {
              size: 12,
              face: "Tahoma",
            },
          },
          edges: {
            width: 0.15,
            color: { inherit: "from" },
            smooth: {
              type: "continuous",
            },
          },
          physics: false,
          interaction: {
            tooltipDelay: 200,
            hideEdgesOnDrag: true,
            hideEdgesOnZoom: true,
          },
        };
        var data = { nodes: nodesDataset, edges: edgesDataset }; // Note: data is coming from ./datasources/WorldCup2014.js

        network = new vis.Network(container, data, options);

        // get a JSON object
        allNodes = nodesDataset.get({ returnType: "Object" });

        network.on("click", neighbourhoodHighlight);
      }

      function neighbourhoodHighlight(params) {
        // if something is selected:
        if (params.nodes.length > 0) {
          highlightActive = true;
          var i, j;
          var selectedNode = params.nodes[0];
          var degrees = 2;

          // mark all nodes as hard to read.
          for (var nodeId in allNodes) {
            allNodes[nodeId].color = "rgba(200,200,200,0.5)";
            if (allNodes[nodeId].hiddenLabel === undefined) {
              allNodes[nodeId].hiddenLabel = allNodes[nodeId].label;
              allNodes[nodeId].label = undefined;
            }
          }
          var connectedNodes = network.getConnectedNodes(selectedNode);
          var allConnectedNodes = [];

          // get the second degree nodes
          for (i = 1; i < degrees; i++) {
            for (j = 0; j < connectedNodes.length; j++) {
              allConnectedNodes = allConnectedNodes.concat(
                network.getConnectedNodes(connectedNodes[j])
              );
            }
          }

          // all second degree nodes get a different color and their label back
          for (i = 0; i < allConnectedNodes.length; i++) {
            allNodes[allConnectedNodes[i]].color = "rgba(150,150,150,0.75)";
            if (allNodes[allConnectedNodes[i]].hiddenLabel !== undefined) {
              allNodes[allConnectedNodes[i]].label =
                allNodes[allConnectedNodes[i]].hiddenLabel;
              allNodes[allConnectedNodes[i]].hiddenLabel = undefined;
            }
          }

          // all first degree nodes get their own color and their label back
          for (i = 0; i < connectedNodes.length; i++) {
            allNodes[connectedNodes[i]].color = undefined;
            if (allNodes[connectedNodes[i]].hiddenLabel !== undefined) {
              allNodes[connectedNodes[i]].label =
                allNodes[connectedNodes[i]].hiddenLabel;
              allNodes[connectedNodes[i]].hiddenLabel = undefined;
            }
          }

          // the main node gets its own color and its label back.
          allNodes[selectedNode].color = undefined;
          if (allNodes[selectedNode].hiddenLabel !== undefined) {
            allNodes[selectedNode].label = allNodes[selectedNode].hiddenLabel;
            allNodes[selectedNode].hiddenLabel = undefined;
          }
        } else if (highlightActive === true) {
          // reset all nodes
          for (var nodeId in allNodes) {
            allNodes[nodeId].color = undefined;
            if (allNodes[nodeId].hiddenLabel !== undefined) {
              allNodes[nodeId].label = allNodes[nodeId].hiddenLabel;
              allNodes[nodeId].hiddenLabel = undefined;
            }
          }
          highlightActive = false;
        }

        // transform the object into an array
        var updateArray = [];
        for (nodeId in allNodes) {
          if (allNodes.hasOwnProperty(nodeId)) {
            updateArray.push(allNodes[nodeId]);
          }
        }
        nodesDataset.update(updateArray);
      }

      redrawAll();

This is something that can be supplied in the template file, for more info on that, checkout #57

I have always wanted to add custom functionality like this to the default template, but have never gotten around to it.

pyem20 commented 3 years ago

Thanks @boludo00. This solves the question of @lazarospa

On my part, I'm looking for the Python counterpart as explained in the Select by node id section found in this link: https://cran.r-project.org/web/packages/visNetwork/vignettes/Introduction-to-visNetwork.html

I have a network graph that has about 500 nodes. Looking for a single node can be challenging. This is where a feature like Select by node id can come handy. Another thing that can be really useful is being able to Add legend - also a section in the above mentioned link.

boludo00 commented 3 years ago

Let me see what I can do about that. I agree that could be helpful when looking for a specific node.

pyem20 commented 3 years ago

Thanks @boludo00. Very much appreciated.

boludo00 commented 3 years ago

I have been messing around with some pretty big changes in order to support and maintain custom interactions, check out PR #91 you can see a demonstration of some added features I think you were requesting. If you want to pull that expiremental branch you can mess with it and see if it needs any more attention, but I'm going to work on cleaning and testing some more things before I release this into the wild.

pyem20 commented 3 years ago

Thank you. The demo looks great! It's exactly the feature I was looking for.

I tried to replicate what you did. After pulling the experimental branch, all I'm presented is the dropdown menu and no graph. Without the pulled branch, and excluding the parameters neighborhood_highlight and select_menu, the graph was generated, but (of course) without the dropdown menu.

boludo00 commented 3 years ago

Oh I see. There might have been an issue with resolving the dependencies that I didn't catch. Are you working out of a Jupyter Notebook by chance? I would take a look at your browser's debugger and check out the console for any errors.

pyem20 commented 3 years ago

Here you go. localhost-1619774542138.log

P.S. Actually, I'm using Anaconda distro and PyCharm. I tried Jupyter notebook to completely replicate your demo. Also, if I may add:

  1. With the current experimental branch, manually selecting a node within the graph highlight itself and its neighbors while greying out the rest. Can we incorporate this as well for the dropdown menu option?
  2. I used Networkx and was able to assign different colors for each node base of a categorical column. Kindly skip to item number 3. _I get the error TypeError: add_node() got multiple values for argument 'color' Note: I wanted to distinguish each node based on a categorical column of a Panda dataframe. In my original code, I didn't use the Networkx library directly (I will try to use Networkx now). I use PyVis to create nodes and edges with g.add_node() and g.add_edge() respectively via a for loop. If I remove the color parameter in g.add-node(), I'm no longer presented with this error; however, that will make my graph monochrome._
  3. Adding a legend in the graph (based on a categorical column) would also be a great addition. If this is too much of a hassle, having item number 1 will be enough.

Thank you.

boludo00 commented 3 years ago
  1. Ok, I got the select working with greying out the rest. It is basically the same behavior of neighborhood highlight, except it doesnt fire on click event. Just on selection. Which means, too reset the nodes colors I added a button you can click instead of clicking off the nodes.

  2. I think I know what you mean by this error. I got this error when attempting to do g.add_node(n_id, n_id, color="blue") because a recent change to add_node added the color kwarg inplace of the label. So therefore it was receiving 2 args for color. To avoid this you can either use g.add_node(n_id, color="red") or g.add_node(n_id, label=n_id, color="red")

  3. I have added a legend before in an internal project so this is definitely doable.

pyem20 commented 3 years ago

Thanks @boludo00.

Have you seen the console errors?

e-xf commented 3 years ago

This feature would be great. I tried using the example with the experimental branch but couldn't get it to work. @boludo00 do you know if this will be on any future update?

tomascolla95 commented 1 year ago

Hi @boludo00

I'm trying to change the color of the nodes when it gets clicked on. I've tried using the options dictionary but it didn't work:

network_graph.set_options( """ var options = { "configure": { "enabled": true }, "interaction": { "hover": true }, "nodes":{ "chosen": true, "color": { "highlight": { "border": "#D66A5E", "background": "#C93C2C" }, "hover" : { "border": "#D66A5E", "background": "#C93C2C" } }, "font": { "size": 20 } } } """)

How can I achieve this ?

Thanks!