Ask Your Question
2

Outputting SVG source of a plot

asked 2020-08-02 10:36:30 -0600

StevenClontz gravatar image

Given a plot, how do I return the source code of its SVG representation? (Not save the SVG to disk.)

E.g. this saves to disk:

plot_slope_field(sin(x+y) + cos(x+y), (x,-3,3), (y,-3,3)).save('foo.svg')

I want something like:

plot_slope_field(sin(x+y) + cos(x+y), (x,-3,3), (y,-3,3)).svg_source()

which returns a string containing the source of the SVG.

edit retag flag offensive close merge delete

Comments

1

The real engine behind this is matplotlib's "savefig" function. It explicitly also allows a "file-like" object (in which case the "file" format must be specified explicitly, because there's no file name extension to infer it from). With that, you'd be able to write to a StringIO object. Probably using a temporary file is easier, though, and it might be the case that the wrapping that sage has done loses the ability to use a file-like object rather than a file name.

nbruin gravatar imagenbruin ( 2020-08-03 03:08:55 -0600 )edit

It's a shame that there's no (optional) keyword argument in Sage ala .save('foo.xml',file_format='svg') that lets you explicitly ask for a desired file format.

StevenClontz gravatar imageStevenClontz ( 2020-08-03 17:50:19 -0600 )edit

It would be totally doable to implement that. Sage plotting (at least the 2d stuff) is all wrapping matplotlib, so whatever is available there can be exposed fairly straightforwardly in sage as well. It just needs someone willing and able to do the job.

nbruin gravatar imagenbruin ( 2020-08-03 20:31:34 -0600 )edit

2 answers

Sort by ยป oldest newest most voted
2

answered 2020-08-02 15:20:01 -0600

slelievre gravatar image

updated 2020-08-02 17:40:29 -0600

Certain classes of Sage objects may provide methods to return svg or tikz representations as strings.

For plots such as the one in the question though, plotting and saving are delegated to matplotlib, who produces the svg string. No direct method provides the svg string.

The obvious workaround is to save to a file, and then read the file into a string.

To illustrate, define a function and plot the corresponding slope field:

sage: f = lambda x, y: sin(x + y) + cos(x + y)
sage: p = plot_slope_field(f, (-3, 3), (-3, 3))

Save the plot into a file (see note at the end of this answer):

sage: filename = 'slope_field.svg'
sage: p.save(filename)

Read the saved file:

sage: with open(filename, 'r') as f:
....:     s = f.read()

Check the result by printing an initial and a final fragment:

sage: print(s[:155], "...", s[-30:], sep='\n')
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
...
  </clipPath>
 </defs>
</svg>

Note: to create the file as a temporary file that will get cleaned up automatically when Sage exits:

sage: filename = tmp_filename(ext='.svg')

It is of course also possible to delete the file oneself, without waiting for Sage to exit:

sage: os.remove(filename)
edit flag offensive delete link more
2

answered 2020-08-02 19:31:55 -0600

StevenClontz gravatar image

As an illustration of @slelievre's answer, I wrote a couple functions to read in the SVG (or other graphics) source to convert the result into Base64.

def base64(obj, file_format="svg"):
    """
    Generates Base64 encoding of the graphic in the requested file_format.
    """
    if not isinstance(obj,Graphics):
        raise TypeError("Only graphics may be encoded as base64")
    if file_format not in ["svg", "png"]:
        raise ValueError("Invalid file format")
    filename = tmp_filename(ext=f'.{file_format}')
    obj.save(filename)
    with open(filename, 'rb') as f:
        from base64 import b64encode
        b64 = b64encode(f.read()).decode('utf-8')
    return b64

def data_url(obj, file_format="svg"):
    """
    Generates Data URL representing the graphic in the requested file_format.
    """
    b64 = base64(obj, file_format=file_format)
    if file_format=="svg":
        file_format = "svg+xml"
    return f"data:image/{file_format};base64,{b64}"

f = lambda x, y: sin(x + y) + cos(x + y)
p = plot_slope_field(f, (-3, 3), (-3, 3))
du = data_url(p,file_format="png")
from IPython.core.display import display, HTML
display(HTML(f"<img src='{du}'>"))
print(f"<img src='{du}'>")
edit flag offensive delete link more

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools

1 follower

Stats

Asked: 2020-08-02 10:36:30 -0600

Seen: 92 times

Last updated: Aug 02