Eunoia is a Rust library for area-proportional Euler and Venn diagrams. Give it a set of sizes and intersections and it fits a layout of shapes whose areas match your data as closely as possible. It is a ground-up rewrite of the R package eulerr: faster, more flexible, and built to power bindings across many languages.
- Four shape families: fit with circles, ellipses, squares, or rectangles; the engine is shape-agnostic until fit time.
- Robust fitting: MDS initialization followed by a global/local optimization pipeline (LM, CMA-ES, trust-region) that mirrors and improves on eulerr.
- Venn diagrams too: canonical n-set Venn arrangements (circles for n≤3, ellipses for n=4-5) independent of the fitter.
- Renderable output: region polygon extraction, clipping, and automatic label placement, ready to draw as SVG.
- Many targets: pure Rust core that ships to JavaScript via WebAssembly and to other languages through a small C ABI.
cargo add eunoiause eunoia::geometry::shapes::Ellipse;
use eunoia::{DiagramSpecBuilder, Fitter, InputType};
fn main() {
let spec = DiagramSpecBuilder::new()
.set("Adventure", 20.0)
.set("Comedy", 14.0)
.set("Drama", 18.0)
.intersection(&["Adventure", "Comedy"], 6.0)
.intersection(&["Adventure", "Drama"], 5.0)
.intersection(&["Comedy", "Drama"], 4.0)
.intersection(&["Adventure", "Comedy", "Drama"], 2.0)
.input_type(InputType::Exclusive)
.build()
.unwrap();
// Swap `Ellipse` for `Circle`, `Square`, or `Rectangle`.
let layout = Fitter::<Ellipse>::new(&spec).seed(1).fit().unwrap();
println!("{} shapes, loss = {:.2e}", layout.shapes().len(), layout.loss());
}layout.shapes() returns the fitted geometry; the plotting module turns a
Layout into region polygons and label anchors for rendering. See the
rustdoc for the full API.
The pure-Rust core powers bindings in several languages, all backed by the same fitting engine:
| Language | Package | Install |
|---|---|---|
| Rust | eunoia |
cargo add eunoia |
| R | eulerr (repo) |
install.packages("eulerr") |
| Python | eunoia (repo) |
pip install eunoia |
| Julia | Eunoia.jl |
(see the package README) |
| JavaScript | @jolars/eunoia |
npm install @jolars/eunoia |
| Web app | eunoia.bz | build diagrams in the browser |
WebAssembly bindings are published as
@jolars/eunoia:
import { euler, venn } from "@jolars/eunoia";
// Fit an Euler diagram from set sizes
const layout = euler({
sets: { A: 5, B: 2, "A&B": 1 },
shape: "circle", // "circle" | "ellipse" | "square" | "rectangle"
output: "shapes", // "shapes" | "polygons" | "regions"
inputType: "exclusive", // "exclusive" | "inclusive"
seed: 42,
});
if (layout.mode === "shapes" && layout.shape === "circle") {
for (const c of layout.circles) {
console.log(c.label, c.x, c.y, c.radius);
}
}
// Or build a canonical n-set Venn diagram
const v = venn({ n: 3, output: "regions" });A renderer-agnostic SVG serializer is available at @jolars/eunoia/svg. The
default entry is built with wasm-pack --target bundler, so it works with any
modern bundler (Vite, Webpack, Rollup, esbuild) and Node 20+.
The default entry imports the .wasm module directly, which only a bundler can
resolve. For a plain HTML page, an Observable
notebook, or any environment without a build step, use the @jolars/eunoia/web
entry instead: a single self-contained ESM file with the WebAssembly module
inlined. Call init() once before fitting:
<script type="module">
import { euler, init } from "https://esm.sh/@jolars/eunoia/web";
await init(); // instantiate the embedded WebAssembly module (once)
const layout = euler({ sets: { A: 5, B: 2, "A&B": 1 } });
console.log(layout.circles);
</script>In Observable:
eunoia = import("https://esm.sh/@jolars/eunoia/web")
await eunoia.init()
layout = eunoia.euler({ sets: { A: 5, B: 2, "A&B": 1 } })@jolars/eunoia/svg is pure JavaScript (no WebAssembly), so it already works
directly from a CDN with no init().
Full runnable examples (a Quarto document and a standalone HTML page) are in
examples/.
- Narrative docs: eunoia.bz/docs/
- Rust API reference: docs.rs/eunoia
Eunoia is distributed under the terms of either the MIT license or the Apache License 2.0, at your option.

