'How to resize / rescale a SVG graphic in an iPython / Jupyter Notebook?

I've a large SVG (.svg graphic) object to display in a iPython / Jupyter Notebook. In fact, I've a large neural network model graphic created with Keras to display.

from IPython.display import SVG
from keras.utils.vis_utils import model_to_dot

SVG(model_to_dot(model,show_shapes=True).create(prog='dot', format='svg'))

So, I want to resize / rescale / shrink the SVG graphic to fit in my Notebook page (particularly horizontally, since the page width is limited).



Solution 1:[1]

Another option is to use the "dpi" (dots per inch) property. A little hacky but this allowed me to shrink my SVG. Tensorflow: model_to_doc

    from IPython.display import SVG
    from keras.utils import model_to_dot

    SVG(model_to_dot(model, show_shapes= True, show_layer_names=True, dpi=65).create(prog='dot', format='svg'))

Solution 2:[2]

A new improved solution particularly useful using spaCy, (tested with the version 2.2.4). In order to be able to scale the .svg without cropping, we must add a viewBox to the XML / HTML svg representation.

Before scaling:

enter image description here

import spacy
import re
from spacy import displacy

nlp_model = spacy.load("en_core_web_sm")
doc = nlp_model(u"To be or not to be, that is the question")
svg = displacy.render(doc,  style='dep', page=True, jupyter=False)

def add_viewbox_svg(svg):
    regex = r'class="displacy" width="([\d\.]*)" height="([\d\.]*)'
    match_results = re.findall(regex,svg)
    new_svg = svg.replace("<svg ","<svg viewBox='0 0 "+
                          match_results[0][0]+" "+
                          match_results[0][1]+
                          "' preserveAspectRatio='none' ")
    return new_svg

new_svg = add_viewbox_svg(svg)

Then using style:

style = "<style>svg{width:100% !important;height:100% !important;</style>"
display(HTML(style))
display(HTML(new_svg))

After scaling:

enter image description here

Solution 3:[3]

This is what worked for me:

from IPython.display import SVG, display, HTML
import base64
_html_template='<img width="{}" src="data:image/svg+xml;base64,{}" >'

def svg_to_fixed_width_html_image(svg, width="100%"):
    text = _html_template.format(width, base64.b64encode(svg))
    return HTML(text)

svg_to_fixed_width_html_image(svg)

enter image description here

Solution 4:[4]

Above CSS-based workaround does not seem to work for me, as both width= and height= were directly set in attribute of generated SVG element.

As an additional workaround, here's what I got:

iv1_dot = model_to_dot(iv1_model, show_shapes=False, show_layer_names=False, rankdir='LR')
iv1_dot.set_size('48x8')
SVG(iv1_dot.create(prog=['dot'], format='svg'))

This allowed me to specify size of graphviz drawing in inch (48x8, in above case).

Solution 5:[5]

The answers above and below are really good but depends upon the system. They might or might not work for you always depending upon your browser plugins, Jupyter version, Colab environment, etc. Generally, SVG() converts the dot image to an SVG file but it is tricky to adjust the size of that by customizing the associating HTML. What I'd suggest is using this:

from keras.utils import plot_model
plot_model(model, to_file='model.png')

You can then use the snippet in the above answer by user48956 to adjust the size of the image generated. But most of the time, it scales on its own.

Further Reference: Here

Solution 6:[6]

The other solutions all had some issues for my use case. I thus propose this solution which manipulates the svg data directly using BeautifulSoup:

from IPython.display import SVG
from bs4 import BeautifulSoup
import re


def scale_svg(svg_object, scale=1.0):

    soup = BeautifulSoup(svg_object.data, 'lxml')
    svg_elt = soup.find("svg")
    w = svg_elt.attrs["width"].rstrip("pt")
    h = svg_elt.attrs["height"].rstrip("pt")

    ws = float(w)*scale
    hs = float(h)*scale

    svg_elt.attrs["width"] = f"{ws}pt"
    svg_elt.attrs["height"] = f"{hs}pt"
    svg_elt.attrs["viewbox"] = f"0.00 0.00 {ws} {hs}"

    g_elt = svg_elt.find("g")
    tf = g_elt.attrs["transform"]
    # non-greedy regex-search-and-replace
    tf2 = re.sub(
        "scale\(.*?\)",
        f"scale({scale} {scale})",
        tf
    )
    g_elt.attrs["transform"] = tf2

    svg_object.data = str(svg_elt)
    
    return svg_object

s1 = SVG("""
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="103.0pt" viewbox="0.00 0.00 170.0 103.0" width="170.0pt">
<g class="graph" id="graph0" transform="scale(1.0 1.0) rotate(0) translate(4 199)">
<title>G</title>
<polygon fill="#ffffff" points="-4,4 -4,-199 166,-199 166,4 -4,4" stroke="transparent"/>
<!-- node0000 -->
<g class="node" id="node1">
<title>node0000</title>
<ellipse cx="81" cy="-159" fill="none" rx="36" ry="36" stroke="#000000"/>
<text fill="#000000" font-family="Times,serif" font-size="10.00" text-anchor="middle" x="81" y="-156.5">ABCDEFGH</text>
</g>
</g>
</svg>
""")

scale_svg(s1, 3.2)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kristina Milkovich
Solution 2
Solution 3
Solution 4 Taisuke Yamada
Solution 5 Amitrajit Bose
Solution 6 cknoll