[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky] [Facebook]
[Build Your Own Python Action Arcade!]

[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Use the chaos game to draw strange attractor fractals in Python

[A strange attractor made by the chaos game in Python] My post Use random points to draw a Sierpinski gasket fractal in Python and PIL uses an algorithm called the chaos game to draw a strange attractor.

A strange attractor is a fractal produced by a chaotic system that generates points that are attracted to some sort of result. The points don't repeat, but they are inescapably drawn to a shape of some sort that is often vaguely defined or even with a ghostly appearance. The image on the right is an example.

This post generalizes the algorithm used in the previous post so it can use more than three anchor points.

Playing the Chaos Game

The way the algorithm works is you define a number of points that I call anchors and a random starting point. For each turn, you pick one of the anchors and move halfway from the current point to that anchor.

That's all there is to it! If you plot a large number of points, they draw a strange attractor. The game makes the point land in some areas frequently while the point visits other areas rarely or not at all.

My previous example ran the chaos game with three anchors to produce a Sierpinski's triangle. This example lets you set the number of anchors. You'll see the most interesting results when there are around seven or fewer anchors and the number isn't four. Run the example to see what happens if the number is four or more than seven.

Creating Anchors

When you change the number of anchors, the app's reset method executes.

def reset(self): '''Set the number of vertices and define the anchors.''' self.drawing = False self.start_button.config(text='Start') # Start in the center. self.current_x = random.uniform(0, self.canvas_wid) self.current_y = random.uniform(0, self.canvas_hgt) self.num_anchors = int(self.num_vertices_var.get()) # Make the anchors. theta0 = -math.pi / 2 dtheta = 2 * math.pi / self.num_anchors anchors = [( math.cos(theta0 + i * dtheta), math.sin(theta0 + i * dtheta)) for i in range(self.num_anchors)] # Transform the anchors so they fit on the canvas. rect = (self.margin, self.margin, self.canvas_wid - self.margin, self.canvas_hgt - self.margin) self.anchors = transform_points(anchors, rect) # Clear and draw the anchors. self.clear()

[A chaos game strange attractor with three anchors] The code sets self.drawing to False to stop drawing if drawing is in progress.

The code then sets the program's current point to a random location on the program's canvas.

Next, the code sets theta0 to -math.pi / 2 so the first anchor is at the top of the canvas. It sets dtheta so it completes a full circle in the desired number of anchors.

The code then uses a list comprehension to loop through the desired number of anchors and generates points for them. That code gives a set of points that are centered around the origin and distance 1 away from it. The method gets a rectangle representing the canvas's area minus a margin around the edges and calls the transform_points function to map the points so they lie within the target rectangle. For information on that function, see my post Map points so they fit within a target area in Python.

The method finishes by calling self.clear to clear the canvas. (That method is pretty straightforward so I won't show it here.)

Generating Points

The draw_points method generates the points.

def draw_points(self): '''Draw some points on the gasket.''' # See if we should stop. if not self.drawing: return # Make a list of new points. new_points = [] # Generate points. for i in range(self.speed_var.get()): # Pick a random point. anchor = random.choice(self.anchors) self.current_x = (self.current_x + anchor[0]) / 2 self.current_y = (self.current_y + anchor[1]) / 2 new_points.append((self.current_x, self.current_y)) # Draw the points on the gasket. self.dr.point(new_points, fill='red') # Display the current result. self.display_image() # Draw again later. self.window.after(10, self.draw_points)

[A chaos game strange attractor with six anchors] If self.drawing is False, the user wants to stop drawing so the method simply returns.

If self.drawing is not False, the code creates a list to hold new points and enters a loop to generate those points. The number of points it generates depends on the value of the Scale widget which is attached to the variable self.speed_var.

For each point, the code picks a random anchor, moves halfway between the current point and the anchor, and adds the new current point to the new_points list.

After it has generated the points, the code calls self.dr.point to draw the points. The self.dr value is an ImageDraw.Draw object that can draw on the current image displayed on the canvas.

The code calls self.display_image to display the current image (it's straightforward so I won't show it here) and then calls after to schedule draw_points to run again in 10 milliseconds.

Conclusion

Those are the program's key pieces. I've skipped a bunch of details, but they're straightforward so I'm sure you can figure them out by looking at the code. Download the program to see those details.
[A Sierpinski pyramid fractal] The picture on the left is a three-dimensional version of the Sierpinski gasket called the Sierpinski pyramid.

This picture was generated by techniques described in my book Build Your Own Ray Tracer With Python.

[Build Your Own Ray Tracer With Python]
© 2024 - 2026 Rocky Mountain Computer Consulting, Inc. All rights reserved.