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

histropediajs

v1.5.0

Published

JavaScript library for rendering interactive zoomable timelines in HTML5 canvas.

Readme

HistropediaJS

An embeddable, high-performance, canvas-based timeline engine for rendering events (known as "articles" in the library) over timescales from days to billions of years. Offers smooth pan/zoom, stacking, card layouts, time bands, image loading, and a flexible API.

  • Canvas-based: fast rendering and smooth animations
  • Huge ranges: day -> billion-year scales, BCE support
  • Customizable: styles, layouts, stacking, density, branding
  • Modular: ESM default build + UMD for script tags

Installation

npm install histropediajs

Documentation

For comprehensive guides, options reference, and API docs, see the hosted documentation: HistropediaJS Documentation.

Importing

histropediajs is published as an ESM package. Use import in bundlers or ESM-capable runtimes. require('histropediajs') is not supported. For non-module browser usage, load the UMD bundle with a <script> tag.

  • ESM (npm / bundlers, recommended):
import Histropedia from 'histropediajs';
// or named exports when you only need part of the public API
import { Timeline, Article, Chart, Dmy, Logger } from 'histropediajs';

The package entry point is a bundled ESM file, so named imports are mainly for clarity and smaller application bundles when your bundler can remove unused exports. They do not expose separate per-module entry points.

  • UMD (script tag) via CDN or self-host:
    • CDN (jsDelivr):
<script src="https://cdn.jsdelivr.net/npm/histropediajs@1/dist/histropedia.umd.min.js"></script>
<script>
  const { Timeline, Dmy } = Histropedia; // global
  // window.Histropedia is also available
</script>
  • Self-host (copy dist file into your public assets during build):
<script src="/assets/vendor/histropedia.umd.js"></script>
<script>
  const { Timeline, Dmy } = Histropedia;
</script>

Quick start

<div id="timeline"></div>
<script type="module">
  import { Timeline } from 'histropediajs';

  const container = document.getElementById('timeline');
  const options = {
    width: 1000,
    height: 500,
    initialDate: { year: 1990, month: 1, day: 1 },
    zoom: { initial: 34 },
  };

  const timeline = new Timeline(container, options);

  timeline.load([
    {
      id: 'apollo11',
      title: 'Apollo 11',
      subtitle: 'First Moon landing',
      from: { year: 1969, month: 7, day: 16 },
      to: { year: 1969, month: 7, day: 24 },
      imageUrl: 'https://example.com/apollo11.jpg',
    },
  ]);
</script>

Data model

Each article (event) uses the following minimal shape. Short property aliases are also supported internally for compact data, but prefer the long form in your code.

{
  id: string|number,
  title: string,
  subtitle?: string,
  lane?: string|number,
  from: { year: number, month?: number, day?: number, precision?: string },
  to?:  { year: number, month?: number, day?: number, precision?: string },
  isToPresent?: boolean,
  rank?: number,
  starred?: boolean,
  hidePeriodLine?: boolean,
  hiddenByFilter?: boolean,
  imageUrl?: string,
  cardLayout?: 'portrait' | 'landscape',
  style?: object,
  hoverStyle?: object,
  activeStyle?: object
}

Notes:

  • BCE years are supported with negative year values; internally year 0 represents 1 BCE. Set shiftBceDates: true to shift year input by 1 so that -1 is 1BCE.
  • Precision accepts day, month, year, decade, century, millennium, million-years, and billion-years. The exported Histropedia.PRECISION_* constants resolve to these strings. Legacy numeric precision values remain supported but are deprecated.
  • If to is omitted and isToPresent is false, the article is treated as an instant event (uses from), but the from.precision determines the span of the period line.
  • Full precision boundary rules and examples: docs/date-precision.md

Lanes

Use lane when you need multiple horizontal event rows that share the same bottom axis. Articles, connectors, hit-testing, and period lines still render on the shared canvas, while lane headers, titles, and bodies render as DOM elements underneath the canvas so they can be styled independently. By default, period lines anchor to each lane bottom while lane layout is active; set article.periodLine.position = 'axis' to keep the legacy shared-axis behavior.

const timeline = new Timeline(container, {
  lane: {
    defaultClassName: 'timeline-lane',
    defaultHeaderClassName: 'timeline-lane-header',
    defaultBodyClassName: 'timeline-lane-body',
    defaultTitleClassName: 'timeline-lane-title',
    data: [
      { id: 'people', title: 'People', layout: { heightWeight: 2 } },
      { id: 'projects', title: 'Projects', layout: { heightWeight: 1 } },
    ],
  },
});

timeline.load([
  {
    id: 'ada',
    lane: 'people',
    title: 'Ada Lovelace',
    from: { year: 1815, month: 12, day: 10 },
  },
]);

