Skip to main content

Mermaid vs D3.js vs Chart.js: Diagrams and Data Visualization (2026)

·PkgPulse Team

TL;DR

Mermaid generates diagrams from text — flowcharts, sequence diagrams, Gantt charts, ER diagrams, all from Markdown-like syntax, no coding needed, used in GitHub, Notion, and docs. D3.js is the low-level data visualization library — binds data to DOM/SVG, full control over every pixel, powers the NYT and Observable, steep learning curve. Chart.js is the simple charting library — responsive charts with minimal code, canvas-based, 8 chart types built-in, great for dashboards. In 2026: Mermaid for documentation diagrams, D3.js for custom/complex visualizations, Chart.js for standard dashboard charts.

Key Takeaways

  • Mermaid: ~3M weekly downloads — text-to-diagram, Markdown, no JS coding needed
  • D3.js: ~5M weekly downloads — full-control SVG/DOM, custom visualizations
  • Chart.js: ~5M weekly downloads — simple API, canvas, 8 chart types, responsive
  • Completely different tools: diagrams (Mermaid), custom viz (D3), charts (Chart.js)
  • Mermaid requires no JavaScript — just text definitions
  • D3 has the steepest learning curve but maximum flexibility

Mermaid

Mermaid — text-to-diagram:

Flowchart

```mermaid
flowchart TD
    A[Start] --> B{Is it working?}
    B -- Yes --> C[Great!]
    B -- No --> D[Debug]
    D --> E[Fix the bug]
    E --> B
    C --> F[Deploy]

### Sequence diagram

```markdown
```mermaid
sequenceDiagram
    participant Client
    participant API
    participant Database

    Client->>API: POST /api/packages
    API->>Database: INSERT package
    Database-->>API: OK
    API-->>Client: 201 Created

    Client->>API: GET /api/packages
    API->>Database: SELECT * FROM packages
    Database-->>API: [packages]
    API-->>Client: 200 [packages]

### ER diagram

```markdown
```mermaid
erDiagram
    USER ||--o{ PACKAGE : creates
    PACKAGE ||--|{ VERSION : has
    PACKAGE ||--o{ DOWNLOAD : tracks
    USER {
        string id PK
        string name
        string email
    }
    PACKAGE {
        string id PK
        string name
        string description
        string userId FK
    }
    VERSION {
        string id PK
        string version
        date publishedAt
        string packageId FK
    }

### JavaScript API

```typescript
import mermaid from "mermaid"

// Initialize:
mermaid.initialize({
  startOnLoad: true,
  theme: "dark",
  securityLevel: "loose",
})

// Render programmatically:
const { svg } = await mermaid.render("diagram-1", `
  flowchart LR
    A[npm install] --> B[Build]
    B --> C[Test]
    C --> D[Deploy]
`)

document.getElementById("output").innerHTML = svg

D3.js

D3.js — data-driven documents:

Bar chart

import * as d3 from "d3"

const data = [
  { name: "react", downloads: 25000000 },
  { name: "vue", downloads: 5000000 },
  { name: "svelte", downloads: 2000000 },
  { name: "solid", downloads: 500000 },
]

const width = 600, height = 400, margin = { top: 20, right: 20, bottom: 40, left: 80 }

const svg = d3.select("#chart")
  .append("svg")
  .attr("width", width)
  .attr("height", height)

const x = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.downloads)!])
  .range([margin.left, width - margin.right])

const y = d3.scaleBand()
  .domain(data.map(d => d.name))
  .range([margin.top, height - margin.bottom])
  .padding(0.2)

svg.selectAll("rect")
  .data(data)
  .join("rect")
  .attr("x", margin.left)
  .attr("y", d => y(d.name)!)
  .attr("width", d => x(d.downloads) - margin.left)
  .attr("height", y.bandwidth())
  .attr("fill", "#3b82f6")

// Add axes:
svg.append("g")
  .attr("transform", `translate(0,${height - margin.bottom})`)
  .call(d3.axisBottom(x).tickFormat(d => `${d / 1e6}M`))

svg.append("g")
  .attr("transform", `translate(${margin.left},0)`)
  .call(d3.axisLeft(y))

Interactive line chart

import * as d3 from "d3"

const data = [
  { date: new Date("2026-01"), value: 100 },
  { date: new Date("2026-02"), value: 150 },
  { date: new Date("2026-03"), value: 130 },
  // ...
]

const x = d3.scaleTime()
  .domain(d3.extent(data, d => d.date) as [Date, Date])
  .range([margin.left, width - margin.right])

const y = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)!])
  .range([height - margin.bottom, margin.top])

const line = d3.line<typeof data[0]>()
  .x(d => x(d.date))
  .y(d => y(d.value))
  .curve(d3.curveMonotoneX)

svg.append("path")
  .datum(data)
  .attr("d", line)
  .attr("fill", "none")
  .attr("stroke", "#3b82f6")
  .attr("stroke-width", 2)

