Source code for spb.plot_functions.functions_2d

"""Plotting module for Sympy.

A plot is represented by the ``Plot`` class that contains a list of the data
series to be plotted. The data series are responsible to generate numerical
data from sympy expressions.

This module gives only the essential. Especially if you need publication ready
graphs and this module is not enough for you, use directly the backend, which
can be accessed with the ``fig`` attribute:
* MatplotlibBackend.fig: returns a Matplotlib figure.
* BokehBackend.fig: return the Bokeh figure object.
* PlotlyBackend.fig: return the Plotly figure object.
* K3DBackend.fig: return the K3D plot object.

Simplicity of code takes much greater importance than performance. Don't use
it if you care at all about performance.
"""

from spb.defaults import TWO_D_B
from spb.graphics import (
    graphics, line, line_parametric_2d, line_parametric_3d,
    surface, surface_parametric, surface_revolution, surface_spherical,
    contour, implicit_2d, implicit_3d, list_2d, list_3d, geometry
)
from spb.series import (
    Parametric2DLineSeries, PlaneSeries, GenericDataSeries,
    LineOver1DRangeSeries
)
from spb.utils import (
    _plot_sympify, _check_arguments, _unpack_args, _instantiate_backend,
)
from sympy import latex, Tuple, Symbol, oo, cos, sin
from sympy.external import import_module


def _set_labels(series, labels, rendering_kw):
    """Apply the label keyword argument to the series.
    """
    if not isinstance(labels, (list, tuple)):
        labels = [labels]
    if len(labels) > 0:
        if len(series) != len(labels):
            raise ValueError(
                "The number of labels must be equal to the "
                "number of expressions being plotted.\nReceived "
                f"{len(series)} expressions and {len(labels)} labels")

        for s, l in zip(series, labels):
            s.label = l

    if rendering_kw is not None:
        if isinstance(rendering_kw, dict):
            rendering_kw = [rendering_kw]
        if len(rendering_kw) == 1:
            rendering_kw *= len(series)
        elif len(series) != len(rendering_kw):
            raise ValueError(
                "The number of rendering dictionaries must be "
                "equal to the number of expressions being plotted.\nReceived "
                f"{len(series)} expressions and {len(rendering_kw)} labels")
        for s, r in zip(series, rendering_kw):
            s.rendering_kw = r


def _create_series(series_type, plot_expr, **kwargs):
    series = []
    for args in plot_expr:
        kw = kwargs.copy()
        if args[-1] is not None:
            kw["rendering_kw"] = args[-1]
        series.append(series_type(*args[:-1], **kw))
    return series


def _create_generic_data_series(**kwargs):
    keywords = ["annotations", "markers", "fill", "rectangles"]
    series = []
    for kw in keywords:
        dictionaries = kwargs.pop(kw, [])
        if isinstance(dictionaries, dict):
            dictionaries = [dictionaries]
        for d in dictionaries:
            args = d.pop("args", [])
            series.append(GenericDataSeries(kw, *args, **d))
    return series


