How to undraw plot with Zelle graphics? - python

This is a code problem for Python 3.5.2 using John Zelle's graphics.py:
I have spent a good amount of time looking for the answer here, but just can not figure it out. The function undraw() exists just like getMouse(). But it seems like it do not work for the plot() command, only the draw() command. What am I doing wrong? And how can I keep the window open, but erase the previous the plot before the next one is drawn?
pdf documentation for the functions of graphics:
http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf
win = GraphWin("Plot",500,500) # Creates a window
for m in range(0,j): # Loop for each function
# Randomizes a color for each function
color = random.choice( ['red','black','green','yellow','pink','blue'] )
for h in range(0,t): # Loop for each pair of values "x,y"
# Find points and plot each point in win
win.plot(axis[h],points[m][h],color)
win.getMouse() # Pause before clicking
win.undraw() # AttributeError: 'GraphWin' object has no attribute 'undraw'

The first issue is that undraw() is a method of GraphicsObject, not GraphWin, so win.undraw() is simply incorrect.
The second issue is that plot() is a low level pixel setting method that does not keep track of what it did at the Zelle Graphics level, unlike objects that are drawn.
However, the underpinning is Tkinter which does keep track of objects that it draws, and GraphWin is a subclass of Canvas, so you can do:
win = GraphWin("Plot", 500, 500) # Creates a window
for m in range(j): # Loop for each function
color = random.choice(['red', 'black', 'green', 'yellow', 'pink', 'blue']) # Randomizes a color for each function
for h in range(t): # Loop for each pair of values "x, y"
win.plot(axis[h], points[m][h], color) # Find points and plot each point in win
win.getMouse() # Pause before clicking
win.delete("all") # Clear out old plot

Related

How can I get the color that is at a set of coordinates in python turtle?

With python, I'm trying to make a function that gets the colour that the turtle is currently on top of. I've found that turtle.pos() gives me coordinates of its location, how can I use those coordinates to get colours?
I think the best way to do this would be using the steps shown here: https://www.kite.com/python/answers/how-to-find-the-rgb-value-of-a-pixel-in-python
One way to go about this is to append every turtle you create into a list, then in your main game loop, loop through the list of turtle to see if the turtle.distance() is smaller than a certain value:
import turtle
wn = turtle.Screen()
turtles = []
yertle = turtle.Turtle('turtle')
while True:
wn.tracer(0)
for t in turtles:
if yertle.distance(t) < 20 and yertle != t:
print(f'Touched {t.color()}')
wn.update()
In the above code I used if yertle.distance(t) < 20 which is for when all the turtles are the default size: 20.

How to update graph in a loop?

