# Creating custom plots

Sometimes, the functions exposed by Sympy's plotting module are not enough to accomplish our visualization objectives. If that's the case, we can either:
1. `lambdify` the symbolic expressions and evaluate it numerically. However, this process is manually intensive.
2. If the expressions can be plotted by the common plotting functions (`plot`, `plot3d`, `plot_parametric`, ...), then we can use the `get_plot_data` function, which automate the _lambdifying_ process. This function accepts the same arguments of the aforementioned plotting functions, therefore it is really easy to get the numerical data we are interested in.

Once we have the numerical data, we can use our preferred plotting library. If we are lucky enough, we can also:
1. use one of the plotting functions as a starting point;
2. extract the numerical data with the `get_plot_data` function;
3. extract the plot object associated to the plotting library;
4. use the appropriate command of the plotting library to add new data to the plot.

Let's see a few examples.

## Example 1 - Editing and Adding Data

The current backends are able to plot lines, gradient lines, contours, quivers, streamlines. However, they are not able to plot things like _curve fills_, bars, ...

In this example we are going to illustrate a procedure that can be used to further customize the plot. Note that the procedure depends on the backend we are going to use, because we are going to use backend-specific commands. In the following, we are going to use ``PlotlyBackend``. For other backends, the procedure might need to be adjusted.


```python
from sympy import *
from spb import *
from spb.backends.matplotlib import MB
from spb.backends.plotly import PB
```

Let's say we would like to plot on the same figure:
* a normal distribution filled to the horizontal axis.
* a dampened oscillation.
* bars following an exponential decay at integer locations.


```python
x, mu, sigma = symbols("x, mu, sigma")
expr1 = 1 / (sigma * sqrt(2 * pi)) * exp(-((x - mu) / sigma)**2 / 2)
expr2 = cos(x) * exp(-x / 6)
expr3 = exp(-x / 5)
display(expr1, expr2, expr3)
```

We start by plotting the first two expressions, as the third one requires a different approach:


```python
p = plot(
    (expr1.subs({sigma: 0.8, mu: 5}), "normal"), 
    (expr2, "oscillation"),
    (x, 0, 10), backend=PB)
```

Now, we'd like to fill the first curve. First, we extract the figure object; then we set the necessary attribute to get the job done:


```python
f = p.fig
f.data[0]["fill"]="tozerox"
f
```

At this point we have to convert ``expr3`` to numerical data. We can do it with ``get_plot_data``, which requires the same arguments as the ``plot`` function, namely ``(expr, range, label [optional], **kwargs)``:


```python
xx, yy = get_plot_data(exp(-x / 5), (x, 0, 10), only_integers=True)
print(xx)
print(yy)
```

Now that we have generated the numerical values at integer locations, we can add the bars with the appropriate command:


```python
import plotly.graph_objects as go
import numpy as np

f.add_trace(go.Bar(x=xx, y=yy, width=np.ones_like(xx) / 2, name="bars"))
f
```

That's it, job done.

## Example 2

The backends are unable to mix 2D and 3D data series. But what if we would like to plot a contour into a 3D plot?

Let's say we'd like to explore the following vector field, $\vec{F}(x, y, z) = (\cos{(z)}, y, x)$, in the rectangular volume limited by $-5 \le x \le 5, \, -5 \le y \le 5, \, -5 \le z \le 5$. We are going to plot the contours of the magnitude of the vector field over 3 orthogonal planes, as well as quivers over a plane normal to the y-direction.


```python
x, y, z = symbols("x:z")
v = Matrix([cos(z), y, x])

# magnitudes of the vector field over 3 orthogonal planes
mag_func = lambda vec: sqrt(sum(t**2 for t in vec))
mag = mag_func(v)
m1 = mag.subs(x, 5)
m2 = mag.subs(y, 5)
m3 = mag.subs(z, 5)
display(mag, m1, m2, m3)
```

Let's extract the data of the magnitudes:


```python
# ranges
rx = (x, -5, 5)
ry = (y, -5, 5)
rz = (z, -5, 5)
# contour data: similarly to plot3d/plot_contour the parameters 
# to get_plot_data follows (expr, range_x, range_y)
xx1, yy1, zz1 = get_plot_data(m1, ry, rz)
xx2, yy2, zz2 = get_plot_data(m2, rx, rz)
xx3, yy3, zz3 = get_plot_data(m3, rx, ry)
```

Now, let's extract the data of the sliced-vector field:


```python
xq1, yq1, zq1, uu, vv, ww = get_plot_data(v, rx, ry, rz, slice=Plane((0, 0, 0), (0, 1, 0)))
```

Finally, we create the custom plot:


```python
%matplotlib widget
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm

ax = plt.figure().add_subplot(projection='3d')

# Plot projections of the contours for each dimension.  By choosing offsets
# that match the appropriate axes limits, the projected contours will sit on
# the 'walls' of the graph
ax.contourf(xx1, zz1, yy1, zdir='y', offset=5, cmap=cm.coolwarm)
ax.contourf(zz2, xx2, yy2, zdir='x', offset=-5, cmap=cm.coolwarm)
ax.contourf(xx3, yy3, zz3, zdir='z', offset=-5, cmap=cm.coolwarm)
ax.quiver(xq1, yq1, zq1, uu, vv, ww, color="g", length=0.5, normalize=True)
          
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(-5, 5)

ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

plt.show()
```


```python

```

c27387da25bf40b1c092c28e037d594a1131b5cb
ipynb
Jupyter Notebook
tutorials/tutorial-4.creating-custom-plots.ipynb
Davide-sd/sympy-plot-backends
f8ad8a5d7aef3c55ddee8e77997e06e0b4f2e14b
BSD-3-Clause
2021-05-04T17:57:32.000Z
2022-02-24T16:14:45.000Z
tutorials/tutorial-4.creating-custom-plots.ipynb
Davide-sd/sympy-plot-backends
f8ad8a5d7aef3c55ddee8e77997e06e0b4f2e14b
BSD-3-Clause
2021-05-04T20:02:20.000Z
2022-03-25T22:08:49.000Z
tutorials/tutorial-4.creating-custom-plots.ipynb
Davide-sd/sympy-plot-backends
f8ad8a5d7aef3c55ddee8e77997e06e0b4f2e14b
BSD-3-Clause
2021-07-23T20:38:14.000Z
2022-02-10T09:20:47.000Z
Qwen/Qwen-72B
1. YES
2. YES