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

gantt-rx

v1.1.0

Published

A powerful React + TypeScript Gantt chart library with histogram, resource allocation, critical path and auto-scheduling

Readme

GanttRx

A powerful, canvas-based Gantt chart library for React + TypeScript.

Replace lucassmaestri with your GitHub username after deploying the demo (e.g. via pnpm build && gh-pages -d dist).

GanttRx renders project schedules with resource histograms, critical path analysis, auto-scheduling, and a complete event system — all in a performant canvas-based engine that handles thousands of tasks without breaking a sweat.


Features

  • Canvas rendering — bars, grid and dependency arrows drawn on layered HTML5 canvases for smooth 60 fps scrolling
  • Virtualized task list — renders only visible rows; tested at 3 000+ tasks
  • Dependency types — Finish-to-Start (FS), Start-to-Start (SS), Finish-to-Finish (FF), Start-to-Finish (SF)
  • Resource histogram — stacked daily bar chart with 6 distribution curves (B-2 → B+2 + flat)
  • Critical path — CPM forward/backward pass, highlights the critical chain
  • Auto-scheduler — topological sort propagates constraint dates through the dependency graph
  • Rescheduling analysis — detects tasks behind schedule and suggests new end dates
  • Actual bars — renders actual start/end bars alongside planned bars
  • Markers — vertical timeline markers with labels
  • Properties panel — slide-in panel to edit any task, its dependencies and resources
  • Configurable columns — add/remove columns (Start, End, Duration, %, WBS, Location, Notes) from the task list with the + button
  • Behind-schedule indicator — amber warning icon next to tasks that are behind schedule
  • PDF export — export the current view to PDF
  • Bilingual — Portuguese (pt-BR) and English (en-US)
  • Full TypeScript — all props, events and data types are exported
  • Zero peer overhead — only requires React ≥ 18

Installation

Via NPM Registry:

npm install gantt-rx
# or
pnpm add gantt-rx
# or
yarn add gantt-rx

Via GitHub Directly (auto-builds on install):

npm install github:lucassmaestri/ganttRx

Import the stylesheet (required):

import 'gantt-rx/dist/style.css';

Quick Start

import { GanttChart } from 'gantt-rx';
import 'gantt-rx/dist/style.css';
import type { GanttTask, Dependency, ResourceAllocation } from 'gantt-rx';

const tasks: GanttTask[] = [
  {
    id: 'g1',
    name: 'Phase 1',
    type: 'group',
    parentId: null,
    plannedStart: new Date('2025-01-01'),
    plannedEnd: new Date('2025-03-31'),
    progress: 0,
    color: '#6366f1',
    collapsed: false,
  },
  {
    id: 't1',
    name: 'Requirements',
    type: 'task',
    parentId: 'g1',
    plannedStart: new Date('2025-01-01'),
    plannedEnd: new Date('2025-01-21'),
    progress: 100,
    color: '#22c55e',
  },
  {
    id: 't2',
    name: 'Design',
    type: 'task',
    parentId: 'g1',
    plannedStart: new Date('2025-01-22'),
    plannedEnd: new Date('2025-02-15'),
    progress: 60,
    color: '#ec4899',
  },
  {
    id: 'ms1',
    name: 'Design Review',
    type: 'milestone',
    parentId: 'g1',
    plannedStart: new Date('2025-02-17'),
    plannedEnd: new Date('2025-02-17'),
    progress: 0,
    color: '#f59e0b',
  },
];

const deps: Dependency[] = [
  { id: 'd1', fromId: 't1', toId: 't2', type: 'FS', offsetDays: 0 },
  { id: 'd2', fromId: 't2', toId: 'ms1', type: 'FS', offsetDays: 1 },
];

export function App() {
  return (
    <div style={{ height: '100vh' }}>
      <GanttChart
        initialTasks={tasks}
        initialDependencies={deps}
        onTaskUpdate={(task, prev) => console.log('updated', task.id)}
      />
    </div>
  );
}

Note: The <GanttChart> component must have a parent with an explicit height.


Loading Data from an API

Dates arriving from a REST / GraphQL API are ISO strings. Convert them before passing to GanttChart:

import { useEffect, useState } from 'react';
import { GanttChart } from 'gantt-rx';
import type { GanttTask, Dependency, ResourceAllocation } from 'gantt-rx';

function parseTask(raw: Record<string, unknown>): GanttTask {
  return {
    ...(raw as GanttTask),
    plannedStart: new Date(raw.plannedStart as string),
    plannedEnd:   new Date(raw.plannedEnd   as string),
    actualStart:  raw.actualStart ? new Date(raw.actualStart as string) : undefined,
    actualEnd:    raw.actualEnd   ? new Date(raw.actualEnd   as string) : undefined,
  };
}

export function ProjectGantt({ projectId }: { projectId: string }) {
  const [tasks, setTasks]   = useState<GanttTask[]>([]);
  const [deps,  setDeps]    = useState<Dependency[]>([]);
  const [res,   setRes]     = useState<ResourceAllocation[]>([]);

  useEffect(() => {
    Promise.all([
      fetch(`/api/projects/${projectId}/tasks`).then(r => r.json()),
      fetch(`/api/projects/${projectId}/dependencies`).then(r => r.json()),
      fetch(`/api/projects/${projectId}/resources`).then(r => r.json()),
    ]).then(([rawTasks, rawDeps, rawRes]) => {
      setTasks(rawTasks.map(parseTask));
      setDeps(rawDeps);
      setRes(rawRes.map((r: Record<string, unknown>) => ({
        ...r,
        startDate: new Date(r.startDate as string),
        endDate:   new Date(r.endDate   as string),
      })));
    });
  }, [projectId]);

  const handleTaskUpdate = async (task: GanttTask) => {
    await fetch(`/api/tasks/${task.id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(task),
    });
  };

  return (
    <div style={{ height: '100vh' }}>
      <GanttChart
        initialTasks={tasks}
        initialDependencies={deps}
        initialResources={res}
        onTaskUpdate={handleTaskUpdate}
        onTaskMove={(moved) => moved.forEach(handleTaskUpdate)}
      />
    </div>
  );
}

Props API

<GanttChart>

| Prop | Type | Description | |------|------|-------------| | initialTasks | GanttTask[] | Tasks loaded on mount | | initialDependencies | Dependency[] | Dependencies loaded on mount | | initialResources | ResourceAllocation[] | Resource allocations loaded on mount | | initialMarkers | GanttMarker[] | Timeline markers loaded on mount | | initialSettings | Partial<GanttSettings> | Initial settings override | | onEvent | (event: GanttChangeEvent) => void | Generic stream — fires on every state change | | onTaskAdd | (task) => void | A task was added | | onTaskUpdate | (task, previous) => void | A task field changed | | onTaskDelete | (taskId, task) => void | A task was deleted | | onTaskMove | (moved[], previous[]) => void | Task(s) dragged to new dates | | onTaskResize | (task, previous, edge) => void | Task bar edge resized | | onTaskProgress | (task, previous) => void | Progress bar dragged | | onDependencyAdd | (dep) => void | A dependency was created | | onDependencyUpdate | (dep, previous) => void | A dependency was changed | | onDependencyRemove | (depId, dep) => void | A dependency was removed | | onResourceAdd | (res) => void | A resource allocation was added | | onResourceUpdate | (res, previous) => void | A resource allocation was changed | | onResourceRemove | (resId, res) => void | A resource allocation was removed | | onMarkerAdd | (marker) => void | A marker was added | | onMarkerUpdate | (marker, previous) => void | A marker was changed | | onMarkerRemove | (markerId, marker) => void | A marker was removed | | onSettingsChange | (settings) => void | Any setting changed | | onSelectionChange | (ids[], activeId) => void | Task selection changed |


Event System

Every change fires through onEvent as a typed GanttChangeEvent<T>:

import type { GanttChangeEvent, GanttEventType } from 'gantt-rx';

<GanttChart
  onEvent={(event) => {
    // event.type  — e.g. 'task.move', 'dependency.add'
    // event.payload — typed payload for this event type
    // event.snapshot — full state snapshot after the change
    // event.timestamp — Date.now() at the time of the change

    if (event.type === 'task.move') {
      const { moved, previous } = event.payload;
      // persist to server...
    }
  }}
/>

All event types

| Type | Payload | |------|---------| | task.add | { task } | | task.update | { task, previous } | | task.delete | { taskId, task } | | task.move | { moved[], previous[] } | | task.resize | { task, previous, edge: 'start' \| 'end' } | | task.progress | { task, previous } | | dependency.add | { dependency } | | dependency.update | { dependency, previous } | | dependency.remove | { dependencyId, dependency } | | resource.add | { resource } | | resource.update | { resource, previous } | | resource.remove | { resourceId, resource } | | marker.add | { marker } | | marker.update | { marker, previous } | | marker.remove | { markerId, marker } | | settings.change | { settings, previous } | | selection.change | { selectedIds[], activeTaskId } |


TypeScript Types

GanttTask

interface GanttTask {
  id: string;
  name: string;
  type: 'task' | 'milestone' | 'group';
  parentId: string | null;
  plannedStart: Date;
  plannedEnd: Date;
  actualStart?: Date;
  actualEnd?: Date;
  progress: number;           // 0–100
  color?: string;             // CSS hex color
  collapsed?: boolean;        // groups only
  isCritical?: boolean;       // set by runCriticalPath
  wbs?: string;               // e.g. '1.2.3'
  location?: string;
  notes?: string;
}

Dependency

interface Dependency {
  id: string;
  fromId: string;
  toId: string;
  type: 'FS' | 'SS' | 'FF' | 'SF';
  offsetDays: number;
}

ResourceAllocation

interface ResourceAllocation {
  id: string;
  taskId: string;
  resourceName: string;
  startDate: Date;
  endDate: Date;
  hours: number;
  departments: Department[];
  distribution: 'B-2' | 'B-1' | 'B0' | 'B+1' | 'B+2' | 'flat';
}

GanttSettings (partial override via initialSettings)

interface GanttSettings {
  rowHeight: number;          // default 26
  columnWidth: number;        // default 28 (px per day at zoom 1)
  zoomFactor: number;         // default 1
  showHistogram: boolean;
  showDependencies: boolean;
  showBaseline: boolean;
  showCriticalPath: boolean;
  autoSchedule: boolean;
  histogramHeight: number;    // default 180
  taskListWidth: number;      // default 300
  theme: 'dark' | 'light';
  showGrid: boolean;
  showActualBars: boolean;
  visibleColumns: TaskListColumn[];  // default ['start', 'duration', 'progress']
}

type TaskListColumn = 'start' | 'end' | 'duration' | 'progress' | 'wbs' | 'location' | 'notes';

GanttSettings Examples

Compact view

<GanttChart
  initialTasks={tasks}
  initialSettings={{
    rowHeight: 20,
    showHistogram: false,
    visibleColumns: ['duration', 'progress'],
  }}
/>

Full audit view with all columns

<GanttChart
  initialTasks={tasks}
  initialSettings={{
    rowHeight: 30,
    showActualBars: true,
    showBaseline: true,
    visibleColumns: ['start', 'end', 'duration', 'progress', 'wbs', 'location'],
  }}
/>

Rescheduling

GanttRx automatically detects tasks that are behind schedule based on their expected progress vs actual progress. Tasks that are ≥ 10% behind their expected completion show:

  • An amber warning icon (▲) in the task list next to the task name
  • The task name turns amber
  • They appear in the Rescheduling Panel (Auto Schedule button → opens panel with suggested new end dates)

Dependency Types

| Type | Meaning | |------|---------| | FS | Finish-to-Start — successor can't start until predecessor finishes | | SS | Start-to-Start — successor can't start until predecessor starts | | FF | Finish-to-Finish — successor can't finish until predecessor finishes | | SF | Start-to-Finish — successor can't finish until predecessor starts |

All types support an offsetDays lag (positive) or lead (negative).


Distribution Curves

Resource hours are spread across the task duration using a Beta distribution:

| Curve | Shape | |-------|-------| | B-2 | Heavy front-load (early peak) | | B-1 | Moderate front-load | | B0 | Symmetric bell curve | | B+1 | Moderate back-load | | B+2 | Heavy back-load (late peak) | | flat| Uniform distribution |


User Interactions

| Action | How | |--------|-----| | Select task | Click a row or bar | | Multi-select | Ctrl + Click | | Open properties | Double-click a row or bar | | Move task | Drag bar horizontally | | Resize task | Drag left/right edge of bar | | Change progress | Drag progress thumb on bar | | Collapse group | Click ▼ / ▶ in the task list | | Add dependency | Drag from the right handle of one bar to another | | Scroll | Mouse wheel or trackpad on timeline/histogram | | Zoom | Toolbar + / − buttons or Ctrl + scroll | | Add columns | Click the + button in the task list header |


Toolbar Quick Reference

| Button | Action | |--------|--------| | + Task | Add a new leaf task | | ⬛ Group | Add a new group | | ◆ Milestone | Add a milestone | | Critical | Toggle critical path highlight | | Histogram | Show / hide resource histogram | | Deps | Show / hide dependency arrows | | Baseline | Show / hide baseline bars | | Grid | Show / hide day grid | | Actual Bars | Show / hide actual bars | | Auto Schedule | Run CPM scheduler + open rescheduling panel | | Markers | Add / remove timeline markers | | Today | Scroll timeline to today | | PT / EN | Toggle language | | PDF | Export to PDF | | + / | Zoom in / out |


Contributing

Pull requests are welcome. For major changes open an issue first.

git clone https://github.com/lucassmaestri/ganttRx.git
cd ganttRx
pnpm install
pnpm dev          # start the demo app at localhost:5173
pnpm build:lib    # build the distributable library

License

MIT