Examples

All examples live under the examples/ directory. Run them with:

uv sync -U --all-groups --all-extras
cd examples && uv run python run_example.py

Or directly:

# Starlette (via daphne/uvicorn)
cd examples/starlette && uv run daphne -p 8000 draw_idle:app

# Quart
cd examples/quart && uv run python basic.py

Starlette

Single standalone figure (draw_idle.py)

Demonstrates draw_idle with an interactive keyboard event: pressing Enter swaps the colors of two scatter plots.

"""
Shows draw_idle(...) working with webagg and webaggext --- pressing enter swaps the
colors of the two scatter plots.

Flags: USE_BASIC_WEBAGG_BACKEND
"""
import os
from matplotlib import pyplot as plt

from starlette.applications import Starlette
from starlette.routing import Route

from mplbed import mplbed_starlette


use_basic_webagg_backend = "USE_BASIC_WEBAGG_BACKEND" in os.environ


def page(request):
    fig, (ax1, ax2) = plt.subplots(nrows=2)

    x = [0, 1, 2]

    coll1 = ax1.scatter(x, x, c=["r", "g", "b"])
    coll2 = ax2.scatter(x, x, c=["b", "g", "r"])

    def accept(event):
        match event.key:
            case "enter":
                # swap facecolors
                fc1 = coll1.get_facecolors()
                fc2 = coll2.get_facecolors()
                coll1.set_facecolors(fc2)
                coll2.set_facecolors(fc1)
                fig.canvas.draw_idle()
            case _:
                pass

    fig.canvas.mpl_connect("key_press_event", accept)
    return mplbed_starlette.figure_standalone(fig)


app = Starlette(
    debug=True,
    routes=[Route('/', page)],
)
mplbed_starlette.setup(app, use_webaggext_backend=not use_basic_webagg_backend)

Two embedded figures (embed2_raw.py)

Embeds two figures side-by-side in a single page using raw string templates.

"""
Creates two embedded figures using raw string templates using Starlette/mplbed/string formatting.
"""
from matplotlib.figure import Figure

from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Route

from mplbed import mplbed_starlette, raw_html


def homepage_template(*, head, fig1, fig2):
    return f"""<!DOCTYPE html>
<html lang="en">
  <head>
    {head}
    <title>matplotlib</title>
  </head>

  <body>
    <div style="display: flex">
      <div>
        <h2>Figure 1</h2>
        {fig1}
      </div>
      <div>
        <h2>Figure 2</h2>
        {fig2}
      </div>
    </div>
  </body>
</html>
"""


def create_figure():
    import numpy as np
    fig = Figure()
    ax = fig.add_subplot()
    t = np.arange(0.0, 3.0, 0.01)
    s = np.sin(2 * np.pi * t)
    ax.plot(t, s)
    return fig


def homepage(request):
    fig1 = create_figure()
    fig2 = create_figure()
    return Response(
        homepage_template(
            head=raw_html.head_content(),
            fig1=raw_html.figure_html(fig1),
            fig2=raw_html.figure_html(fig2),
        ),
        media_type='text/html'
    )


app = Starlette(
    debug=True,
    routes=[Route('/', homepage)],
)
mplbed_starlette.setup(app)

Manual routing (mount_app.py)

Shows how to manually create the mplbed Starlette sub-app and mount it at a custom prefix instead of using setup().

"""
Shows manual creation and routing of the `mplbed` Starlette app.
"""
from matplotlib.figure import Figure

from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Route, Mount

from mplbed import raw_html, mplbed_starlette, mplbed_app_factory


def homepage_template(*, head, fig1):
    return f"""<!DOCTYPE html>
<html lang="en">
  <head>
    {head}
    <title>Sine wave plot</title>
  </head>

  <body>
    <h2>Sine wave plot</h2>
    {fig1}
  </body>
</html>
"""


def create_figure():
    import numpy as np
    fig = Figure()
    ax = fig.add_subplot()
    t = np.arange(0.0, 3.0, 0.01)
    s = np.sin(2 * np.pi * t)
    ax.plot(t, s)
    return fig


