dash-capture
Plotly figures in Dash are rendered by JavaScript in the browser — the Python server never holds the chart as pixels. dash-capture bridges this gap by triggering the capture directly in the running browser, with no server-side headless browser (Chrome, Playwright, webshot2) required. The result is delivered to Python for post-processing, custom rendering, and download.
Installation
pip install dash-capture
Optional extras for built-in decoration renderers:
pip install 'dash-capture[pil]' # bordered / titled / watermarked
Usage
Default — one-line wizard, no setup
from dash_capture import capture_graph
# Returns an html.Div — place it next to your dcc.Graph
capture_graph("my-graph", trigger="Export")
The default renderer is a zero-dependency passthrough: the wizard shows just Generate + Download. Clicking the trigger opens a modal with the live preview and a PNG / JPEG / SVG download.
Plotly chart capture
from dash_capture import capture_graph, plotly_strategy
capture_graph(
"my-graph",
trigger="Export",
strategy=plotly_strategy(
strip_title=True,
strip_legend=True,
strip_margin=True,
format="png", # or "jpeg", "webp", "svg"
),
)
Strip patches remove chart decorations before capture without touching the live chart.
Any DOM element (table, custom widget) — html2canvas
from dash_capture import capture_element
capture_element("my-data-table", trigger="Capture table")
capture_element defaults to html2canvas_strategy() and works with any Dash component that has an id.
Hover toolbar — icon button that appears on mouse-over
For elements without a Plotly modebar, with_hover_toolbar wraps any component in a
floating toolbar that appears on hover. Use icon_button to render a SvgIcon
as a normal Dash button:
from dash_capture import with_hover_toolbar, icon_button, SvgIcon, capture_element
download_icon = SvgIcon(
path="M350 100 H650 V450 H800 L500 750 L200 450 H350 Z M200 820 H800 V900 H200 Z"
)
btn = icon_button(download_icon, "cap-btn", tooltip="Export table")
# with_hover_toolbar returns the same type as its input — full callback transparency
wrapped_table = with_hover_toolbar(table, [btn])
wizard = capture_element("my-table", trigger=btn, filename="table.png")
app.layout = html.Div([wrapped_table, wizard])
with_hover_toolbar uses dash-wrap internally — the returned object is
callback-transparent, so callbacks written against table keep working on
wrapped_table unchanged. The hover CSS is injected into <head> via a
clientside_callback; no assets file or index_string patching needed.
Pass display="block" for full-width elements:
wrapped = with_hover_toolbar(my_div, [btn], display="block")
Built-in PIL renderers (dash-capture[pil])
from dash_capture import capture_graph
from dash_capture.pil import titled, bordered, watermarked
# Title bar above the captured chart
capture_graph("my-graph", renderer=titled)
# Colored border
capture_graph("my-graph", renderer=bordered)
# Diagonal watermark
capture_graph("my-graph", renderer=watermarked)
The wizard auto-generates form fields (text input, color picker, dropdown) from each renderer's type hints, so users can edit title, color, width, etc. before downloading.
Custom renderer
Define a function that takes _target (file-like) and _snapshot_img (callable returning raw PNG bytes). Type-hinted parameters become auto-generated form fields in the wizard.
from dash_capture import capture_graph, renderer
@renderer
def my_renderer(_target, _snapshot_img, title: str = "", dpi: int = 150):
png = _snapshot_img()
# post-process: add a watermark, corporate frame, etc.
_target.write(png)
capture_graph("my-graph", renderer=my_renderer)
The @renderer decorator validates the magic parameter names at definition time. A typo like _snaphot_img raises ValueError with a "did you mean ...?" hint instead of silently failing at runtime.
Low-level — wire capture to your own UI
from dash import Input
from dash_capture import capture_binding, plotly_strategy
binding = capture_binding(
"my-graph",
strategy=plotly_strategy(strip_title=True),
trigger=Input("my-btn", "n_clicks"),
)
# Place binding.store in the layout
# React to binding.store_id to get the base64 PNG
Strategies
| Strategy | Method | Use case |
|---|---|---|
plotly_strategy() |
Plotly.toImage() |
Plotly charts — exact resolution |
html2canvas_strategy() |
html2canvas | Any DOM element (tables, divs) |
canvas_strategy() |
canvas.toDataURL() |
Raw <canvas> elements |
plotly_strategy() accepts strip flags (strip_title, strip_legend, strip_annotations, strip_axis_titles, strip_colorbar, strip_margin) and format. For per-export width / height / scale, declare capture_width: int / capture_height: int / capture_scale: float parameters on your renderer — they get plumbed into Plotly.toImage() automatically.
capture_* parameter resolution
The same capture_width / capture_height / capture_scale magic params work with any strategy that consumes them (plotly_strategy, html2canvas_strategy, dygraph_strategy, …). Three ways to provide values:
| How | Where the value comes from | Use case |
|---|---|---|
Omit (no field_specs, no capture_resolver) |
The element's current size in the browser | Fast, sensible default — works without any config. |
fixed(value) in field_specs |
Inlined as a JS constant | Pin a specific export size at import time. |
capture_resolver=fn |
Computed server-side from form fields | Drive sizes from user input, with snapshot caching keyed by the resolved options. |
from dash_fn_form import fixed
from dash_capture import capture_graph
# (1) Omit — capture at the live element's current size:
capture_graph(graph, renderer=my_renderer)
# (2) fixed — pin an export size:
capture_graph(graph, renderer=my_renderer,
field_specs={"capture_width": fixed(1200), "capture_height": fixed(600)})
# (3) capture_resolver — drive from form fields:
capture_graph(graph, renderer=my_renderer,
capture_resolver=lambda width, height, **_:
{"capture_width": width, "capture_height": height})
Pre-filling fields from the live figure
FromPlotly reads a value from the running Plotly figure to pre-populate auto-generated form fields:
from dash import dcc
from dash_capture import capture_graph, FromPlotly, renderer
graph = dcc.Graph(id="my-graph", figure=fig)
@renderer
def export(_target, _snapshot_img, title: str = "", sources: str = ""):
_target.write(_snapshot_img())
capture_graph(
graph,
renderer=export,
field_specs={"title": FromPlotly("layout.title.text", graph)},
)
License
MIT
Metadata
Release files for dash-capture 0.0.15
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dash_capture-0.0.15.tar.gz | 339.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dash_capture-0.0.15-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 432.3 kB
Release files / dash_capture-0.0.15.tar.gz
| Download URL | dash_capture-0.0.15.tar.gz |
|---|---|
| Size | 339.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
16b9a2036995fcdd7a64acc4bd786ef30725060a554cbe94f5c63d6791aae1c1
|
|
BLAKE2b-256 checksum How to use checksums |
a3faf794afa126a4db6f62c4c23f5acec79a38dd759b448ad125fe0758e5f8d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 7, 2026.
Transparency logRelease files / dash_capture-0.0.15-py3-none-any.whl
| Download URL | dash_capture-0.0.15-py3-none-any.whl |
|---|---|
| Size | 92.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fed899a59a5f824d8e72e1af5205f6037046f90e392e8782d93cf7bf7ad3dc4e
|
|
BLAKE2b-256 checksum How to use checksums |
4dee0a2a460f687b5707ba81a8dd47ca56c8d9108b135faef4ebd6a462bf3ee2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 7, 2026.
Transparency log