// Add tooltip on hover:
svg.selectAll("circle")
  .data(data)
  .join("circle")
  .attr("cx", d => x(d.date))
  .attr("cy", d => y(d.value))
  .attr("r", 4)
  .attr("fill", "#3b82f6")
  .on("mouseover", (event, d) => {
    tooltip.style("opacity", 1)
      .html(`${d.date.toLocaleDateString()}: ${d.value}`)
      .style("left", `${event.pageX + 10}px`)
      .style("top", `${event.pageY - 10}px`)
  })

Treemap

import * as d3 from "d3"

const data = {
  name: "npm",
  children: [
    { name: "react", value: 25000000 },
    { name: "next", value: 8000000 },
    { name: "vue", value: 5000000 },
    { name: "express", value: 30000000 },
    { name: "axios", value: 50000000 },
  ],
}

const root = d3.treemap<typeof data>()
  .size([width, height])
  .padding(2)(
    d3.hierarchy(data).sum(d => d.value)
  )

svg.selectAll("rect")
  .data(root.leaves())
  .join("rect")
  .attr("x", d => d.x0)
  .attr("y", d => d.y0)
  .attr("width", d => d.x1 - d.x0)
  .attr("height", d => d.y1 - d.y0)
  .attr("fill", (_, i) => d3.schemeTableau10[i])

Chart.js

Chart.js — simple charting:

Bar chart

import { Chart } from "chart.js/auto"

new Chart(document.getElementById("chart") as HTMLCanvasElement, {
  type: "bar",
  data: {
    labels: ["react", "vue", "svelte", "solid"],
    datasets: [{
      label: "Weekly Downloads (M)",
      data: [25, 5, 2, 0.5],
      backgroundColor: ["#61dafb", "#42b883", "#ff3e00", "#446b9e"],
      borderRadius: 6,
    }],
  },
  options: {
    responsive: true,
    plugins: {
      title: { display: true, text: "Framework Downloads" },
      legend: { display: false },
    },
    scales: {
      y: { beginAtZero: true, title: { display: true, text: "Downloads (M)" } },
    },
  },
})

Line chart with multiple datasets

import { Chart } from "chart.js/auto"

new Chart(canvas, {
  type: "line",
  data: {
    labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
    datasets: [
      {
        label: "react",
        data: [22, 23, 24, 24, 25, 25],
        borderColor: "#61dafb",
        tension: 0.3,
      },
      {
        label: "vue",
        data: [4.5, 4.6, 4.8, 4.9, 5.0, 5.1],
        borderColor: "#42b883",
        tension: 0.3,
      },
    ],
  },
  options: {
    responsive: true,
    interaction: { mode: "index", intersect: false },
    plugins: {
      title: { display: true, text: "Download Trends (2026)" },
    },
  },
})

Doughnut / Pie chart

import { Chart } from "chart.js/auto"

new Chart(canvas, {
  type: "doughnut",
  data: {
    labels: ["npm", "pnpm", "yarn", "bun"],
    datasets: [{
      data: [60, 25, 10, 5],
      backgroundColor: ["#cb3837", "#f69220", "#2c8ebb", "#fbf0df"],
      borderWidth: 0,
    }],
  },
  options: {
    responsive: true,
    plugins: {
      title: { display: true, text: "Package Manager Market Share" },
    },
  },
})

React wrapper

import { Bar, Line, Doughnut } from "react-chartjs-2"
import { Chart, registerables } from "chart.js"

Chart.register(...registerables)

function DownloadsChart() {
  return (
    <Bar
      data={{
        labels: ["react", "vue", "svelte"],
        datasets: [{
          label: "Downloads (M)",
          data: [25, 5, 2],
          backgroundColor: ["#61dafb", "#42b883", "#ff3e00"],
        }],
      }}
      options={{
        responsive: true,
        plugins: { legend: { display: false } },
      }}
    />
  )
}

Feature Comparison

FeatureMermaidD3.jsChart.js
PurposeText-to-diagramCustom data vizStandard charts
RenderingSVGSVG/DOMCanvas
InputText/MarkdownData + codeData + config
Coding required❌ (text only)✅ (complex)✅ (simple)
Chart typesDiagrams onlyUnlimited8 built-in
Flowcharts✅ (manual)
Sequence diagrams
ER diagrams
InteractiveBasic✅ (full control)✅ (tooltips, zoom)
Animations
ResponsiveManual
Learning curveLowHighLow
React bindingsVia dangerouslySetInnerHTMLManualreact-chartjs-2
Used byGitHub, Notion, docsNYT, ObservableDashboards
Weekly downloads~3M~5M~5M

When to Use Each

Use Mermaid if:

  • Creating diagrams for documentation (flowcharts, sequence, ER)
  • Want to define diagrams in Markdown (GitHub renders natively)
  • No JavaScript coding needed
  • Building docs with VitePress, Docusaurus, or similar

Use D3.js if:

  • Need fully custom visualizations (no template constraints)
  • Building interactive data explorers or dashboards
  • Need maps, treemaps, force graphs, or novel chart types
  • Want complete control over every SVG element

Use Chart.js if:

  • Need standard charts quickly (bar, line, pie, doughnut, radar)
  • Building admin dashboards or analytics pages
  • Want responsive charts with minimal code
  • Using React (react-chartjs-2)

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on Mermaid v11.x, D3.js v7.x, and Chart.js v4.x.

Compare visualization libraries and frontend tooling on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.