This following code below consists of a matplotlib graph of the function y > 5/x, with the ability to fill in the graph as the user pans/zooms outward.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x_range = np.linspace(-10, 10, 400)
y_range = 5 / x_range
line, = ax.plot(x_range, y_range, 'r', linewidth=2, linestyle='--')
ax.fill_between(x_range, y_range, y_range.max(), alpha=0.3, color='gray')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('Inequality: y > 5 / x')
ax.axhline(0, color='black',linewidth=3)
ax.axvline(0, color='black',linewidth=3)
ax.grid(color='gray', linestyle='--', linewidth=0.5)
def update_limits(event):
xlim = ax.get_xlim()
x_range = np.linspace(xlim[0], xlim[1], max(200, int(200 * (xlim[1] - xlim[0]))))
y_range = 5 / x_range
line.set_data(x_range, y_range)
for collection in ax.collections:
collection.remove()
ax.fill_between(x_range, y_range, max(y_range.max(), ax.get_ylim()[1]), alpha=0.3, color='gray')
plt.draw()
fig.canvas.mpl_connect('button_release_event', update_limits)
plt.show()
I've been trying to convert this concept into code that uses the sympy module (by accessing the Matplotlib backends), which has better control over algebraic functions. However, the following code below, does not seem to fill in the graph as the user pans outward. Why is this the case and how do I fix it?
import sympy as sp
from sympy.plotting.plot import MatplotlibBackend
x, y = sp.symbols('x y')
# Define the implicit plot
p1 = sp.plot_implicit(sp.And(y > 5 / x), (x, -10, 10), (y, -10, 10), show=False)
mplPlot = MatplotlibBackend(p1)
mplPlot.process_series()
mplPlot.fig.tight_layout()
mplPlot.ax[0].set_xlabel("x-axis")
mplPlot.ax[0].set_ylabel("y-axis")
def update_limits(event):
global mplPlot
xmin, xmax = mplPlot.ax[0].get_xlim()
ymin, ymax = mplPlot.ax[0].get_ylim()
p1 = sp.plot_implicit(sp.And(y > 5 / x), (x, xmin, xmax), (y, ymin, ymax), show=False)
mplPlot = MatplotlibBackend(p1)
mplPlot.process_series()
mplPlot.fig.tight_layout()
mplPlot.ax[0].set_xlabel("x-axis")
mplPlot.ax[0].set_ylabel("y-axis")
mplPlot.plt.draw()
mplPlot.fig.canvas.mpl_connect('button_release_event', update_limits)
mplPlot.plt.show()
UPDATE: After some debugging (with the sympy code), I have found that the xmax, xmin variables in the update_limits function are only changed once and they stay that way for the rest of the duration that the program is run. If possible I would also like to know why this is.
UPDATE 2: If instead of running mplPlot.plt.draw(), you run mplPlot.plt.show(), a new window is created with the correct graph. This is not what I want, as I want the changes to be put on the same window. Another buggy behavior is revealed when I do this as well, which is when I pan far enough into Quadrant IV of the graph, the graph seems to become unresponsive. This doesn't happen all the time and I can't find an explanation for it. If anyone knows why this is the case, feel free to add that into your answer!
I'm going to use the SymPy Plotting Backend's module, because it already has a lot of code written for interactivity. At the time of writing this answer, version 3.1.1 is out, but it doesn't implement the pan/zoom/etc. events. Still, we can easily implement them.
The first thing you can try is an implicit plot:
There is a problem with this visualization: the function is undefined at x=0, so there shouldn't be that vertical red dashed line. Implicit plotting relies on Matplotlib's
contourfunctionality: it is extremely hard to implement undefined "points/lines" on contour plotting.We can improve the visualization by using
fill_between, like in your first approach. However, the plotting module doesn't implement that functionality. We must create it, like this: