Skip to content

jolars/eunoia

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

662 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Eunoia

Build and Test Crates.io npm version PyPI version CRAN Badge codecov

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.

An area-proportional Euler diagram of three overlapping sets labelled Adventure, Comedy, and Drama.

Highlights

  • 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.

Quick Start (Rust)

cargo add eunoia
use 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.

Web App

Eunoia powers a web app for interactive diagram fitting and exploration, located at https://eunoia.bz/app. The source code for the app resides in the web/ directory of this repo and is built on the JavaScript (npm) package, using Svelte.

The Eunoia Ecosystem

The pure-Rust core powers bindings in several languages, all backed by the same fitting engine:

Language Package Install
R eulerr (repo) install.packages("eulerr")
Python eunoia (repo) pip install eunoia
Julia Eunoia.jl (repo) ]add Eunoia
JavaScript @jolars/eunoia (this repo) npm install @jolars/eunoia

JavaScript/TypeScript

A TypeScript package is published as @jolars/eunoia. Here's a minimal example of fitting an Euler diagram in Node or a bundler environment:

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+.

Browser/Observable (No Bundler)

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/.

Documentation

License

Eunoia is distributed under the terms of either the MIT license or the Apache License 2.0, at your option.

About

A Rust library for Euler and Venn Diagrams

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Contributing

Stars

11 stars

Watchers

1 watching

Forks

Sponsor this project

 

Contributors