[docs] def plot(*args, **kwargs): """Plots a function of a single variable as a curve. Typical usage examples are in the followings: - Plotting a single expression with the default range: .. code-block:: plot(expr, **kwargs) - Plotting a single expression with a custom range, custom label and rendering options. .. code-block:: plot(expr, range, label [opt], rendering_kw [opt], **kwargs) - Plotting multiple expressions with a single range. .. code-block:: plot(expr1, expr2, ..., range, **kwargs) - Plotting multiple expressions with different ranges, custom labels and rendering options. .. code-block:: plot( (expr1, range1, label1 [opt], rendering_kw1 [opt]), (expr2, range2, label2 [opt], rendering_kw2 [opt]), ..., **kwargs) Refer to :func:`~spb.graphics.functions_2d.line` for a full list of keyword arguments to customize the appearances of lines. Refer to :func:`~spb.graphics.graphics.graphics` for a full list of keyword arguments to customize the appearances of the figure (title, axis labels, ...). Parameters ========== label : str or list/tuple, optional The label to be shown in the legend. If not provided, the string representation of expr will be used. The number of labels must be equal to the number of expressions. rendering_kw : dict or list of dicts, optional A dictionary of keywords/values which is passed to the backend's function to customize the appearance of lines. Refer to the plotting library (backend) manual for more informations. If a list of dictionaries is provided, the number of dictionaries must be equal to the number of expressions. Examples ======== .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy import symbols, sin, pi, tan, exp, cos, log, floor >>> from spb import plot >>> x, y = symbols('x, y') Single Plot .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot(x**2, (x, -5, 5)) Plot object containing: [0]: cartesian line: x**2 for x over (-5.0, 5.0) Multiple functions over the same range with custom rendering options: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot(x, log(x), exp(x), (x, -3, 3), aspect="equal", ylim=(-3, 3), ... rendering_kw=[{}, {"linestyle": "--"}, {"linestyle": ":"}]) Plot object containing: [0]: cartesian line: x for x over (-3.0, 3.0) [1]: cartesian line: log(x) for x over (-3.0, 3.0) [2]: cartesian line: exp(x) for x over (-3.0, 3.0) Plotting a summation in which the free symbol of the expression is not used in the lower/upper bounds: .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy import Sum, oo, latex >>> expr = Sum(1 / x ** y, (x, 1, oo)) >>> plot(expr, (y, 2, 10), sum_bound=1e03, title="$%s$" % latex(expr)) Plot object containing: [0]: cartesian line: Sum(x**(-y), (x, 1, 1000)) for y over (2.0, 10.0) Plotting a summation in which the free symbol of the expression is used in the lower/upper bounds. Here, the discretization variable must assume integer values: .. plot:: :context: close-figs :format: doctest :include-source: True >>> expr = Sum(1 / x, (x, 1, y)) >>> plot(expr, (y, 2, 10), adaptive=False, ... scatter=True, is_filled=True, title="$%s$" % latex(expr)) Plot object containing: [0]: cartesian line: Sum(1/x, (x, 1, y)) for y over (2.0, 10.0) Using an adaptive algorithm, detect and plot vertical lines at singularities. Also, apply a transformation function to the discretized domain in order to convert radians to degrees: .. plot:: :context: close-figs :format: doctest :include-source: True >>> import numpy as np >>> plot(tan(x), (x, -1.5*pi, 1.5*pi), ... adaptive=True, adaptive_goal=0.001, ... detect_poles="symbolic", tx=np.rad2deg, ylim=(-7, 7), ... xlabel="x [deg]", grid=False) Plot object containing: [0]: cartesian line: tan(x) for x over (-4.71238898038469, 4.71238898038469) Introducing discontinuities by excluding specified points: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot(floor(x) / x, (x, -3.25, 3.25), ylim=(-1, 5), ... exclude=list(range(-4, 5))) Plot object containing: [0]: cartesian line: floor(x)/x for x over (-3.25, 3.25) Advanced example showing: * detect singularities by setting ``adaptive=False`` (better performance), increasing the number of discretization points (in order to have 'vertical' segments on the lines) and reducing the threshold for the singularity-detection algorithm. * application of color function. .. plot:: :context: close-figs :format: doctest :include-source: True >>> import numpy as np >>> expr = 1 / cos(10 * x) + 5 * sin(x) >>> def cf(x, y): ... # map a colormap to the distance from the origin ... d = np.sqrt(x**2 + y**2) ... # visibility of the plot is limited: ylim=(-10, 10). However, ... # some of the y-values computed by the function are much higher ... # (or lower). Filter them out in order to have the entire ... # colormap spectrum visible in the plot. ... offset = 12 # 12 > 10 (safety margin) ... d[(y > offset) | (y < -offset)] = 0 ... return d >>> p1 = plot(expr, (x, -5, 5), ... "distance from (0, 0)", {"cmap": "plasma"}, ... ylim=(-10, 10), adaptive=False, detect_poles=True, n=3e04, ... eps=1e-04, color_func=cf, title="$%s$" % latex(expr)) Combining multiple plots together: .. plot:: :context: close-figs :format: doctest :include-source: True >>> p2 = plot(5 * sin(x), (x, -5, 5), {"linestyle": "--"}, show=False) >>> (p1 + p2).show() Plotting a numerical function instead of a symbolic expression: .. plot:: :context: close-figs :format: doctest :include-source: True >>> import numpy as np >>> plot(lambda t: np.cos(np.exp(-t)), ("t", -pi, 0)) # doctest: +SKIP Interactive-widget plot of an oscillator. Refer to the interactive sub-module documentation to learn more about the ``params`` dictionary. This plot illustrates: * plotting multiple expressions, each one with its own label and rendering options. * the use of ``prange`` (parametric plotting range). * the use of the ``params`` dictionary to specify sliders in their basic form: (default, min, max). * the use of :py:class:`panel.widgets.slider.RangeSlider`, which is a 2-values widget. In this case it is used to enforce the condition `f1 < f2`. * the use of a parametric title, specified with a tuple of the form: ``(title_str, param_symbol1, ...)``, where: * ``title_str`` must be a formatted string, for example: ``"test = {:.2f}"``. * ``param_symbol1, ...`` must be a symbol or a symbolic expression whose free symbols are contained in the ``params`` dictionary. .. panel-screenshot:: :small-size: 800, 625 from sympy import * from spb import * import panel as pn x, y, f1, f2, d, n = symbols("x, y, f_1, f_2, d, n") plot( (cos(f1 * x) * exp(-d * x), "oscillator 1"), (cos(f2 * x) * exp(-d * x), "oscillator 2"), (exp(-d * x), "upper limit", {"linestyle": ":"}), (-exp(-d * x), "lower limit", {"linestyle": ":"}), prange(x, 0, n * pi), params={ (f1, f2): pn.widgets.RangeSlider(value=(1, 2), start=0, end=10, step=0.1), # frequency d: (0.25, 0, 1), # damping n: (2, 0, 4) # multiple of pi }, ylim=(-1.25, 1.25), title=("$f_1$ = {:.2f} Hz", f1), ) See Also ======== plot_implicit, plot_polar, plot_parametric, plot_list, plot_contour, plot_geometry, plot_piecewise """ args = _plot_sympify(args) plot_expr = _check_arguments(args, 1, 1, **kwargs) global_labels = kwargs.pop("label", []) global_rendering_kw = kwargs.pop("rendering_kw", None) lines = [] for pe in plot_expr: expr, r, label, rendering_kw = pe lines.extend(line(expr, r, label, rendering_kw, **kwargs)) _set_labels(lines, global_labels, global_rendering_kw) gs = _create_generic_data_series(**kwargs) return graphics(*lines, gs, **kwargs)
[docs] def plot_parametric(*args, **kwargs): """ Plots a 2D parametric curve. Typical usage examples are in the followings: - Plotting a single parametric curve with a range: .. code-block:: plot_parametric(expr_x, expr_y, range) - Plotting multiple parametric curves with the same range: .. code-block:: plot_parametric( (expr_x1, expr_y1), (expr_x2, expr_y2), ..., range) - Plotting multiple curves with different ranges, custom labels and rendering options: .. code-block:: plot_parametric( (expr_x1, expr_y1, range1, label1 [opt], rendering_kw1 [opt]), (expr_x2, expr_y2, range2, label2 [opt], rendering_kw2 [opt]), ...) Refer to :func:`~spb.graphics.functions_2d.line_parametric_2d` for a full list of keyword arguments to customize the appearances of lines. Refer to :func:`~spb.graphics.graphics.graphics` for a full list of keyword arguments to customize the appearances of the figure (title, axis labels, ...). Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, cos, sin, pi, floor, log >>> from spb import plot_parametric >>> t, u, v = symbols('t, u, v') A parametric plot of a single expression (a Hypotrochoid using an equal aspect ratio): .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_parametric( ... 2 * cos(u) + 5 * cos(2 * u / 3), ... 2 * sin(u) - 5 * sin(2 * u / 3), ... (u, 0, 6 * pi), aspect="equal") Plot object containing: [0]: parametric cartesian line: (5*cos(2*u/3) + 2*cos(u), -5*sin(2*u/3) + 2*sin(u)) for u over (0.0, 18.84955592153876) A parametric plot with multiple expressions with the same range with solid line colors: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_parametric((2 * cos(t), sin(t)), (cos(t), 2 * sin(t)), ... (t, 0, 2*pi), use_cm=False) Plot object containing: [0]: parametric cartesian line: (2*cos(t), sin(t)) for t over (0.0, 6.283185307179586) [1]: parametric cartesian line: (cos(t), 2*sin(t)) for t over (0.0, 6.283185307179586) A parametric plot with multiple expressions with different ranges, custom labels, custom rendering options and a transformation function applied to the discretized parameter to convert radians to degrees: .. plot:: :context: close-figs :format: doctest :include-source: True >>> import numpy as np >>> plot_parametric( ... (3 * cos(u), 3 * sin(u), (u, 0, 2 * pi), "u [deg]", {"lw": 3}), ... (3 * cos(2 * v), 5 * sin(4 * v), (v, 0, pi), "v [deg]"), ... aspect="equal", tp=np.rad2deg) Plot object containing: [0]: parametric cartesian line: (3*cos(u), 3*sin(u)) for u over (0.0, 6.283185307179586) [1]: parametric cartesian line: (3*cos(2*v), 5*sin(4*v)) for v over (0.0, 3.141592653589793) Introducing discontinuities by excluding specified points: .. plot:: :context: close-figs :format: doctest :include-source: True >>> e1 = log(floor(t))*cos(t) >>> e2 = log(floor(t))*sin(t) >>> plot_parametric(e1, e2, (t, 1, 4*pi), ... exclude=list(range(1, 13)), grid=False) Plot object containing: [0]: parametric cartesian line: (log(floor(t))*cos(t), log(floor(t))*sin(t)) for t over (1.0, 12.566370614359172) Plotting a numerical function instead of a symbolic expression: .. plot:: :context: close-figs :format: doctest :include-source: True >>> import numpy as np >>> fx = lambda t: np.sin(t) * (np.exp(np.cos(t)) - 2 * np.cos(4 * t) - np.sin(t / 12)**5) >>> fy = lambda t: np.cos(t) * (np.exp(np.cos(t)) - 2 * np.cos(4 * t) - np.sin(t / 12)**5) >>> p = plot_parametric(fx, fy, ("t", 0, 12 * pi), ... title="Butterfly Curve", use_cm=False, n=2000) Interactive-widget plot. Refer to the interactive sub-module documentation to learn more about the ``params`` dictionary. This plot illustrates: * the use of ``prange`` (parametric plotting range). * the use of the ``params`` dictionary to specify sliders in their basic form: (default, min, max). .. panel-screenshot:: :small-size: 800, 600 from sympy import * from spb import * x, a, s, e = symbols("x a s, e") plot_parametric( cos(a * x), sin(x), prange(x, s*pi, e*pi), params={ a: (0.5, 0, 2), s: (0, 0, 2), e: (2, 0, 2), }, aspect="equal", xlim=(-1.25, 1.25), ylim=(-1.25, 1.25) ) See Also ======== plot, plot_implicit, plot_polar, plot_list, plot_contour, plot_geometry, plot_piecewise """ args = _plot_sympify(args) plot_expr = _check_arguments(args, 2, 1, **kwargs) global_labels = kwargs.pop("label", []) global_rendering_kw = kwargs.pop("rendering_kw", None) lines = [] for pe in plot_expr: e1, e2, r, label, rendering_kw = pe lines.extend( line_parametric_2d(e1, e2, r, label, rendering_kw, **kwargs)) _set_labels(lines, global_labels, global_rendering_kw) gs = _create_generic_data_series(**kwargs) return graphics(*lines, gs, **kwargs)
[docs] def plot_parametric_region(*args, **kwargs): """ Plots a 2D parametric region. NOTE: this is an experimental plotting function as it only draws lines without fills. The resulting visualization might change when new features will be implemented. Typical usage examples are in the followings: - Plotting a single parametric curve with a range: .. code-block:: plot_parametric(expr_x, expr_y, range_u, range_v) - Plotting multiple parametric curves with the same range: .. code-block:: plot_parametric((expr_x, expr_y), ..., range_u, range_v) - Plotting multiple parametric curves with different ranges: .. code-block:: plot_parametric((expr_x, expr_y, range_u, range_v), ...) Parameters ========== args : `expr_x`, `expr_y` : Expr The expression representing x and y component, respectively, of the parametric function. It can be a: * Symbolic expression representing the function of one variable to be plotted. * Numerical function of one variable, supporting vectorization. In this case the following keyword arguments are not supported: ``params``. `range_u`, `range_v` : (symbol, min, max) A 3-tuple denoting the parameter symbols, start and stop. For example, `(u, 0, 5), (v, 0, 5)`. If the ranges are not specified, then they default to (-10, 10). However, if the arguments are specified as `(expr_x, expr_y, range_u, range_v), ...`, you must specify the ranges for each expressions manually. rendering_kw : dict, optional A dictionary of keywords/values which is passed to the backend's function to customize the appearance of lines. Refer to the plotting library (backend) manual for more informations. aspect : (float, float) or str, optional Set the aspect ratio of the plot. The value depends on the backend being used. Read that backend's documentation to find out the possible values. backend : Plot, optional A subclass of ``Plot``, which will perform the rendering. Default to ``MatplotlibBackend``. n : int, optional The functions are uniformly sampled at ``n`` number of points. Default value to 1000. n1, n2 : int, optional Number of lines to create along each direction. Default to 10. Note: the higher the number, the slower the rendering. rkw_u, rkw_v : dict A dictionary of keywords/values which is passed to the backend's function to customize the appearance of lines along the u and v directions, respectively. These overrides ``rendering_kw`` if provided. Refer to the plotting library (backend) manual for more informations. show : bool, optional The default value is set to ``True``. Set show to ``False`` and the function will not display the plot. The returned instance of the ``Plot`` class can then be used to save or display the plot by calling the ``save()`` and ``show()`` methods respectively. size : (float, float), optional A tuple in the form (width, height) to specify the size of the overall figure. The default value is set to ``None``, meaning the size will be set by the backend. title : str, optional Title of the plot. It is set to the latex representation of the expression, if the plot has only one expression. use_latex : boolean, optional Turn on/off the rendering of latex labels. If the backend doesn't support latex, it will render the string representations instead. xlabel, ylabel : str, optional Label for the x-axis or y-axis, respectively. xscale, yscale : 'linear' or 'log', optional Sets the scaling of the x-axis or y-axis, respectively. Default to ``'linear'``. xlim, ylim : (float, float), optional Denotes the x-axis or y-axis limits, ``(min, max)``, visible in the chart. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, cos, sin, pi, I, re, im, latex >>> from spb import plot_parametric_region Plot a slice of a ring, applying the same style to all lines: .. plot:: :context: close-figs :format: doctest :include-source: True >>> r, theta = symbols("r theta") >>> p = plot_parametric_region(r * cos(theta), r * sin(theta), ... (r, 1, 2), (theta, 0, 2*pi/3), ... {"color": "k", "linewidth": 0.75}, ... n1=5, n2=15, aspect="equal") # doctest: +SKIP Complex mapping, applying to different line styles: .. plot:: :context: close-figs :format: doctest :include-source: True >>> x, y, z = symbols("x y z") >>> f = 1 / z**2 >>> f_cart = f.subs(z, x + I * y) >>> r, i = re(f_cart), im(f_cart) >>> n1, n2 = 30, 30 >>> p = plot_parametric_region(r, i, (x, -2, 2), (y, -2, 2), ... rkw_u={"color": "r", "linewidth": 0.75}, ... rkw_v={"color": "b", "linewidth": 0.75}, ... n1=20, n2=20, aspect="equal", xlim=(-2, 2), ylim=(-2, 2), ... xlabel="Re", ylabel="Im", title="$f(z)=%s$" % latex(f)) """ np = import_module('numpy') n1 = kwargs.pop("n1", 10) n2 = kwargs.pop("n2", 10) rkw_u = kwargs.pop("rkw_u", None) rkw_v = kwargs.pop("rkw_v", None) labels = kwargs.pop("label", []) rendering_kw = kwargs.pop("rendering_kw", None) args = _plot_sympify(args) kwargs["adaptive"] = False kwargs["use_cm"] = False kwargs["legend"] = False plot_expr = _check_arguments(args, 2, 2, **kwargs) series = [] if "params" in kwargs.keys(): raise NotImplementedError for pe in plot_expr: fx, fy, urange, vrange, lbl, rkw = pe u, umin, umax = urange v, vmin, vmax = vrange new_pe = [] for uv in np.linspace(float(umin), float(umax), n1): new_pe.append(( fx.subs(u, uv), fy.subs(u, uv), (v, vmin, vmax), lbl, rkw if rkw_u is None else rkw_u)) for vv in np.linspace(float(vmin), float(vmax), n2): new_pe.append(( fx.subs(v, vv), fy.subs(v, vv), (u, umin, umax), rkw if rkw_v is None else rkw_v)) series += _create_series(Parametric2DLineSeries, new_pe, **kwargs) Backend = kwargs.pop("backend", TWO_D_B) return _instantiate_backend(Backend, *series, **kwargs)
def _plot3d_plot_contour_helper(threed, *args, **kwargs): args = _plot_sympify(args) plot_expr = _check_arguments(args, 1, 2, **kwargs) global_labels = kwargs.pop("label", []) global_rendering_kw = kwargs.pop("rendering_kw", None) surfaces = [] func = surface if threed else contour indeces = [] for i, pe in enumerate(plot_expr): indeces.append(len(surfaces)) expr, r1, r2, label, rendering_kw = pe surfaces.extend( func(expr, r1, r2, label, rendering_kw, **kwargs)) actual_surfaces = [s for i, s in enumerate(surfaces) if i in indeces] _set_labels(actual_surfaces, global_labels, global_rendering_kw) return graphics(*surfaces, **kwargs)
[docs] def plot_contour(*args, **kwargs): """ Draws contour plot of a function of two variables. This function signature is almost identical to :func:`~plot3d`: refer to its documentation for a full list of available argument and keyword arguments. Refer to :func:`~spb.graphics.functions_2d.contour` for a full list of keyword arguments to customize the appearances of contours. Refer to :func:`~spb.graphics.graphics.graphics` for a full list of keyword arguments to customize the appearances of the figure (title, axis labels, ...). Parameters ========== clabels : bool, optional Visualize labels of contour lines. Only works when ``is_filled=False``. Default to True. Note that some backend might not implement this feature. is_filled : bool, optional Choose between filled contours or line contours. Default to True (filled contours). polar_axis : boolean, optional If True, attempt to create a plot with polar axis. Default to False, which creates a plot with cartesian axis. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, cos, exp, sin, pi, Eq, Add >>> from spb import plot_contour >>> x, y = symbols('x, y') Filled contours of a function of two variables. .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_contour(cos((x**2 + y**2)) * exp(-(x**2 + y**2) / 10), ... (x, -5, 5), (y, -5, 5)) Plot object containing: [0]: contour: exp(-x**2/10 - y**2/10)*cos(x**2 + y**2) for x over (-5.0, 5.0) and y over (-5.0, 5.0) Line contours of a function of two variables. .. plot:: :context: close-figs :format: doctest :include-source: True >>> expr = 5 * (cos(x) - 0.2 * sin(y))**2 + 5 * (-0.2 * cos(x) + sin(y))**2 >>> plot_contour(expr, (x, 0, 2 * pi), (y, 0, 2 * pi), is_filled=False) Plot object containing: [0]: contour: 5*(-0.2*sin(y) + cos(x))**2 + 5*(sin(y) - 0.2*cos(x))**2 for x over (0.0, 6.283185307179586) and y over (0.0, 6.283185307179586) Combining together filled and line contours. Use a custom label on the colorbar of the filled contour. .. plot:: :context: close-figs :format: doctest :include-source: True >>> expr = 5 * (cos(x) - 0.2 * sin(y))**2 + 5 * (-0.2 * cos(x) + sin(y))**2 >>> p1 = plot_contour(expr, (x, 0, 2 * pi), (y, 0, 2 * pi), "z", ... {"cmap": "coolwarm"}, show=False, grid=False) >>> p2 = plot_contour(expr, (x, 0, 2 * pi), (y, 0, 2 * pi), ... {"colors": "k", "cmap": None, "linewidths": 0.75}, ... show=False, is_filled=False) >>> (p1 + p2).show() Visually inspect the solutions of a system of 2 non-linear equations. The intersections between the contour lines represent the solutions. .. plot:: :context: close-figs :format: doctest :include-source: True >>> eq1 = Eq((cos(x) - sin(y) / 2)**2 + 3 * (-sin(x) + cos(y) / 2)**2, 2) >>> eq2 = Eq((cos(x) - 2 * sin(y))**2 - (sin(x) + 2 * cos(y))**2, 3) >>> plot_contour(eq1.rewrite(Add), eq2.rewrite(Add), {"levels": [0]}, ... (x, 0, 2 * pi), (y, 0, 2 * pi), is_filled=False, clabels=False) Plot object containing: [0]: contour: 3*(-sin(x) + cos(y)/2)**2 + (-sin(y)/2 + cos(x))**2 - 2 for x over (0.0, 6.283185307179586) and y over (0.0, 6.283185307179586) [1]: contour: -(sin(x) + 2*cos(y))**2 + (-2*sin(y) + cos(x))**2 - 3 for x over (0.0, 6.283185307179586) and y over (0.0, 6.283185307179586) Contour plot with polar axis: .. plot:: :context: close-figs :format: doctest :include-source: True >>> r, theta = symbols("r, theta") >>> plot_contour(sin(2 * r) * cos(theta), (theta, 0, 2*pi), (r, 0, 7), ... {"levels": 100}, polar_axis=True, aspect="equal") Plot object containing: [0]: contour: sin(2*r)*cos(theta) for theta over (0.0, 6.283185307179586) and r over (0.0, 7.0) Interactive-widget plot. Refer to the interactive sub-module documentation to learn more about the ``params`` dictionary. This plot illustrates: * the use of ``prange`` (parametric plotting range). * the use of the ``params`` dictionary to specify sliders in their basic form: (default, min, max). * the use of :py:class:`panel.widgets.slider.RangeSlider`, which is a 2-values widget. .. panel-screenshot:: :small-size: 800, 600 from sympy import * from spb import * import panel as pn x, y, a, b = symbols("x y a b") x_min, x_max, y_min, y_max = symbols("x_min x_max y_min y_max") expr = (cos(x) + a * sin(x) * sin(y) - b * sin(x) * cos(y))**2 plot_contour( expr, prange(x, x_min*pi, x_max*pi), prange(y, y_min*pi, y_max*pi), params={ a: (1, 0, 2), b: (1, 0, 2), (x_min, x_max): pn.widgets.RangeSlider( value=(-1, 1), start=-3, end=3, step=0.1), (y_min, y_max): pn.widgets.RangeSlider( value=(-1, 1), start=-3, end=3, step=0.1), }, grid=False) See Also ======== plot, plot_implicit, plot_polar, plot_parametric, plot_list, plot3d, plot_geometry, plot_piecewise """ return _plot3d_plot_contour_helper(False, *args, **kwargs)
[docs] def plot_implicit(*args, **kwargs): """Plot implicit equations / inequalities. ``plot_implicit``, by default, generates a contour using a mesh grid of fixednumber of points. The greater the number of points, the better the results, but also the greater the memory used. By setting ``adaptive=True``, interval arithmetic will be used to plot functions. If the expression cannot be plotted using interval arithmetic, it defaults to generating a contour using a mesh grid. With interval arithmetic, the line width can become very small; in those cases, it is better to use the mesh grid approach. Typical usage examples are in the following: - Plot a single expression: .. code-block:: plot_implicit(expr, range_x, range_y) - Plot multiple expressions over the same ranges: .. code-block:: plot_implicit(expr1, expr2, range_x, range_y) - Plot multiple expressions over different ranges: .. code-block:: plot_implicit( (expr1, range_x1, range_y1, label1 [opt]), (expr2, range_x2, range_y2, label2 [opt])) Refer to :func:`~spb.graphics.functions_2d.implicit_2d` for a full list of keyword arguments to customize the appearances of lines and regions. Refer to :func:`~spb.graphics.graphics.graphics` for a full list of keyword arguments to customize the appearances of the figure (title, axis labels, ...). Parameters ========== label : str or list/tuple, optional The label to be shown in the legend. If not provided, the string representation of expr will be used. The number of labels must be equal to the number of expressions. Examples ======== Plot expressions: .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, Ne, Eq, And, sin, cos, pi, log, latex >>> from spb import plot_implicit >>> x, y = symbols('x y') Providing only the symbol for the horizontal axis: .. plot:: :context: close-figs :format: doctest :include-source: True >>> p = plot_implicit(x - 1, x) Specify both ranges, set the number of discretization points and plot a region: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_implicit(y > x**2, (x, -5, 5), (y, -10, 10), n=150, grid=False) Plot object containing: [0]: Implicit expression: y > x**2 for x over (-5.0, 5.0) and y over (-10.0, 10.0) Plot a region using a custom color, highlights the limiting border and customize its appearance. In this particular case, the content of ``rendering_kw`` will be sent to matplotlib's ``contour`` of ``contourf`` commands. .. plot:: :context: close-figs :format: doctest :include-source: True >>> expr = 4 * (cos(x) - sin(y) / 5)**2 + 4 * (-cos(x) / 5 + sin(y))**2 >>> plot_implicit(expr <= pi, (x, -pi, pi), (y, -pi, pi), ... grid=False, color="gold", border_color="k", ... rendering_kw={"linestyles": "-.", "linewidths": 1}) Plot object containing: [0]: Implicit expression: 4*(-sin(y)/5 + cos(x))**2 + 4*(sin(y) - cos(x)/5)**2 <= pi for x over (-3.141592653589793, 3.141592653589793) and y over (-3.141592653589793, 3.141592653589793) [1]: Implicit expression: Eq(-4*(-sin(y)/5 + cos(x))**2 - 4*(sin(y) - cos(x)/5)**2 + pi, 0) for x over (-3.141592653589793, 3.141592653589793) and y over (-3.141592653589793, 3.141592653589793) Boolean expressions will be plotted with the adaptive algorithm. Note the thin width of lines: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_implicit( ... Eq(y, sin(x)) & (y > 0), ... Eq(y, sin(x)) & (y < 0), ... (x, -2 * pi, 2 * pi), (y, -4, 4)) Plot object containing: [0]: Implicit expression: (y > 0) & Eq(y, sin(x)) for x over (-6.283185307179586, 6.283185307179586) and y over (-4.0, 4.0) [1]: Implicit expression: (y < 0) & Eq(y, sin(x)) for x over (-6.283185307179586, 6.283185307179586) and y over (-4.0, 4.0) Plotting multiple implicit expressions and setting labels: .. plot:: :context: close-figs :format: doctest :include-source: True >>> V, t, b, L = symbols("V, t, b, L") >>> L_array = [5, 10, 15, 20, 25] >>> b_val = 0.0032 >>> expr = b * V * 0.277 * t - b * L - log(1 + b * V * 0.277 * t) >>> expr_list = [expr.subs({b: b_val, L: L_val}) for L_val in L_array] >>> labels = ["L = %s" % L_val for L_val in L_array] >>> plot_implicit(*expr_list, (t, 0, 3), (V, 0, 1000), label=labels) Plot object containing: [0]: Implicit expression: Eq(0.0008864*V*t - log(0.0008864*V*t + 1) - 0.016, 0) for t over (0.0, 3.0) and V over (0.0, 1000.0) [1]: Implicit expression: Eq(0.0008864*V*t - log(0.0008864*V*t + 1) - 0.032, 0) for t over (0.0, 3.0) and V over (0.0, 1000.0) [2]: Implicit expression: Eq(0.0008864*V*t - log(0.0008864*V*t + 1) - 0.048, 0) for t over (0.0, 3.0) and V over (0.0, 1000.0) [3]: Implicit expression: Eq(0.0008864*V*t - log(0.0008864*V*t + 1) - 0.064, 0) for t over (0.0, 3.0) and V over (0.0, 1000.0) [4]: Implicit expression: Eq(0.0008864*V*t - log(0.0008864*V*t + 1) - 0.08, 0) for t over (0.0, 3.0) and V over (0.0, 1000.0) Comparison of similar expressions plotted with different algorithms. Note: 1. Adaptive algorithm (``adaptive=True``) can be used with any expression, but it usually creates lines with variable thickness. The ``depth`` keyword argument can be used to improve the accuracy, but reduces line thickness even further. 2. Mesh grid algorithm (``adaptive=False``) creates lines with constant thickness. .. plot:: :context: close-figs :format: doctest :include-source: True >>> expr1 = Eq(x * y - 20, 15 * y) >>> expr2 = Eq((x - 3) * y - 20, 15 * y) >>> expr3 = Eq((x - 6) * y - 20, 15 * y) >>> ranges = (x, 15, 30), (y, 0, 50) >>> p1 = plot_implicit(expr1, *ranges, adaptive=True, depth=0, ... label="adaptive=True, depth=0", grid=False, show=False) >>> p2 = plot_implicit(expr2, *ranges, adaptive=True, depth=1, ... label="adaptive=True, depth=1", grid=False, show=False) >>> p3 = plot_implicit(expr3, *ranges, adaptive=False, ... label="adaptive=False", grid=False, show=False) >>> (p1 + p2 + p3).show() If the expression is plotted with the adaptive algorithm and it produces "low-quality" results, maybe it's possible to rewrite it in order to use the mesh grid approach (contours). For example: .. plot:: :context: close-figs :format: doctest :include-source: True >>> from spb import plotgrid >>> expr = Ne(x*y, 1) >>> p1 = plot_implicit( ... expr, (x, -10, 10), (y, -10, 10), ... grid=False, aspect="equal", show=False, ... title="$%s$ : First approach" % latex(expr)) >>> # plot the entire visible region >>> p2 = plot_implicit( ... x < 20, (x, -10, 10), (y, -10, 10), ... show=False, grid=False, aspect="equal", ... title="$%s$ : Second approach" % latex(expr)) >>> # plot the excluded contour >>> p3 = plot_implicit( ... Eq(*expr.args), (x, -10, 10), (y, -10, 10), ... color="w", show_in_legend=False, show=False) >>> plotgrid(p1, (p2 + p3), nc=2) # doctest: +SKIP Interactive-widget implicit plot. Refer to the interactive sub-module documentation to learn more about the ``params`` dictionary. This plot illustrates: * the use of ``prange`` (parametric plotting range). * the use of the ``params`` dictionary to specify sliders in their basic form: (default, min, max). * the use of :py:class:`panel.widgets.slider.RangeSlider`, which is a 2-values widget. .. panel-screenshot:: :small-size: 800, 700 from sympy import * from spb import * import panel as pn x, y, a, b, c, d = symbols("x, y, a, b, c, d") y_min, y_max = symbols("y_min, y_max") expr = Eq(a * x**2 - b * x + c, d * y + y**2) plot_implicit(expr, (x, -2, 2), prange(y, y_min, y_max), params={ a: (10, -15, 15), b: (7, -15, 15), c: (3, -15, 15), d: (2, -15, 15), (y_min, y_max): pn.widgets.RangeSlider( value=(-10, 10), start=-15, end=15, step=0.1) }, n=150, ylim=(-10, 10)) See Also ======== plot, plot_polar, plot_parametric, plot_list, plot_contour, plot_geometry, plot_piecewise """ # if the user is plotting a single expression, then he can pass in one # or two symbols to sort the axis. Ranges will then be automatically # created. args = list(args) if (len(args) == 2) and isinstance(args[1], Symbol): args[1] = Tuple(args[1], -10, 10) elif ( (len(args) >= 3) and isinstance(args[1], Symbol) and isinstance(args[2], Symbol) ): args[1] = Tuple(args[1], -10, 10) args[2] = Tuple(args[2], -10, 10) args = _plot_sympify(args) args = _check_arguments(args, 1, 2, **kwargs) global_labels = kwargs.pop("label", []) global_rendering_kw = kwargs.pop("rendering_kw", None) color = kwargs.pop("color", None) border_color = kwargs.pop("border_color", None) series = [] # attempt to compute the area that should be visible on the plot. xmin, xmax, ymin, ymax = oo, -oo, oo, -oo for (expr, r1, r2, label, rendering_kw) in args: series.extend( implicit_2d( expr, r1, r2, label, rendering_kw, color=color, border_color=border_color, **kwargs)) s = series[-1] if (not s.start_x.free_symbols) and (s.start_x < xmin): xmin = s.start_x if (not s.end_x.free_symbols) and (s.end_x > xmax): xmax = s.end_x if (not s.start_y.free_symbols) and (s.start_y < ymin): ymin = s.start_y if (not s.end_y.free_symbols) and (s.end_y > ymax): ymax = s.end_y _set_labels(series, global_labels, global_rendering_kw) series += _create_generic_data_series(**kwargs) if (xmin != oo) and (xmax != -oo): kwargs.setdefault("xlim", (xmin, xmax)) if (ymin != oo) and (ymax != -oo): kwargs.setdefault("ylim", (ymin, ymax)) return graphics(*series, **kwargs)
[docs] def plot_polar(*args, **kwargs): """The following function creates a 2D polar plot. By default, it uses an equal aspect ratio and doesn't apply a colormap. Typical usage examples are in the followings: - Plotting a single polar curve with a range: .. code-block:: plot_polar(expr, range) - Plotting multiple polar curves with the same range: .. code-block:: plot_polar(expr1, expr2, ..., range) - Plotting multiple curves with different ranges, custom labels and rendering options: .. code-block:: plot_polar( (expr1, range1, label1 [opt], rendering_kw1 [opt]), (expr2, range2, label2 [opt], rendering_kw2 [opt]), ..., **kwargs) This function is going to execute :func:`~spb.graphics.functions_2d.line_parametric_2d`. Refer to its documentation for a full list of keyword arguments to customize the appearances of lines. Refer to :func:`~spb.graphics.graphics.graphics` for a full list of keyword arguments to customize the appearances of the figure (title, axis labels, ...). Parameters ========== polar_axis : boolean, optional If True, attempt to create a plot with polar axis. Default to False, which creates a plot with cartesian axis. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, sin, cos, exp, pi >>> from spb import plot_polar >>> theta = symbols('theta') Plot with cartesian axis: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_polar(3 * sin(2 * theta), (theta, 0, 2*pi)) Plot object containing: [0]: parametric cartesian line: (3*sin(2*theta)*cos(theta), 3*sin(theta)*sin(2*theta)) for theta over (0.0, 6.283185307179586) Plot with polar axis: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_polar( ... exp(sin(theta)) - 2 * cos(4 * theta), (theta, 0, 2 * pi), ... polar_axis=True) Plot object containing: [0]: parametric cartesian line: ((exp(sin(theta)) - 2*cos(4*theta))*cos(theta), (exp(sin(theta)) - 2*cos(4*theta))*sin(theta)) for theta over (0.0, 6.283185307179586) Interactive-widget plot of Guilloché Pattern. Refer to the interactive sub-module documentation to learn more about the ``params`` dictionary. This plot illustrates: * the use of ``prange`` (parametric plotting range). * the use of the ``params`` dictionary to specify the widgets to be created by Holoviz's Panel. .. panel-screenshot:: from sympy import * from spb import * import panel as pn a, b, c, d, e, f, theta, tp = symbols("a:f theta tp") def func(n): t1 = (c + sin(a * theta + d)) t2 = ((b + sin(b * theta + e)) - (c + sin(a * theta + d))) t3 = (f + sin(a * theta + n / pi)) return t1 + t2 * t3 / 2 exprs = [func(n) for n in range(20)] plot_polar( *exprs, prange(theta, 0, tp*pi), {"line_color": "black", "line_width": 0.5}, params={ a: pn.widgets.IntInput(value=6, name="a"), b: pn.widgets.IntInput(value=12, name="b"), c: pn.widgets.IntInput(value=18, name="c"), d: (4.7, 0, 2*pi), e: (1.8, 0, 2*pi), f: (3, 0, 5), tp: (2, 0, 2) }, layout = "sbl", ncols = 1, title="Guilloché Pattern Explorer", backend=BB, legend=False, servable=True, imodule="panel" ) See Also ======== plot, plot_implicit, plot_parametric, plot_list, plot_contour, plot_geometry, plot_piecewise """ global_labels = kwargs.pop("label", []) global_rendering_kw = kwargs.pop("rendering_kw", None) kwargs.setdefault("polar_axis", False) kwargs.setdefault("aspect", "equal") kwargs.setdefault("xlabel", "") kwargs.setdefault("ylabel", "") polar_axis = kwargs.get("polar_axis", False) if polar_axis: kwargs.setdefault("is_polar", True) kwargs.setdefault("use_cm", False) args = _plot_sympify(args) plot_expr = _check_arguments(args, 1, 1, **kwargs) lines = [] # apply polar transformation for i, pe in enumerate(plot_expr): r = pe[0] theta = pe[1][0] e1, e2 = (r * cos(theta), r * sin(theta)) lines.extend(line_parametric_2d(e1, e2, *pe[1:], **kwargs)) _set_labels(lines, global_labels, global_rendering_kw) gs = _create_generic_data_series(**kwargs) return graphics(*lines, gs, **kwargs)
[docs] def plot_geometry(*args, **kwargs): """Plot entities from the sympy.geometry module. Typical usage examples are in the following: - Plotting a single geometric entity: .. code-block:: plot_geometry(geom, label [opt], rendering_kw [opt]) - Plotting multiple geometric entities: .. code-block:: plot_geometry(geom1, geom2, ...) - Plotting multiple geometric entities, setting custom labels and rendering options: .. code-block:: plot_geometry( (geom1, label1 [opt], rendering_kw1 [opt]), (geom2, label2 [opt], rendering_kw2 [opt]), **kwargs) Refer to :func:`~spb.graphics.functions_2d.geometry` for a full list of keyword arguments to customize the appearances of lines and regions. Refer to :func:`~spb.graphics.graphics.graphics` for a full list of keyword arguments to customize the appearances of the figure (title, axis labels, ...). Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import (symbols, Circle, Ellipse, Polygon, ... Curve, Segment, Point2D, Point3D, Line3D, Plane, ... Rational, pi, Point, cos, sin) >>> from spb import plot_geometry >>> x, y, z = symbols('x, y, z') Plot a single geometry, customizing its color: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_geometry( ... Ellipse(Point(-3, 2), hradius=3, eccentricity=Rational(4, 5)), ... {"color": "tab:orange"}, grid=False) Plot object containing: [0]: geometry entity: Ellipse(Point2D(-3, 2), 3, 9/5) Plot several numeric geometric entitiesy. By default, circles, ellipses and polygons are going to be filled. Plotting Curve objects is the same as `plot_parametric`. .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_geometry( ... Circle(Point(0, 0), 5), ... Ellipse(Point(-3, 2), hradius=3, eccentricity=Rational(4, 5)), ... Polygon((4, 0), 4, n=5), ... Curve((cos(x), sin(x)), (x, 0, 2 * pi)), ... Segment((-4, -6), (6, 6)), ... Point2D(0, 0)) Plot object containing: [0]: geometry entity: Circle(Point2D(0, 0), 5) [1]: geometry entity: Ellipse(Point2D(-3, 2), 3, 9/5) [2]: geometry entity: RegularPolygon(Point2D(4, 0), 4, 5, 0) [3]: parametric cartesian line: (cos(x), sin(x)) for x over (0.0, 6.283185307179586) [4]: geometry entity: Segment2D(Point2D(-4, -6), Point2D(6, 6)) [5]: geometry entity: Point2D(0, 0) Plot several numeric geometric entities defined by numbers only, turn off fill. Every entity is represented as a line. .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_geometry( ... Circle(Point(0, 0), 5), ... Ellipse(Point(-3, 2), hradius=3, eccentricity=Rational(4, 5)), ... Polygon((4, 0), 4, n=5), ... Curve((cos(x), sin(x)), (x, 0, 2 * pi)), ... Segment((-4, -6), (6, 6)), ... Point2D(0, 0), is_filled=False) Plot object containing: [0]: geometry entity: Circle(Point2D(0, 0), 5) [1]: geometry entity: Ellipse(Point2D(-3, 2), 3, 9/5) [2]: geometry entity: RegularPolygon(Point2D(4, 0), 4, 5, 0) [3]: parametric cartesian line: (cos(x), sin(x)) for x over (0.0, 6.283185307179586) [4]: geometry entity: Segment2D(Point2D(-4, -6), Point2D(6, 6)) [5]: geometry entity: Point2D(0, 0) Plot several symbolic geometric entities. We need to pass in the `params` dictionary, which will be used to substitute symbols before numerical evaluation. Note: here we also set custom labels: .. plot:: :context: close-figs :format: doctest :include-source: True >>> a, b, c, d = symbols("a, b, c, d") >>> plot_geometry( ... (Polygon((a, b), c, n=d), "triangle"), ... (Polygon((a + 2, b + 3), c, n=d + 1), "square"), ... params = {a: 0, b: 1, c: 2, d: 3}) Plot object containing: [0]: geometry entity: RegularPolygon(Point2D(0, 1), 2, 3, 0) [1]: geometry entity: RegularPolygon(Point2D(2, 4), 2, 4, 0) Plot 3D geometric entities. Note: when plotting a Plane, we must always provide the x/y/z ranges: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_geometry( ... (Point3D(5, 5, 5), "center"), ... (Line3D(Point3D(-2, -3, -4), Point3D(2, 3, 4)), "line"), ... (Plane((0, 0, 0), (1, 1, 1)), ... (x, -5, 5), (y, -4, 4), (z, -10, 10))) Plot object containing: [0]: geometry entity: Point3D(5, 5, 5) [1]: geometry entity: Line3D(Point3D(-2, -3, -4), Point3D(2, 3, 4)) [2]: plane series: Plane(Point3D(0, 0, 0), (1, 1, 1)) over (x, -5, 5), (y, -4, 4), (z, -10, 10) Interactive-widget plot. Refer to the interactive sub-module documentation to learn more about the ``params`` dictionary. .. panel-screenshot:: :small-size: 800, 625 from sympy import * from spb import * import panel as pn a, b, c, d = symbols("a, b, c, d") plot_geometry( (Polygon((a, b), c, n=d), "a"), (Polygon((a + 2, b + 3), c, n=d + 1), "b"), params = { a: (0, -1, 1), b: (1, -1, 1), c: (2, 1, 2), d: pn.widgets.IntInput(value=3, start=3, end=8, name="n") }, aspect="equal", is_filled=False, xlim=(-2.5, 5.5), ylim=(-3, 6.5), imodule="panel") See Also ======== plot, plot_implicit, plot_polar, plot_parametric, plot_list, plot_contour, plot_piecewise """ args = _plot_sympify(args) global_labels = kwargs.pop("label", []) global_rendering_kw = kwargs.pop("rendering_kw", None) if not all([isinstance(a, (list, tuple, Tuple)) for a in args]): args = [args] params = kwargs.pop("params", {}) is_interactive = False if len(params) > 0: param = import_module("param") ipywidgets = import_module("ipywidgets") has_param, has_ipywidgets, has_tuples = False, False, False if param and any(isinstance(t, param.Parameter) for t in params.values()): has_param = True if ipywidgets and any(isinstance(t, ipywidgets.Widget) for t in params.values()): has_ipywidgets = True if any(hasattr(t, "__iter__") for t in params.values()): has_tuples = True is_interactive = any([has_param, has_ipywidgets, has_tuples]) series = [] if is_interactive: kwargs["params"] = params for a in args: exprs, ranges, label, rkw = _unpack_args(*a) if len(ranges) > 0: # assume it is a plane series for e in exprs: lbl = str(e) if not label else label kw = kwargs.copy() kw["rendering_kw"] = rkw kw["label"] = lbl if not is_interactive and params: e = e.subs(params) series.append(PlaneSeries(e, *ranges, **kw)) else: for e in exprs: lbl = str(e) if not label else label kw = kwargs.copy() kw["rendering_kw"] = rkw kw["label"] = lbl if not is_interactive and params: e = e.subs(params) series.extend(geometry(e, **kw)) _set_labels(series, global_labels, global_rendering_kw) return graphics(*series, **kwargs)
[docs] def plot_list(*args, **kwargs): """Plots lists of coordinates (ie, lists of numbers). Typical usage examples are in the followings: - Plotting coordinates of a single function: .. code-block:: plot_list(x, y, **kwargs) - Plotting coordinates of multiple functions, adding custom labels and rendering options: .. code-block:: plot_list( (x1, y1, label1 [opt], rendering_kw1 [opt]), (x2, y2, label2 [opt], rendering_kw2 [opt]), ..., **kwargs) Refer to :func:`~spb.graphics.functions_2d.list_2d` for a full list of keyword arguments to customize the appearances of lines. Refer to :func:`~spb.graphics.graphics.graphics` for a full list of keyword arguments to customize the appearances of the figure (title, axis labels, ...). Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, sin, cos >>> from spb import plot_list >>> x = symbols('x') Plot the coordinates of a single function: .. plot:: :context: close-figs :format: doctest :include-source: True >>> xx = [t / 100 * 6 - 3 for t in list(range(101))] >>> yy = [cos(x).evalf(subs={x: t}) for t in xx] >>> plot_list(xx, yy) Plot object containing: [0]: 2D list plot Plot individual points with custom labels: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_list( ... ([0], [0], "A"), ([1], [1], "B"), ([2], [0], "C"), ... scatter=True, is_filled=True) Plot object containing: [0]: 2D list plot [1]: 2D list plot [2]: 2D list plot Scatter plot of the coordinates of multiple functions, with custom rendering keywords: .. plot:: :context: close-figs :format: doctest :include-source: True >>> xx = [t / 70 * 6 - 3 for t in list(range(71))] >>> yy1 = [cos(x).evalf(subs={x: t}) for t in xx] >>> yy2 = [sin(x).evalf(subs={x: t}) for t in xx] >>> plot_list( ... (xx, yy1, "cos"), ... (xx, yy2, "sin", {"marker": "*", "markerfacecolor": None}), ... scatter=True) Plot object containing: [0]: 2D list plot [1]: 2D list plot Interactive-widget plot. Refer to the interactive sub-module documentation to learn more about the ``params`` dictionary. .. panel-screenshot:: :small-size: 800, 625 from sympy import * from spb import * x, t = symbols("x, t") params = {t: (0, 0, 2*pi)} # plot trajectories p1 = plot_parametric( (cos(x), sin(x), (x, 0, 2*pi), {"linestyle": ":"}), (cos(2 * x) / 2, sin(2 * x) / 2, (x, 0, pi), {"linestyle": ":"}), use_cm=False, aspect="equal", show=False, params=params, imodule="panel") # plot points p2 = plot_list( ([cos(t)], [sin(t)], "A"), ([cos(2 * t) / 2], [sin(2 * t) / 2], "B"), rendering_kw={"marker": "s", "markerfacecolor": None}, params=params, scatter=True, show=False) (p1 + p2).show() See Also ======== plot, plot_implicit, plot_polar, plot_parametric, plot_contour, plot_geometry, plot_piecewise, plot3d_list """ g_labels = kwargs.pop("label", []) g_rendering_kw = kwargs.pop("rendering_kw", None) series = [] def is_tuple(t): # verify that t is a tuple of the form (x, y, label [opt], # rendering_kw [opt]) if hasattr(t, "__iter__"): if isinstance(t, (str, dict)): return False if (len(t) >= 2) and all(hasattr(t[i], "__iter__") for i in [0, 1]): return True return False if not any(is_tuple(e) for e in args): # in case we are plotting a single line args = [args] for a in args: if not isinstance(a, (list, tuple)): raise TypeError( "Each argument must be a list or tuple.\n" "Received type(a) = {}".format(type(a))) if (len(a) < 2) or (len(a) > 4): raise ValueError( "Each argument must contain from 2 to 3 elements.\n" "Received {} elements.".format(len(a))) label = [b for b in a if isinstance(b, str)] label = "" if not label else label[0] rendering_kw = [b for b in a if isinstance(b, dict)] rendering_kw = None if not rendering_kw else rendering_kw[0] series.extend(list_2d(*a[:2], label, rendering_kw, **kwargs)) _set_labels(series, g_labels, g_rendering_kw) return graphics(*series, **kwargs)
[docs] def plot_piecewise(*args, **kwargs): """Plots univariate piecewise functions. Typical usage examples are in the followings: - Plotting a single expression with the default range (-10, 10): .. code-block:: plot_piecewise(expr, **kwargs) - Plotting multiple expressions with a single range: .. code-block:: plot_piecewise(expr1, expr2, ..., range, **kwargs) - Plotting multiple expressions with multiple ranges, custom labels and rendering options: .. code-block:: plot_piecewise( (expr1, range1, label1 [opt], rendering_kw1 [opt]), (expr2, range2, label2 [opt], rendering_kw2 [opt]), ..., **kwargs)` Refer to :func:`~spb.graphics.functions_2d.line` for a full list of keyword arguments to customize the appearances of lines. Refer to :func:`~spb.graphics.graphics.graphics` for a full list of keyword arguments to customize the appearances of the figure (title, axis labels, ...). Parameters ========== dots : boolean Wheter to show circular markers at the endpoints. Default to True. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, sin, cos, pi, Heaviside, Piecewise, Eq >>> from spb import plot_piecewise >>> x = symbols('x') Single Plot .. plot:: :context: close-figs :format: doctest :include-source: True >>> f = Piecewise((x**2, x < 2), (5, Eq(x, 2)), (10 - x, True)) >>> plot_piecewise(f, (x, -2, 5)) Plot object containing: [0]: cartesian line: x**2 for x over (-2.0, 1.999999) [1]: 2D list plot [2]: cartesian line: 10 - x for x over (2.000001, 5.0) [3]: 2D list plot [4]: 2D list plot Single plot without dots (circular markers): .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_piecewise(Heaviside(x, 0).rewrite(Piecewise), ... (x, -10, 10), dots=False) Plot object containing: [0]: cartesian line: 0 for x over (-10.0, 0.0) [1]: cartesian line: 1 for x over (1e-06, 10.0) Plot multiple expressions in which the second piecewise expression has a dotted line style. Use the ``label`` keyword argument to set the appropriate entries for the legend: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_piecewise( ... (Heaviside(x, 0).rewrite(Piecewise), (x, -10, 10)), ... (Piecewise( ... (sin(x), x < -5), ... (cos(x), x > 5), ... (1 / x, True)), (x, -8, 8), {"linestyle": ":"}), ... ylim=(-2, 2), detect_poles=True, legend=True, label=["A", "B"]) Plot object containing: [0]: cartesian line: 0 for x over (-10.0, 0.0) [1]: cartesian line: 1 for x over (1e-06, 10.0) [2]: 2D list plot [3]: 2D list plot [4]: cartesian line: sin(x) for x over (-8.0, -5.000001) [5]: 2D list plot [6]: cartesian line: cos(x) for x over (5.000001, 8.0) [7]: 2D list plot [8]: cartesian line: 1/x for x over (-5.0, 5.0) [9]: 2D list plot [10]: 2D list plot See Also ======== plot, plot_implicit, plot_polar, plot_parametric, plot_list, plot_contour, plot_geometry """ if kwargs.pop("params", None) is not None: raise NotImplementedError( "plot_piecewise doesn't support interactive widgets.") args = _plot_sympify(args) plot_expr = _check_arguments(args, 1, 1) if any(callable(p[0]) for p in plot_expr): raise TypeError("plot_piecewise requires symbolic expressions.") free = set() for p in plot_expr: free |= p[0].free_symbols x = free.pop() if free else Symbol("x") fx = lambda use_latex: x.name if not use_latex else latex(x) wrap = lambda use_latex: "f(%s)" if not use_latex else r"f\left(%s\right)" fy = lambda use_latex: wrap(use_latex) % fx(use_latex) kwargs.setdefault("xlabel", fx) kwargs.setdefault("ylabel", fy) kwargs.setdefault("legend", False) kwargs["process_piecewise"] = True labels = kwargs.pop("label", []) kwargs.pop("rendering_kw", None) if isinstance(labels, str): labels = [labels] * len(plot_expr) # NOTE: rendering_kw keyword argument is not implemented in this function # because it would override the optimal settings chosen by the backend. # If a user want to set custom rendering keywords, just use the notation # (expr, range, label [optional], rendering_kw [optional]) color_series_dict = dict() for i, a in enumerate(plot_expr): expr, r, lbl, rkw = a series = line(expr, r, lbl, rkw, **kwargs) if i < len(labels): # this solve issue 32: # https://github.com/Davide-sd/sympy-plot-backends/issues/32 already_set = False for s in series: if isinstance(s, LineOver1DRangeSeries) and not already_set: s.label = labels[i] already_set = True else: s.label = None color_series_dict[i] = series # NOTE: let's overwrite this keyword argument: the dictionary will be used # by the backend to assign the proper colors to the pieces kwargs["process_piecewise"] = color_series_dict return graphics(**kwargs)