Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts — Claude Skills 360 Blog
Blog / Frontend / Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts
Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Published: June 27, 2027
Read time: 6 min read
By: Claude Skills 360

Chart.js advanced patterns unlock custom plugins and mixed chart types — Chart.register(CategoryScale, LinearScale, BarElement, LineElement, PointElement, Tooltip, Legend) tree-shakes bundle. type: "bar" with a dataset of type: "line" creates mixed charts. Custom plugin: { id: "myPlugin", beforeDraw(chart, args, options) { const ctx = chart.ctx; ctx.save(); ... ctx.restore() } }. ScriptableContext: backgroundColor: (ctx) => ctx.raw > 0 ? "green" : "red" computes colors per-point. ChartDataLabels plugin adds value labels with datalabels.anchor/align/formatter. chartjs-plugin-zoom with zoom.wheel.enabled and pan.enabled adds pan/zoom. chartjs-plugin-streaming enables real-time live data via streaming.onRefresh. Annotations: chartjs-plugin-annotation draws threshold lines and boxes. ctx.createLinearGradient(0, 0, 0, height) creates gradient fills. chartRef.current.getDatasetAtEvent(e) gets clicked datasets. chart.update("none") updates without animation. Claude Code generates Chart.js dashboards, mixed charts, plugins, and real-time feeds.

CLAUDE.md for Chart.js Advanced

## Chart.js Advanced Stack
- Version: chart.js >= 4.4, react-chartjs-2 >= 5.3
- Register: import { Chart, CategoryScale, LinearScale, BarElement, LineElement, PointElement, ArcElement, Title, Tooltip, Legend } from "chart.js"; Chart.register(...)
- Ref: const chartRef = useRef<ChartJS<"bar">>(null); <Bar ref={chartRef} ...>
- Mixed: datasets: [{ type: "bar", data: [] }, { type: "line", data: [] }]
- Gradient: (ctx) => { const grad = ctx.chart.ctx.createLinearGradient(0,0,0,300); grad.addColorStop(0,"#6366f1"); grad.addColorStop(1,"rgba(99,102,241,0)"); return grad }
- Plugin: Chart.register({ id: "myPlugin", beforeDraw(chart) { ... } })
- Zoom: import zoomPlugin from "chartjs-plugin-zoom"; Chart.register(zoomPlugin)

Mixed Chart Component

// components/charts/MixedChart.tsx — bar + line combined with gradient
"use client"
import { useRef, useEffect } from "react"
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  BarElement,
  LineElement,
  PointElement,
  Title,
  Tooltip,
  Legend,
  Filler,
  type ChartData,
  type ChartOptions,
  type ScriptableContext,
} from "chart.js"
import { Chart } from "react-chartjs-2"

ChartJS.register(
  CategoryScale, LinearScale, BarElement, LineElement,
  PointElement, Title, Tooltip, Legend, Filler,
)

type MixedDataPoint = { month: string; revenue: number; units: number; target: number }

interface MixedChartProps {
  data: MixedDataPoint[]
  title?: string
}

