pydot / pydot

Python interface to Graphviz's Dot language
https://pypi.python.org/pypi/pydot
897 stars 160 forks source link

RFE: Rendet Dot results in Jupyter cells #220

Open ankostis opened 4 years ago

ankostis commented 4 years ago

Simply adding this method in pydot.Dot class makes it renderable in jupyter notebooks & lab:

diff --git a/pydot.py b/pydot.py
index 60a2bac..70829b6 100644
--- a/pydot.py
+++ b/pydot.py
@@ -1745,6 +1745,8 @@ class Dot(Graph):

         self.obj_dict = state

+    def _repr_svg_(self):
+        return self.create_svg().decode()

     def set_shape_files(self, file_paths):
         """Add the paths of the required image files.

There is an issue of which encoding to use, and how. But it would be a great facility to data people who love notebooks.

ankostis commented 4 years ago

Note that _repr_svg_() has some drawbacks on jupyterlab (but not plain Jupyter notebooks,, as described in jupyterlab/jupyterlab#7497.

ankostis commented 4 years ago

(continuing my last comment) I reverted to implement the _repr_html() method instead, which works in both jupyter environments, but the SVGs are not re-scalable. Therefore i used the svg-pan-zoom JS library to make them "zoomable" with user controls, as shown in this hackish commit: pygraphkit/graphtik@e350afa78.

For the web, i would try also the all client-JS solution https://visjs.github.io/vis-network/docs/network

ankostis commented 4 years ago

For future reference, this the code for zoomable SVGs in Jupyter:

def _repr_html_monkeypatches(dot):
    """
    Monkey-patching for ``pydot.Dot._repr_html_()` for rendering in jupyter cells.

    :param dot:
        a `pydot.Dot()` instance

    .. Note::
        Had to use ``_repr_html_()`` and not simply ``_repr_svg_()`` because
        (due to https://github.com/jupyterlab/jupyterlab/issues/7497)

    """
    pan_zoom_json" = "{controlIconsEnabled: true, zoomScaleSensitivity: 0.4, fit: true}",
    element_styles = "width: 100%; height: 300px;",
    container_styles = ""
    svg_txt = dot.create_svg().decode()
    html = f"""
        <div class="svg_container">
            <style>
                .svg_container {{
                    {container_styles}
                }}
                .svg_container SVG {{
                    {element_styles}
                }}
            </style>
            <script src="http://ariutta.github.io/svg-pan-zoom/dist/svg-pan-zoom.min.js"></script>
            <script type="text/javascript">
                var scriptTag = document.scripts[document.scripts.length - 1];
                var parentTag = scriptTag.parentNode;
                svg_el = parentTag.querySelector(".svg_container svg");
                svgPanZoom(svg_el, {pan_zoom_json});
            </script>
            {svg_txt}
        </</>
    """
    return html
ferdnyc commented 5 months ago

There is an issue of which encoding to use

Not really, as the encoding is... well,

  1. Probably always utf-8
  2. Most likely embedded in the SVG's <?xml tag

So if one wanted to be really pedantic, they could use:

import re
import pydot

dot = pydot.Dot()
svg = dot.create_svg()
encoding = 'utf-8'
if b'encoding=' in svg:
    match = re.search(b'encoding="([^"]*)"', svg)
    encoding = match.groups()[0].decode(encoding="ascii")
svg_txt = svg.decode(encoding=encoding)

Heh! In fact, interestingly, my point 1 turns out to be more correct. You wouldn't actually want to use that code, as it probably lies:

>>> import pydot
>>> dot = pydot.Dot()
>>> dot.create_svg(encoding="iso-8859-15")
b'<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"\n "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<!-- Generated by graphviz version 8.1.0 (20230707.0739)\n -->\n<!-- Title: G Pages: 1 -->\n<svg width="8pt" height="8pt"\n viewBox="0.00 0.00 8.00 8.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 4)">\n<title>G</title>\n<polygon fill="white" stroke="none" points="-4,4 -4,-4 4,-4 4,4 -4,4"/>\n</g>\n</svg>\n'

Note the encoding="UTF-8" in the resulting bytestring.

The question of whether an SVG created with create_svg(encoding="iso-8859-15") is actually encoded in ISO Western European script, as we requested, or in UTF-8, as it claims, remains an interesting one.

ferdnyc commented 5 months ago

But to finish my thought, the safest option is probably to always explicitly specify "utf-8" both coming and going:

import pydot

def _repr_svg(self):
    return self.create_svg(encoding="utf-8").decode(
        encoding="utf-8")

pydot.Dot._repr_svg = _repr_svg

Ditto for a _repr_html() implementation.