Source code for spb.graphics.functions_2d

from sympy import (
    sin, cos, Piecewise, Sum, Wild, sign, piecewise_fold, Interval, Union,
    FiniteSet, Eq, Ne, Expr
)
from sympy.core.relational import Relational
# NOTE: from sympy import EmptySet is a different thing!!!
from sympy.sets.sets import EmptySet
from spb.series import (
    LineOver1DRangeSeries, Parametric2DLineSeries, ContourSeries,
    ImplicitSeries, List2DSeries, GeometrySeries, HVLineSeries
)
from spb.graphics.utils import _plot_sympify
from spb.utils import (
    _create_missing_ranges, _preprocess_multiple_ranges
)
import warnings


def _process_piecewise(piecewise, _range, label, **kwargs):
    """Extract the pieces of an univariate Piecewise function and create the
    necessary series for an univariate plot.

    Notes
    =====

    As a design choice, the following implementation reuses the existing
    classes, instead of creating a new one to deal with Piecewise. Here, each
    piece is going to create at least one series. If a piece is using a union
    of coditions (for example, ``((x < 0) | (x > 2))``), than two or more
    series of the same expression are created (for example, one covering
    ``x < 0`` and the other covering ``x > 2``), both having the same label.

    However, if a piece is outside of the provided plotting range, then it
    will not be added to the plot. This may lead to not-complete plots in some
    backend, such as BokehBackend, which is capable of auto-recompute the data
    on mouse drag. If the user drags the mouse over an area previously not
    shown (thus no data series), there won't be any line on the plot in this
    area.
    """
    # initial range
    irange = Interval(_range[1], _range[2], False, False)
    # ultimately it will contain all the series
    series = []
    # only contains Line2DSeries with fill=True. They have higher
    # rendering priority, as such they will be added to `series` at last.
    filled_series = []
    dots = kwargs.pop("dots", True)

    def func(expr, _set, c, from_union=False):
        if isinstance(_set, Interval):
            start, end = _set.args[0], _set.args[1]

            # arbitrary small offset
            offset = 1e-06
            # offset must be small even if the interval is small
            diff = end - start
            if diff < 1:
                e = 0
                while diff < 1:
                    diff *= 10
                    e -= 1
                offset *= e

            # prevent NaNs from happening at the ends of the interval
            if _set.left_open:
                start += offset
            if _set.right_open:
                end -= offset

            main_series = LineOver1DRangeSeries(
                expr, (_range[0], start, end), label, **kwargs)
            series.append(main_series)

            if dots:
                xx, yy = main_series.get_data()
                if xx[0] != _range[1]:
                    correct_list = series if _set.left_open else filled_series
                    correct_list.append(
                        List2DSeries(
                            [xx[0]], [yy[0]], scatter=True,
                            fill=not _set.left_open, **kwargs)
                    )
                if xx[-1] != _range[2]:
                    correct_list = series if _set.right_open else filled_series
                    correct_list.append(
                        List2DSeries(
                            [xx[-1]], [yy[-1]], scatter=True,
                            fill=not _set.right_open, **kwargs)
                    )
        elif isinstance(_set, FiniteSet):
            loc, val = [], []
            for _loc in _set.args:
                loc.append(float(_loc))
                val.append(float(expr.evalf(subs={_range[0]: _loc})))
            filled_series.append(
                List2DSeries(loc, val, scatter=True, fill=True, **kwargs))
            if not from_union:
                c += 1
        elif isinstance(_set, Union):
            for _s in _set.args:
                c = func(expr, _s, c, from_union=True)
        elif isinstance(_set, EmptySet):
            # in this case, some pieces are outside of the provided range.
            # don't add any series, but increase the counter nonetheless so
            # that there is one-to-one correspondance between the expression
            # and what is plotted.
            if not from_union:
                c += 1
        else:
            raise TypeError(
                "Unhandle situation:\n" +
                "expr: {}\ncond: {}\ntype(cond): {}\n".format(
                    str(expr), _set, type(_set)) +
                "See if you can rewrite the piecewise without "
                "this type of condition and then plot it again.")

        return c

    piecewise = piecewise_fold(piecewise)
    expr_cond = piecewise.as_expr_set_pairs()
    # for the label, attach the number of the piece
    count = 1
    for expr, cond in expr_cond:
        count = func(expr, irange.intersection(cond), count)

    series += filled_series
    return series


