[Matplotlib-users] Arbitrary artist data on SVG elements

Joshua Klein mobiusklein at gmail.com
Wed Mar 22 16:41:48 EDT 2017


Hello,

I often embed figures as SVG graphics in web pages. As part of this
process, I usually do the following

   1.

   Set gids on artists and link that gid to a set of data describing that
   part of a graphic in an external dictionary. This includes things like
   setting the element’s class, extra contextual information, information that
   would be good to show in a tooltip, ids of related elements, and so forth.
   2.

   Serialize the figure into a file-like object, use an element tree
   implementation’s XMLID to get an element id map and Element objects
   3.

   Iterate over my data dictionary from (1) and set keys in the mapped
   Element’s attrib dictionary, using the id map from (2)
   4.

   Use the element tree implementation’s tostring function to serialize the
   updated Element objects back into a string and then send the string out as
   a response to a web request.
   5.

   After receiving the SVG string from the server on the client, add the
   SVG to the page’s DOM and then hang event handlers on it (or pre-specify
   delegated handlers) that use the added attributes to configure interactive
   behavior.

I looked at the Artist type and saw no good place to store “arbitrary
data”. Before I start working on this I wanted to know if anyone else had a
better solution. I would also like to know if the devs would be opposed to
a PR that adds an extra dictionary/attribute to every Artist instance
created.

Another alternative solution would be to find a way to push my dictionary
mapping gids to extra attributes into the SVGRenderer and have it pass them
as **extras to XMLWriter.element when it processes individual artists.

Here’s a generic example of what I do currently:

def plot_with_extras_for_svg(*data, **kwargs):
    # Do the plotting, generating the id-linked data in `id_mapper`
    ax, id_mapper = plot_my_data(*data, **kwargs)
    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    # compute the total space used in both dimensions when dealing with
    # negative axis bounds
    x_size = sum(map(abs, xlim))
    y_size = sum(map(abs, ylim))

    # Map the used axis space to the drawable region dimensions
    aspect_ratio = x_size / y_size
    canvas_x = 8.
    canvas_y = canvas_x / aspect_ratio

    # Configure the artist to draw within the new drawable region bounds
    fig = ax.get_figure()
    fig.tight_layout(pad=0.2)
    fig.patch.set_visible(False)
    fig.set_figwidth(canvas_x)
    fig.set_figheight(canvas_y)

    ax.patch.set_visible(False)

    # Perform the first serialization
    buff = StringIO()
    fig.savefig(buff, format='svg')

    # Parse XML buffer from `buff` and configure tag attributes
    root, ids = ET.XMLID(buff.getvalue())
    root.attrib['class'] = 'plot-class-svg'
    for id, attributes in id_mapper.items():
        element = ids[id]
        element.attrib.update({("data-" + k): str(v)
                               for k, v in attributes.items()})
        element.attrib['class'] = id.rsplit('-')[0]

    # More drawable space shenanigans
    min_x, min_y, max_x, max_y = map(int, root.attrib["viewBox"].split(" "))
    min_x += 100
    max_x += 200
    view_box = ' '.join(map(str, (min_x, min_y, max_x, max_y)))
    root.attrib["viewBox"] = view_box
    width = float(root.attrib["width"][:-2]) * 1.75
    root.attrib["width"] = "100%"

    height = width / (aspect_ratio)

    root.attrib["height"] = "%dpt" % (height * 1.2)
    root.attrib["preserveAspectRatio"] = "xMinYMin meet"

    # Second serialization
    svg = ET.tostring(root)
    plt.close(fig)

    return svg

Thank you
​
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/matplotlib-users/attachments/20170322/a9aeefda/attachment-0001.html>


More information about the Matplotlib-users mailing list