[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