def _process_summations(sum_bound, expr):
    """Substitute oo (infinity) lower/upper bounds of a summation with an
    arbitrary big integer number.

    Parameters
    ==========

    NOTE:
    Let's consider the following summation: ``Sum(1 / x**2, (x, 1, oo))``.
    The current implementation of lambdify (SymPy 1.9 at the time of
    writing this) will create something of this form:
    ``sum(1 / x**2 for x in range(1, INF))``
    The problem is that ``type(INF)`` is float, while ``range`` requires
    integers, thus the evaluation will fails.
    Instead of modifying ``lambdify`` (which requires a deep knowledge),
    let's apply this quick dirty hack: substitute symbolic ``oo`` with an
    arbitrary large number.
    """
    def new_bound(t, bound):
        if (not t.is_number) or t.is_finite:
            return t
        if sign(t) >= 0:
            return bound
        return -bound

    # select summations whose lower/upper bound is infinity
    w = Wild("w", properties=[
        lambda t: isinstance(t, Sum),
        lambda t: any((not a[1].is_finite) or (not a[2].is_finite) for i, a in enumerate(t.args) if i > 0)
    ])

    for t in list(expr.find(w)):
        sums_args = list(t.args)
        for i, a in enumerate(sums_args):
            if i > 0:
                sums_args[i] = (
                    a[0], new_bound(a[1], sum_bound),
                    new_bound(a[2], sum_bound)
                )
        s = Sum(*sums_args)
        expr = expr.subs(t, s)
    return expr


def _build_line_series(expr, r, label, **kwargs):
    """Loop over the provided arguments. If a piecewise function is found,
    decompose it in such a way that each argument gets its own series.
    """
    series = []
    pp = kwargs.get("process_piecewise", False)
    sum_bound = int(kwargs.get("sum_bound", 1000))
    if not callable(expr) and expr.has(Piecewise) and pp:
        series += _process_piecewise(expr, r, label, **kwargs)
    else:
        if not callable(expr):
            expr = _process_summations(sum_bound, expr)
        series.append(LineOver1DRangeSeries(expr, r, label, **kwargs))
    return series


