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 🙏

© 2025 – Pkg Stats / Ryan Hefner

equilibria-engine

v1.4.1

Published

A framework-agnostic TypeScript library for creating dynamic economic charts with Redux state management and real-time interactivity.

Readme

Equilibria Engine

Version 1.3.0

A framework-agnostic JavaScript library for creating interactive economic visualizations, built on Redux, D3.js, and Math.js. Features a modular architecture with advanced performance optimizations for smooth, responsive charts.

Tests Coverage TypeScript License


Features

  • Interactive Charts: Drag curves, adjust parameters, see live updates
  • YAML-Driven: Define complex economic models in simple, declarative YAML
  • State Sharing: Generate shareable URLs that preserve chart state
  • Educational Focus: Built for teaching economics with scenarios, tutorials, and quizzes
  • Accessible: WCAG 2.1 AA compliant with full keyboard navigation and screen reader support
  • High Performance:
    • 2-5x faster rendering with memoization and selective updates
    • Modular architecture with 14 focused modules
    • LRU caching for expression evaluation
    • Per-chart rendering for multi-chart dashboards
  • Rich Visualizations: 11 element types including lines, areas, points, circles, rectangles, segments, angles, brackets, sliders, and reference lines
  • Math.js Integration: Powerful expression evaluation with custom functions

New in v1.3.0

  • Extended Shapes: Circles, rectangles, segments, and angle markers
  • Parametric Curves: Define curves using parametric equations (supports circles, ellipses, and spirals)
  • Economics Helpers: Pre-built functions for budget lines, indifference curves, supply/demand, and more
  • Enhanced Drag System: Multi-directional dragging with curve constraints
  • Range Brackets: Visualize changes (ΔQ, ΔP) and tax wedges on axes
  • On-Graph Sliders: Direct parameter manipulation within charts

For Developers: See the AI Context Documentation for detailed architecture and implementation guides.


Installation

Using pnpm (recommended in monorepo)

pnpm add equilibria-engine

Using npm

npm install equilibria-engine

Using yarn

yarn add equilibria-engine

Quick Start

1. Basic Setup

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Supply & Demand</title>
  <link rel="stylesheet" href="node_modules/equilibria-engine/src/styles/engine.css">
</head>
<body>
  <div id="app">
    <div id="chart-container"></div>
    <div id="controls"></div>
  </div>
  
  <script type="module">
    import { initEngine, parseYAML, initializeFromYAML } from 'equilibria-engine';

    // Initialize the engine
    const { store, destroy } = initEngine({
      chartContainerId: 'chart-container',
      controlsHost: document.getElementById('controls')
    });

    // Load a model from YAML
    fetch('models/supply-demand.yaml')
      .then(res => res.text())
      .then(yaml => {
        const parsed = parseYAML(yaml);
        store.dispatch(initializeFromYAML(parsed));
        console.log('Model loaded!');
      });
  </script>
</body>
</html>

2. Simple YAML Model

# supply-demand.yaml
metadata:
  title: "Market for Coffee"
  specVersion: "1.0"

parameters:
  demandIntercept:
    value: 100
    min: 50
    max: 150
    label: "Demand Intercept"
  
  supplyIntercept:
    value: 20
    min: 0
    max: 50
    label: "Supply Intercept"
  
  slope:
    value: 2
    min: 0.5
    max: 5
    step: 0.5

charts:
  - id: mainChart
    title: "Coffee Market"
    xAxis:
      label: "Quantity"
      min: 0
      max: 60
    yAxis:
      label: "Price ($)"
      min: 0
      max: 120
    
    elements:
      - id: demand
        type: line
        label: "Demand"
        equation: "demandIntercept - slope * x"
        color: "#2563eb"
        draggable: true
        
      - id: supply
        type: line
        label: "Supply"
        equation: "supplyIntercept + slope * x"
        color: "#dc2626"

Core Concepts

Redux State Management

