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

@ryandymock/ancestor-tree

v0.1.5

Published

Interactive ancestor tree React component built with ReactFlow, featuring expandable generations and customizable UI controls

Readme

Ancestor Tree Library

A React library for displaying interactive ancestor trees using ReactFlow.

Features

  • Interactive Tree Visualization: Display family trees with nodes for individuals and couples
  • Expandable Generations: Click to expand and explore deeper generations
  • Customizable Callbacks: Handle clicks on people, couples, and tree interactions
  • UI Controls: Show/hide zoom controls, mini-map, background, and more
  • TypeScript Support: Fully typed for better development experience

Installation

npm install @mui/material @emotion/react @emotion/styled @mui/icons-material reactflow

Basic Usage

import AncestorTree, { 
  AncestorTreeCallbacks, 
  AncestorTreeUIControls 
} from './components/AncestorTree';
import { Person, PeopleIndex } from './types/person';

function MyApp() {
  // Your people data
  const people: PeopleIndex = {
    "1": {
      id: "1",
      name: "John Doe",
      birth: "1990-01-01",
      spouseId: "4",
      parentIds: ["2", "3"]
    },
    "4": {
      id: "4",
      name: "Jane Doe",
      birth: "1991-02-15",
      spouseId: "1",
    },
    // ... more people
  };

  // Define callbacks for interactions
  const callbacks: AncestorTreeCallbacks = {
    onPersonClick: (person: Person) => {
      console.log("Person clicked:", person);
      // Show person details modal, etc.
    },
    onCoupleClick: (partner1: Person, partner2: Person) => {
      console.log("Couple clicked:", partner1, partner2);
      // Show couple details modal, etc.
    },
    onViewportChange: (x: number, y: number, zoom: number) => {
      console.log("Viewport changed:", { x, y, zoom });
    },
    onTreePan: (x: number, y: number) => {
      console.log("Tree panned:", { x, y });
    },
    onTreeZoom: (zoom: number) => {
      console.log("Tree zoomed:", zoom);
    },
    onCoupleExpansion: (coupleId: string | undefined, isExpanded: boolean) => {
      console.log("Couple expansion:", { coupleId, isExpanded });
    },
  };

  // Configure UI controls
  const uiControls: AncestorTreeUIControls = {
    showControls: true,      // Show zoom/fit controls
    showMiniMap: false,      // Show mini-map
    showBackground: true,    // Show grid background
    enablePan: true,         // Allow panning
    enableZoom: true,        // Allow zooming
    enableFitView: true,     // Auto-fit on load
    backgroundColor: "#fafafa", // Custom background color
  };

  return (
    <div style={{ width: "100vw", height: "100vh" }}>
      <AncestorTree 
        people={people}
        rootId="1"
        callbacks={callbacks}
        uiControls={uiControls}
      />
    </div>
  );
}

API Reference

Props

| Prop | Type | Required | Description | |------|------|----------|-------------| | people | PeopleIndex | Yes | Object containing all people data indexed by ID | | rootId | string | Yes | ID of the root person to start the tree from | | callbacks | AncestorTreeCallbacks | No | Callback functions for various interactions | | uiControls | AncestorTreeUIControls | No | UI control configuration |

AncestorTreeCallbacks

| Callback | Type | Description | |----------|------|-------------| | onPersonClick | (person: Person) => void | Called when a person node is clicked | | onCoupleClick | (partner1: Person, partner2: Person) => void | Called when a couple node is clicked | | onTreePan | (x: number, y: number) => void | Called when the tree is panned | | onTreeZoom | (zoom: number) => void | Called when the tree is zoomed | | onViewportChange | (x: number, y: number, zoom: number) => void | Called when viewport changes (pan or zoom) | | onCoupleExpansion | (coupleId: string \| undefined, isExpanded: boolean) => void | Called when a couple is expanded/collapsed |

AncestorTreeUIControls

| Property | Type | Default | Description | |----------|------|---------|-------------| | showControls | boolean | true | Show/hide zoom and fit controls | | showMiniMap | boolean | false | Show/hide the mini map | | showBackground | boolean | true | Show/hide the grid background | | enablePan | boolean | true | Enable/disable panning | | enableZoom | boolean | true | Enable/disable zooming | | enableFitView | boolean | true | Enable/disable fit view on mount | | backgroundColor | string | "#fafafa" | Custom background color | | nodeHeight | number | 120 | Height of nodes (affects vertical spacing calculation) | | verticalGaps | number[] | [0, 325, 100, 325, 100] | Vertical gaps between nodes for each generation | | defaultVerticalGap | number | 50 | Default vertical gap when generation not specified in verticalGaps | | coupleNodeWidth | number | 320 | Width of couple nodes (automatically adjusts column spacing) | | personNodeWidth | number | 160 | Width of person nodes | | formatPersonSubtitle | (person: Person) => string | undefined | Custom formatter for person subtitle text |

Person Type

interface Person {
  id: string;
  name: string;
  birth?: string;
  death?: string;
  imageUrl?: string;
  spouseId?: string;
  parentIds?: [string?, string?]; // [fatherId, motherId]
}

