def test_common_keywords(): # TODO: here I didn't test axis_center, autoscale, margin kw = dict( title="a", xlabel="x", ylabel="y", zlabel="z", aspect="equal", grid=False, xscale="log", yscale="log", zscale="log", legend=True, xlim=(-1, 1), ylim=(-2, 2), zlim=(-3, 3), size=(5, 10), backend=BB, ) p = Plot(**kw) assert p.title == "a" assert p.xlabel == "x" assert p.ylabel == "y" assert p.zlabel == "z" assert p.aspect == "equal" assert p.grid == False assert p.xscale == "log" assert p.yscale == "log" assert p.zscale == "log" assert p.legend == True assert p.xlim == (-1, 1) assert p.ylim == (-2, 2) assert p.zlim == (-3, 3) assert p.size == (5, 10) assert p._kwargs == kw
def plot_contour(*args, show=True, **kwargs): """ Draws contour plot of a function Usage ===== Single plot ``plot_contour(expr, range_x, range_y, **kwargs)`` If the ranges are not specified, then a default range of (-10, 10) is used. Multiple plot with the same range. ``plot_contour(expr1, expr2, range_x, range_y, **kwargs)`` If the ranges are not specified, then a default range of (-10, 10) is used. Multiple plots with different ranges. ``plot_contour((expr1, range_x, range_y), (expr2, range_x, range_y), ..., **kwargs)`` Ranges have to be specified for every expression. Default range may change in the future if a more advanced default range detection algorithm is implemented. Arguments ========= ``expr`` : Expression representing the function along x. ``range_x``: (x, 0, 5), A 3-tuple denoting the range of the x variable. ``range_y``: (y, 0, 5), A 3-tuple denoting the range of the y variable. Keyword Arguments ================= Arguments for ``ContourSeries`` class: ``n1``: int. The x range is sampled uniformly at ``n1`` of points. ``n2``: int. The y range is sampled uniformly at ``n2`` of points. ``n``: int. The x and y ranges are sampled uniformly at ``n`` of points. It overrides ``n1`` and ``n2``. Arguments for ``Plot`` class: ``title`` : str. Title of the plot. ``size`` : (float, float), optional A tuple in the form (width, height) in inches to specify the size of the overall figure. The default value is set to ``None``, meaning the size will be set by the default backend. See Also ======== Plot, ContourSeries """ from spb.defaults import TWO_D_B args = _plot_sympify(args) kwargs.setdefault("backend", TWO_D_B) kwargs = _set_discretization_points(kwargs, ContourSeries) plot_expr = _check_arguments(args, 1, 2) series = [ContourSeries(*arg, **kwargs) for arg in plot_expr] xlabel = series[0].var_x.name ylabel = series[0].var_y.name kwargs.setdefault("xlabel", xlabel) kwargs.setdefault("ylabel", ylabel) plot_contours = Plot(*series, **kwargs) if show: plot_contours.show() return plot_contours
def plot3d_parametric_surface(*args, show=True, **kwargs): """ Plots a 3D parametric surface plot. Explanation =========== Single plot. ``plot3d_parametric_surface(expr_x, expr_y, expr_z, range_u, range_v, **kwargs)`` If the ranges is not specified, then a default range of (-10, 10) is used. Multiple plots. ``plot3d_parametric_surface((expr_x, expr_y, expr_z, range_u, range_v), ..., **kwargs)`` Ranges have to be specified for every expression. Default range may change in the future if a more advanced default range detection algorithm is implemented. Arguments ========= ``expr_x``: Expression representing the function along ``x``. ``expr_y``: Expression representing the function along ``y``. ``expr_z``: Expression representing the function along ``z``. ``range_u``: ``(u, 0, 5)``, A 3-tuple denoting the range of the ``u`` variable. ``range_v``: ``(v, 0, 5)``, A 3-tuple denoting the range of the v variable. Keyword Arguments ================= Arguments for ``ParametricSurfaceSeries`` class: ``n1``: int. The ``u`` range is sampled uniformly at ``n1`` of points ``n2``: int. The ``v`` range is sampled uniformly at ``n2`` of points ``n``: int. The u and v ranges are sampled uniformly at ``n`` of points. It overrides ``n1`` and ``n2``. Arguments for ``Plot`` class: ``title`` : str. Title of the plot. ``size`` : (float, float), optional A tuple in the form (width, height) in inches to specify the size of the overall figure. The default value is set to ``None``, meaning the size will be set by the default backend. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, cos, sin >>> from sympy.plotting import plot3d_parametric_surface >>> u, v = symbols('u v') Single plot. .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot3d_parametric_surface(cos(u + v), sin(u - v), u - v, ... (u, -5, 5), (v, -5, 5)) Plot object containing: [0]: parametric cartesian surface: (cos(u + v), sin(u - v), u - v) for u over (-5.0, 5.0) and v over (-5.0, 5.0) See Also ======== Plot, ParametricSurfaceSeries """ from spb.defaults import THREE_D_B args = _plot_sympify(args) kwargs.setdefault("backend", THREE_D_B) kwargs = _set_discretization_points(kwargs, ParametricSurfaceSeries) plot_expr = _check_arguments(args, 3, 2) kwargs.setdefault("xlabel", "x") kwargs.setdefault("ylabel", "y") kwargs.setdefault("zlabel", "z") series = [ParametricSurfaceSeries(*arg, **kwargs) for arg in plot_expr] plots = Plot(*series, **kwargs) if show: plots.show() return plots
def plot3d(*args, show=True, **kwargs): """ Plots a 3D surface plot. Usage ===== Single plot ``plot3d(expr, range_x, range_y, **kwargs)`` If the ranges are not specified, then a default range of (-10, 10) is used. Multiple plot with the same range. ``plot3d(expr1, expr2, range_x, range_y, **kwargs)`` If the ranges are not specified, then a default range of (-10, 10) is used. Multiple plots with different ranges. ``plot3d((expr1, range_x, range_y), (expr2, range_x, range_y), ..., **kwargs)`` Ranges have to be specified for every expression. Default range may change in the future if a more advanced default range detection algorithm is implemented. Arguments ========= ``expr`` : Expression representing the function along x. ``range_x``: (x, 0, 5), A 3-tuple denoting the range of the x variable. ``range_y``: (y, 0, 5), A 3-tuple denoting the range of the y variable. Keyword Arguments ================= Arguments for ``SurfaceOver2DRangeSeries`` class: ``n1``: int. The x range is sampled uniformly at ``n1`` of points. ``n2``: int. The y range is sampled uniformly at ``n2`` of points. ``n``: int. The x and y ranges are sampled uniformly at ``n`` of points. It overrides ``n1`` and ``n2``. Arguments for ``Plot`` class: ``title`` : str. Title of the plot. ``size`` : (float, float), optional A tuple in the form (width, height) in inches to specify the size of the overall figure. The default value is set to ``None``, meaning the size will be set by the default backend. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols >>> from sympy.plotting import plot3d >>> x, y = symbols('x y') Single plot .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot3d(x*y, (x, -5, 5), (y, -5, 5)) Plot object containing: [0]: cartesian surface: x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0) Multiple plots with same range .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot3d(x*y, -x*y, (x, -5, 5), (y, -5, 5)) Plot object containing: [0]: cartesian surface: x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0) [1]: cartesian surface: -x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0) Multiple plots with different ranges. .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot3d((x**2 + y**2, (x, -5, 5), (y, -5, 5)), ... (x*y, (x, -3, 3), (y, -3, 3))) Plot object containing: [0]: cartesian surface: x**2 + y**2 for x over (-5.0, 5.0) and y over (-5.0, 5.0) [1]: cartesian surface: x*y for x over (-3.0, 3.0) and y over (-3.0, 3.0) See Also ======== Plot, SurfaceOver2DRangeSeries """ from spb.defaults import THREE_D_B args = _plot_sympify(args) kwargs.setdefault("backend", THREE_D_B) kwargs = _set_discretization_points(kwargs, SurfaceOver2DRangeSeries) series = [] plot_expr = _check_arguments(args, 1, 2) series = [SurfaceOver2DRangeSeries(*arg, **kwargs) for arg in plot_expr] xlabel = series[0].var_x.name ylabel = series[0].var_y.name kwargs.setdefault("xlabel", xlabel) kwargs.setdefault("ylabel", ylabel) kwargs.setdefault("zlabel", "f(%s, %s)" % (xlabel, ylabel)) plots = Plot(*series, **kwargs) if show: plots.show() return plots
def plot3d_parametric_line(*args, show=True, **kwargs): """ Plots a 3D parametric line plot. Usage ===== Single plot: ``plot3d_parametric_line(expr_x, expr_y, expr_z, range, label, **kwargs)`` If the range is not specified, then a default range of (-10, 10) is used. Multiple plots. ``plot3d_parametric_line((expr_x, expr_y, expr_z, range, label), ..., **kwargs)`` Ranges have to be specified for every expression. Default range may change in the future if a more advanced default range detection algorithm is implemented. Arguments ========= ``expr_x`` : Expression representing the function along x. ``expr_y`` : Expression representing the function along y. ``expr_z`` : Expression representing the function along z. ``range``: ``(u, 0, 5)``, A 3-tuple denoting the range of the parameter variable. ``label`` : An optional string denoting the label of the expression to be visualized on the legend. If not provided, the label will be the string representation of the expression. Keyword Arguments ================= Arguments for ``Parametric3DLineSeries`` class. ``n``: The range is uniformly sampled at ``n`` number of points. Arguments for ``Plot`` class. ``title`` : str. Title of the plot. ``size`` : (float, float), optional A tuple in the form (width, height) in inches to specify the size of the overall figure. The default value is set to ``None``, meaning the size will be set by the default backend. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, cos, sin >>> from sympy.plotting import plot3d_parametric_line >>> u = symbols('u') Single plot. .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot3d_parametric_line(cos(u), sin(u), u, (u, -5, 5)) Plot object containing: [0]: 3D parametric cartesian line: (cos(u), sin(u), u) for u over (-5.0, 5.0) Multiple plots with different ranges and custom labels. .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot3d_parametric_line((cos(u), sin(u), u, (u, -5, 5), "a"), ... (sin(u), u**2, u, (u, -3, 3), "b"), legend=True) Plot object containing: [0]: 3D parametric cartesian line: (cos(u), sin(u), u) for u over (-5.0, 5.0) [1]: 3D parametric cartesian line: (sin(u), u**2, u) for u over (-3.0, 3.0) See Also ======== Plot, Parametric3DLineSeries """ from spb.defaults import THREE_D_B args = _plot_sympify(args) kwargs.setdefault("backend", THREE_D_B) kwargs = _set_discretization_points(kwargs, Parametric3DLineSeries) series = [] plot_expr = _check_arguments(args, 3, 1) series = [Parametric3DLineSeries(*arg, **kwargs) for arg in plot_expr] kwargs.setdefault("xlabel", "x") kwargs.setdefault("ylabel", "y") kwargs.setdefault("zlabel", "z") plots = Plot(*series, **kwargs) if show: plots.show() return plots
def plot_parametric(*args, show=True, **kwargs): """ Plots a 2D parametric curve. Parameters ========== args Common specifications are: - Plotting a single parametric curve with a range ``plot_parametric(expr_x, expr_y, range)`` - Plotting multiple parametric curves with the same range ``plot_parametric((expr_x, expr_y), ..., range)`` - Plotting multiple parametric curves with different ranges ``plot_parametric((expr_x, expr_y, range), ...)`` - Plotting multiple parametric curves with different ranges and custom labels ``plot_parametric((expr_x, expr_y, range, label), ...)`` ``expr_x`` is the expression representing $x$ component of the parametric function. ``expr_y`` is the expression representing $y$ component of the parametric function. ``range`` is a 3-tuple denoting the parameter symbol, start and stop. For example, ``(u, 0, 5)``. If the range is not specified, then a default range of (-10, 10) is used. However, if the arguments are specified as ``(expr_x, expr_y, range), ...``, you must specify the ranges for each expressions manually. Default range may change in the future if a more advanced algorithm is implemented. ``label`` : An optional string denoting the label of the expression to be visualized on the legend. If not provided, the label will be the string representation of the expression. adaptive : bool, optional Specifies whether to use the adaptive sampling or not. The default value is set to ``True``. Set adaptive to ``False`` and specify ``n`` if uniform sampling is required. depth : int, optional The recursion depth of the adaptive algorithm. A depth of value $n$ samples a maximum of $2^n$ points. n : int, optional Used when the ``adaptive`` flag is set to ``False``. Specifies the number of the points used for the uniform sampling. xlabel : str, optional Label for the x-axis. ylabel : str, optional Label for the y-axis. xscale : 'linear' or 'log', optional Sets the scaling of the x-axis. yscale : 'linear' or 'log', optional Sets the scaling of the y-axis. axis_center : (float, float), optional Tuple of two floats denoting the coordinates of the center or {'center', 'auto'} xlim : (float, float), optional Denotes the x-axis limits, ``(min, max)```. ylim : (float, float), optional Denotes the y-axis limits, ``(min, max)```. size : (float, float), optional A tuple in the form (width, height) in inches to specify the size of the overall figure. The default value is set to ``None``, meaning the size will be set by the default backend. Examples ======== .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import symbols, cos, sin >>> from sympy.plotting import plot_parametric >>> u = symbols('u') A parametric plot with a single expression: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_parametric((cos(u), sin(u)), (u, -5, 5)) Plot object containing: [0]: parametric cartesian line: (cos(u), sin(u)) for u over (-5.0, 5.0) A parametric plot with multiple expressions with the same range: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_parametric((cos(u), sin(u)), (u, cos(u)), (u, -10, 10)) Plot object containing: [0]: parametric cartesian line: (cos(u), sin(u)) for u over (-10.0, 10.0) [1]: parametric cartesian line: (u, cos(u)) for u over (-10.0, 10.0) A parametric plot with multiple expressions with different ranges and custom labels for each curve: .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot_parametric((cos(u), sin(u), (u, -5, 5), "a"), ... (cos(u), u, "b")) Plot object containing: [0]: parametric cartesian line: (cos(u), sin(u)) for u over (-5.0, 5.0) [1]: parametric cartesian line: (cos(u), u) for u over (-10.0, 10.0) Notes ===== The plotting uses an adaptive algorithm which samples recursively to accurately plot the curve. The adaptive algorithm uses a random point near the midpoint of two points that has to be further sampled. Hence, repeating the same plot command can give slightly different results because of the random sampling. See Also ======== Plot, Parametric2DLineSeries """ from spb.defaults import TWO_D_B args = _plot_sympify(args) series = [] kwargs.setdefault("backend", TWO_D_B) kwargs = _set_discretization_points(kwargs, Parametric2DLineSeries) plot_expr = _check_arguments(args, 2, 1) series = [Parametric2DLineSeries(*arg, **kwargs) for arg in plot_expr] plots = Plot(*series, **kwargs) if show: plots.show() return plots
def plot(*args, show=True, **kwargs): """Plots a function of a single variable as a curve. Parameters ========== args : The first argument is the expression representing the function of single variable to be plotted. The next argument is a 3-tuple denoting the range of the free variable. e.g. ``(x, 0, 5)`` The last optional argument is a string denoting the label of the expression to be visualized on the legend. If not provided, the label will be the string representation of the expression. Typical usage examples are in the followings: - Plotting a single expression with a single range. ``plot(expr, range, **kwargs)`` - Plotting a single expression with the default range (-10, 10). ``plot(expr, **kwargs)`` - Plotting multiple expressions with a single range. ``plot(expr1, expr2, ..., range, **kwargs)`` - Plotting multiple expressions with multiple ranges. ``plot((expr1, range1), (expr2, range2), ..., **kwargs)`` - Plotting multiple expressions with multiple ranges and custom labels. ``plot((expr1, range1, label1), (expr2, range2, label2), ..., legend=True, **kwargs)`` It is best practice to specify range explicitly because default range may change in the future if a more advanced default range detection algorithm is implemented. adaptive : bool, optional The default value is set to ``True``. Set adaptive to ``False`` and specify ``n`` if uniform sampling is required. The plotting uses an adaptive algorithm which samples recursively to accurately plot. The adaptive algorithm uses a random point near the midpoint of two points that has to be further sampled. Hence the same plots can appear slightly different. axis_center : (float, float), optional Tuple of two floats denoting the coordinates of the center or {'center', 'auto'}. Only available with MatplotlibBackend. depth : int, optional Recursion depth of the adaptive algorithm. A depth of value ``n`` samples a maximum of `2^{n}` points. If the ``adaptive`` flag is set to ``False``, this will be ignored. detect_poles : boolean Chose whether to detect and correctly plot poles. Defaulto to False. This improve detection, increase the number of discretization points and/or change the value of `eps`. eps : float An arbitrary small value used by the `detect_poles` algorithm. Default value to 0.1. Before changing this value, it is better to increase the number of discretization points. n : int, optional Used when the ``adaptive`` is set to ``False``. The function is uniformly sampled at ``n`` number of points. If the ``adaptive`` flag is set to ``True``, this will be ignored. only_integers : boolean, optional Default to False. If True, discretize the domain with integer numbers. This can be useful to plot sums. polar : boolean Default to False. If True, generate a polar plot of a curve with radius `expr` as a function of the range 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) in inches to specify the size of the overall figure. The default value is set to ``None``, meaning the size will be set by the default backend. steps : boolean, optional Default to False. If True, connects consecutive points with steps rather than straight segments. title : str, optional Title of the plot. It is set to the latex representation of the expression, if the plot has only one expression. xlabel : str, optional Label for the x-axis. ylabel : str, optional Label for the y-axis. xscale : 'linear' or 'log', optional Sets the scaling of the x-axis. yscale : 'linear' or 'log', optional Sets the scaling of the y-axis. xlim : (float, float), optional Denotes the x-axis limits, ``(min, max)```. ylim : (float, float), optional Denotes the y-axis limits, ``(min, max)```. Examples ======== .. plot:: :context: close-figs :format: doctest :include-source: True >>> from sympy import symbols >>> from sympy.plotting import plot >>> x = symbols('x') 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 plots with single range. .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot(x, x**2, x**3, (x, -5, 5)) Plot object containing: [0]: cartesian line: x for x over (-5.0, 5.0) [1]: cartesian line: x**2 for x over (-5.0, 5.0) [2]: cartesian line: x**3 for x over (-5.0, 5.0) Multiple plots with different ranges and custom labels. .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot((x**2, (x, -6, 6), "$f_{1}$"), (x, (x, -5, 5), "f2"), legend=True) Plot object containing: [0]: cartesian line: x**2 for x over (-6.0, 6.0) [1]: cartesian line: x for x over (-5.0, 5.0) No adaptive sampling. .. plot:: :context: close-figs :format: doctest :include-source: True >>> plot(x**2, adaptive=False, n=400) Plot object containing: [0]: cartesian line: x**2 for x over (-10.0, 10.0) Polar plot: .. code-block:: python plot(1 + sin(10 * x) / 10, (x, 0, 2 * pi), polar=True, aspect="equal") See Also ======== Plot, LineOver1DRangeSeries """ from spb.defaults import TWO_D_B args = _plot_sympify(args) free = set() for a in args: if isinstance(a, Expr): free |= a.free_symbols if len(free) > 1: raise ValueError("The same variable should be used in all " "univariate expressions being plotted.") x = free.pop() if free else Symbol("x") kwargs.setdefault("backend", TWO_D_B) kwargs.setdefault("xlabel", x.name) kwargs.setdefault("ylabel", "f(%s)" % x.name) kwargs = _set_discretization_points(kwargs, LineOver1DRangeSeries) series = [] plot_expr = _check_arguments(args, 1, 1) series = _build_line_series(*plot_expr, **kwargs) plots = Plot(*series, **kwargs) if show: plots.show() return plots
def plotgrid(*args, **kwargs): """Combine multiple plots into a grid-like layout. This function has two modes of operation, depending on the input arguments. Make sure to read the examples to fully understand them. Parameters ========== args : sequence A sequence of aldready created plots. This, in combination with `nr` and `nc` represents the first mode of operation, where a basic grid with (nc * nr) subplots will be created. Keyword Arguments ================= nr, nc : int Number of rows and columns. By default, `nc = 1` and `nr = -1`: this will create as many rows as necessary, and a single column. If we set `nc = 1` and `nc = -1`, it will create as many column as necessary, and a single row. gs : dict A dictionary mapping Matplotlib's GridSpect objects to the plots. The keys represent the cells of the layout. Each cell will host the associated plot. This represents the second mode of operation, as it allows to create more complicated layouts. NOTE: all plots must be instances of MatplotlibBackend! Returns ======= Depending on the types of plots, this function returns either: * None: if all plots are instances of MatplotlibBackend. * an instance of holoviz's panel GridSpec, which will be rendered on Jupyter Notebook when mixed types of plots are received or when all the plots are not instances of MatplotlibBackend. Read the following documentation page to get more information: https://panel.holoviz.org/reference/layouts/GridSpec.html Examples ======== First mode of operation with MatplotlibBackends: .. code-block:: python from sympy import * from spb.backends.matplotlib import MB from spb import * x, y, z = symbols("x, y, z") p1 = plot(sin(x), backend=MB, show=False) p2 = plot(tan(x), backend=MB, detect_poles=True, show=False) p3 = plot(exp(-x), backend=MB, show=False) plotgrid(p1, p2, p3) First mode of operation with different backends. Try this on a Jupyter Notebook. Note that Matplotlib as been integrated as a picture, thus it loses its interactivity. .. code-block:: python p1 = plot(sin(x), backend=MB, show=False) p2 = plot(tan(x), backend=MB, detect_poles=True, show=False) p3 = plot(exp(-x), backend=MB, show=False) plotgrid(p1, p2, p3, nr=1, nc=3) Second mode of operation: using Matplotlib GridSpec and all plots are instances of MatplotlibBackend: .. code-block:: python from matplotlib.gridspec import GridSpec p1 = plot(sin(x), cos(x), show=False, backend=MB) p2 = plot_contour(cos(x**2 + y**2), (x, -3, 3), (y, -3, 3), show=False, backend=BB) p3 = complex_plot(sqrt(x), show=False, backend=PB) p4 = vector_plot(Matrix([-y, x]), (x, -5, 5), (y, -5, 5), show=False, backend=MB) p5 = complex_plot(gamma(z), (z, -3-3*I, 3+3*I), show=False, backend=MB) gs = GridSpec(3, 3) mapping = { gs[0, :1]: p1, gs[1, :1]: p2, gs[2:, :1]: p3, gs[2:, 1:]: p4, gs[0:2, 1:]: p5, } plotgrid(gs=mapping) """ gs = kwargs.get("gs", None) if gs is None: # default layout: 1 columns, as many rows as needed nr = kwargs.get("nr", -1) nc = kwargs.get("nc", 1) nr, nc = _nrows_ncols(nr, nc, len(args)) if all(isinstance(a, MB) for a in args): fig, ax = plt.subplots(nr, nc) ax = ax.flatten() c = 0 for i in range(nr): for j in range(nc): if c < len(args): kw = args[c]._kwargs kw["backend"] = MB kw["fig"] = fig kw["ax"] = ax[c] p = Plot(*args[c].series, **kw) p.process_series() c += 1 else: fig = pn.GridSpec(sizing_mode="stretch_width") c = 0 for i in range(nr): for j in range(nc): if c < len(args): p = args[c] c += 1 if isinstance(p, MB) and (not hasattr(p, "ax")): # if the MatplotlibBackend was created without # showing it p.process_series() fig[i, j] = p.fig if not isinstance(p, MB) else p.fig[0] else: fig = plt.figure() axes = dict() for gs, p in gs.items(): axes[fig.add_subplot(gs)] = p for ax, p in axes.items(): kw = p._kwargs kw["backend"] = MB kw["fig"] = fig kw["ax"] = ax newplot = Plot(*p.series, **kw) newplot.process_series() if isinstance(fig, plt.Figure): fig.tight_layout() fig.show() else: return fig
def test_K3DBackend(): from k3d.objects import Mesh, Line, Vectors from matplotlib.colors import ListedColormap assert hasattr(KB, "colorloop") assert isinstance(KB.colorloop, (list, tuple, ListedColormap)) assert hasattr(KB, "colormaps") assert isinstance(KB.colormaps, (list, tuple)) series = [UnsupportedSeries()] raises(NotImplementedError, lambda: Plot(*series, backend=KBchild2)) ### Setting custom color loop assert len(KBchild1.colorloop.colors) != len(KBchild2.colorloop) _p1 = p13(KBchild1) _p2 = p13(KBchild2) assert len(_p1.series) == len(_p2.series) f1 = _p1.fig f2 = _p2.fig assert all([isinstance(t, Mesh) for t in f1.objects]) assert all([isinstance(t, Mesh) for t in f2.objects]) # there are 6 unique colors in _p1 and 3 unique colors in _p2 assert len(set([o.color for o in f1.objects])) == 6 assert len(set([o.color for o in f2.objects])) == 3 # ### test for line_kw, surface_kw, quiver_kw, stream_kw: they should override # ### defualt settings. # K3D doesn't support 2D plots raises(NotImplementedError, lambda: p2(KBchild1, line_kw=dict(line_color="red"))) raises(NotImplementedError, lambda: p3(KBchild1, line_kw=dict(line_color="red"))) p = p4(KBchild1, line_kw=dict(color=16711680)) assert len(p.series) == 1 f = p.fig assert len(f.objects) == 1 assert isinstance(f.objects[0], Line) assert f.objects[0].color == 16711680 assert f.objects[0].name is None # use_cm=False will force to apply a default solid color to the mesh. # Here, I override that solid color with a custom color. p = p5(KBchild1, surface_kw=dict(color=16711680)) assert len(p.series) == 1 f = p.fig assert len(f.objects) == 1 assert isinstance(f.objects[0], Mesh) assert f.objects[0].color == 16711680 assert f.objects[0].name is None # K3D doesn't support 2D plots raises(NotImplementedError, lambda: p6(KBchild1, contour_kw=dict())) raises( NotImplementedError, lambda: p7(KBchild1, quiver_kw=dict(), contour_kw=dict()) ) raises( NotImplementedError, lambda: p8(KBchild1, stream_kw=dict(), contour_kw=dict()) ) p = p9(KBchild1, quiver_kw=dict(scale=0.5, color=16711680)) assert len(p.series) == 1 f = p.fig assert len(f.objects) == 1 assert isinstance(f.objects[0], Vectors) assert all([c == 16711680 for c in f.objects[0].colors]) p = p10(KBchild1, stream_kw=dict(color=16711680)) assert len(p.series) == 1 f = p.fig assert len(f.objects) == 1 assert isinstance(f.objects[0], Line) assert f.objects[0].color == 16711680 # K3D doesn't support 2D plots raises(NotImplementedError, lambda: p11(KBchild1, contour_kw=dict())) raises(NotImplementedError, lambda: p12(KBchild1, contour_kw=dict())) raises(NotImplementedError, lambda: p14(KBchild1, line_kw=dict())) raises(NotImplementedError, lambda: p15(KBchild1, line_kw=dict())) raises(NotImplementedError, lambda: p16(KBchild1, contour_kw=dict())) p = p17(KBchild1, surface_kw=dict()) assert len(p.series) == 1 f = p.fig assert len(f.objects) == 1 assert isinstance(f.objects[0], Mesh) assert f.objects[0].name is None
def complex_plot(*args, show=True, **kwargs): """Plot complex numbers or complex functions. By default, the aspect ratio of the plot is set to ``aspect="equal"``. Depending on the provided expression, this function will produce different types of plots: * list of complex numbers: creates a scatter plot. * function of 1 variable over a real range: 1. line plot separating the real and imaginary parts. 2. line plot of the modulus of the complex function colored by its argument, if `absarg=True`. 3. line plot of the modulus and the argument, if `abs=True, arg=True`. * function of 2 variables over 2 real ranges: 1. By default, a surface plot of the real part is created. 2. By toggling `real=True, imag=True, abs=True` we can create surface plots of the real, imaginary part or the absolute value. * complex function over a complex range: 1. domain coloring plot. 2. 3D plot of the modulus colored by the argument, if `threed=True`. 3. 3D plot of the real and imaginary part. Explore the example below to better understand how to use it. Arguments ========= expr : Expr Represent the complex number or complex function to be plotted. range : 3-element tuple Denotes the range of the variables. For example: * (z, -5, 5): plot a line from complex point (-5 + 0*I) to (5 + 0*I) * (z, -5 + 2*I, 5 + 2*I): plot a line from complex point (-5 + 2*I) to (5 + 2 * I). Note the same imaginary part for the start/end point. * (z, -5 - 3*I, 5 + 3*I): domain coloring plot of the complex function over the specified domain. label : str The name of the complex function to be eventually shown on the legend. If none is provided, the string representation of the function will be used. To specify multiple complex functions, wrap them into a tuple. Refer to the examples to learn more. Keyword Arguments ================= absarg : boolean If True, plot the modulus of the complex function colored by its argument. If False, separately plot the real and imaginary parts. Default to False. abs : boolean If True, and if the provided range is a real segment, plot the modulus of the complex function. Default to False. adaptive : boolean Attempt to create line plots by using an adaptive algorithm. Default to True. If `absarg=True`, the function will automatically switch to `adaptive=False`, using a uniformly-spaced grid. arg : boolean If True, and if the provided range is a real segment, plot the argument of the complex function. Default to False. depth : int Controls the smootheness of the overall evaluation. The higher the number, the smoother the function, the more memory will be used by the recursive procedure. Default value is 9. detect_poles : boolean Chose whether to detect and correctly plot poles. Defaulto to False. This improve detection, increase the number of discretization points and/or change the value of `eps`. eps : float An arbitrary small value used by the `detect_poles` algorithm. Default value to 0.1. Before changing this value, it is better to increase the number of discretization points. n1, n2 : int Number of discretization points in the real/imaginary-directions, respectively. For domain coloring plots (2D and 3D), default to 300. For line plots default to 1000. n : int Set the same number of discretization points in all directions. For domain coloring plots (2D and 3D), default to 300. For line plots default to 1000. real : boolean If True, and if the provided range is a real segment, plot the real part of the complex function. If a complex range is given and `threed=True`, plot a 3D representation of the real part. Default to False. imag : boolean If True, and if the provided range is a real segment, plot the imaginary part of the complex function. If a complex range is given and `threed=True`, plot a 3D representation of the imaginary part. Default to False. show : boolean Default to True, in which case the plot will be shown on the screen. threed : boolean Default to False. When True, it will plot a 3D representation of the absolute value of the complex function colored by its argument. use_cm : boolean If `absarg=True` and `use_cm=True` then plot the modulus of the complex function colored by its argument. If `use_cm=False`, plot the modulus of the complex function with a solid color. Default to True. Domain Coloring Arguments ========================= coloring : str Default to "a". Chose between different coloring options: "a": standard domain coloring using HSV. "b": enhanced domain coloring using HSV, showing iso-modulus and is-phase lines. "c": enhanced domain coloring using HSV, showing iso-modulus lines. "d": enhanced domain coloring using HSV, showing iso-phase lines. "e": HSV color grading. Read the following article to understand it: https://www.codeproject.com/Articles/80641/Visualizing-Complex-Functions "f": domain coloring implemented by cplot: https://github.com/nschloe/cplot Use the following keywords to further customize the appearance: `abs_scaling`: str Default to "h-1". It can be used to adjust the use of colors. h with a value less than 1.0 adds more color which can help isolating the roots and poles (which are still black and white, respectively). "h-0.0" ignores the magnitude of f(z) completely. "arctan" is another possible scaling. `colorspace` : str Default to "cam16". Can be set to "hsl" to get the common fully saturated, vibrant colors. `abs` and/or `args` : boolean Set them to True to show contour lines for absolute value and argument. `levels` : (n_abs, n_arg) Number of contour levels for the absolute value and the argument. WARNING: if `abs=True` and/or `arg=True`, only MatplotlibBackend will be able to render the plot! Moreover, `iplot` won't be able to update these contour lines. "g": alternating black and white stripes corresponding to modulus. "h": alternating black and white stripes corresponding to phase. "i": alternating black and white stripes corresponding to real part. "j": alternating black and white stripes corresponding to imaginary part. "k": cartesian chessboard on the complex points space. The result will hide zeros. "l": polar Chessboard on the complex points space. The result will show conformality. alpha : float This parameter works when `coloring="f"`. Default to 1. Can be `0 <= alpha <= 1`. It adjust the use of colors. A value less than 1 adds more color which can help isolating the roots and poles (which are still black and white, respectively). alpha=0 ignores the magnitude of f(z) completely. colorspace : str This parameter works when `coloring="f"`. Default to `"cam16"`. Other options are `"cielab", "oklab", "hsl"`. It can be set to `"hsl"` to get the common fully saturated, vibrant colors. This is usually a bad idea since it creates artifacts which are not related with the underlying data. phaseres : int This parameter works when `coloring` is different from `"f"`. Default value to 20. It controls the number of iso-phase or iso-modulus lines. Examples ======== Plot individual complex points: .. code-block:: python complex_plot(3 + 2 * I, 4 * I, 2, aspect="equal", legend=True) Plot two lists of complex points: .. code-block:: python z = symbols("z") expr1 = z * exp(2 * pi * I * z) expr2 = 2 * expr1 l1 = [expr1.subs(z, t / 20) for t in range(20)] l2 = [expr2.subs(z, t / 20) for t in range(20)] complex_plot((l1, "f1"), (l2, "f2"), aspect="equal", legend=True) Plot the real and imaginary part of a function: .. code-block:: python z = symbols("z") complex_plot(sqrt(z), (z, -3, 3), legend=True) .. code-block:: python z = symbols("z") complex_plot((cos(z) + sin(I * z), "f"), (z, -2, 2), legend=True) Plot the modulus of a complex function colored by its magnitude: .. code-block:: python z = symbols("z") complex_plot((cos(z) + sin(I * z), "f"), (z, -2, 2), legend=True, absarg=True) Plot the modulus and the argument of a complex function: .. code-block:: python z = symbols("z") complex_plot((cos(z) + sin(I * z), "f"), (z, -2, 2), legend=True, abs=True, arg=True, real=False, imag=False) Plot the real and imaginary part of a function of two variables over two real ranges: .. code-block:: python x, y = symbols("x, y") complex_plot(sqrt(x*y), (x, -5, 5), (y, -5, 5), real=True, imag=True) Domain coloring plot. Note that it might be necessary to increase the number of discretization points in order to get a smoother plot: .. code-block:: python z = symbols("z") complex_plot(gamma(z), (z, -3 - 3*I, 3 + 3*I), coloring="b", n=500) 3D plot of the absolute value of a complex function colored by its argument: .. code-block:: python z = symbols("z") complex_plot(gamma(z), (z, -3 - 3*I, 3 + 3*I), threed=True, legend=True, zlim=(-1, 6)) 3D plot of the real part a complex function: .. code-block:: python z = symbols("z") complex_plot(gamma(z), (z, -3 - 3*I, 3 + 3*I), threed=True, real=True) """ args = _plot_sympify(args) kwargs = _set_discretization_points(kwargs, ComplexSeries) series = _build_series(*args, **kwargs) if "backend" not in kwargs: kwargs["backend"] = TWO_D_B if any(s.is_3Dsurface for s in series): kwargs["backend"] = THREE_D_B if all( isinstance(s, (SurfaceOver2DRangeSeries, InteractiveSeries)) for s in series): # function of 2 variables if kwargs.get("xlabel", None) is None: kwargs["xlabel"] = str(series[0].var_x) if kwargs.get("ylabel", None) is None: kwargs["ylabel"] = str(series[0].var_y) # do not set anything for zlabel since it could be f(x,y) or # abs(f(x, y)) or something else elif all(not s.is_parametric for s in series): # when plotting real/imaginary or domain coloring/3D plots, the # horizontal axis is the real, the vertical axis is the imaginary if kwargs.get("xlabel", None) is None: kwargs["xlabel"] = "Re" if kwargs.get("ylabel", None) is None: kwargs["ylabel"] = "Im" if kwargs.get("zlabel", None) is None: kwargs["zlabel"] = "Abs" else: if kwargs.get("xlabel", None) is None: kwargs["xlabel"] = "Real" if kwargs.get("ylabel", None) is None: kwargs["ylabel"] = "Abs" if (kwargs.get("aspect", None) is None) and any( s.is_complex and s.is_domain_coloring for s in series): kwargs["aspect"] = "equal" p = Plot(*series, **kwargs) if show: p.show() return p
def test_BokehBackend(): from bokeh.models.glyphs import Line, MultiLine, Image, Segment, ImageRGBA from bokeh.plotting.figure import Figure assert hasattr(BB, "colorloop") assert isinstance(BB.colorloop, (list, tuple)) assert hasattr(BB, "colormaps") assert isinstance(BB.colormaps, (list, tuple)) series = [UnsupportedSeries()] raises(NotImplementedError, lambda: Plot(*series, backend=PB)) ### Setting custom color loop assert len(BBchild.colorloop) != len(BB.colorloop) _p1 = p1(BB) _p2 = p1(BBchild) assert len(_p1.series) == len(_p2.series) f1 = _p1.fig f2 = _p2.fig assert all([isinstance(t.glyph, Line) for t in f1.renderers]) assert all([isinstance(t.glyph, Line) for t in f2.renderers]) # there are 6 unique colors in _p1 and 3 unique colors in _p2 assert len(set([r.glyph.line_color for r in f1.renderers])) == 6 assert len(set([r.glyph.line_color for r in f2.renderers])) == 3 ### test for line_kw, surface_kw, quiver_kw, stream_kw: they should override ### defualt settings. p = p2(BB, line_kw=dict(line_color="red")) assert len(p.series) == 2 f = p.fig assert isinstance(f, Figure) assert len(f.renderers) == 2 assert isinstance(f.renderers[0].glyph, Line) assert f.legend[0].items[0].label["value"] == "sin(x)" assert f.renderers[0].glyph.line_color == "red" assert isinstance(f.renderers[1].glyph, Line) assert f.legend[0].items[1].label["value"] == "cos(x)" assert f.renderers[1].glyph.line_color == "red" assert f.legend[0].visible == True p = p3(BB, line_kw=dict(line_color="red")) assert len(p.series) == 1 f = p.fig assert len(f.renderers) == 1 assert isinstance(f.renderers[0].glyph, MultiLine) assert f.renderers[0].glyph.line_color == "red" # Bokeh doesn't support 3D plots raises(NotImplementedError, lambda: p4(BB, line_kw=dict(line_color="red"))) raises( NotImplementedError, lambda: p5(BB, surface_kw=dict(colorscale=[[0, "cyan"], [1, "cyan"]])), ) # Bokeh doesn't use contour_kw dictionary. Nothing to customize yet. p = p6(BB, contour_kw=dict()) assert len(p.series) == 1 f = p.fig assert len(f.renderers) == 1 assert isinstance(f.renderers[0].glyph, Image) assert f.right[0].title == str(cos(x ** 2 + y ** 2)) p = p7(BB, contour_kw=dict(), quiver_kw=dict(line_color="red")) assert len(p.series) == 2 f = p.fig assert len(f.renderers) == 2 assert isinstance(f.renderers[0].glyph, Image) assert isinstance(f.renderers[1].glyph, Segment) assert f.right[0].title == "Magnitude" assert f.renderers[1].glyph.line_color == "red" p = p8(BB, stream_kw=dict(line_color="red"), contour_kw=dict()) assert len(p.series) == 2 f = p.fig assert len(f.renderers) == 2 assert isinstance(f.renderers[0].glyph, Image) assert isinstance(f.renderers[1].glyph, MultiLine) assert f.right[0].title == "x + y" assert f.renderers[1].glyph.line_color == "red" # Bokeh doesn't support 3D plots raises(NotImplementedError, lambda: p9(BB, quiver_kw=dict(sizeref=5))) raises( NotImplementedError, lambda: p10(BB, stream_kw=dict(colorscale=[[0, "red"], [1, "red"]])), ) p = p11(BB, contour_kw=dict()) assert len(p.series) == 1 f = p.fig assert len(f.renderers) == 1 assert isinstance(f.renderers[0].glyph, Image) p = p12(BB, contour_kw=dict()) assert len(p.series) == 1 f = p.fig assert len(f.renderers) == 1 assert isinstance(f.renderers[0].glyph, Image) p = p14(BB, line_kw=dict(line_color="red")) assert len(p.series) == 2 f = p.fig assert isinstance(f, Figure) assert len(f.renderers) == 2 assert isinstance(f.renderers[0].glyph, Line) assert f.legend[0].items[0].label["value"] == "re(sqrt(x))" assert f.renderers[0].glyph.line_color == "red" assert isinstance(f.renderers[1].glyph, Line) assert f.legend[0].items[1].label["value"] == "im(sqrt(x))" assert f.renderers[1].glyph.line_color == "red" assert f.legend[0].visible == True p = p15(BB, line_kw=dict(line_color="red")) assert len(p.series) == 1 f = p.fig assert len(f.renderers) == 1 assert isinstance(f.renderers[0].glyph, MultiLine) assert f.renderers[0].glyph.line_color == "red" p = p16(BB, contour_kw=dict()) assert len(p.series) == 1 f = p.fig assert len(f.renderers) == 1 assert isinstance(f.renderers[0].glyph, ImageRGBA)
def test_PlotlyBackend(): assert hasattr(PB, "colorloop") assert isinstance(PB.colorloop, (list, tuple)) assert hasattr(PB, "colormaps") assert isinstance(PB.colormaps, (list, tuple)) assert hasattr(PB, "wireframe_colors") assert isinstance(PB.wireframe_colors, (list, tuple)) assert hasattr(PB, "quivers_colors") assert isinstance(PB.quivers_colors, (list, tuple)) series = [UnsupportedSeries()] raises(NotImplementedError, lambda: Plot(*series, backend=PB)) ### Setting custom color loop assert len(PBchild.colorloop) != len(PB.colorloop) _p1 = p1(PB) _p2 = p1(PBchild) assert len(_p1.series) == len(_p2.series) f1 = _p1.fig f2 = _p2.fig assert all([isinstance(t, go.Scatter) for t in f1.data]) assert all([isinstance(t, go.Scatter) for t in f2.data]) # there are 6 unique colors in _p1 and 3 unique colors in _p2 assert len(set([d["line"]["color"] for d in f1.data])) == 6 assert len(set([d["line"]["color"] for d in f2.data])) == 3 ### test for line_kw, surface_kw, quiver_kw, stream_kw: they should override ### defualt settings. p = p2(PB, line_kw=dict(line_color="red")) assert len(p.series) == 2 f = p.fig assert isinstance(f, go.Figure) assert len(f.data) == 2 assert isinstance(f.data[0], go.Scatter) assert f.data[0]["name"] == "sin(x)" assert f.data[0]["line"]["color"] == "red" assert isinstance(f.data[1], go.Scatter) assert f.data[1]["name"] == "cos(x)" assert f.data[1]["line"]["color"] == "red" assert f.layout["showlegend"] == True p = p3(PB, line_kw=dict(line_color="red")) assert len(p.series) == 1 f = p.fig assert len(f.data) == 1 assert isinstance(f.data[0], go.Scatter) assert f.data[0]["name"] == "(cos(x), sin(x))" assert f.data[0]["line"]["color"] == "red" p = p4(PB, line_kw=dict(line_color="red")) assert len(p.series) == 1 f = p.fig assert len(f.data) == 1 assert isinstance(f.data[0], go.Scatter3d) assert f.data[0]["line"]["color"] == "red" assert f.data[0]["name"] == "(cos(x), sin(x), x)" assert f.data[0]["line"]["colorbar"]["title"]["text"] == "(cos(x), sin(x), x)" # use_cm=False will force to apply a default solid color to the mesh. # Here, I override that solid color with a custom color. p = p5(PB, surface_kw=dict(colorscale=[[0, "cyan"], [1, "cyan"]])) assert len(p.series) == 1 f = p.fig assert len(f.data) == 1 assert isinstance(f.data[0], go.Surface) assert f.data[0]["name"] == "cos(x**2 + y**2)" assert f.data[0]["showscale"] == False assert f.data[0]["colorscale"] == ((0, "cyan"), (1, "cyan")) assert f.layout["showlegend"] == False p = p6(PB, contour_kw=dict(contours=dict(coloring="lines"))) assert len(p.series) == 1 f = p.fig assert len(f.data) == 1 assert isinstance(f.data[0], go.Contour) assert f.data[0]["contours"]["coloring"] == "lines" assert f.data[0]["colorbar"]["title"]["text"] == str(cos(x ** 2 + y ** 2)) p = p7( PB, quiver_kw=dict(line_color="red"), contour_kw=dict(contours=dict(coloring="lines")), ) assert len(p.series) == 2 f = p.fig assert len(f.data) == 2 assert isinstance(f.data[0], go.Contour) assert isinstance(f.data[1], go.Scatter) assert f.data[0]["contours"]["coloring"] == "lines" assert f.data[0]["colorbar"]["title"]["text"] == "Magnitude" assert f.data[1]["line"]["color"] == "red" p = p8( PB, stream_kw=dict(line_color="red"), contour_kw=dict(contours=dict(coloring="lines")), ) assert len(p.series) == 2 f = p.fig assert len(f.data) == 2 assert isinstance(f.data[0], go.Contour) assert isinstance(f.data[1], go.Scatter) assert f.data[0]["contours"]["coloring"] == "lines" assert f.data[0]["colorbar"]["title"]["text"] == "x + y" assert f.data[1]["line"]["color"] == "red" p = p9(PB, quiver_kw=dict(sizeref=5)) assert len(p.series) == 1 f = p.fig assert len(f.data) == 1 assert isinstance(f.data[0], go.Cone) assert f.data[0]["sizeref"] == 5 assert f.data[0]["colorbar"]["title"]["text"] == str(Matrix([z, y, x])) p = p10(PB, stream_kw=dict(colorscale=[[0, "red"], [1, "red"]])) assert len(p.series) == 1 f = p.fig assert len(f.data) == 1 assert isinstance(f.data[0], go.Streamtube) assert f.data[0]["colorscale"] == ((0, "red"), (1, "red")) assert f.data[0]["colorbar"]["title"]["text"] == str(Matrix([z, y, x])) p = p11(PB, contour_kw=dict(colorscale=[[0, "rgba(0,0,0,0)"], [1, "red"]])) assert len(p.series) == 1 f = p.fig assert len(f.data) == 1 assert isinstance(f.data[0], go.Contour) assert f.data[0]["colorscale"] == ((0, "rgba(0,0,0,0)"), (1, "red")) p = p12(PB, contour_kw=dict(fillcolor="red")) assert len(p.series) == 1 f = p.fig assert len(f.data) == 1 assert isinstance(f.data[0], go.Contour) assert f.data[0]["fillcolor"] == "red" p = p14(PB, line_kw=dict(line_color="red")) assert len(p.series) == 2 f = p.fig assert isinstance(f, go.Figure) assert len(f.data) == 2 assert isinstance(f.data[0], go.Scatter) assert f.data[0]["name"] == "re(sqrt(x))" assert f.data[0]["line"]["color"] == "red" assert isinstance(f.data[1], go.Scatter) assert f.data[1]["name"] == "im(sqrt(x))" assert f.data[1]["line"]["color"] == "red" assert f.layout["showlegend"] == True p = p15(PB, line_kw=dict(line_color="red")) assert len(p.series) == 1 f = p.fig assert len(f.data) == 1 assert isinstance(f.data[0], go.Scatter) assert f.data[0]["name"] == "Abs(sqrt(x))" assert f.data[0]["line"]["color"] == "red" p = p16(PB, contour_kw=dict()) assert len(p.series) == 1 f = p.fig assert len(f.data) == 2 assert isinstance(f.data[0], go.Image) assert f.data[0]["name"] == "sqrt(x)" assert isinstance(f.data[1], go.Scatter) assert f.data[1]["marker"]["colorbar"]["title"]["text"] == "Argument" p = p17(PB, surface_kw=dict()) assert len(p.series) == 1 f = p.fig assert len(f.data) == 1 assert isinstance(f.data[0], go.Surface) assert f.data[0]["name"] == "sqrt(x)" assert f.data[0]["showscale"] == False assert f.layout["showlegend"] == False
def test_MatplotlibBackend(): assert hasattr(MB, "colorloop") assert isinstance(MB.colorloop, (ListedColormap, list, tuple)) assert hasattr(MB, "colormaps") assert isinstance(MB.colormaps, (list, tuple)) series = [UnsupportedSeries()] raises(NotImplementedError, lambda: Plot(*series, backend=MB).process_series()) ### test for line_kw, surface_kw, quiver_kw, stream_kw: they should override ### defualt settings. p = p2(MB, line_kw=dict(color="red")) assert len(p.series) == 2 # MatplotlibBackend only add data to the plot when the following method # is internally called. But show=False, hence it is not called. p.process_series() f, ax = p.fig assert isinstance(f, plt.Figure) assert isinstance(ax, Axes) assert len(ax.get_lines()) == 2 assert ax.get_lines()[0].get_label() == "sin(x)" assert ax.get_lines()[0].get_color() == "red" assert ax.get_lines()[1].get_label() == "cos(x)" assert ax.get_lines()[1].get_color() == "red" p = p3(MB, line_kw=dict(color="red")) assert len(p.series) == 1 # parametric plot. The label is shown on the colorbar, which is only visible # when legend=True. p.legend = True p.process_series() f, ax = p.fig # parametric plot with use_cm=True -> LineCollection assert len(ax.collections) == 1 assert isinstance(ax.collections[0], LineCollection) assert f.axes[1].get_ylabel() == "(cos(x), sin(x))" assert all(*(ax.collections[0].get_color() - np.array([1.0, 0.0, 0.0, 1.0])) == 0) p = p4(MB, line_kw=dict(color="red")) assert len(p.series) == 1 p.legend = True p.process_series() f, ax = p.fig assert len(ax.collections) == 1 assert isinstance(ax.collections[0], Line3DCollection) assert f.axes[1].get_ylabel() == "(cos(x), sin(x), x)" assert all(*(ax.collections[0].get_color() - np.array([1.0, 0.0, 0.0, 1.0])) == 0) # use_cm=False will force to apply a default solid color to the mesh. # Here, I override that solid color with a custom color. p = p5(MB, surface_kw=dict(color="red")) assert len(p.series) == 1 p.process_series() f, ax = p.fig assert len(ax.collections) == 1 assert isinstance(ax.collections[0], Poly3DCollection) # TODO: apparently, without showing the plot, the colors are not applied # to a Poly3DCollection... -.-' # # matplotlib renders shadows, hence there are different red colors. Here # # we check that the G, B components are zero, hence the color is Red. # colors = ax.collections[0].get_facecolors() # assert all(c[1] == 0 and c[2] == 0 for c in colors) # casso p = p6(MB, contour_kw=dict(cmap="jet")) assert len(p.series) == 1 p.process_series() f, ax = p.fig # TODO: isn't there an exact number of collections associated to contour plots? assert len(ax.collections) > 0 assert f.axes[1].get_ylabel() == str(cos(x ** 2 + y ** 2)) # TODO: how to retrieve the colormap from a contour series????? # assert ax.collections[0].cmap.name == "jet" p = p7(MB, quiver_kw=dict(color="red"), contour_kw=dict(cmap="jet")) assert len(p.series) == 2 p.process_series() f, ax = p.fig assert len(ax.collections) > 0 assert isinstance(ax.collections[-1], Quiver) assert f.axes[1].get_ylabel() == "Magnitude" # TODO: how to retrieve the colormap from a contour series????? # assert ax.collections[0].cmap.name == "jet" p = p8(MB, stream_kw=dict(color="red"), contour_kw=dict(cmap="jet")) assert len(p.series) == 2 p.process_series() f, ax = p.fig assert len(ax.collections) > 0 assert isinstance(ax.collections[-1], LineCollection) assert f.axes[1].get_ylabel() == "x + y" assert all(*(ax.collections[-1].get_color() - np.array([1.0, 0.0, 0.0, 1.0])) == 0) p = p9(MB, quiver_kw=dict(cmap="jet")) assert len(p.series) == 1 p.process_series() f, ax = p.fig assert len(ax.collections) == 1 assert isinstance(ax.collections[0], Line3DCollection) assert ax.collections[0].cmap.name == "jet" p = p10(MB, stream_kw=dict()) raises(NotImplementedError, lambda: p.process_series()) p = p12(MB, contour_kw=dict(cmap="jet")) assert len(p.series) == 1 p.process_series() f, ax = p.fig assert len(ax.collections) > 0 # TODO: how to retrieve the colormap from a contour series????? # assert ax.collections[0].cmap.name == "jet" p = p14(MB, line_kw=dict(color="red")) assert len(p.series) == 2 p.process_series() f, ax = p.fig assert len(ax.get_lines()) == 2 assert ax.get_lines()[0].get_label() == "re(sqrt(x))" assert ax.get_lines()[0].get_color() == "red" assert ax.get_lines()[1].get_label() == "im(sqrt(x))" assert ax.get_lines()[1].get_color() == "red" p = p15(MB, line_kw=dict(color="red")) assert len(p.series) == 1 p.process_series() f, ax = p.fig assert len(ax.collections) == 1 assert isinstance(ax.collections[0], LineCollection) assert f.axes[1].get_ylabel() == "Abs(sqrt(x))" assert all(*(ax.collections[0].get_color() - np.array([1.0, 0.0, 0.0, 1.0])) == 0) p = p16(MB, contour_kw=dict()) assert len(p.series) == 1 p.process_series() f, ax = p.fig assert len(ax.images) == 1 assert f.axes[1].get_ylabel() == "Argument" p = p17(MB, surface_kw=dict(color="red")) assert len(p.series) == 1 p.process_series() f, ax = p.fig assert len(ax.collections) == 1 assert isinstance(ax.collections[0], Poly3DCollection)
def plot_implicit(*args, show=True, **kwargs): """Plot implicit equations / inequalities. plot_implicit, by default, generates a contour using a mesh grid of fixed number of points. The greater the number of points, 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. Arguments ========= expr : Expr, Relational, BooleanFunction The equation / inequality that is to be plotted. ranges : tuples Two tuple denoting the discretization domain, for example: `(x, -10, 10), (y, -10, 10)` If no range is given, then the free symbols in the expression will be assigned in the order they are sorted. label : str The name of the expression to be eventually shown on the legend. If none is provided, the string representation will be used. Keyword Arguments ================= adaptive : Boolean 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. It switches to a fall back algorithm (meshgrid approach) if the expression cannot be plotted using interval arithmetic. depth : integer The depth of recursion for adaptive mesh 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. n1, n2 : int Number of discretization points in the horizontal and vertical directions when `adaptive=False`. Default to 1000. n : integer Set the number of discretization points when `adaptive=False` in both direction simultaneously. Default value is 1000. The greater the value the more accurate the plot, but the more memory will be used. show : Boolean Default value is True. If set to False, the plot will not be shown. See ``Plot`` for further information. title : string The title for the plot. xlabel : string The label for the x-axis ylabel : string The label for the y-axis Examples ======== Plot expressions: .. plot:: :context: reset :format: doctest :include-source: True >>> from sympy import plot_implicit, symbols, Eq, And >>> x, y = symbols('x y') Without any ranges for the symbols in the expression: .. plot:: :context: close-figs :format: doctest :include-source: True >>> p1 = plot_implicit(Eq(x**2 + y**2, 5)) With the range for the symbols: .. plot:: :context: close-figs :format: doctest :include-source: True >>> p2 = plot_implicit( ... Eq(x**2 + y**2, 3), (x, -3, 3), (y, -3, 3)) Using mesh grid without adaptive meshing with number of points specified: .. plot:: :context: close-figs :format: doctest :include-source: True >>> p3 = plot_implicit( ... (x**2 + y**2 - 1)**3 - x**2 * y**3, ... (x, -1.5, 1.5), (y, -1.5, 1.5), ... n = 1000) Using adaptive meshing and Boolean expressions: .. plot:: :context: close-figs :format: doctest :include-source: True >>> p4 = plot_implicit( ... Eq(y, sin(x)) & (y > 0), ... Eq(y, sin(x)) & (y < 0), ... (x, -2 * pi, 2 * pi), (y, -4, 4), ... adaptive=True) Using adaptive meshing with depth of recursion as argument: .. plot:: :context: close-figs :format: doctest :include-source: True >>> p5 = plot_implicit( ... Eq(x**2 + y**2, 5), (x, -4, 4), (y, -4, 4), ... adaptive=True, depth = 2) Plotting regions: .. plot:: :context: close-figs :format: doctest :include-source: True >>> p6 = plot_implicit(y > x**2) """ from spb.defaults import TWO_D_B args = _plot_sympify(args) args = _check_arguments(args, 1, 2) kwargs = _set_discretization_points(kwargs, ImplicitSeries) series_kw = dict() series_kw["n1"] = kwargs.pop("n1", 1000) series_kw["n2"] = kwargs.pop("n2", 1000) series_kw["depth"] = kwargs.pop("depth", 0) series_kw["adaptive"] = kwargs.pop("adaptive", False) series = [] xmin, xmax, ymin, ymax = oo, -oo, oo, -oo for a in args: s = ImplicitSeries(*a, **series_kw) if s.start_x < xmin: xmin = s.start_x if s.end_x > xmax: xmax = s.end_x if s.start_y < ymin: ymin = s.start_y if s.end_y > ymax: ymax = s.end_y series.append(s) kwargs.setdefault("backend", TWO_D_B) kwargs.setdefault("xlim", (xmin, xmax)) kwargs.setdefault("ylim", (ymin, ymax)) kwargs.setdefault("xlabel", series[-1].var_x.name) kwargs.setdefault("ylabel", series[-1].var_y.name) p = Plot(*series, **kwargs) if show: p.show() return p
def geometry_plot(*args, show=True, **kwargs): """Plot entities from the sympy.geometry module. Arguments ========= geom : GeometryEntity Represent the geometric entity to be plotted. label : str The name of the complex function to be eventually shown on the legend. If none is provided, the string representation of the function will be used. To specify multiple complex functions, wrap them into a tuple. Refer to the examples to learn more. Keyword Arguments ================= fill : boolean Default to True. Fill the polygon/circle/ellipse. params : dict Substitution dictionary to properly evaluate symbolic geometric entities. The keys contains symbols, the values the numeric number associated to the symbol. Examples ======== 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`. .. code-block:: python geometry_plot( 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 several numeric geometric entities defined by numbers only, turn off fill. Every entity is represented as a line. .. code-block:: python geometry_plot( 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), fill=False) 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: .. code-block:: python a, b, c, d = symbols("a, b, c, d") geometry_plot( (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 3D geometric entities. Note: when plotting a Plane, we must always provide the x/y/z ranges: .. code-block:: python geometry_plot( (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)) ) """ from spb.defaults import TWO_D_B, THREE_D_B args = _plot_sympify(args) series = [] if not all([isinstance(a, (list, tuple, Tuple)) for a in args]): args = [args] for a in args: exprs, ranges, label = _unpack_args(*a) r = ranges if len(ranges) > 0 else [None] if len(exprs) == 1: series.append(GeometrySeries(exprs[0], *r, label, **kwargs)) else: # this is the case where the user provided: v1, v2, ..., range # we use the same ranges for each expression for e in exprs: series.append(GeometrySeries(e, *r, str(e), **kwargs)) any_3D = any(s.is_3D for s in series) if ("aspect" not in kwargs) and (not any_3D): kwargs["aspect"] = "equal" if any_3D: kwargs.setdefault("backend", THREE_D_B) else: kwargs.setdefault("backend", TWO_D_B) p = Plot(*series, **kwargs) if show: p.show() return p
def smart_plot(*args, show=True, **kwargs): """Smart plot interface. Using the same interface of the other plot functions, namely (expr, range, label), it unifies the plotting experience. If a symbolic expression can be plotted with any of the plotting functions exposed by spb.functions or spb.vectors, then this function will be able to plot it as well. Keyword Arguments ================= The usual keyword arguments available on every other plotting functions are available (`xlabel`, ..., `adaptive`, `n`, ...). On top of that we can set: pt : str Specify which kind of plot we are intereseted. Default value is None, indicating the function will use automatic detection. Possible values are: "p": to specify a line plot. "pp": to specify a 2d parametric line plot. "p3dl": to specify a 3d parametric line plot. "p3d": to specify a 3d plot. "p3ds": to specify a 3d parametric surface plot. "pi": to specify an implificit plot. "pinter": to specify an interactive plot. In such a case, you will also have to provide a `param` dictionary mapping theparameters to their values. To specify a complex-interactive plot, set `is_complex=True`. "v2d": to specify a 2D vector plot. "v3d": to specify a 3D vector plot. "c": to specify a complex plot. "g": to specify a geometric entity plot. Examples ======== Plotting different types of expressions with automatic detection: .. code-block:: python from sympy import symbols, sin, cos, Matrix from spb.backends.plotly import PB x, y = symbols("x, y") smart_plot( (Matrix([-sin(y), cos(x)]), (x, -5, 5), (y, -3, 3), "vector"), (sin(x), (x, -5, 5)), aspect="equal", n=20, legend=True, quiver_kw=dict(scale=0.25), line_kw=dict(line_color="cyan"), backend=PB ) Specify the kind of plot we are interested in: .. code-block:: python from sympy import symbols, cos x, y = symbols("x, y") plot(cos(x**2 + y**2), (x, -3, 3), (y, -3, 3), xlabel="x", ylabel="y", pt="pc") See also ======== plot, plot_parametric, plot3d, plot3d_parametric_line, plot3d_parametric_surface, plot_contour, plot_implicit, vector_plot, complex_plot """ args = _plot_sympify(args) if not all([isinstance(a, (list, tuple, Tuple)) for a in args]): args = [args] series = [] for arg in args: series.append(_build_series(*arg, **kwargs)) if "backend" not in kwargs.keys(): from spb.defaults import TWO_D_B, THREE_D_B is_3D = any([s.is_3D for s in series]) kwargs["backend"] = THREE_D_B if is_3D else TWO_D_B plots = Plot(*series, **kwargs) if show: plots.show() return plots
def vector_plot(*args, show=True, **kwargs): """Plot a 2D or 3D vector field. By default, the aspect ratio of the plot is set to ``aspect="equal"``. Arguments ========= expr : Vector, or Matrix with 2 or 3 elements, or list/tuple with 2 or 3 elements) Represent the vector to be plotted. Note: if a 3D vector is given with a list/tuple, it might happens that the internal algorithm could think of it as a range. Therefore, 3D vectors should be given as a Matrix or as a Vector: this reduces ambiguities. ranges : 3-element tuples Denotes the range of the variables. For example (x, -5, 5). For 2D vector plots, 2 ranges should be provided. For 3D vector plots, 3 ranges are needed. label : str The name of the vector field to be eventually shown on the legend. If none is provided, the string representation of the vector will be used. To specify multiple vector fields, wrap them into a tuple. Refer to the examples to learn more. Keyword Arguments ================= contours_kw : dict A dictionary of keywords/values which is passed to the plotting library contour function to customize the appearance. Refer to the plotting library (backend) manual for more informations. n1, n2, n3 : int Number of discretization points in the x/y/z-direction respectively for the quivers or streamlines. Default to 25. n : int Set the same number of discretization points in all directions for the quivers or streamlines. It overrides n1, n2, n3. Default to 25. nc : int Number of discretization points for the scalar contour plot. Default to 100. quiver_kw : dict A dictionary of keywords/values which is passed to the plotting library quivers function to customize the appearance. Refer to the plotting library (backend) manual for more informations. scalar : boolean, Expr, None or list/tuple of 2 elements Represents the scalar field to be plotted in the background. Can be: True: plot the magnitude of the vector field. False/None: do not plot any scalar field. Expr: a symbolic expression representing the scalar field. List/Tuple: [scalar_expr, label], where the label will be shown on the colorbar. Default to True. show : boolean Default to True, in which case the plot will be shown on the screen. slice : Plane, list, Expr Plot the 3D vector field over the provided slice. It can be: Plane: a Plane object from sympy.geometry module. list: a list of planes. Expr: a symbolic expression representing a surface. The number of discretization points will be `n1`, `n2`, `n3`. Note that: 1. only quivers plots are supported with slices. 2. `n3` will be used only with planes parallel to xz or yz. streamlines : boolean Whether to plot the vector field using streamlines (True) or quivers (False). Default to False. stream_kw : dict A dictionary of keywords/values which is passed to the backend streamlines-plotting function to customize the appearance. Refer to the backend's manual for more informations. Examples ======== Quivers plot of a 2D vector field with a contour plot in background representing the vector's magnitude (a scalar field): .. code-block:: python x, y = symbols("x, y") vector_plot([-sin(y), cos(x)], (x, -3, 3), (y, -3, 3)) Streamlines plot of a 2D vector field with no background scalar field: .. code-block:: python x, y = symbols("x, y") vector_plot([-sin(y), cos(x)], (x, -3, 3), (y, -3, 3), streamlines=True, scalar=None) Plot of two 2D vectors with background the contour plot of magnitude of the first vector: .. code-block:: python x, y = symbols("x, y") vector_plot([-sin(y), cos(x)], [y, x], n=20, scalar=sqrt((-sin(y))**2 + cos(x)**2), legend=True) 3D vector field: .. code-block:: python x, y, z = symbols("x, y, z") vector_plot([x, y, z], (x, -10, 10), (y, -10, 10), (z, -10, 10), n=8) 3D vector field with 3 orthogonal slice planes: .. code-block:: python x, y, z = symbols("x, y, z") vector_plot([z, y, x], (x, -10, 10), (y, -10, 10), (z, -10, 10), n=8, slice=[ Plane((-10, 0, 0), (1, 0, 0)), Plane((0, -10, 0), (0, 2, 0)), Plane((0, 0, -10), (0, 0, 1)), ]) """ args = _plot_sympify(args) args = _preprocess(*args) kwargs = _set_discretization_points(kwargs, Vector3DSeries) # TODO: do I need these? if "n1" not in kwargs: kwargs["n1"] = 25 if "n2" not in kwargs: kwargs["n2"] = 25 if "aspect" not in kwargs.keys(): kwargs["aspect"] = "equal" series = _build_series(*args, **kwargs) if all([isinstance(s, (Vector2DSeries, ContourSeries)) for s in series]): from spb.defaults import TWO_D_B backend = kwargs.pop("backend", TWO_D_B) elif all([isinstance(s, Vector3DSeries) for s in series]): from spb.defaults import THREE_D_B backend = kwargs.pop("backend", THREE_D_B) else: raise ValueError("Mixing 2D vectors with 3D vectors is not allowed.") p = Plot(*series, backend=backend, **kwargs) if show: p.show() return p