JavaScript API Reference

The public surface of @jolars/eunoia. This page catalogs the exported functions and their options; the shipped TypeScript definitions remain the exhaustive, field-level source. New here? Start with the JavaScript quickstart.

All functions are pure (no shared state) and synchronous, except init() on the ./web entry, which must be awaited once before fitting.

euler(options)

Fits an area-proportional Euler diagram from set sizes and returns a Layout.

import { euler } from "@jolars/eunoia";

const layout = euler({ sets: { A: 5, B: 3, "A&B": 1.5 } });
OptionTypeDefaultMeaning
setsRecord<string, number>(required)Sizes keyed by combination expression ("A", "A&B", …).
inputType"exclusive" \| "inclusive""exclusive"Whether values are exclusive pieces or full set unions.
shape"circle" \| "ellipse" \| "square" \| "rectangle""circle"Shape family to fit with.
output"shapes" \| "polygons" \| "regions""shapes"What the Layout carries (see Layout).
seednumber \| bigintRNG seed for reproducible fits.
optimizerOptimizercore defaultOptimization algorithm (see below).
lossLossTypeoptimizer’sRegion-error loss function.
tolerancenumberOptimizer convergence tolerance.
restartsnumber10Random restarts of the two-phase fit; lowest loss is kept.
polygonVerticesnumber256Vertices per polygon outline ("polygons"/"regions").
complementnumberItems outside every set; fits a bounding container. See Complement.

Optimizer is one of "cmaEsTrf", "cmaEsLm", "cmaEs", "levenbergMarquardt", "trf", "lbfgs", "nelderMead". LossType is one of "sumSquared", "sumAbsolute", "sumAbsoluteRegionError", "sumSquaredRegionError", "maxAbsolute", "maxSquared", "rootMeanSquared", "stress", "diagError". The defaults match the Rust core and are what you want unless you are deliberately exploring; see the Fitter Pipeline chapter.

venn(options)

Lays out a canonical n-set Venn diagram (fixed template, not proportional—set sizes are ignored). Returns a Layout.

import { venn } from "@jolars/eunoia";

const layout = venn({ n: 3 }); // 1 ≤ n ≤ 5
OptionTypeDefaultMeaning
nnumber(required)Number of sets.
shape"circle" \| "ellipse" \| "square" \| "rectangle""ellipse"Shape family.
output"polygons" \| "regions""polygons"What the Layout carries.
polygonVerticesnumber256Vertices per outline.
complementnumberOptional complement container.

Only "ellipse" reaches n = 5; "circle" gives the classic 1–3-circle diagrams, and "square"/"rectangle" are axis-aligned. The non-ellipse shapes cap at n = 3.

The Layout Object

Both euler and venn return a Layout, a union discriminated on mode, which follows the output option:

outputmodeCarries
"shapes""shapes"circles/ellipses/squares/rectangles, the primitive params.
"polygons""polygons"the shapes plus polygons, one closed outline per set.
"regions""regions"regions, the exclusive pieces (A only, A&B, …) + setAnchors + shapeOutlines.

Every Layout also carries:

  • shape: the shape family used ("circle", "ellipse", …).
  • metrics: { loss, stress, diagError, iterations, targetAreas, fittedAreas, regionError, residuals }. See Goodness of Fit.
  • container: present only when complement was set: { x, y, width, height }.

Each shape and region carries a labelAnchor ({ x, y }) for interior labels; "regions" mode adds setAnchors and shapeOutlines. The latter is one closed ring per set — the set’s own silhouette, which region decomposition clips away and so cannot be recovered from regions without a union pass. Use it for seam-free per-set strokes, or to feed placeSetLabels. Coordinates are abstract, centered units—compute a bounding box for your viewBox.

placeLabelsForRegions(options)

Places one label per region, choosing interior or exterior positions (with leader lines) based on whether each region can hold its measured label. Returns Record<string, LabelPlacement> keyed by combination.

import { placeLabelsForRegions } from "@jolars/eunoia";

const placements = placeLabelsForRegions({
  regions: layout.regions,
  container: layout.container,
  sizes: { A: { w: 0.4, h: 0.2 }, "A&B": { w: 0.3, h: 0.15 } },
  strategy: { leader: { type: "straight" }, tether: "poi" },
});

