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
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 compile
d. 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.