Home > Enterprise >  How to display a Matplotlib chart with FastAPI/ Nextjs without saving chart locally?
How to display a Matplotlib chart with FastAPI/ Nextjs without saving chart locally?

Time:09-18

I am using Nextjs frontend and FastAPI backend for a website. I have an input form for an 'ethereum address' on the frontend and using the inputted address, I am generating a matplotlib chart in the backend that displays 'ethereum balance over time'. Now, I am trying to return this chart using FastAPI so I can display it on the frontend. I do not want to save the chart locally.

Here is my relevant code so far:

Frontend/ nexjs file called 'Chart.tsx'. 'ethAddress' in the body is capturing data inputted in the input form.

fetch("http://localhost:8000/image", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(ethAddress),
    }).then(fetchEthAddresses);

Backend python file that generates matplotlib chart called ethBalanceTracker.py

#Imports
#Logic for chart here

        plt.plot(times, balances)
        buf = BytesIO()
        plt.savefig(buf, format="png")
        buf.seek(0)

        return StreamingResponse(buf, media_type="image/png")

Backend python file using FastAPI called api.py

@app.get("/image")
async def get_images() -> dict:
    return {"data": images}

@app.post("/image")
async def add_image(ethAddress: dict) -> dict:

    test = EthBalanceTracker.get_transactions(ethAddress["ethAddress"])
    images.append(test)

I have tried the above code and a few other variants. I am using StreamingResponse because I do not want to save the chart locally. My issue is I cannot get the chart to display in localhost:8000/images and am getting an 'Internal Server Error'.

CodePudding user response:

You should pass buf.getvalue() as the content of your Response, in order to get the bytes containing the entire contents of the buffer. Additionally, you shouldn't be using StreamingResponse, if the entire image data are already loaded into memory (in this case, in an in-memory bytes buffer), but rather return a Response directly and set the Content-Disposition header, so that the image can be viewed in the browser, as described in this answer, as well as this and this answer. If you use Fetch API or Axios to fetch the image, please have a look at this answer on how to display the image on client side. You could also use FastAPI/Starlette's BackgroundTasks to close the buffer after returning the response, in order to release the memory, as described here. Example:

import io
import matplotlib
matplotlib.use('AGG')
import matplotlib.pyplot as plt
from fastapi import FastAPI, Response, BackgroundTasks

app = FastAPI()

def create_img():
    plt.rcParams['figure.figsize'] = [7.50, 3.50]
    plt.rcParams['figure.autolayout'] = True
    plt.plot([1, 2])
    img_buf = io.BytesIO()
    plt.savefig(img_buf, format='png')
    plt.close()
    return img_buf
    
@app.get('/')
def get_img(background_tasks: BackgroundTasks):
    img_buf = create_img()
    background_tasks.add_task(img_buf.close)
    headers = {'Content-Disposition': 'inline; filename="out.png"'}
    return Response(img_buf.getvalue(), headers=headers, media_type='image/png')

On a different note, if you are gettting the following warning with using matplotlib:

UserWarning: Starting a Matplotlib GUI outside of the main thread will likely fail.
WARNING: QApplication was not created in the main() thread.

this is because matplotlib is not thread-safe, and most GUI backends require being run from the main thread (this is actually a warning from Qt library itself). To avoid getting that warning, you can simply cahnge to a non-GUI backend—since you don't even need one, as you are displaying the image in the user's browser on client side—using matplotlib.use(), as shown in the Backends documentation and in the example above (Note: matplotlib.use() must be used before importing pyplot). The AGG used in the example above is a backend that renders graphs as PNGs.

  • Related