From 4473a6871ef99a9cf3a33923498686dc9439b273 Mon Sep 17 00:00:00 2001 From: George Lacey Date: Fri, 10 Feb 2017 17:17:22 +0000 Subject: [PATCH] Add graph drawing functionality --- Graph.py | 15 + Main.py | 7 +- graphics.py | 1001 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1021 insertions(+), 2 deletions(-) create mode 100644 Graph.py create mode 100644 graphics.py diff --git a/Graph.py b/Graph.py new file mode 100644 index 0000000..5a7b1cd --- /dev/null +++ b/Graph.py @@ -0,0 +1,15 @@ +from graphics import * + +class Graph(object): + + def __init__(self, p_width, p_height, p_name): + self.win = GraphWin("perceptron", p_width, p_height) + self.win.setCoords(0, 0, 640, 480) + self.x_offset = p_width / 2 + self.y_offset = p_height / 2 + self.win.setCoords(0, 0, 640, 480) + Line(Point(0, p_height / 2), Point(p_width, p_height / 2)).draw(self.win) + Line(Point(p_width / 2, 0), Point(p_width / 2, p_height)).draw(self.win) + + def drawLine(self, p_point1, p_point2): + Line(p_point1, p_point2).draw(self.win).move(self.x_offset, self.y_offset) diff --git a/Main.py b/Main.py index 5ce31e3..04259b9 100644 --- a/Main.py +++ b/Main.py @@ -1,10 +1,13 @@ from Perceptron import Perceptron from random import Random from Dataset import Dataset +from Graph import * rand = Random() data = Dataset(open("input.txt").read().split('\n'), open("target.txt").read().split('\n')) learn_rate = 0.01 +graph = Graph(640, 480, "Perceptron") +graph.drawLine(Point(-1000, -1000 + 20), Point(1000, 1000 + 20)) p = Perceptron() i = data.inputs @@ -20,8 +23,8 @@ for ind in range(0, 500): print("error: %f" % p.guess(i[key].target, learn_rate)) for i in range(0, 1000): - x = input("Arg1") - y = input("Arg2") + x = input("Arg1: ") + y = input("Arg2: ") p.input(x, y) if p.activation() == 1: print("TRUE") diff --git a/graphics.py b/graphics.py new file mode 100644 index 0000000..d59d796 --- /dev/null +++ b/graphics.py @@ -0,0 +1,1001 @@ +# graphics.py +"""Simple object oriented graphics library + +The library is designed to make it very easy for novice programmers to +experiment with computer graphics in an object oriented fashion. It is +written by John Zelle for use with the book "Python Programming: An +Introduction to Computer Science" (Franklin, Beedle & Associates). + +LICENSE: This is open-source software released under the terms of the +GPL (http://www.gnu.org/licenses/gpl.html). + +PLATFORMS: The package is a wrapper around Tkinter and should run on +any platform where Tkinter is available. + +INSTALLATION: Put this file somewhere where Python can see it. + +OVERVIEW: There are two kinds of objects in the library. The GraphWin +class implements a window where drawing can be done and various +GraphicsObjects are provided that can be drawn into a GraphWin. As a +simple example, here is a complete program to draw a circle of radius +10 centered in a 100x100 window: + +-------------------------------------------------------------------- +from graphics import * + +def main(): + win = GraphWin("My Circle", 100, 100) + c = Circle(Point(50,50), 10) + c.draw(win) + win.getMouse() # Pause to view result + win.close() # Close window when done + +main() +-------------------------------------------------------------------- +GraphWin objects support coordinate transformation through the +setCoords method and mouse and keyboard interaction methods. + +The library provides the following graphical objects: + Point + Line + Circle + Oval + Rectangle + Polygon + Text + Entry (for text-based input) + Image + +Various attributes of graphical objects can be set such as +outline-color, fill-color and line-width. Graphical objects also +support moving and hiding for animation effects. + +The library also provides a very simple class for pixel-based image +manipulation, Pixmap. A pixmap can be loaded from a file and displayed +using an Image object. Both getPixel and setPixel methods are provided +for manipulating the image. + +DOCUMENTATION: For complete documentation, see Chapter 4 of "Python +Programming: An Introduction to Computer Science" by John Zelle, +published by Franklin, Beedle & Associates. Also see +http://mcsp.wartburg.edu/zelle/python for a quick reference""" + +__version__ = "5.0" + +# Version 5 8/26/2016 +# * update at bottom to fix MacOS issue causing askopenfile() to hang +# * update takes an optional parameter specifying update rate +# * Entry objects get focus when drawn +# * __repr_ for all objects +# * fixed offset problem in window, made canvas borderless + +# Version 4.3 4/25/2014 +# * Fixed Image getPixel to work with Python 3.4, TK 8.6 (tuple type handling) +# * Added interactive keyboard input (getKey and checkKey) to GraphWin +# * Modified setCoords to cause redraw of current objects, thus +# changing the view. This supports scrolling around via setCoords. +# +# Version 4.2 5/26/2011 +# * Modified Image to allow multiple undraws like other GraphicsObjects +# Version 4.1 12/29/2009 +# * Merged Pixmap and Image class. Old Pixmap removed, use Image. +# Version 4.0.1 10/08/2009 +# * Modified the autoflush on GraphWin to default to True +# * Autoflush check on close, setBackground +# * Fixed getMouse to flush pending clicks at entry +# Version 4.0 08/2009 +# * Reverted to non-threaded version. The advantages (robustness, +# efficiency, ability to use with other Tk code, etc.) outweigh +# the disadvantage that interactive use with IDLE is slightly more +# cumbersome. +# * Modified to run in either Python 2.x or 3.x (same file). +# * Added Image.getPixmap() +# * Added update() -- stand alone function to cause any pending +# graphics changes to display. +# +# Version 3.4 10/16/07 +# Fixed GraphicsError to avoid "exploded" error messages. +# Version 3.3 8/8/06 +# Added checkMouse method to GraphWin +# Version 3.2.3 +# Fixed error in Polygon init spotted by Andrew Harrington +# Fixed improper threading in Image constructor +# Version 3.2.2 5/30/05 +# Cleaned up handling of exceptions in Tk thread. The graphics package +# now raises an exception if attempt is made to communicate with +# a dead Tk thread. +# Version 3.2.1 5/22/05 +# Added shutdown function for tk thread to eliminate race-condition +# error "chatter" when main thread terminates +# Renamed various private globals with _ +# Version 3.2 5/4/05 +# Added Pixmap object for simple image manipulation. +# Version 3.1 4/13/05 +# Improved the Tk thread communication so that most Tk calls +# do not have to wait for synchonization with the Tk thread. +# (see _tkCall and _tkExec) +# Version 3.0 12/30/04 +# Implemented Tk event loop in separate thread. Should now work +# interactively with IDLE. Undocumented autoflush feature is +# no longer necessary. Its default is now False (off). It may +# be removed in a future version. +# Better handling of errors regarding operations on windows that +# have been closed. +# Addition of an isClosed method to GraphWindow class. + +# Version 2.2 8/26/04 +# Fixed cloning bug reported by Joseph Oldham. +# Now implements deep copy of config info. +# Version 2.1 1/15/04 +# Added autoflush option to GraphWin. When True (default) updates on +# the window are done after each action. This makes some graphics +# intensive programs sluggish. Turning off autoflush causes updates +# to happen during idle periods or when flush is called. +# Version 2.0 +# Updated Documentation +# Made Polygon accept a list of Points in constructor +# Made all drawing functions call TK update for easier animations +# and to make the overall package work better with +# Python 2.3 and IDLE 1.0 under Windows (still some issues). +# Removed vestigial turtle graphics. +# Added ability to configure font for Entry objects (analogous to Text) +# Added setTextColor for Text as an alias of setFill +# Changed to class-style exceptions +# Fixed cloning of Text objects + +# Version 1.6 +# Fixed Entry so StringVar uses _root as master, solves weird +# interaction with shell in Idle +# Fixed bug in setCoords. X and Y coordinates can increase in +# "non-intuitive" direction. +# Tweaked wm_protocol so window is not resizable and kill box closes. + +# Version 1.5 +# Fixed bug in Entry. Can now define entry before creating a +# GraphWin. All GraphWins are now toplevel windows and share +# a fixed root (called _root). + +# Version 1.4 +# Fixed Garbage collection of Tkinter images bug. +# Added ability to set text atttributes. +# Added Entry boxes. + +import time, os, sys + +try: # import as appropriate for 2.x vs. 3.x + import tkinter as tk +except: + import Tkinter as tk + + +########################################################################## +# Module Exceptions + +class GraphicsError(Exception): + """Generic error class for graphics module exceptions.""" + pass + + +OBJ_ALREADY_DRAWN = "Object currently drawn" +UNSUPPORTED_METHOD = "Object doesn't support operation" +BAD_OPTION = "Illegal option value" + +########################################################################## +# global variables and funtions + +_root = tk.Tk() +_root.withdraw() + +_update_lasttime = time.time() + + +def update(rate=None): + global _update_lasttime + if rate: + now = time.time() + pauseLength = 1 / rate - (now - _update_lasttime) + if pauseLength > 0: + time.sleep(pauseLength) + _update_lasttime = now + pauseLength + else: + _update_lasttime = now + + _root.update() + + +############################################################################ +# Graphics classes start here + +class GraphWin(tk.Canvas): + """A GraphWin is a toplevel window for displaying graphics.""" + + def __init__(self, title="Graphics Window", + width=200, height=200, autoflush=True): + assert type(title) == type(""), "Title must be a string" + master = tk.Toplevel(_root) + master.protocol("WM_DELETE_WINDOW", self.close) + tk.Canvas.__init__(self, master, width=width, height=height, + highlightthickness=0, bd=0) + self.master.title(title) + self.pack() + master.resizable(0, 0) + self.foreground = "black" + self.items = [] + self.mouseX = None + self.mouseY = None + self.bind("", self._onClick) + self.bind_all("", self._onKey) + self.height = int(height) + self.width = int(width) + self.autoflush = autoflush + self._mouseCallback = None + self.trans = None + self.closed = False + master.lift() + self.lastKey = "" + if autoflush: _root.update() + + def __repr__(self): + if self.isClosed(): + return "" + else: + return "GraphWin('{}', {}, {})".format(self.master.title(), + self.getWidth(), + self.getHeight()) + + def __str__(self): + return repr(self) + + def __checkOpen(self): + if self.closed: + raise GraphicsError("window is closed") + + def _onKey(self, evnt): + self.lastKey = evnt.keysym + + def setBackground(self, color): + """Set background color of the window""" + self.__checkOpen() + self.config(bg=color) + self.__autoflush() + + def setCoords(self, x1, y1, x2, y2): + """Set coordinates of window to run from (x1,y1) in the + lower-left corner to (x2,y2) in the upper-right corner.""" + self.trans = Transform(self.width, self.height, x1, y1, x2, y2) + self.redraw() + + def close(self): + """Close the window""" + + if self.closed: return + self.closed = True + self.master.destroy() + self.__autoflush() + + def isClosed(self): + return self.closed + + def isOpen(self): + return not self.closed + + def __autoflush(self): + if self.autoflush: + _root.update() + + def plot(self, x, y, color="black"): + """Set pixel (x,y) to the given color""" + self.__checkOpen() + xs, ys = self.toScreen(x, y) + self.create_line(xs, ys, xs + 1, ys, fill=color) + self.__autoflush() + + def plotPixel(self, x, y, color="black"): + """Set pixel raw (independent of window coordinates) pixel + (x,y) to color""" + self.__checkOpen() + self.create_line(x, y, x + 1, y, fill=color) + self.__autoflush() + + def flush(self): + """Update drawing to the window""" + self.__checkOpen() + self.update_idletasks() + + def getMouse(self): + """Wait for mouse click and return Point object representing + the click""" + self.update() # flush any prior clicks + self.mouseX = None + self.mouseY = None + while self.mouseX == None or self.mouseY == None: + self.update() + if self.isClosed(): raise GraphicsError("getMouse in closed window") + time.sleep(.1) # give up thread + x, y = self.toWorld(self.mouseX, self.mouseY) + self.mouseX = None + self.mouseY = None + return Point(x, y) + + def checkMouse(self): + """Return last mouse click or None if mouse has + not been clicked since last call""" + if self.isClosed(): + raise GraphicsError("checkMouse in closed window") + self.update() + if self.mouseX != None and self.mouseY != None: + x, y = self.toWorld(self.mouseX, self.mouseY) + self.mouseX = None + self.mouseY = None + return Point(x, y) + else: + return None + + def getKey(self): + """Wait for user to press a key and return it as a string.""" + self.lastKey = "" + while self.lastKey == "": + self.update() + if self.isClosed(): raise GraphicsError("getKey in closed window") + time.sleep(.1) # give up thread + + key = self.lastKey + self.lastKey = "" + return key + + def checkKey(self): + """Return last key pressed or None if no key pressed since last call""" + if self.isClosed(): + raise GraphicsError("checkKey in closed window") + self.update() + key = self.lastKey + self.lastKey = "" + return key + + def getHeight(self): + """Return the height of the window""" + return self.height + + def getWidth(self): + """Return the width of the window""" + return self.width + + def toScreen(self, x, y): + trans = self.trans + if trans: + return self.trans.screen(x, y) + else: + return x, y + + def toWorld(self, x, y): + trans = self.trans + if trans: + return self.trans.world(x, y) + else: + return x, y + + def setMouseHandler(self, func): + self._mouseCallback = func + + def _onClick(self, e): + self.mouseX = e.x + self.mouseY = e.y + if self._mouseCallback: + self._mouseCallback(Point(e.x, e.y)) + + def addItem(self, item): + self.items.append(item) + + def delItem(self, item): + self.items.remove(item) + + def redraw(self): + for item in self.items[:]: + item.undraw() + item.draw(self) + self.update() + + +class Transform: + """Internal class for 2-D coordinate transformations""" + + def __init__(self, w, h, xlow, ylow, xhigh, yhigh): + # w, h are width and height of window + # (xlow,ylow) coordinates of lower-left [raw (0,h-1)] + # (xhigh,yhigh) coordinates of upper-right [raw (w-1,0)] + xspan = (xhigh - xlow) + yspan = (yhigh - ylow) + self.xbase = xlow + self.ybase = yhigh + self.xscale = xspan / float(w - 1) + self.yscale = yspan / float(h - 1) + + def screen(self, x, y): + # Returns x,y in screen (actually window) coordinates + xs = (x - self.xbase) / self.xscale + ys = (self.ybase - y) / self.yscale + return int(xs + 0.5), int(ys + 0.5) + + def world(self, xs, ys): + # Returns xs,ys in world coordinates + x = xs * self.xscale + self.xbase + y = self.ybase - ys * self.yscale + return x, y + + +# Default values for various item configuration options. Only a subset of +# keys may be present in the configuration dictionary for a given item +DEFAULT_CONFIG = {"fill": "", + "outline": "black", + "width": "1", + "arrow": "none", + "text": "", + "justify": "center", + "font": ("helvetica", 12, "normal")} + + +class GraphicsObject: + """Generic base class for all of the drawable objects""" + + # A subclass of GraphicsObject should override _draw and + # and _move methods. + + def __init__(self, options): + # options is a list of strings indicating which options are + # legal for this object. + + # When an object is drawn, canvas is set to the GraphWin(canvas) + # object where it is drawn and id is the TK identifier of the + # drawn shape. + self.canvas = None + self.id = None + + # config is the dictionary of configuration options for the widget. + config = {} + for option in options: + config[option] = DEFAULT_CONFIG[option] + self.config = config + + def setFill(self, color): + """Set interior color to color""" + self._reconfig("fill", color) + + def setOutline(self, color): + """Set outline color to color""" + self._reconfig("outline", color) + + def setWidth(self, width): + """Set line weight to width""" + self._reconfig("width", width) + + def draw(self, graphwin): + + """Draw the object in graphwin, which should be a GraphWin + object. A GraphicsObject may only be drawn into one + window. Raises an error if attempt made to draw an object that + is already visible.""" + + if self.canvas and not self.canvas.isClosed(): raise GraphicsError(OBJ_ALREADY_DRAWN) + if graphwin.isClosed(): raise GraphicsError("Can't draw to closed window") + self.canvas = graphwin + self.id = self._draw(graphwin, self.config) + graphwin.addItem(self) + if graphwin.autoflush: + _root.update() + return self + + def undraw(self): + + """Undraw the object (i.e. hide it). Returns silently if the + object is not currently drawn.""" + + if not self.canvas: return + if not self.canvas.isClosed(): + self.canvas.delete(self.id) + self.canvas.delItem(self) + if self.canvas.autoflush: + _root.update() + self.canvas = None + self.id = None + + def move(self, dx, dy): + + """move object dx units in x direction and dy units in y + direction""" + + self._move(dx, dy) + canvas = self.canvas + if canvas and not canvas.isClosed(): + trans = canvas.trans + if trans: + x = dx / trans.xscale + y = -dy / trans.yscale + else: + x = dx + y = dy + self.canvas.move(self.id, x, y) + if canvas.autoflush: + _root.update() + + def _reconfig(self, option, setting): + # Internal method for changing configuration of the object + # Raises an error if the option does not exist in the config + # dictionary for this object + if option not in self.config: + raise GraphicsError(UNSUPPORTED_METHOD) + options = self.config + options[option] = setting + if self.canvas and not self.canvas.isClosed(): + self.canvas.itemconfig(self.id, options) + if self.canvas.autoflush: + _root.update() + + def _draw(self, canvas, options): + """draws appropriate figure on canvas with options provided + Returns Tk id of item drawn""" + pass # must override in subclass + + def _move(self, dx, dy): + """updates internal state of object to move it dx,dy units""" + pass # must override in subclass + + +class Point(GraphicsObject): + def __init__(self, x, y): + GraphicsObject.__init__(self, ["outline", "fill"]) + self.setFill = self.setOutline + self.x = float(x) + self.y = float(y) + + def __repr__(self): + return "Point({}, {})".format(self.x, self.y) + + def _draw(self, canvas, options): + x, y = canvas.toScreen(self.x, self.y) + return canvas.create_rectangle(x, y, x + 1, y + 1, options) + + def _move(self, dx, dy): + self.x = self.x + dx + self.y = self.y + dy + + def clone(self): + other = Point(self.x, self.y) + other.config = self.config.copy() + return other + + def getX(self): return self.x + + def getY(self): return self.y + + +class _BBox(GraphicsObject): + # Internal base class for objects represented by bounding box + # (opposite corners) Line segment is a degenerate case. + + def __init__(self, p1, p2, options=["outline", "width", "fill"]): + GraphicsObject.__init__(self, options) + self.p1 = p1.clone() + self.p2 = p2.clone() + + def _move(self, dx, dy): + self.p1.x = self.p1.x + dx + self.p1.y = self.p1.y + dy + self.p2.x = self.p2.x + dx + self.p2.y = self.p2.y + dy + + def getP1(self): return self.p1.clone() + + def getP2(self): return self.p2.clone() + + def getCenter(self): + p1 = self.p1 + p2 = self.p2 + return Point((p1.x + p2.x) / 2.0, (p1.y + p2.y) / 2.0) + + +class Rectangle(_BBox): + def __init__(self, p1, p2): + _BBox.__init__(self, p1, p2) + + def __repr__(self): + return "Rectangle({}, {})".format(str(self.p1), str(self.p2)) + + def _draw(self, canvas, options): + p1 = self.p1 + p2 = self.p2 + x1, y1 = canvas.toScreen(p1.x, p1.y) + x2, y2 = canvas.toScreen(p2.x, p2.y) + return canvas.create_rectangle(x1, y1, x2, y2, options) + + def clone(self): + other = Rectangle(self.p1, self.p2) + other.config = self.config.copy() + return other + + +class Oval(_BBox): + def __init__(self, p1, p2): + _BBox.__init__(self, p1, p2) + + def __repr__(self): + return "Oval({}, {})".format(str(self.p1), str(self.p2)) + + def clone(self): + other = Oval(self.p1, self.p2) + other.config = self.config.copy() + return other + + def _draw(self, canvas, options): + p1 = self.p1 + p2 = self.p2 + x1, y1 = canvas.toScreen(p1.x, p1.y) + x2, y2 = canvas.toScreen(p2.x, p2.y) + return canvas.create_oval(x1, y1, x2, y2, options) + + +class Circle(Oval): + def __init__(self, center, radius): + p1 = Point(center.x - radius, center.y - radius) + p2 = Point(center.x + radius, center.y + radius) + Oval.__init__(self, p1, p2) + self.radius = radius + + def __repr__(self): + return "Circle({}, {})".format(str(self.getCenter()), str(self.radius)) + + def clone(self): + other = Circle(self.getCenter(), self.radius) + other.config = self.config.copy() + return other + + def getRadius(self): + return self.radius + + +class Line(_BBox): + def __init__(self, p1, p2): + _BBox.__init__(self, p1, p2, ["arrow", "fill", "width"]) + self.setFill(DEFAULT_CONFIG['outline']) + self.setOutline = self.setFill + + def __repr__(self): + return "Line({}, {})".format(str(self.p1), str(self.p2)) + + def clone(self): + other = Line(self.p1, self.p2) + other.config = self.config.copy() + return other + + def _draw(self, canvas, options): + p1 = self.p1 + p2 = self.p2 + x1, y1 = canvas.toScreen(p1.x, p1.y) + x2, y2 = canvas.toScreen(p2.x, p2.y) + return canvas.create_line(x1, y1, x2, y2, options) + + def setArrow(self, option): + if not option in ["first", "last", "both", "none"]: + raise GraphicsError(BAD_OPTION) + self._reconfig("arrow", option) + + +class Polygon(GraphicsObject): + def __init__(self, *points): + # if points passed as a list, extract it + if len(points) == 1 and type(points[0]) == type([]): + points = points[0] + self.points = list(map(Point.clone, points)) + GraphicsObject.__init__(self, ["outline", "width", "fill"]) + + def __repr__(self): + return "Polygon" + str(tuple(p for p in self.points)) + + def clone(self): + other = Polygon(*self.points) + other.config = self.config.copy() + return other + + def getPoints(self): + return list(map(Point.clone, self.points)) + + def _move(self, dx, dy): + for p in self.points: + p.move(dx, dy) + + def _draw(self, canvas, options): + args = [canvas] + for p in self.points: + x, y = canvas.toScreen(p.x, p.y) + args.append(x) + args.append(y) + args.append(options) + return GraphWin.create_polygon(*args) + + +class Text(GraphicsObject): + def __init__(self, p, text): + GraphicsObject.__init__(self, ["justify", "fill", "text", "font"]) + self.setText(text) + self.anchor = p.clone() + self.setFill(DEFAULT_CONFIG['outline']) + self.setOutline = self.setFill + + def __repr__(self): + return "Text({}, '{}')".format(self.anchor, self.getText()) + + def _draw(self, canvas, options): + p = self.anchor + x, y = canvas.toScreen(p.x, p.y) + return canvas.create_text(x, y, options) + + def _move(self, dx, dy): + self.anchor.move(dx, dy) + + def clone(self): + other = Text(self.anchor, self.config['text']) + other.config = self.config.copy() + return other + + def setText(self, text): + self._reconfig("text", text) + + def getText(self): + return self.config["text"] + + def getAnchor(self): + return self.anchor.clone() + + def setFace(self, face): + if face in ['helvetica', 'arial', 'courier', 'times roman']: + f, s, b = self.config['font'] + self._reconfig("font", (face, s, b)) + else: + raise GraphicsError(BAD_OPTION) + + def setSize(self, size): + if 5 <= size <= 36: + f, s, b = self.config['font'] + self._reconfig("font", (f, size, b)) + else: + raise GraphicsError(BAD_OPTION) + + def setStyle(self, style): + if style in ['bold', 'normal', 'italic', 'bold italic']: + f, s, b = self.config['font'] + self._reconfig("font", (f, s, style)) + else: + raise GraphicsError(BAD_OPTION) + + def setTextColor(self, color): + self.setFill(color) + + +class Entry(GraphicsObject): + def __init__(self, p, width): + GraphicsObject.__init__(self, []) + self.anchor = p.clone() + # print self.anchor + self.width = width + self.text = tk.StringVar(_root) + self.text.set("") + self.fill = "gray" + self.color = "black" + self.font = DEFAULT_CONFIG['font'] + self.entry = None + + def __repr__(self): + return "Entry({}, {})".format(self.anchor, self.width) + + def _draw(self, canvas, options): + p = self.anchor + x, y = canvas.toScreen(p.x, p.y) + frm = tk.Frame(canvas.master) + self.entry = tk.Entry(frm, + width=self.width, + textvariable=self.text, + bg=self.fill, + fg=self.color, + font=self.font) + self.entry.pack() + # self.setFill(self.fill) + self.entry.focus_set() + return canvas.create_window(x, y, window=frm) + + def getText(self): + return self.text.get() + + def _move(self, dx, dy): + self.anchor.move(dx, dy) + + def getAnchor(self): + return self.anchor.clone() + + def clone(self): + other = Entry(self.anchor, self.width) + other.config = self.config.copy() + other.text = tk.StringVar() + other.text.set(self.text.get()) + other.fill = self.fill + return other + + def setText(self, t): + self.text.set(t) + + def setFill(self, color): + self.fill = color + if self.entry: + self.entry.config(bg=color) + + def _setFontComponent(self, which, value): + font = list(self.font) + font[which] = value + self.font = tuple(font) + if self.entry: + self.entry.config(font=self.font) + + def setFace(self, face): + if face in ['helvetica', 'arial', 'courier', 'times roman']: + self._setFontComponent(0, face) + else: + raise GraphicsError(BAD_OPTION) + + def setSize(self, size): + if 5 <= size <= 36: + self._setFontComponent(1, size) + else: + raise GraphicsError(BAD_OPTION) + + def setStyle(self, style): + if style in ['bold', 'normal', 'italic', 'bold italic']: + self._setFontComponent(2, style) + else: + raise GraphicsError(BAD_OPTION) + + def setTextColor(self, color): + self.color = color + if self.entry: + self.entry.config(fg=color) + + +class Image(GraphicsObject): + idCount = 0 + imageCache = {} # tk photoimages go here to avoid GC while drawn + + def __init__(self, p, *pixmap): + GraphicsObject.__init__(self, []) + self.anchor = p.clone() + self.imageId = Image.idCount + Image.idCount = Image.idCount + 1 + if len(pixmap) == 1: # file name provided + self.img = tk.PhotoImage(file=pixmap[0], master=_root) + else: # width and height provided + width, height = pixmap + self.img = tk.PhotoImage(master=_root, width=width, height=height) + + def __repr__(self): + return "Image({}, {}, {})".format(self.anchor, self.getWidth(), self.getHeight()) + + def _draw(self, canvas, options): + p = self.anchor + x, y = canvas.toScreen(p.x, p.y) + self.imageCache[self.imageId] = self.img # save a reference + return canvas.create_image(x, y, image=self.img) + + def _move(self, dx, dy): + self.anchor.move(dx, dy) + + def undraw(self): + try: + del self.imageCache[self.imageId] # allow gc of tk photoimage + except KeyError: + pass + GraphicsObject.undraw(self) + + def getAnchor(self): + return self.anchor.clone() + + def clone(self): + other = Image(Point(0, 0), 0, 0) + other.img = self.img.copy() + other.anchor = self.anchor.clone() + other.config = self.config.copy() + return other + + def getWidth(self): + """Returns the width of the image in pixels""" + return self.img.width() + + def getHeight(self): + """Returns the height of the image in pixels""" + return self.img.height() + + def getPixel(self, x, y): + """Returns a list [r,g,b] with the RGB color values for pixel (x,y) + r,g,b are in range(256) + + """ + + value = self.img.get(x, y) + if type(value) == type(0): + return [value, value, value] + elif type(value) == type((0, 0, 0)): + return list(value) + else: + return list(map(int, value.split())) + + def setPixel(self, x, y, color): + """Sets pixel (x,y) to the given color + + """ + self.img.put("{" + color + "}", (x, y)) + + def save(self, filename): + """Saves the pixmap image to filename. + The format for the save image is determined from the filname extension. + + """ + + path, name = os.path.split(filename) + ext = name.split(".")[-1] + self.img.write(filename, format=ext) + + +def color_rgb(r, g, b): + """r,g,b are intensities of red, green, and blue in range(256) + Returns color specifier string for the resulting color""" + return "#%02x%02x%02x" % (r, g, b) + + +def test(): + win = GraphWin() + win.setCoords(0, 0, 10, 10) + t = Text(Point(5, 5), "Centered Text") + t.draw(win) + p = Polygon(Point(1, 1), Point(5, 3), Point(2, 7)) + p.draw(win) + e = Entry(Point(5, 6), 10) + e.draw(win) + win.getMouse() + p.setFill("red") + p.setOutline("blue") + p.setWidth(2) + s = "" + for pt in p.getPoints(): + s = s + "(%0.1f,%0.1f) " % (pt.getX(), pt.getY()) + t.setText(e.getText()) + e.setFill("green") + e.setText("Spam!") + e.move(2, 0) + win.getMouse() + p.move(2, 3) + s = "" + for pt in p.getPoints(): + s = s + "(%0.1f,%0.1f) " % (pt.getX(), pt.getY()) + t.setText(s) + win.getMouse() + p.undraw() + e.undraw() + t.setStyle("bold") + win.getMouse() + t.setStyle("normal") + win.getMouse() + t.setStyle("italic") + win.getMouse() + t.setStyle("bold italic") + win.getMouse() + t.setSize(14) + win.getMouse() + t.setFace("arial") + t.setSize(20) + win.getMouse() + win.close() + + +# MacOS fix 2 +# tk.Toplevel(_root).destroy() + +# MacOS fix 1 +update() + +if __name__ == "__main__": + test() \ No newline at end of file