# -*- coding: UTF-8 -*-
""" Graph module - plot function graphs
Thomas Führinger, 2005-10-28
"""
import gtk, sys, __main__
class Graph2D:
def __init__(self):
# Create backing pixmap of the appropriate size
def ConfigureEvent(Widget, Event):
x, y, w, h = Widget.get_allocation()
self.PixMap = gtk.gdk.Pixmap(Widget.window, w, h)
self.CanvasWidth = w
self.CanvasHeight = h
self.Refresh()
return True
# Redraw the screen from the backing pixmap
def ExposeEvent(Widget, Event):
x, y, w, h = Event.area
Widget.window.draw_drawable(Widget.get_style().fg_gc[gtk.STATE_NORMAL], self.PixMap, x, y, x, y, w, h)
return False
def ButtonPressEvent(Widget, Event):
global xSel, ySel
# Start marking selection
if Event.button == 1:
self.Selection[0][0], self.Selection[0][1] = int(Event.x), int(Event.y)
self.Selection[1][0], self.Selection[1][1] = None, None
# Show popup menu allowing to split window
if Event.button == 3 and self.WorkbenchWindow is not None:
m = self.WorkbenchWindow.PopupMenuCreate()
MenuItemSave = self.ActionSave.create_menu_item()
m.append(MenuItemSave)
m.popup(None, None, None, 3, Event.time)
# End of selection
def ButtonReleaseEvent(Widget, Event):
if Event.button == 1 and Event.x != self.Selection[0][0] and Event.y != self.Selection[0][1]:
xmi, ymi = min(self.GraphX(self.Selection[0][0]), self.GraphX(Event.x)), min(self.GraphY(self.Selection[0][1]), self.GraphY(Event.y))
xma, yma = max(self.GraphX(self.Selection[0][0]), self.GraphX(Event.x)), max(self.GraphY(self.Selection[0][1]), self.GraphY(Event.y))
self.xMin, self.yMin, self.xMax, self.yMax = xmi, ymi, xma, yma
self.GtkSpinButton.set_value(1)
self.Refresh()
self.Selection[1][0] = None
self.Selection[0][0] = None
# Draw rectangle during mouse movement
def MotionNotifyEvent(Widget, Event):
if Event.is_hint:
x, y, State = Event.window.get_pointer()
else:
x = Event.x
y = Event.y
State = Event.state
if State & gtk.gdk.BUTTON1_MASK and self.Selection[0][0] is not None:
gc = self.DrawingArea.get_style().black_gc
gc.set_function(gtk.gdk.INVERT)
if self.Selection[1][0] is not None:
x0 = min(self.Selection[1][0], self.Selection[0][0])
y0 = min(self.Selection[1][1], self.Selection[0][1])
w = abs(self.Selection[1][0] - self.Selection[0][0])
h = abs(self.Selection[1][1] - self.Selection[0][1])
self.PixMap.draw_rectangle(gc, False, x0, y0, w, h)
x0 = min(self.Selection[0][0], int(x))
y0 = min(self.Selection[0][1], int(y))
w = abs(int(x) - self.Selection[0][0])
h = abs(int(y) - self.Selection[0][1])
self.PixMap.draw_rectangle(gc, False, x0, y0, w, h)
self.Selection[1][0], self.Selection[1][1] = int(x), int(y)
self.DrawDrawable()
self.xMax = 10.0
self.xMin = -10.0
self.xScale = 1
self.yMax = 10.0
self.yMin = -10.0
self.yScale = 1
self.y = []
self.Zoom = 1
self.ConnectPoints = True
self.GtkWidget = gtk.VBox()
self.WorkbenchWindow = None
self.ToolBar = gtk.Toolbar()
ti = gtk.ToolItem()
ti.add(gtk.Label(" Zoom Factor "))
self.ToolBar.insert(ti, -1)
self.GtkSpinButton = gtk.SpinButton(None, 1, 1)
self.GtkSpinButton.set_numeric(True)
self.GtkSpinButton.set_range(0.1, 100.0)
self.GtkSpinButton.set_increments(.2, .2)
self.GtkSpinButton.set_value(1)
ti = gtk.ToolItem()
ti.add(self.GtkSpinButton)
self.ToolBar.insert(ti, -1)
self.ActionRefresh = gtk.Action("Refresh", "_Refresh", "Refresh graphs", gtk.STOCK_REFRESH)
self.ActionRefresh.connect ("activate", self.Refresh)
self.ToolBar.insert(self.ActionRefresh.create_tool_item(), -1)
self.HandleBox = gtk.HandleBox()
self.HandleBox.add(self.ToolBar)
self.GtkWidget.pack_start(self.HandleBox, False, False, 0)
self.ActionSave = gtk.Action("Save", "_Save", "Save graph", gtk.STOCK_SAVE)
self.ActionSave.connect ("activate", self.Save)
# Marked area point[0, 1][x, y]
self.Selection = [[None, None], [None, None]]
self.DrawingArea = gtk.DrawingArea()
self.DrawingArea.set_size_request(60, 60)
self.DrawingArea.connect("expose_event", ExposeEvent)
self.DrawingArea.connect("configure_event", ConfigureEvent)
self.DrawingArea.connect("button_press_event", ButtonPressEvent)
self.DrawingArea.connect("button_release_event", ButtonReleaseEvent)
self.DrawingArea.connect("motion_notify_event", MotionNotifyEvent)
self.DrawingArea.set_events(gtk.gdk.EXPOSURE_MASK | gtk.gdk.LEAVE_NOTIFY_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.POINTER_MOTION_MASK |gtk.gdk.POINTER_MOTION_HINT_MASK)
self.GtkWidget.pack_start(self.DrawingArea, True, True, 0)
def DrawDrawable(self):
x, y, w, h = self.DrawingArea.get_allocation()
self.DrawingArea.window.draw_drawable(self.DrawingArea.get_style().fg_gc[gtk.STATE_NORMAL], self.PixMap, 0, 0, 0, 0, w, h)
def Refresh(self, Widget=None, Event=None):
self.GtkSpinButton.update()
self.Zoom = self.GtkSpinButton.get_value()
self.PixMap.draw_rectangle(self.DrawingArea.get_style().white_gc, True, 0, 0, self.CanvasWidth, self.CanvasHeight)
# draw cross
self.PixMap.draw_lines(self.DrawingArea.get_style().black_gc, [self.CanvasPoint(0, self.yMin * self.Zoom), self.CanvasPoint(0, self.yMax / self.Zoom)])
self.PixMap.draw_lines(self.DrawingArea.get_style().black_gc, [self.CanvasPoint(self.xMin * self.Zoom, 0), self.CanvasPoint(self.xMax / self.Zoom, 0)])
# draw scaling x
iv = int(self.xScale * self.CanvasWidth/(self.xMax - self.xMin) * self.Zoom)
os = self.CanvasX(0) % iv
for i in xrange(self.CanvasWidth / iv + 1):
self.PixMap.draw_lines(self.DrawingArea.get_style().black_gc, [(os + i * iv, self.CanvasY(0) - 5), (os + i * iv, self.CanvasY(0) + 5)])
# draw scaling x
iv = int(self.yScale * self.CanvasHeight/(self.yMax - self.yMin) * self.Zoom)
os = self.CanvasY(0) % iv
for i in xrange(self.CanvasHeight / iv + 1):
self.PixMap.draw_lines(self.DrawingArea.get_style().black_gc, [(self.CanvasX(0) - 5, i * iv + os), (self.CanvasX(0) + 5, i * iv + os)])
# plot
GC1 = self.DrawingArea.get_style().fg_gc[gtk.STATE_NORMAL]
GC1.foreground = gtk.gdk.color_parse("blue")
PrevY = []
for i in xrange(len(self.y)): PrevY.append(None)
for i in xrange(self.CanvasWidth):
__main__.x = self.GraphX(i + 1)
for ii in xrange(len(self.y)):
try:
y = self.y[ii]()
yC = self.CanvasY(y)
if yC < 0 or yC > self.CanvasHeight:
raise ValueError
if self.ConnectPoints and PrevY[ii] is not None:
self.PixMap.draw_lines(GC1, [(i, PrevY[ii]), (i + 1, yC)])
else:
self.PixMap.draw_points(GC1, [(i + 1, yC)])
PrevY[ii] = yC
except:
#print "Error at %d: %s" % (__main__.x, sys.exc_value)
PrevY[ii] = None
self.DrawDrawable()
def CanvasX(self, x):
"Calculate position on canvas to point on graph"
return int((x - self.xMin / self.Zoom) * self.CanvasWidth/(self.xMax - self.xMin) * self.Zoom)
def CanvasY(self, y):
return int((self.yMax / self.Zoom - y) * self.CanvasHeight/(self.yMax - self.yMin) * self.Zoom)
def CanvasPoint(self, x, y):
return (self.CanvasX(x), self.CanvasY(y))
def GraphX(self, x):
"Calculate position on graph from point on canvas"
return x * (self.xMax - self.xMin) / self.Zoom / self.CanvasWidth + self.xMin / self.Zoom
def GraphY(self, y):
return self.yMax / self.Zoom - (y * (self.yMax * self.Zoom - self.yMin / self.Zoom) / self.CanvasHeight)
def ConnectWindow(self, WorkbenchWindow):
"make widget appear in WorkbenchWindow"
self.WorkbenchWindow = WorkbenchWindow
self.WorkbenchWindow.Connect(self.GtkWidget)
self.GtkWidget.show_all()
def Save(self, Widget, Event=None):
"Save graph as .png"
FileDialog = gtk.FileChooserDialog("Save as..", None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))
FileDialog.set_default_response(gtk.RESPONSE_OK)
Filter = gtk.FileFilter()
Filter.add_mime_type("image/png")
Filter.add_pattern("*.png")
FileDialog.add_filter(Filter)
FileDialog.set_filename("FunctionGraph.png")
Response = FileDialog.run()
FileDialog.destroy()
if Response == gtk.RESPONSE_OK:
x, y, w, h = self.DrawingArea.get_allocation()
PixBuffer = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h)
PixBuffer.get_from_drawable(self.PixMap, self.PixMap.get_colormap(), 0, 0, 0, 0, w, h)
PixBuffer.save(FileDialog.get_filename(), "png")
syntax highlighted by Code2HTML, v. 0.9.1