sizes are the measured label dimensions in diagram units, which usually means a render → measure → re-place loop. The strategy knobs (leader, precision, tether, leaderGap) and that loop are covered in Label Placement.

placeSetLabels(options)

Places one label per set, just outside that set’s own shape and with no leader line — the label is adjacent to the thing it names. Returns Record<string, LabelPlacement> keyed by set name, every entry with kind: "exteriorSet".

import { placeSetLabels } from "@jolars/eunoia";

const setPlacements = placeSetLabels({
  outlines: layout.shapeOutlines,
  sizes: { A: { w: 0.4, h: 0.2 }, B: { w: 0.4, h: 0.2 } },
  strategy: { obstacles: labelObstacles({ placements, sizes, padding: 0.15 }) },
});

Each label rotates around its shape to the angle with the most clearance to the other sets’ outlines, the labels already placed, and any strategy.obstacles. Label boxes never overlap each other; clearance from the shapes is best-effort. The knobs (margin, angularSteps, obstacles, precision) and the algorithm are covered in Label Placement.

placementsBbox(options)

Returns the bounding box ({ x, y, width, height }, or undefined) enclosing a set of placed label boxes; useful for extending the viewport so exterior labels aren’t clipped.

placeGlyphsForRegions(options)

Packs equally-sized circular glyphs—one mark per data unit, eulerGlyphs-style—inside each region. Returns { radius, positions, unplaced? }: the single diagram-wide radius plus the glyph centers per combination.

import { placeGlyphsForRegions } from "@jolars/eunoia";

const glyphs = placeGlyphsForRegions({
  regions: layout.regions,
  counts: { A: 20, B: 12, "A&B": 5 },
  options: { arrangement: "uniform" }, // or "random" with a seed
});

Omit options.radius to auto-size the glyphs (largest radius at which every region holds its count). Arrangements, the gap knob, and the fixed-radius overflow behavior are covered in Glyphs. Pass the result straight to the SVG serializer via ToSvgOptions.glyphs.

options.obstacles takes keep-out boxes ({ x, y, width, height }, center plus full extents) that glyph centers steer clear of—see Label keep-out.

placeGlyphBoxesForRegions(options)

Packs per-item text boxes—member names—inside each region. The rectangular-footprint sibling of placeGlyphsForRegions: hand it the { w, h } you measured for every item and it returns { scale, boxes, unplaced? }—one centered { x, y, width, height } per item, plus the single diagram-wide factor those boxes were packed at.

import { placeGlyphBoxesForRegions } from "@jolars/eunoia";

const members = { A: ["Ada", "Grace"], "A&B": ["Katherine"] };
const sizes = Object.fromEntries(
  Object.entries(members).map(([k, names]) => [k, names.map(measure)]),
);

const placed = placeGlyphBoxesForRegions({
  regions: layout.regions,
  sizes,
  options: { arrangement: "uniform" }, // or "random" with a seed
});
// Render your text at `fontSize * placed.scale`.

Omit options.scale to auto-fit. Auto-scale only ever shrinks (the bracket is [minScale, 1], default floor 0.35), since you own the reference font size—deliberately asymmetric with the disc packer, which grows to fill.

boxes[combo] is a prefix of sizes[combo], so boxes[combo][i] belongs to your labels[combo][i]. Pass the result plus the strings to the SVG serializer via ToSvgOptions.glyphBoxes. options.obstacles takes the same keep-out boxes as placeGlyphsForRegions. See Member labels.

labelObstacles(options)

Turns a placeLabelsForRegions result plus the sizes you measured into the keep-out boxes placeGlyphsForRegions expects, each padded by an optional padding on every side. Pure JS—no WebAssembly call.

import { labelObstacles, placeGlyphsForRegions, placeLabelsForRegions } from "@jolars/eunoia";

const placements = placeLabelsForRegions({ regions: layout.regions, sizes });
const glyphs = placeGlyphsForRegions({
  regions: layout.regions,
  counts: { A: 20, B: 12, "A&B": 5 },
  options: { obstacles: labelObstacles({ placements, sizes, padding: 0.15 }) },
});