I created a little model to show a disease could spread. I succeded in showing a different graph for each iteration but I would like to plot a single graph that gets updated at each iteration and shows how the particles move from one iteration to the other.
This is where i call the data i want to plot:
def plotter(population):
for people in population:
if people.status==0:
plt.scatter(people.positionx,people.positiony,c='b')
else:
if people.healthstatus==0:
plt.scatter(people.positionx,people.positiony,c='g')
if people.healthstatus==1:
plt.scatter(people.positionx,people.positiony,c='y')
if people.healthstatus==2:
plt.scatter(people.positionx,people.positiony,c='r')
this is the main where iterate the model
def main(iterations,populationsize):
popde=generator(populationsize)
population=popde[0]
dead=popde[1]
plt.ion()
for numit in range(iterations):
population=movement(population)
popde2=infection(population,populationsize,dead)
population=popde2[0]
dead=popde2[1]
populationsize=popde2[2]
plotter(population)
plt.pause(0.1)
plt.draw()
The code works perfectly fine, it's just a style issue
I tried looking for other solutions on the web but I couldn't the one that fits my problem. Thanks in advance to all those who will help!
I assume that population is a list and that the attributes positionx and positiony of the object people are scalar numbers (float or int). For future questions consider posting a minimal working example. The following code is based on this answer to a similar question.
It is not necessary to create an individual scatter plot for each person. The code below collects all x-, y-positions and colors in one timestep and plots them in a single scatter plot object. This scatter plot is created before the loop (saved to the variable sc) and then passed to the plotter function, which updates the plot properties (positions & colors) each iteration.
import matplotlib.pyplot as plt
def main(iterations,populationsize):
popde=generator(populationsize)
population=popde[0]
dead=popde[1]
plt.ion()
sc = plt.scatter([], []) # initialise empty scatter plot
for numit in range(iterations):
population=movement(population)
popde2=infection(population,populationsize,dead)
population=popde2[0]
dead=popde2[1]
populationsize=popde2[2]
plotter(population, sc) # pass scatter object to plotter function
plt.draw() # update the current figure
plt.pause(0.1)
def plotter(population, sc):
# create list of all x- and y-positions
posx = [people.positionx for people in population]
posy = [people.positiony for people in population]
# create list of colors
color = ['#88888']*len(population) # initialise all colors as grey
for ip, people in enumerate(population):
if people.status == 0:
color[ip] = 'b'
elif people.healthstatus == 0:
color[ip] = 'g'
elif people.healthstatus == 1:
color[ip] = 'y'
elif people.healthstatus == 2:
color[ip] = 'r'
# if none of these criteria match, the marker remains grey
sc.set_offsets(np.c_[posx, posy]) # update marker positions
sc.set_color(color) # update marker color
As a side note, I would recommend to use the "object-oriented coding style" of matplotlib when writing scripts or functions. The above code always plots in the currently active figure, so if you have previously created another figure or the active figure changes while the code is running you might suddenly be plotting in the wrong figure.

Tetris [PyGame] - Let the shapes fall

I'm trying to clone the Tetris game and already have PyGame pick a random shape and display it. I drew an array-backed grid and 'told' PyGame to draw colored squares in certain cells in order to get the cells.
def iShape():
grid [0][5] = 3
grid [0][6] = 3
grid [0][7] = 3
grid [0][8] = 3
pygame.init()
this tells the system on which cell of the grid it will draw the square in order to get the shape.
def draw():
allShapes = ['''all Shapes that I defined''']
pick = random.choice (allShapes)
... #otherstuff
if pick == iShape:
if grid[row][column] == 3:
color = orange
#draw the squares
I have been trying to think of how I could let the shapes fall slowly so the player can move/rotate them before they hit the ground but none of my ideas work out. Does anyone have a suggestion?
try to create a def called clock or tick(anything) and have that control the drop speed. or you could youse the in built python timer by doin inport mathand there is a command to have times so you could have then drop a pice of the grid every second or something like that sry about the brightness
I've found a Tetris clone called Tetromino online. I can't quite say it will work as it probably uses a different code style, but you could get an idea from it. Its at the link https://inventwithpython.com/pygame/chapter7.html

Is there any way I could improve the speed of this Python Program?

