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.
Files changed (39) hide show
  1. dstory-0.6.0/.gitignore +34 -0
  2. dstory-0.6.0/LICENSE +21 -0
  3. dstory-0.6.0/PKG-INFO +345 -0
  4. dstory-0.6.0/README.md +309 -0
  5. dstory-0.6.0/pyproject.toml +72 -0
  6. dstory-0.6.0/src/dstory/__init__.py +33 -0
  7. dstory-0.6.0/src/dstory/api.py +111 -0
  8. dstory-0.6.0/src/dstory/brand.py +243 -0
  9. dstory-0.6.0/src/dstory/bundle.py +406 -0
  10. dstory-0.6.0/src/dstory/cli.py +240 -0
  11. dstory-0.6.0/src/dstory/cookbook/__init__.py +290 -0
  12. dstory-0.6.0/src/dstory/cookbook/annotated-scatter.js +93 -0
  13. dstory-0.6.0/src/dstory/cookbook/bar-steps.js +83 -0
  14. dstory-0.6.0/src/dstory/cookbook/beeswarm.js +87 -0
  15. dstory-0.6.0/src/dstory/cookbook/bump-chart.js +92 -0
  16. dstory-0.6.0/src/dstory/cookbook/dot-timeline.js +83 -0
  17. dstory-0.6.0/src/dstory/cookbook/line-reveal.js +79 -0
  18. dstory-0.6.0/src/dstory/cookbook/range-dots.js +98 -0
  19. dstory-0.6.0/src/dstory/cookbook/slope-chart.js +90 -0
  20. dstory-0.6.0/src/dstory/cookbook/small-multiples.js +94 -0
  21. dstory-0.6.0/src/dstory/cookbook/waffle.js +70 -0
  22. dstory-0.6.0/src/dstory/prep.py +240 -0
  23. dstory-0.6.0/src/dstory/references/vizzu-chart-gallery.md +383 -0
  24. dstory-0.6.0/src/dstory/scaffold.py +408 -0
  25. dstory-0.6.0/src/dstory/schema.py +183 -0
  26. dstory-0.6.0/src/dstory/templates/index.html +97 -0
  27. dstory-0.6.0/src/dstory/templates/scenes/example-line-reveal.js +103 -0
  28. dstory-0.6.0/src/dstory/templates/scenes/example-scrolly.js +82 -0
  29. dstory-0.6.0/src/dstory/templates/story.css +497 -0
  30. dstory-0.6.0/src/dstory/templates/story.js +662 -0
  31. dstory-0.6.0/src/dstory/themes/brutalist.toml +46 -0
  32. dstory-0.6.0/src/dstory/themes/data-fi.toml +46 -0
  33. dstory-0.6.0/src/dstory/themes/editorial-noir.toml +46 -0
  34. dstory-0.6.0/src/dstory/themes/minimal-light.toml +46 -0
  35. dstory-0.6.0/src/dstory/themes/playful-pastel.toml +46 -0
  36. dstory-0.6.0/src/dstory/themes/scientific-bright.toml +46 -0
  37. dstory-0.6.0/src/dstory/themes/tech-glow.toml +46 -0
  38. dstory-0.6.0/src/dstory/themes/warm-print.toml +46 -0
  39. dstory-0.6.0/src/dstory/vet.py +627 -0
@@ -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).