[Matplotlib-users] Arbitrary artist data on SVG elements

Joshua Klein mobiusklein at gmail.com
Sat Apr 1 22:39:46 EDT 2017


5 could just be folded into 4 just by writing defs as raw XML. As for
validating the extra content XML, it’s possible to validate the markup
syntax, but it’s not easy to validate the SVG semantics of the markup.
Syntactic validation can be carried out just using the standard library’s
xml.etree.ElementTree.fromstring for detection, wrapped in a try-except to
catch the initial error and raise a more informative error.
​

On Sat, Apr 1, 2017 at 10:02 PM, Thomas Caswell <tcaswell at gmail.com> wrote:

> 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/20170401/8c6a01cc/attachment-0001.html>


More information about the Matplotlib-users mailing list