timeline.loadLaneArticles('projects', [
  {
    id: 'analytical-engine',
    title: 'Analytical Engine',
    from: { year: 1837 },
  },
]);
  • Lane geometry stays inline so the renderer can place each lane correctly, but decorative inline styles are only written when you explicitly configure lane.defaultStyle or LaneData.style.
  • Lane roots always include histropedia-lane, histropedia-lane-index-N, and a position class, where N follows the stable timeline.lanes order from the axis outward.
  • Lower lanes stack above upper lanes with inline z-index values, making stacked CSS effects easier to write.
  • timeline.articles stays flat across all lanes for backward compatibility.
  • ArticleData.lane is the primary way to place events; omitted values fall back to options.lane.defaultId ('default').
  • lane.topGap reserves space above the topmost lane and reduces the height available for lane layout.
  • lane.axisGap controls the space between the painted top edge of the shared main line and the lane area. Set it to 0 if the nearest lane body should meet the main line.
  • lane.layout.heightWeight distributes the remaining automatic lane height across visible lanes only. Omit it to keep the default equal split, or use lane.layout.height for a fixed lane height.
  • timeline.loadLanes(...) adds or updates lane definitions, including implicit lanes created from article data.
  • Per-lane article defaults live under lane.article and merge over global options.article presentation defaults.

API

Default export

import Histropedia from 'histropediajs';
// Histropedia = { Timeline, Article, Chart, Lane, TimeBand, Dmy, Logger, ...constants, ...sorters, setDebug, isDebugEnabled, enableDebug, disableDebug }

Classes and utilities

  • Timeline: main canvas timeline. new Timeline(containerOrCanvas, options)
  • Article: article model used internally; available for advanced usage
  • Dmy: lightweight date helper (year, month, day, BCE support)
  • Logger: centralized console logger with setEnabled(boolean)
  • Constants: zoom levels, precisions, density controls, etc.

Timeline constructor

new Timeline(container: HTMLElement | HTMLCanvasElement, options?: TimelineOptions)

If container is an element, the timeline creates and appends its own <canvas>. If you pass a <canvas>, it will use it directly and derive width from the element unless overridden.

Core methods

  • load(articles: ArticleInput[]): load or append articles (deduplicates by id).
  • loadLanes(lanes: LaneData[]): add or update lane definitions.
  • loadLaneArticles(laneId, articles): stamp a lane id onto a batch and forward to load(...).
  • loadTimeBands(...): add timeline-wide background bands.
  • loadCharts(...): add time-series charts; duplicate ids are skipped with a warning.
  • loadLaneCharts(laneId, charts): stamp a lane id onto a batch and forward to loadCharts(...).
  • setStartDate(date, options?): set the left edge date. date accepts a Dmy-compatible object or date string; use options.padding to place that date away from the left edge, and options.animation for animated movement.
  • setCentreDate(date, pixelOffset?): center the given date.
  • setSize(width: number, height: number): resize canvas and reflow.
  • setZoom(level: number, pivotPixel?: number, offsetX?: number): programmatic zoom.
  • on(event: string, handler) / off(event, handler) / trigger(event, ...args): event system.
  • select(articleId): bring to the front and trigger the article-select event.

Events

Register handlers using timeline.on(name, callback) or pass options.on = { [name]: handler }.

Full list of events and payloads: docs/events.md

Basic usage:

// Register a handler
timeline.on('article-click', (article, evt) => {
  console.log('article-click', article, evt);
});

// Or via options
const timeline = new Timeline(container, {
  on: {
    'article-click': (article, evt) => console.log('article-click', article, evt),
  },
});

// Trigger an event programmatically (advanced)
timeline.trigger('my-custom-event', { any: 'payload' });

Debugging and logging

HistropediaJS uses a centralized Logger service to control console output. Logging is disabled by default so it will not spam your console unless you explicitly turn it on.

Histropedia.setDebug() and the other global debug helpers control logging only. They do not enable visual canvas diagnostics. Visual debug overlays are configured per timeline and are disabled by default:

const timeline = new Histropedia.Timeline(container, {
  debugOverlays: {
    densityRegions: true,
  },
});

debugOverlays.densityRegions draws non-interactive vertical guides over the timeline at the current article-density region boundaries. It does not change density filtering, stacking, interaction, selection, or saved timeline data.

  • Enable / disable debug logging (default export helpers):
import Histropedia from 'histropediajs';

// Turn on all HistropediaJS logging
Histropedia.enableDebug();

// Or explicitly set the flag
Histropedia.setDebug(true);

