Dot Matrix Chart for Proportional Counts — Apache ECharts

A dot matrix chart displays proportions using a grid of equally-sized dots where filled or colored dots represent counts out of a total. Each dot corresponds to one unit, making it intuitive to read "X out of N" statistics at a glance. Unlike waffle charts that use percentage-based squares, dot matrix charts emphasize absolute counts with variable grid sizes, excelling at risk communication and survey result visualization.

Dot Matrix Chart for Proportional Counts rendered with Apache ECharts

Renders

JavaScript source (Apache ECharts)

// anyplot.ai
// dot-matrix-proportional: Dot Matrix Chart for Proportional Counts
// Library: echarts 6.1.0 | JavaScript 22.23.2
// Quality: 89/100 | Created: 2026-09-05

//# anyplot-orientation: landscape
const t = window.ANYPLOT_TOKENS;
// "muted" semantic anchor (other/rest/neutral) isn't in ANYPLOT_TOKENS — derive it
// from the theme-adaptive hexes in default-style-guide.md "Semantic anchors".
const muted = t.theme === "dark" ? "#A8A79F" : "#6B6A63";

// --- Data (in-memory, deterministic) ----------------------------------------
// Customer satisfaction survey: 200 respondents, filled left-to-right, top-to-bottom.
const total = 200;
const cols = 20;
const rows = 10;
const categories = [
  { name: "Satisfied", count: 118, color: t.palette[0] }, // positive sentiment -> Imprint brand green
  { name: "Neutral", count: 52, color: muted }, // neutral sentiment -> Imprint neutral anchor
  // negative sentiment -> Imprint semantic red; dashed ink border is a non-color
  // secondary cue so the two sentiment extremes stay distinguishable under
  // red-green color-vision deficiency, not just by hue.
  { name: "Dissatisfied", count: 30, color: t.palette[4], accent: true },
];

let cursor = 0;
const series = categories.map((cat) => {
  const points = [];
  for (let i = 0; i < cat.count; i += 1) {
    const idx = cursor + i;
    points.push([idx % cols, Math.floor(idx / cols)]);
  }
  cursor += cat.count;
  const pct = Math.round((cat.count / total) * 100);
  return {
    name: `${cat.name} — ${cat.count} (${pct}%)`,
    type: "scatter",
    symbol: "circle",
    symbolSize: 44,
    data: points,
    itemStyle: cat.accent
      ? { color: cat.color, borderColor: t.ink, borderWidth: 2, borderType: "dashed" }
      : { color: cat.color, borderColor: t.pageBg, borderWidth: 1.5 },
  };
});

// Emphasize the majority category (ECharts markArea) to lift plain grid-of-
// circles into a small "at a glance" callout, and to showcase a native
// ECharts feature beyond a generic scatter port.
const majority = categories.reduce((best, cat) => (cat.count > best.count ? cat : best));
const majorityFullRows = majority === categories[0] ? Math.floor(majority.count / cols) : 0;
if (majorityFullRows > 0) {
  series[0].markArea = {
    silent: true,
    itemStyle: { color: majority.color, opacity: 0.08 },
    label: {
      show: true,
      position: "insideTopLeft",
      color: t.inkSoft,
      fontSize: 13,
      fontWeight: 500,
      formatter: `Majority — ${Math.round((majority.count / total) * 100)}%`,
    },
    data: [
      [
        { xAxis: -0.6, yAxis: -0.6 },
        { xAxis: cols - 0.4, yAxis: majorityFullRows - 0.4 },
      ],
    ],
  };
}

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

// --- Option --------------------------------------------------------------------
chart.setOption({
  animation: false,
  color: t.palette,
  backgroundColor: "transparent",
  title: {
    text: "dot-matrix-proportional · javascript · echarts · anyplot.ai",
    left: "center",
    top: 24,
    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },
  },
  legend: {
    bottom: 20,
    left: "center",
    itemGap: 32,
    itemWidth: 16,
    itemHeight: 16,
    icon: "circle",
    textStyle: { color: t.inkSoft, fontSize: 16 },
  },
  grid: { left: 90, right: 90, top: 140, bottom: 110 },
  xAxis: {
    type: "value",
    min: -0.6,
    max: cols - 0.4,
    show: false,
    splitLine: { show: false },
  },
  yAxis: {
    type: "value",
    min: -0.6,
    max: rows - 0.4,
    inverse: true,
    show: false,
    splitLine: { show: false },
  },
  tooltip: {
    trigger: "item",
    formatter: (p) => p.seriesName,
  },
  series,
});

Retrieve this implementation

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

Part of Dot Matrix Chart for Proportional Counts on anyplot.ai.

Other implementations