Save plots as .PNG image

Streamlit is a great tool, but I find it misses some very important features. One of them is the possibility to export a page as HTML. This would allow to have a copy of all plots, and all the text needed to generate reports and PDFs.
A further possibility would have been given adding an id or at least a class to streamlit plots, such as st.line_chart, in order to manage it in pure javascript behind the curtains. But alas the are not available.

To overcome this issue, I need to save plot as .PNG image, with the preference for echarts ones (for Sankey they are a lot better than Plotly’s ones).

Even if my problem is a multifaceted one, even answering to just one of my questions would provide me a big help.

For the curious:

  • I am using echarts because in some cases (i.e. Sankey plot) they are well made.
  • I use streamlit_raw_echarts instead of streamlit_echarts because it provides the possibility to get the plot data using echarts’ getDataURL().

Here is a minimal working code:

import streamlit as st
from streamlit_raw_echarts import st_echarts as st_echarts_raw
import base64

st.subheader("Start")

placeholder = st.empty()
with placeholder.container():
   st.write("Create plot...")

   option = {
      "xAxis": {
         "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
      },
      "yAxis": {},
      "series": [
         {
            "type": "bar",
            "data": [23, 24, 18, 25, 27, 28, 25]
         },
         {
            "type": "bar",
            "data": [26, 24, 18, 22, 23, 20, 27]
         }
      ]
   }
   
   img = st_echarts_raw(option, returnData={})
   
   st.write("...plot created")

   if img:
      st.write(img[:60]+ "... and so on")
      imgdata = base64.b64decode(img[22:])
      with open("image.png", 'wb') as f:
         f.write(imgdata)
      st.success("and saved! :grinning:")
   else:
      st.error("Ops, no image to save :confused:")
      
# placeholder.empty()  <==== removing the comment image is not saved 

st.subheader("End")
   

Here are the questions

  1. Is there any way to save an image programmatically after it has been created using plotly/bokeh/etc from streamlit, with or without other libraries? Not using a download link! I need to embed the image into a report, and cannot ask the user to save the images clicking on a button.

  2. The plot must be visible in order to be saved. Removing the comment in placeholder.empty() the image is not saved at all. According to my logic, it should be saved and then removed from the placeholder BEFORE being visualized.
    Is it possible to create an invisible plot in background?

  3. Sometimes the plot is incomplete when saved.
    This is what is visualized:
    canvas

and this is what is saved:

It happens with echarts Barcharts, while echarts Sankeys are ok (even if the style must be slightly manipulated because not all options, such as transparency, are used by streamlit_raw_echarts)

Versions:
Streamlit 1.19
Python 3.8.2
Ubuntu 20.04
Tried with the latest versions of Firefox, Chrome, Brave Browsers.

Thanks in advance

I am not familiar with echarts but this might be of help…

Yes, each ploting library has it’s own method to export a plot as an static image. The image will be saved in the server you’re running streamlit though, users will have to download that with a button. Files should not appear automatically in an user’s computer without their consent :sweat_smile:

Creating a figure does not imply that it must to be shown in your app. You can create the figure object, export it as PNG and never call the streamlit function that brings it to the webpage.

Here is an example using plotly

import streamlit as st
import plotly.graph_objects as go

st.subheader("🎨 Plotting and export as static image")

placeholder = st.empty()

x = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
y1 = [23, 24, 18, 25, 27, 28, 25]
y2 = [26, 24, 18, 22, 23, 20, 27]

with placeholder.container():

    fig = go.Figure()

    for i,(y,c) in enumerate(zip([y1, y2], ["red", "blue"])):
        fig.add_trace(
            go.Bar(
                x=x, y=y, name=f"Bar {i}", marker_color=c
            )
        )

    if st.checkbox("**Show figure from `plotly`?**"):
        st.plotly_chart(fig, use_container_width=True)
   
    my_saved_image = "fig1.png"
    fig.write_image(my_saved_image)

    if st.checkbox("**Show the exported PNG image?**"):
        st.image(my_saved_image)
4 Likes

Afaik the bokeh export will probably not work on streamlit cloud or will cause much more hassle, since it is based on selenium, webdriver and a (headless) browser.
But matplotlib and plotly image exports should work on streamlit cloud to the best of my knowledge.
And regarding the plotly export you probably have to install the kaleido package as well.

1 Like

You are right about bokeh :melting_face:. In the case of plotly, the kaleido package is required but installing it via pip is pretty simple.

1 Like

I have problems when trying to save plots; the one was built by st.altair_chart, and another one with st.components.v1.html

Do you know how to save these plots into jpg/png file?

I would recommend using altair’s built-in functionality for exporting a chart as an image:
https://altair-viz.github.io/user_guide/saving_charts.html#png-svg-and-pdf-format

As for something you did with st.components.v1.html I would look for a Python library for converting HTML to an image. For example, imgkit · PyPI (though I have never used it and can’t attest to any quality).