[Matplotlib-users] Arbitrary artist data on SVG elements

Thomas Caswell tcaswell at gmail.com
Sat Apr 1 22:02:34 EDT 2017


That seems reasonable (but I don't know the svg backend super well).

One concern in the special keys in `svg_attribs`,  would reserving the keys
'defs' and 'extra_content' get in the way of users?  It may be better to do
this as 4 parameters.

For 5 if you are not sure maybe just skip it for now?

For 4 is it possible/reasonable to validate the xml before we write it?

On Sat, Apr 1, 2017 at 8:57 PM Joshua Klein <mobiusklein at gmail.com> wrote:

> Hello,
>
> I couldn’t find an example in the gallery, but just reading Figure.savefig,
> and FigureCanvasBase.print_figure it was pretty clear how extra arguments
> would flow to the backend, and that appropriately prefixed keyword
> arguments would insulate the the high level API.
>
> Since the preferred approach would be to just migrate this logic into the
> SVG backend, it would also be a good opportunity to expose some of the
> other parts of the SVG canvas that are otherwise left constant or
> effectively constant by association e.g. height vs. viewBox dimensions,
> setting other attributes on the <svg> element, and the inclusion of some
> extra external components like including <defs> sections as described in
> svg_filter_line <http://matplotlib.org/examples/misc/svg_filter_line.html>
> .
>
> Proposed implementation would be:
>
>    1. Add keyword arguments svg_gid_data and svg_attribs to
>    FigureCanvasSVG._print_svg to be passed to RendererSVG, which will be
>    expected to be Mapping-like objects.
>    2. Add svg_gid_data and svg_attribs arguments to RendererSVG.__init__
>    and attributes by the same name.
>    3. When RendererSVG begins writing the <svg> tag, use the default
>    values as written, and those key-value pairs of self.svg_attribs
>    except for "defs" and "extra_content" keys.
>    4. After completing the opening <svg> tag, if a "extra_content" key is
>    in self.svg_attribs, this content will be written verbatim into the
>    output stream, where malformed XML will produce invalid markup.
>    5. If "defs" is in self.svg_attribs, the value will be written into
>    the stream verbatim, (or map a dict of dicts to XML? Seems too much work
>    for something I don’t know enough about).
>    6. When RendererSVG begins rendering an artist, it will check if the
>    artist has an assigned gid by calling Artist.get_gid, and if a gid is
>    set, check self.svg_gid_data for additional data to include when
>    opening the artist’s appropriate tag. No translation will be done so
>    attribute names will be used as-is. This could be used to set on<event>
>    handlers and set the class attribute, as well as adding data-<name>
>    attributes for adding semantic data to the graphical elements.
>
> I can also fix an omission in FigureCanvasSVG.print_svgz failing to
> propagate **kwargs to _print_svg.
>
> Thank you,
> Joshua Klein
>>
> On Sat, Apr 1, 2017 at 4:30 PM, Thomas Caswell <tcaswell at gmail.com> wrote:
>
> Joshua,
>
> That is an interesting use case!
>
> I am hesitant to add this attribute to Artist because it is very specific
> to the SVG backend (none of the other backends would make use of this as
> far as I know). On the other hand, a generic way to use gid to add extra
> information in the SVG backend could be interesting.  I am pretty sure
> there are examples of optianal backend-specific kwargs going into
> `savefig`, what would the API for that look like?
>
> Tom
>
> On Wed, Mar 22, 2017 at 4:42 PM Joshua Klein <mobiusklein at gmail.com>
> wrote:
>
> 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
>> _______________________________________________
> Matplotlib-users mailing list
> Matplotlib-users at python.org
> https://mail.python.org/mailman/listinfo/matplotlib-users
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/matplotlib-users/attachments/20170402/4da4bc7b/attachment-0001.html>


More information about the Matplotlib-users mailing list