[docs] def line(expr, range=None, label=None, rendering_kw=None, **kwargs): """Plot a function of one variable over a 2D space. Parameters ========== expr : Expr or callable It can either be a symbolic expression representing the function of one variable to be plotted, or a numerical function of one variable, supporting vectorization. In the latter case the following keyword arguments are not supported: ``params``, ``sum_bound``. range : (symbol, min, max) A 3-tuple denoting the range of the x variable. Default values: `min=-10` and `max=10`. label : str, optional The label to be shown in the legend. If not provided, the string representation of ``expr`` will be used. 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. adaptive : bool, optional Setting ``adaptive=True`` activates the adaptive algorithm implemented in [python-adaptive]_ to create smooth plots. Use ``adaptive_goal`` and ``loss_fn`` to further customize the output. The default value is ``False``, which uses an uniform sampling strategy, where the number of discretization points is specified by the ``n`` keyword argument. adaptive_goal : callable, int, float or None Controls the "smoothness" of the evaluation. Possible values: * ``None`` (default): it will use the following goal: ``lambda l: l.loss() < 0.01`` * number (int or float). The lower the number, the more evaluation points. This number will be used in the following goal: ``lambda l: l.loss() < number`` * callable: a function requiring one input element, the learner. It must return a float number. Refer to [python-adaptive]_ for more information. color_func : callable or Expr, optional Define the line color mapping. It can either be: * A numerical function of 2 variables, x, y (the points computed by the internal algorithm) supporting vectorization. * A symbolic expression having at most as many free symbols as ``expr``. * None: the default value (no color mapping). detect_poles : boolean or str, optional Chose whether to detect and correctly plot poles. There are two algorithms at work: 1. based on the gradient of the numerical data, it introduces NaN values at locations where the steepness is greater than some threshold. This splits the line into multiple segments. To improve detection, increase the number of discretization points ``n`` and/or change the value of ``eps``. 2. a symbolic approach based on the ``continuous_domain`` function from the ``sympy.calculus.util`` module, which computes the locations of discontinuities. If any is found, vertical lines will be shown. Possible options: * ``True``: activate poles detection computed with the numerical gradient. * ``False``: no poles detection. * ``"symbolic"``: use both numerical and symbolic algorithms. Default to ``False``. eps : float, optional An arbitrary small value used by the ``detect_poles`` algorithm. Default value to 0.1. Before changing this value, it is recommended to increase the number of discretization points. exclude : list, optional A list of numerical values in the horizontal coordinate which are going to be excluded from the plot. In practice, it introduces discontinuities in the resulting line. force_real_eval : boolean, optional Default to False, with which the numerical evaluation is attempted over a complex domain, which is slower but produces correct results. Set this to True if performance is of paramount importance, but be aware that it might produce wrong results. It only works with ``adaptive=False``. scatter : boolean, optional Default to False, which will render a line connecting all the points. If True, a scatter plot will be generated. fill : boolean, optional Default to True, which will render empty circular markers. It only works if ``scatter=True``. If False, filled circular markers will be rendered. loss_fn : callable or None The loss function to be used by the ``adaptive`` learner. Possible values: * ``None`` (default): it will use the ``default_loss`` from the adaptive module. * callable : Refer to [python-adaptive]_ for more information. Specifically, look at ``adaptive.learner.learner1D`` to find more loss functions. n : int, optional Used when the ``adaptive=False``: the function is uniformly sampled at ``n`` number of points. Default value to 1000. If the ``adaptive=True``, this parameter will be ignored. only_integers : boolean, optional Default to ``False``. If ``True``, discretize the domain with integer numbers. It only works when ``adaptive=False``. When ``only_integers=True``, the number of discretization points is choosen by the algorithm. params : dict A dictionary mapping symbols to parameters. This keyword argument enables the interactive-widgets plot, which doesn't support the adaptive algorithm (meaning it will use ``adaptive=False``). Learn more by reading the documentation of the interactive sub-module. steps : {'pre', 'post', 'mid', False}, default: False, optional If set, it connects consecutive points with steps rather than straight segments. sum_bound : int, optional When plotting sums, the expression will be pre-processed in order to replace lower/upper bounds set to +/- infinity with this +/- numerical value. Default value to 1000. Note: the higher this number, the slower the evaluation. tx, ty : callable, optional Apply a numerical function to the discretized x-direction or to the output of the numerical evaluation, the y-direction. xscale : 'linear' or 'log', optional Sets the scaling of the discretized range. Default to ``'linear'``. Returns ======= series : list A list containing one instance of ``LineOver1DRangeSeries``. Examples ======== .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy import symbols, sin, pi, tan, exp, cos, log, floor >>> from spb import * >>> x, y = symbols('x, y') Single Plot .. plot:: :context: close-figs :format: doctest :include-source: True >>> graphics(line(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 >>> graphics( ... line(x, (x, -3, 3)), ... line(log(x), (x, -3, 3), rendering_kw={"linestyle": "--"}), ... line(exp(x), (x, -3, 3), rendering_kw={"linestyle": ":"}), ... aspect="equal", ylim=(-3, 3) ... ) 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)) >>> graphics( ... line(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)) >>> graphics( ... line(expr, (y, 2, 10), scatter=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 >>> graphics( ... line( ... 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 >>> graphics( ... line(floor(x) / x, (x, -3.25, 3.25), exclude=list(range(-4, 5))), ... ylim=(-1, 5) ... ) Plot object containing: [0]: cartesian line: floor(x)/x for x over (-3.25, 3.25) Creating a step plot: .. plot:: :context: close-figs :format: doctest :include-source: True >>> graphics( ... line(x-2, (x, 0, 10), only_integers=True, steps="pre", label="pre"), ... line(x, (x, 0, 10), only_integers=True, steps="mid", label="mid"), ... line(x+2, (x, 0, 10), only_integers=True, steps="post", label="post"), ... ) Plot object containing: [0]: cartesian line: x - 2 for x over (0.0, 10.0) [1]: cartesian line: x for x over (0.0, 10.0) [2]: cartesian line: x + 2 for x over (0.0, 10.0) 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. * combining together multiple lines. .. 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 >>> graphics( ... line( ... expr, (x, -5, 5), "distance from (0, 0)", ... rendering_kw={"cmap": "plasma"}, ... adaptive=False, detect_poles=True, n=3e04, ... eps=1e-04, color_func=cf), ... line(5 * sin(x), (x, -5, 5), rendering_kw={"linestyle": "--"}), ... ylim=(-10, 10), title="$%s$" % latex(expr) ... ) Plot object containing: [0]: cartesian line: 5*sin(x) + 1/cos(10*x) for x over (-5.0, 5.0) [1]: cartesian line: 5*sin(x) for x over (-5.0, 5.0) 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") params = { (f1, f2): pn.widgets.RangeSlider( value=(1, 2), start=0, end=10, step=0.1), # frequencies d: (0.25, 0, 1), # damping n: (2, 0, 4) # multiple of pi } graphics( line(cos(f1 * x) * exp(-d * x), prange(x, 0, n * pi), label="oscillator 1", params=params), line(cos(f2 * x) * exp(-d * x), prange(x, 0, n * pi), label="oscillator 2", params=params), line(exp(-d * x), prange(x, 0, n * pi), label="upper limit", rendering_kw={"linestyle": ":"}, params=params), line(-exp(-d * x), prange(x, 0, n * pi), label="lower limit", rendering_kw={"linestyle": ":"}, params=params), ylim=(-1.25, 1.25), title=("$f_1$ = {:.2f} Hz", f1) ) See Also ======== line_parametric_2d, line_polar, implicit_2d, list_2d, geometry, spb.graphics.functions_3d.line_parametric_3d """ expr = _plot_sympify(expr) params = kwargs.get("params", {}) range = _create_missing_ranges( [expr], [range] if range else [], 1, params)[0] return _build_line_series( expr, range, label, rendering_kw=rendering_kw, **kwargs)
[docs] def line_parametric_2d( expr1, expr2, range=None, label=None, rendering_kw=None, colorbar=True, use_cm=True, **kwargs ): """Plots a 2D parametric curve. Parameters ========== expr1, expr2 : Expr or callable The expression representing the horizontal and vertical components of the parametric function. It can either be a symbolic expression representing the function of one variable to be plotted, or a numerical function of one variable, supporting vectorization. In the latter case the following keyword arguments are not supported: ``params``, ``sum_bound``. range : (symbol, min, max) A 3-tuple denoting the parameter symbol, start value and stop value. For example, ``(u, 0, 5)``. If the range is not specified, then a default range of (-10, 10) is used. label : str, optional The label to be shown in the legend. If not provided, the string representation of ``expr1`` and ``expr1`` will be used. 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. adaptive : bool, optional Setting ``adaptive=True`` activates the adaptive algorithm implemented in [python-adaptive]_ to create smooth plots. Use ``adaptive_goal`` and ``loss_fn`` to further customize the output. The default value is ``False``, which uses an uniform sampling strategy, where the number of discretization points is specified by the ``n`` keyword argument. adaptive_goal : callable, int, float or None Controls the "smoothness" of the evaluation. Possible values: * ``None`` (default): it will use the following goal: ``lambda l: l.loss() < 0.01`` * number (int or float). The lower the number, the more evaluation points. This number will be used in the following goal: ``lambda l: l.loss() < number`` * callable: a function requiring one input element, the learner. It must return a float number. Refer to [python-adaptive]_ for more information. colorbar : boolean, optional Show/hide the colorbar. Default to True (colorbar is visible). Only works when ``use_cm=True``. color_func : callable, optional Define the line color mapping when ``use_cm=True``. It can either be: * A numerical function supporting vectorization. The arity can be: * 1 argument: ``f(t)``, where ``t`` is the parameter. * 2 arguments: ``f(x, y)`` where ``x, y`` are the coordinates of the points. * 3 arguments: ``f(x, y, t)``. * A symbolic expression having at most as many free symbols as ``expr1`` or ``expr2``. * None: the default value (color mapping applied to the parameter). exclude : list, optional A list of numerical values along the parameter which are going to be excluded from the plot. In practice, it introduces discontinuities in the resulting line. force_real_eval : boolean, optional Default to False, with which the numerical evaluation is attempted over a complex domain, which is slower but produces correct results. Set this to True if performance is of paramount importance, but be aware that it might produce wrong results. It only works with ``adaptive=False``. loss_fn : callable or None The loss function to be used by the adaptive learner. Possible values: * ``None`` (default): it will use the ``default_loss`` from the adaptive module. * callable : Refer to [python-adaptive]_ for more information. Specifically, look at ``adaptive.learner.learner1D`` to find more loss functions. n : int, optional Used when the ``adaptive=False``. The function is uniformly sampled at ``n`` number of points. Default value to 1000. If the ``adaptive=True``, this parameter will be ignored. params : dict A dictionary mapping symbols to parameters. This keyword argument enables the interactive-widgets plot, which doesn't support the adaptive algorithm (meaning it will use ``adaptive=False``). Learn more by reading the documentation of the interactive sub-module. tx, ty, tp : callable, optional Apply a numerical function to the x-direction, y-direction and parameter, respectively. use_cm : boolean, optional If True, apply a color map to the parametric lines. If False, solid colors will be used instead. Default to True. xscale : 'linear' or 'log', optional Sets the scaling of the parameter. Returns ======= series : list A list containing one instance of ``Parametric2DLineSeries``. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, cos, sin, pi, floor, log >>> from spb import * >>> 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 >>> graphics( ... line_parametric_2d( ... 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 >>> graphics( ... line_parametric_2d(2 * cos(t), sin(t), (t, 0, 2*pi), use_cm=False), ... line_parametric_2d(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 >>> graphics( ... line_parametric_2d( ... 3 * cos(u), 3 * sin(u), (u, 0, 2 * pi), "u [deg]", ... rendering_kw={"lw": 3}, tp=np.rad2deg), ... line_parametric_2d( ... 3 * cos(2 * v), 5 * sin(4 * v), (v, 0, pi), "v [deg]", ... tp=np.rad2deg ... ), ... aspect="equal" ... ) 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) >>> graphics( ... line_parametric_2d( ... 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) >>> graphics( ... line_parametric_2d(fx, fy, ("t", 0, 12 * pi), ... use_cm=False, n=2000), ... title="Butterfly Curve", ... ) # doctest: +SKIP 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") graphics( line_parametric_2d( 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 ======== spb.graphics.functions_3d.line_parametric_3d, line_polar, line """ expr1, expr2 = map(_plot_sympify, [expr1, expr2]) params = kwargs.get("params", {}) range = _create_missing_ranges( [expr1, expr2], [range] if range else [], 1, params)[0] s = Parametric2DLineSeries( expr1, expr2, range, label, rendering_kw=rendering_kw, colorbar=colorbar, use_cm=use_cm, **kwargs) return [s]
[docs] def line_polar(expr, range=None, label=None, rendering_kw=None, **kwargs): """Creates a 2D polar plot of a function of one variable. This function executes ``line_parametric_2d``. Refer to its documentation for a full list of keyword arguments. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, sin, cos, exp, pi >>> from spb import * >>> theta = symbols('theta') Plot with cartesian axis: .. plot:: :context: close-figs :format: doctest :include-source: True >>> graphics( ... line_polar(3 * sin(2 * theta), (theta, 0, 2*pi)), ... aspect="equal" ... ) 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 >>> graphics( ... line_polar(exp(sin(theta)) - 2 * cos(4 * theta), (theta, 0, 2 * pi)), ... polar_axis=True, aspect="equal" ... ) 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 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) } series = [] for n in range(20): series += line_polar( func(n), prange(theta, 0, tp*pi), params=params, rendering_kw={"line_color": "black", "line_width": 0.5}) graphics( *series, aspect="equal", layout = "sbl", ncols = 1, title="Guilloché Pattern Explorer", backend=BB, legend=False, servable=True, imodule="panel" ) See Also ======== line_parametric_2d, line """ expr = _plot_sympify(expr) params = kwargs.get("params", {}) kwargs.setdefault("use_cm", False) range = _create_missing_ranges( [expr], [range] if range else [], 1, params)[0] theta = range[0] return line_parametric_2d( expr * cos(theta), expr * sin(theta), range, label, rendering_kw, **kwargs)
[docs] def contour( expr, range1=None, range2=None, label=None, rendering_kw=None, colorbar=True, clabels=True, fill=True, **kwargs ): """Plots contour lines or filled contours of a function of two variables. Parameters ========== clabels : bool, optional Visualize labels of contour lines. Only works when ``fill=False``. Default to True. Note that some backend might not implement this feature. colorbar : boolean, optional Show/hide the colorbar. Default to True (colorbar is visible). Only works when ``use_cm=True``. fill : bool, optional Choose between filled contours or line contours. Default to True (filled contours). **kwargs : Keyword arguments are the same as :func:`~spb.graphics.functions_3d.surface`. Refer to its documentation for a for a full list of keyword arguments. Returns ======= series : list A list containing one instance of ``ContourSeries``. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, cos, exp, sin, pi, Eq, Add >>> from spb import * >>> x, y = symbols('x, y') Filled contours of a function of two variables. .. plot:: :context: close-figs :format: doctest :include-source: True >>> graphics( ... contour( ... cos((x**2 + y**2)) * exp(-(x**2 + y**2) / 10), ... (x, -5, 5), (y, -5, 5) ... ), ... grid=False ... ) 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 >>> graphics( ... contour(expr, (x, 0, 2 * pi), (y, 0, 2 * pi), fill=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 >>> graphics( ... contour(expr, (x, 0, 2 * pi), (y, 0, 2 * pi), "z", ... rendering_kw={"cmap": "coolwarm"}), ... contour(expr, (x, 0, 2 * pi), (y, 0, 2 * pi), ... rendering_kw={"colors": "k", "cmap": None, "linewidths": 0.75}, ... fill=False), ... grid=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) [1]: 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) 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) >>> graphics( ... contour( ... eq1.rewrite(Add), (x, 0, 2 * pi), (y, 0, 2 * pi), ... rendering_kw={"levels": [0]}, ... fill=False, clabels=False), ... contour( ... eq2.rewrite(Add), (x, 0, 2 * pi), (y, 0, 2 * pi), ... rendering_kw={"levels": [0]}, ... fill=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") >>> graphics( ... contour( ... sin(2 * r) * cos(theta), (theta, 0, 2*pi), (r, 0, 7), ... rendering_kw={"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 graphics( 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 ======== spb.graphics.functions_3d.surface """ expr = _plot_sympify(expr) params = kwargs.get("params", {}) if not (range1 and range2): warnings.warn( "No ranges were provided. This function will attempt to find " "them, however the order will be arbitrary, which means the " "visualization might be flipped." ) ranges = _preprocess_multiple_ranges([expr], [range1, range2], 2, params) s = ContourSeries( expr, *ranges, label, rendering_kw=rendering_kw, colorbar=colorbar, fill=fill, clabels=clabels, **kwargs) return [s]
[docs] def implicit_2d( expr, range1=None, range2=None, label=None, rendering_kw=None, color=None, border_color=None, border_kw=None, **kwargs ): """ Plot implicit equations / inequalities. ``implicit_2d``, 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. Parameters ========== expr : Expr, Relational, BooleanFunction The equation / inequality that is to be plotted. range1, range2 : tuples or Symbol Tuple denoting the discretization domain, for example: ``(x, -10, 10)``. label : str, optional The label to be shown when multiple expressions are plotted. If not provided, the string representation of the expression will be used. rendering_kw : dict, optional A dictionary of keywords/values which is passed to the backend's function to customize the appearance of contours. Refer to the plotting library (backend) manual for more informations. color : str, optional Specify the color of lines/regions. Default to None (automatic coloring by the backend). border_color : str or bool, optional If given, a limiting border will be added when plotting inequalities (<, <=, >, >=). Use ``border_kw`` if more customization options are required. border_kw : dict, optional If given, a limiting border will be added when plotting inequalities (<, <=, >, >=). This is a dictionary of keywords/values which is passed to the backend's function to customize the appearance of the limiting border. Refer to the plotting library (backend) manual for more informations. adaptive : bool, optional The default value is set to ``False``, meaning that the internal algorithm uses a mesh grid approach. In such case, Boolean combinations of expressions cannot be plotted. If set to ``True``, the internal algorithm uses interval arithmetic. If the expression cannot be plotted with interval arithmetic, it switches to the meshgrid approach. depth : integer The depth of recursion for adaptive grid. Default value is 0. Takes value in the range (0, 4). Think of the resulting plot as a picture composed by pixels. By increasing ``depth`` we are increasing the number of pixels, thus obtaining a more accurate plot. n, n1, n2 : int Number of discretization points in the horizontal and vertical directions when ``adaptive=False``. Default to 100. ``n`` is a shortcut to set the same number of discretization points on both directions. params : dict A dictionary mapping symbols to parameters. This keyword argument enables the interactive-widgets plot. Learn more by reading the documentation of the interactive sub-module. show_in_legend : bool If True, add a legend entry for the expression being plotted. This option is useful to hide a particular expression when combining together multiple plots. Default to True. Returns ======= series : list A list containing at most two instances of ``Implicit2DSeries``. 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 * >>> x, y = symbols('x y') Plot a line representing an equality: .. plot:: :context: close-figs :format: doctest :include-source: True >>> graphics(implicit_2d(x - 1, (x, -5, 5), (y, -5, 5))) Plot object containing: [0]: Implicit expression: Eq(x - 1, 0) for x over (-5.0, 5.0) and y over (-5.0, 5.0) Plot a region: .. plot:: :context: close-figs :format: doctest :include-source: True >>> graphics( ... implicit_2d(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. .. plot:: :context: close-figs :format: doctest :include-source: True >>> expr = 4 * (cos(x) - sin(y) / 5)**2 + 4 * (-cos(x) / 5 + sin(y))**2 >>> graphics( ... implicit_2d( ... expr <= pi, (x, -pi, pi), (y, -pi, pi), ... color="gold", border_color="k", ... border_kw={"linestyles": "-.", "linewidths": 1} ... ), ... grid=False ... ) 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 >>> graphics( ... implicit_2d( ... Eq(y, sin(x)) & (y > 0), (x, -2 * pi, 2 * pi), (y, -4, 4)), ... implicit_2d( ... Eq(y, sin(x)) & (y < 0), (x, -2 * pi, 2 * pi), (y, -4, 4)), ... ylim=(-2, 2) ... ) 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) >>> series = [] >>> for L_val in L_array: ... series += implicit_2d( ... expr.subs({b: b_val, L: L_val}), (t, 0, 3), (V, 0, 1000), ... label="L = %s" % L_val) >>> graphics(*series) 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) >>> graphics( ... implicit_2d( ... expr1, *ranges, adaptive=True, depth=0, ... label="adaptive=True, depth=0"), ... implicit_2d( ... expr2, *ranges, adaptive=True, depth=1, ... label="adaptive=True, depth=1"), ... implicit_2d( ... expr3, *ranges, adaptive=False, label="adaptive=False"), ... grid=False ... ) Plot object containing: [0]: Implicit expression: Eq(x*y - 20, 15*y) for x over (15.0, 30.0) and y over (0.0, 50.0) [1]: Implicit expression: Eq(y*(x - 3) - 20, 15*y) for x over (15.0, 30.0) and y over (0.0, 50.0) [2]: Implicit expression: Eq(y*(x - 6) - 20, 15*y) for x over (15.0, 30.0) and y over (0.0, 50.0) 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 = graphics( ... implicit_2d(expr, (x, -10, 10), (y, -10, 10)), ... grid=False, title="$%s$ : First approach" % latex(expr), ... aspect="equal", show=False) >>> p2 = graphics( ... implicit_2d(x < 20, (x, -10, 10), (y, -10, 10)), ... implicit_2d(Eq(*expr.args), (x, -10, 10), (y, -10, 10), ... color="w", show_in_legend=False), ... grid=False, title="$%s$ : Second approach" % latex(expr), ... aspect="equal", show=False) >>> plotgrid(p1, p2, 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) graphics( implicit_2d(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 ======== line, spb.graphics.functions_3d.implicit_3d """ expr = _plot_sympify(expr) params = kwargs.get("params", {}) if not (range1 and range2): warnings.warn( "No ranges were provided. This function will attempt to find " "them, however the order will be arbitrary, which means the " "visualization might be flipped." ) series = [] ranges = _preprocess_multiple_ranges([expr], [range1, range2], 2, params) series.append(ImplicitSeries( expr, *ranges, label, color=color, rendering_kw=rendering_kw, **kwargs)) if (border_color or border_kw) and (not isinstance(expr, (Expr, Eq, Ne))): kw = kwargs.copy() kw["color"] = border_color kw["rendering_kw"] = border_kw kw.setdefault("show_in_legend", False) if isinstance(expr, Relational): expr = expr.rhs - expr.lhs series.append( ImplicitSeries(expr, *ranges, label, **kw)) return series
[docs] def list_2d(coord_x, coord_y, label=None, rendering_kw=None, **kwargs): """Plots lists of coordinates. Parameters ========== coord_x, coord_y : list or tuple List of coordinates. label : str, optional The label to be shown in the legend. 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 scatter : boolean, optional Default to False, which will render a line connecting all the points. If True, a scatter plot will be generated. fill : boolean, optional Default to False, which will render empty circular markers. It only works if ``scatter=True``. If True, filled circular markers will be rendered. params : dict A dictionary mapping symbols to parameters. This keyword argument enables the interactive-widgets plot. Learn more by reading the documentation of the interactive sub-module. Returns ======= series : list A list containing one instance of ``List2DSeries``. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, sin, cos >>> from spb import * >>> 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] >>> graphics(list_2d(xx, yy)) Plot object containing: [0]: 2D list plot Plot individual points with custom labels. Each point will be converted to a list by the algorithm: .. plot:: :context: close-figs :format: doctest :include-source: True >>> graphics( ... list_2d(0, 0, "A", scatter=True), ... list_2d(1, 1, "B", scatter=True), ... list_2d(2, 0, "C", scatter=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] >>> graphics( ... list_2d(xx, yy1, "cos", scatter=True), ... list_2d(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)} graphics( line_parametric_2d( cos(x), sin(x), (x, 0, 2*pi), rendering_kw={"linestyle": ":"}, use_cm=False), line_parametric_2d( cos(2 * x) / 2, sin(2 * x) / 2, (x, 0, pi), rendering_kw={"linestyle": ":"}, use_cm=False), list_2d( cos(t), sin(t), "A", rendering_kw={"marker": "s", "markerfacecolor": None}, params=params, scatter=True), list_2d( cos(2 * t) / 2, sin(2 * t) / 2, "B", rendering_kw={"marker": "s", "markerfacecolor": None}, params=params, scatter=True), aspect="equal" ) See Also ======== line, spb.graphics.functions_3d.list_3d """ if not hasattr(coord_x, "__iter__"): coord_x = [coord_x] if not hasattr(coord_y, "__iter__"): coord_y = [coord_y] s = List2DSeries( coord_x, coord_y, label, rendering_kw=rendering_kw, **kwargs) return [s]
[docs] def geometry(geom, label=None, rendering_kw=None, fill=True, **kwargs): """Plot entities from the sympy.geometry module. Parameters ========== geom : GeometryEntity Represent the geometric entity to be plotted. label : str, optional The name of the geometry entity to be eventually shown on the legend. If not provided, the string representation of ``geom`` will be used. rendering_kw : dict, optional A dictionary of keywords/values which is passed to the backend's function to customize the appearance of lines or fills. Refer to the plotting library (backend) manual for more informations. fill : boolean Default to True. Fill the polygon/circle/ellipse. A dictionary mapping symbols to parameters. This keyword argument enables the interactive-widgets plot. Learn more by reading the documentation of the interactive sub-module. Returns ======= series : list A list containing one instance of ``GeometrySeries``. 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 * >>> x, y, z = symbols('x, y, z') Plot a single geometry, customizing its color: .. plot:: :context: close-figs :format: doctest :include-source: True >>> graphics( ... geometry( ... Ellipse(Point(-3, 2), hradius=3, eccentricity=Rational(4, 5)), ... rendering_kw={"color": "tab:orange"}), ... grid=False, aspect="equal" ... ) 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 >>> graphics( ... geometry(Circle(Point(0, 0), 5)), ... geometry(Ellipse(Point(-3, 2), hradius=3, eccentricity=Rational(4, 5))), ... geometry(Polygon((4, 0), 4, n=5)), ... geometry(Curve((cos(x), sin(x)), (x, 0, 2 * pi))), ... geometry(Segment((-4, -6), (6, 6))), ... geometry(Point2D(0, 0)), ... aspect="equal", grid=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 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 >>> graphics( ... geometry(Circle(Point(0, 0), 5), fill=False), ... geometry( ... Ellipse(Point(-3, 2), hradius=3, eccentricity=Rational(4, 5)), ... fill=False), ... geometry(Polygon((4, 0), 4, n=5), fill=False), ... geometry(Curve((cos(x), sin(x)), (x, 0, 2 * pi)), fill=False), ... geometry(Segment((-4, -6), (6, 6)), fill=False), ... geometry(Point2D(0, 0), fill=False), ... aspect="equal", grid=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 3D geometric entities. Instances of ``Plane`` must be plotted with ``implicit_3d`` or with ``plane`` (with the necessary ranges). .. k3d-screenshot:: from sympy import * from spb import * x, y, z = symbols("x, y, z") graphics( geometry( Point3D(0, 0, 0), label="center", rendering_kw={"point_size": 1}), geometry(Line3D(Point3D(-2, -3, -4), Point3D(2, 3, 4)), "line"), plane( Plane((0, 0, 0), (1, 1, 1)), (x, -5, 5), (y, -4, 4), (z, -10, 10)), backend=KB ) 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") params = { a: (0, -1, 1), b: (1, -1, 1), c: (2, 1, 2), d: pn.widgets.IntInput(value=6, start=3, end=8, name="n") } graphics( geometry(Polygon((a, b), c, n=d), "a", params=params), geometry( Polygon((a + 2, b + 3), c, n=d + 1), "b", params=params, fill=False), aspect="equal", xlim=(-2.5, 5.5), ylim=(-3, 6.5), imodule="panel") See Also ======== line, spb.graphics.functions_3d.plane """ s = GeometrySeries( geom, label=label, rendering_kw=rendering_kw, fill=fill, **kwargs) return [s]
def hline(v, label=None, rendering_kw=None, show_in_legend=True, **kwargs): """Create an horizontal line at a given location in a 2D space. Parameters ========== v : float or Expr The y-coordinate where to draw the horizontal line. label : str, optional The label to be shown on the legend. 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. show_in_legend : bool, optional Show/hide the line from the legend. Default to True (line is visible). Returns ======= A list containing one instance of ``HVLineSeries``. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import * >>> from spb import * >>> x = symbols("x") >>> graphics( ... line(cos(x), (x, -pi, pi)), ... hline( ... 0.5, rendering_kw={"linestyle": ":"}, ... show_in_legend=False), ... grid=False ... ) Plot object containing: [0]: cartesian line: cos(x) for x over (-3.141592653589793, 3.141592653589793) [1]: horizontal line at y = 0.500000000000000 Interactive widget plot: .. panel-screenshot:: :small-size: 800, 625 from sympy import * from spb import * x, u, v, w = symbols("x u v w") params = { u: (1, 0, 2), v: (1, 0, 2), w: (0.5, -1, 1) } graphics( line(u * cos(v * x), (x, -pi, pi), params=params), hline( w, rendering_kw={"linestyle": ":"}, show_in_legend=False, params=params), grid=False, ylim=(-2, 2) ) See Also ======== line """ return [ HVLineSeries( v, True, label, rendering_kw=rendering_kw, show_in_legend=show_in_legend, **kwargs) ] def vline(v, label=None, rendering_kw=None, show_in_legend=True, **kwargs): """Create an horizontal line at a given location in a 2D space. Parameters ========== v : float or Expr The x-coordinate where to draw the horizontal line. label : str, optional The label to be shown on the legend. 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. show_in_legend : bool, optional Show/hide the line from the legend. Default to True (line is visible). Returns ======= A list containing one instance of ``HVLineSeries``. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import * >>> from spb import * >>> x = symbols("x") >>> graphics( ... line(cos(x), (x, -pi, pi)), ... vline(1, rendering_kw={"linestyle": ":"}, show_in_legend=False), ... vline(-1, rendering_kw={"linestyle": ":"}, show_in_legend=False), ... grid=False ... ) Plot object containing: [0]: cartesian line: cos(x) for x over (-3.141592653589793, 3.141592653589793) [1]: horizontal line at y = 0.500000000000000 Interactive widget plot: .. panel-screenshot:: :small-size: 800, 625 from sympy import * from spb import * x, u, v, w = symbols("x u v w") params = { u: (1, 0, 2), v: (1, 0, 2), w: (1, -pi, pi) } graphics( line(u * cos(v * x), (x, -pi, pi), params=params), vline( w, rendering_kw={"linestyle": ":"}, show_in_legend=False, params=params), grid=False, ylim=(-2, 2) ) See Also ======== line """ return [ HVLineSeries( v, False, label, rendering_kw=rendering_kw, show_in_legend=show_in_legend, **kwargs) ]