@mango0422/micro-charts
v0.2.4
Published
Lightweight, zero-dependency chart library inspired by uPlot. Canvas-based, high performance.
Maintainers
Readme
Micro Charts
Lightweight alternative to recharts with uPlot-inspired optimizations. Zero dependencies. Canvas-based. 11 chart types.
Features
- Lightweight: ~60KB (IIFE), ~18KB gzipped - 10x smaller than recharts
- Zero dependencies: Completely standalone, no external libraries
- Fast: Canvas-based rendering with uPlot-inspired optimizations
- Style caching (minimal Canvas API calls)
- Global RAF scheduler (efficient batched rendering)
- Zero-allocation loops (reduced GC pressure)
- Tree-shakeable: Import only what you need, individual charts 1-3KB
- 11 Chart Types: Gauge, Pie, Bar, Progress, StackedBar, HeatMap, Radar, Funnel, HorizontalBar, VerticalBar, MultiLine
- Framework-agnostic: Works with React, Vue, Svelte, or Vanilla JS
- TypeScript: Full type definitions included
When to Use
Choose the right tool for your use case:
| Feature | Micro Charts | uPlot | recharts | | -------------------- | ---------------- | ----- | -------- | | Bundle Size | ~60KB | ~50KB | ~500KB | | Dependencies | 0 | 0 | 10+ | | Chart Types | 11 | 1 | 20+ | | Time-series | ✅ | ✅✅✅ | ✅ | | Dashboards/KPIs | ✅✅✅ | ❌ | ✅✅ | | Large Datasets | Good (10K pts) | Excellent (1M+ pts) | Fair (1K pts) | | Animations | ✅ | ❌ | ✅ | | React Native | Manual | Manual | Native | | Learning Curve | Easy | Medium | Easy |
Use Micro Charts when:
- ✅ Building dashboards with diverse chart types (Gauge, Pie, Radar, etc.)
- ✅ Replacing recharts to reduce bundle size by 90%
- ✅ Need zero dependencies and full control
- ✅ Working with moderate datasets (up to 10K points)
Use uPlot when:
- ✅ Building high-performance time-series visualizations
- ✅ Handling massive datasets (100K - 1M+ datapoints)
- ✅ Need extreme optimization (60fps streaming at 10% CPU)
Use recharts when:
- ✅ Deep React ecosystem integration required
- ✅ Need extensive customization and components
- ✅ Bundle size is not a concern
Installation
npm install @mango0422/micro-charts
# or
pnpm add @mango0422/micro-charts
# or
yarn add @mango0422/micro-chartsCDN (IIFE)
<!-- jsdelivr (recommended - faster) -->
<script src="https://cdn.jsdelivr.net/npm/@mango0422/micro-charts/dist/index.global.js"></script>
<!-- unpkg (alternative) -->
<!-- <script src="https://unpkg.com/@mango0422/micro-charts/dist/index.global.js"></script> -->
<script>
const { GaugeChart } = MicroCharts;
</script>Quick Start
GaugeChart
import { GaugeChart } from "@mango0422/micro-charts";
const gauge = new GaugeChart(document.getElementById("gauge"), {
value: 75,
max: 100,
thresholds: { warning: 60, critical: 80 },
});
// Update value
gauge.setData(90);
// Cleanup
gauge.destroy();PieChart
import { PieChart } from "@mango0422/micro-charts";
const pie = new PieChart(
document.getElementById("pie"),
[
{ label: "Normal", value: 120 },
{ label: "Warning", value: 30 },
{ label: "Error", value: 10 },
],
{ innerRadius: 0.5 } // Donut mode
);BarChart
import { BarChart } from "@mango0422/micro-charts";
const bar = new BarChart(
document.getElementById("bar"),
[
{ label: "Server 1", value: 85 },
{ label: "Server 2", value: 60 },
{ label: "Server 3", value: 95 },
],
{ orientation: "horizontal" }
);ProgressBar
import { ProgressBar } from "@mango0422/micro-charts";
const progress = new ProgressBar(document.getElementById("progress"), {
value: 75,
max: 100,
});StackedBarChart
import { StackedBarChart } from "@mango0422/micro-charts";
const stackedBar = new StackedBarChart(
document.getElementById("stacked"),
{
categories: ["Mon", "Tue", "Wed", "Thu", "Fri"],
series: [
{ label: "Inbound", data: [120, 150, 180, 140, 200] },
{ label: "Outbound", data: [80, 90, 100, 95, 110] },
{ label: "Error", data: [5, 8, 3, 10, 6] },
],
},
{ showTotal: true, showLegend: true }
);HeatMap
import { HeatMap } from "@mango0422/micro-charts";
const heatmap = new HeatMap(
document.getElementById("heatmap"),
{
rows: ["00:00", "01:00", "02:00", "03:00"],
columns: ["Mon", "Tue", "Wed", "Thu", "Fri"],
values: [
[120, 150, 180, 140, 200],
[80, 90, 100, 95, 110],
[60, 70, 65, 80, 75],
[40, 50, 55, 60, 65],
],
},
{ colorScheme: "sequential", showValues: true }
);RadarChart
import { RadarChart } from "@mango0422/micro-charts";
const radar = new RadarChart(
document.getElementById("radar"),
{
axes: [
{ label: "CPU", max: 100 },
{ label: "Memory", max: 100 },
{ label: "Disk", max: 100 },
{ label: "Network", max: 100 },
],
datasets: [
{ label: "Server A", data: [80, 70, 90, 60], color: "#3b82f6" },
{ label: "Server B", data: [60, 85, 75, 80], color: "#ef4444" },
],
},
{ showGrid: true, showLegend: true }
);FunnelChart
import { FunnelChart } from "@mango0422/micro-charts";
const funnel = new FunnelChart(
document.getElementById("funnel"),
[
{ label: "Packets Received", value: 10000 },
{ label: "Valid Packets", value: 9500 },
{ label: "Processed", value: 9200 },
{ label: "Forwarded", value: 8800 },
{ label: "Delivered", value: 8500 },
],
{ showPercentage: true, orientation: "vertical" }
);HorizontalBarChart
import { HorizontalBarChart } from "@mango0422/micro-charts";
const hbar = new HorizontalBarChart(
document.getElementById("hbar"),
[
{ label: "Core 0", value: 45 },
{ label: "Core 1", value: 78 },
{ label: "Core 2", value: 92 },
],
{
domain: [0, 100],
showGrid: true,
tooltip: {
formatter: (value) => `${value}% Usage`,
},
}
);VerticalBarChart
import { VerticalBarChart } from "@mango0422/micro-charts";
const vbar = new VerticalBarChart(
document.getElementById("vbar"),
[
{ label: "0-10%", value: 12 },
{ label: "10-30%", value: 8 },
{ label: "30-50%", value: 4 },
{ label: "50-70%", value: 2 },
],
{
allowDecimals: false,
showGrid: true,
}
);MultiLineChart
import { MultiLineChart } from "@mango0422/micro-charts";
const line = new MultiLineChart(
document.getElementById("line"),
[
{ timestamp: 1706800000000, eth0: 1500000, eth1: 2300000 },
{ timestamp: 1706800060000, eth0: 1620000, eth1: 2100000 },
{ timestamp: 1706800120000, eth0: 1480000, eth1: 2500000 },
],
{
series: [
{ key: "eth0", name: "Interface 0", color: "#3b82f6" },
{ key: "eth1", name: "Interface 1", color: "#22c55e" },
],
yAxis: {
tickFormatter: (v) => `${(v / 1000000).toFixed(1)} Mbps`,
},
tooltip: {
filter: (value) => value !== null && value > 0,
sort: (a, b) => b.value - a.value,
},
}
);API Reference
GaugeChart
new GaugeChart(container: HTMLElement, options: GaugeChartOptions)Options:
| Option | Type | Default | Description |
| ------------ | ------- | --------- | ------------------------------- |
| value | number | required | Current value |
| min | number | 0 | Minimum value |
| max | number | 100 | Maximum value |
| size | number | 200 | Canvas size (px) |
| thickness | number | 0.2 | Arc thickness ratio (0-1) |
| thresholds | object | - | { warning: number, critical: number } |
| colors | object | - | Custom colors for states |
| showValue | boolean | true | Show value text |
| animate | boolean | true | Enable animation |
| duration | number | 500 | Animation duration (ms) |
Methods:
setData(value: number)- Update value with animationsetOptions(options: Partial<GaugeChartOptions>)- Update optionsresize(size: number)- Resize the chartdestroy()- Cleanup and remove
PieChart
new PieChart(container: HTMLElement, data: PieChartData[], options?: PieChartOptions)Data:
interface PieChartData {
label: string;
value: number;
color?: string; // Auto-generated if not provided
}Options:
| Option | Type | Default | Description |
| ---------------- | -------- | ------- | ------------------------------ |
| size | number | 200 | Canvas size (px) |
| innerRadius | number | 0 | Inner radius ratio (0-1), 0 = pie, >0 = donut |
| startAngle | number | -90 | Start angle (degrees) |
| animate | boolean | true | Enable animation |
| duration | number | 600 | Animation duration (ms) |
| showLabels | boolean | false | Show segment labels |
| onSegmentClick | function | - | Click handler (index, data) => void |
Methods:
setData(data: PieChartData[])- Update data with animationsetOptions(options: Partial<PieChartOptions>)- Update optionsresize(size: number)- Resize the chartdestroy()- Cleanup and remove
BarChart
new BarChart(container: HTMLElement, data: BarChartData[], options?: BarChartOptions)Data:
interface BarChartData {
label: string;
value: number;
color?: string;
}Options:
| Option | Type | Default | Description |
| -------------- | -------- | ---------- | ------------------------------ |
| width | number | 400 | Canvas width (px) |
| height | number | 300 | Canvas height (px) |
| orientation | string | 'vertical' | 'vertical' or 'horizontal' |
| max | number | auto | Maximum value (auto-calculated)|
| barThickness | number | 0.6 | Bar thickness ratio (0-1) |
| showValues | boolean | false | Show value labels |
| showGrid | boolean | true | Show background grid |
| animate | boolean | true | Enable animation |
| duration | number | 500 | Animation duration (ms) |
Methods:
setData(data: BarChartData[])- Update data with animationsetOptions(options: Partial<BarChartOptions>)- Update optionsresize(width: number, height: number)- Resize the chartdestroy()- Cleanup and remove
ProgressBar
new ProgressBar(container: HTMLElement, options: ProgressBarOptions)Options:
| Option | Type | Default | Description |
| ----------------- | ------- | --------- | ------------------------ |
| value | number | required | Current value |
| max | number | 100 | Maximum value |
| width | number | 300 | Width (px) |
| height | number | 20 | Height (px) |
| color | string | '#3b82f6' | Fill color |
| backgroundColor | string | '#e5e7eb' | Background color |
| showPercentage | boolean | true | Show percentage text |
| animate | boolean | true | Enable animation |
| duration | number | 400 | Animation duration (ms) |
| borderRadius | number | 4 | Border radius (px) |
Methods:
setData(value: number)- Update value with animationsetOptions(options: Partial<ProgressBarOptions>)- Update optionsresize(width: number, height: number)- Resize the bardestroy()- Cleanup and remove
StackedBarChart
new StackedBarChart(container: HTMLElement, data: StackedBarData, options?: StackedBarOptions)Data:
interface StackedBarData {
categories: string[]; // X-axis labels
series: Array<{
label: string; // Series name
data: number[]; // Values for each category
color?: string; // Auto-generated if not provided
}>;
}Options:
| Option | Type | Default | Description |
| -------------- | -------- | ---------- | ---------------------------------- |
| width | number | 500 | Canvas width (px) |
| height | number | 300 | Canvas height (px) |
| orientation | string | 'vertical' | 'vertical' or 'horizontal' |
| barThickness | number | 0.7 | Bar thickness ratio (0-1) |
| showValues | boolean | false | Show value labels on segments |
| showTotal | boolean | true | Show total on top of stack |
| showLegend | boolean | true | Show series legend |
| showGrid | boolean | true | Show background grid |
| animate | boolean | true | Enable animation |
| duration | number | 600 | Animation duration (ms) |
| stacked100 | boolean | false | Normalize to 100% |
Methods:
setData(data: StackedBarData)- Update data with animationsetOptions(options: Partial<StackedBarOptions>)- Update optionsresize(width: number, height: number)- Resize the chartdestroy()- Cleanup and remove
HeatMap
new HeatMap(container: HTMLElement, data: HeatMapData, options?: HeatMapOptions)Data:
interface HeatMapData {
rows: string[]; // Y-axis labels
columns: string[]; // X-axis labels
values: number[][]; // 2D array [row][col]
}Options:
| Option | Type | Default | Description |
| ---------------- | -------- | ------------ | -------------------------------------- |
| width | number | 600 | Canvas width (px) |
| height | number | 400 | Canvas height (px) |
| cellPadding | number | 2 | Padding between cells |
| showValues | boolean | false | Show numeric values in cells |
| colorScheme | string | 'sequential' | 'sequential' or 'diverging' |
| colors | object | - | { min, mid, max } color configuration|
| min | number | auto | Manual min value |
| max | number | auto | Manual max value |
| showAxisLabels | boolean | true | Show row/column labels |
| animate | boolean | true | Enable animation |
| duration | number | 400 | Animation duration (ms) |
| onCellClick | function | - | Click handler (row, col, value) => void|
Methods:
setData(data: HeatMapData)- Update data with animationsetOptions(options: Partial<HeatMapOptions>)- Update optionsresize(width: number, height: number)- Resize the chartdestroy()- Cleanup and remove
RadarChart
new RadarChart(container: HTMLElement, data: RadarChartData, options?: RadarChartOptions)Data:
interface RadarChartData {
axes: Array<{
label: string; // Axis name
max?: number; // Max value for this axis
}>;
datasets: Array<{
label: string; // Dataset name
data: number[]; // Values for each axis
color?: string; // Auto-generated if not provided
fill?: boolean; // Fill polygon, default: true
fillOpacity?: number; // Fill opacity 0-1, default: 0.2
}>;
}Options:
| Option | Type | Default | Description |
| ------------ | ------- | ------- | ---------------------------- |
| size | number | 400 | Canvas size (px, square) |
| levels | number | 5 | Number of concentric levels |
| showGrid | boolean | true | Show grid lines |
| showLabels | boolean | true | Show axis labels |
| showValues | boolean | false | Show data point values |
| showLegend | boolean | true | Show dataset legend |
| animate | boolean | true | Enable animation |
| duration | number | 600 | Animation duration (ms) |
Methods:
setData(data: RadarChartData)- Update data with animationsetOptions(options: Partial<RadarChartOptions>)- Update optionsresize(size: number)- Resize the chartdestroy()- Cleanup and remove
FunnelChart
new FunnelChart(container: HTMLElement, data: FunnelChartData, options?: FunnelChartOptions)Data:
type FunnelChartData = Array<{
label: string; // Stage name
value: number; // Count at this stage
color?: string; // Auto-generated if not provided
}>;Options:
| Option | Type | Default | Description |
| ---------------- | -------- | ---------- | ---------------------------------- |
| width | number | 500 | Canvas width (px) |
| height | number | 400 | Canvas height (px) |
| orientation | string | 'vertical' | 'vertical' or 'horizontal' |
| neckRatio | number | 0.3 | Width ratio at bottom |
| gap | number | 4 | Gap between stages |
| showLabels | boolean | true | Show stage labels |
| showValues | boolean | true | Show values |
| showPercentage | boolean | true | Show % of previous stage |
| animate | boolean | true | Enable animation |
| duration | number | 600 | Animation duration (ms) |
| onStageClick | function | - | Click handler (index, stage) => void|
Methods:
setData(data: FunnelChartData)- Update data with animationsetOptions(options: Partial<FunnelChartOptions>)- Update optionsresize(width: number, height: number)- Resize the chartdestroy()- Cleanup and remove
HorizontalBarChart
new HorizontalBarChart(container: HTMLElement, data: HorizontalBarData[], options?: HorizontalBarChartOptions)Data:
interface HorizontalBarData {
label: string;
value: number;
color?: string;
metadata?: Record<string, unknown>;
}Options:
| Option | Type | Default | Description |
| ---------------- | -------- | --------- | -------------------------------------- |
| width | number | 400 | Canvas width (px) |
| height | number | auto | Auto-calculated based on bar count |
| barHeight | number | 16 | Height of each bar |
| barSpacing | number | 6 | Spacing between bars |
| barRadius | number | 4 | Right corner radius |
| domain | [number, number] | [0, 100] | Value range |
| showGrid | boolean | true | Show vertical grid lines |
| gridDash | number[] | [3, 3] | Grid line dash pattern |
| labelWidth | number | 80 | Width reserved for labels |
| showValues | boolean | true | Show value labels |
| valueFormatter | function | - | Format value display |
| tooltip | object/false | false | Tooltip configuration |
| animate | boolean | true | Enable animation |
| duration | number | 500 | Animation duration (ms) |
Methods:
setData(data: HorizontalBarData[])- Update data with animationsetOptions(options: Partial<HorizontalBarChartOptions>)- Update optionsresize(width?: number, height?: number)- Resize the chartdestroy()- Cleanup and remove
VerticalBarChart
new VerticalBarChart(container: HTMLElement, data: VerticalBarData[], options?: VerticalBarChartOptions)Data:
interface VerticalBarData {
label: string;
value: number;
color?: string;
}Options:
| Option | Type | Default | Description |
| ---------------- | -------- | ------------ | -------------------------------------- |
| width | number | 400 | Canvas width (px) |
| height | number | 300 | Canvas height (px) |
| barThickness | number | 0.6 | Bar thickness ratio (0-1) |
| barRadius | number | 4 | Top corner radius |
| yDomain | [number, number] or 'auto' | 'auto' | Y-axis range |
| yTickCount | number | 5 | Number of Y-axis ticks |
| allowDecimals | boolean | false | Allow decimal values on Y-axis |
| showGrid | boolean | true | Show horizontal grid lines |
| gridDash | number[] | [3, 3] | Grid line dash pattern |
| xAxisHeight | number | 30 | Space for X-axis labels |
| tooltip | object/false | false | Tooltip configuration |
| animate | boolean | true | Enable animation |
| duration | number | 500 | Animation duration (ms) |
Methods:
setData(data: VerticalBarData[])- Update data with animationsetOptions(options: Partial<VerticalBarChartOptions>)- Update optionsresize(width?: number, height?: number)- Resize the chartdestroy()- Cleanup and remove
MultiLineChart
new MultiLineChart(container: HTMLElement, data: MultiLineData[], options: MultiLineChartOptions)Data:
interface MultiLineData {
timestamp: number; // Unix timestamp in milliseconds
[key: string]: number | null; // Dynamic keys for each series
}
interface SeriesConfig {
key: string; // Data key
name: string; // Display name
color: string; // Series color
id?: string; // Optional unique identifier
}Options:
| Option | Type | Default | Description |
| -------------- | -------- | ------------ | -------------------------------------- |
| width | number | 600 | Canvas width (px) |
| height | number | 300 | Canvas height (px) |
| margin | object | { top: 20, right: 20, bottom: 30, left: 70 } | Margins |
| series | SeriesConfig[] | required | Series configuration |
| lineWidth | number | 1.5 | Line width |
| lineType | string | 'linear' | 'linear', 'monotone', or 'step' |
| connectNulls | boolean | true | Connect null values |
| xAxis | object | - | X-axis configuration |
| yAxis | object | - | Y-axis configuration |
| showGrid | boolean | true | Show horizontal grid lines |
| gridDash | number[] | [3, 3] | Grid line dash pattern |
| tooltip | object/false | false | Tooltip configuration |
| animate | boolean | true | Enable animation |
| duration | number | 500 | Animation duration (ms) |
Methods:
setData(data: MultiLineData[])- Update data with animationsetSeries(series: SeriesConfig[])- Update series configurationsetOptions(options: Partial<MultiLineChartOptions>)- Update optionsresize(width?: number, height?: number)- Resize the chartdestroy()- Cleanup and remove
Framework Integration
React
import { useEffect, useRef } from "react";
import { GaugeChart } from "@mango0422/micro-charts";
function Gauge({ value }: { value: number }) {
const ref = useRef<HTMLDivElement>(null);
const chartRef = useRef<GaugeChart>();
useEffect(() => {
if (ref.current) {
chartRef.current = new GaugeChart(ref.current, { value });
return () => chartRef.current?.destroy();
}
}, []);
useEffect(() => {
chartRef.current?.setData(value);
}, [value]);
return <div ref={ref} />;
}Vue 3
<template>
<div ref="chartRef"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from "vue";
import { GaugeChart } from "@mango0422/micro-charts";
const props = defineProps<{ value: number }>();
const chartRef = ref<HTMLDivElement>();
let chart: GaugeChart;
onMounted(() => {
if (chartRef.value) {
chart = new GaugeChart(chartRef.value, { value: props.value });
}
});
onUnmounted(() => {
chart?.destroy();
});
watch(
() => props.value,
(newValue) => {
chart?.setData(newValue);
}
);
</script>Performance
Benchmarks
Rendering performance on typical hardware (tested with various data sizes):
| Data Points | Multi-Line | Bar Charts | Pie/Gauge | |-------------|------------|------------|-----------| | 100 | <5ms | <3ms | <2ms | | 1,000 | <15ms | <8ms | <5ms | | 10,000 | <50ms | <30ms | <20ms | | 100,000 | <500ms | N/A* | N/A* |
*Bar and Pie charts aren't practical with 100K+ items
Bundle Size Comparison
| Library | Bundle Size | Dependencies | Chart Types | Best For | | -------------- | ----------- | ------------ | ----------- | -------- | | Micro Charts | ~60KB (18KB gzipped) | 0 | 11 | Dashboards, KPIs | | uPlot | ~50KB (15KB gzipped) | 0 | 1 (time-series) | Large time-series only | | Chart.js | ~200KB | 0 | 8 | General purpose | | recharts | ~500KB | 10+ | 20+ | React apps, rich features |
Performance Optimizations
- Canvas Rendering: Hardware-accelerated, HiDPI-aware
- Style Caching: Minimize Canvas API calls by caching fill/stroke styles
- Global RAF Scheduler: Batch all animations and renders in single requestAnimationFrame loop
- Zero-Allocation Loops: Avoid GC pressure with index-based iteration
- Geometry Caching: Pre-calculate bar rectangles and layout values
When to Use What
Use Micro Charts if:
- Building dashboards or monitoring UIs
- Need variety (gauge, pie, bar, time-series)
- Working with <10K data points per chart
- Want minimal bundle size impact
Use uPlot if:
- Only need time-series charts
- Dealing with 100K+ data points
- Performance is absolutely critical
- Can sacrifice chart variety for speed
Use recharts if:
- Already using React
- Need extensive customization
- Want rich interactions (zoom, brush, etc.)
- Bundle size isn't a concern
Running Benchmarks
npm run build
open benchmark/index.htmlSee benchmark/README.md for details.
Development
# Install dependencies
pnpm install
# Build
pnpm build
# Watch mode
pnpm devInspiration
Performance optimizations inspired by uPlot:
- Canvas-based rendering
- Style caching
- Global RAF scheduler
- Zero-allocation loops
License
MIT © mango0422
