1
votes

I'm drawing the following plot using Matplotlib:

import matplotlib.pyplot as mlp
import numpy.linalg as npl

def ploteig(self, erg:bool) -> None:

    theta = np.arange(start=0, stop=2.0*np.pi, step=0.01)
    r = np.ones(len(theta))

    values, _ = npl.eig(self._p)
    values = values.astype(complex)

    x_unit_circle = r * np.cos(theta)
    y_unit_circle = r * np.sin(theta)

    x_eigenvalues = np.unique(np.append(values, np.complex(1.0)))
    y_eigenvalues = np.zeros(len(x_eigenvalues))

    has_slem = False

    if erg:
        values_abs = np.sort(np.abs(values))
        values_ct1 = np.isclose(values_abs, 1.0)
        if not np.all(values_ct1):
            mu = values_abs[~values_ct1][-1]
            if not np.isclose(mu, 0.0):
                r *= mu;
                x_slem_circle = r * np.cos(theta)
                y_slem_circle = r * np.sin(theta)
                has_slem = True

    fig, ax = mlp.subplots()
    ax.plot(x_unit_circle, y_unit_circle, color='red', linestyle='-', linewidth=3)
    ax.plot(x_eigenvalues, y_eigenvalues, color='blue', linestyle='None', marker='*', markersize=10)

    if has_slem:
        ax.plot(x_slem_circle, y_slem_circle, color='red', linestyle='--', linewidth=1)

    ax.grid(True)
    ax.set_aspect('equal', 'datalim')

    mlp.show()

When has_slem is True, then the slem circle is always smaller than the unit circle, hence the plot produces two concentric circles where the outer circle is given by (x_unit_circle,y_unit_circle) and the inner circle is given by (x_slem_circle,y_slem_circle).

What I would like to do is to fill the area between the two circles with a light red color. This is what I tried so far:

if has_slem:
    ax.plot(x_slem_circle, y_slem_circle, color='red', linestyle='--', linewidth=1)
    ax.fill_between(x_unit_circle, y_unit_circle, -y_unit_circle, color="red", alpha=0.2)
    ax.fill_between(x_slem_circle, y_slem_circle, -y_slem_circle, color="white")

But this approach has two problems:

  1. If the axes color is changed, the second fill_between call would produce a wrong fill based on white color.
  2. The filled area looks a little bit misaligned with respect to the inner circle (there is a small white gap), as you can see on the screenshot below.

Plot

So, here comes my question: is there a better and more precise approach for filling the area between the two circles that allows me to bypass both problems?

On a totally unrelated note: is it ok to call mlp.show() inside the function? I don't know what are the best practices here... maybe it's better to return the figure handle and let the consumer decide when to pop it up?

1

1 Answers

1
votes

pyplot.contourf is what you are looking for. Something that can go like this:

# inner radius
inner = 0.5

# the two circles
thetas = np.linspace(0,2*np.pi, 200)
# you don't need r = np.one(len(thetas))
x_unit_circle = np.cos(thetas)
y_unit_circle = np.sin(thetas)

x_eigens = x_unit_circle * inner
y_eigens = y_unit_circle * inner

xs = np.linspace(-1.1,1.1, 201)
ys = np.linspace(-1.1,1.1, 201)

# mesh for contours
xv,yv = np.meshgrid(xs,ys)

# generate the level map
r = xv**2 + yv**2

pyplot.figure(figsize=(8,8))

# plot the contours with two levels only
# notice the xv, yv parameters
pyplot.contourf(xv, yv, r, levels=[inner**2,1], colors=('r','g','b'))

# plot the two circles
pyplot.plot(x_unit_circle, y_unit_circle, color='b', linewidth=3)
pyplot.plot(x_eigens, y_eigens, color='g', linewidth=3, linestyle='--')

pyplot.show()

and result in different background: enter image description here