I'm looking for an interactive graphing library for Python.
By "graph", I meant a set of nodes connected by a set of vertices (not a plot of values over x-y axis, nor a grid of pixels).
By "interactive", I meant I can drag-and-drop the nodes around and I need to be able to click on the nodes/vertices and have the library pass the nodes/vertices to my callbacks, which may add/remove nodes/vertices or display information (I cannot load the full graph at startup as the dataset is too large/complex; instead I'll be loading only the necessary slices of data depending on user inputs).
By Python, I meant the programming language Python, the graphing library should have CPython binding. I have Python 2.7 and Python 3.1, but can downgrade to 2.6 if necessary. This language requirement is because the dataset I'm working with only have Python binding.
The graphing library must support directed graph and be able to layout the nodes automatically. I need to put labels on the nodes.
Preferably, the layouting algorithm should place adjacent nodes near each other. It should be able to handle from 100-1000 nodes and about 300-4000 vertices reasonably in my 4 year old laptop (I typically start with around 100 nodes, but the number might expand depending on user input). Preferably it should be a library with not too many dependencies (except perhaps for Gnome). Open source is preferred.
I have already written a simple prototype of my program using Tkinter Canvas, but I need a more serious graphing library to expand the program. I've looked at graphviz and matplotlib, but apparently they're only for working with static graphs and apparently would need significant amount of work to do interactive manipulations (correct me if I'm wrong, I've only looked at them briefly). I've also tried generating the graph to an SVG file and using Inkscape to view it, but it's too slow and takes too much memory and because of the sheer number of vertices it becomes a tangled mess.
Looks like Nodebox might be what you want:
http://nodebox.net/code/index.php/Graph Mac OSX
http://www.cityinabottle.org/nodebox/ Windows (using OpenGL)
The graph object has functionality for mouse interaction as well, bundled
in the graph.events object. It has the
following properties:
graph.events.hovered: None or the node over which the mouse hovers.
graph.events.pressed: None or the node on which the mouse is
pressing down.
graph.events.dragged: None or the node being dragged.
graph.events.clicked: None or the last node clicked.
graph.events.popup: when True, will display a popup window over the
hovered node.
Also came accross Gephi, looks like that might have the functionality you want as well.
http://gephi.org/ Windows, Linux and Mac OSX
Gephi is an interactive visualization
and exploration platform for all kinds
of networks and complex systems,
dynamic and hierarchical graphs.
You should definitely look at the igraph library if you haven't.
It's a powerful library that can handle large graphs and different layout styles. It can also be used for directed graphs and for interactive and non-interactive visualitzations in 2D and 3D according to the list of features. There is also a tutorial.
Update: Another well-known library is NetworkX for which there are Python packages here. Note that the Mac/Windows software Nodebox, recommended by Acorn, uses NetworkX algorithms.
I have the same problem.
In the end, I think nodebox opengl seems to do the trick.
Don't try to use the graph library at the following link
http://nodebox.net/code/index.php/Graph
with nodebox opengl. It doesn't work, that graph library is only compatible with the mac OSX nodebox. But in anycase that is ok because you don't need it.
See for example the following question:
Adding label to an edge of a graph in nodebox opnegl
It shows example code which works for me, the code can be modified so that clicking on a node not only allows you to move the node, but also allows you to modify the graph.
Just delete
label = "Placeholder"
from the code and it works.
EDIT:
I put some more detailed example code here:
Nodebox open GL Graph, size function not recognized. (Ubuntu)
I thought and tried all the solutions given in this question and finally end up with the following solution.
I think the best scalable solution is using interactive mode of Matplotlib together with networkx. The following code segment explain how to display an annotation of a datapoint for a mouse click. Since we are using Networkx, this solution was much more scalable than anticipated.
import networkx as nx
import matplotlib.pyplot as plt
import nx_altair as nxa
from pylab import *
class AnnoteFinder: # thanks to http://www.scipy.org/Cookbook/Matplotlib/Interactive_Plotting
"""
callback for matplotlib to visit a node (display an annotation) when points are clicked on. The
point which is closest to the click and within xtol and ytol is identified.
"""
def __init__(self, xdata, ydata, annotes, axis=None, xtol=None, ytol=None):
self.data = list(zip(xdata, ydata, annotes))
if xtol is None: xtol = ((max(xdata) - min(xdata))/float(len(xdata)))/2
if ytol is None: ytol = ((max(ydata) - min(ydata))/float(len(ydata)))/2
self.xtol = xtol
self.ytol = ytol
if axis is None: axis = gca()
self.axis= axis
self.drawnAnnotations = {}
self.links = []
def __call__(self, event):
if event.inaxes:
clickX = event.xdata
clickY = event.ydata
print(dir(event),event.key)
if self.axis is None or self.axis==event.inaxes:
annotes = []
smallest_x_dist = float('inf')
smallest_y_dist = float('inf')
for x,y,a in self.data:
if abs(clickX-x)<=smallest_x_dist and abs(clickY-y)<=smallest_y_dist :
dx, dy = x - clickX, y - clickY
annotes.append((dx*dx+dy*dy,x,y, a) )
smallest_x_dist=abs(clickX-x)
smallest_y_dist=abs(clickY-y)
print(annotes,'annotate')
# if clickX-self.xtol < x < clickX+self.xtol and clickY-self.ytol < y < clickY+self.ytol :
# dx,dy=x-clickX,y-clickY
# annotes.append((dx*dx+dy*dy,x,y, a) )
print(annotes,clickX,clickY,self.xtol,self.ytol )
if annotes:
annotes.sort() # to select the nearest node
distance, x, y, annote = annotes[0]
self.drawAnnote(event.inaxes, x, y, annote)
def drawAnnote(self, axis, x, y, annote):
if (x, y) in self.drawnAnnotations:
markers = self.drawnAnnotations[(x, y)]
for m in markers:
m.set_visible(not m.get_visible())
self.axis.figure.canvas.draw()
else:
t = axis.text(x, y, "%s" % (annote), )
m = axis.scatter([x], [y], marker='d', c='r', zorder=100)
self.drawnAnnotations[(x, y)] = (t, m)
self.axis.figure.canvas.draw()
df = pd.DataFrame('LOAD YOUR DATA')
# Build your graph
G = nx.from_pandas_edgelist(df, 'from', 'to')
pos = nx.spring_layout(G,k=0.1, iterations=20) # the layout gives us the nodes position x,y,annotes=[],[],[] for key in pos:
x, y, annotes = [], [], []
for key in pos:
d = pos[key]
annotes.append(key)
x.append(d[0])
y.append(d[1])
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)
ax.set_title('select nodes to navigate there')
nx.draw(G, pos, font_size=6,node_color='#A0CBE2', edge_color='#BB0000', width=0.1,
node_size=2,with_labels=True)
af = AnnoteFinder(x, y, annotes)
connect('button_press_event', af)
show()
Related
I'm using mayaVI to plot a surface and a vector field in 3D, with the functions mayavi.mlab.surf and mayavi.mlab.quiver3D. These functions do not have many keyword arguments that let me modify the appearance of the surface and quivers, in comparison to the Mayavi pipeline, where I can edit things down to the most miniscule detail (for example quiver arrow head radius - see example figure below). The issue is that once I have made these changes in the mayaVI pipeline, there seems to be no way to save these settings until the next time I want to redraw the figure.
I'm in particular interested in editing Contour properties of the surface, and the Glyph Source properties of the vectors (Shaft radius, Tip radius, Tip length).
Question: Is there an easy way to save the Mayavi pipeline settings until next time, or edit them directly in my Python script (i.e. without using the UI)?
Example:
Code:
#!/usr/bin/env python
import numpy as np
from mayavi import mlab
xmax = 2.0*np.pi
x, y, z = np.mgrid[-xmax:xmax:25j, -xmax:xmax:25j, -xmax:xmax:1j]
v_x = np.sin(x)*np.cos(y)
v_y = np.cos(x)*np.sin(y)
v_z = np.zeros_like(z)
v_abs = np.sqrt(v_x**2 + v_y**2) # scalar field
surf = mlab.surf( x[:,:,0], y[:,:,0], v_abs[:,:,0], colormap='magma' )
obj_j = mlab.quiver3d( x[:,:,0], y[:,:,0], z[:,:,-1], v_x[:,:,0], v_y[:,:,0], v_z[:,:,0], mode='arrow')
mlab.show()
For example, to change the tip length of the arrows,
obj = mlab.quiver3d(..., mode='arrow')
obj.glyph.glyph_source.glyph_source.tip_length = 0.9
There doesn't seem to be any complete documentation of the mayavi pipeline, but one can guess from the GUI interface about the parameters:
I am new to Python and matplotlib, and I recently referenced to THIS to update my tripcolor plot. With following data preparation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.tri as tri
import math
r = np.zeros((100,100))
t = np.zeros((100,100))
for i in range(0,100):
for j in range(0,100):
r[i,j]=i
t[i,j]=2*math.pi*j/100
x=r*np.cos(t)
y=r*np.sin(t)
z=r*r
xf=x.flatten()
yf=y.flatten()
zf=z.flatten()
triang = tri.Triangulation(xf,yf)
If I use tripcolor as it is intended,
# Works well
p = plt.tripcolor(triang, zf)
correct figure appears. But, if I try to update after creating tripcolor,
# Not working well
p = plt.tripcolor(triang, xf)
p.set_array(zf)
then, wrong figure appears. Both xf and zf have identical dimensions.
What am I doing wrong? What is the cause of the problem, and how can I avoid it?
Many thanks in advance.
=========================================================
Update
Thank you all. I actually solved myself.
The key was that I need to assign color for each area, which is controlled by shading argument, and default value for tripcolor is 'flat', which is, color for each vertex. So, when I plot the first figure, I need to make sure shading is 'gouraud', which assigns color for each area.
So,
p = plt.tripcolor(triang, xf, shading='gouraud')
p.set_array(zf)
works as I intended.
The reason
p = plt.tripcolor(triang, xf)
p.set_array(zf)
is not working as (may be) expected, is the following. In your case plt.tripcolor() returns a PolyCollection. The PolyCollection's set_array() will essentially set the colors of that Collection. However, the underlying triangles will not be changed, such that you end up with the triangles from xf but the colors from zf.
Since the generation of the tripcolor PolyCollection is quite involved (as it calls Triangulation itself) and there probably is no helper function to set the data externally (at least I am not aware of any), the solution might be not to update the tripcolor at all and instead generate a new one.
Is there any reason for you to update? Couldn't you just directly create p = plt.tripcolor(triang, zf)?
In case there is a real reason to it, like in an animation or so, an option would be to delete the first tripcolor plot before setting up the next.
# create one plot
p = plt.tripcolor(triang, xf)
#delete this plot (you need both lines actually!!)
p.remove()
del p
#create second plot
p = plt.tripcolor(triang, zf)
This is not really efficient, though, and in case someone has a better idea, I'd like to hear about that one as well.
I have a Traits and Mayavi script that presents an mlab scene and several traits editors. The editors affect what data is shown in a surface, quiver3d and legend (Scalar LUT Manager) by calling my drawing method. Each change triggers a clear figure and re-draw.
Learning from the Mlab interactive dialog example the plot3d* uses mlab_source.set to change the data without clearing the figure and re-drawing. In update_plot():
if self.plot is None:
self.plot = self.scene.mlab.plot3d(x, y, z, t, tube_radius=0.025, colormap='Spectral')
else:
self.plot.mlab_source.set(x=x, y=y, z=z, scalars=t)
What my surface and quiver3d calls return are mayavi.modules.surface.Surface and mayavi.modules.vectors.Vectors objects respectively. Surface and LUTManager report no mlab_source: AttributeError: 'Surface'/'LUTManager' object has no attribute 'mlab_source'. Quiver3d reports an mayavi.tools.sources.MGlyphSource
1) How can I change the data/source in my surface and scalar LUTManager?
2) How do I correctly change the quiver’s data/source?
When I attempt to change the values of the quiver I get a TraitError: Cannot set the undefined 'u' attribute of a 'Vectors' object. This puzzles me because I used the six-value initializer.
if self.quiver is None:
self.quiver = self.scene.mlab.quiver3d(xyz[:,0], xyz[:,1], xyz[:,2],
velocity[:,0], velocity[:,1], velocity[:,2],
figure=self.scene.mayavi_scene, scale_factor = self.scale)
else:
self.quiver.mlab_source.set(x = xyz[:,0], y = xyz[:,1], z = xyz[:,2],
u = velocity[:,0], v = velocity[:,1], w = velocity[:,2])
In the example the plot3d returns a mayavi.modules.surface.Surface and its mlab_source object is a mayavi.tools.sources.MLineSource. Searching the docs for MLineSource is fruitless but externally yields Enthought Tool Suite 3.2 results. Are the Tool Suite docs current?
*self.plot, self.surface and self.quiver are declared as variable = Instance(PipelineBase). PipelineBase is imported from mayavi.core.api.
Based on your comments:
The reason there is no reference to an mlab source in surface is because there is no mlab source. Your module just consists of a raw vtk source, which mayavi is perfectly happy to render unadulterated. However you retain a reference to the PolyData and so you could edit the scalars from that. (You could also use mlab.triangular_mesh which very likely does what you want while introducing an intervening TriangularMeshSource to control the vtk PolyData).
You can get to the LUT with surface.module_manager.scalar_lut_manager.
And you should also be able to get to the mlab level source for the vectors as in the comments, I don't know what is the problem with that if there still is one. The use of traitsui should not have an effect -- the problem is probably a conflict between mayavi and your object model. Try setting the trait type of your mayavi objects to be Any.
I'm creating a tool for geospatial visualization of economic data using Matplotlib and Basemap.
However, right now, the only way I thought of that gives me enough flexibility is to create a new basemap every time I want to change the data.
Here are the relevant parts of the code I'm using:
class WorldMapCanvas(FigureCanvas):
def __init__(self,data,country_data):
self.text_objects = {}
self.figure = Figure()
self.canvas = FigureCanvas(self.figure)
self.axes = self.figure.add_subplot(111)
self.data = data
self.country_data = country_data
#this draws the graph
super(WorldMapCanvas, self).__init__(Figure())
self.map = Basemap(projection='robin',lon_0=0,resolution='c', ax=self.axes)
self.country_info = self.map.readshapefile(
'shapefiles/world_country_admin_boundary_shapefile_with_fips_codes', 'world', drawbounds=True,linewidth=.3)
self.map.drawmapboundary(fill_color = '#85A6D9')
self.map.fillcontinents(color='white',lake_color='#85A6D9')
self.map.drawcoastlines(color='#6D5F47', linewidth=.3)
self.map.drawcountries(color='#6D5F47', linewidth=.3)
self.countrynames = []
for shapedict in self.map.world_info:
self.countrynames.append(shapedict['CNTRY_NAME'])
min_key = min(data, key=data.get)
max_key = max(data, key=data.get)
minv = data[min_key]
maxv = data[max_key]
for key in self.data.keys():
self.ColorCountry(key,self.GetCountryColor(data[key],minv,maxv))
self.canvas.draw()
How can I create these plots faster?
I couldn't think of a solution to avoid creating a map every time I run my code. I tried creating the canvas/figure outside of the class but it didn't make that much of a difference. The slowest call is the one that creates the Basemap and loads the shape data. Everything else runs quite fast.
Also, I tried saving the Basemap for future use but since I need new axes I couldn't get it to work. Maybe you can point me in the right direction on how to do this.
I'd like you to know that I'm using the canvas as a PySide QWidget and that I'm plotting different kinds of maps depending on the data, this is just one of them (another would be a map of Europe, for instance, or the US).
You can pickle and unpickle Basemap instances (there is an example of doing this in the basemap source) which might save you a fair chunk of time on the plot creation.
Additionally, it is probably worth seeing how long the shapefile reading is taking (you may want to pickle that too).
Finally, I would seriously consider investigating the option of updating country colours for data, rather than making a new figure each time.
HTH,
So I'm representing a token ring network (doing the simulation in SimPy), I'm a totally newbie to matplotlib, but I was told that it'd be really good for representing my simulation visually.
So I googled around and found out how to draw shapes and lines - using add_patch and add_line respectively to the axes (I believe). So now I have this output which is absolutely fine:
(can't post images yet!!)
http://img137.imageshack.us/img137/7822/screenshot20100121at120.png
But I'm getting this using the pylab.show() function, and what I think I want is to achieve this using the pylab.plot() function so that I can then update it as my simulation progresses using pylab.draw() afterward.
My code is as follows:
plab.ion()
plab.axes()
for circ in self.circleList:
plab.gca().add_patch(circ)
for line in self.lineList:
plab.gca().add_line(line)
plab.axis('scaled')
plab.show()
Where circleList and lineList are lists containing the circles and lines on the diagram
I'm probably misunderstanding something simple here, but I can't actually find any examples that aren't overtly graph based that use the plot() function.
Clarification:
How can I get that same output, using pylab.plot() instead of pylab.show() ?
Replicating your image using the plot method:
from pylab import *
points = []
points.append((-0.25, -1.0))
points.append((0.7, -0.7))
points.append((1,0))
points.append((0.7,1))
points.append((-0.25,1.2))
points.append((-1,0.5))
points.append((-1,-0.5))
points.append((-0.25, -1.0))
a_line = plot(*zip(*points))[0]
a_line.set_color('g')
a_line.set_marker('o')
a_line.set_markerfacecolor('b')
a_line.set_markersize(30)
axis([-1.5,1.5,-1.5,1.5])
show()
EDIT BASED ON COMMENTS
This uses python multiprocessing library to run the matplotlib animation in a separate process. The main process uses a queue to pass data to it which then updates the plot image.
# general imports
import random, time
from multiprocessing import Process, Queue
# for matplotlib
import random
import numpy as np
import matplotlib
matplotlib.use('GTKAgg') # do this before importing pylab
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
def matplotLibAnimate(q,points):
# set up initial plot
fig = plt.figure()
ax = fig.add_subplot(111)
circles = []
for point in points:
ax.add_patch(Circle(point,0.1))
a_line, = ax.plot(*zip(*points))
a_line.set_color('g')
a_line.set_lw(2)
currentNode = None
def animate(currentNode = currentNode):
while 1:
newNode = q.get()
if currentNode: currentNode.remove()
circle = Circle(newNode,0.1)
currentNode = ax.add_patch(circle)
circle.set_fc('r')
fig.canvas.draw()
# start the animation
import gobject
gobject.idle_add(animate)
plt.show()
#initial points
points = ((-0.25, -1.0),(0.7, -0.7),(1,0),(0.7,1),(-0.25,1.2),(-1,0.5),(-1,-0.5),(-0.25, -1.0))
q = Queue()
p = Process(target = matplotLibAnimate, args=(q,points,))
p.start()
# feed animation data
while 1:
time.sleep(random.randrange(4))
q.put(random.sample(points,1)[0])
Of course, after doing this I think you'll be better served with whatnick's image solution. I'd create my own GUI and not use matplotlibs built in widget. I'd then "animate" my GUI by generating PNGs and swapping them.
It sounds like Mark has the answer you were looking for, but if you decide to go with whatnick's approach and build an animation from individual pngs, here is the code to implement Amit's suggestion to use mencoder (from http://en.wikibooks.org/wiki/Mplayer):
mencoder mf://*.png -mf w=400:h=400 -ovc lavc -lavcopts vcodec=xvid -of avi -o output.avi
The core technique is to update the data of the elements being rendered using set_data. Then call draw(). See if your circle and line elements have set_data functions. Otherwise you can use pyvtk. The other option is to render and save the plots to png files and later build an animation from those.