Equilibria uses Redux for predictable state management:

import { createStore, getState, dispatch } from 'equilibria-engine';

// Create store with initial state
const store = createStore(initialState);

// Get current state
const state = store.getState();

// Dispatch actions
import { setParameter, activateScenario } from 'equilibria-engine';
store.dispatch(setParameter('demandIntercept', 120));
store.dispatch(activateScenario('imposeTax'));

State Sharing via URL

Generate shareable links that preserve chart state:

import { serializeState, deserializeState } from 'equilibria-engine';

// Save state to URL
const compressed = serializeState(store.getState());
const url = `${window.location.origin}?state=${compressed}`;

// Restore state from URL
const params = new URLSearchParams(window.location.search);
const compressed = params.get('state');
if (compressed) {
  const state = deserializeState(compressed);
  store = createStore(state);
}

Architecture

Modular Rendering System

The engine features a modular architecture with 14 focused modules for maintainability and performance:

src/engine/
├── rendering/
│   ├── core.ts                    # Main orchestration
│   ├── ChartRenderer.ts           # Per-chart rendering class
│   ├── scales.ts                  # D3 scale calculation
│   ├── axes.ts                    # Axes rendering
│   ├── gridlines.ts               # Gridlines rendering
│   ├── initialization.ts          # Chart setup
│   ├── elements/
│   │   ├── lines.ts               # Line rendering
│   │   ├── areas.ts               # Area rendering
│   │   ├── points.ts              # Point rendering
│   │   └── annotations.ts         # Text annotations
│   ├── interactions/
│   │   └── drag.ts                # Drag behaviors
│   ├── math/
│   │   ├── evaluator.ts           # Expression evaluation (with memoization)
│   │   └── intersections.ts      # Curve intersections
│   └── types.ts                   # TypeScript definitions
├── cache/
│   └── memoization.ts             # LRU cache infrastructure
├── chartSubscriptions.ts          # Selective rendering
├── reducer.ts                     # Redux reducer
└── store.ts                       # Redux store

Performance Optimizations

1. Expression Memoization

  • Two-level LRU caching (compiled expressions + results)
  • 5.5x speedup for repeated evaluations
  • Smart cache key generation

2. Selective Rendering

  • Per-chart dirty checking
  • Only affected charts re-render
  • 2-3x faster multi-chart updates

3. Modular Design

  • Tree-shakeable imports
  • Independent module testing
  • Smaller bundle sizes

Performance Benchmarks

| Operation | Before | After | Improvement | |-----------|--------|-------|-------------| | Parameter update | ~16ms | ~8ms | 2x faster | | Chart re-render | ~60ms | ~30ms | 2x faster | | 4-chart dashboard | ~120ms | ~40ms | 3x faster | | 1000 evaluations | ~45ms | ~8ms | 5.5x faster |


API Reference

Core Functions

initializeEngine(options)

Initialize the Equilibria engine with configuration options.

Parameters:

  • options.chartContainer (string): CSS selector for chart container
  • options.controlsContainer (string): CSS selector for controls container
  • options.initialState (object, optional): Initial Redux state

Returns: Engine instance with methods: loadYAML(), getState(), dispatch()

serializeState(state)

Compress and encode Redux state for URL sharing.

Parameters:

  • state (object): Redux state object

Returns: Base64-encoded compressed string

deserializeState(compressed)

Decompress and decode state from URL.

Parameters:

  • compressed (string): Base64-encoded compressed state

Returns: Redux state object

Redux Actions

import {
  setParameter,
  setMultipleParameters,
  shiftCurve,
  toggleElementVisibility,
  activateScenario,
  deactivateScenario
} from 'equilibria-engine';

// Set parameter value
dispatch(setParameter('price', 100));

// Batch update parameters
dispatch(setMultipleParameters({
  tax: 10,
  subsidy: 0
}));

// Shift a curve element
dispatch(shiftCurve('demand', { yShift: 10 }));

