{"spec_id":"wireframe-3d-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// wireframe-3d-basic: Basic 3D Wireframe Plot\n// Library: muix 7.29.1 | JavaScript 22.23.1\n// Quality: 82/100 | Created: 2026-08-04\n//# anyplot-orientation: landscape\n// anyplot.ai\n// wireframe-3d-basic: Basic 3D Wireframe Plot\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-08-04\n\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { useDrawingArea } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: wave surface z = sin(x) · cos(y) over a 24x24 grid ---------------\nconst GRID_N = 24;\nconst AXIS_MIN = -3.2;\nconst AXIS_MAX = 3.2;\n\nconst grid: number[] = [];\nfor (let i = 0; i < GRID_N; i++) {\n  grid.push(AXIS_MIN + ((AXIS_MAX - AXIS_MIN) * i) / (GRID_N - 1));\n}\n\n// height[i][j] at (x = grid[i], y = grid[j])\nconst height: number[][] = grid.map((x) => grid.map((y) => Math.sin(x) * Math.cos(y)));\nconst heightFlat = height.flat();\nconst Z_MIN = Math.min(...heightFlat);\nconst Z_MAX = Math.max(...heightFlat);\n\n// --- 3D -> 2D axonometric projection (elevation 22°, azimuth -38°) ---------\nconst ELEV = (22 * Math.PI) / 180;\nconst AZIM = (-38 * Math.PI) / 180;\n\nfunction project(x: number, y: number, z: number) {\n  const xRot = x * Math.cos(AZIM) - y * Math.sin(AZIM);\n  const yRot = x * Math.sin(AZIM) + y * Math.cos(AZIM);\n  return { sx: xRot, sy: yRot * Math.sin(ELEV) + z * Math.cos(ELEV) };\n}\n\n// Two line families satisfy \"grid lines in both x and y directions\": lines\n// that trace along x (each at one fixed y), and lines that trace along y\n// (each at one fixed x).\nconst linesAlongX = grid.map((y, j) => grid.map((x, i) => project(x, y, height[i][j])));\nconst linesAlongY = grid.map((x, i) => grid.map((y, j) => project(x, y, height[i][j])));\n\n// Schematic axis triad anchored at the grid's near corner, mirroring how\n// boxed 3D axes pin their tick arms to the bounding-box edges.\nconst ORIGIN = { x: AXIS_MIN, y: AXIS_MIN, z: Z_MIN };\nconst AXES = [\n  { key: \"x\", end: { x: AXIS_MAX, y: AXIS_MIN, z: Z_MIN }, from: AXIS_MIN, to: AXIS_MAX, label: \"X\" },\n  { key: \"y\", end: { x: AXIS_MIN, y: AXIS_MAX, z: Z_MIN }, from: AXIS_MIN, to: AXIS_MAX, label: \"Y\" },\n  { key: \"z\", end: { x: AXIS_MIN, y: AXIS_MIN, z: Z_MAX }, from: Z_MIN, to: Z_MAX, label: \"Z\" },\n];\n\n// Bounding box of every projected point (mesh + axis arms), padded so labels\n// clear the SVG edges.\nconst allPoints = [\n  ...linesAlongX.flat(),\n  ...linesAlongY.flat(),\n  project(ORIGIN.x, ORIGIN.y, ORIGIN.z),\n  ...AXES.map((a) => project(a.end.x, a.end.y, a.end.z)),\n];\nconst SX_MIN = Math.min(...allPoints.map((p) => p.sx));\nconst SX_MAX = Math.max(...allPoints.map((p) => p.sx));\nconst SY_MIN = Math.min(...allPoints.map((p) => p.sy));\nconst SY_MAX = Math.max(...allPoints.map((p) => p.sy));\nconst PAD_X = (SX_MAX - SX_MIN) * 0.2;\nconst PAD_Y = (SY_MAX - SY_MIN) * 0.26;\n\nconst TITLE_H = 60;\n\n// Custom overlay: maps projected (sx, sy) into the ChartContainer's drawing\n// area and paints the mesh + axis triad as plain SVG — the composition\n// pattern MUI X documents for chart types the built-in series don't cover.\nfunction Wireframe() {\n  const { left, top, width, height: areaHeight } = useDrawingArea();\n  const xOf = (sx: number) => left + ((sx - (SX_MIN - PAD_X)) / (SX_MAX + PAD_X - (SX_MIN - PAD_X))) * width;\n  const yOf = (sy: number) =>\n    top + areaHeight - ((sy - (SY_MIN - PAD_Y)) / (SY_MAX + PAD_Y - (SY_MIN - PAD_Y))) * areaHeight;\n\n  const toPolyline = (pts: { sx: number; sy: number }[]) => pts.map((p) => `${xOf(p.sx)},${yOf(p.sy)}`).join(\" \");\n\n  const origin2d = project(ORIGIN.x, ORIGIN.y, ORIGIN.z);\n  const originPx = { x: xOf(origin2d.sx), y: yOf(origin2d.sy) };\n\n  return (\n    <g>\n      {linesAlongX.map((line, idx) => (\n        <polyline\n          key={`x-${idx}`}\n          points={toPolyline(line)}\n          fill=\"none\"\n          stroke={t.palette[0]}\n          strokeWidth={1.6}\n          opacity={0.85}\n        />\n      ))}\n      {linesAlongY.map((line, idx) => (\n        <polyline\n          key={`y-${idx}`}\n          points={toPolyline(line)}\n          fill=\"none\"\n          stroke={t.palette[1]}\n          strokeWidth={1.6}\n          opacity={0.85}\n        />\n      ))}\n      {AXES.map((a) => {\n        const end2d = project(a.end.x, a.end.y, a.end.z);\n        const endPx = { x: xOf(end2d.sx), y: yOf(end2d.sy) };\n        const dirX = endPx.x - originPx.x;\n        const dirY = endPx.y - originPx.y;\n        const len = Math.hypot(dirX, dirY) || 1;\n        const unitX = dirX / len;\n        const unitY = dirY / len;\n        const perpX = -unitY * 11;\n        const perpY = unitX * 11;\n        // Intermediate ticks at 1/4, 1/2, 3/4 — the min end is shared by all\n        // three arms at the corner and gets one combined label below.\n        const tickFracs = [0.25, 0.5, 0.75];\n        return (\n          <g key={a.key}>\n            <line x1={originPx.x} y1={originPx.y} x2={endPx.x} y2={endPx.y} stroke={t.inkSoft} strokeWidth={2.5} />\n            {tickFracs.map((frac) => {\n              const tickValue = a.from + (a.to - a.from) * frac;\n              const tickX = originPx.x + dirX * frac;\n              const tickY = originPx.y + dirY * frac;\n              return (\n                <g key={frac}>\n                  <line\n                    x1={tickX - perpX}\n                    y1={tickY - perpY}\n                    x2={tickX + perpX}\n                    y2={tickY + perpY}\n                    stroke={t.inkSoft}\n                    strokeWidth={1.5}\n                  />\n                  <text\n                    x={tickX + perpX * 2.1}\n                    y={tickY + perpY * 2.1}\n                    fill={t.inkSoft}\n                    textAnchor=\"middle\"\n                    style={{ fontSize: 13, fontFamily: \"Inter, system-ui, sans-serif\" }}\n                  >\n                    {tickValue.toFixed(1)}\n                  </text>\n                </g>\n              );\n            })}\n            <text\n              x={endPx.x + unitX * 34}\n              y={endPx.y + unitY * 34}\n              fill={t.inkSoft}\n              textAnchor=\"middle\"\n              style={{ fontSize: 13, fontFamily: \"Inter, system-ui, sans-serif\" }}\n            >\n              {a.to.toFixed(1)}\n            </text>\n            <text\n              x={endPx.x + unitX * 58}\n              y={endPx.y + unitY * 58}\n              fill={t.ink}\n              textAnchor=\"middle\"\n              style={{ fontSize: 20, fontWeight: 700, fontFamily: \"Inter, system-ui, sans-serif\" }}\n            >\n              {a.label}\n            </text>\n          </g>\n        );\n      })}\n      <text\n        x={originPx.x - 14}\n        y={originPx.y + 26}\n        fill={t.inkSoft}\n        textAnchor=\"end\"\n        style={{ fontSize: 12, fontFamily: \"Inter, system-ui, sans-serif\" }}\n      >\n        ({AXIS_MIN.toFixed(1)}, {AXIS_MIN.toFixed(1)}, {Z_MIN.toFixed(1)})\n      </text>\n      {/* Legend: explain the two mesh line colors (VQ storytelling gap). */}\n      <g transform={`translate(${left + width - 190}, ${top + 4})`}>\n        <rect x={0} y={0} width={14} height={14} fill={t.palette[0]} />\n        <text\n          x={20}\n          y={11}\n          fill={t.inkSoft}\n          style={{ fontSize: 13, fontFamily: \"Inter, system-ui, sans-serif\" }}\n        >\n          X-direction lines\n        </text>\n        <rect x={0} y={22} width={14} height={14} fill={t.palette[1]} />\n        <text\n          x={20}\n          y={33}\n          fill={t.inkSoft}\n          style={{ fontSize: 13, fontFamily: \"Inter, system-ui, sans-serif\" }}\n        >\n          Y-direction lines\n        </text>\n      </g>\n    </g>\n  );\n}\n\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n\n  return (\n    <div\n      style={{\n        width: W,\n        height: H,\n        background: t.pageBg,\n        fontFamily: \"Inter, system-ui, sans-serif\",\n        display: \"flex\",\n        flexDirection: \"column\",\n      }}\n    >\n      <div style={{ height: TITLE_H, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n        <span style={{ fontSize: 22, fontWeight: 600, color: t.ink }}>\n          wireframe-3d-basic · javascript · muix · anyplot.ai\n        </span>\n      </div>\n      <ChartContainer\n        width={W}\n        height={H - TITLE_H}\n        skipAnimation\n        series={[]}\n        xAxis={[{ min: 0, max: 1 }]}\n        yAxis={[{ min: 0, max: 1 }]}\n        margin={{ top: 20, bottom: 30, left: 30, right: 30 }}\n      >\n        <Wireframe />\n      </ChartContainer>\n    </div>\n  );\n}\n"}