How to enlarge and reduce an already drawn lines, using certain buttons? - python

Suppose I have the following program made with Tkinter in Python, in which you can click to draw any shape.
import tkinter
canvas = tkinter.Canvas(width=500, height=500, bg='white')
canvas.pack()
line = ()
def draw():
canvas.delete('all')
if len(line) >= 4:
canvas.create_line(line, fill='red', width=2)
def newline(coordinates):
global line
line = line + (coordinates.x, coordinates.y)
draw()
canvas.bind('<Button-1>', newline)
I have tried this for smaller image, but it didn't work.
def reduced():
line_reduced = ()
for i in newline:
line_reduced += (i/2,)
canvas.delete('all')
canvas.create_line(line_reduced, fill='red', width=4, tags='foto1')
I would need to add it so that this shape can then be reduced or enlarged using two keys. The image would therefore remain the same (shape), it would only be reduced / enlarged.
I will be grateful for any advice.

If you are trying to scale a bunch of lines on a canvas you could use scale, but then everything new that you add to the canvas would also have to be scaled or it starts getting weird. What you likely really need is math. Specifically this equation N = Scale * (N - Pn) + Pn. Below is a simple drawing interface with the buttons you requested. Primarily it's just a bunch of storing points and creating lines. The part of interest to you is this line:
line[n] = scale * (point - center) + center.
Lines are stored as [x1, y1, x2, y2] in my example code (the beginning and end of a line). line would refer to such a list. line[n] refers to each x and y value in that list as n is incremented. That being said:
line[n] = scale * (point - center) + center is really saying to subtract the center from the point, multiply it by the scale number, and then add the center back in so the scaled point stays it's scaled distance from the center.
Let's run through this one time so you definitely understand.
Chalkboard:
x1 = 100
center = 200
scale = 2
formula: x1 = scale * (x1 - center) + center
1 : x1 = 2 * (100 - 200) + 200
2 : x1 = 2 * (-100) + 200
3 : x1 = -200 + 200
4 : x1 = 0
The old x1 was 100 away from the center
now x1 is 200 away from the center
x1 is scale times further from the center than it was before
Code:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600, bg='white')
canvas.pack()
#for storing line canvas ids and line coordinates
line_ids, lines = [], []
#line properties ~ width and fill
global w, f
w, f = 2, 'red'
#draw a line
def draw(event):
if event.type is tk.EventType.ButtonPress:
#store line start coordinate
lines.append([event.x, event.y])
#create a dummy line
line_ids.append(canvas.create_line(*lines[-1], *lines[-1], fill=f, width=w))
elif event.type is tk.EventType.Motion:
#keep deleting and redrawing the current line til you release the mouse
canvas.delete(line_ids.pop())
line_ids.append(canvas.create_line(*lines[-1], event.x, event.y, fill=f, width=w))
elif event.type is tk.EventType.ButtonRelease:
#append the end coordinate to the last line
lines[-1] += [event.x, event.y]
#add mouse events to canvas for drawing functionality
for event in ['<B1-Motion>', '<Button-1>', '<ButtonRelease-1>']:
canvas.bind(event, draw)
#scale lines
def scale_lines(scale):
#remove all canvas references to lines
canvas.delete("all")
line_ids = []
#traverse every line
for i, line in enumerate(lines):
#traverse every point in THIS line
for n, point in enumerate(line):
#toggle between center x and center y depending if point is an x or y
center = canvas.winfo_width()/2 if not n%2 else canvas.winfo_height()/2
#scale this point
line[n] = scale * (point - center) + center
#create a new line with scaled points
line_ids.append(canvas.create_line(*line, fill=f, width=w))
#increase/decrease buttons
tk.Button(root, command=lambda: scale_lines(.5), text='decrease').pack(side='left')
tk.Button(root, command=lambda: scale_lines(2), text='increase').pack(side='left')
root.mainloop()

Related

How would I resize a polygon to fit in the canvas? Python Tkinter

I'm currently coding a congruent triangle drawer, it looks like this: GUI Of Drawer
It basically works this way: You input the length of the base that is the side BC and then you input the degrees of the point ⋪B and ⋪C, and then it calculates the interception between two lines that are made from the angles( A point to draw ∆ABC ), I visually drew them so you can understand it better here,
b = (b_x, b_y)
b_a = (b_x + math.cos(to_radian(-b_angle)) * 400, b_y + math.sin(to_radian(-b_angle)) * 400)
c = (c_x, c_y)
c_a = (c_x - math.cos(to_radian(c_angle)) * 400, c_y - math.sin(to_radian(c_angle)) * 400)
Now the problem is that when you input long degrees, the interception of the two lines (A point coordinates) the A point is made out of the canvas so you can't actually see the whole triangle(as you can see in the first image), so I'm here for a solution to resize the triangle(or polygon) and make it fit in the canvas, I already thought about checking if the A coordinates are greater than the canvas size, but since I'm not familiar with tkinter and python, I don't know how to resize the triangle and make it fit.
You can use scale() to resize the polygon.
Assume canv is the instance of Canvas and the triangle is the id of the polygon object:
x1, y1, x2, y2 = canv.bbox(triangle) # get the bounding box of the triangle
w, h = x2-x1+10, y2-y1+10 # make the w and h a bit larger
canv_w, canv_h = canv.winfo_width(), canv.winfo_height() # get canvas width and height
r = min(canv_w/w, canv_h/h) # determine the scale ratio
canv.scale(triangle, 0, 0, r, r) # resize triangle
canv.config(scrollregion=canv.bbox(triangle)) # adjust the view window
I did this, but it doesn't draw the triangle and I can't see any changes :
triangle = self.canvas.create_polygon(coords, fill="light blue", outline="black")
canvas_w, canvas_h = self.canvas.winfo_width(), self.canvas.winfo_height()
if a[0] > canvas_w or a[1] > canvas_h:
x1, y1, x2, y2 = self.canvas.bbox(triangle)
w, h = x2 - x1 + 10, y2 - y1 + 10
scale_r = min(canvas_w / w, canvas_h / h)
self.canvas.scale(triangle, 0, 0, scale_r, scale_r)
self.canvas.config(scrollregion=self.canvas.bbox(triangle))
Note: "a" is equal to the tuple of coordinates of the point "A"
a = (a_x, a_y)

Cutting the codes in the program and making it neater

I'm having a hard time cutting the code and making it into a loop so that it would make the code of the program, neater.
Although my code works as it suppose to be, I think there is a right way of creating it, adding a for loop rather than writing all of these codes, I know there is an easy way to do this, I just couldn't figure how to do it properly. I know I'm suppose to create a for loop.
squares
from graphics import *
def main():
win = GraphWin("Squares", 500, 500)
rect = Rectangle(Point(0,500), Point(500,0))
rect.setFill("Red")
rect.draw(win)
rect2 = Rectangle(Point(20,480), Point(480,20))
rect2.setFill("white")
rect2.draw(win)
rect3 = Rectangle(Point(40,460), Point(460,40))
rect3.setFill("red")
rect3.draw(win)
rect4 = Rectangle(Point(60,440), Point(440,60))
rect4.setFill("white")
rect4.draw(win)
rect5 = Rectangle(Point(80,420), Point(420,80))
rect5.setFill("red")
rect5.draw(win)
rect6 = Rectangle(Point(100,400), Point(400,100))
rect6.setFill("white")
rect6.draw(win)
rect7 = Rectangle(Point(120,380), Point(380,120))
rect7.setFill("red")
rect7.draw(win)
rect8 = Rectangle(Point(140,360), Point(360,140))
rect8.setFill("white")
rect8.draw(win)
rect9 = Rectangle(Point(160,340), Point(340,160))
rect9.setFill("red")
rect9.draw(win)
rect10 = Rectangle(Point(180,320), Point(320,180))
rect10.setFill("white")
rect10.draw(win)
rect11 = Rectangle(Point(200,300), Point(300,200))
rect11.setFill("red")
rect11.draw(win)
rect12 = Rectangle(Point(220,280), Point(280,220))
rect12.setFill("white")
rect12.draw(win)
The results shows squares into some sort of a patchwork
Try the following:
from graphics import *
def main():
win = GraphWin("Squares", 500, 500)
# create all rects
rects = [Rectangle(Point(0 + 20*i,500 - 20*i), Point(500 - 20*i, 0 + 20*i)) for i in range(12)]
# draw all rects
for idx, rect in enumerate(rects):
rect.fill("red" if idx % 2 == 0 else "white")
rect.draw(win)
If the patchwork is just a background and you don't plan on modifying it you could use this:
from graphics import *
def main():
win = GraphWin("Squares", 500, 500)
i = 1
for x in range(0, 221, 20):
rect = Rectangle(Point(x, 500 - x), Point(500 - x,x))
rect.setFill("red" if i % 2 else "white")
rect.draw(win)
i += 1
An alternate approach that only needs to draw half as many rectangles due to using the rectangle's outline as the other color:
SQUARE, WIDTH = 500, 20
def main():
win = GraphWin("Squares", SQUARE, SQUARE)
save_config = dict(DEFAULT_CONFIG)
DEFAULT_CONFIG.update(dict(outline='red', fill='white', width=WIDTH))
for xy in range(WIDTH//2, SQUARE//2, WIDTH*2):
Rectangle(Point(xy, SQUARE - xy), Point(SQUARE - xy, xy)).draw(win)
DEFAULT_CONFIG.update(save_config)
It's fully parameterized so you can fit it to a different size square or have different width stripes by adjusting the SQUARE and WIDTH parameters. Rather than draw 12 rectangles in alternating colors, with the parameters as currently set, it draws 6 white rectangles with red outlines:

How to change the coordinates of a shape (i.e. oval) in tkinter?

I am trying to make a program which when you press the S button it moves shape to the square below it on the grid. I have managed to get the shape to move the first time but after that it just keeps getting bigger.
Here is my code:
from tkinter import *
root = Tk()
global y
y = 0
x = 0
def down(event):
global y
global x
y = y+100
x = x+ 100
global pirate
canvas.delete(pirate)
pirate = canvas.create_oval((x,y), (100,100), fill = 'red')
print(y)
canvas = Canvas(root, width = 1000, height = 1000)
canvas.pack()
for a in range (10):
i = a*100
canvas.create_line((i,0), (i,1000))
for a in range (10):
i = a*100
canvas.create_line((0,i), (1000,i))
pirate = canvas.create_oval((x, y),(100, 100), fill = 'red')
root.bind('<Key - S>', down)
root.mainloop()
As ArtOfWarfare mentioned in comments, instead of creating new ovals everytime, create one and move that thing around.
def down(event):
canvas.move(pirate, 0, 100)
Above code is sufficient to move your oval one square down in your code.
Assuming you'll need to move oval other than just down, instead of binding only S to canvas, I think you should get all key events and do stuff depending on pressed char.
def keyPressed(event):
if event.char.lower() == 's': #move it down if it's S or s
canvas.move(pirate, 0, 100)
root.bind('<Key>', keyPressed) #get all key pressed events
You have a problem that in python-tk the oval is NOT specified create_oval(x,y,w,h), but create_oval(x1,y1,x2,y2). Hope that's helpful!

Replicating this design in Python

I've been trying to replicate this design using Python.
I am using the Graphics module, obtained here. I can't use any other Graphic Module at the moment.
This code here allows me to draw 5 circles in a line repeated by a loop.
def fdShape():
win = GraphWin(199,199)
centre = Point(20,100)
for y in range(5):
for x in range(5):
centre = Point(x * 40 + 20, y * 40 + 60)
circle = Circle(centre, 20)
circle.setOutline("red")
circle.draw(win)
One problem that I've found with this code is that it leaves a blank line at the top of the window and place the last circle line at the bottom, beyond the borders of the window. Thats the first problem.
The second is using the code to display semi circles displayed in red. As you can see in the image at the top of this page. I'm unsure of how to replicate this picture using Python.
Thank you!
This looks pretty close:
from graphic import *
def main():
repeat = 5
diameter = 40
radius = diameter // 2
offset = radius // 2
win = GraphWin("Graphic Design", diameter*repeat + offset, diameter*repeat)
win.setBackground('white')
for i in range(repeat):
for j in range(repeat):
draw_symbol(win, i % 2,
Point(i*diameter + offset, j*diameter), radius, 'red')
win.getMouse()
win.close()
def draw_symbol(win, kind, lower_left, radius, colour):
centre = Point(lower_left.x+radius, lower_left.y+radius)
circle = Circle(centre, radius)
circle.setOutline('')
circle.setFill(colour)
circle.draw(win)
if kind == 0:
rectangle = Rectangle(lower_left,
Point(lower_left.x+radius, lower_left.y+radius*2))
else:
rectangle = Rectangle(lower_left,
Point(lower_left.x+radius*2, lower_left.y+radius))
rectangle.setOutline('white')
rectangle.setFill('white')
rectangle.draw(win)
circle = Circle(centre, radius)
circle.setWidth(1)
circle.setOutline(colour)
circle.setFill('')
circle.draw(win)
main()
Two issues I see.
GraphWin should be initialized to 200x200, not 199x199.
This line:
centre = Point(x * 40 + 20, y * 40 + 60)
Should most likely be:
centre = Point(x * 40 + 20, y * 40 + 20)

Randomly orientated lines drawn off a random point in python

I'm trying to create python program that has several vertical lines which act as boundaries where randomly generated points or "dots" (as referred to in the code) which draw a straight line at a random degree. If the straight line intersects with one of the vertical "boundaries" I want to make it change colour. I have a picture of what I am trying to achieve which will probably explain my situation a bit clearer. The code I post below has drawn the "vertical boundaries" and has the points randomly generated within the region, however that is where I am stuck.
What I am aiming to achieve:
Example of program
My current Code:
setup(750,750)
screen_size = 750
max_coord = (screen_size - 30) / 2
### change the number of dots you have via that variable
num_dots = 500
bgcolor('yellow')
dot_size=5
reset() # Create an empty window
pi = Turtle()
hideturtle()
def parallel_lines(number):
pi.pensize(2)
pi.pencolor('black')
width = pi.window_width()
height = pi.window_height()
pi.setheading(90)
pi.penup()
pi.setposition(width/-2, height/-2)
for i in range(1, number +2):
pi.pendown()
pi.forward(height)
pi.penup()
pi.setposition(width/-2+i*(width/(number+1)),height/-2)
parallel_lines(7)
## centre turtle back in the middle of the page
goto(0,0)
### list to hold the dots
x_coords = []
y_coords = []
### Draw the dots via randomint
penup()
color("blue")
for dot_num in range(num_dots):
dot_pos_x = randint (-max_coord, max_coord)
dot_pos_y = randint (-max_coord, max_coord)
goto(dot_pos_x, dot_pos_y)
dot(dot_size)
x_coords.append(dot_pos_x)
y_coords.append(dot_pos_y)
done()
Thank you in advance for anyone that can help.
Here's an implementation of the program the OP describes. If there is a line intersection, it uses Python 3 turtle's undo feature to remove the line and redraw it in the alternate color:
from turtle import Turtle, Screen
from random import randint, randrange
SCREEN_SIZE = 750
PLANK_COUNT = 8
PINHEAD_SIZE = 5
FLOOR_COLOR = "yellow"
DEFAULT_COLOR = "blue"
CROSSING_COLOR = "red"
screen = Screen()
screen.setup(SCREEN_SIZE, SCREEN_SIZE)
screen.bgcolor(FLOOR_COLOR)
# configure numbers to replicate Lazzarini's setup
NUMBER_PINS = 3408
PIN_LENGTH = 78.125
PLANK_WIDTH = screen.window_width() / PLANK_COUNT
def parallel_lines(turtle, width, height):
turtle.penup()
turtle.setheading(90)
turtle.sety(height / -2)
x_coordinates = []
for i in range(PLANK_COUNT + 1):
x = i * PLANK_WIDTH - width / 2
turtle.setx(x)
turtle.pendown()
turtle.forward(height)
turtle.penup()
turtle.left(180)
x_coordinates.append(x)
return x_coordinates
pi = Turtle(visible=False)
pi.speed("fastest")
x_coordinates = parallel_lines(pi, screen.window_width(), screen.window_height())
def crosses(x0, x1, coordinates):
for coordinate in coordinates:
if x0 <= coordinate <= x1 or x1 <= coordinate <= x0:
return True
return False
previous_crossings = crossings = 0
max_coord = screen.window_width() / 2
for pin in range(NUMBER_PINS):
x0, y0 = randint(-max_coord, max_coord), randint(-max_coord, max_coord)
pi.color(DEFAULT_COLOR)
pi.goto(x0, y0)
pi.dot(PINHEAD_SIZE)
pi.setheading(randrange(360))
pi.pendown()
pi.forward(PIN_LENGTH)
if crosses(x0, pi.xcor(), x_coordinates):
pi.undo()
pi.color(CROSSING_COLOR)
pi.dot(PINHEAD_SIZE)
pi.forward(PIN_LENGTH)
crossings += 1
pi.penup()
if previous_crossings != crossings:
estimate = (2 * PIN_LENGTH * pin) / (PLANK_WIDTH * crossings)
print(estimate)
previous_crossings = crossings
screen.exitonclick()
Now for the rest of the story. What the OP didn't mention is that this is a drawing of planks in a floor, and we're dropping pins onto it, keeping track of how many cross lines in the flooring, as a means of estimating the value of PI (π)!
Read about Buffon's needle for details. The gist is the probability of a pin crossing a line is a function of PI so we can turn the equation around, drop actual (or virtual) pins, to estimate PI.
The program outputs the running estimate for PI (π), based on pins dropped so far, to the console:
...
3.121212121212121
3.1215970961887476
3.1370772946859904
3.134418324291742
3.131768953068592
3.1381381381381384
3.1384892086330933
3.1358467983243568
3.1451612903225805
3.1454979129397733
3.1458333333333335
3.1491384432560903
3.1465005931198102
3.1438721136767316
3.144208037825059
3.144542772861357
3.1419316843345113

Categories