Arbitrary artist data on SVG elements

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>

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/2d693775/attachment-0001.html>

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/7bf53e49/attachment-0001.html>

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>

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>

The described functionality has been implemented and unit tests written to
exercise the new code-paths at
https://github.com/mobiusklein/matplotlib/tree/svg-extra-data, and is
passing the unit test suite on both Py2 and Py3. Before submitting a pull
request I want to document what new features are available, but I?m not
sure where is appropriate. The Figure.savefig and pyplot.savefig docstrings
don?t describe any particular keyword argument forwarding for individual
backends, and the only equivalent interface that is backend-specific that I
am aware of is PdfPages, which instantiates a whole new object that takes
over the figure-saving process.

Most of the names in backend_svg are unmentioned in the documentation
anyway since they don?t have docstrings, instead using something that looks
like doxygen comments above classes and functions. Should I migrate these
to use NumPy-style docstrings so that Sphinx can pick them up, and then
describe the additional features in the module-level and print_svg
docstrings?

Thank you,
Joshua Klein
?

···

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

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/20170403/15ccd151/attachment-0001.html>