PeopleIndex Type

type PeopleIndex = Record<string, Person>;

Example Use Cases

1. Basic Tree with Click Handlers

const callbacks = {
  onPersonClick: (person) => {
    setSelectedPerson(person);
    setShowPersonModal(true);
  },
  onCoupleClick: (partner1, partner2) => {
    setSelectedCouple([partner1, partner2]);
    setShowCoupleModal(true);
  },
};

2. Tracking User Interactions

const callbacks = {
  onViewportChange: (x, y, zoom) => {
    // Save viewport state for user preferences
    localStorage.setItem('treeViewport', JSON.stringify({ x, y, zoom }));
  },
  onCoupleExpansion: (coupleId, isExpanded) => {
    // Track which branches users explore
    analytics.track('couple_expansion', { coupleId, isExpanded });
  },
};

3. Minimal UI for Embedding

const uiControls = {
  showControls: false,
  showMiniMap: false,
  showBackground: false,
  enablePan: false,
  enableZoom: false,
};

4. Custom Spacing for Theme Compatibility

// If your MUI theme causes card overlapping, adjust vertical spacing
const uiControls = {
  nodeHeight: 140,              // Increase if cards are taller due to theme
  verticalGaps: [0, 400, 150, 400, 150], // Increase gaps between generations
  defaultVerticalGap: 75,       // Increase default gap for expanded generations
};

5. Configurable Node Widths

// Adjust node widths to accommodate longer names or more content
const uiControls = {
  coupleNodeWidth: 400,         // Wider couple cards (default: 320px)
  personNodeWidth: 200,         // Wider person cards (default: 160px)
  // Column spacing automatically adjusts based on width changes
  // Each generation shifts by (newWidth - defaultWidth) * generationIndex
};

// Example: Making nodes narrower for compact display
const compactControls = {
  coupleNodeWidth: 280,         // 40px narrower than default
  personNodeWidth: 140,         // 20px narrower than default
  // Generation 1 shifts left by 40px, Generation 2 by 80px, etc.
};

6. Custom Subtitle Formatting

// Customize what information appears in the subtitle for each person
const uiControls = {
  formatPersonSubtitle: (person) => {
    // Show only birth year and location
    const birthYear = person.birth ? person.birth.split('-')[0] : '?';
    const location = person.location || 'Unknown';
    return `Born ${birthYear} • ${location}`;
    
    // Or show age if still alive
    // const age = person.death ? null : new Date().getFullYear() - parseInt(person.birth?.split('-')[0] || '0');
    // return age ? `Age ${age}` : `${person.birth} – ${person.death}`;
    
    // Or show just the ID for minimal display
    // return person.id;
  },
};

7. Advanced Subtitle Formatting with Template Variables

The library includes a powerful template-based subtitle formatter with many built-in variables:

const uiControls = {
  formatPersonSubtitle: createSubtitleFormatter("{birth*MMM dd, yyyy} – {death*MMM dd, yyyy}"),
};

// Helper function to create template-based formatters
function createSubtitleFormatter(template: string) {
  return (person: Person) => {
    // Implementation handles all variable replacements
    return template
      .replace(/{name}/g, person.name || "")
      .replace(/{birth\*([^}]+)}/g, (match, format) => formatDate(person.birth, format))
      // ... (see full implementation in examples)
  };
}

Available Variables:

  • {name} - Full name
  • {firstName} - First name only
  • {lastName} - Last name(s) only
  • {initials} - First letter of each name part (e.g., "J.D.")
  • {birth} - Raw birth date string
  • {death} - Raw death date string
  • {id} - Person ID
  • {birthYear} - Birth year only
  • {deathYear} - Death year only
  • {age} - Age at death (if deceased)
  • {currentAge} - Current age (if alive)
  • {lifespan} - Formatted as "1950-2020" or "1950-"
  • {isAlive} - "Living" or "Deceased"
  • {status} - Visual indicator: 🟢 for living, ⚫ for deceased

Date Formatting with Asterisk Syntax:

  • {birth*MM/dd/yyyy} → "03/15/1950" (US format)
  • {birth*dd-MM-yyyy} → "15-03-1950" (European format)
  • {birth*MMM dd, yyyy} → "Mar 15, 1950" (readable format)
  • {birth*MMMM dd, yyyy} → "March 15, 1950" (full month name)
  • {death*yyyy-MM-dd} → "2020-12-25" (ISO format)

Example Templates:

// US date format
"{birth*MM/dd/yyyy} – {death*MM/dd/yyyy}"

// Readable dates  
"{birth*dd MMM yyyy} to {death*dd MMM yyyy}"

// Name with status
"{firstName} {lastName} {status}"

// Full month names
"{birth*MMMM dd, yyyy}"

// Initials with lifespan
"{initials} • {lifespan}"

// Current age for living people
"{birthYear} (Age: {currentAge})"

Development

This library is built with:

  • React + TypeScript
  • ReactFlow for graph visualization
  • Material-UI for components and icons

License

MIT