// Toggle element visibility
dispatch(toggleElementVisibility('supply'));

// Activate scenario
dispatch(activateScenario('imposeTax'));

// Deactivate scenario
dispatch(deactivateScenario('imposeTax'));

Integration Guide

Integrating with Your Application

The Equilibria Engine is designed to be framework-agnostic and easy to integrate into any JavaScript application.

Basic Integration Pattern

import { initEngine } from 'equilibria-engine';

// 1. Initialize the engine
const { store, destroy } = initEngine({
  chartContainerId: 'my-chart-container',
  controlsHost: document.getElementById('controls'),
  initialState: null, // or load from YAML
  renderConfig: {
    width: 800,
    height: 600,
    margin: { top: 20, right: 20, bottom: 40, left: 50 }
  },
  useSelectiveRendering: true // Enable for multi-chart performance
});

// 2. Load a model
fetch('/models/my-model.yaml')
  .then(res => res.text())
  .then(yaml => {
    const parsed = parseYAML(yaml);
    store.dispatch(initializeFromYAML(parsed));
  });

// 3. Clean up when done
window.addEventListener('beforeunload', () => {
  destroy();
});

React Integration

import { useEffect, useRef } from 'react';
import { initEngine, parseYAML, initializeFromYAML } from 'equilibria-engine';

function EconomicChart({ modelYaml }) {
  const chartRef = useRef(null);
  const controlsRef = useRef(null);
  const engineRef = useRef(null);

  useEffect(() => {
    // Initialize engine
    const { store, destroy } = initEngine({
      chartContainerId: chartRef.current,
      controlsHost: controlsRef.current,
      useSelectiveRendering: true
    });
    
    engineRef.current = { store, destroy };

    // Load model
    if (modelYaml) {
      const parsed = parseYAML(modelYaml);
      store.dispatch(initializeFromYAML(parsed));
    }

    // Cleanup
    return () => destroy();
  }, [modelYaml]);

  return (
    <div>
      <div ref={chartRef} className="chart-container" />
      <div ref={controlsRef} className="controls-container" />
    </div>
  );
}

Vue Integration

<template>
  <div>
    <div ref="chartContainer" class="chart-container"></div>
    <div ref="controlsContainer" class="controls-container"></div>
  </div>
</template>

<script>
import { initEngine, parseYAML, initializeFromYAML } from 'equilibria-engine';

export default {
  props: ['modelYaml'],
  mounted() {
    const { store, destroy } = initEngine({
      chartContainerId: this.$refs.chartContainer,
      controlsHost: this.$refs.controlsContainer,
      useSelectiveRendering: true
    });
    
    this.engine = { store, destroy };

    if (this.modelYaml) {
      const parsed = parseYAML(this.modelYaml);
      store.dispatch(initializeFromYAML(parsed));
    }
  },
  beforeUnmount() {
    this.engine?.destroy();
  }
};
</script>

Svelte Integration

<script>
  import { onMount, onDestroy } from 'svelte';
  import { initEngine, parseYAML, initializeFromYAML } from 'equilibria-engine';
  
  export let modelYaml;
  
  let chartContainer;
  let controlsContainer;
  let engine;
  
  onMount(() => {
    const { store, destroy } = initEngine({
      chartContainerId: chartContainer,
      controlsHost: controlsContainer,
      useSelectiveRendering: true
    });
    
    engine = { store, destroy };
    
    if (modelYaml) {
      const parsed = parseYAML(modelYaml);
      store.dispatch(initializeFromYAML(parsed));
    }
  });
  
  onDestroy(() => {
    engine?.destroy();
  });
</script>

<div bind:this={chartContainer} class="chart-container"></div>
<div bind:this={controlsContainer} class="controls-container"></div>

Performance Best Practices

1. Use Selective Rendering for Multi-Chart Dashboards

const { store, destroy } = initEngine({
  chartContainerId: 'container',
  controlsHost: controls,
  useSelectiveRendering: true // Only re-render affected charts
});

2. Batch Parameter Updates

import { setMultipleParameters } from 'equilibria-engine';

// ❌ Bad - triggers multiple re-renders
dispatch(setParameter('tax', 10));
dispatch(setParameter('subsidy', 5));
dispatch(setParameter('quota', 100));

// ✅ Good - single re-render
dispatch(setMultipleParameters({
  tax: 10,
  subsidy: 5,
  quota: 100
}));

3. Monitor Cache Performance

import { getCacheStats } from 'equilibria-engine';

// Check cache hit rates
const stats = getCacheStats();
console.log('Expression cache:', stats.compiledExpressions);
console.log('Evaluation cache:', stats.evaluations);
console.log('Hit rate:', (stats.evaluations.utilization * 100).toFixed(1) + '%');

4. Clear Caches on Model Changes

import { clearEvaluationCaches } from 'equilibria-engine';

// Clear caches when loading a completely new model
function loadNewModel(yaml) {
  clearEvaluationCaches();
  const parsed = parseYAML(yaml);
  store.dispatch(initializeFromYAML(parsed));
}

State Management Patterns

Subscribe to Specific Changes

let previousParameters = null;

store.subscribe(() => {
  const state = store.getState();
  
  // Only react to parameter changes
  if (state.parameters !== previousParameters) {
    previousParameters = state.parameters;
    console.log('Parameters changed:', state.parameters);
    // Update external UI, analytics, etc.
  }
});

Integrate with External State Management

// Redux Toolkit example
import { createSlice } from '@reduxjs/toolkit';

const economicsSlice = createSlice({
  name: 'economics',
  initialState: { equilibriaState: null },
  reducers: {
    syncEquilibriaState: (state, action) => {
      state.equilibriaState = action.payload;
    }
  }
});

// Sync Equilibria state to your app's Redux store
equilibriaStore.subscribe(() => {
  const eqState = equilibriaStore.getState();
  appStore.dispatch(syncEquilibriaState(eqState));
});

Advanced Usage

Custom Math Functions

Extend Math.js with domain-specific functions:

import { math } from 'equilibria-engine';

math.import({
  elasticity: function(price, quantity, dP, dQ) {
    return (dQ / quantity) / (dP / price);
  }
});

// Use in YAML equations
// equation: "elasticity(price, quantity, 1, -2)"

Event Handling

Listen to state changes:

store.subscribe(() => {
  const state = store.getState();
  console.log('State updated:', state);
  
  // Custom logic based on state
  if (state.parameters.price.value > 100) {
    console.log('Price is high!');
  }
});

Programmatic Chart Updates

import { render, ChartRenderer } from 'equilibria-engine';

// Render all charts
render('chart-container', store.getState(), config, store);

// Use ChartRenderer for per-chart control
const renderer = new ChartRenderer('chart-container', config, store);
renderer.renderChart('mainChart', chartDef, store.getState());

Example Models

Supply & Demand with Tax

parameters:
  tax:
    value: 0
    min: 0
    max: 30
    step: 5
    label: "Tax ($/unit)"

charts:
  - id: mainChart
    elements:
      - id: supply
        type: line
        equation: "20 + 2 * x + tax"  # Tax shifts supply up
        color: "#dc2626"
      
      - id: consumerSurplus
        type: area
        topBoundary: "demand"
        bottomBoundary: "equilibriumPrice"
        leftBoundary: "0"
        rightBoundary: "equilibriumQuantity"
        color: "#10b981"
        opacity: 0.3

Production Possibilities Frontier

parameters:
  alpha:
    value: 0.5
    min: 0
    max: 1
    step: 0.1
    label: "Technology Parameter"

charts:
  - id: ppf
    title: "Production Possibilities Frontier"
    elements:
      - id: frontier
        type: line
        equation: "100 * (1 - (x / 100) ** alpha)"
        color: "#2563eb"

