Skip to content

Commit 67b23cc

Browse files
committed
feat: Add first-class Graphic type with algebraic operators and PNG/SVG export
Core Features: - Graphic type with +, |||, / operators for overlay/faceting - HtmlEval instance for #eval rendering in infoview - Fluent combinators: .domain, .samples, .color, .title, etc. - Interactive sliders with source code rewriting via #iplot PNG Rendering Infrastructure: - Bitmap module for RGB colors and pixel manipulation - CRC32 and Adler32 checksums for PNG encoding - Rasterization primitives (Bresenham's line algorithm) - savePNG/saveSVG export functions Documentation Fixes: - Fixed Verso header nesting errors - Fixed mermaid code block to plain text - Converted lean code blocks to plain for examples - Updated module docstrings for Verso compatibility Also includes TestExport.lean for verifying PNG/SVG export works.
1 parent dc3ef16 commit 67b23cc

18 files changed

Lines changed: 1727 additions & 26 deletions

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ _out/
88
*.un~
99
.venv/
1010
node_modules/
11+
12+
# Generated test files
13+
*.png
14+
*.svg
15+
!doc/img/*.svg

LeanPlot.lean

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,39 @@ Interactive plotting for Lean 4.
55
66
## Main API
77
8-
Import `LeanPlot.API` and `LeanPlot.DSL` for the primary plotting functions:
9-
- `plot` - Single function plots
8+
Import `LeanPlot.Graphic` for first-class algebraic graphics:
9+
- `plot f` - Create a function plot
10+
- `scatter pts` - Scatter plot from points
11+
- `bar pts` - Bar chart from points
12+
- Algebraic operators: `+` (overlay), `|||` (horizontal facet), `/` (vertical facet)
13+
- Fluent combinators: `.domain`, `.samples`, `.color`, `.title`, etc.
14+
15+
Import `LeanPlot.API` and `LeanPlot.DSL` for legacy plotting functions:
1016
- `plotMany` - Multiple function comparison
11-
- `scatter` - Scatter plots
12-
- `bar` - Bar charts
1317
- `#plot` - Convenient syntax for quick visualization
1418
19+
## PNG/SVG Export
20+
21+
Import `LeanPlot.Render.Export` for file export:
22+
- `g.savePNG "path.png"` - Save graphic to PNG
23+
- `g.saveSVG "path.svg"` - Save graphic to SVG
24+
1525
## Advanced Features
1626
1727
- `LeanPlot.GrammarOfGraphics` - Grammar of Graphics DSL
28+
- `LeanPlot.Interactive` - Two-way slider widgets
1829
- `LeanPlot.PlotComposition` - Subplot grids and composition
1930
- `LeanPlot.Transform` - Data transformations (log, sqrt, etc.)
2031
- `LeanPlot.Faceting` - Small multiples layouts
21-
- `LeanPlot.Debug` - PNG export utilities
2232
2333
## Demos
2434
2535
See `LeanPlot.Demos.*` for example usage.
2636
-/
2737

2838
-- Core API (what users should import)
39+
import LeanPlot.Graphic -- First-class algebraic graphics
40+
import LeanPlot.Interactive -- Two-way slider widgets
2941
import LeanPlot.API
3042
import LeanPlot.DSL
3143
import LeanPlot.ToFloat
@@ -51,6 +63,14 @@ import LeanPlot.Faceting
5163
import LeanPlot.Debug
5264
import LeanPlot.Metaprogramming
5365

66+
-- Rendering / Export
67+
import LeanPlot.Render.Bitmap
68+
import LeanPlot.Render.Rasterize
69+
import LeanPlot.Render.PNG.CRC32
70+
import LeanPlot.Render.PNG.Adler32
71+
import LeanPlot.Render.PNG.Encode
72+
import LeanPlot.Render.Export
73+
5474
-- Internal (re-exported for compatibility)
5575
import LeanPlot.Series
5676
import LeanPlot.Core

LeanPlot/API.lean

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ These are now the **recommended** way to create plots in LeanPlot.
9090
/-- **Simple line chart** - Just pass your function, get beautiful plot!
9191
9292
Examples:
93-
```lean
93+
```
9494
#plot plot (fun t => t^2) -- Automatic "time" labels
9595
#plot plot (fun x => Float.sin x) -- Automatic "x" labels
9696
#plot plot (fun i => i * 3) (steps := 100) -- Custom sample count
@@ -106,7 +106,7 @@ This is the new recommended way to plot functions. Zero configuration needed!
106106
/-- **Simple multi-function plot** - Multiple functions, automatic everything!
107107
108108
Examples:
109-
```lean
109+
```
110110
#plot plotMany #[("sin", fun x => Float.sin x), ("cos", fun x => Float.cos x)]
111111
#plot plotMany #[("linear", fun t => t), ("quadratic", fun t => t^2)] (domain := (0.0, 2.0))
112112
```
@@ -122,7 +122,7 @@ Automatic colors, legend, and labels. Perfect for comparing functions!
122122
123123
Examples:
124124
125-
```lean
125+
```
126126
#plot scatter (fun x => x + Random.rand) -- Show function with noise
127127
#plot scatter (fun t => Float.sin t) (steps := 50) -- Fewer points
128128
```
@@ -135,7 +135,7 @@ Examples:
135135
/-- **Simple bar chart** - Bars with automatic styling!
136136
137137
Examples:
138-
```lean
138+
```
139139
#plot bar (fun i => i^2) (steps := 10) -- Discrete function as bars
140140
#plot bar (fun x => Float.floor x) (steps := 20) -- Step function
141141
```

LeanPlot/Demos/BarDemo.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import LeanPlot.Plot
33

44
open LeanPlot
55
open LeanPlot.PlotSpec
6-
6+
set_option doc.verso true
77
namespace LeanPlot.Demos
88

9-
/-- A tiny dataset of five bars illustrating the `bar` constructor. -/
9+
/-- A tiny dataset of five bars illustrating the {name}`bar` constructor. -/
1010
@[inline] def barData : Array (Float × Float) := #[(0.0, 1.0), (1.0, 2.0), (2.0, 3.0), (3.0, 2.0), (4.0, 1.0)]
1111

1212
/- Render the bar chart. -/

LeanPlot/Demos/SimpleSyntaxDemo.lean

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@ namespace LeanPlot.Demos.SimpleSyntaxDemo
2121
/-- Example 2: With custom sample count -/
2222
#plot (fun t => Float.sin t) using 400
2323

24-
/-- Example 3: Works with any function
24+
/-- Example 3: Works with any function -/
2525
#plot (fun x => Float.exp (-x) * Float.cos (3 * x))
2626

27-
-- Example 4: Without parentheses also works
27+
/-- Example 4: Without parentheses also works -/
2828
#plot fun x => Float.tanh (x - 1)
2929

30-
-- Example 5: With type annotations
30+
/-- Example 5: With type annotations -/
3131
#plot (fun x : Float => x^3 - 2*x)
3232

3333
/-! ## Doc Comments as Captions
3434
3535
You can add a doc comment before `#plot` to display a caption:
3636
-/
3737

38-
/-- The classic parabola y = x² -/
38+
/-- The classic quadratic function y = x² -/
3939
#plot (fun x => x^2)
4040

4141
/-- A damped oscillation: exponential decay × cosine -/
@@ -68,4 +68,66 @@ Now you can write:
6868
It's that simple!
6969
-/
7070

71+
/-! ## Syntax Experiment: Exactly 0 or 2 Doc Strings
72+
73+
Just for fun, here's a command that requires either no doc strings or exactly two
74+
(title + subtitle). Uses `(docComment docComment)?` pattern.
75+
-/
76+
77+
open Lean Elab Command ProofWidgets in
78+
/-- A plot command that takes exactly 0 or 2 doc strings (title + subtitle). -/
79+
syntax (name := plot2docs) (docComment docComment)? "#plot₂ " term : command
80+
81+
open Lean Elab Command ProofWidgets LeanPlot.PlotCommand in
82+
/-- A plot command that takes exactly 0 or 2 doc strings (title + subtitle). -/
83+
@[command_elab plot2docs]
84+
def elabPlot2Docs : CommandElab := fun stx => do
85+
let (title?, subtitle?, term) ← match stx with
86+
| `($t:docComment $s:docComment #plot₂ $e:term) =>
87+
pure (some t.getDocString, some s.getDocString, e)
88+
| `(#plot₂ $e:term) =>
89+
pure (none, none, e)
90+
| _ => throwUnsupportedSyntax
91+
let wrappedStx ← `(LeanPlot.API.plot $term)
92+
let htX ← liftTermElabM <| HtmlCommand.evalCommandMHtml <| ← ``(ProofWidgets.HtmlEval.eval $wrappedStx)
93+
let ht ← htX
94+
-- Build HTML with optional title + subtitle
95+
let finalHtml := match title?, subtitle? with
96+
| some t, some s =>
97+
let style : Json := Json.mkObj [
98+
("display", "flex"), ("flexDirection", "column"), ("marginBottom", "8px")
99+
]
100+
let titleStyle : Json := Json.mkObj [
101+
("fontSize", "16px"), ("fontWeight", "600"), ("color", "#1f2937"),
102+
("fontFamily", "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace")
103+
]
104+
let subStyle : Json := Json.mkObj [
105+
("fontSize", "12px"), ("color", "#6b7280"), ("fontStyle", "italic"),
106+
("fontFamily", "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace")
107+
]
108+
Html.element "div" #[] #[
109+
Html.element "div" #[("style", style)] #[
110+
Html.element "div" #[("style", titleStyle)] #[.text t],
111+
Html.element "div" #[("style", subStyle)] #[.text s]
112+
],
113+
ht
114+
]
115+
| _, _ => ht
116+
liftCoreM <| Widget.savePanelWidgetInfo
117+
(hash ProofWidgets.HtmlDisplayPanel.javascript)
118+
(return json% { html: $(← Server.rpcEncode finalHtml) })
119+
stx
120+
121+
-- ✅ Works: No doc strings
122+
#plot₂ (fun x => x^2)
123+
124+
-- ✅ Works: Exactly two doc strings (title + subtitle)
125+
/-- The Parabola -/
126+
/-- A classic quadratic function showing y = x² -/
127+
#plot₂ (fun x => x^2)
128+
129+
-- ❌ Won't parse: Just one doc string (try uncommenting!)
130+
-- /-- Only one doc string -/
131+
-- #plot₂ (fun x => x^3)
132+
71133
end LeanPlot.Demos.SimpleSyntaxDemo

0 commit comments

Comments
 (0)