coeftable 0.13.0__tar.gz → 0.14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {coeftable-0.13.0 → coeftable-0.14.0}/.github/workflows/ci.yml +12 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/CHANGELOG.md +34 -0
- coeftable-0.14.0/Makefile +25 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/PKG-INFO +135 -1
- {coeftable-0.13.0 → coeftable-0.14.0}/README.md +131 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/pyproject.toml +3 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/__init__.py +6 -0
- coeftable-0.14.0/src/coeftable/_tooltip_runtime.js +419 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/_version.py +2 -2
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/cards/regions.py +39 -2
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/frame.py +5 -9
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/grid.py +14 -11
- coeftable-0.14.0/src/coeftable/interactive.py +58 -0
- coeftable-0.14.0/src/coeftable/labels.py +218 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/plots.py +4 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/render.py +48 -2
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/series.py +47 -2
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/spec.py +74 -4
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/svg.py +222 -4
- coeftable-0.14.0/src/coeftable/tooltips.py +143 -0
- coeftable-0.14.0/tests/conftest.py +31 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_cards.py +0 -46
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_frame.py +42 -33
- coeftable-0.14.0/tests/test_interactive.py +536 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_plots.py +0 -19
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_regions.py +89 -0
- coeftable-0.14.0/tests/test_render.py +840 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_series.py +54 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_sparkline.py +66 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_svg.py +179 -0
- coeftable-0.14.0/tests/test_tooltips.py +49 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/uv.lock +108 -15
- coeftable-0.13.0/Makefile +0 -20
- coeftable-0.13.0/tests/test_render.py +0 -356
- {coeftable-0.13.0 → coeftable-0.14.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/.github/release.yml +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/.github/workflows/post-release.yml +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/.github/workflows/publish.yml +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/.github/workflows/release.yml +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/.gitignore +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/.pre-commit-config.yaml +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/AGENTS.md +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/LICENSE +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/docs/images/example.png +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/docs/images/forest-robust-autoscale.png +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/docs/images/trend-example.png +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/noxfile.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/_axis.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/annotations.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/cards/__init__.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/cards/adornments.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/cards/appearance.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/cards/card.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/cards/chrome.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/cards/fragments.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/cards/measure.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/cards/panel.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/cards/template.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/collapsible.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/errors.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/format.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/__init__.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/_layered.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/_routes.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/_staged.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/breakout.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/causal_graph.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/driver_tree.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/event_flow.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/honesty.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/metric_tree.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/model.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/product_flow.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/render.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/report.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/state.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/timeline.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/graph/topology.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/src/coeftable/theme.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_annotations.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_breakout.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_card_column.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_card_entry.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_causal_graph.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_collapsible.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_driver_tree.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_event_flow.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_event_flow_routes.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_format.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_graph.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_graph_report.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_honesty.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_package.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_panel.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_product_flow.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_product_flow_reference.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_public_api.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_spec.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_theme.py +0 -0
- {coeftable-0.13.0 → coeftable-0.14.0}/tests/test_timeline.py +0 -0
|
@@ -30,3 +30,15 @@ jobs:
|
|
|
30
30
|
with:
|
|
31
31
|
python-version: ${{ matrix.python-version }}
|
|
32
32
|
- run: uv run --with nox nox -s "tests-${{ matrix.python-version }}"
|
|
33
|
+
|
|
34
|
+
browser:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
needs: prek
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v6
|
|
39
|
+
- uses: astral-sh/setup-uv@v6
|
|
40
|
+
with:
|
|
41
|
+
python-version: "3.13"
|
|
42
|
+
- run: uv sync --extra dev --extra browser
|
|
43
|
+
- run: uv run python -m playwright install --with-deps chromium
|
|
44
|
+
- run: uv run pytest --no-cov tests/test_interactive.py
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
<a id="v0.13.1"></a>
|
|
2
|
+
# [v0.13.1](https://github.com/kylejcaron/coeftable/releases/tag/v0.13.1) - 2026-09-30
|
|
3
|
+
|
|
4
|
+
<!-- Release notes generated using configuration in .github/release.yml at v0.13.1 -->
|
|
5
|
+
|
|
6
|
+
## What's Changed
|
|
7
|
+
### Maintenance
|
|
8
|
+
* Update changelog for v0.13.0 by [@github-actions](https://github.com/github-actions)[bot] in [#63](https://github.com/kylejcaron/coeftable/pull/63)
|
|
9
|
+
* Neutralize unsafe Markdown targets in table labels by [@kylejcaron](https://github.com/kylejcaron) in [#64](https://github.com/kylejcaron/coeftable/pull/64)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
**Full Changelog**: https://github.com/kylejcaron/coeftable/compare/v0.13.0...v0.13.1
|
|
13
|
+
|
|
14
|
+
[Changes][v0.13.1]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
<a id="v0.13.0"></a>
|
|
18
|
+
# [v0.13.0](https://github.com/kylejcaron/coeftable/releases/tag/v0.13.0) - 2026-09-28
|
|
19
|
+
|
|
20
|
+
<!-- Release notes generated using configuration in .github/release.yml at v0.13.0 -->
|
|
21
|
+
|
|
22
|
+
## What's Changed
|
|
23
|
+
### Maintenance
|
|
24
|
+
* Update changelog for v0.12.1 by [@github-actions](https://github.com/github-actions)[bot] in [#61](https://github.com/kylejcaron/coeftable/pull/61)
|
|
25
|
+
* Add visible y-axes to sparkline surfaces by [@kylejcaron](https://github.com/kylejcaron) in [#62](https://github.com/kylejcaron/coeftable/pull/62)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
**Full Changelog**: https://github.com/kylejcaron/coeftable/compare/v0.12.1...v0.13.0
|
|
29
|
+
|
|
30
|
+
[Changes][v0.13.0]
|
|
31
|
+
|
|
32
|
+
|
|
1
33
|
<a id="v0.12.1"></a>
|
|
2
34
|
# [v0.12.1](https://github.com/kylejcaron/coeftable/releases/tag/v0.12.1) - 2026-09-24
|
|
3
35
|
|
|
@@ -266,6 +298,8 @@
|
|
|
266
298
|
[Changes][v0.1.0]
|
|
267
299
|
|
|
268
300
|
|
|
301
|
+
[v0.13.1]: https://github.com/kylejcaron/coeftable/compare/v0.13.0...v0.13.1
|
|
302
|
+
[v0.13.0]: https://github.com/kylejcaron/coeftable/compare/v0.12.1...v0.13.0
|
|
269
303
|
[v0.12.1]: https://github.com/kylejcaron/coeftable/compare/v0.12.0...v0.12.1
|
|
270
304
|
[v0.12.0]: https://github.com/kylejcaron/coeftable/compare/v0.11.0...v0.12.0
|
|
271
305
|
[v0.11.0]: https://github.com/kylejcaron/coeftable/compare/v0.10.0...v0.11.0
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.PHONY: setup tests tests-all tests-browser lint typecheck prek
|
|
2
|
+
|
|
3
|
+
setup:
|
|
4
|
+
uv sync --extra dev
|
|
5
|
+
uv run prek install
|
|
6
|
+
|
|
7
|
+
tests:
|
|
8
|
+
uv run pytest
|
|
9
|
+
|
|
10
|
+
tests-all:
|
|
11
|
+
uv run nox -s tests
|
|
12
|
+
|
|
13
|
+
tests-browser:
|
|
14
|
+
uv sync --extra dev --extra browser
|
|
15
|
+
uv run python -m playwright install chromium
|
|
16
|
+
uv run pytest --no-cov tests/test_interactive.py
|
|
17
|
+
|
|
18
|
+
lint:
|
|
19
|
+
uv run nox -s lint
|
|
20
|
+
|
|
21
|
+
typecheck:
|
|
22
|
+
uv run nox -s typecheck
|
|
23
|
+
|
|
24
|
+
prek:
|
|
25
|
+
SKIP=no-commit-to-branch uv run prek run --all-files
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coeftable
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: Publication-quality summary tables for estimates with uncertainty.
|
|
5
5
|
Project-URL: Homepage, https://github.com/kylejcaron/coeftable
|
|
6
6
|
Project-URL: Repository, https://github.com/kylejcaron/coeftable
|
|
@@ -39,7 +39,10 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
39
39
|
Classifier: Topic :: Scientific/Engineering
|
|
40
40
|
Requires-Python: >=3.12
|
|
41
41
|
Requires-Dist: great-tables>=0.22
|
|
42
|
+
Requires-Dist: multimark>=0.1.3
|
|
42
43
|
Requires-Dist: narwhals>=2.24
|
|
44
|
+
Provides-Extra: browser
|
|
45
|
+
Requires-Dist: playwright>=1.50; extra == 'browser'
|
|
43
46
|
Provides-Extra: dev
|
|
44
47
|
Requires-Dist: nox>=2025.5; extra == 'dev'
|
|
45
48
|
Requires-Dist: pandas>=2.2; extra == 'dev'
|
|
@@ -116,6 +119,16 @@ a `parameter` column.
|
|
|
116
119
|
- `split_columns` — an optional column whose values produce repeated column
|
|
117
120
|
groups side by side, useful for comparing methods.
|
|
118
121
|
|
|
122
|
+
Row and nested labels support safe Markdown, including `{{m s^-1}}` units
|
|
123
|
+
notation. Raw HTML in labels is displayed as text; dangerous link targets are
|
|
124
|
+
neutralized. Group and split-column labels are literal text. These rules apply
|
|
125
|
+
to standalone `as_raw_html()` and the table's initial `.gt()` HTML rendering;
|
|
126
|
+
subsequent formatting added through `.gt()` follows Great Tables' own rules.
|
|
127
|
+
LaTeX export also escapes group and split-column labels, including numeric and
|
|
128
|
+
date-valued groups.
|
|
129
|
+
Native `.gt()` spanner styles and footnotes select split headings using their
|
|
130
|
+
original string IDs.
|
|
131
|
+
|
|
119
132
|
**Series columns bend this rule.** A point estimate is one number (plus
|
|
120
133
|
bounds), so a triple of scalar columns holds it. A `.sparkline(...)` series
|
|
121
134
|
is N points, not one -- most naturally via the companion-frame door, a
|
|
@@ -541,6 +554,118 @@ shaded intervals via each function's `annotations=` parameter. Because a
|
|
|
541
554
|
standalone plot and a table column share one `Theme`, a report that mixes
|
|
542
555
|
both stays visually consistent.
|
|
543
556
|
|
|
557
|
+
## Inspect plot values
|
|
558
|
+
|
|
559
|
+
Pass `tooltip=ct.Tooltip()` to opt a plot into value inspection, then activate
|
|
560
|
+
the composed HTML with `ct.interactive_html`. Existing renderers remain passive:
|
|
561
|
+
`as_raw_html()`, `_repr_html_()`, `.gt()`, and standalone SVG strings do not
|
|
562
|
+
execute scripts themselves.
|
|
563
|
+
|
|
564
|
+
```python
|
|
565
|
+
from pathlib import Path
|
|
566
|
+
|
|
567
|
+
import coeftable as ct
|
|
568
|
+
import polars as pl
|
|
569
|
+
from coeftable.cards import Card, Trend
|
|
570
|
+
from coeftable.plots import sparkline_bar
|
|
571
|
+
|
|
572
|
+
history = pl.DataFrame({
|
|
573
|
+
"metric": ["Revenue", "Revenue", "Revenue"],
|
|
574
|
+
"day": [0, 2, 4],
|
|
575
|
+
"estimate": [1.0, None, 5.0],
|
|
576
|
+
"n": [10, 20, 30],
|
|
577
|
+
})
|
|
578
|
+
table = ct.CoefTable(pl.DataFrame({"metric": ["Revenue"]}), rows="metric").sparkline(
|
|
579
|
+
"Trend", value="estimate", x="day", data=history, ylim=(0, 3),
|
|
580
|
+
tooltip=ct.Tooltip(
|
|
581
|
+
fields=[ct.TooltipField("n", label="Sample size")],
|
|
582
|
+
value_label="Revenue", x_label="Day",
|
|
583
|
+
),
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
# Direct plots and card regions receive aligned arrays, not dataframe fields.
|
|
587
|
+
extras = [ct.TooltipValues("Sample size", [10, 20, 30])]
|
|
588
|
+
line = sparkline_bar(
|
|
589
|
+
[0, 2, 4], [1, None, 5], [None] * 3, [None] * 3,
|
|
590
|
+
x_domain=(0, 4), domain=(0, 3), ref=None, color="#4C72B0", fmt=ct.Number(),
|
|
591
|
+
tooltip=ct.Tooltip(value_label="Revenue", x_label="Day"), tooltip_values=extras,
|
|
592
|
+
)
|
|
593
|
+
card = Card("Revenue", content=[
|
|
594
|
+
Trend(
|
|
595
|
+
x=[0, 2, 4], y=[1, None, 5], x_domain=(0, 4), domain=(0, 3),
|
|
596
|
+
tooltip=ct.Tooltip(value_label="Revenue", x_label="Day"), tooltip_values=extras,
|
|
597
|
+
),
|
|
598
|
+
])
|
|
599
|
+
fragment = table.as_raw_html() + line + card.as_raw_html()
|
|
600
|
+
document = ct.interactive_html(fragment, full_document=True)
|
|
601
|
+
Path("inspection.html").write_text(document, encoding="utf-8")
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Primary values are bold; the x coordinate is context, not the headline.
|
|
605
|
+
`Tooltip(value_label="Sales", x_label="Date")` names both values explicitly.
|
|
606
|
+
Labels default to `Value` and `x`; forest inspection uses only `value_label`.
|
|
607
|
+
|
|
608
|
+
`TooltipField` binds **main-frame scalar columns for forest plots**, main-frame
|
|
609
|
+
list columns for list-mode sparklines, and **companion-frame scalar columns**
|
|
610
|
+
when `data=` is supplied. Companion sorting applies the same permutation to
|
|
611
|
+
the extra measures. List-mode drawing retains its input order. Extras never
|
|
612
|
+
affect plot domains or make an otherwise blank table cell drawable.
|
|
613
|
+
|
|
614
|
+
`forest_bar` and card `Interval` accept `tooltip_values` of length one per
|
|
615
|
+
field; `sparkline_bar` and `Trend` require each field to match the point arrays.
|
|
616
|
+
For `sparkline_multi`, `tooltip_values` is an outer sequence aligned to the
|
|
617
|
+
input traces: include an empty entry for any trace without extras. An empty
|
|
618
|
+
outer sequence means no extras on any trace. `Trace` remains unchanged.
|
|
619
|
+
Direct plots/regions reject unresolved `Tooltip.fields`; extra values without
|
|
620
|
+
an enabled tooltip raise `SpecError`.
|
|
621
|
+
|
|
622
|
+
**Selection and formatting.** Move anywhere horizontally inside the plot's
|
|
623
|
+
data area, or drag, to select the nearest observed x. Midpoint ties choose
|
|
624
|
+
the smaller x. Missing y displays “Missing”; an arm without that exact x
|
|
625
|
+
displays “No observation”. Duplicate observations at one x remain separate.
|
|
626
|
+
No interpolation or substitution from another date. Off-scale values keep
|
|
627
|
+
their actual values and an above/below (forest: left/right) indication.
|
|
628
|
+
Forest plots expose their one interval anywhere in the data area.
|
|
629
|
+
|
|
630
|
+
`Tooltip(fmt=..., x_fmt=...)` overrides observation value/interval and x
|
|
631
|
+
formatters independently. Otherwise the primary plot/region formatter is used
|
|
632
|
+
for values (`Number()` for direct `forest_bar`). Numeric x defaults to
|
|
633
|
+
`Number()`. Temporal table coordinates and `Trend(temporal=True)` use full
|
|
634
|
+
`YYYY-MM-DD HH:MM:SS` coordinate dates, not contextual axis ticks such as “14”.
|
|
635
|
+
These dates follow the existing epoch-coordinate convention formatted in UTC;
|
|
636
|
+
this does not reinterpret the source timezone. Direct numeric emitters need
|
|
637
|
+
an explicit `x_fmt` for date coordinates. Each extra field has its own `fmt`;
|
|
638
|
+
null/NaN extras display “—” without invoking it. Formatters must return strings.
|
|
639
|
+
|
|
640
|
+
**Input.** Focus an enabled plot, then use Left/Right, Home/End, or Escape.
|
|
641
|
+
There is one tab stop per plot, not per point. Touch/pen release leaves the
|
|
642
|
+
inspector available to read until dismissal or another selection; cancellation
|
|
643
|
+
clears it and vertical page scrolling remains available. Long inspectors wrap
|
|
644
|
+
and scroll within the viewport: PageUp/PageDown work while the plot is focused;
|
|
645
|
+
Tab can enter the scroll region. Escape or an outside tap dismisses.
|
|
646
|
+
|
|
647
|
+
| Delivery path | Behavior |
|
|
648
|
+
|---|---|
|
|
649
|
+
| Saved full document | Interactive offline when scripts are permitted; no kernel/server/CDN needed. |
|
|
650
|
+
| Parser-inserted fragment | `interactive_html(fragment)` activates within its own wrapper. |
|
|
651
|
+
| Dynamic `innerHTML` insertion | Scripts are inert. Load the packaged `coeftable/_tooltip_runtime.js` yourself and call `window.coeftableTooltips.mount(root)`; the returned function disposes the mount. Repeated mounts of the same root are idempotent. |
|
|
652
|
+
| Live marimo | `mo.iframe(ct.interactive_html(fragment, full_document=True), height="500px")`; `mo.Html` is a passive path. |
|
|
653
|
+
| SVG as an image, PNG/PDF, scripts blocked/disabled | Existing static appearance, without inspection. |
|
|
654
|
+
|
|
655
|
+
Compose table, standalone and card HTML first, then activate once.
|
|
656
|
+
`interactive_html` accepts **trusted fragments only**, not complete
|
|
657
|
+
`html`/`head`/`body` documents, and is not an HTML sanitizer. If using
|
|
658
|
+
`table.gt().as_raw_html(...)`, request a fragment rather than a document.
|
|
659
|
+
Restrictive CSP policies may block the inline bootstrap/styles; asset/nonces
|
|
660
|
+
integration, automatic notebook activation, Python callbacks and untested
|
|
661
|
+
notebook/export hosts are not promised.
|
|
662
|
+
|
|
663
|
+
**Data disclosure:** all configured observation values and extra fields are
|
|
664
|
+
embedded in browser-readable output, including missing/off-scale observations.
|
|
665
|
+
Plain-text escaping prevents formatter output from becoming markup; it does
|
|
666
|
+
not make sensitive data private. Include only fields intended for recipients.
|
|
667
|
+
|
|
668
|
+
|
|
544
669
|
## Metric cards
|
|
545
670
|
|
|
546
671
|
`coeftable.cards` composes the standalone plots into measured, foldable
|
|
@@ -1121,3 +1246,12 @@ the Sparkline y-domain and clip or omit distant marks. Annotations supplement
|
|
|
1121
1246
|
rather than replace
|
|
1122
1247
|
`ref`: `ref` remains the built-in semantic reference that controls colors and
|
|
1123
1248
|
its optional dashed line.
|
|
1249
|
+
|
|
1250
|
+
## Development checks
|
|
1251
|
+
|
|
1252
|
+
`make setup` installs development dependencies without enabling optional browser
|
|
1253
|
+
tests. `make tests` runs the installed test selection. For plot interaction
|
|
1254
|
+
checks, `make tests-browser` installs the optional Playwright package and
|
|
1255
|
+
Chromium, then runs `tests/test_interactive.py`. Without the browser extra,
|
|
1256
|
+
only browser-dependent cases skip; HTML assembly checks still run. An installed
|
|
1257
|
+
Playwright package without Chromium is an error, not a passing skip.
|
|
@@ -62,6 +62,16 @@ a `parameter` column.
|
|
|
62
62
|
- `split_columns` — an optional column whose values produce repeated column
|
|
63
63
|
groups side by side, useful for comparing methods.
|
|
64
64
|
|
|
65
|
+
Row and nested labels support safe Markdown, including `{{m s^-1}}` units
|
|
66
|
+
notation. Raw HTML in labels is displayed as text; dangerous link targets are
|
|
67
|
+
neutralized. Group and split-column labels are literal text. These rules apply
|
|
68
|
+
to standalone `as_raw_html()` and the table's initial `.gt()` HTML rendering;
|
|
69
|
+
subsequent formatting added through `.gt()` follows Great Tables' own rules.
|
|
70
|
+
LaTeX export also escapes group and split-column labels, including numeric and
|
|
71
|
+
date-valued groups.
|
|
72
|
+
Native `.gt()` spanner styles and footnotes select split headings using their
|
|
73
|
+
original string IDs.
|
|
74
|
+
|
|
65
75
|
**Series columns bend this rule.** A point estimate is one number (plus
|
|
66
76
|
bounds), so a triple of scalar columns holds it. A `.sparkline(...)` series
|
|
67
77
|
is N points, not one -- most naturally via the companion-frame door, a
|
|
@@ -487,6 +497,118 @@ shaded intervals via each function's `annotations=` parameter. Because a
|
|
|
487
497
|
standalone plot and a table column share one `Theme`, a report that mixes
|
|
488
498
|
both stays visually consistent.
|
|
489
499
|
|
|
500
|
+
## Inspect plot values
|
|
501
|
+
|
|
502
|
+
Pass `tooltip=ct.Tooltip()` to opt a plot into value inspection, then activate
|
|
503
|
+
the composed HTML with `ct.interactive_html`. Existing renderers remain passive:
|
|
504
|
+
`as_raw_html()`, `_repr_html_()`, `.gt()`, and standalone SVG strings do not
|
|
505
|
+
execute scripts themselves.
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
from pathlib import Path
|
|
509
|
+
|
|
510
|
+
import coeftable as ct
|
|
511
|
+
import polars as pl
|
|
512
|
+
from coeftable.cards import Card, Trend
|
|
513
|
+
from coeftable.plots import sparkline_bar
|
|
514
|
+
|
|
515
|
+
history = pl.DataFrame({
|
|
516
|
+
"metric": ["Revenue", "Revenue", "Revenue"],
|
|
517
|
+
"day": [0, 2, 4],
|
|
518
|
+
"estimate": [1.0, None, 5.0],
|
|
519
|
+
"n": [10, 20, 30],
|
|
520
|
+
})
|
|
521
|
+
table = ct.CoefTable(pl.DataFrame({"metric": ["Revenue"]}), rows="metric").sparkline(
|
|
522
|
+
"Trend", value="estimate", x="day", data=history, ylim=(0, 3),
|
|
523
|
+
tooltip=ct.Tooltip(
|
|
524
|
+
fields=[ct.TooltipField("n", label="Sample size")],
|
|
525
|
+
value_label="Revenue", x_label="Day",
|
|
526
|
+
),
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
# Direct plots and card regions receive aligned arrays, not dataframe fields.
|
|
530
|
+
extras = [ct.TooltipValues("Sample size", [10, 20, 30])]
|
|
531
|
+
line = sparkline_bar(
|
|
532
|
+
[0, 2, 4], [1, None, 5], [None] * 3, [None] * 3,
|
|
533
|
+
x_domain=(0, 4), domain=(0, 3), ref=None, color="#4C72B0", fmt=ct.Number(),
|
|
534
|
+
tooltip=ct.Tooltip(value_label="Revenue", x_label="Day"), tooltip_values=extras,
|
|
535
|
+
)
|
|
536
|
+
card = Card("Revenue", content=[
|
|
537
|
+
Trend(
|
|
538
|
+
x=[0, 2, 4], y=[1, None, 5], x_domain=(0, 4), domain=(0, 3),
|
|
539
|
+
tooltip=ct.Tooltip(value_label="Revenue", x_label="Day"), tooltip_values=extras,
|
|
540
|
+
),
|
|
541
|
+
])
|
|
542
|
+
fragment = table.as_raw_html() + line + card.as_raw_html()
|
|
543
|
+
document = ct.interactive_html(fragment, full_document=True)
|
|
544
|
+
Path("inspection.html").write_text(document, encoding="utf-8")
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
Primary values are bold; the x coordinate is context, not the headline.
|
|
548
|
+
`Tooltip(value_label="Sales", x_label="Date")` names both values explicitly.
|
|
549
|
+
Labels default to `Value` and `x`; forest inspection uses only `value_label`.
|
|
550
|
+
|
|
551
|
+
`TooltipField` binds **main-frame scalar columns for forest plots**, main-frame
|
|
552
|
+
list columns for list-mode sparklines, and **companion-frame scalar columns**
|
|
553
|
+
when `data=` is supplied. Companion sorting applies the same permutation to
|
|
554
|
+
the extra measures. List-mode drawing retains its input order. Extras never
|
|
555
|
+
affect plot domains or make an otherwise blank table cell drawable.
|
|
556
|
+
|
|
557
|
+
`forest_bar` and card `Interval` accept `tooltip_values` of length one per
|
|
558
|
+
field; `sparkline_bar` and `Trend` require each field to match the point arrays.
|
|
559
|
+
For `sparkline_multi`, `tooltip_values` is an outer sequence aligned to the
|
|
560
|
+
input traces: include an empty entry for any trace without extras. An empty
|
|
561
|
+
outer sequence means no extras on any trace. `Trace` remains unchanged.
|
|
562
|
+
Direct plots/regions reject unresolved `Tooltip.fields`; extra values without
|
|
563
|
+
an enabled tooltip raise `SpecError`.
|
|
564
|
+
|
|
565
|
+
**Selection and formatting.** Move anywhere horizontally inside the plot's
|
|
566
|
+
data area, or drag, to select the nearest observed x. Midpoint ties choose
|
|
567
|
+
the smaller x. Missing y displays “Missing”; an arm without that exact x
|
|
568
|
+
displays “No observation”. Duplicate observations at one x remain separate.
|
|
569
|
+
No interpolation or substitution from another date. Off-scale values keep
|
|
570
|
+
their actual values and an above/below (forest: left/right) indication.
|
|
571
|
+
Forest plots expose their one interval anywhere in the data area.
|
|
572
|
+
|
|
573
|
+
`Tooltip(fmt=..., x_fmt=...)` overrides observation value/interval and x
|
|
574
|
+
formatters independently. Otherwise the primary plot/region formatter is used
|
|
575
|
+
for values (`Number()` for direct `forest_bar`). Numeric x defaults to
|
|
576
|
+
`Number()`. Temporal table coordinates and `Trend(temporal=True)` use full
|
|
577
|
+
`YYYY-MM-DD HH:MM:SS` coordinate dates, not contextual axis ticks such as “14”.
|
|
578
|
+
These dates follow the existing epoch-coordinate convention formatted in UTC;
|
|
579
|
+
this does not reinterpret the source timezone. Direct numeric emitters need
|
|
580
|
+
an explicit `x_fmt` for date coordinates. Each extra field has its own `fmt`;
|
|
581
|
+
null/NaN extras display “—” without invoking it. Formatters must return strings.
|
|
582
|
+
|
|
583
|
+
**Input.** Focus an enabled plot, then use Left/Right, Home/End, or Escape.
|
|
584
|
+
There is one tab stop per plot, not per point. Touch/pen release leaves the
|
|
585
|
+
inspector available to read until dismissal or another selection; cancellation
|
|
586
|
+
clears it and vertical page scrolling remains available. Long inspectors wrap
|
|
587
|
+
and scroll within the viewport: PageUp/PageDown work while the plot is focused;
|
|
588
|
+
Tab can enter the scroll region. Escape or an outside tap dismisses.
|
|
589
|
+
|
|
590
|
+
| Delivery path | Behavior |
|
|
591
|
+
|---|---|
|
|
592
|
+
| Saved full document | Interactive offline when scripts are permitted; no kernel/server/CDN needed. |
|
|
593
|
+
| Parser-inserted fragment | `interactive_html(fragment)` activates within its own wrapper. |
|
|
594
|
+
| Dynamic `innerHTML` insertion | Scripts are inert. Load the packaged `coeftable/_tooltip_runtime.js` yourself and call `window.coeftableTooltips.mount(root)`; the returned function disposes the mount. Repeated mounts of the same root are idempotent. |
|
|
595
|
+
| Live marimo | `mo.iframe(ct.interactive_html(fragment, full_document=True), height="500px")`; `mo.Html` is a passive path. |
|
|
596
|
+
| SVG as an image, PNG/PDF, scripts blocked/disabled | Existing static appearance, without inspection. |
|
|
597
|
+
|
|
598
|
+
Compose table, standalone and card HTML first, then activate once.
|
|
599
|
+
`interactive_html` accepts **trusted fragments only**, not complete
|
|
600
|
+
`html`/`head`/`body` documents, and is not an HTML sanitizer. If using
|
|
601
|
+
`table.gt().as_raw_html(...)`, request a fragment rather than a document.
|
|
602
|
+
Restrictive CSP policies may block the inline bootstrap/styles; asset/nonces
|
|
603
|
+
integration, automatic notebook activation, Python callbacks and untested
|
|
604
|
+
notebook/export hosts are not promised.
|
|
605
|
+
|
|
606
|
+
**Data disclosure:** all configured observation values and extra fields are
|
|
607
|
+
embedded in browser-readable output, including missing/off-scale observations.
|
|
608
|
+
Plain-text escaping prevents formatter output from becoming markup; it does
|
|
609
|
+
not make sensitive data private. Include only fields intended for recipients.
|
|
610
|
+
|
|
611
|
+
|
|
490
612
|
## Metric cards
|
|
491
613
|
|
|
492
614
|
`coeftable.cards` composes the standalone plots into measured, foldable
|
|
@@ -1067,3 +1189,12 @@ the Sparkline y-domain and clip or omit distant marks. Annotations supplement
|
|
|
1067
1189
|
rather than replace
|
|
1068
1190
|
`ref`: `ref` remains the built-in semantic reference that controls colors and
|
|
1069
1191
|
its optional dashed line.
|
|
1192
|
+
|
|
1193
|
+
## Development checks
|
|
1194
|
+
|
|
1195
|
+
`make setup` installs development dependencies without enabling optional browser
|
|
1196
|
+
tests. `make tests` runs the installed test selection. For plot interaction
|
|
1197
|
+
checks, `make tests-browser` installs the optional Playwright package and
|
|
1198
|
+
Chromium, then runs `tests/test_interactive.py`. Without the browser extra,
|
|
1199
|
+
only browser-dependent cases skip; HTML assembly checks still run. An installed
|
|
1200
|
+
Playwright package without Chromium is an error, not a passing skip.
|
|
@@ -23,6 +23,7 @@ classifiers = [
|
|
|
23
23
|
]
|
|
24
24
|
dependencies = [
|
|
25
25
|
"great-tables>=0.22",
|
|
26
|
+
"multimark>=0.1.3",
|
|
26
27
|
"narwhals>=2.24",
|
|
27
28
|
]
|
|
28
29
|
|
|
@@ -38,6 +39,7 @@ dev = [
|
|
|
38
39
|
"nox>=2025.5",
|
|
39
40
|
"pyarrow>=25.0.0",
|
|
40
41
|
]
|
|
42
|
+
browser = ["playwright>=1.50"]
|
|
41
43
|
|
|
42
44
|
[project.urls]
|
|
43
45
|
Homepage = "https://github.com/kylejcaron/coeftable"
|
|
@@ -95,6 +97,7 @@ convention = "numpy"
|
|
|
95
97
|
|
|
96
98
|
[tool.pytest.ini_options]
|
|
97
99
|
testpaths = ["tests"]
|
|
100
|
+
markers = ["integration: exercises rendering, subprocess, filesystem, or browser boundaries"]
|
|
98
101
|
addopts = [
|
|
99
102
|
"-v",
|
|
100
103
|
"--strict-markers",
|
|
@@ -13,8 +13,10 @@ from coeftable.format import (
|
|
|
13
13
|
Percent,
|
|
14
14
|
TimeFormat,
|
|
15
15
|
)
|
|
16
|
+
from coeftable.interactive import interactive_html
|
|
16
17
|
from coeftable.spec import CardColumn, CoefTable, Estimate, Forest, Passthrough, Sparkline
|
|
17
18
|
from coeftable.theme import Theme, role_for
|
|
19
|
+
from coeftable.tooltips import Tooltip, TooltipField, TooltipValues
|
|
18
20
|
|
|
19
21
|
try:
|
|
20
22
|
__version__ = version("coeftable")
|
|
@@ -40,7 +42,11 @@ __all__ = [
|
|
|
40
42
|
"SpecError",
|
|
41
43
|
"Theme",
|
|
42
44
|
"TimeFormat",
|
|
45
|
+
"Tooltip",
|
|
46
|
+
"TooltipField",
|
|
47
|
+
"TooltipValues",
|
|
43
48
|
"__version__",
|
|
49
|
+
"interactive_html",
|
|
44
50
|
"role_for",
|
|
45
51
|
]
|
|
46
52
|
|