Range Brackets

Brackets visualize ranges and changes on axes (ΔQ, ΔP, tax wedges).

parameters:
  q1:
    value: 30
    label: "Initial Quantity"
  q2:
    value: 50
    label: "Final Quantity"

charts:
  - id: mainChart
    elements:
      - id: quantityChange
        type: bracket
        axis: 'x'
        from: "q1"
        to: "q2"
        label: 'ΔQ'
        offset: -20
        color: "#6366f1"

      - id: priceChange
        type: bracket
        axis: 'y'
        from: 40
        to: 60
        label: 'ΔP'
        offset: 20
        color: "#ec4899"

Properties:

  • type: 'bracket' - Element type
  • axis: 'x' | 'y' - Which axis to place bracket on
  • from: number | string - Start value (parameter reference allowed)
  • to: number | string - End value (parameter reference allowed)
  • label?: string - Text label for the bracket
  • offset?: number - Distance from axis (negative = below/left, positive = above/right)
  • color?: string - Bracket color

Use Cases:

  • Show quantity/price changes (ΔQ, ΔP)
  • Indicate tax wedges between curves
  • Highlight equilibrium shifts
  • Mark consumer/producer surplus regions

On-Graph Sliders

Sliders provide direct on-chart parameter manipulation.

parameters:
  tax:
    value: 0
    min: 0
    max: 30

charts:
  - id: interactive
    elements:
      - id: taxSlider
        type: slider
        param: "tax"
        axis: 'y'
        min: 0
        max: 30
        label: "Tax"
        format: "$,.0f"
        color: "#8b5cf6"
        position: 10  # Position on perpendicular axis

Properties:

  • type: 'slider' - Element type
  • param: string - Parameter name to control
  • axis: 'x' | 'y' - Which axis to place slider handle on
  • min: number | string - Minimum value
  • max: number | string - Maximum value
  • label?: string - Display label
  • format?: string - D3 format string for value display (e.g., "$,.0f", ".2f")
  • color?: string - Handle and track color
  • position?: number | string - Position on perpendicular axis

Use Cases:

  • Tax/subsidy controls on price axis
  • Quota controls on quantity axis
  • Technology parameter sliders
  • Interactive comparative statics

JSON Schema & IDE Integration

Equilibria includes JSON Schema export for enhanced IDE support and YAML validation.

Generate Schema

Export JSON Schema v7 from Zod schemas:

# One-time export
pnpm schema:export

# Output: schema/equilibria-document.schema.json (~41KB)

VSCode Integration

1. Install Extension

Install YAML by Red Hat from the VSCode marketplace.

2. Schema Auto-Configuration

The .vscode/settings.json file is pre-configured to map the schema to your YAML files:

{
  "yaml.schemas": {
    "./schema/equilibria-document.schema.json": [
      "*.equilibria.yaml",
      "examples/**/*.yaml",
      "tests/fixtures/**/*.yaml"
    ]
  }
}

3. IDE Features

Once configured, you get:

  • Autocomplete: Press Ctrl+Space for field suggestions
  • Inline Validation: Real-time error detection while typing
  • Hover Documentation: See descriptions for all fields
  • Type Checking: Automatic validation of data types
  • Required Fields: Warnings for missing required properties
  • Enum Values: Dropdown suggestions for specVersion, element types, etc.

Example YAML with IDE Support:

# Type "met" and press Ctrl+Space to autocomplete "metadata:"
metadata:
  title: "My Economic Model"
  specVersion: "1.0"  # ← Autocomplete suggests valid versions
  
# Type "param" and press Ctrl+Space
parameters:
  price:
    value: 100
    min: 0      # ← Hover to see "Minimum allowed value (number)"
    max: 200
    label: "Market Price"
    
# Type "chart" and see the array structure suggested
charts:
  - id: "mainChart"
    elements:
      - type: "line"  # ← Autocomplete suggests: line, area, point, verticalLine, etc.
        equation: "100 - 2 * x"
        color: "#2563eb"

