I'm currently coding a Mandelbrot set using matplotlib with a zoom function to infinitely zoom in on any section of it, however when I zoom in the new data that's calculated is off-center from the graph axes.
This screenshot shows the graph after one zoom in - the set isn't generated correctly for the axes. The graph should be symmetrical about y=0, but it isn't - it changes whenever I zoom. I can't think of what could be causing this - I can't see any issue in the code. How do i fix this?
Here's my code:
import numpy as np
import matplotlib.pyplot as plt
def mandelbrot_set(xmin, xmax, ymin, ymax, width, height, maxiter):
x = np.linspace(xmin, xmax, width).reshape((1, width))
y = np.linspace(ymin, ymax, height).reshape((height, 1))
c = x + y * 1j
z = c
fractal = np.zeros(z.shape, dtype=int)
for i in range(maxiter):
z = z**2 + c
mask = np.abs(z) > 2
fractal += mask
z[mask] = 2
return fractal
xmin, xmax, ymin, ymax = -2, 1, -1.5, 1.5
maxiter = 300
width, height = 500, 500
fractal = mandelbrot_set(xmin, xmax, ymin, ymax, width, height, maxiter)
fig, ax = plt.subplots(figsize=(10, 10))
im = ax.imshow(fractal, cmap='magma', extent=(xmin, xmax, ymin, ymax))
def update_plot(xmin, xmax, ymin, ymax):
fractal = mandelbrot_set(xmin, xmax, ymin, ymax, width, height, maxiter)
im.set_data(fractal)
im.set_extent((xmin, xmax, ymin, ymax))
fig.canvas.draw()
def on_scroll(event):
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
x, y = event.xdata, event.ydata
if event.button == 'up':
scale_factor = 0.5
else:
scale_factor = 2.0
xmin = x - (x - xmin)*scale_factor
xmax = x + (xmax - x)*scale_factor
ymin = y - (y - ymin)*scale_factor
ymax = y + (ymax - y)*scale_factor
print(f"xmin: {xmin}, xmax: {xmax}, ymin: {ymin}, ymax: {ymax}")
update_plot(xmin, xmax, ymin, ymax)
fig.canvas.mpl_connect('scroll_event', on_scroll)
plt.show()
Your issue is that imshow
by default plots with origin='upper'
(https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.imshow.html#matplotlib.axes.Axes.imshow) meaning Y axis is reversed, breaking your boundary computation downstream, use
im = ax.imshow(fractal, cmap='magma', extent=(xmin, xmax, ymin, ymax), origin='lower')
and it will solve your problems.
Also not sure if this was on purpose from your side, but this bondary update rules seem to make more sense?
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
x_scale = xmax - xmin
y_scale = ymax - ymin
dx = x_scale/2*scale_factor
dy = y_scale/2*scale_factor
xmin = x - dx
xmax = x + dx
ymin = y - dy
ymax = y + dy