dstory 0.6.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.
- dstory-0.6.0/.gitignore +34 -0
- dstory-0.6.0/LICENSE +21 -0
- dstory-0.6.0/PKG-INFO +345 -0
- dstory-0.6.0/README.md +309 -0
- dstory-0.6.0/pyproject.toml +72 -0
- dstory-0.6.0/src/dstory/__init__.py +33 -0
- dstory-0.6.0/src/dstory/api.py +111 -0
- dstory-0.6.0/src/dstory/brand.py +243 -0
- dstory-0.6.0/src/dstory/bundle.py +406 -0
- dstory-0.6.0/src/dstory/cli.py +240 -0
- dstory-0.6.0/src/dstory/cookbook/__init__.py +290 -0
- dstory-0.6.0/src/dstory/cookbook/annotated-scatter.js +93 -0
- dstory-0.6.0/src/dstory/cookbook/bar-steps.js +83 -0
- dstory-0.6.0/src/dstory/cookbook/beeswarm.js +87 -0
- dstory-0.6.0/src/dstory/cookbook/bump-chart.js +92 -0
- dstory-0.6.0/src/dstory/cookbook/dot-timeline.js +83 -0
- dstory-0.6.0/src/dstory/cookbook/line-reveal.js +79 -0
- dstory-0.6.0/src/dstory/cookbook/range-dots.js +98 -0
- dstory-0.6.0/src/dstory/cookbook/slope-chart.js +90 -0
- dstory-0.6.0/src/dstory/cookbook/small-multiples.js +94 -0
- dstory-0.6.0/src/dstory/cookbook/waffle.js +70 -0
- dstory-0.6.0/src/dstory/prep.py +240 -0
- dstory-0.6.0/src/dstory/references/vizzu-chart-gallery.md +383 -0
- dstory-0.6.0/src/dstory/scaffold.py +408 -0
- dstory-0.6.0/src/dstory/schema.py +183 -0
- dstory-0.6.0/src/dstory/templates/index.html +97 -0
- dstory-0.6.0/src/dstory/templates/scenes/example-line-reveal.js +103 -0
- dstory-0.6.0/src/dstory/templates/scenes/example-scrolly.js +82 -0
- dstory-0.6.0/src/dstory/templates/story.css +497 -0
- dstory-0.6.0/src/dstory/templates/story.js +662 -0
- dstory-0.6.0/src/dstory/themes/brutalist.toml +46 -0
- dstory-0.6.0/src/dstory/themes/data-fi.toml +46 -0
- dstory-0.6.0/src/dstory/themes/editorial-noir.toml +46 -0
- dstory-0.6.0/src/dstory/themes/minimal-light.toml +46 -0
- dstory-0.6.0/src/dstory/themes/playful-pastel.toml +46 -0
- dstory-0.6.0/src/dstory/themes/scientific-bright.toml +46 -0
- dstory-0.6.0/src/dstory/themes/tech-glow.toml +46 -0
- dstory-0.6.0/src/dstory/themes/warm-print.toml +46 -0
- dstory-0.6.0/src/dstory/vet.py +627 -0
dstory-0.6.0/.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.eggs/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
wheels/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
.env
|
|
17
|
+
|
|
18
|
+
# Tooling
|
|
19
|
+
.uv/
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
.coverage
|
|
22
|
+
.coverage.*
|
|
23
|
+
.tox/
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
# IDE
|
|
32
|
+
.idea/
|
|
33
|
+
.vscode/
|
|
34
|
+
*.swp
|
dstory-0.6.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kedar Dabhadkar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
dstory-0.6.0/PKG-INFO
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dstory
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Build interactive scrollytelling and slide-deck data stories from Python — bundle, vet, theme, and ship a single self-contained HTML file.
|
|
5
|
+
Project-URL: Homepage, https://github.com/dkedar7/dstory
|
|
6
|
+
Project-URL: Issues, https://github.com/dkedar7/dstory/issues
|
|
7
|
+
Author-email: Kedar Dabhadkar <kedar.dabhadkar@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: d3,data-storytelling,data-visualization,interactive,scrollytelling,vizzu
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: click>=8.1
|
|
23
|
+
Requires-Dist: pydantic>=2.5
|
|
24
|
+
Requires-Dist: rich>=13.0
|
|
25
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
26
|
+
Provides-Extra: all
|
|
27
|
+
Requires-Dist: numpy>=1.24; extra == 'all'
|
|
28
|
+
Requires-Dist: pandas>=2.0; extra == 'all'
|
|
29
|
+
Requires-Dist: playwright>=1.40; extra == 'all'
|
|
30
|
+
Provides-Extra: prep
|
|
31
|
+
Requires-Dist: numpy>=1.24; extra == 'prep'
|
|
32
|
+
Requires-Dist: pandas>=2.0; extra == 'prep'
|
|
33
|
+
Provides-Extra: vet
|
|
34
|
+
Requires-Dist: playwright>=1.40; extra == 'vet'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# dstory
|
|
38
|
+
|
|
39
|
+
> Turn DataFrames into NYT-style scrollytelling and slide-deck data stories — one self-contained HTML file you can email, host, or present. Pure Python in, no JavaScript required.
|
|
40
|
+
|
|
41
|
+
**[📖 Documentation](https://dkedar7.github.io/dstory/)** ·
|
|
42
|
+
**[▶ Live demo: *Ctrl-Alt-Reinvent — Microsoft, 1975–2025*](https://dkedar7.github.io/dstory/demo/)** — a 40-scene data essay built with dstory ·
|
|
43
|
+
**[▶ Cookbook gallery](https://dkedar7.github.io/dstory/gallery/)** — the chart recipes, rendered ·
|
|
44
|
+
**[▶ Theme showcase](https://dkedar7.github.io/dstory/themes/)** — same story, eight personalities ·
|
|
45
|
+
**[▶ Morph showcase](https://dkedar7.github.io/dstory/morphs/)** — bars curl into pies, live, zero JS
|
|
46
|
+
|
|
47
|
+
`dstory` is the deterministic infrastructure for data stories: a one-call builder, a single-file bundler, a 5-dimension vetter (renders / data fidelity / editorial integrity / visual / a11y), a brand system for your colors and typography, and pydantic schemas that surface authoring bugs at build time instead of delivery time.
|
|
48
|
+
|
|
49
|
+
## Who is this for?
|
|
50
|
+
|
|
51
|
+
- **Python data scientists (no JS):** animated chart sequences via declarative [Vizzu](#vizzu-scenes-no-javascript) scenes, plus a [cookbook](#the-cookbook) of finished D3 charts you copy and own. DataFrame → story.html in one notebook cell.
|
|
52
|
+
- **Authors comfortable with D3:** full creative control — scenes are plain JS files you write; dstory does everything else and stays out of your chart code.
|
|
53
|
+
- **AI-assisted authoring:** dstory is the substrate the companion **data-storytelling** Claude skill builds on — the agent writes the bespoke D3; the vetter gates what ships.
|
|
54
|
+
|
|
55
|
+
## Status
|
|
56
|
+
|
|
57
|
+
Alpha — under active development. API may change before 1.0.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install dstory # core: schema + brand + scaffold + bundle
|
|
63
|
+
pip install "dstory[vet]" # adds Playwright for browser-driven vetting
|
|
64
|
+
pip install "dstory[prep]" # adds pandas for data-prep helpers
|
|
65
|
+
pip install "dstory[all]" # everything
|
|
66
|
+
|
|
67
|
+
# After installing the [vet] extra, install Chromium:
|
|
68
|
+
playwright install chromium
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quick start: notebook, zero JavaScript
|
|
72
|
+
|
|
73
|
+
One cell, DataFrame to shippable HTML:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import pandas as pd
|
|
77
|
+
from dstory import build
|
|
78
|
+
from dstory.prep import to_long
|
|
79
|
+
|
|
80
|
+
df = pd.DataFrame({
|
|
81
|
+
"month": ["Jan", "Feb", "Mar", "Apr"],
|
|
82
|
+
"Signups": [1000, 1500, 2900, 3400],
|
|
83
|
+
"Churn": [200, 240, 250, 260],
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
result = build({
|
|
87
|
+
"meta": {"title": "Q1 Growth", "theme": "scientific-bright"},
|
|
88
|
+
"scenes": [{
|
|
89
|
+
"id": "scene-growth", "kind": "vizzu", "dataset": "metrics",
|
|
90
|
+
"alt": "Animated bar-to-line sequence: signups more than tripled while churn stayed flat.",
|
|
91
|
+
"series": [
|
|
92
|
+
{"name": "month", "type": "dimension"},
|
|
93
|
+
{"name": "variable", "type": "dimension"},
|
|
94
|
+
{"name": "value", "type": "measure"},
|
|
95
|
+
],
|
|
96
|
+
"frames": [
|
|
97
|
+
{"headline": "Signups took off in March",
|
|
98
|
+
"config": {"channels": {"x": "month", "y": "value", "color": "variable"},
|
|
99
|
+
"geometry": "rectangle"}},
|
|
100
|
+
{"headline": "While churn barely moved",
|
|
101
|
+
"config": {"channels": {"x": "month", "y": "value", "color": "variable"},
|
|
102
|
+
"geometry": "line"}},
|
|
103
|
+
],
|
|
104
|
+
}],
|
|
105
|
+
"datasets": {"metrics": to_long(df, id_vars="month")},
|
|
106
|
+
}, "q1-growth", vet=True)
|
|
107
|
+
|
|
108
|
+
result.html # → q1-growth/dist/story.html — open it, scroll, the chart morphs
|
|
109
|
+
result.passed # → True (the 5-dimension vet report)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`kind: "vizzu"` scenes are fully declarative — frames animate between any
|
|
113
|
+
chart configuration (bar → line → stacked → polar…, 40+ types; see
|
|
114
|
+
[`references/vizzu-chart-gallery.md`](src/dstory/references/vizzu-chart-gallery.md)).
|
|
115
|
+
No scene JS file exists for this story at all.
|
|
116
|
+
|
|
117
|
+
## The cookbook
|
|
118
|
+
|
|
119
|
+
When you want the bespoke-D3 look without writing D3 from scratch: 10 complete,
|
|
120
|
+
themed, responsive scene renderers — **[see them rendered](https://dkedar7.github.io/dstory/gallery/)**.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
dstory recipes # list: line-reveal, beeswarm, bump-chart, ...
|
|
124
|
+
dstory scene my-story scene-growth --from line-reveal # copy one in, wired and ready
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A recipe is source code dropped into *your* project — edit it freely, it's
|
|
128
|
+
yours. Each documents its dataset columns and `scene.config` options in the
|
|
129
|
+
file header. All are theme-token driven and respect `prefers-reduced-motion`.
|
|
130
|
+
There is still no `BarChart()` API and never will be: recipes are starting
|
|
131
|
+
points, not abstractions.
|
|
132
|
+
|
|
133
|
+
## Quick start (CLI)
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# 1. Scaffold a project from one of the 8 bundled themes
|
|
137
|
+
dstory init my-story --theme editorial-noir --audience general-public
|
|
138
|
+
|
|
139
|
+
# 2. Add scenes: from the cookbook, a blank stub, or by hand
|
|
140
|
+
dstory scene my-story scene-trend --from line-reveal
|
|
141
|
+
# ...then edit my-story/data.json (datasets, headlines, claims)
|
|
142
|
+
|
|
143
|
+
# 3. Preview while authoring
|
|
144
|
+
dstory preview my-story
|
|
145
|
+
|
|
146
|
+
# 4. Bundle to a single self-contained HTML file
|
|
147
|
+
dstory bundle my-story --vendor
|
|
148
|
+
# → my-story/dist/story.html (--vendor inlines d3/scrollama/Motion: works offline)
|
|
149
|
+
|
|
150
|
+
# 5. Vet before delivery
|
|
151
|
+
dstory vet my-story/dist/story.html --data my-story/data.json
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Other commands: `dstory themes` (the 8 brand presets), `dstory recipes` (the cookbook), `dstory --help`.
|
|
155
|
+
|
|
156
|
+
## Claims that can't drift from the data
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from dstory.prep import compute_ratio, compute_claim
|
|
160
|
+
|
|
161
|
+
ratio = compute_ratio(after=2900, before=1000) # 2.9
|
|
162
|
+
claim = compute_claim(
|
|
163
|
+
id="c1",
|
|
164
|
+
text_template="Signups {phrase} in Q1", # → "Signups tripled in Q1"
|
|
165
|
+
value=ratio,
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`compute_claim()` derives the phrase from the value using the same vocabulary
|
|
170
|
+
and windows the vetter checks ("doubled"…"quintupled", percentages), so a claim
|
|
171
|
+
built this way cannot fail data-fidelity vetting — and a hand-written claim that
|
|
172
|
+
drifts from its value blocks the build. `compute_ratio()` and
|
|
173
|
+
`compute_pct_change()` tag their result with its unit, and the claim records it
|
|
174
|
+
(`"unit": "ratio"` or `"pct"`), so a 3.4× ratio is never printed as "3.4%" and a
|
|
175
|
+
+2% change is never called "doubled". For a plain float, pass
|
|
176
|
+
`kind="ratio"` or `kind="pct"`.
|
|
177
|
+
|
|
178
|
+
## How is this different from…?
|
|
179
|
+
|
|
180
|
+
| | dstory |
|
|
181
|
+
|------|------|
|
|
182
|
+
| **Streamlit / Dash** | No server. The output is one HTML file that works from an email attachment. Narrative-first (scroll/slides), not dashboard-first. |
|
|
183
|
+
| **ipyvizzu-story** | dstory's vizzu scenes cover the same ground, then add: 5 other scene kinds (scrolly D3, full-bleed, pinned…), a brand/theming system, claim-vs-data vetting, and a11y/editorial gates. |
|
|
184
|
+
| **Quarto** | Quarto renders documents; dstory builds bespoke *graphics-led* stories with scroll-driven chart morphs, and vets the output in a real browser before you ship. |
|
|
185
|
+
| **Flourish / Datawrapper** | Code-first and version-controlled; your data never leaves your machine; no per-seat licensing. But you bring the polish — dstory brings the rails. |
|
|
186
|
+
|
|
187
|
+
## Brand system
|
|
188
|
+
|
|
189
|
+
Brands let consumers fit `dstory` to their visual system without forking the package.
|
|
190
|
+
|
|
191
|
+
A brand is a TOML file (or a `Brand` Python object) defining color tokens, typography, motion personality, atmosphere, and chart palette. The 8 bundled presets are themselves brand TOMLs.
|
|
192
|
+
|
|
193
|
+
```toml
|
|
194
|
+
# acme-brand.toml
|
|
195
|
+
[brand]
|
|
196
|
+
name = "Acme Corp"
|
|
197
|
+
extends = "minimal-light" # inherit base; override below
|
|
198
|
+
|
|
199
|
+
[colors]
|
|
200
|
+
accent = "#FF6B35"
|
|
201
|
+
accent_2 = "#1B4332"
|
|
202
|
+
|
|
203
|
+
[colors.categorical]
|
|
204
|
+
palette = ["#FF6B35", "#1B4332", "#3A86FF", "#FFBE0B", "#8338EC", "#06A77D", "#D62828"]
|
|
205
|
+
|
|
206
|
+
[typography]
|
|
207
|
+
display = "'Fraunces', Georgia, serif"
|
|
208
|
+
body = "'Inter Tight', system-ui, sans-serif"
|
|
209
|
+
google_fonts = ["Fraunces:opsz,wght@9..144,400;9..144,700",
|
|
210
|
+
"Inter+Tight:wght@400;500;600"]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Use it via CLI or Python:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
dstory init acme-story --theme ./acme-brand.toml
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from dstory import Brand, init
|
|
221
|
+
brand = Brand.from_toml("acme-brand.toml") # or .from_preset("editorial-noir").extend(accent="#FF6B35")
|
|
222
|
+
init("acme-story", brand=brand)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The 8 bundled presets:
|
|
226
|
+
|
|
227
|
+
| Name | Vibe |
|
|
228
|
+
|------|------|
|
|
229
|
+
| `editorial-noir` | Urgent, somber, magazine-cover serious |
|
|
230
|
+
| `scientific-bright` | Rigorous, optimistic, paper-like |
|
|
231
|
+
| `tech-glow` | Dense, dark, terminal-influenced |
|
|
232
|
+
| `minimal-light` | Restrained, gallery, lots of whitespace |
|
|
233
|
+
| `warm-print` | Print-magazine warmth, ledger feel |
|
|
234
|
+
| `playful-pastel` | Friendly, illustrative, kid- and student-friendly |
|
|
235
|
+
| `brutalist` | Raw, unapologetic, monospace-heavy |
|
|
236
|
+
| `data-fi` | Futurist, cinematic, chart-as-art |
|
|
237
|
+
|
|
238
|
+
## Scene kinds
|
|
239
|
+
|
|
240
|
+
A `data.json` story is composed of scenes; each declares its `kind`:
|
|
241
|
+
|
|
242
|
+
- `simple` — single chart-driven moment (headline + commentary + chart mount)
|
|
243
|
+
- `scrolly` — sticky chart + stepping text; chart morphs on each step (custom D3 logic)
|
|
244
|
+
- `bleed` — full-viewport cinematic moment
|
|
245
|
+
- `pinned` — central object pinned for one scroll-section, transformed by scroll progress
|
|
246
|
+
- `vizzu` — animated data pivots declared as `frames[]`; supports rectangle/circle/line/area geometries × cartesian/polar coordinates × all channel combos. See [`references/vizzu-chart-gallery.md`](src/dstory/references/vizzu-chart-gallery.md) for the 40+ chart types this enables.
|
|
247
|
+
- `custom` — chrome-free; the renderer owns the entire `<section>` mount. Use for kinetic typography, embedded interactives, full-bleed magazine layouts, anything the four-h2-then-mount pattern doesn't fit.
|
|
248
|
+
|
|
249
|
+
### Per-scene layout overrides
|
|
250
|
+
|
|
251
|
+
| Field | Values | Effect |
|
|
252
|
+
|------|------|------|
|
|
253
|
+
| `width` | `narrow` (~36rem) / `default` (~56rem) / `wide` (~78rem) / `full` (100%) | Override the scene's max-width without touching CSS |
|
|
254
|
+
| `chrome` | `true` (default) / `false` | When `false`, omit the auto-generated h2/p/source elements; renderer takes full control |
|
|
255
|
+
|
|
256
|
+
### Reaching every reader
|
|
257
|
+
|
|
258
|
+
`meta` fields that widen who can read (and share) the story:
|
|
259
|
+
|
|
260
|
+
| Field | Effect |
|
|
261
|
+
|------|------|
|
|
262
|
+
| `lang` / `dir` | Set `<html lang dir>` — screen-reader voice, hyphenation, RTL layout |
|
|
263
|
+
| `description` | `<meta name="description">` + Open Graph/Twitter description (falls back to `deck`) |
|
|
264
|
+
| `share_image` | og:image / twitter:image for link unfurls (absolute URL) |
|
|
265
|
+
| `hero_image` | Full-bleed hero photo with a theme-aware scrim; local files are inlined at bundle time |
|
|
266
|
+
|
|
267
|
+
Per scene, set `alt` — one sentence stating what the chart shows *and* the
|
|
268
|
+
takeaway. It becomes `role="img"` + `aria-label` on the chart mount, and the
|
|
269
|
+
vetter flags chart scenes that lack it. The template also ships a
|
|
270
|
+
skip-to-content link, a reading progress bar, `prefers-reduced-motion`
|
|
271
|
+
support, a print stylesheet (browser print → clean PDF handout), and
|
|
272
|
+
`#slide-N` deep links in slides mode.
|
|
273
|
+
|
|
274
|
+
The vetter is audience-aware too: it scores the story's prose (Flesch reading
|
|
275
|
+
ease) against `meta.audience` and notes when the writing is harder than that
|
|
276
|
+
audience should have to work for.
|
|
277
|
+
|
|
278
|
+
For project-wide CSS overrides, set `Story.extra_css` in your data:
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
story = Story.model_validate({
|
|
282
|
+
"meta": {...},
|
|
283
|
+
"extra_css": ".my-special-scene { display: grid; ... }",
|
|
284
|
+
...
|
|
285
|
+
})
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The bundler appends `extra_css` as a final `<style>` block, overriding anything in story.css.
|
|
289
|
+
|
|
290
|
+
## Story modes
|
|
291
|
+
|
|
292
|
+
By default the story plays as a scroll-driven essay. Set `meta.mode: "slides"` in `data.json` to flip it into a click/keyboard slide deck (arrow keys, click-to-advance, prev/next buttons, position indicator). Same scenes, same charts, same theme — different pacing.
|
|
293
|
+
|
|
294
|
+
| `meta.mode` | Feels like | Pick for |
|
|
295
|
+
|------|------|------|
|
|
296
|
+
| `"scroll"` (default) | A film | Published web essays, blogs, mobile, social shares |
|
|
297
|
+
| `"slides"` | A presentation | Live talks, exec briefings, screen-shared meetings, walkthroughs |
|
|
298
|
+
|
|
299
|
+
## Vetting (mandatory)
|
|
300
|
+
|
|
301
|
+
`dstory vet` (or `dstory.vet()`) grades the bundled HTML on five dimensions:
|
|
302
|
+
|
|
303
|
+
1. **Renders correctly** — headless browser at 1440/768/480, no console errors, charts paint
|
|
304
|
+
2. **Data fidelity** — every numeric claim cross-checked against `data.json` (catches "tripled" vs `value=2.4`)
|
|
305
|
+
3. **Editorial integrity** — no slop phrases, no placeholders, sources present, headlines are insights
|
|
306
|
+
4. **Visual quality** — full-page screenshots saved at all viewports for manual review
|
|
307
|
+
5. **Accessibility & ethics** — color contrast, no PII leaks, dual-axis warnings, alt text
|
|
308
|
+
|
|
309
|
+
A bundled story is "ready to ship" only when all five pass.
|
|
310
|
+
|
|
311
|
+
## Project layout
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
dstory/
|
|
315
|
+
├── src/dstory/
|
|
316
|
+
│ ├── api.py # build() — Story → bundled HTML in one call
|
|
317
|
+
│ ├── schema.py # pydantic models for data.json (Story, Meta, Scene, Claim, ...)
|
|
318
|
+
│ ├── brand.py # Brand class — TOML loader, preset extension, CSS emission
|
|
319
|
+
│ ├── scaffold.py # init() — copy template + write theme.css
|
|
320
|
+
│ ├── bundle.py # bundle() — inline CSS/JS/images/data; --vendor for offline
|
|
321
|
+
│ ├── vet.py # vet() — 5-dimension report (Playwright optional)
|
|
322
|
+
│ ├── prep.py # pandas helpers + compute_claim() (vetter-aligned)
|
|
323
|
+
│ ├── cli.py # `dstory` CLI
|
|
324
|
+
│ ├── cookbook/ # 10 complete scene recipes (+ registry & demos)
|
|
325
|
+
│ ├── themes/ # 8 preset brand TOMLs
|
|
326
|
+
│ └── templates/ # HTML/CSS/JS skeleton copied into new projects
|
|
327
|
+
├── docs/gallery/ # rendered cookbook gallery (GitHub Pages)
|
|
328
|
+
├── tests/ # 134 tests, pytest (browser e2e skips without Chromium)
|
|
329
|
+
└── pyproject.toml
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Why does this exist?
|
|
333
|
+
|
|
334
|
+
Data storytelling is two distinct jobs:
|
|
335
|
+
|
|
336
|
+
1. **Plumbing** — scaffolding, bundling, validating, vetting. Deterministic. Same bug shouldn't be re-fixed in every project.
|
|
337
|
+
2. **Creative work** — the story arc, the chart design, the annotation language, the editorial voice. Varies by piece.
|
|
338
|
+
|
|
339
|
+
`dstory` packages the plumbing so you don't reinvent it (or its bugs) per project. The creative work — written in plain JS/D3 in your `scenes/*.js` files — stays under your control. There is intentionally no `BarChart()` component in this library.
|
|
340
|
+
|
|
341
|
+
For the creative authoring guide (story arcs, audience adaptation, chart selection, scrollytelling patterns, anti-slop rules), see the companion **data-storytelling** Claude skill.
|
|
342
|
+
|
|
343
|
+
## License
|
|
344
|
+
|
|
345
|
+
MIT — see [LICENSE](LICENSE).
|