Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,33 @@ jobs:
output/streamlit-verification/*.html
output/streamlit-startup-controls/
retention-days: 14
pandas-components:
name: pandas component report (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- run: |
python -m pip install --only-binary=:all: -r examples/pandas-components/requirements-check.txt
- name: Rebuild the source kit and inspect its actual saved PDFs
shell: bash
run: |
python tools/build_pandas_components.py
git diff --exit-code -- docs/assets/pandas-components/project.zip docs/assets/pandas-components/source.json
python tools/verify_pandas_components.py --out target/pandas-components
- uses: actions/upload-artifact@v4
if: always()
with:
name: pandas-component-report-${{ matrix.os }}
path: target/pandas-components/
retention-days: 14
matplotlib:
name: Matplotlib report (${{ matrix.os }})
strategy:
Expand Down Expand Up @@ -943,6 +970,14 @@ jobs:
- name: Materialize the verified published-wheel CSS evidence
run: python tools/prepare_css_corpus_site.py site
- run: python tools/check_site.py site
- name: Verify pandas component downloads and both standalone HTML exports
run: python tools/check_pandas_components_browser.py --site site --out output/pandas-components-browser
- uses: actions/upload-artifact@v4
if: always()
with:
name: pandas-components-browser-verification
path: output/pandas-components-browser/
retention-days: 14
- name: Verify the experimental guide and its visible downloads
run: python tools/check_component_preview_ui.py --site site --out output/component-preview-ui --browser chrome
- uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -1031,7 +1066,7 @@ jobs:
path: site
deploy:
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
needs: [build, accessible-showcase, showcase-authoring, component-notebook, page-band-notebook, complete-html, component-preview, bootstrap-preview, components, node-components, dotnet-components, aspnet, chinese, arabic, markdown, tagged-notice, rust-download, matplotlib, numbered-report, axum, lambda-starter, n8n-workflow, laravel-queue, laravel-platform, streamlit]
needs: [build, accessible-showcase, showcase-authoring, component-notebook, page-band-notebook, complete-html, component-preview, bootstrap-preview, components, node-components, dotnet-components, aspnet, chinese, arabic, markdown, tagged-notice, rust-download, matplotlib, pandas-components, numbered-report, axum, lambda-starter, n8n-workflow, laravel-queue, laravel-platform, streamlit]
runs-on: ubuntu-latest
permissions:
pages: write
Expand Down
9 changes: 9 additions & 0 deletions docs/assets/pandas-components/a11y-source.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"diagnostics": [],
"error_count": 0,
"errors": [],
"mode": "raise",
"ok": true,
"warning_count": 0,
"warnings": []
}
Binary file added docs/assets/pandas-components/html-exports.zip
Binary file not shown.
Binary file added docs/assets/pandas-components/page-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/pandas-components/page-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/pandas-components/page-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/pandas-components/project.zip
Binary file not shown.
18 changes: 18 additions & 0 deletions docs/assets/pandas-components/render-report.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"schema": "fullbleed.pandas_components_example.v1",
"fullbleed": "2.6.0a9",
"pandas": "3.0.6",
"rows": 40,
"totals": {
"products": 40,
"units": 6040,
"cents": 15023625,
"categories": 4
},
"page_count": 3,
"pdf_sha256": "426ac79dde41026a9640acf7e93bc9229c67b2070fe3bc221ba26f66153953a8",
"font_sha256": "29160a80ff49ddcab2c97711247e08b1fab27a484a329ce8b813d820dc559031",
"profile": "tagged",
"timestamp": "2026-10-20T00:00:00Z",
"validation_scope": "Source structural checks and native layout checks; no PDF/UA conformance claim."
}
Binary file added docs/assets/pandas-components/report.pdf
Binary file not shown.
19 changes: 19 additions & 0 deletions docs/assets/pandas-components/source.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"schema": "fullbleed.pandas_components_source.v1",
"fullbleed": "2.6.0a9",
"pandas": "3.0.6",
"zip_sha256": "d467cbf65b30ca2c62c63c063a5b0a41cde557ead5fa8ad34d0b269076c8ec1b",
"zip_bytes": 30386,
"files": {
"Inter-OFL.txt": "5b9321a4298cfeb6b34354164a1c3afc3db114569984c502b9b35d988fd58c57",
"LICENSE": "36a2115938f6e68cf0d87eccff7a6a3e57c17017e909cbd640d51aff1fa0c5a9",
"README.md": "63318aad3c51a6c83138909aff626190d0d984cec698b6888aa9b062b1bb3200",
"components.py": "af4f29fa8c111780f49f884dd389e03d8fc482aae1276c520484780dc1f69fa0",
"data.py": "000d71075dd56cd8f87056d3c996edc4702df0987266123f12bedd4c96aaed84",
"products.csv": "559901ddf863989e1498ac8a7fa725f2f77c7547232781c58b0e84b737b10265",
"report.py": "3a9cf616caa6dc8fd0f9894be00af1ce8c32cd795c3d281a82e0e82597ba7a73",
"requirements-check.txt": "4eadc7023aba8e289cb2e9b4f46eed8c350d9c55500f98c19d938010d46add8d",
"requirements.txt": "500618199fa41ef2d783c668ff566ef99af7273246a63fe15d73d55c134cc84c",
"verify.py": "f0cfa1d19a871b8604d178be66fe314d24af6831031ccdb63a3752cc2f138073"
}
}
39 changes: 39 additions & 0 deletions docs/assets/pandas-components/verification.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"ok": true,
"cases": {
"sample": {
"pages": 3,
"sha256": "426ac79dde41026a9640acf7e93bc9229c67b2070fe3bc221ba26f66153953a8",
"rows": 40
},
"one": {
"pages": 1,
"sha256": "e428990c8874e3951a799060cb3ec33ff176f30358107929e10d88663deda826",
"rows": 1
},
"mixed": {
"pages": 1,
"sha256": "01dd255a3909aa03e4feec2b14f4f6daa4dd69996241a0038c54609e7fb164b1",
"rows": 3
},
"many": {
"pages": 7,
"sha256": "da4094a1c8d64abbb305ba4fe3e6d936f1c044d3f9b9213a57a5aaa4429c6d34",
"rows": 120
}
},
"invalid_inputs_rejected": [
"empty",
"missing-column",
"duplicate",
"blank-product",
"missing-units",
"negative-units",
"fractional-cents",
"bad-date"
],
"fresh_process_pdf_identical": true,
"input_frames_unchanged": true,
"html_styled_and_unstyled_checked": true,
"scope": "Saved-PDF text, row order, repeated headings, page totals, page bounds, independent rasterization; no PDF/UA or browser-pagination claim."
}
5 changes: 5 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ changing content, fonts, or row counts.

## Gridline product report from pandas

For a component-authored design using the same data, try the
[pandas component report](guides/pandas-components.md): three pages, reusable
metrics, finalized page totals, and styled or unstyled HTML export, pinned to
Fullbleed **2.6.0a9**.

<div class="showcase-entry" markdown>
<figure markdown>
[![Gridline product report with summary metrics, orange SVG category bars, and a striped product ledger.](assets/pandas-report/report-1.png)](assets/pandas-report/report.pdf)
Expand Down
210 changes: 210 additions & 0 deletions docs/guides/pandas-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
---
title: Build pandas PDF reports with Python components
description: Turn a DataFrame into a paginated PDF with reusable Python components, typed styles, repeated table headers and page totals. Download the runnable example and both HTML export modes.
---

# pandas reports with Python components

Keep the data preparation in pandas and build the report from reusable Python
functions. This example turns the same fictional Gridline CSV used in the
[HTML/CSS pandas guide](pandas-to-pdf.md) into a new three-page design: summary
cards, a striped product ledger, repeated headers, and finalized page totals.

!!! warning "Pinned experimental API"
This project uses the published **Fullbleed 2.6.0a9** wheel and **pandas 3.0.6**.
The component API is experimental. Keep the exact pin while evaluating it;
the [HTML/CSS example](pandas-to-pdf.md) remains a separate stable-release path.

[Download the PDF](../assets/pandas-components/report.pdf){ .md-button .md-button--primary download="gridline-components.pdf" }
[Get the runnable source](../assets/pandas-components/project.zip){ .md-button download="fullbleed-pandas-components.zip" }
[Download both HTML exports](../assets/pandas-components/html-exports.zip){ .md-button download="fullbleed-pandas-html.zip" }

[![Gridline report with a forest-green title, three summary cards, an orange net-sales figure, and a striped product table.](../assets/pandas-components/page-1.png)](../assets/pandas-components/report.pdf)

[Preview page 2](../assets/pandas-components/page-2.png) ·
[Preview page 3](../assets/pandas-components/page-3.png) ·
[Inspect the verification record](../assets/pandas-components/verification.json) ·
[Source checksums](../assets/pandas-components/source.json)

The previews come from the actual PDF. All data and the Gridline organization are
fictional. This is a component-authored design using the earlier example's data;
the two designs do not claim pixel parity.

## Run the complete example

Extract the source ZIP and open its `fullbleed-pandas-components` directory. Use
Python 3.11 or later:

```sh
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
python -m pip install --only-binary=:all: -r requirements.txt
python report.py --previews
```

Open `output/report.pdf`. The command also writes styled HTML, unstyled HTML,
source structural diagnostics, pagination evidence, and optional PNG previews.
The published wheel includes the font and native renderer; PDF generation needs
no browser, system font, or remote asset.

```sh
python report.py --csv products.csv --title "The autumn edit." --period "Autumn 2026" --out output/autumn
```

## Bridge a DataFrame to a table

`data.py` validates the CSV and preserves its row order. `report.py` calls:

```python
Ledger(frame.to_dict(orient="records"))
```

Each `Column` callback receives one record. It can return text or another
component. Here is the core of the table in `components.py`:

```python
from fullbleed.ui.experimental import Column, DataTable, Strong, mm
from data import dollars, restock

def Ledger(records):
return DataTable(
records,
caption="Product ledger · all values in USD",
columns=(
Column("SKU", lambda row: row["sku"], width=mm(23)),
Column("Product", lambda row: Strong(row["product"])),
Column("Category", lambda row: row["category"], width=mm(23)),
Column("Units", lambda row: f'{row["units"]:,}', align="right", width=mm(19)),
Column("Net sales", lambda row: dollars(row["net_sales_cents"]), align="right", width=mm(30)),
Column("Last restock", lambda row: restock(row["last_restock"]), width=mm(30)),
),
class_name="ledger",
)
```

The complete file adds header, caption, and cell styles with `.with_parts()`.
The table supplies semantic column headers and repeats them across PDF pages.
`Flow` lets the document paginate; short metric cards use `Stack` internally.

## Give reusable components a styling API

The metric is a Python function. Its two named parts let callers style the value
or label without reaching through its implementation:

```python
from fullbleed.ui.experimental import (
Box, Paragraph, Stack, component, pt, style, token,
)
from components import Small

@component(parts=("value", "label"))
def Metric(value, label):
return Box(
Stack(
Paragraph(value, part="value", style=style(
font_size=pt(26), font_weight=700, line_height=1.15,
)),
Small(label, part="label"),
gap=pt(5),
),
style=style(background=token("paper"), padding=pt(12)),
)

sales = Metric("$150,236.25", "net sales · USD").with_parts(
value=style(color=token("accent")),
)
```

`Theme` defines the palette and shared typography. `styled(Paragraph, ...)`
creates the reusable `Small` and `Kicker` factories. Styles are immutable values;
an override produces a new component value.

### What does `class_name` do?

`class_name="ledger"` attaches the class to the table. It applies no declarations
by itself. The document supplies the selector and its styles:

```python
Document(
..., # report components
styles={
".ledger tbody tr:nth-child(even)": style(background="#f5f7f2"),
".folio": style(font_size=pt(8), color=token("muted")),
},
)
```

Use a component's `style=` or `.with_style()` for one instance, `styled()` for a
reusable factory, and `.with_parts()` for its declared public parts. Use
`class_name` plus document selectors when a rule needs a selector, such as the
alternating rows above.

## Repeat the brand and count the final pages

The document owns its page geometry. Its header and footer contain the same
Python component values used in the body:

```python
Page(
"A4", landscape=True,
margin=(mm(20), mm(16), mm(19), mm(16)),
header=PageBand(left=Brand(), right=Small(period)),
footer=PageBand(
left=Small("GRIDLINE / FICTIONAL DATA"),
right=PageNumbers(total=True, prefix="", separator=" / ", class_name="folio"),
),
)
```

`PageNumbers` uses the finalized page count. The 40-row sample produces `1 / 3`
through `3 / 3`; the 120-row check produces seven pages. Review your own content
after changing widths, fonts, data, or page size. An oversized row or page band
can raise a structured layout error.

## Export HTML with or without styles

The same document can be used without PDF rendering:

```python
document = build_report(frame)
styled_html = document.to_html(include_styles=True, a11y_mode="raise")
plain_html = document.to_html(include_styles=False, a11y_mode="raise")
```

`True` embeds the collected component CSS. `False` removes component CSS and
inline styles while retaining authored classes and semantic elements. The example's
export function also adds font loading and screen framing to the styled export;
keep its `fonts/` directory beside the HTML. These are static browser documents,
with a single header and footer. Browser pagination does not reproduce PDF pages.

## Keep presentation and data rules separate

The CSV contract is explicit: unique nonblank SKU, nonblank product and category,
nonnegative integer units, signed integer cents, and an ISO restock date or a
missing value. Additional columns are ignored. `Decimal` formats currency;
Python integers sum the amounts. Missing dates display “Not recorded”. Data
containing markup remains literal text, and building the report leaves the frame
unchanged. Normalize an existing DataFrame to this contract before authoring it.

## Reproduce the checks

```sh
python -m pip install --only-binary=:all: -r requirements-check.txt
python verify.py --out output/verification
```

The checks save actual PDFs for 40 rows, one row, mixed long/Unicode/literal-markup
values, and 120 rows. Independent readers check row order and completeness,
repeated headings, page totals, and glyph bounds, and rasterize every page. Eight
invalid input cases are rejected. A fresh Python process reproduces the sample's
exact PDF bytes using the fixed timestamp and bundled font. Both HTML modes and
unchanged input frames are also checked.

The PDF uses the tagged profile and passes source structural checks. These checks
do not establish PDF/UA conformance or assistive-technology usability. For that
separate workflow, see the [accessible component showcase](accessible-showcase.md).

Continue with the [current component preview](component-preview-9.md),
[reusable page-band notebook](../getting-started/page-band-notebook.md), or
[HTML/CSS pandas report](pandas-to-pdf.md).
5 changes: 5 additions & 0 deletions docs/guides/pandas-to-pdf.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ The organization and data are fictional.
For an interactive data app, try the [Streamlit report starter](streamlit-pdf.md)
with CSV input, date/group filters and a prepared PDF download.

Prefer Python component authoring? The [component report starter](pandas-components.md)
uses this same CSV with reusable metrics, table columns, typed styles, repeated
page bands, and both HTML export modes. It is separately pinned to **2.6.0a9**,
the experimental component API.

[Download the two-page PDF](../assets/pandas-report/report.pdf){ .md-button .md-button--primary }
[Get the complete source](../assets/pandas-report/source.zip){ .md-button }

Expand Down
Loading
Loading