def homepage(request):
    fig1 = create_figure()
    return Response(
        homepage_template(
            head=raw_html.head_content(),
            fig1=raw_html.figure_html(fig1),
        ),
        media_type='text/html'
    )


mplbed_app = mplbed_app_factory()
MPLBED_PREFIX = "/mymplbedprefix"
app = Starlette(
    debug=True,
    routes=[Route('/', homepage), Mount(MPLBED_PREFIX, app=mplbed_app, name="webagg")],
)
mplbed_starlette.setup(app, prefix=MPLBED_PREFIX, mplbed_starlette_app=mplbed_app, manage_routing=False)

MNE integration (integrate_mne.py)

Embeds an interactive MNE raw-data plot in a Starlette page.

"""
Shows how an existing application can be integrated with mplbed. This example
uses MNE, an EEG/MNE library, to create a figure and then embeds it in a
Starlette application.
"""
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Route

from mplbed import mplbed_starlette, raw_html

import mne


def homepage_template(*, head, fig):
    return f"""<!DOCTYPE html>
<html lang="en">
  <head>
    {head}
    <title>matplotlib</title>
  </head>

  <body>
    <div style="display: flex">
      <div>
        <h2>Figure</h2>
        {fig}
      </div>
    </div>
  </body>
</html>
"""


def homepage(request):
    sample_data_folder = mne.datasets.sample.data_path()
    sample_data_raw_file = (
        sample_data_folder / "MEG" / "sample" / "sample_audvis_filt-0-40_raw.fif"
    )
    raw = mne.io.read_raw_fif(sample_data_raw_file)
    fig = raw.plot(show=False)

    return Response(
        homepage_template(
            head=raw_html.head_content(),
            fig=raw_html.figure_html(fig, on_close="msg_disable")
        ),
        media_type='text/html'
    )


app = Starlette(
    debug=True,
    routes=[Route('/', homepage)],
)
mplbed_starlette.setup(app)

Quart

Inline, iframe, and popup figures (basic.py)

Shows three embedding styles in a single Quart/Jinja2 app: an inline figure, a figure loaded inside an <iframe>, and a figure that opens as a modal popup.

"""
Shows inline figures, external figures and popups using mplbed/Quart/jinja2.
"""

from quart import Quart, render_template
from mplbed import mplbed_quart
from mplbed import safe_html


app = Quart(__name__)
mplbed_quart.setup(app)


def mk_plot():
    import matplotlib.pyplot as plt
    import numpy as np

    t = np.arange(0.0, 2 * np.pi, 0.01)
    s = np.sin(t)

    fig, ax = plt.subplots()
    ax.plot(t, s)

    return fig


@app.route('/')
async def index():
    return await render_template(
        "index.html",
        inline_fig=safe_html.figure_html(mk_plot()),
        external_iframe=mplbed_quart.iframe_for("figure"),
        open_inline_popup=safe_html.figure_html(mk_plot(), target="modal")
    )


@app.route('/figure')
async def figure():
    return mplbed_quart.figure_standalone(mk_plot())


if __name__ == "__main__":
    import os
    app.run(debug=True, port=int(os.environ.get("PORT", 8000)))

The Jinja2 templates used by this example:

templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4" crossorigin="anonymous"></script>
    <title>{% block title %}{% endblock %}</title>
    {{ mplbed_head(False) }}
</head>
<body style="padding: 1em; font-family: sans-serif;">
    <div id="content">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

templates/index.html

{% extends "base.html" %}

{% block title %}Home{% endblock %}
        
{% block content %}
    <h2 style="font-size: 2em;">Inline figure</h2>
    {{ inline_fig }}

    <hr style="margin: 5em 0;">

    <h2 style="font-size: 2em;">External iframe</h2>
    {{ external_iframe }}

    <hr style="margin: 5em 0;">

    <h2 style="font-size: 2em;">Open inline popup</h2>
    {{ open_inline_popup }}
{% endblock %}