SVG Serializer: @jolars/eunoia/svg

A wasm-free, DOM-free entry that turns a Layout into SVG. It never touches WebAssembly, so it works during server-side rendering and from a CDN as-is.

import { toSvg } from "@jolars/eunoia/svg";

document.body.innerHTML = toSvg(layout, { showLabels: true });
  • toSvg(layout, opts?): a complete standalone <svg> document string.
  • svgBody(layout, opts?): inner markup only, when you own the <svg> element.
  • viewBox(layout, opts?): the { x, y, w, h } viewBox for a layout.
  • boundingBox(layout, opts?): geometry bounds, including exterior labels when placements + sizes are supplied.
  • polygonPath, regionPath, leaderPath: SVG d path builders.
  • Color helpers: PALETTES, paletteColors, defaultColorFor, colorForSet, mixColors, plus nestedSets/regionTitleLines for region labeling.

ToSvgOptions covers palettes and per-set color overrides, opacity, strokes, label and count rendering, a legend, padding, the placements/labelSizes from placeLabelsForRegions for exterior labels with leader lines, the setLabelPlacements/setLabelSizes from placeSetLabels (which move every set name outside its shape and suppress the interior copies), and the glyphs result from placeGlyphsForRegions (rendered above region fills, below labels; tinted from each region’s own color with a finer, darker edge by default, tunable via tint, fill, stroke, and strokeWidth).

showCounts prints each region’s own area. When the numbers you want are not the geometry — a Venn layout is topological, so its areas are not quantities at all — pass counts, keyed by canonical combination:

toSvg(vennLayout, { showCounts: true, counts: { A: 12, "A&B": 4 } });

A supplied counts map is authoritative: a region with no entry draws no count, which is what lets you annotate only the regions you have numbers for.

glyphBoxes renders a placeGlyphBoxesForRegions result in the same slot. The placer deliberately carries no text, so pass the strings alongside it as labels, index-aligned with boxes:

toSvg(layout, {
  glyphBoxes: { scale: placed.scale, boxes: placed.boxes, labels: members, fontSize: 12 },
});

fontSize is the reference size you measured at—the rendered size is fontSize * scale, emitted once on each region’s <g>. color, fontWeight, fontFamily, and className override the defaults, and background (off by default) adds a rounded chip behind each label, tinted from the region color like the glyph discs. glyphs and glyphBoxes may both be set; discs draw first.

Interactivity: tooltips and data hooks

The serializer can attach hover tooltips and identifying attributes to each region (and each set shape) so a host page can layer interactivity on top of the static markup. Three ToSvgOptions fields drive this, and each hook receives a RegionInfo ({ combination, sets, area }):

  • tooltip(info) => string | null — a non-empty return becomes a <title> child on the fill element, giving native (zero-JS) browser hover tooltips.
  • interactive: boolean — emits data-combination and data-area on every fill, a payload-free hook for event delegation.
  • regionAttrs(info) => Record<string, string | number | null> — arbitrary extra attributes (typically data-*); nullish values are skipped, and keys override the interactive defaults.
const svg = toSvg(layout, {
  interactive: true, // -> data-combination, data-area on each fill
  tooltip: (r) => `${r.combination}: ${r.area.toFixed(0)}`,
});

Member names. Eunoia only sees set and intersection sizes, never the underlying elements, so the combination -> members mapping is yours to build (set algebra over your own data). For small lists, format them in tooltip. For large lists (e.g. gene sets), don’t bake them into the file — enable interactive and look the members up on hover from a JS-side map keyed on data-combination, keeping the SVG small.

Bundler-Less Entry: @jolars/eunoia/web

A single self-contained ESM file with the WebAssembly inlined. Re-exports everything from the default entry, plus:

  • init(): Promise<void>: instantiate the embedded wasm once; idempotent. Must be awaited before euler/venn.
<script type="module">
  import { euler, init } from "https://esm.sh/@jolars/eunoia/web";
  await init();
  const layout = euler({ sets: { A: 5, B: 3, "A&B": 1.5 } });
</script>

For exact field-level types, rely on the TypeScript definitions shipped with the package; your editor’s “go to definition”/IntelliSense will surface them.

Documentation for Eunoia v1.9.0