I re-created the game Lights Out using Python and Tkinter and as far as I know there are no bugs but it is very slow especially if you set the grid size to be much higher then 10x10 (I have a slider in game that lets you do this.) I was just wondering if you had any ideas on how I could get it to run faster.
#Nicholas Eckstein
#Lights Out
#11/20/14
import random
import math
from tkinter import *
from tkinter import ttk
Lastx, lasty = 0,0
GridSize="410x520"
def reset():#Resets the grid size to the size set in the slider and randomizes cells.
global grid
global GridSize
gridMaker()
canvas.delete("all")#Clears the screen
ResetMin=math.trunc(len(grid)/3) ##Picks random amount of cells to switch states.
ResetMax=math.trunc(len(grid)/2) #Amount chosen is relative to the grid size.
ResetAmount=random.randint(ResetMin,ResetMax) ##(Random amount in between 1/2 and 1/3 of the cells.
iterate=0
while iterate<ResetAmount:#Picks random cells to switch states until iterate==ResetAmount
#cell=random.choice(grid)#All cells exist in a list of lists called grid.
#cell.pop(2) #A single list inside the Grid List is a cell.
#cell.append(1) #This Cell consists of 2 ranges and a state. [range(105, 125), range(5, 25), 0]
#iterate+=1 #The first range is the width of the cell, the second range is for the height, and the last number is for the state.
#The grid list looks something like this: [[range(105, 125), range(5, 25), 0], [range(125, 145), range(5, 25), 0], [range(145, 165), range(5, 25), 0]...]
cell=random.choice(grid)
cellx=cell[0][5]
celly=cell[1][5]
iterate+=1
CellSwitcher(cellx,celly)
GridSize=str((len(grid)/2)*20)+"x"+str(((len(grid)/2)*20)+110)#This sets the gridsize to the size determined by the slider
art()
def art():#Takes the information from the Grid list and "draws" the cells.
for cell in grid:
if cell[2]==1:
canvas.create_rectangle(cell[0][0],cell[1][0],cell[0][19],cell[1][19],fill="white")
canvas.create_rectangle(cell[0][0]+2,cell[1][0]+2,cell[0][19],cell[1][19],fill="black",outline="black")
else:
canvas.create_rectangle(cell[0][0],cell[1][0],cell[0][19],cell[1][19],fill="black")
canvas.create_rectangle(cell[0][0]+2,cell[1][0]+2,cell[0][19],cell[1][19],fill="white",outline="white")
def xy(event):#Takes the position of the mouse click
global lastx, lasty
lastx, lasty = event.x, event.y
CellSwitcher(lastx,lasty)
def CellSwitcher(lastx,lasty):#Switches the states of the cells neighboring the cell you clicked.
for coord in grid:
if lastx in coord[0] and lasty in coord[1]:
if coord[2]==0:
coord.pop(2)
coord.append(1)
else:
coord.pop(2)
coord.append(0)
if [coord[0],range(coord[1][0]+20,coord[1][19]+21),0] in grid: ####
grid[grid.index([coord[0],range(coord[1][0]+20,coord[1][19]+21),0])].pop(2) #
grid[grid.index([coord[0],range(coord[1][0]+20,coord[1][19]+21)])].append(1) #
elif [coord[0],range(coord[1][0]+20,coord[1][19]+21),1] in grid: # Switch Top Neighbor's state
grid[grid.index([coord[0],range(coord[1][0]+20,coord[1][19]+21),1])].pop(2) #
grid[grid.index([coord[0],range(coord[1][0]+20,coord[1][19]+21)])].append(0) #
####
if [coord[0],range(coord[1][0]-20,coord[1][19]-19),0] in grid: ####
grid[grid.index([coord[0],range(coord[1][0]-20,coord[1][19]-19),0])].pop(2) #
grid[grid.index([coord[0],range(coord[1][0]-20,coord[1][19]-19)])].append(1) #
elif [coord[0],range(coord[1][0]-20,coord[1][19]-19),1] in grid: # Switch Bottom Neighbor's state
grid[grid.index([coord[0],range(coord[1][0]-20,coord[1][19]-19),1])].pop(2) #
grid[grid.index([coord[0],range(coord[1][0]-20,coord[1][19]-19)])].append(0) #
####
if [range(coord[0][0]+20,coord[0][19]+21),coord[1],0] in grid: ####
grid[grid.index([range(coord[0][0]+20,coord[0][19]+21),coord[1],0])].pop(2) #
grid[grid.index([range(coord[0][0]+20,coord[0][19]+21),coord[1]])].append(1) #
elif [range(coord[0][0]+20,coord[0][19]+21),coord[1],1] in grid: # Switch Right Neighbor's state
grid[grid.index([range(coord[0][0]+20,coord[0][19]+21),coord[1],1])].pop(2) #
grid[grid.index([range(coord[0][0]+20,coord[0][19]+21),coord[1]])].append(0) #
####
if [range(coord[0][0]-20,coord[0][19]-19),coord[1],0] in grid: ####
grid[grid.index([range(coord[0][0]-20,coord[0][19]-19),coord[1],0])].pop(2) #
grid[grid.index([range(coord[0][0]-20,coord[0][19]-19),coord[1]])].append(1) #
elif [range(coord[0][0]-20,coord[0][19]-19),coord[1],1] in grid: # Switch Left Neighbor's state
grid[grid.index([range(coord[0][0]-20,coord[0][19]-19),coord[1],1])].pop(2) #
grid[grid.index([range(coord[0][0]-20,coord[0][19]-19),coord[1]])].append(0) #
####
art()
root = Tk()#Create the window
root.geometry(GridSize)#Set Window Size
root.resizable(0,0)#Stop people from resizing the window
root.title("Lights Out")
canvas = Canvas(root,background=root.cget('bg'))#Create the part of the window that draws the grid
canvas.bind("<Button-1>", xy)#Detect clicking and send coordinates of mouse
canvas.pack(fill=BOTH, expand=YES)#Resize canvas to window size and allign.
SizeLabel = Label(root,text="Grid Size")#Write the "reset" label
SizeLabel.pack()#Allign Label
Size = Scale(root,orient=HORIZONTAL,length=400,width=20,sliderlength=60,from_=1,to=20,tickinterval=1)#Create, orientate, and set the size of the slider
Size.set(10)#Set starting position for slider
Size.pack()#Allign Slider
Reset = Button(root,text ="Reset",command = reset)#Create the reset button
Reset.pack()#Allign the reset button
def gridMaker():#This function creates the grid list.
global grid
grid=[]
xCoord=205-(int(math.trunc(Size.get())/2)*20)#Centers the grid
yCoord=5
iterate=0
while yCoord<Size.get()*20:
grid.append([range(xCoord,xCoord+20),range(yCoord, yCoord+20),0])#Adds a cell to the grid list with the ranges based on what xCoord and yCoord are.
if Size.get()%2==1:#Tests to see if the grid size is odd or even
if xCoord<205+(int(math.trunc(Size.get())/2)*20):
xCoord+=20
else:
xCoord=205-(int(math.trunc(Size.get())/2)*20)
yCoord+=20
else:
if xCoord<205+(int(math.trunc(Size.get())/2)*20)-20:
xCoord+=20
else:
xCoord=205-(int(math.trunc(Size.get())/2)*20)
yCoord+=20
gridMaker()#Draws the grid
reset()#Adds Randomizes Cell States
root.mainloop()
Your CellSwitcher function iterates over all items in the cell, when it eventually only modifies nine cells (itself and its 8 neighbors), right? Why iterate over every single cell? If you know the cell that was clicked (eg: row 3, column 2) you can easily compute the neighboring cells. So, part of the answer is to remove the iteration over all of the cells and replace it with a direct lookup of the clicked-on cell and its neighbors.
Also, your reset function calls CellSwitcher which seems like overkill. If you're randomly setting the color of each cell, why go through CellSwitcher, since it changes the colors of all its neighbors?
Perhaps the biggest culprit is that you are recreating all of the canvas objects on each call to CellSwitcher, without deleting any of the old objects. There's no reason to do that -- create all of the canvas objects just once and then change them with the itemconfig method of the canvas.
The canvas has performance problems when you have lots of items. In your case, after the GUI first comes up you've already created 9800 canvas items. Click on a single cell and the canvas now has 10,200 items. And so on. The canvas can pretty easily handle thousands of items, even tens of thousands. However, when I move the slider to 20 you end up creating a whopping 125,600 objects on the canvas which will definitely cause the canvas to under-perform.

