Turtle Gradient Color (review code) - python

I am trying to use Turtle to print 30 hexagons that are spiralling and have a gradient color change from red to black.
I am multiplying my for loop i in order to change the set values in (r, b, g) so, at some point, it will exceed r=255. I include an if statement to have it not exceed this, but it's giving me the error:
File "<ipython-input-4-35d45ac44fdd>", line 20
if r > '255'
^
SyntaxError: invalid syntax
What is causing this error?
FYI I am using Anaconda and a Jupyter notebook.
Here is my code:
import turtle
def draw_hexagon (t, size):
n=6
angle= 360/n
for i in range(n):
t.forward(size)
t.left(angle)
turtle.colormode(255)
mega=turtle.Turtle()
mega.speed(1000)
leng = 100
for i in range(30):
r = 5+(i*10)
g = 0
b = 0
color = (r, b, g)
if r > 255
print color(r,b,g)
mega.fillcolor(color)
mega.begin_fill()
draw_hexagon(mega, leng)
mega.end_fill()
leng = leng + 5
mega.left(5)
turtle.exitonclick()

My advice is to not use the if statement to get around the error but rather fix your math to avoid it in the first place. Plus some code cleanup:
from turtle import Turtle, Screen
MIN_COLOR = 5
MAX_COLOR = 255
COUNT = 30
ANGLE = 5
STARTING_LENGTH = 100
LENGTH_INCREMENT = 5
N = 6
def draw_polygon(turtle, size):
angle = 360 / N
for _ in range(N):
turtle.forward(size)
turtle.left(angle)
screen = Screen()
screen.colormode(255)
mega = Turtle()
mega.speed('fastest')
length = STARTING_LENGTH
for r in range(COUNT):
red = round(r * ((MAX_COLOR - MIN_COLOR) / (COUNT - 1))) + MIN_COLOR
color = (red, 0, 0)
mega.fillcolor(color)
mega.begin_fill()
draw_polygon(mega, length)
mega.end_fill()
length += LENGTH_INCREMENT
mega.left(ANGLE)
mega.hideturtle()
screen.exitonclick()

Related

Drawing radiating circular trapezoid pattern with Turtle