Schema Details

  • Format: JSON Schema Draft 7
  • Size: ~41KB (fully inlined, no $refs)
  • Validation: Includes Zod error messages for better feedback
  • Metadata: Custom x-equilibria-version and x-generated-from fields
  • Source: Generated from Zod schemas in src/schemas/

Regenerate After Schema Changes

If you modify the Zod schemas, regenerate the JSON Schema:

pnpm schema:export

Testing

The engine includes comprehensive test coverage:

# Run all tests
pnpm test

# Watch mode
pnpm test:watch

# Coverage report
pnpm test:coverage

# E2E tests
pnpm test:e2e

Test Stats:

  • 430 tests passing
  • 85%+ code coverage
  • Unit, integration, and E2E tests

Accessibility

Equilibria is designed with accessibility as a core requirement:

  • WCAG 2.1 AA compliant
  • ✅ Full keyboard navigation (Tab, Arrow keys, Enter, Space)
  • ✅ Screen reader support (NVDA, JAWS, VoiceOver tested)
  • ✅ ARIA labels on all interactive elements
  • ✅ High contrast mode support
  • ✅ Focus indicators on all controls
  • ✅ Descriptive button text and labels

Keyboard Shortcuts

  • Tab - Navigate between controls
  • Arrow Up/Down - Adjust slider values
  • Enter - Activate buttons
  • Space - Toggle checkboxes
  • Escape - Close dialogs

Browser Support

  • Chrome 100+
  • Firefox 100+
  • Safari 15+
  • Edge 100+

Bundle Size

  • Full bundle: ~157KB gzipped
    • Redux: 5KB
    • Math.js: 60KB (selective imports: ~40KB)
    • D3.js: 70KB (selective imports: ~45KB)
    • js-yaml: 10KB
    • pako: 12KB
    • Engine code: 15KB

Optimization Tips:

  • Import only needed D3 modules
  • Use selective Math.js imports
  • Enable tree-shaking in build

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

Quick Start for Contributors:

# Clone repo
git clone https://github.com/your-org/equilibria.git

# Install dependencies (from monorepo root)
pnpm install

# Run tests
cd packages/equilibria-engine
pnpm test

# Make changes and test
pnpm test:watch

Changelog

See CHANGELOG.md for version history and breaking changes.


License

MIT © 2025 Kinetonomics


Links


CDN Usage (quick snippets)

You can load the built bundles directly from popular CDNs (jsDelivr / unpkg). These files are produced in dist/ during the package build.

UMD (script tag) — jsDelivr / unpkg (pinned to v1.0.2)

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/equilibria-engine.umd.min.js"></script>
<!-- or unpkg -->
<script src="https://unpkg.com/[email protected]/dist/equilibria-engine.umd.min.js"></script>

The UMD build exposes a global. Common names to try are EquilibriaEngine or equilibriaEngine (the exact global depends on the build name in Rollup). Example usage:

<script>
  // The UMD bundle attaches to window
  const { initializeEngine } = window.EquilibriaEngine || window.equilibriaEngine || {};
  const engine = initializeEngine({ chartContainer: '#chart', controlsContainer: null });
  // engine.loadYAML(...)
</script>

ESM (native module import) — esm.sh (pinned to v1.0.2)

<script type="module">
  // For reliable named ESM exports in browser demos, use the esm.sh pre-bundled entry.
  import { initializeEngine } from 'https://esm.sh/[email protected]/dist/equilibria-engine.esm.js?bundle';

  const engine = initializeEngine({ chartContainer: '#chart' });
  // engine.loadYAML(...)
</script>

Credits

Built with:

  • Redux - State management
  • D3.js - Data visualization
  • Math.js - Expression evaluation
  • js-yaml - YAML parsing
  • pako - Compression
  • [zod] (https://zod.dev/) - YAML Schemas

Support