npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@broberg/bodymap

v0.2.4

Published

Interactive body pain-map: a body a patient clicks to mark where it hurts, producing a structured PainReport (bodymap/v1). Framework-neutral core (region taxonomy + zod PainReport model + selection) + a 2D SVG React renderer + a rotatable 3D renderer (van

Readme

@broberg/bodymap

Interactive body pain-map: a genderless body a patient clicks to mark where it hurts, producing a structured PainReport (the shared bodymap/v1 wire format) — never a bare image. The clinical region taxonomy, the data model and the selection engine are framework-neutral; the renderer is swappable. This release ships the headless core, a 2D SVG React renderer (front + back view) and a rotatable 3D renderer (vanilla three.js) — all on the same core and the same bodymap/v1 wire.

npm i @broberg/bodymap

The data model

One PainReport is an array of points — one per marked region, latest wins:

type PainPoint = {
  region: string;      // a region key, e.g. "shoulder_left"
  intensity: number;   // 0–10
  type?: "stikkende" | "dump" | "konstant" | "jagende";
  timestamp: string;   // ISO
};
type PainReport = PainPoint[];

The taxonomy is the authoritative fd-sundhed clinical set: 18 side-less codes (HEAD, NECK, SHOULDER, UARM, ELBOW, FARM, WRIST, HAND, CHEST, THORA, LUMBAR, HIP, GROIN, THIGH, KNEE, LOWLEG, ANKLE, FOOT) × a side (left / right / centre) → 30 concrete regions. REGIONS is the full list; getRegion(key) looks one up.

Core (no React)

import { createPainSelection, serializeReport, deserializeReport } from "@broberg/bodymap";

const sel = createPainSelection();
sel.set("shoulder_left", 7, "jagende");
sel.set("lumbar", 4);
sel.getReport();                 // validated PainReport

const wire = serializeReport(sel.getReport(), { view: "front" });
// → { schema: "bodymap/v1", view: "front",
//     points: [{ region: "SHOULDER", side: "left", intensity: 7, quality: "jagende" }, …] }

deserializeReport(wire);         // bodymap/v1 → internal PainReport

serializeReport maps each region key to its side-less clinical code + side, so the report is portable across the web components and the native mobile apps. painReportSchema (zod) is exported for validating untrusted input.

2D renderer (React)

import { BodyMap } from "@broberg/bodymap/react";

<BodyMap
  defaultValue={[]}                        // or `value` for a controlled component
  onChange={(report) => save(report)}      // full validated PainReport on every change
  config={{ groin: { visible: false } }}   // per-app region toggles (optional)
/>;

Click a region → pick intensity (0–10) and quality → the region fills by intensity and onChange fires. No native form controls; every control carries a data-testid (bodymap-region-<key>) for E2E/visual testing. react is an optional peer dependency — the core works without it.

Per-app region config

type RegionConfig = Record<string, { visible?: boolean; selectable?: boolean }>;

An absent key is visible + selectable. Hidden ⇒ never selectable. resolveRegions and isSelectable apply the config for custom renderers.

3D renderer (React + three.js)

A rotatable 3D body on the same core + wire — a realistic Blender Studio human base mesh (CC0), drag to rotate, scroll to zoom, hover to highlight, click a body part to mark pain (the part colours by intensity). Vanilla three.js (not react-three-fiber) so it runs in React, Preact and a Capacitor webview alike.

import { BodyMap3D } from "@broberg/bodymap/three";

<BodyMap3D
  models={{ male: "/models/body-male.glb", female: "/models/body-female.glb" }}
  onChange={(report) => save(report)}   // same PainReport (bodymap/v1)
  palette={palette}                      // same BodymapPalette
  locale="da"                            // da | en (region names + UI)
  autoRotate={false}                     // idle when static (battery + Lens-friendly)
  canvasHeight="45vh"                    // shorter body on mobile → picker panel stays visible
  showRegionCode={false}                 // hide the clinical code-badge in patient-facing flows
/>;
  • three is an optional peer — only this subpath pulls it in (npm i three). The 2D renderer + core stay three-free.
  • You host the models. The package ships reference GLBs at @broberg/bodymap/models/body-male.glb + …/body-female.glb (~512 KB each) — copy them to your public/ (or import url from "@broberg/bodymap/models/body-male.glb?url" with a bundler that emits asset URLs) and pass the URLs via models. Nothing is bundled into the JS, and no model is fetched from a third party.
  • On-demand rendering. The scene renders only while auto-rotating or while a gesture is settling, then goes idle — so it doesn't drain a phone's battery and a headless Lens/Playwright run can actually land clicks.
  • WebGL-safe. With no WebGL context the component renders a graceful fallback instead of crashing.
  • Same contract as 2D. onChange emits the identical PainReport, so swapping <BodyMap> for <BodyMap3D> needs zero change to your report handling. It honours the per-app config too — a non-selectable region isn't pickable in 3D either.
  • Body type from a profile. Pass sex="male" | "female" to run it fully controlled (the parent owns it), and showSexToggle={false} to hide the picker — common when the body type comes from the user's profile and a mid-flow toggle would just be noise. Omit both to get the built-in toggle (uncontrolled).
  • Mobile panel visibility. The picker panel is a flex-sibling below the body; on a narrow viewport a full-height body pushes it a screen down. Pass canvasHeight (default "60vh"; a bare number = px) — a shorter body on mobile keeps the panel visible the moment a region is selected. You drive the responsive value; the package stays non-opinionated (no forced bottom-sheet).
  • Hide the code-badge. The selection panel shows the region's clinical code ("HIP · right") next to the readable name — useful for a clinician, noise in a patient-facing flow. Pass showRegionCode={false} (default true) to hide it and keep only the human name. The badge carries data-testid="bodymap3d-region-code".
  • Report view. Lock every region (config with selectable: false) and the interactive hover-hint is suppressed — a locked 3D body reads as a report, not a form. Set ui.hoverHint to show a display caption instead.
  • No WebXR/VR. This is a rotatable in-page 3D canvas, not an immersive session — WebXR is intentionally out (a Capacitor webview can't host it reliably).
  • Every control carries a data-testid (bodymap3d-canvas, bodymap3d-sex-*, bodymap3d-intensity-<n>, bodymap3d-type-<quality>, bodymap3d-region-code, bodymap3d-ready).

Colour control — BodymapPalette

Both renderers theme off one palette (consumer-defined):

import { defaultPalette, heatFor, baseColorFor, type BodymapPalette } from "@broberg/bodymap";

const palette: BodymapPalette = {
  body: "#c8ccdd",
  hover: "#5CC4B7",
  selected: "#141969",
  heat: { low: "#FFE049", mid: "#F09A3E", high: "#D61C64" },
  regions: { chest: "#e6e9f2" },   // optional per-region base colours
};

heatFor(8, palette);               // → "#D61C64"  (intensity → heat colour)
baseColorFor("chest", palette);    // → "#e6e9f2"  (per-region override, else body)

Roadmap

  • True per-zone mesh segmentation for the 3D renderer (v0.2.0 assigns each vertex to its nearest region anchor; sharp painted zones come next)
  • Preact adapter

License

MIT · part of the @broberg/* shared inventory.