I'm trying to update the pixel size of my grid once I press the zoom in or zoom out buttons. When I click on the zoom in, the pixel size should increase it's size by one, when I zoom out, the pixel size should decrease by 1. I'm having trouble updating the grid without deleting what has been drawn in it. This is my code
from tkinter import *
from tkinter import colorchooser
import math
PixelSize = 10
class Grilla:
colorCelda = "black"
colorDefault = "white"
colorBorde = "black"
bordeDefault = "black"
def __init__(self, root, master, x, y, size):
self.master = master
self.abs = x
self.ord = y
self.size = size
self.fill = False
def switch(self):
self.fill = not self.fill
def reset(self):
self.fill = False
def draw(self):
if self.master is not None:
outline = Grilla.colorBorde
fill = Grilla.colorCelda
if not self.fill:
outline = Grilla.bordeDefault
fill = Grilla.colorDefault
xmin = self.abs * self.size
xmax = xmin + self.size
ymin = self.ord * self.size
ymax = ymin + self.size
self.master.create_rectangle(xmin, ymin, xmax, ymax, fill=fill, outline=outline)
class CellGrilla(Canvas):
def __init__(self, master, numFil, numCol, tamGrid, *args, **kwargs):
Canvas.__init__(self, master, width=tamGrid * numCol, height=tamGrid * numFil, *args, **kwargs)
self.bind("<Button-1>", self.square_clicked)
self.cellSize = tamGrid
self.pen = "draw"
self._grid = []
for row in range(numFil):
line = []
for column in range(numCol):
line.append(Grilla(master, self, column, row, tamGrid))
self._grid.append(line)
self.switched = []
self.draw()
def square_clicked(self, event):
row, column = self._coordenadas(event)
cell = self._grid[row][column]
if self.pen == "draw":
cell.switch()
if cell.fill:
self.switched.append(cell)
else:
self.switched.remove(cell)
cell.draw()
def draw(self):
for row in self._grid:
for cell in row:
cell.draw()
def _coordenadas(self, event):
row = event.y // self.cellSize
column = event.x // self.cellSize
return row, column
def switch_to_draw(self):
self.pen = "draw"
def color(self):
colorSelec = colorchooser.askcolor()[1]
if colorSelec:
Grilla.colorCelda = colorSelec
Grilla.colorBorde = colorSelec
def clear(self):
self.switched = []
for row in self._grid:
for cell in row:
cell.reset()
cell.draw()
def DDA(self):
x1 = self.switched[0].abs
x2 = self.switched[1].abs
y1 = self.switched[0].ord
y2 = self.switched[1].ord
length = abs(x2 - x1) if abs(x2 - x1) > abs(y2 - y1) else abs(y2 - y1)
dx = abs(x1 - x2) / float(length)
dy = abs(y1 - y2) / float(length)
x, y = x1, y1
for i in range(length):
if x1 < x2:
x += dx
else:
x -= dx
if y1 < y2:
y += dy
else:
y -= dy
cell = self._grid[int(y)][int(x)]
cell.switch()
cell.draw()
def bresenhamLine(self):
x1 = self.switched[0].abs
x2 = self.switched[1].abs
y1 = self.switched[0].ord
y2 = self.switched[1].ord
dx = x2 - x1
dy = y2 - y1
D = 2 * dy - dx
x, y = x1, y1
for x in range(x1 + 1, x2 + 1):
if D > 0:
y += 1
D += (2 * dy - 2 * dx)
else:
D += 2 * dy
cell = self._grid[int(y)][int(x)]
cell.switch()
cell.draw()
def bresenhamCircle(self):
x1 = self.switched[0].abs
x2 = self.switched[1].abs
y1 = self.switched[0].ord
y2 = self.switched[1].ord
radius = int(math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2)))
x = 0
y = radius
switch = 3 - (2 * radius)
self.dibujarCirculo(x1, y1, x, y)
while x <= y:
x = x + 1
if switch < 0:
switch = switch + (4 * x) + 6
else:
switch = switch + (4 * (x - y)) + 10
y = y - 1
self.dibujarCirculo(x1, y1, x, y)
def dibujarCirculo(self, xc, yc, x, y):
self.drawPoint(xc + x, yc + y)
self.drawPoint(xc + x, yc - y)
self.drawPoint(xc + y, yc + x)
self.drawPoint(xc + y, yc - x)
self.drawPoint(xc - x, yc + y)
self.drawPoint(xc - x, yc - y)
self.drawPoint(xc - y, yc + x)
self.drawPoint(xc - y, yc - x)
def drawPoint(self, x, y):
cell = self._grid[int(y)][int(x)]
cell.switch()
cell.draw()
def Ellipse(self):
x1 = self.switched[0].abs
x2 = self.switched[1].abs
y1 = self.switched[0].ord
y2 = self.switched[1].ord
radius = int(math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2)))
x = 0
y = radius
switch = 3 - (2 * radius)
self.dibujarElipse(x1, y1, x, y)
while x <= y:
x = x + 1
if switch < 0:
switch = switch + (4 * x) + 6
else:
switch = switch + (4 * (x - y)) + 10
y = y - 1
self.dibujarElipse(x1, y1, x, y)
def dibujarElipse(self, xc, yc, x, y):
self.drawPoint(xc + x, yc + y)
self.drawPoint(xc + x, yc - y)
self.drawPoint(xc + y, yc + x)
self.drawPoint(xc + y, yc - x)
self.drawPoint(xc - x, yc + y)
self.drawPoint(xc - x, yc - y)
self.drawPoint(xc - y, yc + x)
self.drawPoint(xc - y, yc - x)
def zoomIn(self):
global PixelSize
if PixelSize >= 10:
PixelSize = PixelSize + 1
def zoomOut(self):
global PixelSize
if PixelSize > 11:
PixelSize = PixelSize - 1
# def floodFill(self, grid, fila, columna, nuevoColor):
# color = grid[fila][columna]
if __name__ == "__main__":
app = Tk()
grid = CellGrilla(app, 75, 75, PixelSize)
grid.grid(row=1, column=1, rowspan=4, sticky="news")
colorBoton = Button(app, text="Elegir color", command=grid.color, height=1, width=30)
ddaBoton = Button(app, text="DDA", command=grid.DDA, height=1, width=30)
zoomInBoton = Button(app, text="Zoom in", command=grid.zoomIn, height=1, width=30)
zoomOutBoton = Button(app, text="Zoom out", command=grid.zoomOut, height=1, width=30)
reset_btn = Button(app, text="Borrar", command=grid.clear, height=1, width=30)
BresenhamLine = Button(app, text="Bresenham Line", command=grid.bresenhamLine, height=1, width=30)
BresenhamCircle = Button(app, text="Bresenham Circle", command=grid.bresenhamCircle, height=1, width=30)
Ellipse = Button(app, text="Elipse", command=grid.bresenhamCircle, height=1, width=30)
# FloodFill = Button(app, text="Rellenar", command=grid.bresenhamCircle, height=1, width=30)
zoomInBoton.grid(row=1, column=2, sticky="news")
zoomOutBoton.grid(row=2, column=2, sticky="news")
colorBoton.grid(row=3, column=2, sticky="news")
ddaBoton.grid(row=4, column=2, sticky="news")
reset_btn.grid(row=1, column=3, sticky="news")
BresenhamLine.grid(row=2, column=3, sticky="news")
BresenhamCircle.grid(row=3, column=3, sticky="news")
Ellipse.grid(row=4, column =3, sticky="news")
# FloodFill.grid(row=1, column=4, sticky="news")
app.mainloop()
Related
I am having a problem solving this problem, I need to create a connection between the L1 label (coordinates n) and for loop (it's marked in code with "n counter" ), so when I select in tkinter window from 0 to 5 and on the click of the button "display" to show a right image of Von Koch snowflake, and a custom starting line a(xa,ya) and b(xb,yb) in python.
from matplotlib import pyplot as plt
import numpy as np
from tkinter import *
import tkinter
top = tkinter.Tk()
top.geometry("400x400")
L1 = Label(top, text="Coordinates of n")
L1.pack(side=TOP)
L1 = Label(top)
L1.pack( side=TOP)
n = StringVar()
n1 = Entry(top, textvariable=n)
n1.pack(side=TOP)
def Nacrtaj():
class Segment:
def __init__(self):
self.x1 = 0
self.x2 = 0
self.y1 = 0
self.y2 = 0
def display(self):
x=[self.x1, self.x2]
y=[self.y1, self.y2]
plt.axis('equal')
plt.plot(x,y,"-", lw=0.5, color='k')
def breakSegment(self, n):
x=np.zeros(n+1)
y=np.zeros(n+1)
dx=(self.x2-self.x1)/n
dy=(self.y2-self.y1)/n
for i in range(n+1):
x[i]=self.x1+dx*i
y[i]=self.y1+dy*i
return x,y
def rotation(x, y, phi):
phi=phi*np.pi/180.
dx=x[1]-x[0]
dy=y[1]-y[0]
xr=x[0]+dx*np.cos(phi)-dy*np.sin(phi)
yr=y[0]+dx*np.sin(phi)+dy*np.cos(phi)
return xr, yr
def createSegmentList(x,y):
list_seg=[]
for i in range(len(x)-1):
seg_new=Segment()
seg_new.x1 = x[i]
seg_new.y1 = y[i]
seg_new.x2 = x[i+1]
seg_new.y2 = y[i+1]
list_seg.append(seg_new)
return list_seg
def create_shape(x,y,n):
x1=np.zeros(len(x)+1)
y1=np.zeros(len(x)+1)
x1[:n+1]=x[:n+1]
y1[:n+1]=y[:n+1]
x1[n+2:]=x[n+1:]
y1[n+2:]=y[n+1:]
x1[n+1], y1[n+1]=rotation([x[n],x[n+1]],[y[n],y[n+1]], 60)
return x1, y1
A=Segment()
A.x2=1
list_seg=[A]
for j in range(5): #n counter
list_new = []
for i in list_seg:
x,y=i.breakSegment(3)
x,y=create_shape(x,y,1)
list_sub_seg=createSegmentList(x,y)
for k in list_sub_seg:
list_new.append(k)
list_seg=list_new
for i in list_seg:
i.display()
A.display()
plt.show()
B1 = Button(top, text="Display", command=Nacrtaj)
B1.pack(side = BOTTOM)
top.mainloop()
If I inderstand problem:
You don't have value in L1 because it is only a Label but in n1 which is Entry and in n which is StringVar assigned to this Entry so you need to get int( n1.get() ) or int( n.get() )
for j in range( int(n.get()) ):
EDIT:
I would add Entry for Ax,Ay,Bx,By directly in main window to put values before clicking Button
And it needs also code which gets values after clicking Button
start_segment = Segment()
start_segment.x1 = float(entry_ax.get())
start_segment.y1 = float(entry_ay.get())
start_segment.x2 = float(entry_bx.get())
start_segment.y2 = float(entry_by.get())
Full working code:
import tkinter as tk # PEP8: `import *` is not preferred
import numpy as np
from matplotlib import pyplot as plt
# --- classes ---
class Segment:
def __init__(self):
self.x1 = 0
self.x2 = 0
self.y1 = 0
self.y2 = 0
def display(self):
x = [self.x1, self.x2]
y = [self.y1, self.y2]
plt.axis('equal')
plt.plot(x, y, "-", lw=0.5, color='k')
def break_segment(self, n):
x = np.zeros(n+1)
y = np.zeros(n+1)
dx = (self.x2 - self.x1) / n
dy = (self.y2 - self.y1) / n
for i in range(n+1):
x[i] = self.x1 + dx * i
y[i] = self.y1 + dy * i
return x, y
# --- functions ---
def rotation(x, y, phi):
phi = phi*np.pi/180.
cos_phi = np.cos(phi)
sin_phi = np.sin(phi)
dx = x[1] - x[0]
dy = y[1] - y[0]
xr = x[0] + dx*cos_phi - dy*sin_phi
yr = y[0] + dx*sin_phi + dy*cos_phi
return xr, yr
def create_segments(x, y):
all_segments = []
for i in range(len(x)-1):
segment = Segment()
segment.x1 = x[i]
segment.y1 = y[i]
segment.x2 = x[i+1]
segment.y2 = y[i+1]
all_segments.append(segment)
return all_segments
def create_shape(x, y, n):
x1 = np.zeros(len(x)+1)
y1 = np.zeros(len(x)+1)
x1[:n+1] = x[:n+1]
y1[:n+1] = y[:n+1]
x1[n+2:] = x[n+1:]
y1[n+2:] = y[n+1:]
x1[n+1], y1[n+1] = rotation([x[n], x[n+1]], [y[n], y[n+1]], 60)
return x1, y1
def display():
button['text'] = 'wait'
top.update() # force `mainloop` to display new text on button
start_segment = Segment()
start_segment.x1 = float(entry_ax.get())
start_segment.y1 = float(entry_ay.get())
start_segment.x2 = float(entry_bx.get())
start_segment.y2 = float(entry_by.get())
all_segments = [start_segment]
for j in range( int(entry.get()) ): #n counter
new_segments = []
for segment in all_segments:
x, y = segment.break_segment(3)
x, y = create_shape(x, y, 1)
all_subsegments = create_segments(x, y)
#for subsegment in all_subsegments:
# new_segments.append(subsegment)
new_segments += all_subsegments
all_segments = new_segments
for segment in all_segments:
segment.display()
#start_segment.display()
plt.show()
button['text'] = 'Display'
# --- main ---
top = tk.Tk()
#top.geometry("400x400")
label = tk.Label(top, text="Coordinates of n")
label.pack(side='top')
entry = tk.Entry(top)
entry.pack(side='top')
entry.insert('end', 2) # default value at start
# --- frame to create rows using `grid()` ---
frame = tk.Frame(top)
frame.pack()
# --- row 0 ---
label_ax = tk.Label(frame, text="ax:")
label_ax.grid(row=0, column=0)
entry_ax = tk.Entry(frame, width=5)
entry_ax.grid(row=0, column=1)
entry_ax.insert('end', 0) # default value at start
label_ay = tk.Label(frame, text="ay:")
label_ay.grid(row=0, column=2)
entry_ay = tk.Entry(frame, width=5)
entry_ay.grid(row=0, column=3)
entry_ay.insert('end', 0) # default value at start
# --- row 1 ---
label_bx = tk.Label(frame, text="bx:")
label_bx.grid(row=1, column=0)
entry_bx = tk.Entry(frame, width=5)
entry_bx.grid(row=1, column=1)
entry_bx.insert('end', 1) # default value at start
label_by = tk.Label(frame, text="by:")
label_by.grid(row=1, column=2)
entry_by = tk.Entry(frame, width=5)
entry_by.grid(row=1, column=3)
entry_by.insert('end', 0) # default value at start
# ---
button = tk.Button(top, text="Display", command=display)
button.pack(side='bottom')
top.mainloop()
Result for A(0,0) B(1,1):
Thanks to lots of help in Drag and drop the object in Tkinter UI, I could manage to draw three square that are draggable.
Now I am trying to draw 3 lines between each squares and I cannot find way to enable it. What I tried is following :
from tkinter import *
window = Tk()
window.state('zoomed')
window.configure(bg = 'white')
def drag(event):
new_x = event.x_root - window.winfo_rootx()
new_y = event.y_root - window.winfo_rooty()
event.widget.place(x=new_x, y=new_y,anchor=CENTER)
card = Canvas(window, width=10, height=10, bg='red1')
card.place(x=300, y=600,anchor=CENTER)
card.bind("<B1-Motion>", drag)
another_card = Canvas(window, width=10, height=10, bg='red2')
another_card.place(x=600, y=600,anchor=CENTER)
another_card.bind("<B1-Motion>", drag)
third_card = Canvas(window, width=10, height=10, bg='red3')
third_card.place(x=600, y=600,anchor=CENTER)
third_card.bind("<B1-Motion>", drag)
def line(x1, y1, x2, y2):
print(x1, y1, x2, y2)
Canvas.create_line(x1, y1, x2, y2, fill="green")
coor_1 = canvas.coords(card)
coor_2 = canvas.coords(another_card)
line(coor_1[0],coor_1[1],coor_1[0],coor_2[1])
window.mainloop()
It didn't work and I don't think it will work since this code does not catch the change occurred by dragging object, But I cannot guess how to code since I do not understand how the event function works completely. How should I make a code for it ?
for the purpose of self-study:
import tkinter as tk
window = tk.Tk()
window.state('zoomed')
class DragAndDropArea(tk.Canvas):
def __init__(self,master, **kwargs):
tk.Canvas.__init__(self,master, **kwargs)
self.active = None
card_I = self.draw_card(300,600, 100,100, 'red1')
card_II = self.draw_card(600,600, 100,100, 'red2')
card_III = self.draw_card(400,400, 100,100, 'red3')
self.bind_tention(card_I,card_III)
self.bind_tention(card_I,card_II)
self.bind_tention(card_III,card_II)
self.bind('<ButtonPress-1>', self.get_item)
self.bind('<B1-Motion>',self.move_active)
self.bind('<ButtonRelease-1>', self.set_none)
def set_none(self,event):
self.active = None
def get_item(self,event):
try:
item = self.find_withtag('current')
self.active = item[0]
except IndexError:
print('no item was clicked')
def move_active(self,event):
if self.active != None:
coords = self.coords(self.active)
width = coords[2] - coords[0] #x2-x1
height= coords[1] - coords[3] #y1-y2
position = coords[0],coords[1]#x1,y1
x1 = event.x - width/2
y1 = event.y - height/2
x2 = event.x + width/2
y2 = event.y + height/2
self.coords(self.active, x1,y1, x2,y2)
try:
self.update_tention(self.active)
except IndexError:
print('no tentions found')
def update_tention(self, tag):
tentions = self.find_withtag(f'card {tag}')
for tention in tentions:
bounded_cards = self.gettags(tention)
card = bounded_cards[0].split()[-1]
card2= bounded_cards[1].split()[-1]
x1,y1 = self.get_mid_point(card)
x2,y2 = self.get_mid_point(card2)
self.coords(tention, x1,y1, x2,y2)
self.lower(tention)
def draw_card(self, x,y, width,height, color):
x1,y1 = x,y
x2,y2 = x+width,y+height
reference = self.create_rectangle(x1,y1,x2,y2,
fill = color)
return reference
def bind_tention(self, card, another_card):
x1,y1 = self.get_mid_point(card)
x2,y2 = self.get_mid_point(another_card)
tag_I = f'card {card}'
tag_II= f'card {another_card}'
reference = self.create_line(x1,y1,x2,y2, fill='green',
tags=(tag_I,tag_II))
self.lower(reference)
def get_mid_point(self, card):
coords = self.coords(card)
width = coords[2] - coords[0] #x2-x1
height= coords[1] - coords[3] #y1-y2
position = coords[0],coords[1]#x1,y1
mid_x = position[0] + width/2
mid_y = position[1] - height/2
return mid_x,mid_y
area = DragAndDropArea(window, bg='white')
area.pack(fill='both',expand=1)
window.mainloop()
This program reads an image which is in location C:/Square.png and lines are plotted over it. The plot title is also defined. I want to show this whole image in tkinter window using canvas. How do I do it?
This is the image. The name has to be changed and we can run the code. https://imgur.com/RkV02yY
import math
import matplotlib.pyplot as plt
def plot_output(opt_w, opt_h, n_x, n_y):
y_start, y_end = 100, 425
x_start, x_end = 25, 400
img = plt.imread("C:/Square.png") #Please change the location
fig, ax = plt.subplots(figsize=(10, 10))
plt.axis('off')
ax.imshow(img)
x_interval = (x_end - x_start)/n_x*2
h_x = range(x_start, x_end, 5)
for i in range(0,int(n_y)):
if i != 0:
ax.plot(h_x, [y_start + (y_end-y_start)/n_y*i]*len(h_x), '--', linewidth=5, color='firebrick')
plt.title(str(int(n_x*n_y)) + ' ABCD\n'+'TYUI:'+str(opt_w)+', Yummy:'+str(opt_h))
def get_get(min_w, min_h, max_w, max_h, PL, PH, min_t, max_t, cost_m, cost_a):
x = 1
if max_w < PL:
x = math.ceil(PL / max_w)
cost_rest = cost_m * PL * PH * (max_t + min_t) / 2 + cost_a * PH * x
cost_y = float("inf")
y = None
if min_h == 0:
min_h = 1
for i in range(math.ceil(PH / max_h), math.floor(PH / min_h)+1):
tmp_cost = cost_m * PL * PH * (max_t - min_t) / 2 / i + cost_a * PL * i
if tmp_cost < cost_y:
cost_y = tmp_cost
y = i
opt_w, opt_h, opt_cost = PL/x, PH/y, cost_rest + cost_y
plot_output(opt_w, opt_h, x, y)
return opt_w, opt_h, opt_cost
PL=30
PH=10
min_t=0.1
max_t=0.3
cost_m=0.1
cost_a=0.1
min_w=0.5
min_h=0.5
max_w=4
max_h=3
get_get(min_w, min_h, max_w, max_h, PL, PH, min_t, max_t, cost_m, cost_a)
It creates image, lines and text (title) on canvas
import math
import tkinter as tk
from PIL import ImageTk
def plot_output(opt_w, opt_h, n_x, n_y):
global img # solution for PhotoImage bug
y_start, y_end = 100, 425
x_start, x_end = 25, 400
margin_top = 35 # place for title
# --- image ---
#img = ImageTk.PhotoImage(file="image.jpg") #Please change the location
canvas.create_image(0, margin_top, image=img, anchor='nw')
canvas['width'] = img.width()
canvas['height'] = img.height() + margin_top
# --- lines ---
y_step = (y_end-y_start)/n_y
for i in range(1, n_y):
y = y_start + y_step*i
y += margin_top # place for title
canvas.create_line(x_start, y, x_end, y, fill='red', width=7, dash=(25, 10))
#for y in range(y_start+y_step, y_end-1, y_step):
# y += margin_top # place for title
# canvas.create_line(x_start, y, x_end, y, fill='red', width=7, dash=(25, 10))
# --- title ---
text = '{} ABCD\nTYUI:{}, Yummy:{}'.format(int(n_x*n_y), opt_w, opt_h)
x = img.width()//2
y = margin_top # bottom of title because `create_text` uses `anchor='s'`
canvas.create_text(x, y, text=text, anchor='s', justify='center')
def get_get(min_w, min_h, max_w, max_h, PL, PH, min_t, max_t, cost_m, cost_a):
x = 1
if max_w < PL:
x = math.ceil(PL / max_w)
cost_rest = cost_m * PL * PH * (max_t + min_t) / 2 + cost_a * PH * x
cost_y = float("inf")
y = None
if min_h == 0:
min_h = 1
for i in range(math.ceil(PH / max_h), math.floor(PH / min_h)+1):
tmp_cost = cost_m * PL * PH * (max_t - min_t) / 2 / i + cost_a * PL * i
if tmp_cost < cost_y:
cost_y = tmp_cost
y = i
opt_w, opt_h, opt_cost = PL/x, PH/y, cost_rest + cost_y
plot_output(opt_w, opt_h, x, y)
return opt_w, opt_h, opt_cost
# --- main ---
PL = 30
PH = 10
min_t = 0.1
max_t = 0.3
cost_m = 0.1
cost_a = 0.1
min_w = 0.5
min_h = 0.5
max_w = 4
max_h = 3
root = tk.Tk()
canvas = tk.Canvas(root, width=1000, height=1000)
canvas.pack()
get_get(min_w, min_h, max_w, max_h, PL, PH, min_t, max_t, cost_m, cost_a)
root.mainloop()
When I draw a rectangle in the canvas I know its coordinates, absolute (canvasx(), canvasy()) and relative (the ones that I have passed).
Then, I may be able to move the canvas using the canvas scan_mark() and scan_dragto() methods.
How may I return to the original position (before one or more of these scan_mark()/scan_dragto() calls) in order to re-center the rectangle of which the user might even have lost the position?
Is there any reset view command/method? How may I keep track of the change that has occurred?
The Tkinter documentation I've seen doesn't seem to mention this, but the underlying Tk Canvas xview/yview methods can be called with no parameter to get the current scroll position (actually, as a tuple of two elements, the second not being of any use to you). So, the following should work (not tested):
Save position:
origX = yourcanvas.xview()[0]
origY = yourcanvas.yview()[0]
Restore position:
yourcanvas.xview_moveto(origX)
yourcanvas.yview_moveto(origY)
As in the previous comment #jasonharper, his suggestion worked. For others who might have the same problem I attach a working example even though it has not been cleaned so well to be proud of, it may give you way to see how zoom in and out, view drag and reset might work.
import tkinter as tk
import tkinter.ttk as ttk
class GriddedMazeCanvas(tk.Canvas):
def almost_centered(self, cols, rows):
width = int(self['width'])
height = int(self['height'])
cell_dim = self.settings['cell_dim']
rows = rows % height
cols = cols % width
w = cols * cell_dim
h = rows * cell_dim
if self.zoom < 0:
raise ValueError('zoom is negative:', self.zoom)
zoom = self.zoom // 2 + 1
if self.drawn() and 1 != zoom:
w *= zoom
h *= zoom
h_shift = (width - w) // 2
v_shift = (height - h) // 2
return [h_shift, v_shift,
h_shift + w, v_shift + h]
def __init__(self, *args, **kwargs):
if 'settings' not in kwargs:
raise ValueError("'settings' not passed.")
settings = kwargs['settings']
del kwargs['settings']
super().__init__(*args, **kwargs)
self.config(highlightthickness=0)
self.settings = settings
self.bind_events()
def draw_maze(self, cols, rows):
self.cols = cols
self.rows = rows
if self.not_drawn():
self.cells = {}
self.cell_dim = self.settings['cell_dim']
self.border_thickness = self.settings['border_thickness']
self.zoom = 0
self.delete(tk.ALL)
maze, coords = self._draw_maze(cols, rows, fix=False)
lines = self._draw_grid(coords)
return maze, lines
def _draw_maze(self, cols, rows, fix=True):
data = self.settings
to_max = data['to_max']
border_thickness = data['border_thickness']
poligon_color = data['poligon_color']
poligon_border_color = data['poligon_border_color']
coords = self.almost_centered(cols, rows)
if fix:
# Fix for the disappearing NW borders
if to_max == cols:
coords[0] += 1
if to_max == rows:
coords[1] += 1
maze = self.create_rectangle(*coords,
fill=poligon_color,
outline=poligon_border_color,
width=border_thickness,
tag='maze')
return maze, coords
def _draw_grid(self, coords):
data = self.settings
poligon_border_color = data['poligon_border_color']
cell_dim = data['cell_dim']
if coords is None:
if self.not_drawn():
raise ValueError('The maze is still uninitialized.')
x1, y1, x2, y2 = self.almost_centered(self.cols, self.rows)
else:
x1, y1, x2, y2 = coords
if self.drawn() and 0 != self.zoom:
if self.zoom < 0:
self.zoom = 0
print('no zooming at negative values.')
else:
zoom = self.zoom // 2 + 1
cell_dim *= zoom
lines = []
for i, x in enumerate(range(x1, x2, cell_dim)):
line = self.create_line(x, y1, x, y2,
fill=poligon_border_color,
tags=('grid', 'grid_hl_{}'.format(i)))
lines.append(line)
for i, y in enumerate(range(y1, y2, cell_dim)):
line = self.create_line(x1, y, x2, y,
fill=poligon_border_color,
tags=('grid', 'grid_vl_{}'.format(i)))
lines.append(line)
return lines
def drawn(self):
return hasattr(self, 'cells')
def not_drawn(self):
return not self.drawn()
def bind_events(self):
self.bind('<Button-4>', self.onZoomIn)
self.bind('<Button-5>', self.onZoomOut)
self.bind('<ButtonPress-1>', self.onScrollStart)
self.bind('<B1-Motion>', self.onScrollMove)
self.tag_bind('maze', '<ButtonPress-3>', self.onMouseRight)
def onScrollStart(self, event):
print(event.x, event.y, self.canvasx(event.x), self.canvasy(event.y))
self.scan_mark(event.x, event.y)
def onMouseRight(self, event):
col, row = self.get_pos(event)
print('zoom, col, row:', self.zoom, col, row)
def onScrollMove(self, event):
delta = event.x, event.y
self.scan_dragto(*delta, gain=1)
def onZoomIn(self, event):
if self.not_drawn():
return
max_zoom = 16
self.zoom += 2
if self.zoom > max_zoom:
print("Can't go beyond", max_zoom)
self.zoom = max_zoom
return
print('Zooming in.', event.num, event.x, event.y, self.zoom)
self.draw_maze(self.cols, self.rows)
def onZoomOut(self, event):
if self.not_drawn():
return
self.zoom -= 2
if self.zoom < 0:
print("Can't go below zero.")
self.zoom = 0
return
print('Zooming out.', event.num, event.x, event.y, self.zoom)
self.draw_maze(self.cols, self.rows)
def get_pos(self, event):
x, y = event.x, event.y
cols, rows = self.cols, self.rows
cell_dim, zoom = self.cell_dim, self.zoom
x1, y1, x2, y2 = self.almost_centered(cols, rows)
if not (x1 <= x <= x2 and y1 <= y <= y2):
print('Here we are out of bounds.')
return None, None
scale = (zoom // 2 + 1) * cell_dim
col = (x - x1) // scale
row = (y - y1) // scale
return col, row
class CanvasButton(ttk.Button):
def freeze_origin(self):
if not hasattr(self, 'origin'):
canvas = self.canvas
self.origin = canvas.xview()[0], canvas.yview()[0]
def reset(self):
canvas = self.canvas
x, y = self.origin
canvas.yview_moveto(x)
canvas.xview_moveto(y)
def __init__(self, *args, **kwargs):
if not 'canvas' in kwargs:
raise ValueError("'canvas' not passed.")
canvas = kwargs['canvas']
del kwargs['canvas']
super().__init__(*args, **kwargs)
self.config(command=self.reset)
self.canvas = canvas
root = tk.Tk()
settings = {'cell_dim': 3,
'to_max': 200,
'border_thickness': 1,
'poligon_color': '#F7F37E',
'poligon_border_color': '#AC5D33'}
frame = ttk.Frame(root)
canvas = GriddedMazeCanvas(frame,
settings=settings,
width=640,
height=480)
button = CanvasButton(frame, text='Reset', canvas=canvas)
button.freeze_origin()
canvas.draw_maze(20, 10)
canvas.grid(row=0, column=0, sticky=tk.NSEW)
button.grid(row=1, column=0, sticky=tk.EW)
frame.rowconfigure(0, weight=1)
frame.grid()
root.mainloop()
Tested with Python 3.4 on latest Ubuntu 16.10
(Events: MouseLeft, Mouseright, MouseWheel, ButtonPress)
HTH
I have the following code which draws ellipse on the circumference of a circle and then draws chords between ellipses. The chords are drawn fine but the event bombs on me and goes into a infinite loop.
def binomial(i, n):
return math.factorial(n) / float(
math.factorial(i) * math.factorial(n - i))
def bernstein(t, i, n):
return binomial(i, n) * (t ** i) * ((1 - t) ** (n - i))
def bezier(t, points):
n = len(points) - 1
x = y = 0
for i, pos in enumerate(points):
bern = bernstein(t, i, n)
x += pos[0] * bern
y += pos[1] * bern
return x, y
def bezier_curve_range(n, points):
for i in xrange(n):
t = i / float(n - 1)
yield bezier(t, points)
def getControlPt(x1, y1, x2, y2, t):
new_px = (1 - t) * x1 + t * x2
new_py = (1 - t) * y1 + t * y2
return new_px, new_py
class Temp1(QWidget):
def __init__(self, c, parent=None):
super(Temp1, self).__init__(parent)
self.text = "Hi"
self.cords = c
def paintEvent(self, e):
qp = QPainter(self)
qp.setPen(Qt.red)
bluePen = QPen(Qt.blue, 1, Qt.DashLine)
steps = 10000
for i in range(15):
for j in range(15):
if i == j:
continue
px, py = getControlPt(self.cords[i][0], self.cords[i][1], self.cords[j][0], self.cords[j][1], 0.55)
px1, py1 = getControlPt(px, py, self.cords[j][0], self.cords[j][1], 0.25)
px2, py2 = getControlPt(px1, py1, self.cords[j][0], self.cords[j][1], 0.75)
cp = (self.cords[i], (px, py) , (px1, py1), (px2, py2), self.cords[j])
oldPoint = cp[0]
qp.setPen(bluePen)
for point in bezier_curve_range(steps, cp):
qp.drawLine(oldPoint[0], oldPoint[1], point[0], point[1])
oldPoint = point
class Temp(QPushButton):
def __init__(self, x, y, w, h, theta, cords, parent=None):
super(Temp, self).__init__(parent)
self.w = w
self.h = h
self.x = x
self.y = y
self.text = "test"
self.angle = theta
self.cords = cords
def paintEvent(self, e):
qp = QPainter(self)
qp.setPen(Qt.red)
qp.drawEllipse(0, 0, self.w - 2, self.h -2)
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.cords = []
self.initUI()
def initUI(self):
self.setWindowTitle('Points')
self.drawP()
def drawP(self):
theta = 2 * np.pi / 15
for i in range(15):
angle = theta * (i + 1)
dx = int(round(400 + 300 * np.cos(angle)))
dy = int(round(400 + 300 * np.sin(angle)))
self.cords.append((dx, dy))
for i in range(15):
t = Temp(self.cords[i][0], self.cords[i][1], 45, 20, np.degrees(angle), self.cords, parent=self)
t.setGeometry(self.cords[i][0], self.cords[i][1] , 45, 20)
t1 = Temp1(self.cords, parent = self)
t1.setGeometry(0, 0, 800, 600)
app = QApplication(sys.argv)
ex = Example()
ex.setGeometry(0, 0, 800, 600)
ex.show()
sys.exit(app.exec_())
The problem is with the paintevent function of class Temp1.
If you throw a print 'Temp1.paintEvent', e, i, j inside the nested for loops inside of Temp1.paintEvent, you'll see that it's not hanging, it's just slow, and every time you hide the window, then bring it back up, it has to go through the whole paintEvent again before actually showing it. If you're patient the window will eventually pop up.