chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
from sympy.plotting.series import BaseSeries, GenericDataSeries
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
from sympy.utilities.iterables import is_sequence
|
||||
|
||||
|
||||
__doctest_requires__ = {
|
||||
('Plot.append', 'Plot.extend'): ['matplotlib'],
|
||||
}
|
||||
|
||||
|
||||
# Global variable
|
||||
# Set to False when running tests / doctests so that the plots don't show.
|
||||
_show = True
|
||||
|
||||
def unset_show():
|
||||
"""
|
||||
Disable show(). For use in the tests.
|
||||
"""
|
||||
global _show
|
||||
_show = False
|
||||
|
||||
|
||||
def _deprecation_msg_m_a_r_f(attr):
|
||||
sympy_deprecation_warning(
|
||||
f"The `{attr}` property is deprecated. The `{attr}` keyword "
|
||||
"argument should be passed to a plotting function, which generates "
|
||||
"the appropriate data series. If needed, index the plot object to "
|
||||
"retrieve a specific data series.",
|
||||
deprecated_since_version="1.13",
|
||||
active_deprecations_target="deprecated-markers-annotations-fill-rectangles",
|
||||
stacklevel=4)
|
||||
|
||||
|
||||
def _create_generic_data_series(**kwargs):
|
||||
keywords = ["annotations", "markers", "fill", "rectangles"]
|
||||
series = []
|
||||
for kw in keywords:
|
||||
dictionaries = kwargs.pop(kw, [])
|
||||
if dictionaries is None:
|
||||
dictionaries = []
|
||||
if isinstance(dictionaries, dict):
|
||||
dictionaries = [dictionaries]
|
||||
for d in dictionaries:
|
||||
args = d.pop("args", [])
|
||||
series.append(GenericDataSeries(kw, *args, **d))
|
||||
return series
|
||||
|
||||
|
||||
class Plot:
|
||||
"""Base class for all backends. A backend represents the plotting library,
|
||||
which implements the necessary functionalities in order to use SymPy
|
||||
plotting functions.
|
||||
|
||||
For interactive work the function :func:`plot` is better suited.
|
||||
|
||||
This class permits the plotting of SymPy expressions using numerous
|
||||
backends (:external:mod:`matplotlib`, textplot, the old pyglet module for SymPy, Google
|
||||
charts api, etc).
|
||||
|
||||
The figure can contain an arbitrary number of plots of SymPy expressions,
|
||||
lists of coordinates of points, etc. Plot has a private attribute _series that
|
||||
contains all data series to be plotted (expressions for lines or surfaces,
|
||||
lists of points, etc (all subclasses of BaseSeries)). Those data series are
|
||||
instances of classes not imported by ``from sympy import *``.
|
||||
|
||||
The customization of the figure is on two levels. Global options that
|
||||
concern the figure as a whole (e.g. title, xlabel, scale, etc) and
|
||||
per-data series options (e.g. name) and aesthetics (e.g. color, point shape,
|
||||
line type, etc.).
|
||||
|
||||
The difference between options and aesthetics is that an aesthetic can be
|
||||
a function of the coordinates (or parameters in a parametric plot). The
|
||||
supported values for an aesthetic are:
|
||||
|
||||
- None (the backend uses default values)
|
||||
- a constant
|
||||
- a function of one variable (the first coordinate or parameter)
|
||||
- a function of two variables (the first and second coordinate or parameters)
|
||||
- a function of three variables (only in nonparametric 3D plots)
|
||||
|
||||
Their implementation depends on the backend so they may not work in some
|
||||
backends.
|
||||
|
||||
If the plot is parametric and the arity of the aesthetic function permits
|
||||
it the aesthetic is calculated over parameters and not over coordinates.
|
||||
If the arity does not permit calculation over parameters the calculation is
|
||||
done over coordinates.
|
||||
|
||||
Only cartesian coordinates are supported for the moment, but you can use
|
||||
the parametric plots to plot in polar, spherical and cylindrical
|
||||
coordinates.
|
||||
|
||||
The arguments for the constructor Plot must be subclasses of BaseSeries.
|
||||
|
||||
Any global option can be specified as a keyword argument.
|
||||
|
||||
The global options for a figure are:
|
||||
|
||||
- title : str
|
||||
- xlabel : str or Symbol
|
||||
- ylabel : str or Symbol
|
||||
- zlabel : str or Symbol
|
||||
- legend : bool
|
||||
- xscale : {'linear', 'log'}
|
||||
- yscale : {'linear', 'log'}
|
||||
- axis : bool
|
||||
- axis_center : tuple of two floats or {'center', 'auto'}
|
||||
- xlim : tuple of two floats
|
||||
- ylim : tuple of two floats
|
||||
- aspect_ratio : tuple of two floats or {'auto'}
|
||||
- autoscale : bool
|
||||
- margin : float in [0, 1]
|
||||
- backend : {'default', 'matplotlib', 'text'} or a subclass of BaseBackend
|
||||
- size : optional tuple of two floats, (width, height); default: None
|
||||
|
||||
The per data series options and aesthetics are:
|
||||
There are none in the base series. See below for options for subclasses.
|
||||
|
||||
Some data series support additional aesthetics or options:
|
||||
|
||||
:class:`~.LineOver1DRangeSeries`, :class:`~.Parametric2DLineSeries`, and
|
||||
:class:`~.Parametric3DLineSeries` support the following:
|
||||
|
||||
Aesthetics:
|
||||
|
||||
- line_color : string, or float, or function, optional
|
||||
Specifies the color for the plot, which depends on the backend being
|
||||
used.
|
||||
|
||||
For example, if ``MatplotlibBackend`` is being used, then
|
||||
Matplotlib string colors are acceptable (``"red"``, ``"r"``,
|
||||
``"cyan"``, ``"c"``, ...).
|
||||
Alternatively, we can use a float number, 0 < color < 1, wrapped in a
|
||||
string (for example, ``line_color="0.5"``) to specify grayscale colors.
|
||||
Alternatively, We can specify a function returning a single
|
||||
float value: this will be used to apply a color-loop (for example,
|
||||
``line_color=lambda x: math.cos(x)``).
|
||||
|
||||
Note that by setting line_color, it would be applied simultaneously
|
||||
to all the series.
|
||||
|
||||
Options:
|
||||
|
||||
- label : str
|
||||
- steps : bool
|
||||
- integers_only : bool
|
||||
|
||||
:class:`~.SurfaceOver2DRangeSeries` and :class:`~.ParametricSurfaceSeries`
|
||||
support the following:
|
||||
|
||||
Aesthetics:
|
||||
|
||||
- surface_color : function which returns a float.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
How the plotting module works:
|
||||
|
||||
1. Whenever a plotting function is called, the provided expressions are
|
||||
processed and a list of instances of the
|
||||
:class:`~sympy.plotting.series.BaseSeries` class is created, containing
|
||||
the necessary information to plot the expressions
|
||||
(e.g. the expression, ranges, series name, ...). Eventually, these
|
||||
objects will generate the numerical data to be plotted.
|
||||
2. A subclass of :class:`~.Plot` class is instantiaed (referred to as
|
||||
backend, from now on), which stores the list of series and the main
|
||||
attributes of the plot (e.g. axis labels, title, ...).
|
||||
The backend implements the logic to generate the actual figure with
|
||||
some plotting library.
|
||||
3. When the ``show`` command is executed, series are processed one by one
|
||||
to generate numerical data and add it to the figure. The backend is also
|
||||
going to set the axis labels, title, ..., according to the values stored
|
||||
in the Plot instance.
|
||||
|
||||
The backend should check if it supports the data series that it is given
|
||||
(e.g. :class:`TextBackend` supports only
|
||||
:class:`~sympy.plotting.series.LineOver1DRangeSeries`).
|
||||
|
||||
It is the backend responsibility to know how to use the class of data series
|
||||
that it's given. Note that the current implementation of the ``*Series``
|
||||
classes is "matplotlib-centric": the numerical data returned by the
|
||||
``get_points`` and ``get_meshes`` methods is meant to be used directly by
|
||||
Matplotlib. Therefore, the new backend will have to pre-process the
|
||||
numerical data to make it compatible with the chosen plotting library.
|
||||
Keep in mind that future SymPy versions may improve the ``*Series`` classes
|
||||
in order to return numerical data "non-matplotlib-centric", hence if you code
|
||||
a new backend you have the responsibility to check if its working on each
|
||||
SymPy release.
|
||||
|
||||
Please explore the :class:`MatplotlibBackend` source code to understand
|
||||
how a backend should be coded.
|
||||
|
||||
In order to be used by SymPy plotting functions, a backend must implement
|
||||
the following methods:
|
||||
|
||||
* show(self): used to loop over the data series, generate the numerical
|
||||
data, plot it and set the axis labels, title, ...
|
||||
* save(self, path): used to save the current plot to the specified file
|
||||
path.
|
||||
* close(self): used to close the current plot backend (note: some plotting
|
||||
library does not support this functionality. In that case, just raise a
|
||||
warning).
|
||||
"""
|
||||
|
||||
def __init__(self, *args,
|
||||
title=None, xlabel=None, ylabel=None, zlabel=None, aspect_ratio='auto',
|
||||
xlim=None, ylim=None, axis_center='auto', axis=True,
|
||||
xscale='linear', yscale='linear', legend=False, autoscale=True,
|
||||
margin=0, annotations=None, markers=None, rectangles=None,
|
||||
fill=None, backend='default', size=None, **kwargs):
|
||||
|
||||
# Options for the graph as a whole.
|
||||
# The possible values for each option are described in the docstring of
|
||||
# Plot. They are based purely on convention, no checking is done.
|
||||
self.title = title
|
||||
self.xlabel = xlabel
|
||||
self.ylabel = ylabel
|
||||
self.zlabel = zlabel
|
||||
self.aspect_ratio = aspect_ratio
|
||||
self.axis_center = axis_center
|
||||
self.axis = axis
|
||||
self.xscale = xscale
|
||||
self.yscale = yscale
|
||||
self.legend = legend
|
||||
self.autoscale = autoscale
|
||||
self.margin = margin
|
||||
self._annotations = annotations
|
||||
self._markers = markers
|
||||
self._rectangles = rectangles
|
||||
self._fill = fill
|
||||
|
||||
# Contains the data objects to be plotted. The backend should be smart
|
||||
# enough to iterate over this list.
|
||||
self._series = []
|
||||
self._series.extend(args)
|
||||
self._series.extend(_create_generic_data_series(
|
||||
annotations=annotations, markers=markers, rectangles=rectangles,
|
||||
fill=fill))
|
||||
|
||||
is_real = \
|
||||
lambda lim: all(getattr(i, 'is_real', True) for i in lim)
|
||||
is_finite = \
|
||||
lambda lim: all(getattr(i, 'is_finite', True) for i in lim)
|
||||
|
||||
# reduce code repetition
|
||||
def check_and_set(t_name, t):
|
||||
if t:
|
||||
if not is_real(t):
|
||||
raise ValueError(
|
||||
"All numbers from {}={} must be real".format(t_name, t))
|
||||
if not is_finite(t):
|
||||
raise ValueError(
|
||||
"All numbers from {}={} must be finite".format(t_name, t))
|
||||
setattr(self, t_name, (float(t[0]), float(t[1])))
|
||||
|
||||
self.xlim = None
|
||||
check_and_set("xlim", xlim)
|
||||
self.ylim = None
|
||||
check_and_set("ylim", ylim)
|
||||
self.size = None
|
||||
check_and_set("size", size)
|
||||
|
||||
@property
|
||||
def _backend(self):
|
||||
return self
|
||||
|
||||
@property
|
||||
def backend(self):
|
||||
return type(self)
|
||||
|
||||
def __str__(self):
|
||||
series_strs = [('[%d]: ' % i) + str(s)
|
||||
for i, s in enumerate(self._series)]
|
||||
return 'Plot object containing:\n' + '\n'.join(series_strs)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self._series[index]
|
||||
|
||||
def __setitem__(self, index, *args):
|
||||
if len(args) == 1 and isinstance(args[0], BaseSeries):
|
||||
self._series[index] = args
|
||||
|
||||
def __delitem__(self, index):
|
||||
del self._series[index]
|
||||
|
||||
def append(self, arg):
|
||||
"""Adds an element from a plot's series to an existing plot.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Consider two ``Plot`` objects, ``p1`` and ``p2``. To add the
|
||||
second plot's first series object to the first, use the
|
||||
``append`` method, like so:
|
||||
|
||||
.. plot::
|
||||
:format: doctest
|
||||
:include-source: True
|
||||
|
||||
>>> from sympy import symbols
|
||||
>>> from sympy.plotting import plot
|
||||
>>> x = symbols('x')
|
||||
>>> p1 = plot(x*x, show=False)
|
||||
>>> p2 = plot(x, show=False)
|
||||
>>> p1.append(p2[0])
|
||||
>>> p1
|
||||
Plot object containing:
|
||||
[0]: cartesian line: x**2 for x over (-10.0, 10.0)
|
||||
[1]: cartesian line: x for x over (-10.0, 10.0)
|
||||
>>> p1.show()
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
extend
|
||||
|
||||
"""
|
||||
if isinstance(arg, BaseSeries):
|
||||
self._series.append(arg)
|
||||
else:
|
||||
raise TypeError('Must specify element of plot to append.')
|
||||
|
||||
def extend(self, arg):
|
||||
"""Adds all series from another plot.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Consider two ``Plot`` objects, ``p1`` and ``p2``. To add the
|
||||
second plot to the first, use the ``extend`` method, like so:
|
||||
|
||||
.. plot::
|
||||
:format: doctest
|
||||
:include-source: True
|
||||
|
||||
>>> from sympy import symbols
|
||||
>>> from sympy.plotting import plot
|
||||
>>> x = symbols('x')
|
||||
>>> p1 = plot(x**2, show=False)
|
||||
>>> p2 = plot(x, -x, show=False)
|
||||
>>> p1.extend(p2)
|
||||
>>> p1
|
||||
Plot object containing:
|
||||
[0]: cartesian line: x**2 for x over (-10.0, 10.0)
|
||||
[1]: cartesian line: x for x over (-10.0, 10.0)
|
||||
[2]: cartesian line: -x for x over (-10.0, 10.0)
|
||||
>>> p1.show()
|
||||
|
||||
"""
|
||||
if isinstance(arg, Plot):
|
||||
self._series.extend(arg._series)
|
||||
elif is_sequence(arg):
|
||||
self._series.extend(arg)
|
||||
else:
|
||||
raise TypeError('Expecting Plot or sequence of BaseSeries')
|
||||
|
||||
def show(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def save(self, path):
|
||||
raise NotImplementedError
|
||||
|
||||
def close(self):
|
||||
raise NotImplementedError
|
||||
|
||||
# deprecations
|
||||
|
||||
@property
|
||||
def markers(self):
|
||||
""".. deprecated:: 1.13"""
|
||||
_deprecation_msg_m_a_r_f("markers")
|
||||
return self._markers
|
||||
|
||||
@markers.setter
|
||||
def markers(self, v):
|
||||
""".. deprecated:: 1.13"""
|
||||
_deprecation_msg_m_a_r_f("markers")
|
||||
self._series.extend(_create_generic_data_series(markers=v))
|
||||
self._markers = v
|
||||
|
||||
@property
|
||||
def annotations(self):
|
||||
""".. deprecated:: 1.13"""
|
||||
_deprecation_msg_m_a_r_f("annotations")
|
||||
return self._annotations
|
||||
|
||||
@annotations.setter
|
||||
def annotations(self, v):
|
||||
""".. deprecated:: 1.13"""
|
||||
_deprecation_msg_m_a_r_f("annotations")
|
||||
self._series.extend(_create_generic_data_series(annotations=v))
|
||||
self._annotations = v
|
||||
|
||||
@property
|
||||
def rectangles(self):
|
||||
""".. deprecated:: 1.13"""
|
||||
_deprecation_msg_m_a_r_f("rectangles")
|
||||
return self._rectangles
|
||||
|
||||
@rectangles.setter
|
||||
def rectangles(self, v):
|
||||
""".. deprecated:: 1.13"""
|
||||
_deprecation_msg_m_a_r_f("rectangles")
|
||||
self._series.extend(_create_generic_data_series(rectangles=v))
|
||||
self._rectangles = v
|
||||
|
||||
@property
|
||||
def fill(self):
|
||||
""".. deprecated:: 1.13"""
|
||||
_deprecation_msg_m_a_r_f("fill")
|
||||
return self._fill
|
||||
|
||||
@fill.setter
|
||||
def fill(self, v):
|
||||
""".. deprecated:: 1.13"""
|
||||
_deprecation_msg_m_a_r_f("fill")
|
||||
self._series.extend(_create_generic_data_series(fill=v))
|
||||
self._fill = v
|
||||
@@ -0,0 +1,5 @@
|
||||
from sympy.plotting.backends.matplotlibbackend.matplotlib import (
|
||||
MatplotlibBackend, _matplotlib_list
|
||||
)
|
||||
|
||||
__all__ = ["MatplotlibBackend", "_matplotlib_list"]
|
||||
@@ -0,0 +1,318 @@
|
||||
from collections.abc import Callable
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.external import import_module
|
||||
import sympy.plotting.backends.base_backend as base_backend
|
||||
from sympy.printing.latex import latex
|
||||
|
||||
|
||||
# N.B.
|
||||
# When changing the minimum module version for matplotlib, please change
|
||||
# the same in the `SymPyDocTestFinder`` in `sympy/testing/runtests.py`
|
||||
|
||||
|
||||
def _str_or_latex(label):
|
||||
if isinstance(label, Basic):
|
||||
return latex(label, mode='inline')
|
||||
return str(label)
|
||||
|
||||
|
||||
def _matplotlib_list(interval_list):
|
||||
"""
|
||||
Returns lists for matplotlib ``fill`` command from a list of bounding
|
||||
rectangular intervals
|
||||
"""
|
||||
xlist = []
|
||||
ylist = []
|
||||
if len(interval_list):
|
||||
for intervals in interval_list:
|
||||
intervalx = intervals[0]
|
||||
intervaly = intervals[1]
|
||||
xlist.extend([intervalx.start, intervalx.start,
|
||||
intervalx.end, intervalx.end, None])
|
||||
ylist.extend([intervaly.start, intervaly.end,
|
||||
intervaly.end, intervaly.start, None])
|
||||
else:
|
||||
#XXX Ugly hack. Matplotlib does not accept empty lists for ``fill``
|
||||
xlist.extend((None, None, None, None))
|
||||
ylist.extend((None, None, None, None))
|
||||
return xlist, ylist
|
||||
|
||||
|
||||
# Don't have to check for the success of importing matplotlib in each case;
|
||||
# we will only be using this backend if we can successfully import matploblib
|
||||
class MatplotlibBackend(base_backend.Plot):
|
||||
""" This class implements the functionalities to use Matplotlib with SymPy
|
||||
plotting functions.
|
||||
"""
|
||||
|
||||
def __init__(self, *series, **kwargs):
|
||||
super().__init__(*series, **kwargs)
|
||||
self.matplotlib = import_module('matplotlib',
|
||||
import_kwargs={'fromlist': ['pyplot', 'cm', 'collections']},
|
||||
min_module_version='1.1.0', catch=(RuntimeError,))
|
||||
self.plt = self.matplotlib.pyplot
|
||||
self.cm = self.matplotlib.cm
|
||||
self.LineCollection = self.matplotlib.collections.LineCollection
|
||||
self.aspect = kwargs.get('aspect_ratio', 'auto')
|
||||
if self.aspect != 'auto':
|
||||
self.aspect = float(self.aspect[1]) / self.aspect[0]
|
||||
# PlotGrid can provide its figure and axes to be populated with
|
||||
# the data from the series.
|
||||
self._plotgrid_fig = kwargs.pop("fig", None)
|
||||
self._plotgrid_ax = kwargs.pop("ax", None)
|
||||
|
||||
def _create_figure(self):
|
||||
def set_spines(ax):
|
||||
ax.spines['left'].set_position('zero')
|
||||
ax.spines['right'].set_color('none')
|
||||
ax.spines['bottom'].set_position('zero')
|
||||
ax.spines['top'].set_color('none')
|
||||
ax.xaxis.set_ticks_position('bottom')
|
||||
ax.yaxis.set_ticks_position('left')
|
||||
|
||||
if self._plotgrid_fig is not None:
|
||||
self.fig = self._plotgrid_fig
|
||||
self.ax = self._plotgrid_ax
|
||||
if not any(s.is_3D for s in self._series):
|
||||
set_spines(self.ax)
|
||||
else:
|
||||
self.fig = self.plt.figure(figsize=self.size)
|
||||
if any(s.is_3D for s in self._series):
|
||||
self.ax = self.fig.add_subplot(1, 1, 1, projection="3d")
|
||||
else:
|
||||
self.ax = self.fig.add_subplot(1, 1, 1)
|
||||
set_spines(self.ax)
|
||||
|
||||
@staticmethod
|
||||
def get_segments(x, y, z=None):
|
||||
""" Convert two list of coordinates to a list of segments to be used
|
||||
with Matplotlib's :external:class:`~matplotlib.collections.LineCollection`.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
x : list
|
||||
List of x-coordinates
|
||||
|
||||
y : list
|
||||
List of y-coordinates
|
||||
|
||||
z : list
|
||||
List of z-coordinates for a 3D line.
|
||||
"""
|
||||
np = import_module('numpy')
|
||||
if z is not None:
|
||||
dim = 3
|
||||
points = (x, y, z)
|
||||
else:
|
||||
dim = 2
|
||||
points = (x, y)
|
||||
points = np.ma.array(points).T.reshape(-1, 1, dim)
|
||||
return np.ma.concatenate([points[:-1], points[1:]], axis=1)
|
||||
|
||||
def _process_series(self, series, ax):
|
||||
np = import_module('numpy')
|
||||
mpl_toolkits = import_module(
|
||||
'mpl_toolkits', import_kwargs={'fromlist': ['mplot3d']})
|
||||
|
||||
# XXX Workaround for matplotlib issue
|
||||
# https://github.com/matplotlib/matplotlib/issues/17130
|
||||
xlims, ylims, zlims = [], [], []
|
||||
|
||||
for s in series:
|
||||
# Create the collections
|
||||
if s.is_2Dline:
|
||||
if s.is_parametric:
|
||||
x, y, param = s.get_data()
|
||||
else:
|
||||
x, y = s.get_data()
|
||||
if (isinstance(s.line_color, (int, float)) or
|
||||
callable(s.line_color)):
|
||||
segments = self.get_segments(x, y)
|
||||
collection = self.LineCollection(segments)
|
||||
collection.set_array(s.get_color_array())
|
||||
ax.add_collection(collection)
|
||||
else:
|
||||
lbl = _str_or_latex(s.label)
|
||||
line, = ax.plot(x, y, label=lbl, color=s.line_color)
|
||||
elif s.is_contour:
|
||||
ax.contour(*s.get_data())
|
||||
elif s.is_3Dline:
|
||||
x, y, z, param = s.get_data()
|
||||
if (isinstance(s.line_color, (int, float)) or
|
||||
callable(s.line_color)):
|
||||
art3d = mpl_toolkits.mplot3d.art3d
|
||||
segments = self.get_segments(x, y, z)
|
||||
collection = art3d.Line3DCollection(segments)
|
||||
collection.set_array(s.get_color_array())
|
||||
ax.add_collection(collection)
|
||||
else:
|
||||
lbl = _str_or_latex(s.label)
|
||||
ax.plot(x, y, z, label=lbl, color=s.line_color)
|
||||
|
||||
xlims.append(s._xlim)
|
||||
ylims.append(s._ylim)
|
||||
zlims.append(s._zlim)
|
||||
elif s.is_3Dsurface:
|
||||
if s.is_parametric:
|
||||
x, y, z, u, v = s.get_data()
|
||||
else:
|
||||
x, y, z = s.get_data()
|
||||
collection = ax.plot_surface(x, y, z,
|
||||
cmap=getattr(self.cm, 'viridis', self.cm.jet),
|
||||
rstride=1, cstride=1, linewidth=0.1)
|
||||
if isinstance(s.surface_color, (float, int, Callable)):
|
||||
color_array = s.get_color_array()
|
||||
color_array = color_array.reshape(color_array.size)
|
||||
collection.set_array(color_array)
|
||||
else:
|
||||
collection.set_color(s.surface_color)
|
||||
|
||||
xlims.append(s._xlim)
|
||||
ylims.append(s._ylim)
|
||||
zlims.append(s._zlim)
|
||||
elif s.is_implicit:
|
||||
points = s.get_data()
|
||||
if len(points) == 2:
|
||||
# interval math plotting
|
||||
x, y = _matplotlib_list(points[0])
|
||||
ax.fill(x, y, facecolor=s.line_color, edgecolor='None')
|
||||
else:
|
||||
# use contourf or contour depending on whether it is
|
||||
# an inequality or equality.
|
||||
# XXX: ``contour`` plots multiple lines. Should be fixed.
|
||||
ListedColormap = self.matplotlib.colors.ListedColormap
|
||||
colormap = ListedColormap(["white", s.line_color])
|
||||
xarray, yarray, zarray, plot_type = points
|
||||
if plot_type == 'contour':
|
||||
ax.contour(xarray, yarray, zarray, cmap=colormap)
|
||||
else:
|
||||
ax.contourf(xarray, yarray, zarray, cmap=colormap)
|
||||
elif s.is_generic:
|
||||
if s.type == "markers":
|
||||
# s.rendering_kw["color"] = s.line_color
|
||||
ax.plot(*s.args, **s.rendering_kw)
|
||||
elif s.type == "annotations":
|
||||
ax.annotate(*s.args, **s.rendering_kw)
|
||||
elif s.type == "fill":
|
||||
# s.rendering_kw["color"] = s.line_color
|
||||
ax.fill_between(*s.args, **s.rendering_kw)
|
||||
elif s.type == "rectangles":
|
||||
# s.rendering_kw["color"] = s.line_color
|
||||
ax.add_patch(
|
||||
self.matplotlib.patches.Rectangle(
|
||||
*s.args, **s.rendering_kw))
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
'{} is not supported in the SymPy plotting module '
|
||||
'with matplotlib backend. Please report this issue.'
|
||||
.format(ax))
|
||||
|
||||
Axes3D = mpl_toolkits.mplot3d.Axes3D
|
||||
if not isinstance(ax, Axes3D):
|
||||
ax.autoscale_view(
|
||||
scalex=ax.get_autoscalex_on(),
|
||||
scaley=ax.get_autoscaley_on())
|
||||
else:
|
||||
# XXX Workaround for matplotlib issue
|
||||
# https://github.com/matplotlib/matplotlib/issues/17130
|
||||
if xlims:
|
||||
xlims = np.array(xlims)
|
||||
xlim = (np.amin(xlims[:, 0]), np.amax(xlims[:, 1]))
|
||||
ax.set_xlim(xlim)
|
||||
else:
|
||||
ax.set_xlim([0, 1])
|
||||
|
||||
if ylims:
|
||||
ylims = np.array(ylims)
|
||||
ylim = (np.amin(ylims[:, 0]), np.amax(ylims[:, 1]))
|
||||
ax.set_ylim(ylim)
|
||||
else:
|
||||
ax.set_ylim([0, 1])
|
||||
|
||||
if zlims:
|
||||
zlims = np.array(zlims)
|
||||
zlim = (np.amin(zlims[:, 0]), np.amax(zlims[:, 1]))
|
||||
ax.set_zlim(zlim)
|
||||
else:
|
||||
ax.set_zlim([0, 1])
|
||||
|
||||
# Set global options.
|
||||
# TODO The 3D stuff
|
||||
# XXX The order of those is important.
|
||||
if self.xscale and not isinstance(ax, Axes3D):
|
||||
ax.set_xscale(self.xscale)
|
||||
if self.yscale and not isinstance(ax, Axes3D):
|
||||
ax.set_yscale(self.yscale)
|
||||
if not isinstance(ax, Axes3D) or self.matplotlib.__version__ >= '1.2.0': # XXX in the distant future remove this check
|
||||
ax.set_autoscale_on(self.autoscale)
|
||||
if self.axis_center:
|
||||
val = self.axis_center
|
||||
if isinstance(ax, Axes3D):
|
||||
pass
|
||||
elif val == 'center':
|
||||
ax.spines['left'].set_position('center')
|
||||
ax.spines['bottom'].set_position('center')
|
||||
elif val == 'auto':
|
||||
xl, xh = ax.get_xlim()
|
||||
yl, yh = ax.get_ylim()
|
||||
pos_left = ('data', 0) if xl*xh <= 0 else 'center'
|
||||
pos_bottom = ('data', 0) if yl*yh <= 0 else 'center'
|
||||
ax.spines['left'].set_position(pos_left)
|
||||
ax.spines['bottom'].set_position(pos_bottom)
|
||||
else:
|
||||
ax.spines['left'].set_position(('data', val[0]))
|
||||
ax.spines['bottom'].set_position(('data', val[1]))
|
||||
if not self.axis:
|
||||
ax.set_axis_off()
|
||||
if self.legend:
|
||||
if ax.legend():
|
||||
ax.legend_.set_visible(self.legend)
|
||||
if self.margin:
|
||||
ax.set_xmargin(self.margin)
|
||||
ax.set_ymargin(self.margin)
|
||||
if self.title:
|
||||
ax.set_title(self.title)
|
||||
if self.xlabel:
|
||||
xlbl = _str_or_latex(self.xlabel)
|
||||
ax.set_xlabel(xlbl, position=(1, 0))
|
||||
if self.ylabel:
|
||||
ylbl = _str_or_latex(self.ylabel)
|
||||
ax.set_ylabel(ylbl, position=(0, 1))
|
||||
if isinstance(ax, Axes3D) and self.zlabel:
|
||||
zlbl = _str_or_latex(self.zlabel)
|
||||
ax.set_zlabel(zlbl, position=(0, 1))
|
||||
|
||||
# xlim and ylim should always be set at last so that plot limits
|
||||
# doesn't get altered during the process.
|
||||
if self.xlim:
|
||||
ax.set_xlim(self.xlim)
|
||||
if self.ylim:
|
||||
ax.set_ylim(self.ylim)
|
||||
self.ax.set_aspect(self.aspect)
|
||||
|
||||
|
||||
def process_series(self):
|
||||
"""
|
||||
Iterates over every ``Plot`` object and further calls
|
||||
_process_series()
|
||||
"""
|
||||
self._create_figure()
|
||||
self._process_series(self._series, self.ax)
|
||||
|
||||
def show(self):
|
||||
self.process_series()
|
||||
#TODO after fixing https://github.com/ipython/ipython/issues/1255
|
||||
# you can uncomment the next line and remove the pyplot.show() call
|
||||
#self.fig.show()
|
||||
if base_backend._show:
|
||||
self.fig.tight_layout()
|
||||
self.plt.show()
|
||||
else:
|
||||
self.close()
|
||||
|
||||
def save(self, path):
|
||||
self.process_series()
|
||||
self.fig.savefig(path)
|
||||
|
||||
def close(self):
|
||||
self.plt.close(self.fig)
|
||||
@@ -0,0 +1,3 @@
|
||||
from sympy.plotting.backends.textbackend.text import TextBackend
|
||||
|
||||
__all__ = ["TextBackend"]
|
||||
@@ -0,0 +1,24 @@
|
||||
import sympy.plotting.backends.base_backend as base_backend
|
||||
from sympy.plotting.series import LineOver1DRangeSeries
|
||||
from sympy.plotting.textplot import textplot
|
||||
|
||||
|
||||
class TextBackend(base_backend.Plot):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def show(self):
|
||||
if not base_backend._show:
|
||||
return
|
||||
if len(self._series) != 1:
|
||||
raise ValueError(
|
||||
'The TextBackend supports only one graph per Plot.')
|
||||
elif not isinstance(self._series[0], LineOver1DRangeSeries):
|
||||
raise ValueError(
|
||||
'The TextBackend supports only expressions over a 1D range')
|
||||
else:
|
||||
ser = self._series[0]
|
||||
textplot(ser.expr, ser.start, ser.end)
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
Reference in New Issue
Block a user