Tkinter canvas updating speed reduces during the course of a program

The following python program creates a Tkinter Canvas object and draws random matrix on it.
It also measures the time it takes to make 10 subsequent updates. As you may see from the
output below, this time grows continiously and substantially during the course of
the program. What is the reason for this behavior and how can I fix it?
from Tkinter import Tk, Canvas
import time
import numpy as np
window = Tk()
nRows = 30
nCols = 30
CELL_SIZE = 10
canvas = Canvas(window, width=CELL_SIZE*nRows,
height=CELL_SIZE*nCols)
canvas.pack()
def drawbox(m):
for y in range(nRows):
for x in range(nCols):
if m[y][x]:
color = '#00FF00'
else:
color = '#000000'
canvas.create_rectangle(CELL_SIZE*x,
CELL_SIZE*y,
CELL_SIZE*x+CELL_SIZE,
CELL_SIZE*y+CELL_SIZE,
fill=color,
outline="#000000", width=1)
count = 0
timeStart = time.time()
while(True):
board = np.random.rand(nRows, nCols) > 0.5
if count % 10 == 0:
print '%.1f seconds'%(time.time() - timeStart)
timeStart = time.time()
count = 0
count += 1
drawbox(board)
canvas.after(5)
canvas.update()
Here is the output
0.0 seconds
1.7 seconds
4.1 seconds
6.3 seconds
8.7 seconds
You create new items at each updates. The canvas display all the rectangles you have previously added and thus go slower and slower (each update create 900 rectangles, after 30 you have 27,000 objects in your scene...)
To avoid this, you may create your rectangles once, and then only update their colors.
You could have at toplevel:
rectangles = [ [ canvas.create_rectangle (CELL_SIZE*x, CELL_SIZE*y,
CELL_SIZE*x+CELL_SIZE, CELL_SIZE*y+CELL_SIZE,
fill="#000000",outline="#000000", width=1)
for x in range(nCols)] for y in range(nRows)]
and in drawbox:
canvas.itemconfig(rectangles[y][x], fill=color)
Every time drawbox is called in your program, you're creating a new set of rectangles and then drawing them on top of the old rectangles. As time goes on, you're drawing more and more rectangles (even though it doesn't look like it since the new rectangles are being drawn above the old ones). Also note that with the way your program is written, you're bleeding memory.
The way to fix this is to create the rectangles on the first go-around and then update them on the subsequent passes using canvas.itemconfig(rectangle_id,fill=color). I've posted an (ugly) modification to your drawbox below which accomplishes this.
def drawbox(m,_rectangles={}):
if(_rectangles):
myrectangles=_rectangles
else:
myrectangles={}
for y in range(nRows):
for x in range(nCols):
if m[y][x]:
color = '#00FF00'
else:
color = '#000000'
if(not _rectangles):
cid=canvas.create_rectangle(CELL_SIZE*x,
CELL_SIZE*y,
CELL_SIZE*x+CELL_SIZE,
CELL_SIZE*y+CELL_SIZE,
fill=color,
outline="#000000", width=1)
myrectangles[(y,x)]=cid
else:
canvas.itemconfig(_rectangles[(y,x)],fill=color)
if(not _rectangles):
_rectangles.update(myrectangles)
The canvas is known to be slow the more items you add (though it can typcially handle 1000's or 10's of 1000's without too much problem). You have a couple of problems. One, as other answers have pointed out, is that you keep creating more and more objects. By reusing the existing objects and updating their coordinates and colors you should see a dramatic improvement in speed.
The second problem is your infinite loop and your sleep (canvas.after(5)). There's a much better way to achieve the effect without the annoying side effect of your GUI freezing for 5 ms at a time.
All you need to do is create a function that draws or updates the objects, then puts an event on the queue to call itself again after some period of time. It will then automatically update without you having to explicitly create a loop.
For example:
def redraw():
board = np.random.rand(nRows, nCols) > 0.5
drawbox(board)
canvas.after(100, redraw)

Categories