Skip to content

Commit

Permalink
Merge pull request #1223 from Eric89GXL/graph-visual
Browse files Browse the repository at this point in the history
ENH: Graph visual
  • Loading branch information
larsoner committed May 19, 2016
2 parents b8b14e5 + 2622212 commit 48b0409
Show file tree
Hide file tree
Showing 17 changed files with 959 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ install:
if [ "${PYTHON}" == "2.6" ]; then
conda install --yes --quiet wxpython numpy$NUMPY > ${REDIRECT_TO};
else
conda install --yes --quiet ipython$IPYTHON ipython-notebook numpy$NUMPY > ${REDIRECT_TO};
conda install --yes --quiet ipython$IPYTHON ipython-notebook numpy$NUMPY networkx > ${REDIRECT_TO};
fi;
if [ "${PYTHON}" == "3.4" ]; then
pip install -q freetype-py husl pypng cassowary https://github.com/mpld3/mplexporter/zipball/master > ${REDIRECT_TO};
Expand Down
41 changes: 41 additions & 0 deletions examples/basics/scene/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Vispy Development Team.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.

"""
This example demonstrates how to visualise a NetworkX graph using a VisPy
Graph.
"""

import sys

import networkx as nx

from vispy import app, scene
from vispy.visuals.graphs import layouts


canvas = scene.SceneCanvas(title='Simple NetworkX Graph', size=(600, 600),
bgcolor='white', show=True)
view = canvas.central_widget.add_view('panzoom')

graph = nx.adjacency_matrix(
nx.fast_gnp_random_graph(500, 0.005, directed=True))
layout = layouts.get_layout('force_directed', iterations=100)

visual = scene.visuals.Graph(
graph, layout=layout, line_color='black', arrow_type="stealth",
arrow_size=30, node_symbol="disc", node_size=20,
face_color=(1, 0, 0, 0.2), border_width=0.0, animate=True, directed=False,
parent=view.scene)


@canvas.events.draw.connect
def on_draw(event):
if not visual.animate_layout():
canvas.update()

if __name__ == '__main__':
if sys.flags.interactive != 1:
app.run()
4 changes: 2 additions & 2 deletions examples/basics/visuals/arrows_quiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ def on_resize(self, event):
self.visual.transforms.configure(canvas=self, viewport=vp)

def rotate_arrows(self, point_towards):
direction_vectors = (self.grid_coords -
point_towards).astype(np.float32)
direction_vectors = (self.grid_coords - point_towards).astype(
np.float32)
norms = np.sqrt(np.sum(direction_vectors**2, axis=-1))
direction_vectors[:, 0] /= norms
direction_vectors[:, 1] /= norms
Expand Down
59 changes: 59 additions & 0 deletions examples/basics/visuals/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Vispy Development Team.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.

"""
This example demonstrates how to visualise a NetworkX graph using the
GraphVisual.
"""

import sys

import networkx as nx

from vispy import app, visuals
from vispy.visuals.graphs import layouts
from vispy.visuals.transforms import STTransform


class Canvas(app.Canvas):
def __init__(self):
app.Canvas.__init__(self, title="Simple NetworkX Graph",
keys="interactive", size=(600, 600))

graph = nx.adjacency_matrix(
nx.fast_gnp_random_graph(500, 0.005, directed=True))
layout = layouts.get_layout('force_directed', iterations=100)

self.visual = visuals.GraphVisual(
graph, layout=layout, line_color='black', arrow_type="stealth",
arrow_size=30, node_symbol="disc", node_size=20,
face_color=(1, 0, 0, 0.5), border_width=0.0, animate=True,
directed=False)

self.visual.transform = STTransform(self.visual_size, (20, 20))
self.show()

@property
def visual_size(self):
return self.physical_size[0] - 40, self.physical_size[1] - 40

def on_resize(self, event):
self.visual.transform.scale = self.visual_size
vp = (0, 0, self.physical_size[0], self.physical_size[1])
self.context.set_viewport(*vp)
self.visual.transforms.configure(canvas=self, viewport=vp)

def on_draw(self, event):
self.context.clear('white')
self.visual.draw()
if not self.visual.animate_layout():
self.update()


if __name__ == '__main__':
win = Canvas()

if sys.flags.interactive != 1:
app.run()
1 change: 1 addition & 0 deletions vispy/scene/visuals.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ def generate_docstring(subclass, clsname):
Compound = create_visual_node(visuals.CompoundVisual)
Cube = create_visual_node(visuals.CubeVisual)
Ellipse = create_visual_node(visuals.EllipseVisual)
Graph = create_visual_node(visuals.GraphVisual)
GridLines = create_visual_node(visuals.GridLinesVisual)
GridMesh = create_visual_node(visuals.GridMeshVisual)
Histogram = create_visual_node(visuals.HistogramVisual)
Expand Down
1 change: 1 addition & 0 deletions vispy/visuals/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@
from .xyz_axis import XYZAxisVisual # noqa
from .border import _BorderVisual # noqa
from .colorbar import ColorBarVisual # noqa
from .graphs import GraphVisual # noqa
1 change: 1 addition & 0 deletions vispy/visuals/graphs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .graph import GraphVisual # noqa
212 changes: 212 additions & 0 deletions vispy/visuals/graphs/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Vispy Development Team.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
"""
Graph Visual
============
This visual can be used to visualise graphs or networks.
"""

from ..visual import CompoundVisual
from ..line import ArrowVisual
from ..markers import MarkersVisual
from . import layouts


