Get the FREE Ultimate OpenClaw Setup Guide →

chartjs-plugins

npx machina-cli add skill sjnims/chartjs-expert/chartjs-plugins --openclaw
Files (1)
SKILL.md
9.4 KB

Chart.js Custom Plugins (v4.5.1)

Complete guide to creating and using custom plugins in Chart.js.

Plugin Basics

Plugins are the most efficient way to customize or change Chart.js default behavior. Plugins provide hooks into the chart lifecycle to execute custom code.

Plugin Types

TypeScopeUse Case
InlineSingle chart instanceOne-off customizations
SharedMultiple chartsReusable across specific charts
GlobalAll chartsSite-wide defaults

Inline Plugins

Define directly in chart config:

const chart = new Chart(ctx, {
  type: 'bar',
  data: data,
  plugins: [{
    id: 'myInlinePlugin',
    beforeDraw: (chart, args, options) => {
      // Custom drawing logic
    }
  }]
});

Limitations: Cannot be registered globally, not reusable.

Shared Plugins

Define once, use in multiple charts:

const myPlugin = {
  id: 'mySharedPlugin',
  beforeDraw: (chart, args, options) => {
    // Custom logic
  }
};

const chart1 = new Chart(ctx1, {
  plugins: [myPlugin]
});

const chart2 = new Chart(ctx2, {
  plugins: [myPlugin]
});

Global Plugins

Register once, apply to all charts:

const myPlugin = {
  id: 'myGlobalPlugin',
  beforeDraw: (chart, args, options) => {
    // Custom logic
  }
};

Chart.register(myPlugin);

// Now all charts use this plugin automatically
const chart = new Chart(ctx, { type: 'bar', data: data });

Plugin Structure

Required Properties

const plugin = {
  id: 'unique-plugin-id',  // Required for configuration

  // Lifecycle hooks (all optional)
  beforeInit: (chart, args, options) => {},
  afterInit: (chart, args, options) => {},
  beforeUpdate: (chart, args, options) => {},
  afterUpdate: (chart, args, options) => {},
  beforeDraw: (chart, args, options) => {},
  afterDraw: (chart, args, options) => {},
  // ... more hooks

  // Default options (optional)
  defaults: {
    color: 'red',
    lineWidth: 2
  }
};

Plugin ID Naming

Follow npm package naming conventions:

  • Lowercase letters, numbers, hyphens only
  • No leading dot or underscore
  • Should be descriptive
  • Prefix with chartjs-plugin- for public packages

Plugin Lifecycle Hooks

Initialization Hooks

HookWhenCancelableUse Case
beforeInitBefore chart initializationNoSet up data structures
afterInitAfter chart initializationNoAccess initialized chart

Update Hooks

HookWhenCancelableUse Case
beforeUpdateBefore chart updateYesModify data before update
afterUpdateAfter chart updateNoReact to data changes
beforeLayoutBefore layout calculationYesModify layout constraints
afterLayoutAfter layout calculationNoAccess calculated layout
beforeDatasetsUpdateBefore datasets updateYesIntercept dataset updates
afterDatasetsUpdateAfter datasets updateNoReact to dataset changes

Render Hooks

HookWhenCancelableUse Case
beforeRenderBefore renderingYesSkip render conditionally
beforeDrawBefore drawing chartYesDraw custom backgrounds
afterDrawAfter drawing chartNoDraw overlays, annotations
afterRenderAfter rendering completeNoPost-render operations

Event Hooks

HookWhenUse Case
beforeEventBefore event processingIntercept/modify events
afterEventAfter event processingReact to user interactions

Destruction Hook

HookWhenUse Case
afterDestroyAfter chart destroyedClean up resources

Plugin Configuration

Defining Options

Configure plugins via options.plugins.{plugin-id}:

const plugin = {
  id: 'backgroundPlugin',
  beforeDraw: (chart, args, options) => {
    const {ctx} = chart;
    ctx.save();
    ctx.fillStyle = options.color || 'white';
    ctx.fillRect(0, 0, chart.width, chart.height);
    ctx.restore();
  },
  defaults: {
    color: 'lightGreen'
  }
};

Chart.register(plugin);

const chart = new Chart(ctx, {
  options: {
    plugins: {
      backgroundPlugin: {
        color: 'lightBlue'  // Override default
      }
    }
  }
});

Disabling Plugins

Disable global plugin for specific chart:

const chart = new Chart(ctx, {
  options: {
    plugins: {
      myPlugin: false  // Disable this plugin
    }
  }
});

Disable all plugins:

const chart = new Chart(ctx, {
  options: {
    plugins: false  // Disable all plugins
  }
});

Common Plugin Patterns

Canvas Background Color

const canvasBackgroundColor = {
  id: 'canvasBackgroundColor',
  beforeDraw: (chart, args, options) => {
    const {ctx, chartArea} = chart;
    ctx.save();
    ctx.globalCompositeOperation = 'destination-over';
    ctx.fillStyle = options.color || 'white';
    ctx.fillRect(0, 0, chart.width, chart.height);
    ctx.restore();
  }
};

Chart Area Border

const chartAreaBorder = {
  id: 'chartAreaBorder',
  beforeDraw(chart, args, options) {
    const {ctx, chartArea: {left, top, width, height}} = chart;
    ctx.save();
    ctx.strokeStyle = options.borderColor;
    ctx.lineWidth = options.borderWidth;
    ctx.setLineDash(options.borderDash || []);
    ctx.strokeRect(left, top, width, height);
    ctx.restore();
  },
  defaults: {
    borderColor: 'black',
    borderWidth: 1,
    borderDash: []
  }
};