I was just wondering how to draw some trapezoids in my turtle code.
I want my output to be like this:
What my output is right now:
Here's the code I've written so far:
import turtle as trtl
num_sides = 6
side_length = 15
circumradius = side_length
trtl.pencolor((245, 176, 66))
trtl.pensize(8)
for move_turtle in range(1):
trtl.penup()
trtl.sety(-circumradius)
trtl.pendown()
trtl.circle(circumradius, steps = num_sides)
circumradius *= 2
side_length = side_length + 8
trtl.pensize(12)
for move_turtle in range(1):
trtl.pencolor((255, 83, 71))
trtl.penup()
trtl.sety(-circumradius)
trtl.pendown()
trtl.circle(circumradius, steps = num_sides)
circumradius *= 2
for move_turtle in range(1):
trtl.pencolor((247, 220, 67))
trtl.penup()
trtl.sety(-circumradius)
trtl.pendown()
trtl.circle(circumradius, steps = num_sides)
circumradius *= 2
trtl.hideturtle()
What techniques can I use to get the desired output?
The approach I often take when given patterns like this is to think about a slight-of-hand trick that can produce the result with less trouble than figuring out how to draw something potentially fussy like a trapezoid.
In this case, if you remove the white spaces, we have a "pie"-shaped circle of different solid colors radiating from the middle. If we can manage to solve that simpler problem, then we can draw white lines on top and wind up with the desired result.
The only trig we need is the angle-to-coordinates formula:
x = cos(radians(angle)) * radius
y = sin(radians(angle)) * radius
Using this, we can iterate over n points (with some rotational offset) along the circumference of a circle with a loop and fill in the colors to make a pie:
import math
import turtle
def draw_pie(t, r, n, colors, rot_offset=0.5):
for i in range(n + 1):
a = 360 / n * (i + rot_offset)
t.color(colors[i%len(colors)])
t.begin_fill()
t.goto(0, 0)
x = math.cos(math.radians(a)) * r
y = math.sin(math.radians(a)) * r
t.goto(x, y)
t.end_fill()
if __name__ == "__main__":
t = turtle.Turtle()
t.screen.setup(540, 540)
t.penup()
t.speed("fastest")
sides = 6
draw_pie(t, 450, sides, ["#dd2", "orange", "#d02"])
t.ht()
turtle.exitonclick()
Next, we need to draw a bunch of thick white lines with square corners. Unfortunately, turtle draws rounded edges by default which aren't suitable for our needs. There are many tricks for making square edges (stamping is a reasonable method). The approach I used here was writing a custom rectangle drawing function that takes advantage of turtle.distance and turtle.setheading(turtle.towards(x, y)) which lets me point to the next point along the circumference of the circle. The rest is just loops to compute angles and picking the right values.
Here's the code:
import math
import turtle
def draw_rect(t, x, y, xx, yy, width):
t.goto(x, y)
t.setheading(t.towards(xx, yy))
t.begin_fill()
for d in [t.distance(xx, yy), width] * 2:
t.forward(d)
t.left(90)
t.end_fill()
def draw_pie(t, r, n, colors, rot_offset=0.5):
for i in range(n + 1):
a = 360 / n * (i + rot_offset)
t.color(colors[i%len(colors)])
t.begin_fill()
t.goto(0, 0)
x = math.cos(math.radians(a)) * r
y = math.sin(math.radians(a)) * r
t.goto(x, y)
t.end_fill()
def draw_reg_polygon(t, r, n, thickness, rot_offset=0.5):
for i in range(n):
a = 360 / n * (i + rot_offset)
x = math.cos(math.radians(a)) * r
y = math.sin(math.radians(a)) * r
a = 360 / n * (1 + i + rot_offset)
xx = math.cos(math.radians(a)) * r
yy = math.sin(math.radians(a)) * r
draw_rect(t, x, y, xx, yy, thickness)
if __name__ == "__main__":
t = turtle.Turtle()
t.screen.setup(540, 540)
t.penup()
t.speed("fastest")
sides = 6
draw_pie(t, 450, sides, ["#dd2", "orange", "#d02"])
t.color("white")
for r in range(50, 500, 100):
draw_reg_polygon(t, r, sides, 45)
t.ht()
turtle.exitonclick()

Change my Python Turtle code for N columns and M rows

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!

how can i get turtle to draw a web spiral?