// Later, turn logging off again
Histropedia.disableDebug();
// Equivalent:
Histropedia.setDebug(false);

// Check current state
const isOn = Histropedia.isDebugEnabled(); // boolean
  • Using the Logger directly (ESM / bundlers):
import { Logger } from 'histropediajs';

Logger.setEnabled(true); // enable logging
Logger.debug('Timeline initialised');
Logger.info('Loaded articles', articles);
Logger.warn('Something looks odd');
Logger.error('Something went wrong', err);
  • UMD / script-tag usage:
<script src="https://cdn.jsdelivr.net/npm/histropediajs@1/dist/histropedia.umd.min.js"></script>
<script>
  // Enable debug logging
  Histropedia.enableDebug();

  // Or via the Logger instance
  Histropedia.Logger.setEnabled(true);
  Histropedia.Logger.debug('Timeline ready');
</script>
  • Changing the debug label (prefix / "suffix" tag):

All log messages are prefixed with a label (by default [Histropedia]) so you can easily spot them in the console. You can change this to include your app name, environment, or any other marker.

import { Logger } from 'histropediajs';

// Set a custom label that will appear in front of every log message
Logger.setPrefix('[MyApp Timeline]');

Logger.setEnabled(true);
Logger.debug('Loading articles...');

For script-tag/UMD usage:

Histropedia.Logger.setPrefix('[MyApp Timeline]');
Histropedia.enableDebug();
Histropedia.Logger.info('Timeline is ready');

The prefix is applied to all standard console-style methods exposed by the logger (log, info, warn, error, debug, group, groupCollapsed, groupEnd).

Options

TimelineOptions tunes layout, interaction, article rendering, and image loading. Start with the defaults and override only what you need for your embed.

  • Layout: adjust width, height, and verticalOffset to size and position the canvas.
  • Navigation: set initialDate, zoom.initial, and enableUserControl to define how people explore the timeline.
  • Articles: control stacking and presentation with article.autoStacking, adaptive defaultCardLayout, cardLayoutBreakpoints, distanceToBaseline, and density settings.
  • Lanes: use lane.defaultStyle, lane.data, and per-lane article overrides for shared-axis multi-lane layouts.
  • Charts: use chart.data or loadCharts(...) for read-only line charts, with optional lane placement.
  • Images: use image.sanitizer.allowedOrigins, caching limits, or a custom image.customSanitizer to match your content policies.
  • Events: register callbacks via the on map or helpers such as onArticleClick.

Find the full option reference, defaults, and advanced patterns in the hosted docs: https://js.histropedia.com/documentation.

Card layouts

Two built-in layouts are registered: portrait and landscape. By default the timeline falls back to article.defaultCardLayout: 'portrait', while ordered article.cardLayoutBreakpoints can override that fallback using the currently available vertical space for article cards.

const timeline = new Timeline(container, {
  article: {
    distanceToBaseline: 350,
    defaultCardLayout: 'portrait',
    cardLayoutBreakpoints: [{ maxHeight: 220, layout: 'landscape' }],
  },
});

Explicit article.cardLayout values still win, and lane defaults can set lane.article.defaultCardLayout as that lane's fallback layout when no breakpoint matches.

You can register custom layouts:

import { Timeline } from 'histropediajs';

Timeline.registerCardLayout({
  name: 'my-card',
  draw(ctx) {
    // draw using this.position, this._getCurrentStyle(), this.image, etc.
  },
  defaultStyle: {
    /* optional style defaults */
  },
  defaultHoverStyle: {
    /* optional */
  },
  defaultActiveStyle: {
    /* optional */
  },
});

const timeline = new Timeline(container, {
  article: { defaultCardLayout: 'my-card' },
});

Images and security

  • Every article image is sanitized before load. By default the timeline calls sanitizeImageUrl (src/services/url-sanitizer.js), which trims the value, enforces the scheme allowlist (http, https, data, blob), and rejects non image data: payloads.
  • Origins are unrestricted unless you supply image.sanitizer.allowedOrigins. Add host patterns (full origins, *.example.com, .example.com, bare hostnames) to enforce an allowlist, or provide a custom sanitizer.
  • blob: and data: URLs skip origin checks but still require an allowed scheme. data: URLs must begin with data:image/.
  • If you provide image.customSanitizer, it receives the raw URL and your image.sanitizer options object (including any custom fields you add). Return a trusted URL string to allow the load or null to block it; when null is returned the article keeps rendering without an image.
