Skip to content

saemeon/dash-capture

Repository files navigation

PyPI Python License Plotly Dash Ruff uv ty prek

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.

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

About

dash-capture

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages