Network Adjacency Matrix Heatmap — MUI X Charts

A matrix-based representation of a network or graph where rows and columns represent nodes and cell color indicates the presence or weight of edges between them. This visualization complements node-link diagrams by excelling at revealing clusters, structural patterns, and density in large or dense networks where node-link layouts become cluttered. Reordering nodes by cluster, degree, or community membership exposes block-diagonal structure and makes group boundaries immediately visible.

Network Adjacency Matrix Heatmap rendered with MUI X Charts

Renders

JavaScript source (MUI X Charts)

// anyplot.ai
// heatmap-adjacency: Network Adjacency Matrix Heatmap
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 95/100 | Created: 2026-09-05
//# anyplot-orientation: square
// anyplot.ai
// heatmap-adjacency: Network Adjacency Matrix Heatmap
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-09-05

import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { ScatterChart } from "@mui/x-charts/ScatterChart";
import { ContinuousColorLegend } from "@mui/x-charts/ChartsLegend";
import { useXScale, useYScale, useDrawingArea } from "@mui/x-charts/hooks";

const tokens = window.ANYPLOT_TOKENS;

// --- Data (in-memory, deterministic) — cross-team collaboration network ----
// A fixed-seed LCG replaces the browser's non-reproducible Math.random().
function lcg(seed) {
  let s = seed >>> 0;
  return () => {
    s = (Math.imul(1664525, s) + 1013904223) >>> 0;
    return s / 4294967295;
  };
}
const random = lcg(42);

const teams = [
  { name: "Engineering", members: ["Ava", "Noah", "Mia", "Liam", "Zoe"] },
  { name: "Design", members: ["Ivy", "Theo", "Nora", "Omar", "Luca"] },
  { name: "Product", members: ["Maya", "Eli", "Ruby", "Finn", "Sara"] },
  { name: "Marketing", members: ["Nina", "Cole", "Ana", "Drew", "Wes"] },
];
const names = teams.flatMap((team) => team.members);
const clusterSize = teams[0].members.length;
const nodeCount = names.length;

// Edge weight = shared Slack threads/docs per month. Same-team pairs link
// often and strongly; cross-team pairs link rarely and weakly — this is what
// makes the community structure visible as darker diagonal blocks.
const weights = Array.from({ length: nodeCount }, () => new Array(nodeCount).fill(0));
for (let i = 0; i < nodeCount; i += 1) {
  for (let j = i + 1; j < nodeCount; j += 1) {
    const sameTeam = Math.floor(i / clusterSize) === Math.floor(j / clusterSize);
    const linkRoll = random();
    let weight = 0;
    if (sameTeam && linkRoll < 0.85) {
      weight = Math.round(35 + random() * 65);
    } else if (!sameTeam && linkRoll < 0.22) {
      weight = Math.round(5 + random() * 30);
    }
    weights[i][j] = weight;
    weights[j][i] = weight;
  }
}

// Full matrix — both triangles filled, since the underlying graph is
// undirected (a diagonal stays 0: no self-collaboration edges).
const points = [];
for (let row = 0; row < nodeCount; row += 1) {
  for (let col = 0; col < nodeCount; col += 1) {
    points.push({ id: `${row}-${col}`, x: names[col], y: names[row], z: weights[row][col] });
  }
}

const edgeWeights = weights.flat().filter((w) => w > 0);
const minWeight = Math.min(...edgeWeights);
const maxWeight = Math.max(...edgeWeights);

// Custom marker: filled square matrix cells instead of the default circles.
// Absent edges (z === 0) render as the plain page background — visually
// distinct from every real, colored edge — rather than the palest color step.
function AdjacencyCell(props) {
  const { series, xScale, yScale, colorGetter, color } = props;
  const cellWidth = xScale.bandwidth();
  const cellHeight = yScale.bandwidth();

  return (
    <g>
      {series.data.map((point, i) => {
        const x0 = xScale(point.x) ?? 0;
        const y0 = yScale(point.y) ?? 0;
        const fill = point.z > 0 ? (colorGetter ? colorGetter(i) : color) : tokens.pageBg;
        const isTopLink = point.z === maxWeight;
        return (
          <rect
            key={point.id}
            x={x0}
            y={y0}
            width={cellWidth}
            height={cellHeight}
            fill={fill}
            stroke={isTopLink ? tokens.ink : "none"}
            strokeWidth={isTopLink ? 2 : 0}
          />
        );
      })}
    </g>
  );
}

// Ink-soft dividers at team boundaries (drawn at low opacity, distinct from
// both the colored edges and the blank/absent-edge background) so the
// block-diagonal cluster structure reads at a glance.
function ClusterBoundaries() {
  const xScale = useXScale("col");
  const yScale = useYScale("row");
  const drawingArea = useDrawingArea();
  const marks = [];
  for (let k = clusterSize; k < nodeCount; k += clusterSize) {
    const bx = xScale(names[k]) ?? 0;
    const by = yScale(names[k]) ?? 0;
    marks.push(
      <line
        key={`v-${k}`}
        x1={bx}
        y1={drawingArea.top}
        x2={bx}
        y2={drawingArea.top + drawingArea.height}
        stroke={tokens.inkSoft}
        strokeOpacity={0.4}
        strokeWidth={2}
      />,
      <line
        key={`h-${k}`}
        x1={drawingArea.left}
        y1={by}
        x2={drawingArea.left + drawingArea.width}
        y2={by}
        stroke={tokens.inkSoft}
        strokeOpacity={0.4}
        strokeWidth={2}
      />,
    );
  }
  return <g>{marks}</g>;
}

export default function Chart() {
  const { width, height } = window.ANYPLOT_SIZE;
  const TITLE_HEIGHT = 76;
  const MARGIN_TOP = 50;
  const MARGIN_LEFT = 70;
  const MARGIN_RIGHT = 130;
  const MARGIN_BOTTOM = 40;

  const LEGEND_EDGE_PADDING = 14;
  const chartWidth = width - LEGEND_EDGE_PADDING;
  const chartHeight = height - TITLE_HEIGHT;

  return (
    <Box sx={{ width, height, bgcolor: tokens.pageBg, display: "flex", flexDirection: "column" }}>
      <Box sx={{ height: TITLE_HEIGHT, display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center" }}>
        <Typography sx={{ color: tokens.ink, fontSize: 22, fontWeight: 500, lineHeight: 1.2, fontFamily: "inherit" }}>
          heatmap-adjacency · javascript · muix · anyplot.ai
        </Typography>
        <Typography sx={{ color: tokens.inkSoft, fontSize: 13, lineHeight: 1.2, fontFamily: "inherit", pt: "4px" }}>
          Monthly shared threads between teammates, grouped by team
        </Typography>
      </Box>
      <Box sx={{ flex: 1, display: "flex", alignItems: "flex-start", justifyContent: "flex-start" }}>
        <ScatterChart
          width={chartWidth}
          height={chartHeight}
          skipAnimation
          disableVoronoi
          series={[
            {
              id: "collaboration",
              type: "scatter",
              data: points,
              label: "Collaboration weight",
              xAxisId: "col",
              yAxisId: "row",
              zAxisId: "weight",
            },
          ]}
          xAxis={[
            {
              id: "col",
              scaleType: "band",
              data: names,
              categoryGapRatio: 0.04,
              tickLabelStyle: { fontSize: 13, fill: tokens.inkSoft },
              disableTicks: true,
              disableLine: true,
            },
          ]}
          yAxis={[
            {
              id: "row",
              scaleType: "band",
              data: names,
              categoryGapRatio: 0.04,
              tickLabelStyle: { fontSize: 13, fill: tokens.inkSoft },
              disableTicks: true,
              disableLine: true,
            },
          ]}
          zAxis={[
            {
              id: "weight",
              min: minWeight,
              max: maxWeight,
              colorMap: { type: "continuous", min: minWeight, max: maxWeight, color: [tokens.seq[0], tokens.seq[1]] },
            },
          ]}
          topAxis="col"
          bottomAxis={null}
          leftAxis="row"
          rightAxis={null}
          margin={{ top: MARGIN_TOP, right: MARGIN_RIGHT, bottom: MARGIN_BOTTOM, left: MARGIN_LEFT }}
          slots={{ scatter: AdjacencyCell }}
          slotProps={{ legend: { hidden: true } }}
        >
          <ClusterBoundaries />
          <ContinuousColorLegend
            axisId="weight"
            axisDirection="z"
            position={{ horizontal: "right", vertical: "middle" }}
            direction="column"
            length="55%"
            thickness={14}
            minLabel={({ formattedValue }) => `${formattedValue} threads`}
            maxLabel={({ formattedValue }) => `${formattedValue} threads`}
            labelStyle={{ fontSize: 12, fill: tokens.inkSoft, fontFamily: "inherit" }}
          />
        </ScatterChart>
      </Box>
    </Box>
  );
}

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/heatmap-adjacency/muix/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "heatmap-adjacency",
  "language": "javascript",
  "library": "muix",
  "page": "https://anyplot.ai/heatmap-adjacency/javascript/muix",
  "hub": "https://anyplot.ai/heatmap-adjacency",
  "code_json": "https://api.anyplot.ai/specs/heatmap-adjacency/muix/code",
  "spec_json": "https://api.anyplot.ai/specs/heatmap-adjacency",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/heatmap-adjacency/javascript/muix/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/heatmap-adjacency/javascript/muix/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/heatmap-adjacency/javascript/muix/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/heatmap-adjacency/javascript/muix/plot-dark.html",
  "quality_score": 95.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Network Adjacency Matrix Heatmap on anyplot.ai.

Other implementations