Relationship / Advanced

Network Diagram

Show connections and isolated entities with editable nodes and edges.

Back to collection

Interactive Chart Preview

JAVASCRIPT
Live, interactive exampleChart.js

Chart.js Code Example

JAVASCRIPT
import Chart from 'chart.js/auto';

// Illustrative data used by the live preview.
const chartData = {
  "nodeHeaders": [
    "Node ID",
    "Label"
  ],
  "edgeHeaders": [
    "First Node ID",
    "Second Node ID"
  ],
  "network": {
    "nodes": [
      {
        "id": "web",
        "label": "Web App"
      },
      {
        "id": "api",
        "label": "API"
      },
      {
        "id": "auth",
        "label": "Identity"
      },
      {
        "id": "db",
        "label": "Database"
      },
      {
        "id": "cache",
        "label": "Cache"
      },
      {
        "id": "archive",
        "label": "Archive"
      }
    ],
    "edges": [
      {
        "source": "web",
        "target": "api"
      },
      {
        "source": "web",
        "target": "auth"
      },
      {
        "source": "api",
        "target": "auth"
      },
      {
        "source": "api",
        "target": "db"
      },
      {
        "source": "api",
        "target": "cache"
      }
    ]
  },
  "datasets": []
};

function createConfig(chartData) {
    const graph = chartData.network;
    const nodes = graph.nodes.map((node, index) => {
        const angle = -Math.PI / 2 + index * Math.PI * 2 / graph.nodes.length;
        return { ...node, x: graph.nodes.length === 1 ? 0 : Math.cos(angle), y: graph.nodes.length === 1 ? 0 : Math.sin(angle), degree: graph.edges.filter(edge => edge.source === node.id || edge.target === node.id).length };
    });
    const byId = new Map(nodes.map(node => [node.id, node]));
    const edges = graph.edges.map(edge => ({ source: byId.get(edge.source), target: byId.get(edge.target) }));
    const radius = Math.max(1.5, Math.min(7, 180 / nodes.length));
    const label = (node) => `${node.label} (${node.id})`;
    const shortLabel = (node) => label(node).length > 22 ? label(node).slice(0, 21) + '…' : label(node);
    const geometry = (chart) => ({ x: (chart.chartArea.left + chart.chartArea.right) / 2, y: (chart.chartArea.top + chart.chartArea.bottom) / 2, size: Math.max(1, Math.min(chart.chartArea.width / 2 - 85, chart.chartArea.height / 2 - 30)) });
    return {
        type: 'scatter', data: { datasets: [{ data: nodes.map(node => ({ x: node.x, y: node.y })), pointRadius: radius, pointHoverRadius: radius + 2, backgroundColor: '#2563eb' }] },
        options: { responsive: true, maintainAspectRatio: false, animation: false,
            scales: { x: { display: false, min: -1.7, max: 1.7 }, y: { display: false, min: -1.2, max: 1.2, reverse: true } },
            plugins: { legend: { display: false }, title: { display: true, text: 'Undirected Associations' }, tooltip: { callbacks: { title: items => label(nodes[items[0].dataIndex]), label: item => `Neighbors: ${nodes[item.dataIndex].degree}` } } },
        },
        plugins: [{ id: 'network-connections', afterDatasetsUpdate(chart) {
                    const center = geometry(chart);
                    chart.getDatasetMeta(0).data.forEach((element, index) => { element.x = center.x + nodes[index].x * center.size; element.y = center.y + nodes[index].y * center.size; });
                }, beforeDatasetsDraw(chart) {
                    const { ctx } = chart, center = geometry(chart);
                    ctx.save();
                    ctx.strokeStyle = '#94a3b8';
                    ctx.lineWidth = 1.5;
                    edges.forEach(edge => { ctx.beginPath(); ctx.moveTo(center.x + edge.source.x * center.size, center.y + edge.source.y * center.size); ctx.lineTo(center.x + edge.target.x * center.size, center.y + edge.target.y * center.size); ctx.stroke(); });
                    ctx.restore();
                }, afterDatasetsDraw(chart) {
                    if (nodes.length > 20)
                        return;
                    const { ctx } = chart, center = geometry(chart);
                    ctx.save();
                    ctx.fillStyle = '#334155';
                    ctx.font = '10px sans-serif';
                    ctx.textBaseline = 'middle';
                    nodes.forEach(node => { ctx.textAlign = node.x >= 0 ? 'left' : 'right'; ctx.fillText(shortLabel(node), center.x + node.x * center.size * 1.09 + (nodes.length === 1 ? 12 : 0), center.y + node.y * center.size * 1.09, 82); });
                    ctx.restore();
                } }],
    };
}

const chart = new Chart(document.getElementById('myChart'), createConfig(chartData));

Preview and code follow the same library selection.

Run This Example

Install the package below in a browser project such as Vite. Put the copied code in a client JavaScript module and add this container to the page before the module runs.

npm install chart.js@4.5.1
<div style="height:320px"><canvas id="myChart"></canvas></div>

The example includes its sample data. When removing the chart, destroy or dispose of its instance and disconnect any resize observer.

Reading This Example

This illustrative service network has six nodes and five undirected associations. API connects to Web App, Identity, Database, and Cache, giving it four neighbors. Web App, API, and Identity form a cycle. Archive has no supplied edges and remains visible as an isolated node. The layout shows neither request direction nor traffic volume.

All library tabs use these illustrative values. Rendering styles and library APIs differ, so use the table to compare exact numbers.

About this example & sample data
Sample values used in the chart
Node IDLabel
webWeb App
apiAPI
authIdentity
dbDatabase
cacheCache
archiveArchive
Sample Edges
First Node IDSecond Node ID
webapi
webauth
apiauth
apidb
apicache

READ THE CHART

Read Connections Without Inventing Distance

A Network Diagram represents entities as nodes and their relationships as edges. It helps you inspect which entities connect, which form connected groups, and which have no recorded connection. The meaning of an edge must come from the dataset.

01

Define What an Edge Means

This example uses undirected, unweighted associations. A line between two services means a supplied association exists; it does not specify who calls whom, how often they communicate, or how much data moves. Node circles have equal size and edges have equal styling. Use a Sankey Diagram for directed flow amounts or a Chord Diagram for weighted group pairs.

02

Keep IDs Separate From Labels

The Nodes table defines each entity by a unique ID and a readable label. Labels may repeat, but IDs remain distinct and case-sensitive. The Edges table references those IDs. Renaming an ID or removing a connected node leaves its old references invalid until you update or remove them. The editor keeps the previous valid diagram and copied examples during those changes.

03

Preserve Isolated Nodes and Cycles

A node does not need an edge to remain in the diagram. An empty Edges table is valid and shows all nodes as isolated. Enter each undirected pair once; reversing its endpoints does not make a new relationship. Self-links are excluded from this example. Cycles among different nodes and disconnected groups are valid.

04

Treat Layout as a Reading Aid

Nodes follow their table order around a circle, using a deterministic layout rather than a moving force simulation. The native ECharts layout can use a different rotation. Positions, distances, crossings, and empty spaces do not encode geography, importance, or strength. A crossing line does not create a new node. Dense labels may be hidden while every node remains in the table, copied data, and preview. Seaborn draws the points; Matplotlib draws the connection lines in that example.

Edit Chart Data