Pictogram Chart (Isotype Visualization) — MUI X Charts

A pictogram chart represents quantities using repeated icons or symbols, where each icon stands for a fixed number of units. Inspired by Otto Neurath's ISOTYPE system, this visualization makes numerical comparisons more intuitive and engaging than plain bar charts. It is especially effective for public-facing data communication and infographics where visual appeal and immediate comprehension are important.

Pictogram Chart (Isotype Visualization) rendered with MUI X Charts

Renders

JavaScript source (MUI X Charts)

// anyplot.ai
// pictogram-basic: Pictogram Chart (Isotype Visualization)
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 91/100 | Created: 2026-08-20
//# anyplot-orientation: landscape
// anyplot.ai
// pictogram-basic: Pictogram Chart (Isotype Visualization)
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-08-20
import { ScatterChart } from "@mui/x-charts/ScatterChart";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";

const t = window.ANYPLOT_TOKENS;
const SIZE = window.ANYPLOT_SIZE;
const BRAND = t.palette[0];

// --- Data (in-memory, deterministic) ----------------------------------------
// Weekly harvest volume for a small farm co-op, in tons.
const categories = ["Apples", "Oranges", "Bananas", "Mangoes", "Grapes"];
const harvestTons = [35, 22, 18, 27, 14];
const tonsPerIcon = 5;

const iconCounts = harvestTons.map((tons) => tons / tonsPerIcon);
const maxIconCount = Math.max(...iconCounts);
const labelX = Math.ceil(maxIconCount) + 0.6;
const xMax = labelX + 0.9;

// One data point per full icon, plus one clipped point for the remainder,
// plus a trailing "label" point carrying the exact harvested tonnage.
const points = [];
categories.forEach((_, row) => {
  const count = iconCounts[row];
  const fullIcons = Math.floor(count + 1e-9);
  for (let i = 0; i < fullIcons; i += 1) {
    points.push({ id: `icon-${row}-${i}`, kind: "icon", x: i + 0.5, y: row, frac: 1 });
  }
  const remainder = count - fullIcons;
  if (remainder > 0.02) {
    points.push({ id: `icon-${row}-${fullIcons}`, kind: "icon", x: fullIcons + 0.5, y: row, frac: remainder });
  }
  points.push({ id: `label-${row}`, kind: "label", x: labelX, y: row, text: `${harvestTons[row]} t` });
});

const ICON_RADIUS = 42;

// Custom scatter renderer: draws each unit as a circle icon (fully filled,
// or clipped to its fractional remainder), plus the row's exact-value label —
// the pictogram encoding itself, using the real xScale/yScale MUI X computes.
function PictogramMarks({ series, xScale, yScale, color }) {
  return (
    <g>
      {series.data.map((d) => {
        const cx = xScale(d.x);
        const cy = yScale(d.y);
        if (d.kind === "label") {
          return (
            <text
              key={d.id}
              x={cx}
              y={cy}
              fill={t.ink}
              fontSize={18}
              fontWeight={600}
              textAnchor="start"
              dominantBaseline="central"
            >
              {d.text}
            </text>
          );
        }
        if (d.frac >= 1) {
          return <circle key={d.id} cx={cx} cy={cy} r={ICON_RADIUS} fill={color} />;
        }
        const clipId = `pictogram-clip-${d.id}`;
        return (
          <g key={d.id}>
            <circle cx={cx} cy={cy} r={ICON_RADIUS} fill="none" stroke={color} strokeWidth={2} strokeOpacity={0.4} />
            <clipPath id={clipId}>
              <rect x={cx - ICON_RADIUS} y={cy - ICON_RADIUS} width={2 * ICON_RADIUS * d.frac} height={2 * ICON_RADIUS} />
            </clipPath>
            <circle cx={cx} cy={cy} r={ICON_RADIUS} fill={color} clipPath={`url(#${clipId})`} />
          </g>
        );
      })}
    </g>
  );
}

const title = `Weekly Fruit Harvest · pictogram-basic · javascript · muix · anyplot.ai`;
const titleFontSize = title.length > 67 ? Math.round(22 * (67 / title.length)) : 22;

const HEADER_HEIGHT = 90;

// --- Chart (default-exported component — the harness mounts it) ------------
export default function Chart() {
  return (
    <Box
      sx={{
        width: SIZE.width,
        height: SIZE.height,
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        fontFamily: "Roboto, Helvetica, Arial, sans-serif",
      }}
    >
      <Box
        sx={{
          height: HEADER_HEIGHT,
          display: "flex",
          flexDirection: "column",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <Typography sx={{ color: t.ink, fontSize: titleFontSize, fontWeight: 600, lineHeight: 1.2 }}>
          {title}
        </Typography>
        <Typography sx={{ color: t.inkSoft, fontSize: 14, lineHeight: 1.2, pt: 0.5 }}>
          ● = {tonsPerIcon} tons of fruit
        </Typography>
      </Box>
      <ScatterChart
        width={SIZE.width}
        height={SIZE.height - HEADER_HEIGHT}
        margin={{ top: 50, right: 90, bottom: 50, left: 190 }}
        colors={[BRAND]}
        disableVoronoi
        tooltip={{ trigger: "none" }}
        series={[{ id: "harvest", data: points }]}
        xAxis={[{ id: "icons", min: 0, max: xMax, scaleType: "linear" }]}
        yAxis={[
          {
            id: "categories",
            scaleType: "point",
            data: categories.map((_, i) => i),
            valueFormatter: (i) => categories[i],
            disableTicks: true,
            tickLabelStyle: { fontSize: 16 },
          },
        ]}
        bottomAxis={null}
        slots={{ scatter: PictogramMarks }}
      />
    </Box>
  );
}

Part of Pictogram Chart (Isotype Visualization) on anyplot.ai.

Other implementations