Issue
The following code in a script works as expected,
from sympy import *
x = symbols('x')
p = plot(x, x*(1-x), (x, 0, 1))
ax = p._backend.ax[0]
ax.set_yticks((0, .05, .25))
p._backend.fig.savefig('Figure_1.png')
but when I copy the code above in a notebook cell, this is what I get
If it is possible to manipulate the (hidden) attributes of a Sympy's plot
when one works in a Jupyter notebook, how can it be done?
Solution
- As per this answer of Display two Sympy plots as two Matplotlib subplots, with inline mode in Jupyter,
p.backend(p)
must be used. - The problem is that
sympy.plotting.plot.plot(*args, show=True, **kwargs)
creates its own figure and axes andplots.show()
displays the plot immediately.- Because of the way inline mode works, the plot is shown before the changes are implemented by
ax.set_yticks((0, .05, .25))
.
- Because of the way inline mode works, the plot is shown before the changes are implemented by
- Tested in
python v3.12.0
,matplotlib v3.8.1
,sympy v1.11.1
.
from sympy import symbols, plot
import matplotlib.pyplot as plt
x = symbols('x')
# note show=False, the default is True
p = plot(x, x*(1-x), (x, 0, 1), show=False)
fig, ax = plt.subplots()
backend = p.backend(p)
backend.ax = ax
backend._process_series(backend.parent._series, ax, backend.parent)
backend.ax.set_yticks((0, .05, .25))
plt.close(backend.fig)
plt.show()
- In interactive mode
%matplotlib qt
, the code in the OP works fine.
from sympy import symbols, plot
import matplotlib.pyplot as plt
%matplotlib qt # %matplotlib inline - to revert to inline
x = symbols('x')
p = plot(x, x*(1-x), (x, 0, 1))
ax = p._backend.ax[0]
ax.set_yticks((0, .05, .25))
With a custom backend instance.
This has as the advantage that matplotlib
should not be explicitly imported.
One should be aware of what you are doing: sympy
provides a ready-to-use interface to plot functions on the fly, without the need to worry about (boring) mathematical technicality such as domain/range. If instead you try to act against such defaults you need to go back to the source code and do some reverse engineering.
In the OP, custom ticks are required, but by default the MatplotlibBackend
sets a linear
scale, for both x & y, from the sympy.plotting.plot.Plot
object, xscale='linear', yscale='linear'
, which interfere with the matplotlib.axes.Axes.set_yticks
method.
From the source code
1467 if parent.yscale and not isinstance(ax, Axes3D):
1468 ax.set_yscale(parent.yscale)
By setting parent.yscale
to False
(or None
, ''
) the condition will never be meet.
super
is built-in python method used for accessing inherited methods that have been overridden in a class.
from sympy.plotting.plot import MatplotlibBackend
from sympy import symbols
class JupyterPlotter(MatplotlibBackend):
def _process_series(self, series, ax, parent):
parent.yscale = False
ax.set_yticks((0, .05, .25)) # custom yticks
super()._process_series(series, ax, parent)
x = symbols('x')
p = plot(x, x*(1-x), (x, 0, 1), backend=JupyterPlotter)
Plots with special needs may require a special implementation, here is a different example.
Answered By - Trenton McKinney
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.