Related
I am using this grid.py coloring program here https://scipython.com/blog/a-simple-colouring-grid/ The relevant code that makes the grid is
self.cells = []
for iy in range(n):
for ix in range(n):
xpad, ypad = pad * (ix+1), pad * (iy+1)
x, y = xpad + ix*xsize, ypad + iy*ysize
rect = self.w.create_rectangle(x, y, x+xsize,
y+ysize, fill=UNFILLED)
self.cells.append(rect)
I was wondering if I could get it so the squares stagger as shown below:
You could use generator functions to, for each line, generate the coordinates, and the fill of each cell.
note row % 4 < 2: this is what generates identical fill pattern for consecutive rows the rows
You can adapt the following code sample to fit your Grid class with ease.
import tkinter as tk
WIDTH, HEIGHT = 300, 300
def line_fills(row, numcells):
"""yields the fill value of each cells of a line, from
the row position of the line
"""
fill = True if row % 4 < 2 else False # note %4: this is what generates identical fill pattern for consecutive rows the rows
for _ in range(numcells):
yield fill
fill = not fill
def line_coords(row, numcells, side):
"""yields the rectangle coordinates of each cells in a line, from
the row position of the line
"""
y0, y1 = row * side, (row+1) * side
for col in range(numcells):
x0, x1 = col * side, (col+1) * side
yield x0, y0, x1, y1
def staggered_grid(canvas):
"""generates and draws a staggered grid on a canvas
"""
side = 10
w, h = WIDTH // side + 1, HEIGHT // side + 1
fills = ['', 'grey']
for row in range(h):
for coord, fill in zip(line_coords(row, w, side), line_fills(row, w)):
fill = 'grey' if fill else ''
canvas.create_rectangle(*coord, fill=fill)
root = tk.Tk()
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT)
canvas.pack()
staggered_grid(canvas)
root.mainloop()
Below is an example based on your code (added missing parts):
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
### added missing parts
pad = 0
xsize = ysize = 30
n = 8
self.w = tk.Canvas(self, width=(xsize+pad)*n+pad+1, height=(ysize+pad)*n+pad+1, highlightthickness=0)
self.w.pack()
###
self.cells = []
for iy in range(n):
for ix in range(n):
xpad, ypad = pad * (ix+1), pad * (iy+1)
x, y = xpad + ix*xsize, ypad + iy*ysize
color = 'white' if (iy//2+ix)%2 else 'gray'
rect = self.w.create_rectangle(x, y, x+xsize, y+ysize, fill=color) # use color instead of UNFILLED
self.cells.append(rect)
app = App()
app.mainloop()
And the result:
I have my code ready to show 3x3 equilateral triangles, but I don't know how to change it so N amount of equilateral triangles can be shown in a column (one triangle above another one) and M amount of equilateral triangles can be shown in a row (one triangle next to another one). Any help is much appreciated!
My code is:
import turtle
ara = turtle.Turtle() #name of the turtle
ara.speed(0)
ara.pensize(10)
def pile_left(t, l, n): #first column
t = l * 3 ** 0.5 / 2
for j in range(n):
if j == 0 : ara.fillcolor('red')
if j == 1 : ara.fillcolor('orange')
if j == 2 : ara.fillcolor('green')
ara.pu()
ara.shape('turtle')
ara.begin_fill()
for i in range(3):
ara.lt(120)
ara.fd(l)
ara.end_fill()
ara.sety(ara.ycor() + t)
pile_left(ara, 60, 3)
def pile_middle(t, l, n): #second column
t = l * 3 ** 0.5 / 2
ara.goto(60,0)
for j in range(n):
if j == 0 : ara.fillcolor('purple')
if j == 1 : ara.fillcolor('yellow')
if j == 2 : ara.fillcolor('peachpuff')
ara.pu()
ara.shape('turtle')
ara.begin_fill()
for i in range(3):
ara.lt(120)
ara.fd(l)
ara.end_fill()
ara.sety(ara.ycor() + t)
pile_middle(ara, 60, 3)
def pile_right(t, l, n): #third column
t = l * 3 ** 0.5 / 2
ara.goto(120,0)
for j in range(n):
if j == 0 : ara.fillcolor('grey')
if j == 1 : ara.fillcolor('brown')
if j == 2 : ara.fillcolor('blue')
ara.pu()
ara.shape('turtle')
ara.begin_fill()
for i in range(3):
ara.lt(120)
ara.fd(l)
ara.end_fill()
ara.sety(ara.ycor() + t)
pile_right(ara, 60, 3)
turtle.mainloop()
You can run it on trinket.io/python to see who it currently does.
I have my code ready to show 3x3 equilateral triangles
No, not really. You have your code ready to show 1x3 equilateral triangles, and you simply duplicated your code three times to emulate a second dimension. Your problem is that you can't duplicate your code M times to solve this problem.
The answer in this situation is less, but smarter, code. We're going to need nested loops for N and M, and the ability to move forward by length l for each M and vertically by height t for each N:
from turtle import Screen, Turtle
COLORS = ['red', 'orange', 'green', 'purple', 'yellow', 'pink', 'grey', 'brown', 'blue']
def pile(turtle, length, columns, rows):
height = length * 3 ** 0.5 / 2
x_origin = turtle.xcor()
color = 0
for _ in range(rows):
for _ in range(columns):
turtle.color(COLORS[color % len(COLORS)])
turtle.begin_fill()
for _ in range(3):
turtle.forward(length)
turtle.left(120)
turtle.end_fill()
turtle.forward(length)
color += 1
turtle.setposition(x_origin, turtle.ycor() + height)
screen = Screen()
yertle = Turtle('turtle')
yertle.speed('fastest')
yertle.penup()
pile(yertle, 60, 4, 5)
yertle.hideturtle()
screen.exitonclick()
However, this is a perfect example where stamping would be simpler than filling. We can make the turtle itself an equilateral triangle, then move it and stamp it:
from turtle import Screen, Turtle
COLORS = ['red', 'orange', 'green', 'purple', 'yellow', 'pink', 'grey', 'brown', 'blue']
CURSOR_SIZE = 20
def pile(turtle, length, columns, rows):
turtle.shapesize(length / CURSOR_SIZE)
height = length * 3 ** 0.5 / 2
y_origin = turtle.ycor()
color = 0
for _ in range(columns):
for _ in range(rows):
turtle.color(COLORS[color % len(COLORS)])
turtle.stamp()
turtle.forward(height)
color += 1
turtle.setposition(turtle.xcor() + length, y_origin)
screen = Screen()
yertle = Turtle('triangle')
yertle.speed('fastest')
yertle.setheading(90)
yertle.penup()
pile(yertle, 60, 4, 5)
yertle.hideturtle()
screen.exitonclick()
Not only simpler, but faster too!
So I created a recurring pattern using two different designs. The problem now is making the colours, so I have to ask the user to pick 3 different colours and use them to make certain areas of the designs that colour. For example what I mean is, if I were to choose the 5x5 patch layout then the first 2 boxes of patterns across and below should be one colour (so that's four boxes) and so on.
EDIT: to clear up the confusion.
So if you look at the 500x500 one. type '5' when it asks for what patch size you want.
you should see two different pattern designs.
all I want is the x = 0 to x = 200 and y = 0 to y = 200 to be blue colour. Meaning that everything in that area (by area, I mean all the pattern designs that are in there) should be a blue colour. The same colour should be used for x = 300 to x = 500 and y = 300 to y = 500.
the x = 300 to x = 500 and y = 0 to y = 200 should be red colour and the x = 0 to x = 200 and y = 300 to y = 500 should also be red.
and all that is left is the '+' part in the middle which should have the third colour, green.
https://prntscr.com/m48xxd this is what I mean by the colours. so the top left and bottom right are the same colours. the top right and bottom left are te same colours and the '+' is another colour.
7.https://prntscr.com/m48xzm here is the 7x7 version
I just put colours, for now, to make it easier to look at.
I have already tried doing the 'getX' and 'getY' but I can't seem to get it right.
from graphics import *
def main():
global patchWorkSize
patchWorkSize = int(input("What patchwork size would you like?"))
if patchWorkSize == 5:
patchWork5()
elif patchWorkSize == 7:
patchWork7()
elif patchWorkSize == 9:
patchWork9()
else:
print('Only patchwork sizes 5, 7 and 9 are avaliable')
def patchWork5():
layout()
for y in range(0,500,100):
for x in range(0,500,200):
for l1 in range(0,100,20):
line = Line(Point(l1+x,0+y), Point(100+x,100-l1+y))
line.draw(win)
for l2 in range(100,0,-20):
line3 = Line(Point(l2+x,100+y), Point(0+x,100-l2+y))
line3.draw(win)
for l3 in range(100,0,-20):
line2 = Line(Point(100+x,0+l3+y), Point(0+l3+x,100+y))
line2.setFill('red')
line2.draw(win)
for l4 in range(0,100,20):
line4 = Line(Point(0+x,100-l4+y), Point(100-l4+x,0+y))
line4.setFill('red')
line4.draw(win)
for e in range(0,500,100):
for t in range(100,500,200):
for o in range(0,500,10):
for c in range(5,100,10):
circle1 = Circle(Point(c+t,5+o+e), 5)
circle1.setFill('blue')
circle1.draw(win)
def patchWork7():
layout()
for y in range(0,700,100):
for x in range(0,700,200):
for l1 in range(0,100,20):
line = Line(Point(l1+x,0+y), Point(100+x,100-l1+y))
line.draw(win)
for l2 in range(100,0,-20):
line3 = Line(Point(l2+x,100+y), Point(0+x,100-l2+y))
line3.draw(win)
for l3 in range(100,0,-20):
line2 = Line(Point(100+x,0+l3+y), Point(0+l3+x,100+y))
line2.setFill('red')
line2.draw(win)
for l4 in range(0,100,20):
line4 = Line(Point(0+x,100-l4+y), Point(100-l4+x,0+y))
line4.setFill('red')
line4.draw(win)
for e in range(0,700,100):
for t in range(100,700,200):
for o in range(0,700,10):
for c in range(5,100,10):
circle1 = Circle(Point(c+t,5+o+e), 5)
circle1.setFill('blue')
circle1.draw(win)
def patchWork9():
layout()
for y in range(0,900,100):
for x in range(0,900,200):
for l1 in range(0,100,20):
line = Line(Point(l1+x,0+y), Point(100+x,100-l1+y))
line.draw(win)
for l2 in range(100,0,-20):
line3 = Line(Point(l2+x,100+y), Point(0+x,100-l2+y))
line3.draw(win)
for l3 in range(100,0,-20):
line2 = Line(Point(100+x,0+l3+y), Point(0+l3+x,100+y))
line2.setFill('red')
line2.draw(win)
for l4 in range(0,100,20):
line4 = Line(Point(0+x,100-l4+y), Point(100-l4+x,0+y))
line4.setFill('red')
line4.draw(win)
for e in range(0,900,100):
for t in range(100,900,200):
for o in range(0,900,10):
for c in range(5,100,10):
circle1 = Circle(Point(c+t,5+o+e), 5)
circle1.setFill('blue')
circle1.draw(win)
def layout():
global win
win = GraphWin('Patchwork',patchWorkSize*100, patchWorkSize*100)
for i in range(0, patchWorkSize*100,100):
line = Line(Point(i,0), Point(i, patchWorkSize*100))
line2 = Line(Point(0,i), Point(patchWorkSize*100, i))
line.draw(win)
line2.draw(win)
main()
The key is to paramertize everything into cubes of a convenient size, and then turn the filling code into subroutines that can be called to fill those cubes. Below is a rework of your code along these lines that can handle any odd number 3 or greater as an input:
from graphics import *
UNIT = 100
def patchWork(win, size):
blocks = size // 2
def hatch_box(x, y):
for n in range(0, UNIT, UNIT//5):
line = Line(Point(n + x * UNIT, y * UNIT), Point((x + 1) * UNIT, UNIT - n + y * UNIT))
line.draw(win)
for n in range(UNIT, 0, -UNIT//5):
line = Line(Point(n + x * UNIT, (y + 1) * UNIT), Point(x * UNIT, UNIT - n + y * UNIT))
line.draw(win)
for n in range(UNIT, 0, -UNIT//5):
line = Line(Point((x + 1) * UNIT, n + y * UNIT), Point(n + x * UNIT, (y + 1) * UNIT))
line.setFill('red')
line.draw(win)
for n in range(0, UNIT, UNIT//5):
line = Line(Point(x * UNIT, UNIT - n + y * UNIT), Point(UNIT - n + x * UNIT, y * UNIT))
line.setFill('red')
line.draw(win)
for y in range(blocks):
for x in range(blocks):
hatch_box(x, y)
for y in range(blocks + 1, 2 * blocks + 1):
for x in range(blocks + 1, 2 * blocks + 1):
hatch_box(x, y)
def draw_circles(x, y):
for o in range(0, UNIT, UNIT // 10):
for c in range(0, UNIT, UNIT // 10):
circle = Circle(Point(c + UNIT // 20 + x * UNIT, o + UNIT // 20 + y * UNIT), UNIT // 20)
circle.setFill('blue')
circle.draw(win)
for y in range(blocks):
for x in range(blocks + 1, 2 * blocks + 1):
draw_circles(x, y)
draw_circles(y, x)
def draw_cube(x, y):
cube = Rectangle(Point(x * UNIT, y * UNIT), Point((x + 1) * UNIT, (y + 1) * UNIT))
cube.setFill('yellow')
cube.draw(win)
x = blocks
for y in range(0, 2 * blocks + 1):
draw_cube(x, y)
draw_cube(y, x)
def layout(size):
win = GraphWin('Patchwork', size * UNIT, size * UNIT)
for i in range(0, size * UNIT, UNIT):
Line(Point(i, 0), Point(i, size * UNIT)).draw(win)
Line(Point(0, i), Point(size * UNIT, i)).draw(win)
return win
def main():
patchWorkSize = int(input("What patchwork size would you like? "))
if patchWorkSize % 2 == 1 and patchWorkSize > 2:
win = layout(patchWorkSize)
patchWork(win, patchWorkSize)
win.getMouse() # pause for click in window
win.close()
else:
print('Only odd sizes 3 or greater are available')
main()
You should be able to change UNIT, within reason.
I'm not going to write everything for you, but the code below shows how to choose the color for each area based on its position within the patchwork grid (i and j). The color is determined in color_func().
Hopefully this will be enough for you to figure out how to apply the relatively simple coding pattern shown to draw the desired graphics.
Hint: Compute the coordinates of each graphics element based on (or relative to) these same position values (i.e. don't write a separate function for every possible patchwork size).
This code still seems overly repetitive to me, and could probably be made more concise, but I'll leave that to you, too... ;¬)
def main():
# size = int(input("What patchwork size would you like?"))
# Hardcoded for testing.
#
# if size % 2 == 0:
# raise RuntimeError('patchwork size must be odd')
patch_work(5)
print()
patch_work(7)
def patch_work(n):
colors = '0', '1', '2'
mid = n // 2
# Call color_func() for every possible position in patchwork and
# prints the results in rows.
for i in range(n):
row = []
for j in range(n):
row.append(color_func(n, i, j, colors))
print(''.join(row))
def color_func(n, i, j, colors):
mid = n // 2
if i == mid:
index = 2
elif i < mid:
if j < mid:
index = 0
elif j == mid:
index = 2
elif j > mid:
index = 1
elif i > mid:
if j < mid:
index = 1
elif j == mid:
index = 2
elif j > mid:
index = 0
return colors[index]
if __name__ == '__main__':
main()
Output:
00211
00211
22222
11200
11200
0002111
0002111
0002111
2222222
1112000
1112000
1112000
For the last part of my code I need to output the maximum velocity and the angle at which the velocity is max, but we are not allowed to use the max function. My code so far:
#This program calculates and plots the position and velocity of a piston
import numpy as np
import matplotlib.pyplot as plt
def piston_position(r,l,a):
#input: r = radius (cm), l = length (cm), a = angle (radians)
#output: x = position (cm)
x = (r * np.cos(a) + ((l**2) - (r**2) * (np.sin(a)**2)**0.5))
return x
def piston_velocity (r, l, a, w):
#input: r = radius (cm), l = length (cm), a = angle (radians), w = angular velocity (radians/seconds)
#output: v = velocity (cm/s)
v = (-r * w * np.sin(a) - ((r**2 * w * np.sin(a) * np.cos(a))/((l**2 - r**2 *np.sin(a)**2)**0.5)))
return v
a = np.linspace(0,360,30)
x1 = piston_position(3,15,a)
v1 = piston_velocity(3,15,a,100)
x2 = piston_position(5,15,a)
v2 = piston_velocity(5,15,a,100)
plt.figure(1)
plt.subplot(211)
plt.plot(a,x1, 'b^--', label ='r=3cm', mfc = 'r', color = "black")
plt.plot(a,x2, 'bs--', label ='r=5cm', mfc = 'b', color = "black")
plt.title ("Subplot for Position")
plt.ylabel ("Position (cm)")
#plt.xlabel ("Angle (degrees)") --- Note: caused an overlap in text
plt.legend()
plt.subplot(212)
plt.plot(a,v1, 'b^--', label ='r=3cm', mfc = 'r', color = "black")
plt.plot(a,v2, 'bs--', label ='r=5cm', mfc = 'b', color = "black")
plt.title ("Subplot for Velocity")
plt.ylabel ("Velocity (cm/s)")
plt.xlabel ("Angle (degrees)")
plt.legend()
plt.show()
a3 = np.array(a)
v3 = sorted(piston_velocity(3,15,a3,100))
v4 = piston_velocity(5,15,a3,100)
for i in range(0,len(v3)):
print((int(a3[i])),int(v3[i]))
With the code I have I return all the angle values and velocity and I am unsure how to output just the max velocity with its corresponding angle.
I appreciate the help!
Collect all your velocity/agle tuples into a list.
Create your own max function - python is a programming language:
def getMax(iterab):
"""Returns the larges number (or > / __ge__(self,a,b) is defined) value) from iterab"""
c = iterab[0] # make sure it has content
for other in iterab[1:]:
if other > c:
c = other
return other
def getMaxTuple(tupsIter,idx=0):
"""Returns the tuple from tupsIter with the larges value on position idx"""
c = tupsIter[0]
for other in tupsIter[1:]:
if other[idx] > c[idx]:
c = other
return other
Print them:
print(getMax(range(2,5))) # 4
print(getMaxTuple( [ (a,a**3) for a in range(10) ],1 )) # (9,729)
The easiest way is to get the index of the maximum value (although usually I would go for np.argmax):
index = 0
value = v1[0]
for i in range(len(v1)):
if v1[i] > value:
index = i
value = v1[i]
Then you can get the angle using:
angle = a[index]
This returns maximum velocity of v1: 305.9m/s with angle 111.7, which seems pretty accurate when comparing to the graph.
I'm using the following procedure to calculate hexagonal polygon coordinates of a given radius for a square grid of a given extent (lower left --> upper right):
def calc_polygons(startx, starty, endx, endy, radius):
sl = (2 * radius) * math.tan(math.pi / 6)
# calculate coordinates of the hexagon points
p = sl * 0.5
b = sl * math.cos(math.radians(30))
w = b * 2
h = 2 * sl
origx = startx
origy = starty
# offsets for moving along and up rows
xoffset = b
yoffset = 3 * p
polygons = []
row = 1
counter = 0
while starty < endy:
if row % 2 == 0:
startx = origx + xoffset
else:
startx = origx
while startx < endx:
p1x = startx
p1y = starty + p
p2x = startx
p2y = starty + (3 * p)
p3x = startx + b
p3y = starty + h
p4x = startx + w
p4y = starty + (3 * p)
p5x = startx + w
p5y = starty + p
p6x = startx + b
p6y = starty
poly = [
(p1x, p1y),
(p2x, p2y),
(p3x, p3y),
(p4x, p4y),
(p5x, p5y),
(p6x, p6y),
(p1x, p1y)]
polygons.append(poly)
counter += 1
startx += w
starty += yoffset
row += 1
return polygons
This works well for polygons into the millions, but quickly slows down (and takes up very large amounts of memory) for large grids. I'm wondering whether there's a way to optimise this, possibly by zipping together numpy arrays of vertices that have been calculated based on the extents, and removing the loops altogether – my geometry isn't good enough for this, however, so any suggestions for improvements are welcome.
Decompose the problem into the regular square grids (not-contiguous). One list will contain all shifted hexes (i.e. the even rows) and the other will contain unshifted (straight) rows.
def calc_polygons_new(startx, starty, endx, endy, radius):
sl = (2 * radius) * math.tan(math.pi / 6)
# calculate coordinates of the hexagon points
p = sl * 0.5
b = sl * math.cos(math.radians(30))
w = b * 2
h = 2 * sl
# offsets for moving along and up rows
xoffset = b
yoffset = 3 * p
row = 1
shifted_xs = []
straight_xs = []
shifted_ys = []
straight_ys = []
while startx < endx:
xs = [startx, startx, startx + b, startx + w, startx + w, startx + b, startx]
straight_xs.append(xs)
shifted_xs.append([xoffset + x for x in xs])
startx += w
while starty < endy:
ys = [starty + p, starty + (3 * p), starty + h, starty + (3 * p), starty + p, starty, starty + p]
(straight_ys if row % 2 else shifted_ys).append(ys)
starty += yoffset
row += 1
polygons = [zip(xs, ys) for xs in shifted_xs for ys in shifted_ys] + [zip(xs, ys) for xs in straight_xs for ys in straight_ys]
return polygons
As you predicted, zipping results in much faster performance, especially for larger grids. On my laptop I saw 3x speedup when calculating 30 hexagon grid - 10x speed for 2900 hexagon grid.
>>> from timeit import Timer
>>> t_old = Timer('calc_polygons_orig(1, 1, 100, 100, 10)', 'from hexagons import calc_polygons_orig')
>>> t_new = Timer('calc_polygons_new(1, 1, 100, 100, 10)', 'from hexagons import calc_polygons_new')
>>> t_old.timeit(20000)
9.23395299911499
>>> t_new.timeit(20000)
3.12791109085083
>>> t_old_large = Timer('calc_polygons_orig(1, 1, 1000, 1000, 10)', 'from hexagons import calc_polygons_orig')
>>> t_new_large = Timer('calc_polygons_new(1, 1, 1000, 1000, 10)', 'from hexagons import calc_polygons_new')
>>> t_old_large.timeit(200)
9.09613299369812
>>> t_new_large.timeit(200)
0.7804560661315918
There might be an opportunity to create an iterator rather than the list to save memory. Depends on how your code is using the list of the polygons.
Here is a solution that doesn't require any looping. It creates grid of 50x50 hexagons:
coord_x, coord_y = np.meshgrid(50, 50, sparse=False, indexing='xy')
ratio = np.sqrt(3) / 2
coord_y = coord_y * ratio # Condense the coordinates along Y-axes
coord_x = coord_x.astype(np.float)
coord_x[1::2, :] += 0.5 # Shift every other row of the grid
coord_x = coord_x.reshape(-1, 1) # Flatten the grid matrices into [2500, 1] arrays
coord_y = coord_y.reshape(-1, 1)
radius = 5 # Inflate each hexagon to the required radius
coord_x *= radius
coord_y *= radius
This is a code snippet from my hexalattice package in python, that does that for you (+ advanced options of grid rotation, size and plotting)
Here you can find additional examples and link to the repo: LINK