const timeline = new Histropedia.Timeline(container, {
  image: {
    sanitizer: {
      // Configure the built-in sanitizer
      allowedSchemes: ['https', 'data'],
      allowedOrigins: [
        'https://static.example-cdn.com',
        '*.imgix.net',
        '.example.com',
        'media.example.com',
      ],
    },
    // Optional custom sanitizer (takes precedence)
    customSanitizer: (url, opts) => {
      try {
        const candidate = new URL(url, window.location.href);
        const isHttps = candidate.protocol === 'https:';
        const onCdn = candidate.hostname.endsWith('.example-cdn.com');
        return isHttps && onCdn ? candidate.href : null;
      } catch {
        return null;
      }
    },
  },
});

Image loader configuration

The image loader queues work on demand, deduplicates concurrent requests, and evicts cached bitmaps when the byte budget is exceeded. Tune it via options.image:

  • maxCacheBytes (default 64 * 1024 * 1024) caps the decoded raster cache. Set 0 to disable caching or Infinity to remove the cap.
  • maxConcurrent (default 6) limits concurrent fetch/decode operations.
  • requireCORS (default false) sets crossOrigin = 'anonymous' so the canvas stays untainted when your CDN returns the correct CORS headers.
  • decodeMode selects the decode pipeline: 'auto', 'prefer-bitmap', 'bitmap', 'prefer-img', or 'img'.

See src/services/url-sanitizer.js and src/services/image-loader.js for reference implementations and additional behaviour such as shape cropping and cache management.

Accessibility

  • Pointer interactions support mouse, touch, and trackpads.
  • Consider keyboard affordances and focus management in your host app; the canvas itself is not focusable by default.

Examples and demo

A demo app with scenarios is included.

  • Run the demo locally:
npm install
npm run dev

Then open the served URL (Vite dev server). Switch scenarios via the toolbar.

Browser support / Build targets

Build targets

| Build | File | Target | Notes | | ----- | ----------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ESM | histropedia.esm.js | ES2020 | Published npm entrypoint for bundlers and other ESM-capable runtimes. Uses native async/await, optional chaining (?.), nullish coalescing (??). | | UMD | histropedia.umd.js / histropedia.umd.min.js | ES2017 | For <script> tags, CDN usage, or self-hosted browser embeds. The minified UMD file is published for UNPKG/jsDelivr. This build is not a CommonJS require() entrypoint. |

Web APIs used

The library uses several Web APIs that may not exist in all browsers. Most are feature-detected with automatic fallbacks; a few require polyfills if you support older environments.

Performance boosts (feature-detected)

These APIs improve performance when available. The timeline works correctly without them, but may use more main-thread time or be slightly less efficient:

| API | Used for | Without it | | --------------------- | ---------------------------------- | --------------------------------------------------------------------------- | | createImageBitmap() | Off-thread image decoding | Falls back to HTMLImageElement + canvas; images decode on the main thread | | OffscreenCanvas | Off-screen image cropping/resizing | Falls back to regular <canvas>; slightly more GC pressure | | img.decode() | Async decode before paint | Falls back to onload; may cause minor jank when large images appear | | performance.now() | High-resolution cache timestamps | Falls back to Date.now(); millisecond precision instead of sub-ms |

Graceful degradation (feature-detected)

These APIs provide enhanced functionality when available. The timeline remains fully functional without them, with minor behavioural differences:

| API | Used for | Without it | | --------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------ | | AbortController | Cancelling in-flight image fetches | Requests complete even if no longer needed; wastes some bandwidth | | document.fonts (CSS Font Loading API) | Detecting when custom fonts finish loading | Text may briefly render in fallback font before redraw | | Pointer Events | Unified mouse/touch/pen handling | Falls back to separate mouse + touch listeners; same end behaviour |

APIs that may need polyfills

These APIs are used directly without fallbacks. They're widely supported in modern browsers, but very old environments may need polyfills:

| API | Used for | | ----------------------- | ---------------------------------------- | | fetch() | Network image loading | | Promise | Async operations throughout | | Map / Set | Caching, deduplication, pointer tracking | | URL constructor | Parsing and validating image URLs | | requestAnimationFrame | Smooth animations |

Development

See CONTRIBUTING.md for the build, test, and release workflow, including npm scripts, linting, and publishing guidance.

TypeScript

TypeScript definitions are included in the package via index.d.ts. The types are automatically picked up by TypeScript-aware editors and bundlers.

Resources

  • Hosted documentation (guides, API reference, option matrix): https://js.histropedia.com/documentation
  • Event payload reference: docs/events.md
  • Contribution workflow: CONTRIBUTING.md
  • Security policy: SECURITY.md

License

This package is distributed under a custom HistropediaJS license. Please review the full terms at: https://js.histropedia.com/licence.

For repository consumers, a LICENSE stub is included pointing to the hosted license text. Commercial use or redistribution may require additional permissions - contact us if unsure.