Body Muscles

Body Muscles

Framework-agnostic interactive SVG body map with 70+ anatomical regions, intensity visualization, and zero dependencies.

$ npm install body-muscles

Features

0

Zero Dependencies

Pure TypeScript. No React, no framework lock-in. Works everywhere the DOM exists.

70+ Regions

Detailed anatomical mapping covering head, torso, arms, legs, hands and feet.

Intensity Scale

0-10 color gradient from neutral through yellow, orange, to deep red.

Dual Views

Anterior (front) and posterior (back) views with smooth transitions.

TypeScript First

Full type definitions, declaration maps, and ESM + CJS dual publish.

Accessible

Keyboard navigation, ARIA labels, and semantic roles on every path.

Interactive Demo

Click any muscle region to select it. Use the controls on the right to switch views and toggle muscle groups.

Selected Muscle

Click a body part to see details

Muscle Groups
Session
Selected 0
Avg Intensity 0
Intensity Scale

Installation

npm install body-muscles
yarn add body-muscles
pnpm add body-muscles
<script type="module">
  import { BodyChart, ViewSide } from 'https://esm.sh/body-muscles';
</script>

esm.sh — CDN that serves npm packages as native ES modules.

<!-- Global: window.BodyMuscles -->
<script src="https://unpkg.com/body-muscles/dist/umd/body-muscles.umd.min.js"></script>

<script>
  const { BodyChart, ViewSide } = BodyMuscles;
</script>

Works everywhere — no bundler needed, supports file://.

Quick Start

import { BodyChart, ViewSide } from 'body-muscles';

const chart = new BodyChart(document.getElementById('container'), {
  view: ViewSide.FRONT,
  bodyState: {},
  onMuscleClick: (id, name) => {
    console.log(`Clicked: ${name} (${id})`);
  },
  onMuscleHover: (id) => {
    console.log('Hovered:', id);
  },
});

// Update body state
chart.update({
  bodyState: {
    'biceps-left': { intensity: 7, selected: true },
    'chest-upper-right': { intensity: 4, selected: false },
  }
});

// Switch to back view
chart.update({ view: ViewSide.BACK });

// Cleanup when done
chart.destroy();

API Reference

new BodyChart(container, options)

Creates and mounts an interactive SVG body map inside the given DOM element.

BodyChartOptions

Property Type Default Description
view ViewSide Anatomical view: "FRONT" or "BACK"
bodyState BodyState Map of muscle IDs to { intensity, selected }
onMuscleClick (id, name) => void noop Fired when a muscle region is clicked
onMuscleHover (id | null) => void noop Fired on hover enter/leave
className string "" Extra CSS class for the container
ariaLabel string auto Accessibility label
showViewLabel boolean false Show "Anterior / Posterior" overlay label
enableTransitions boolean true Smooth CSS transitions

Instance Methods

Method Description
update(options) Merge partial options. Changing view triggers a full re-render; other changes update paths in-place.
destroy() Remove the chart from the DOM and clean up all event listeners.

Types

enum ViewSide { FRONT = "FRONT", BACK = "BACK" }

type MuscleId = string;

interface BodyPartState {
  intensity: number;  // 0-10
  selected: boolean;
}

type BodyState = Partial<Record<MuscleId, BodyPartState>>;

Data Exports

Export Description
MUSCLE_MAP All 70+ muscle definitions (front + back)
FRONT_MUSCLES Anterior-view muscle definitions
BACK_MUSCLES Posterior-view muscle definitions
MUSCLE_GROUPS Named groups: Head & Neck, Shoulders, Arms, Chest, Back, Abdominals, Legs, Hands & Feet
INTENSITY_COLORS Color map (0-10) from slate → yellow → orange → red

Utility Functions

Function Description
getMuscleColor(state, isHovered) Returns hex color for a given BodyPartState
filterMuscles(view) Returns MuscleDef[] for the given view
createBodyPartState(intensity?, selected?) Factory with validation
isValidIntensity(value) Type guard for 0-10 integer
extractMuscleSide(id) Returns "left" | "right" | "central"
extractMuscleGroup(id) Returns base group string

Framework Examples

Vanilla JavaScript

<div id="body-map"></div>
<script type="module">
  import { BodyChart, ViewSide } from './dist/esm/index.js';

  const state = {};
  const chart = new BodyChart(document.getElementById('body-map'), {
    view: ViewSide.FRONT,
    bodyState: state,
    onMuscleClick(id, name) {
      const cur = state[id] || { intensity: 0, selected: false };
      state[id] = { ...cur, selected: !cur.selected };
      chart.update({ bodyState: state });
    },
  });
</script>

React

import { useRef, useEffect, useState } from 'react';
import { BodyChart, ViewSide } from 'body-muscles';

function BodyMap() {
  const ref = useRef(null);
  const chartRef = useRef(null);
  const [bodyState, setBodyState] = useState({});

  useEffect(() => {
    chartRef.current = new BodyChart(ref.current, {
      view: ViewSide.FRONT,
      bodyState,
      onMuscleClick(id) {
        setBodyState(prev => ({
          ...prev,
          [id]: { intensity: prev[id]?.intensity ?? 0, selected: !prev[id]?.selected },
        }));
      },
    });
    return () => chartRef.current?.destroy();
  }, []);

  useEffect(() => {
    chartRef.current?.update({ bodyState });
  }, [bodyState]);

  return <div ref={ref} />;
}

Vue 3

<template>
  <div ref="container" />
</template>

<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { BodyChart, ViewSide } from 'body-muscles';

const container = ref(null);
const bodyState = ref({});
let chart;

onMounted(() => {
  chart = new BodyChart(container.value, {
    view: ViewSide.FRONT,
    bodyState: bodyState.value,
    onMuscleClick(id) {
      const cur = bodyState.value[id] || { intensity: 0, selected: false };
      bodyState.value = { ...bodyState.value, [id]: { ...cur, selected: !cur.selected } };
    },
  });
});

watch(bodyState, (s) => chart?.update({ bodyState: s }), { deep: true });
onUnmounted(() => chart?.destroy());
</script>

Svelte

<script>
  import { onMount, onDestroy } from 'svelte';
  import { BodyChart, ViewSide } from 'body-muscles';

  let container;
  let chart;
  let bodyState = {};

  onMount(() => {
    chart = new BodyChart(container, {
      view: ViewSide.FRONT,
      bodyState,
      onMuscleClick(id) {
        const cur = bodyState[id] || { intensity: 0, selected: false };
        bodyState = { ...bodyState, [id]: { ...cur, selected: !cur.selected } };
        chart.update({ bodyState });
      },
    });
  });

  onDestroy(() => chart?.destroy());
</script>

<div bind:this={container} />