Custom Text Overlay

const textOverlay = {
  id: 'textOverlay',
  afterDraw: (chart, args, options) => {
    const {ctx, chartArea: {left, right, top, bottom}} = chart;
    ctx.save();

    const centerX = (left + right) / 2;
    const centerY = (top + bottom) / 2;

    ctx.font = options.font || '20px Arial';
    ctx.fillStyle = options.color || 'black';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(options.text || '', centerX, centerY);

    ctx.restore();
  }
};

Empty State Handler

const emptyStatePlugin = {
  id: 'emptyState',
  afterDraw(chart, args, options) {
    const {datasets} = chart.data;
    let hasData = datasets.some(ds => ds.data.length > 0);

    if (!hasData) {
      const {ctx, chartArea: {left, top, right, bottom}} = chart;
      const centerX = (left + right) / 2;
      const centerY = (top + bottom) / 2;

      ctx.save();
      ctx.font = '16px Arial';
      ctx.fillStyle = 'gray';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(options.message || 'No data available', centerX, centerY);
      ctx.restore();
    }
  }
};

Accessing Chart Elements

Chart Object Properties

beforeDraw: (chart, args, options) => {
  const {
    ctx,              // Canvas 2D context
    canvas,           // Canvas element
    width,            // Chart width
    height,           // Chart height
    chartArea,        // {left, top, right, bottom, width, height}
    scales,           // {x, y, r, ...}
    data,             // Chart data
    options,          // Chart options
    config            // Chart configuration
  } = chart;
}

Scales and Coordinates

afterDraw: (chart, args, options) => {
  const {scales: {x, y}} = chart;

  // Convert data value to pixel
  const pixelX = x.getPixelForValue(dataValue);
  const pixelY = y.getPixelForValue(dataValue);

  // Convert pixel to data value
  const dataX = x.getValueForPixel(pixelX);
  const dataY = y.getValueForPixel(pixelY);
}

TypeScript Support

Plugin Type Declaration

import {ChartType, Plugin} from 'chart.js';

declare module 'chart.js' {
  interface PluginOptionsByType<TType extends ChartType> {
    myPlugin?: {
      color?: string;
      width?: number;
      enabled?: boolean;
    }
  }
}

const myPlugin: Plugin = {
  id: 'myPlugin',
  beforeDraw(chart, args, options) {
    // TypeScript now knows about options.color, options.width
    const color = options.color || 'red';
  }
};

Best Practices

Performance

  • Use beforeDraw/afterDraw sparingly - called on every render
  • Cache calculations in afterUpdate hooks
  • Avoid heavy computations in render hooks
  • Use ctx.save() and ctx.restore() to avoid side effects

Error Handling

const safePlugin = {
  id: 'safePlugin',
  afterDraw: (chart, args, options) => {
    try {
      // Plugin logic
    } catch (error) {
      console.error('Plugin error:', error);
    }
  }
};

Conditional Rendering

const conditionalPlugin = {
  id: 'conditionalPlugin',
  beforeDraw: (chart, args, options) => {
    if (!options.enabled) {
      return;  // Skip if disabled
    }
    // Draw logic
  }
};

Additional Resources

  • See references/plugin-api.md for complete hook reference
  • See examples/quadrants-plugin.md for working quadrant background example
  • See examples/empty-state-plugin.md for empty state handler example

Source

git clone https://github.com/sjnims/chartjs-expert/blob/main/skills/chartjs-plugins/SKILL.mdView on GitHub

Overview

Learn to build and apply custom Chart.js plugins (v4.5.1). The guide covers inline, shared, and global plugins, the required plugin structure and defaults, and lifecycle hooks to tailor chart behavior.

How This Skill Works

Plugins hook into the chart lifecycle via hooks like beforeInit, afterInit, beforeDraw, afterDraw, beforeUpdate, and others. They can be defined inline in a chart config, registered globally with Chart.register, or created as reusable shared plugins used across charts.

When to Use It

  • You need to customize a single chart without affecting others (inline plugin).
  • You want to reuse a plugin across multiple charts (shared plugin).
  • You want consistent behavior or defaults across all charts (global plugin).
  • You need to intercept data or layout updates (beforeUpdate, beforeDatasetsUpdate).
  • You want to draw custom visuals after rendering (beforeDraw/afterDraw/afterRender).

Quick Start

  1. Step 1: Define a plugin object with an id and at least one lifecycle hook (e.g., beforeDraw).
  2. Step 2: Register globally with Chart.register(myPlugin) or attach in a chart's plugins array.
  3. Step 3: Create charts using the plugin and verify the behavior (e.g., observe custom drawing or data changes).

Best Practices

  • Choose the right scope (inline, shared, or global) based on reusability.
  • Use a unique id for your plugin; follow the structure shown (id: 'unique-plugin-id').
  • Name public plugins with the chartjs-plugin- prefix and follow npm naming rules.
  • Provide a defaults object to configure options and document them clearly.
  • Document which lifecycle hooks you implement and test for compatibility with other plugins.

Example Use Cases

  • beforeDraw plugin draws a custom background before the chart renders.
  • Shared plugin defined once and used in multiple charts to apply common behavior.
  • Global plugin registered with Chart.register to apply to all charts automatically.
  • beforeUpdate plugin modifies data before the chart updates.
  • afterDraw plugin renders overlays or annotations after rendering.

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers