Bipartite Network Graph — Apache ECharts

A bipartite network graph visualizes relationships between two distinct sets of entities, where edges only connect nodes from different sets — never within the same set. The two node groups are arranged in separate columns or rows, making the two-mode structure immediately apparent. This layout is fundamental for understanding cross-category relationships, revealing which entities from one set are linked to which entities in the other, and exposing patterns like hubs, clusters, and isolated nodes.

Bipartite Network Graph rendered with Apache ECharts

Renders

JavaScript source (Apache ECharts)

// anyplot.ai
// network-bipartite: Bipartite Network Graph
// Library: echarts 6.1.0 | JavaScript 22.23.2
// Quality: 92/100 | Created: 2026-09-05

const theme = window.ANYPLOT_THEME;
const t = window.ANYPLOT_TOKENS;
const muted = theme === "dark" ? "#A8A79F" : "#6B6A63";

// --- Data (in-memory, deterministic) ----------------------------------------
// Author-paper affiliation network: which researchers contributed to which
// publications. Edge weight = number of shared authorship credits.
const researchers = [
  "A. Chen", "B. Diallo", "C. Kowalski", "D. Nakamura", "E. Osei",
  "F. Petrova", "G. Reyes", "H. Singh", "I. Tanaka", "J. Volkov",
];
const papers = [
  "Graph Embeddings", "Federated Learning", "Attention Mechanisms",
  "Robotic Grasping", "Climate Modeling", "Protein Folding",
  "Speech Synthesis", "Autonomous Driving", "Drug Discovery",
  "Quantum Computing", "Computer Vision", "Natural Language",
  "Recommender Systems", "Time Series Forecasting",
];

// [researcherIndex, paperIndex, weight]
const links = [
  [0, 0, 3], [0, 1, 2], [0, 3, 4], [0, 5, 1], [0, 13, 1],
  [1, 0, 2], [1, 2, 3],
  [2, 1, 4], [2, 4, 2], [2, 6, 3],
  [3, 3, 1], [3, 7, 2], [3, 8, 3], [3, 9, 1],
  [4, 2, 2], [4, 5, 3],
  [5, 6, 4], [5, 10, 2], [5, 11, 1],
  [6, 4, 3], [6, 9, 2],
  [7, 8, 2], [7, 10, 3], [7, 12, 1],
  [8, 7, 1], [8, 13, 4],
  [9, 11, 2], [9, 12, 3], [9, 13, 2],
];

// --- Layout: two fixed columns, degree-weighted node size -------------------
const researcherDegree = researchers.map(
  (_, i) => links.filter((l) => l[0] === i).length,
);
const paperDegree = papers.map(
  (_, j) => links.filter((l) => l[1] === j).length,
);

// Crossing minimization: barycenter heuristic, alternating a few sweeps
// between the two columns so each side settles near the average position
// of its connected neighbors on the other side.
const researcherLinks = researchers.map((_, i) =>
  links.filter((l) => l[0] === i).map((l) => l[1]),
);
const paperLinks = papers.map((_, j) =>
  links.filter((l) => l[1] === j).map((l) => l[0]),
);

const barycenterSort = (order, neighborLists, otherOrder) => {
  const otherRank = new Map(otherOrder.map((idx, pos) => [idx, pos]));
  return order
    .map((idx, pos) => {
      const neighbors = neighborLists[idx];
      const avg = neighbors.length
        ? neighbors.reduce((sum, n) => sum + otherRank.get(n), 0) /
          neighbors.length
        : pos;
      return { idx, avg };
    })
    .sort((a, b) => a.avg - b.avg)
    .map((e) => e.idx);
};

let researcherOrder = researchers.map((_, i) => i);
let paperOrder = papers.map((_, j) => j);
for (let sweep = 0; sweep < 8; sweep++) {
  paperOrder = barycenterSort(paperOrder, paperLinks, researcherOrder);
  researcherOrder = barycenterSort(researcherOrder, researcherLinks, paperOrder);
}
const researcherPos = new Map(researcherOrder.map((idx, pos) => [idx, pos]));
const paperPos = new Map(paperOrder.map((idx, pos) => [idx, pos]));

const yFor = (i, n) => (n === 1 ? 0.5 : i / (n - 1));
const sizeFor = (degree) => 16 + degree * 6;

const nodes = [
  ...researchers.map((name, i) => ({
    id: `r${i}`,
    name,
    category: 0,
    x: 0,
    y: yFor(researcherPos.get(i), researchers.length),
    symbolSize: sizeFor(researcherDegree[i]),
    label: { position: "left" },
  })),
  ...papers.map((name, j) => ({
    id: `p${j}`,
    name,
    category: 1,
    x: 1,
    y: yFor(paperPos.get(j), papers.length),
    symbolSize: sizeFor(paperDegree[j]),
    label: { position: "right" },
  })),
];

const maxWeight = Math.max(...links.map((l) => l[2]));
const edges = links.map(([r, p, weight]) => ({
  source: `r${r}`,
  target: `p${p}`,
  value: weight,
  lineStyle: {
    color: weight === maxWeight ? t.amber : muted,
    width: 1 + (weight / maxWeight) * 4,
    opacity: 0.35 + (weight / maxWeight) * 0.3,
    curveness: 0.08,
  },
  emphasis: {
    lineStyle: { opacity: 1, width: 2 + (weight / maxWeight) * 4 },
    label: { show: true, formatter: "{c}", color: t.ink, fontSize: 12 },
  },
}));

// --- Init ---------------------------------------------------------------------
const chart = echarts.init(document.getElementById("container"));

// --- Option ---------------------------------------------------------------
chart.setOption({
  animation: false,
  backgroundColor: "transparent",
  title: {
    text: "network-bipartite · javascript · echarts · anyplot.ai",
    left: "center",
    top: 24,
    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },
  },
  legend: {
    data: ["Researchers", "Papers"],
    top: 78,
    left: "center",
    itemGap: 32,
    textStyle: { color: t.inkSoft, fontSize: 15 },
  },
  series: [
    {
      type: "graph",
      layout: "none",
      preserveAspect: "contain",
      roam: false,
      left: "16%",
      right: "16%",
      top: "16%",
      bottom: "8%",
      symbol: "circle",
      categories: [
        { name: "Researchers", itemStyle: { color: t.palette[0] } },
        { name: "Papers", itemStyle: { color: t.palette[1] } },
      ],
      label: {
        show: true,
        color: t.inkSoft,
        fontSize: 14,
        distance: 10,
      },
      itemStyle: { borderColor: t.pageBg, borderWidth: 2 },
      emphasis: { focus: "adjacency", scale: false, lineStyle: { opacity: 1 } },
      blur: { itemStyle: { opacity: 0.25 }, lineStyle: { opacity: 0.1 } },
      data: nodes,
      links: edges,
    },
  ],
});

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/network-bipartite/echarts/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": "network-bipartite",
  "language": "javascript",
  "library": "echarts",
  "page": "https://anyplot.ai/network-bipartite/javascript/echarts",
  "hub": "https://anyplot.ai/network-bipartite",
  "code_json": "https://api.anyplot.ai/specs/network-bipartite/echarts/code",
  "spec_json": "https://api.anyplot.ai/specs/network-bipartite",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/network-bipartite/javascript/echarts/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/network-bipartite/javascript/echarts/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/network-bipartite/javascript/echarts/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/network-bipartite/javascript/echarts/plot-dark.html",
  "quality_score": 92.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Bipartite Network Graph on anyplot.ai.

Other implementations