export function MixedChart({ data, title }: MixedChartProps) {
  const chartRef = useRef<ChartJS<"bar">>(null)

  const labels = data.map((d) => d.month)

  function buildGradient(ctx: CanvasRenderingContext2D, color: string, alpha = 0.3): CanvasGradient {
    const gradient = ctx.createLinearGradient(0, 0, 0, 300)
    gradient.addColorStop(0, color.replace(")", `, ${alpha})`).replace("rgb(", "rgba("))
    gradient.addColorStop(1, color.replace(")", ", 0)").replace("rgb(", "rgba("))
    return gradient
  }

  const chartData: ChartData<"bar"> = {
    labels,
    datasets: [
      {
        type: "bar",
        label: "Revenue",
        data: data.map((d) => d.revenue),
        backgroundColor: (ctx: ScriptableContext<"bar">) => {
          const chart = ctx.chart
          const { canvas } = chart
          const context = canvas.getContext("2d")
          if (!context) return "#6366f1"
          return buildGradient(context, "rgb(99, 102, 241)")
        },
        borderColor: "#6366f1",
        borderWidth: 0,
        borderRadius: 4,
        borderSkipped: false,
        yAxisID: "y",
        order: 2,
      },
      {
        type: "line",
        label: "Units Sold",
        data: data.map((d) => d.units),
        borderColor: "#22c55e",
        pointBackgroundColor: "#22c55e",
        pointBorderColor: "#fff",
        pointBorderWidth: 2,
        pointRadius: 5,
        borderWidth: 2,
        tension: 0.4,
        yAxisID: "y1",
        order: 1,
      },
      {
        type: "line",
        label: "Target",
        data: data.map((d) => d.target),
        borderColor: "#f59e0b",
        borderDash: [8, 4],
        borderWidth: 1.5,
        pointRadius: 0,
        tension: 0,
        yAxisID: "y",
        order: 0,
      },
    ],
  }

  const options: ChartOptions<"bar"> = {
    responsive: true,
    maintainAspectRatio: false,
    interaction: { mode: "index", intersect: false },
    plugins: {
      legend: {
        position: "top" as const,
        labels: { boxWidth: 12, padding: 16, font: { size: 11 }, color: "#6b7280" },
      },
      title: title ? { display: true, text: title, font: { size: 13, weight: "bold" }, color: "#111" } : { display: false },
      tooltip: {
        backgroundColor: "#fff",
        titleColor: "#111",
        bodyColor: "#6b7280",
        borderColor: "#e5e7eb",
        borderWidth: 1,
        padding: 10,
        boxPadding: 4,
        callbacks: {
          label: (ctx) => ` ${ctx.dataset.label}: ${Number(ctx.raw).toLocaleString()}`,
        },
      },
    },
    scales: {
      x: {
        grid: { display: false },
        ticks: { color: "#9ca3af", font: { size: 11 } },
      },
      y: {
        position: "left" as const,
        grid: { color: "#f3f4f6" },
        ticks: { color: "#9ca3af", font: { size: 11 }, callback: (v) => `$${Number(v) / 1000}k` },
      },
      y1: {
        position: "right" as const,
        grid: { display: false },
        ticks: { color: "#9ca3af", font: { size: 11 } },
      },
    },
  }

  return (
    <div className="rounded-2xl border bg-card p-5">
      <div style={{ height: 320 }}>
        <Chart ref={chartRef} type="bar" data={chartData} options={options} />
      </div>
    </div>
  )
}

Custom Plugin: Watermark + Threshold Line

// lib/charts/plugins.ts — custom Chart.js plugins
import { type Plugin } from "chart.js"

/** Draws a horizontal threshold line with label */
export function thresholdPlugin(threshold: number, label: string, color = "#ef4444"): Plugin {
  return {
    id: `threshold-${threshold}`,
    afterDraw(chart) {
      const { ctx, scales } = chart
      const yAxis = scales["y"]
      if (!yAxis) return

      const y = yAxis.getPixelForValue(threshold)
      const { left, right } = chart.chartArea

      ctx.save()
      ctx.beginPath()
      ctx.setLineDash([6, 4])
      ctx.moveTo(left, y)
      ctx.lineTo(right, y)
      ctx.strokeStyle = color
      ctx.lineWidth = 1.5
      ctx.stroke()

      ctx.setLineDash([])
      ctx.fillStyle = color
      ctx.font = "11px Inter, system-ui, sans-serif"
      ctx.textAlign = "right"
      ctx.fillText(label, right - 4, y - 5)
      ctx.restore()
    },
  }
}

/** Watermark text in the center of the chart */
export const watermarkPlugin: Plugin = {
  id: "watermark",
  beforeDraw(chart) {
    const { ctx, chartArea } = chart
    const { left, right, top, bottom } = chartArea
    const cx = (left + right) / 2
    const cy = (top + bottom) / 2

    ctx.save()
    ctx.globalAlpha = 0.04
    ctx.font = "bold 28px Inter, sans-serif"
    ctx.fillStyle = "#000"
    ctx.textAlign = "center"
    ctx.textBaseline = "middle"
    ctx.translate(cx, cy)
    ctx.rotate(-Math.PI / 8)
    ctx.fillText("INTERNAL USE ONLY", 0, 0)
    ctx.restore()
  },
}

Real-Time Streaming Chart