im trying to get my turtle to draw a spiralling spiderweb but i just cant get the spiral to spiral or to loop until it hits the edge. ive tried several different things but i cant work it out. im pretty new to coding :)
from turtle import *
from math import sin, cos, pi
from random import randint
shape("circle")
turtlesize(0.3)
speed(5)
n=int(input("give number of main lines: "))
r=int(input("give length of main lines: "))
spiraldistance=r/10
angle=360/n
rad=(pi*angle)/180
for i in range(n):
forward(r)
backward(r)
right(hoek)
x=cos(rad*0)*spiraldistance
y=sin(rad*0)*spiraldistance
goto(x,y)
integers = []
for j in range(0, r):
p = 10/n
integers.append(j)
integers.append(p)
x=cos(rad*j)*(spiraldistance+p)
y=sin(rad*j)*(spiraldistance+p)
goto(x,y)
input("Press enter to finish")
i need it to spiral this way look at the screenshots
https://gyazo.com/028228823b7aab611db144436cf93868
https://gyazo.com/5c9ca19cfa34be5559bdbc3365f65f0d
pls help :(
Inside loop you have to change p but you always use the same value
p = 10/n
If you use += instead of =
p += 10/n
then you can get spiral
Example code:
from turtle import *
from math import sin, cos, pi
shape("circle")
turtlesize(0.3)
speed(0)
#---
#n = int(input("give number of main lines: "))
n = 5
#r = int(input("give length of main lines: "))
r = 200
#---
angle = 360/n
for i in range(n):
forward(r)
backward(r)
right(angle)
#---
spiraldistance = r/10
rad = (pi*angle)/180
p = 0
for j in range(r):
x = cos(rad*j) * (spiraldistance+p)
y = sin(rad*j) * (spiraldistance+p)
goto(x, y)
p += 10/n
exitonclick()
Result for n = 5:
Result for n = 15:
EDIT: To stop spiral before end of lines which have leght r you would have to compare spiraldistance+p with r` - ie
if spiraldistance+p >= r:
break
Or better use while loop for this
spiraldistance = r/10
rad = (pi*angle)/180
p = 0
j = 0
while spiraldistance+p < r:
x = cos(rad*j) * (spiraldistance+p)
y = sin(rad*j) * (spiraldistance+p)
goto(x, y)
p += 10/n
j += 1
EDIT: I added steps to choose how many times spiral "cross" every line.
from turtle import *
from math import sin, cos, pi
shape("circle")
turtlesize(0.3)
speed(0)
#--- settings ---
# number of main lines
#n = int(input("give number of main lines: "))
n = 15
# length of main lines
#r = int(input("give length of main lines: "))
length = 200
# number of steps on every main line
steps = 15
#--- main lines ---
angle = 360/n
for i in range(n):
forward(length)
backward(length)
right(angle)
#--- spiral ---
p = (length/n)/steps
rad = (pi*angle)/180
spiraldistance = 0
j = 0
while spiraldistance < length:
spiraldistance += p
x = cos(j) * spiraldistance
y = sin(j) * spiraldistance
goto(x, y)
j += rad
#--- keep open ---
#mainloop()
exitonclick()
Steps 5 and 15:

How to render Mandelbrot Set faster?

I'm currently drawing the Mandelbrot set pixel by pixel with PhotoImage and tkinter. I'm using basically the algorithm directly with no modifications. Are there methods to make the calculation faster? Maybe filling in large areas of color quickly, or precalcuating constants?
Part of the code:
ITERATIONS = 50
WIDTH, HEIGHT = 600, 600
CENTER = (-.5, 0)
DIAMETER = 2.5
def mandel(c):
z = 0
for i in range(ITERATIONS):
z = z**2 + c
if abs(z) > 2:
return i
return ITERATIONS
root = Tk()
canvas = Canvas(root, width=WIDTH,height=HEIGHT)
canvas.pack()
img = PhotoImage(width=WIDTH, height=HEIGHT)
canvas.create_image((WIDTH/2, HEIGHT/2), image=img, state="normal")
real = CENTER[0] - 0.5 * DIAMETER
imag = CENTER[1] - 0.5 * DIAMETER
def color(i):
colors = ("#0000AA", "#88DDFF", "#FF8800", "#000000")
if i == ITERATIONS:
return colors[-1]
else:
choice = (i//2) % len(colors)
return colors[choice]
for x in range(WIDTH):
for y in range(HEIGHT):
i = mandel(complex(real, imag))
img.put(color(i), (x, HEIGHT-y))
imag += DIAMETER / HEIGHT
imag = CENTER[1] - 0.5 * DIAMETER
real += DIAMETER / WIDTH
mainloop()
Setting one pixel at a time is likely the main source of the slowdown. Instead of calling put for each pixel, computer a whole row of pixels, or an entire matrix of pixels, and then call put one time at the end of the loop.
You can find an example here, among other places: https://web.archive.org/web/20170512214049/http://tkinter.unpythonic.net:80/wiki/PhotoImage#Fill_Many_Pixels_at_Once
Here is my code, it draws a 640x480 Mandelbrot in 8-9 seconds.
It does up to 256 iterations per pixel, uses a color map list, 'puts' only once to PhotoImage and doesn't rely on symetry, so it could show any zoomed area of the set.
It's a pity that Tkinter doesn't allow access to the raster information of PhotoImage as a buffer and that the clumsy string is required.
from tkinter import Tk, Canvas, PhotoImage,NW,mainloop
from time import clock
def mandel(kx,ky):
""" calculates the pixel color of the point of mandelbrot plane
passed in the arguments """
global clr
maxIt = 256
c = complex(kx, ky)
z = complex(0.0, 0.0)
for i in range(maxIt):
z = z * z + c
if abs(z) >= 2.0:
return (255-clr[i],0,0)
return(0,0,0)
def prepare_mdb(xa,xb,ya,yb):
""" pre-calculates coordinates of the mandelbrot plane required for each
pixel in the screen"""
global x,y,xm,ym
xm.clear
ym.clear
xm=[xa + (xb - xa) * kx /x for kx in range(x)]
ym=[ya + (yb - ya) * ky /y for ky in range(y)]
x=640
y=480
#corners of the mandelbrot plan to display
xa = -2.0; xb = 1.0
ya = -1.5; yb = 1.5
#precalculated color table
clr=[ int(255*((i/255)**12)) for i in range(255,-1,-1)]
xm=[]
ym=[]
prepare_mdb(xa,xb,ya,yb)
#Tk
window = Tk()
canvas = Canvas(window, width = x, height = y, bg = "#000000")
t1=clock()
img = PhotoImage(width = x, height = y)
canvas.create_image((0, 0), image = img, state = "normal", anchor = NW)
pixels=" ".join(("{"+" ".join(('#%02x%02x%02x' % mandel(i,j) for i in xm))+"}" for j in ym))
img.put(pixels)
canvas.pack()
print(clock()-t1)
mainloop()
Pure python is not that fast for numeric code. The easiest way to speed things up would be to use PyPy. If that is not fast enough, vectorize your algorithms using numpy. If that is still not fast enough, use Cython, or consider rewriting it in C.
For a modest increase in speed (but not enough to offset the difference between a compiled language and an interpreted one), you can precalculate some of the values.
Right now, you're calculating DIAMETER / HEIGHT once per inner loop, and CENTER[1] - 0.5 * DIAMETER as well as DIAMETER / WIDTH once per outer loop. Do this beforehand.
len(colors) also won't change and can be replaced by a constant. In fact, I'd probably write that function as
def color(i):
if i == ITERATIONS:
return "#000000"
else:
return ("#0000AA", "#88DDFF", "#FF8800", "#000000")[(i//2) % 4]
# are you sure you don't want ("#0000AA", "#88DDFF", "#FF8800")[(i//2) % 3] ?
Also, x**2 is slower than x*x (because the x**y operator doesn't shortcut for the trivial case of y==2), so you can speed that calculation up a bit.
Most time is spent in the inner loop in mandel(). z*z instead of z**2 had a slight effect. There is not much else to speed up there that I can see. Removing constants from other loops had little effect, though I tend to prefer doing so. Choosing ITERATIONS so that ITERATIONS//2 % len(colors) == len(colors)-1, as in 46 //2 % 4 == 3, allows simplification of the code. Exploiting symmetry around the x-axis cuts time in half. Starting imag at 0 avoids the roundoff error of 300 subtractions from +/- DIAMETER / 2 and results in a clean center line in the image.
from tkinter import *
ITERATIONS = 46
WIDTH, HEIGHT = 601, 601 # odd for centering and exploiting symmetry
DIAMETER = 2.5
start = (-.5 - DIAMETER / 2, 0) # Start y on centerline
d_over_h = DIAMETER / HEIGHT
d_over_w = DIAMETER / WIDTH
def mandel(c):
z = 0
for i in range(ITERATIONS):
z = z*z + c
if abs(z) > 2:
return i
return ITERATIONS
root = Tk()
canvas = Canvas(root, width=WIDTH,height=HEIGHT)
canvas.pack()
img = PhotoImage(width=WIDTH, height=HEIGHT)
canvas.create_image(((WIDTH+1)//2, (HEIGHT+1)//2), image=img, state="normal")
real, imag = start
colors = ("#0000AA", "#88DDFF", "#FF8800", "#000000")
ncolors = len(colors)
yrange = range(HEIGHT//2, -1, -1) # up from centerline
ymax = HEIGHT - 1
for x in range(WIDTH):
for y in yrange:
i = mandel(complex(real, imag))
color = colors[i//2 % ncolors]
img.put(color, (x, y))
img.put(color, (x, ymax - y))
imag += d_over_h
imag = start[1]
real += d_over_w
mainloop()
Complex numbers in python can be slow, especially if you call abs(x) every iteration. Representing the complex number with c_r and c_i, for real and imaginary parts, reduces the number of calculations you do every iteration.
def mandel(c):
z = 0
for i in range(ITERATIONS):
z = z**2 + c
if abs(z) > 2:
return i
return ITERATIONS
instead of z = 0, replace it with z_r,z_i=0,0
we also have to change c in the parameters.
Now we have:
def mandel(c_r,c_i):
z_r = 0
z_i = 0
for i in range(ITERATIONS):
z = z**2 + c
if abs(z) > 2:
return i
return ITERATIONS
Instead of using abs(z) > 2, we can now use z_r * z_r + z_i + z_i > 4
Also, we replace z**2 + c with a new version using our new variables
(Know that (a+bi)^2 = a^2 - b^2 + 2abi
def mandel(c_r,c_i):
z_r = 0
z_i = 0
z_r_squared = 0
z_i_squared = 0
for i in range(ITERATIONS):
z_r_squared = z_r * z_r
z_i_squared = z_i * z_i
z_r = z_r_squared - z_i_squared + c_r
z_i = 2 * z_r * z_i + c_i
if z_r_squared + z_r_squared > 4:
return i
return ITERATIONS
Finally, you have to change where the mandelbrot function is called, so
i = mandel(complex(real, imag))
becomes
i = mandel(real, imag)

Drawing directions fields

Is there a way to draw direction fields in python?
My attempt is to modify http://www.compdigitec.com/labs/files/slopefields.py giving
#!/usr/bin/python
import math
from subprocess import CalledProcessError, call, check_call
def dy_dx(x, y):
try:
# declare your dy/dx here:
return x**2-x-2
except ZeroDivisionError:
return 1000.0
# Adjust window parameters
XMIN = -5.0
XMAX = 5.0
YMIN = -10.0
YMAX = 10.0
XSCL = 0.5
YSCL = 0.5
DISTANCE = 0.1
def main():
fileobj = open("data.txt", "w")
for x1 in xrange(int(XMIN / XSCL), int(XMAX / XSCL)):
for y1 in xrange(int(YMIN / YSCL), int(YMAX / YSCL)):
x= float(x1 * XSCL)
y= float(y1 * YSCL)
slope = dy_dx(x,y)
dx = math.sqrt( DISTANCE/( 1+math.pow(slope,2) ) )
dy = slope*dx
fileobj.write(str(x) + " " + str(y) + " " + str(dx) + " " + str(dy) + "\n")
fileobj.close()
try:
check_call(["gnuplot","-e","set terminal png size 800,600 enhanced font \"Arial,12\"; set xrange [" + str(XMIN) + ":" + str(XMAX) + "]; set yrange [" + str(YMIN) + ":" + str(YMAX) + "]; set output 'output.png'; plot 'data.txt' using 1:2:3:4 with vectors"])
except (CalledProcessError, OSError):
print "Error: gnuplot not found on system!"
exit(1)
print "Saved image to output.png"
call(["xdg-open","output.png"])
return 0
if __name__ == '__main__':
main()
However the best image I get from this is.
How can I get an output that looks more like the first image? Also, how can I add the three solid lines?
You can use this matplotlib code as a base. Modify it for your needs.
I have updated the code to show same length arrows. The important option is to set the angles option of the quiver function, so that the arrows are correctly printed from (x,y) to (x+u,y+v) (instead of the default, which just takes into account of (u,v) when computing the angles).
It is also possible to change the axis form "boxes" to "arrows". Let me know if you need that change and I could add it.
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import numpy as np
fig = plt.figure()
def vf(x, t):
dx = np.zeros(2)
dx[0] = 1.0
dx[1] = x[0] ** 2 - x[0] - 2.0
return dx
# Solution curves
t0 = 0.0
tEnd = 10.0
# Vector field
X, Y = np.meshgrid(np.linspace(-5, 5, 20), np.linspace(-10, 10, 20))
U = 1.0
V = X ** 2 - X - 2
# Normalize arrows
N = np.sqrt(U ** 2 + V ** 2)
U = U / N
V = V / N
plt.quiver(X, Y, U, V, angles="xy")
t = np.linspace(t0, tEnd, 100)
for y0 in np.linspace(-5.0, 0.0, 10):
y_initial = [y0, -10.0]
y = odeint(vf, y_initial, t)
plt.plot(y[:, 0], y[:, 1], "-")
plt.xlim([-5, 5])
plt.ylim([-10, 10])
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
I had a lot of fun making one of these as a hobby project using pygame. I plotted the slope at each pixel, using shades of blue for positive and shades of red for negative. Black is for undefined. This is dy/dx = log(sin(x/y)+cos(y/x)):
You can zoom in & out - here is zoomed in on the middle upper part here:
and also click on a point to graph the line going through that point:
It's just 440 lines of code, so here is the .zip of all the files. I guess I'll excerpt relevant bits here.
The equation itself is input as a valid Python expression in a string, e.g. "log(sin(x/y)+cos(y/x))". This is then compiled. This function here graphs the color field, where self.func.eval() gives the dy/dx at the given point. The code is a bit complicated here because I made it render in stages - first 32x32 blocks, then 16x16, etc. - to make it snappier for the user.
def graphcolorfield(self, sqsizes=[32,16,8,4,2,1]):
su = ScreenUpdater(50)
lastskip = self.xscreensize
quitit = False
for squaresize in sqsizes:
xsquaresize = squaresize
ysquaresize = squaresize
if squaresize == 1:
self.screen.lock()
y = 0
while y <= self.yscreensize:
x = 0
skiprow = y%lastskip == 0
while x <= self.xscreensize:
if skiprow and x%lastskip==0:
x += squaresize
continue
color = (255,255,255)
try:
m = self.func.eval(*self.ct.untranscoord(x, y))
if m >= 0:
if m < 1:
c = 255 * m
color = (0, 0, c)
else:
#c = 255 - 255 * (1.0/m)
#color = (c, c, 255)
c = 255 - 255 * (1.0/m)
color = (c/2.0, c/2.0, 255)
else:
pm = -m
if pm < 1:
c = 255 * pm
color = (c, 0, 0)
else:
c = 255 - 255 * (1.0/pm)
color = (255, c/2.0, c/2.0)
except:
color = (0, 0, 0)
if squaresize > 1:
self.screen.fill(color, (x, y, squaresize, squaresize))
else:
self.screen.set_at((x, y), color)
if su.update():
quitit = True
break
x += xsquaresize
if quitit:
break
y += ysquaresize
if squaresize == 1:
self.screen.unlock()
lastskip = squaresize
if quitit:
break
This is the code which graphs a line through a point:
def _grapheqhelp(self, sx, sy, stepsize, numsteps, color):
x = sx
y = sy
i = 0
pygame.draw.line(self.screen, color, (x, y), (x, y), 2)
while i < numsteps:
lastx = x
lasty = y
try:
m = self.func.eval(x, y)
except:
return
x += stepsize
y = y + m * stepsize
screenx1, screeny1 = self.ct.transcoord(lastx, lasty)
screenx2, screeny2 = self.ct.transcoord(x, y)
#print "(%f, %f)-(%f, %f)" % (screenx1, screeny1, screenx2, screeny2)
try:
pygame.draw.line(self.screen, color,
(screenx1, screeny1),
(screenx2, screeny2), 2)
except:
return
i += 1
stx, sty = self.ct.transcoord(sx, sy)
pygame.draw.circle(self.screen, color, (int(stx), int(sty)), 3, 0)
And it runs backwards & forwards starting from that point:
def graphequation(self, sx, sy, stepsize=.01, color=(255, 255, 127)):
"""Graph the differential equation, given the starting point sx and sy, for length
length using stepsize stepsize."""
numstepsf = (self.xrange[1] - sx) / stepsize
numstepsb = (sx - self.xrange[0]) / stepsize
self._grapheqhelp(sx, sy, stepsize, numstepsf, color)
self._grapheqhelp(sx, sy, -stepsize, numstepsb, color)
I never got around to drawing actual lines because the pixel approach looked too cool.
Try changing your values for the parameters to this:
XSCL = .2
YSCL = .2
These parameters determine how many points are sampled on the axes.
As per your comment, you'll need to also plot the functions for which the derivation dy_dx(x, y) applies.
Currently, you're only calculating and plotting the slope lines as calculated by your function dy_dx(x,y). You'll need to find (in this case 3) functions to plot in addition to the slope.
Start by defining a function:
def f1_x(x):
return x**3-x**2-2x;
and then, in your loop, you'll have to also write the desired values for the functions into the fileobj file.

Categories