class GraphVisual(CompoundVisual):
"""Visual for displaying graphs or networks.
Parameters
----------
adjacency_mat : array or sparse
The adjacency matrix of the graph.
directed : bool
Whether the graph is directed or not. If True, then this visual will
draw arrows for the directed edges.
line_color : str or :class:`vispy.color.colormap.ColorMap`
The color to use for the edges.
line_width : number
The edge thickness.
arrow_type : str
The kind of arrow head to use. See :class:`vispy.visuals.ArrowHead`
for more information.
arrow_size : number
The size of the arrow head.
node_symbol : string
The marker to use for nodes. See
:class:`vispy.visuals.MarkersVisual` for more information.
node_size : number
The size of the node
border_color : str or :class:`vispy.color.colormap.ColorMap`
The border color for nodes.
face_color : str or :class:`vispy.color.colormap.ColorMap`
The face color for nodes.
border_width : number
The border size for nodes.
See Also
--------
ArrowVisual, MarkersVisual
"""

_arrow_attributes = ('arrow_type', 'arrow_size')
_arrow_kwargs = ('line_color', 'line_width')
_node_kwargs = ('node_symbol', 'node_size', 'border_color', 'face_color',
'border_width')

_arrow_kw_trans = dict(line_color='color', line_width='width')
_node_kw_trans = dict(node_symbol='symbol', node_size='size',
border_color='edge_color', border_width='edge_width')

def __init__(self, adjacency_mat=None, directed=False, layout=None,
animate=False, line_color=None, line_width=None,
arrow_type=None, arrow_size=None, node_symbol=None,
node_size=None, border_color=None, face_color=None,
border_width=None):

self._edges = ArrowVisual(method='gl', connect='segments')
self._nodes = MarkersVisual()

self._arrow_data = {}
self._node_data = {}

self._adjacency_mat = None

self._layout = None
self._layout_iter = None
self.layout = layout

self._directed = directed
self.directed = directed

self._animate = False
self.animate = animate

CompoundVisual.__init__(self, [self._edges, self._nodes])

self.set_data(adjacency_mat, line_color=line_color,
line_width=line_width, arrow_type=arrow_type,
arrow_size=arrow_size, node_symbol=node_symbol,
node_size=node_size, border_color=border_color,
face_color=face_color, border_width=border_width)

@property
def adjacency_matrix(self):
return self._adjacency_mat

@property
def layout(self):
return self._layout

@layout.setter
def layout(self, value):
if type(value) == str:
self._layout = layouts.get_layout(value)
else:
assert callable(value)
self._layout = value

self._layout_iter = None

@property
def directed(self):
return self._directed

@directed.setter
def directed(self, value):
self._directed = bool(value)

@property
def animate(self):
return self._animate

@animate.setter
def animate(self, value):
self._animate = bool(value)

def animate_layout(self):
if self._layout_iter is None:
if self._adjacency_mat is None:
raise ValueError("No adjacency matrix set yet. An adjacency "
"matrix is required to calculate the layout.")

self._layout_iter = iter(self._layout(self._adjacency_mat,
self._directed))

try:
node_vertices, line_vertices, arrows = next(self._layout_iter)
except StopIteration:
return True

self._nodes.set_data(pos=node_vertices, **self._node_data)
self._edges.set_data(pos=line_vertices, arrows=arrows,
**self._arrow_data)

return False

def set_final_layout(self):
if self._layout_iter is None:
if self._adjacency_mat is None:
raise ValueError("No adjacency matrix set yet. An adjacency "
"matrix is required to calculate the layout.")

self._layout_iter = iter(self._layout(self._adjacency_mat,
self._directed))

# Calculate the final position of the nodes and lines
node_vertices = None
line_vertices = None
arrows = None
for node_vertices, line_vertices, arrows in self._layout_iter:
pass

self._nodes.set_data(pos=node_vertices, **self._node_data)
self._edges.set_data(pos=line_vertices, arrows=arrows,
**self._arrow_data)

def reset_layout(self):
self._layout_iter = None

def set_data(self, adjacency_mat=None, **kwargs):
if adjacency_mat is not None:
if adjacency_mat.shape[0] != adjacency_mat.shape[1]:
raise ValueError("Adjacency matrix should be square.")

self._adjacency_mat = adjacency_mat

for k in self._arrow_attributes:
if k in kwargs:
translated = (self._arrow_kw_trans[k] if k in
self._arrow_kw_trans else k)

setattr(self._edges, translated, kwargs.pop(k))

arrow_kwargs = {}
for k in self._arrow_kwargs:
if k in kwargs:
translated = (self._arrow_kw_trans[k] if k in
self._arrow_kw_trans else k)

arrow_kwargs[translated] = kwargs.pop(k)

node_kwargs = {}
for k in self._node_kwargs:
if k in kwargs:
translated = (self._node_kw_trans[k] if k in
self._node_kw_trans else k)

node_kwargs[translated] = kwargs.pop(k)

if len(kwargs) > 0:
raise TypeError("%s.set_data() got invalid keyword arguments: %S"
% (self.__class__.__name__, list(kwargs.keys())))

# The actual data is set in GraphVisual.animate_layout or
# GraphVisual.set_final_layout
self._arrow_data = arrow_kwargs
self._node_data = node_kwargs

if not self._animate:
self.set_final_layout()

0 comments on commit 48b0409

Please sign in to comment.