Hugin vs Matplotlib
Side-by-side examples comparing Hugin (OCaml) with Matplotlib (Python). Hugin uses a declarative, pipeline-oriented API while Matplotlib uses an imperative, object-oriented approach.
Key Differences
| Hugin | Matplotlib | |
|---|---|---|
| Style | Declarative, immutable specs | Imperative, mutable state |
| Composition | \|> pipeline |
Method calls on axes |
| State | No global state | plt global state |
| Colors | OKLCH color space | sRGB strings |
| Output | render_png, render_svg, show |
plt.savefig, plt.show |
Line Plot
Hugin:
open Hugin
let () =
let x = Nx.linspace Nx.float32 0. (2. *. Float.pi) 100 in
layers [
line ~x ~y:(Nx.sin x) ~label:"sin(x)" ~color:Color.blue ();
line ~x ~y:(Nx.cos x) ~label:"cos(x)" ~color:Color.vermillion
~line_style:`Dashed ();
]
|> title "Trigonometric Functions"
|> xlabel "Angle (radians)"
|> ylabel "Value"
|> ylim (-1.2) 1.2
|> grid_lines true
|> legend
|> render_png "trig.png"
Matplotlib:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 100)
plt.figure()
plt.plot(x, np.sin(x), label="sin(x)", color="blue")
plt.plot(x, np.cos(x), label="cos(x)", color="red", linestyle="--")
plt.title("Trigonometric Functions")
plt.xlabel("Angle (radians)")
plt.ylabel("Value")
plt.ylim(-1.2, 1.2)
plt.grid(True)
plt.legend()
plt.savefig("trig.png")
Scatter Plot
Hugin:
open Hugin
let () =
let x = Nx.rand Nx.float32 [| 200 |] in
let y = Nx.rand Nx.float32 [| 200 |] in
let c = Nx.add x y in
point ~x ~y ~color_by:c ~size:8. ~marker:Circle ()
|> title "Random Scatter"
|> render_png "scatter.png"
Matplotlib:
import numpy as np
import matplotlib.pyplot as plt
x = np.random.rand(200)
y = np.random.rand(200)
c = x + y
plt.figure()
plt.scatter(x, y, c=c, s=64, marker="o")
plt.title("Random Scatter")
plt.colorbar()
plt.savefig("scatter.png")
Bar Chart
Hugin:
open Hugin
let () =
let x = Nx.create Nx.float32 [| 4 |] [| 1.; 2.; 3.; 4. |] in
let h = Nx.create Nx.float32 [| 4 |] [| 3.; 7.; 2.; 5. |] in
bar ~x ~height:h ~color:Color.orange ()
|> title "Quarterly Revenue"
|> xticks [ (1., "Q1"); (2., "Q2"); (3., "Q3"); (4., "Q4") ]
|> ylabel "Revenue ($M)"
|> render_png "bar.png"
Matplotlib:
import matplotlib.pyplot as plt
x = [1, 2, 3, 4]
h = [3, 7, 2, 5]
plt.figure()
plt.bar(x, h, color="orange")
plt.title("Quarterly Revenue")
plt.xticks(x, ["Q1", "Q2", "Q3", "Q4"])
plt.ylabel("Revenue ($M)")
plt.savefig("bar.png")
Histogram
Hugin:
open Hugin
let () =
let data = Nx.randn Nx.float32 [| 1000 |] in
hist ~x:data ~bins:(`Num 30) ~density:true ~color:Color.sky_blue ()
|> title "Normal Distribution"
|> xlabel "Value"
|> ylabel "Density"
|> render_png "hist.png"
Matplotlib:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randn(1000)
plt.figure()
plt.hist(data, bins=30, density=True, color="skyblue")
plt.title("Normal Distribution")
plt.xlabel("Value")
plt.ylabel("Density")
plt.savefig("hist.png")
Multi-Panel Layout
Hugin:
open Hugin
let () =
let x = Nx.linspace Nx.float32 0. (2. *. Float.pi) 100 in
let p1 = line ~x ~y:(Nx.sin x) () |> title "sin" in
let p2 = line ~x ~y:(Nx.cos x) () |> title "cos" in
let p3 = line ~x ~y:(Nx.tan x) () |> title "tan" |> ylim (-5.) 5. in
let p4 = hist ~x:(Nx.rand Nx.float32 [| 500 |]) () |> title "random" in
Layout.grid [ [ p1; p2 ]; [ p3; p4 ] ]
|> render_png "grid.png"
Matplotlib:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 100)
fig, axes = plt.subplots(2, 2)
axes[0, 0].plot(x, np.sin(x)); axes[0, 0].set_title("sin")
axes[0, 1].plot(x, np.cos(x)); axes[0, 1].set_title("cos")
axes[1, 0].plot(x, np.tan(x)); axes[1, 0].set_title("tan")
axes[1, 0].set_ylim(-5, 5)
axes[1, 1].hist(np.random.rand(500)); axes[1, 1].set_title("random")
plt.tight_layout()
plt.savefig("grid.png")
Heatmap
Hugin:
open Hugin
let () =
let data = Nx.init Nx.float32 [| 8; 10 |] (fun idx ->
let i = Float.of_int idx.(0) and j = Float.of_int idx.(1) in
Float.sin (i *. 0.5) *. Float.cos (j *. 0.4))
in
heatmap ~data ~annotate:true ~cmap:Cmap.viridis ()
|> title "Heatmap"
|> render_png "heatmap.png"
Matplotlib:
import numpy as np
import matplotlib.pyplot as plt
data = np.fromfunction(
lambda i, j: np.sin(i * 0.5) * np.cos(j * 0.4), (8, 10)
)
fig, ax = plt.subplots()
im = ax.imshow(data, cmap="viridis")
for i in range(8):
for j in range(10):
ax.text(j, i, f"{data[i, j]:.2g}", ha="center", va="center")
ax.set_title("Heatmap")
plt.colorbar(im)
plt.savefig("heatmap.png")
Styling
Hugin:
open Hugin
let () =
let x = Nx.linspace Nx.float32 0. (2. *. Float.pi) 50 in
line ~x ~y:(Nx.sin x)
~color:Color.vermillion
~line_style:`Dashed
~line_width:2.5
~marker:Triangle
~alpha:0.7
()
|> render_png "styled.png"
Matplotlib:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 50)
plt.figure()
plt.plot(x, np.sin(x), color="red", linestyle="--",
linewidth=2.5, marker="^", alpha=0.7)
plt.savefig("styled.png")
Themes
Hugin provides built-in themes with context scaling. Matplotlib uses style sheets.
Hugin:
(* Dark theme scaled for a presentation *)
let theme = Theme.dark |> Theme.talk in
line ~x ~y () |> with_theme theme |> render_png "slide.png"
Matplotlib:
plt.style.use("dark_background")
plt.rcParams.update({"font.size": 14})
plt.plot(x, y)
plt.savefig("slide.png")
Save and Export
Hugin:
let spec = line ~x ~y () |> title "My Plot" in
spec |> render_png "plot.png";
spec |> render_svg "plot.svg";
spec |> render_pdf "plot.pdf";
spec |> show (* interactive SDL window *)
Matplotlib:
plt.plot(x, y)
plt.title("My Plot")
plt.savefig("plot.png")
plt.savefig("plot.svg")
plt.savefig("plot.pdf")
plt.show()
In Hugin, the spec is an immutable value. You can render the same spec to multiple formats without rebuilding it. In Matplotlib, the figure is mutable state that savefig and show consume.
Interactive Display
Hugin:
show ~width:1600. ~height:1200. spec
The SDL window is resizable. The plot re-renders at the new dimensions. Press Escape or Q to close.
Matplotlib:
plt.show()