Elements and Assets#

Element Catalog#

coco_pipe.report.elements provides 19 reusable HTML primitives. Every section adder in the report module — and any custom adder you write — ultimately renders one or more of these. The primitives are data-first: they accept Python objects (DataFrames, Plotly figures, strings, byte buffers) and emit standalone HTML.

All elements share a common contract:

  • Subclass of Element.

  • render() -> str returns an HTML fragment.

  • collect_payload(registry) (optional) pushes heavy data into the global registry under a UUID; the rendered fragment references that UUID via data-id.

1. Catalog at a Glance#

Element

Use

HtmlElement

Raw HTML string passthrough.

ImageElement

Base64-encoded image from bytes, file, Path, or a matplotlib Figure.

PlotlyElement

Plotly figure with lazy hydration.

TableElement

Static HTML table from a DataFrame / dict / list.

InteractiveTableElement

Searchable, sortable, paged table with CSV export.

MetricsTableElement

Table with per-column “best” highlighting.

StatCardElement

Big-number stat with delta and unit.

CalloutElement

Boxed info/warning/error/success message.

CodeBlockElement

Syntax-tagged <pre><code> with copy.

MarkdownElement

Markdown → HTML (graceful fallback).

BadgeElement

Pill label with color.

ProgressBarElement

Inline progress bar.

TimelineElement

Ordered list of timestamped events.

TabsElement

Tabbed container holding other elements.

AccordionElement

<details>-backed collapsible section.

ColumnsElement

CSS-grid row of side-by-side elements.

ContainerElement

Base class for any container; itself adds add_element / add_markdown.

DownloadAssetElement

<a download> button backed by inline base64 payload (CSV, JSON, bytes).

Element

Abstract base; subclass for custom widgets.

2. Data Elements#

2.1 HtmlElement#

Bare HTML wrapper — bypass the typed elements when you need a one-off fragment.

from coco_pipe.report.elements import HtmlElement

sec.add_element(HtmlElement("<div class='text-xs'>raw HTML</div>"))

2.2 ImageElement#

Embeds any image as a base64 data URI. Accepts:

  • bytes — raw image bytes (any format the browser supports).

  • str or Path — read from disk.

  • Figure — saved at 150 DPI to PNG.

from coco_pipe.report.elements import ImageElement

ImageElement(matplotlib_fig, caption="ROC curve", width="600px")
ImageElement(Path("logo.png"))
ImageElement(raw_bytes)         # accepts any browser-supported image format

Every image also emits an inline “Download PNG” link so the reader can extract the asset.

2.3 PlotlyElement#

Plotly figure with lazy hydration: the figure JSON is collected into the global payload registry; the rendered HTML emits a <div class="lazy-plot" data-id="…"> placeholder. The browser hydrates each placeholder when it scrolls into view.

import plotly.graph_objects as go
from coco_pipe.report.elements import PlotlyElement

fig = go.Figure(data=[go.Scatter(x=[1, 2], y=[3, 4])])
sec.add_element(PlotlyElement(fig, height="400px"))

PlotlyElement understands Plotly’s optimization that base64-encodes large numeric arrays ({"dtype": "f8", "bdata": "…"}) and decodes them so the rendered JSON is roundtrip-stable.

2.4 TableElement#

Static HTML table. Accepts a DataFrame, a dict (rendered as one-row key/value), a list of records, or a dict of lists.

from coco_pipe.report.elements import TableElement

TableElement(scores_df, title="Per-fold scores")
TableElement({"Accuracy": 0.84, "ROC AUC": 0.91})
TableElement([{"x": 1}, {"x": 2}])

Tables include an inline “Download CSV” button.

2.5 InteractiveTableElement#

Same input as TableElement but client-side searchable, sortable, paged, with per-column “selector” dropdowns. Useful for exploratory tables with hundreds of rows.

from coco_pipe.report.elements import InteractiveTableElement

InteractiveTableElement(
    big_df,
    selector_columns=["dataset", "model"],
    default_sort={"column": "score", "direction": "desc"},
    page_size=25,
)

2.6 MetricsTableElement#

A TableElement that highlights the “best” value in each metric column. Pass higher_is_better= to control direction per column (True / False / per-column list).

from coco_pipe.report.elements import MetricsTableElement

MetricsTableElement(
    scores_df,
    highlight_cols=["accuracy", "log_loss"],
    higher_is_better=["accuracy"],          # lower is better for log_loss
)

3. UI Primitives#

3.1 StatCardElement#

Large-number card with unit, delta, and a color tag. Use Report.add_summary_card() to render a row of these at the top of a report.

from coco_pipe.report.elements import StatCardElement

StatCardElement("Accuracy", 0.95, unit="%", delta="+2.1%", color="green")

3.2 CalloutElement#

Boxed message with an icon. kind is one of "info", "warning", "error", "success".

from coco_pipe.report.elements import CalloutElement

CalloutElement("Result includes 3 outliers.", kind="warning",
               title="Outliers detected")

3.3 CodeBlockElement#

Syntax-tagged <pre><code> block. Adds a “Copy” button when copyable=True.

from coco_pipe.report.elements import CodeBlockElement

CodeBlockElement(
    'reducer.fit(X, y=labels)',
    language="python",
    title="Reproducer",
    copyable=True,
)

3.4 MarkdownElement#

Markdown → HTML using the markdown package when available; falls back to <pre class="whitespace-pre-wrap"> rendering when not.

from coco_pipe.report.elements import MarkdownElement

MarkdownElement("# Notes\n* Item 1\n* Item 2")

3.5 BadgeElement / ProgressBarElement / TimelineElement#

from coco_pipe.report.elements import (
    BadgeElement, ProgressBarElement, TimelineElement,
)

BadgeElement("Experimental", color="red")
ProgressBarElement(value=75, max_value=100, label="Accuracy", color="green")
TimelineElement([
    {"title": "Start",  "time": "10:00", "description": "Fit reducer"},
    {"title": "End",    "time": "10:42", "description": "Score complete",
     "status": "green"},
])

4. Layout Primitives#

4.1 TabsElement#

Tabbed container; keys are tab labels, values are any element.

from coco_pipe.report.elements import TabsElement, PlotlyElement

TabsElement({
    "ROC":    PlotlyElement(roc_fig),
    "PR":     PlotlyElement(pr_fig),
    "Calibr": PlotlyElement(cal_fig),
})

4.2 AccordionElement#

Collapsible section backed by <details>.

from coco_pipe.report.elements import AccordionElement, CodeBlockElement

acc = AccordionElement("Show training script", open=False)
acc.add_element(CodeBlockElement(open("train.py").read(), language="python"))
sec.add_element(acc)

4.3 ColumnsElement#

CSS-grid row. Sub-elements are placed side by side.

from coco_pipe.report.elements import ColumnsElement, PlotlyElement

ColumnsElement(
    [PlotlyElement(roc_fig), PlotlyElement(pr_fig)],
    cols=2, gap="gap-4",
)

Section.add_columns() is a shortcut for the same pattern.

4.4 ContainerElement#

Base class. Section and Report inherit from it. Useful when subclassing for custom containers.

5. Download Helpers#

DownloadAssetElement embeds binary or text payloads as base64 data and produces a <a download> link. Useful for shipping the source data alongside the chart.

from coco_pipe.report.elements import DownloadAssetElement

DownloadAssetElement(
    data=csv_bytes,
    filename="scores.csv",
    mime_type="text/csv",
    label="Download per-fold scores",
    style="gray",
)

The payload is captured into the global registry and decoded on-demand when the user clicks; the HTML stays small.

6. Custom Elements#

To add a new element type, subclass Element:

from coco_pipe.report.elements import Element

class KPIElement(Element):
    def __init__(self, label, value):
        self.label, self.value = label, value

    def render(self) -> str:
        return (
            f"<div class='kpi'>"
            f"<span class='label'>{self.label}</span>"
            f"<span class='value'>{self.value}</span>"
            f"</div>"
        )

If your element holds heavy data, override collect_payload and use data-id="…" in the rendered fragment. See PlotlyElement and DownloadAssetElement for canonical implementations.

See Custom Elements and Adders for binding custom adders to Report.

JavaScript Asset Modes#

Rendered reports rely on three JavaScript bundles:

  • Plotly — interactive plot rendering.

  • Tailwind Play CDN — runtime CSS compiler driven by the markup.

  • pako — gzip decompression of the embedded data payload.

How those bundles are served is controlled by the asset_urls constructor argument on Report and every factory in coco_pipe.report.api.

1. Three Modes#

asset_urls argument

Behavior

None (default)

<script src="…cdn URL…"> tags. Requires network.

dict

CDN defaults merged with the user’s overrides.

"inline"

The bundles are downloaded once (with caching) and embedded directly in <script>...</script> tags. Resulting HTML opens fully offline.

The current mode is exposed on the report as Report.asset_mode ("cdn" / "custom" / "inline") and surfaced in the Run Info drawer.

2. CDN Mode (Default)#

from coco_pipe.report import Report

report = Report(title="Online")
report.save("online.html")

The rendered HTML loads:

  • https://cdn.plot.ly/plotly-2.27.0.min.js

  • https://cdn.tailwindcss.com

  • https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js

Pros: nothing to host, small HTML. Cons: requires network when the report is opened.

3. Self-Hosted URLs#

Pass a dict to point at your own bundle URLs:

report = Report(
    title="Self-hosted",
    asset_urls={
        "plotly": "/static/plotly-2.27.0.min.js",
        "tailwind": "/static/tailwind.min.js",
        "pako": "/static/pako-2.1.0.min.js",
    },
)

Only override the URLs you want to change; unspecified slots fall back to the CDN defaults. The mode is set to "custom".

Useful when your team intranet hosts vendored JS, or when you need a specific Plotly version different from the default.

4. Inline Mode (Fully Offline)#

report = Report(title="Air-gapped", asset_urls="inline")
report.save("standalone.html")

On the first run, get_vendored_contents() downloads the three bundles into ~/.cache/coco-pipe/report-assets/ and reads them as strings. The template then inlines them in <script>...</script> tags instead of <script src=...>. Subsequent runs skip the download and just read from cache.

The resulting HTML is fully self-contained — opens identically on a laptop with no internet, an air-gapped review machine, or a printed- PDF reference copy.

4.1 Cache location#

The cache directory defaults to ~/.cache/coco-pipe/report-assets/. Override it via environment variable when generating reports on a shared cluster:

export COCO_PIPE_REPORT_ASSET_CACHE=/scratch/coco-pipe-assets

4.2 Pre-warming the cache#

For automated pipelines running on a machine without internet, run the download step once on a machine that does:

from coco_pipe.report._assets import vendor_assets

cache_dir = vendor_assets()                        # downloads if missing
cache_dir = vendor_assets(force=True)              # re-download

Then ship the cache directory alongside the code base, set the env var to its path on the target machine, and asset_urls="inline" will use the local copies without ever touching the network.

4.3 Cost#

Inline mode adds roughly:

Bundle

Approximate size

Plotly

~3 MB minified

Tailwind Play CDN

~70 KB

pako

~50 KB

So a typical inline report grows by ~3-4 MB compared to a CDN report. Worth it when offline access matters; skip when it doesn’t.

5. Failure Modes#

Scenario

Behavior

Network unavailable in CDN mode

Report renders fine; opens blank or broken in the browser. Switch to "inline" to fix.

Network unavailable in inline mode

First call raises OSError (urllib). Pre-warm the cache via vendor_assets() on an online machine.

Unknown asset_urls value

Report._resolve_assets() raises TypeError at construction.

Cache directory not writable

First call raises OSError. Override location with COCO_PIPE_REPORT_ASSET_CACHE.

6. Asset Mode at a Glance — When to Use Which#

Use case

Recommended mode

Interactive notebook exploration

None (CDN) — smallest HTML, network always available.

Web-served reports

dict pointing at your CDN paths — control over the asset bundle versions.

Email / Slack-shared HTML

"inline" — recipients can open the file with no network access.

Long-term archive / publication

"inline" — frozen, self-contained record.

Air-gapped / cluster runs

"inline" + pre-warmed cache + env override.