Home > Enterprise >  How to get plot as svg string in sympy plotting backends library?
How to get plot as svg string in sympy plotting backends library?

Time:11-27

I use sympy plotting backends library to create plots directly from sympy expressions. I chose this library because it gives more options for plots fine tunning compared to standart sympy plotting module. As backend in choosen library i use matplotlib.

My aim is to get resulting plot as svg string in order to insert it in the web page later. I need to do it programmatically. I use the code below:

from spb import plot, MB
import io
from sympy import symbols, sin, cos

x = symbols("x")

# create plot
p1 = plot(
    (sin(x), "a", dict(color="k", linestyle=":")),
    (cos(x), "b"),
    backend=MB, show=False)

# buffer:
f = io.StringIO()

# save plot in buffer as svg string:
p1._fig.savefig(f, format = "svg")

# return result as svg string to insert it in web page later:
return f.getvalue()

The problem is that i get an exception:

'NoneType' object has no attribute 'savefig'
 p1._fig.savefig(f, format = "svg")

But if i slightly modify the code:

...
# buffer:
f = io.StringIO()

# show plot:
p1.show()

# save plot in buffer as svg string:
p1._fig.savefig(f, format = "svg")
...

Everything works just fine. But the problem is that i do not want to show the plot, i need to save it as svg string. Anyone knows how to solve this task?

CodePudding user response:

Here I am, the developer of that module.

That error is caused by the fact that when you create a plot with MatplotlibBackend and show=False, the figure is not created (too long to explain why); this behavior is specific to MatplotlibBackend, the other backends should not be affected by it. So, p1.fig is None.

However, the plotting function exposes the save method, which is nothing more than a wrapper to a specific plotting library "save" functionality. If you look at the source code, you'll see that MatplotlibBackend.save calls matplotlib's savefig, but first it checks if the figure has been created. If not, it forces the creation.

So, all you have to do is:

p1.save(f, format = "svg")

One final note. If possible, do not use attributes or methods starting with _ (underscore). They represent private attributes and the names might change from version to version. If you really need to retrieve the matplotlib figure, use p1.fig.

EDIT to answer the question in the comment about performance:

For back-compatibility reasons, the new module uses an adaptive algorithm for line plots by default, which is different from the one used on SymPy. On the one hand it can be easily applied to a wider set of applications, on the other hand it is slower.

You have two options: either way you might want to change the configuration file of the module.

Option 1: the adaptive algorithm minimizes some loss function (loss_fn) and stops when a threshold (adaptive_goal) has been reached. We can increase this threshold (which by default is set to 0.01), thus improving performance but sacrificing the smooth quality of lines.

from spb.defaults import cfg, set_defaults

# it requires a few tries to find an appropriate value
cfg["adaptive"]["goal"] = 0.02
set_defaults(cfg)

# restart the kernel to load the new configuration

Option 2: don't use the adaptive algorithm and switch to the uniform meshing algorithm which uses Numpy and vectorization (usually done with adaptive=False and possibly setting an appropriate number of discretization points n=something)! This is very very fast in comparison to the adaptive algorithm.

Think about it: generally, our plots are relatively small in comparison to the screen size. 1000 points per line (or whatever number you decide to use) should create smooth-enough lines.

So, you can deactivate the adaptive algorithm on a plot-by-plot basis (with adaptive=False), or you can set the module to always use the uniform meshing algorithm (this is the setup that I use on my machine).

from spb.defaults import cfg, set_defaults

# disable adaptive algorithm
cfg["adaptive"]["used_by_default"] = False
set_defaults(cfg)

# restart the kernel to load the new configuration

Then, when you create a plot and you feel like it should be smoother, simply increase the number of discretization points by setting n=something (default value is 1000).

You can find more customization options on this documentation page.

  • Related