// components/charts/StreamingChart.tsx — live data with ring buffer
"use client"
import { useRef, useEffect, useState, useCallback } from "react"
import { Chart as ChartJS, CategoryScale, LinearScale, LineElement, PointElement, Tooltip, Filler } from "chart.js"
import { Line } from "react-chartjs-2"
import type { ChartData, ChartOptions } from "chart.js"

ChartJS.register(CategoryScale, LinearScale, LineElement, PointElement, Tooltip, Filler)

const BUFFER = 60

interface StreamingChartProps {
  source: () => number
  label?: string
  color?: string
  warningThreshold?: number
}

export function StreamingChart({ source, label = "Metric", color = "#6366f1", warningThreshold }: StreamingChartProps) {
  const [points, setPoints] = useState<{ t: string; v: number }[]>(() =>
    Array.from({ length: 20 }, () => ({ t: new Date().toLocaleTimeString(), v: source() })),
  )

  useEffect(() => {
    const id = setInterval(() => {
      setPoints((prev) => [
        ...prev.slice(-(BUFFER - 1)),
        { t: new Date().toLocaleTimeString("en", { hour12: false }), v: source() },
      ])
    }, 1000)
    return () => clearInterval(id)
  }, [source])

  const isWarning = warningThreshold !== undefined && (points[points.length - 1]?.v ?? 0) > warningThreshold

  const chartData: ChartData<"line"> = {
    labels: points.map((p) => p.t),
    datasets: [{
      label,
      data: points.map((p) => p.v),
      borderColor: isWarning ? "#f59e0b" : color,
      backgroundColor: (ctx) => {
        const c = ctx.chart.ctx
        const g = c.createLinearGradient(0, 0, 0, 120)
        g.addColorStop(0, isWarning ? "rgba(245,158,11,0.3)" : `${color}33`)
        g.addColorStop(1, "rgba(255,255,255,0)")
        return g
      },
      fill: true,
      tension: 0.4,
      pointRadius: 0,
      borderWidth: 2,
    }],
  }

  const options: ChartOptions<"line"> = {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    plugins: { legend: { display: false }, tooltip: { mode: "index", intersect: false } },
    scales: {
      x: { display: false },
      y: {
        grid: { color: "#f3f4f6" },
        ticks: { color: "#9ca3af", font: { size: 10 } },
        ...(warningThreshold ? { suggestedMax: warningThreshold * 1.3 } : {}),
      },
    },
  }

  return (
    <div className={`rounded-2xl border p-4 ${isWarning ? "border-amber-400/50" : "bg-card"}`}>
      <div className="flex items-center justify-between mb-2">
        <span className="text-sm font-medium">{label}</span>
        <span className={`text-lg font-bold tabular-nums ${isWarning ? "text-amber-600" : ""}`}>
          {points[points.length - 1]?.v.toFixed(1)}
        </span>
      </div>
      <div style={{ height: 100 }}>
        <Line data={chartData} options={options} />
      </div>
    </div>
  )
}

For the Recharts alternative when a more React-idiomatic component API (each chart element as JSX children rather than config objects), TypeScript-first design, and a larger React ecosystem of tutorials is preferred — Recharts is built natively for React while Chart.js originated as a vanilla JavaScript library with react-chartjs-2 as a wrapper, making Chart.js stronger for canvas-based custom plugins and real-time streaming, see the Recharts guide. For the ApexCharts alternative when built-in pan/zoom, interactive drill-down, brushing, and extensive chart type variety (candlestick, range bar, polar area, funnel) are needed without third-party plugins — ApexCharts has more built-in features while Chart.js has a more minimal, plugin-extensible core, see the ApexCharts guide. The Claude Skills 360 bundle includes Chart.js skill sets covering mixed charts, custom plugins, and real-time streaming. Start with the free tier to try advanced charting generation.

Keep Reading

Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027
Frontend

Claude Code for Luxon: Modern Date and Time Handling

Handle dates and times with Luxon and Claude Code — DateTime.now() and DateTime.fromISO(), DateTime.fromFormat() with custom parsing tokens, plus() and minus() for duration math, diff() between two DateTimes, startOf() and endOf() for range boundaries, setLocale() for locale-aware formatting, toRelative() for relative time strings, toISO() and toFormat() for output, Interval for date ranges, Duration for exact amounts, IANAZone for timezone conversion, and Settings.defaultLocale.

5 min read Jun 24, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free