13

This answer from a few years ago shows how you can make jupyter notebook create graphs as svg. The solution is to tell the InlineBackend to use svg as output.

import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
plt.plot(...)

This will cause all images to be in svg format inside the notebook as well as in the produced ipynb file; the file will have a line like

"data": {  "image/svg+xml": [  "<?xml  .....

in it.

The problem is now that this does not work if the %matplotlib notebook backend is used. %config InlineBackend does not change anything for the notebook backend, hence the output file contains a PNG image

"data": { "text/html": [  "<img src=\"data:image/png;base64,iVBORw0....

So the question is: How do I get the ipynb file to include a static version of the plot that is created with the %matplotlib notebook backend as SVG image?

There is a small comment by @mark jay from one month ago, who wanted to do exactly what I would like to do now, but there is no answer or hint to that comment.

In my code I have plotted directly from the dataframe:

%matplotlib notebook
import pandas as pd
df = pd.read_sql(sql1, connection)
...
...
df.plot(subplots=True, kind='bar')

This functions perfectly well without importing matplotlib.pyplot but it also can't be coerced to create the graphic as an svg. I suppose if the base case would work, I could modify the plotting code so it did not involve pandas or dataframes.

cardamom
  • 6,873
  • 11
  • 48
  • 102
  • If you go into jupyter notebook, plot something directly from a pandas dataframe, save the notebook and close it, and then open up the .ipynb file in a text editor you will find that your graphic is stored as .png, pixel level data. Can you make it store as svg so it takes up less space for certain kinds of graphics. – cardamom Aug 04 '17 at 08:38
  • It took me a while to understand the problem. I therefore edited the question, to now make it clearer. Please have a look at it and decide if this is what you want to ask (mind that the problem is independend on the use of pandas). If you are unhappy with my edit, click on my edit and use the rollback button. – ImportanceOfBeingErnest Aug 04 '17 at 10:19
  • yes thanks, added a bit more about pandas plot, but from your edit can see you know a bit more about these graphics formats than me, I just know that one is more compact when it's just bars and a few lines. – cardamom Aug 04 '17 at 10:58
  • I've been trying out other plotting libraries and plotly seems to provide the best results for an interactive plot. Bokeh is also nice too. https://plot.ly/python/ – mark jay Aug 05 '17 at 04:30
  • Thanks for the tip, I read that plotly is open source, didn't think it was, can see it does dashboards too. – cardamom Aug 05 '17 at 10:40

2 Answers2

2

Since apparently even after a bounty period noone was able to provide a solution, a workaround may be the following.

  1. Create you notebook with %matplotlib notebook. Once you're satisfied with the result, save it.
  2. Use a copy of it and replace %matplotlib notebook with

    %matplotlib inline
    %config InlineBackend.figure_format = 'svg'
    

    Rerun the complete notebook. Save the result.

  3. Open the resulting ipynb file in a text editor and replace the previous two lines again with %matplotlib notebook.

The final result will be a ipynb with svg images. But once opened and run, it will use the notebook backend for figure creation.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Thanks for setting the bounty, it's got another 20 hours. Have systematically tested this creating a new file for each step. My .ipynb file size after step 1, 2 and 3 are 174kB, 411kB and 184kB. When I do a text search on the file contents, `image/svg+xml` is only in the file after step 2. Added `figsize=(10, 30)` btw as per [this answer](https://stackoverflow.com/a/638443/4288043) to set the size after step 2. Step 3, the text editor edit and rerun turns it back into a png. That figsize command really replaces my need for the mouse resizing feature of `matplotlib notebook` and stores svg. – cardamom Aug 15 '17 at 16:04
1

From whatI understand from reading about matplotlib backends, nbagg, which is called using %matplotlib notebook uses the Agg (Anti-Grain Geometry) render which is not capable of rendering vector graphics. Unfortunately this is the only out of the box way of using an interactive inline backend for Jupyter.

Docs Link https://matplotlib.org/faq/usage_faq.html#what-is-interactive-mode
Similar Answer How to make matplotlibs nbagg backend generate SVGs?

If you don't need the interactivity just keep use

import pandas as pd
from IPython.display import SVG, display
from numpy import ndarray

def svg_add(chart, size=(4,4), dpi=100):
    """Takes a chart, optional tuple of ints for size, int for dpi
    default is 4 by 4 inches with 100 dpi"""

    if type(chart) == ndarray:
        fig = chart[0].get_figure()
        fig.set_size_inches(size)
        fig.savefig("mybar.svg", dpi=dpi)
        display(SVG(filename='mybar.svg'))
    else:
        fig = chart.get_figure()
        fig.set_size_inches(size)
        fig.savefig("mybar.svg", dpi=dpi)
        display(SVG(filename='mybar.svg'))

then

df = pd.DataFrame([[2,5]],columns=['a','b'])
bar_chart = df.plot(subplots=False, kind='bar')
svg_add(chart=bar_chart,size=(3,3),dpi=100)
#or
#svg_add(bar_chart,(3,3),100)
fcsr
  • 921
  • 10
  • 17
  • The question is about how to get the ipynb file to include the svg. The `%matplotlib notebook` backend is perfectly capable of producing svg when using `savefig`; but what is needed is a way to include that svg into the ipynb file. – ImportanceOfBeingErnest Aug 14 '17 at 08:17
  • `%matplotlib notebook` uses the `Nbagg` backend. Which only produces PNG files when rendered in the notebook. So when its rendered as PNG in the Notebook and then the notebook is saved, knowing that it can only render PNG, why would you expect that it can be saved as SVG rendered in the notebook? Matplotlib does accept custom backends, maybe you can write your own. Its now a question of what can be done versus what you want it to do – fcsr Aug 14 '17 at 09:23
  • I don't think anyone expects the notebook backend to render svg into the notebook. Did anyone say so? However, since it is possible to generate svg, there must surely be a way to include the svg in the ipynb file. This is what the question asks for. I would be thinking rather in the direction of `$ ipython nbconvert` and some template or so. In any case, I don't see how this answer helps in coming closer to that goal. – ImportanceOfBeingErnest Aug 14 '17 at 09:43
  • I understand what you mean, and I'm pretty sure theres a hacky way using html `magic` or separating the configuration per cell of the notebook. like using `%matplotlib notebook` on one cell and `%matplotlib inline` in the other, or just using html and including the svg itself. But the way the question is framed is you want it to happen with `%matplotlib notebook`. if you just want the svg to display then I updated my answer to show saving svg then rendering into notebook – fcsr Aug 14 '17 at 09:56
  • Thanks for that @fcsr yes that works, I notice the saved .svg is 132kB which is not as small as I thought it would be. That works perfectly with your `subplots=False` although in my use case `subplots=True` which leads to the error `AttributeError: 'numpy.ndarray' object has no attribute 'get_figure'` I suspect the real answer my the question is that the Pandas, Matplotlib or Jupyter devs have not really created this functionality (yet) and it can only really be achieved through hacks. – cardamom Aug 15 '17 at 15:17
  • edited answer, tested with other graphs, seems like either the output is an ndarray or axes. – fcsr Aug 16 '17 at 00:10
  • let me know how big your file was after the edit to the code, just curious – fcsr Aug 16 '17 at 00:30
  • Am going through and experimenting with your code and edits @fcsr trying to work out if you have written that function to get a similar output to `subplots=True` but with `subplots=False` – cardamom Aug 17 '17 at 10:46
  • yup, if its `subplots=True` the output will be an `numpy.ndarray` so it just takes the first object in the array to turn into svg, if its `subplots=False` the output will be `axes.subplot` which can readily be turned into svg – fcsr Aug 17 '17 at 11:32
  • So what is the benefit of this compared to a simple `inline`+SVG combo? – P-Gn Feb 16 '18 at 13:24