{"spec_id":"map-route-path","library":"muix","language":"javascript","code":"// anyplot.ai\n// map-route-path: Route Path Map\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 85/100 | Created: 2026-09-02\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ScatterPlot } from \"@mui/x-charts/ScatterChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { ChartsTooltip } from \"@mui/x-charts/ChartsTooltip\";\nimport { ChartsLegend, ContinuousColorLegend } from \"@mui/x-charts/ChartsLegend\";\nimport { useDrawingArea, useXScale, useYScale } from \"@mui/x-charts/hooks\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: deterministic LCG — a cycling commute route through Amsterdam ----\nfunction makeLcg(seed: number) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nconst rand = makeLcg(42);\n\nconst N_POINTS = 140;\nconst START = { lon: 4.868, lat: 52.358 }; // home, Amsterdam-West\nconst TARGET = { lon: 4.907, lat: 52.376 }; // office, Amsterdam city centre\nconst LON_SPAN = TARGET.lon - START.lon;\nconst LAT_SPAN = TARGET.lat - START.lat;\n\n// Longitude advances monotonically with progress fraction `f`, which rules\n// out any self-crossing loop by construction — the lateral wiggle (mostly on\n// latitude) then reads as organic canal-side turns, not a tangled scribble.\nconst lon: number[] = [];\nconst lat: number[] = [];\nfor (let i = 0; i < N_POINTS; i++) {\n  const f = i / (N_POINTS - 1);\n  const lonWiggle = Math.sin(f * Math.PI * 3) * 0.0012 * Math.sin(f * Math.PI);\n  const latWiggle = Math.sin(f * Math.PI * 4 + 0.6) * 0.0022 * Math.sin(f * Math.PI);\n  const lonNoise = (rand() - 0.5) * 0.0004;\n  const latNoise = (rand() - 0.5) * 0.0004;\n  lon.push(START.lon + LON_SPAN * f + lonWiggle + lonNoise);\n  lat.push(START.lat + LAT_SPAN * f + latWiggle + latNoise);\n}\n\nconst routePoints = lon.map((x, i) => ({ x, y: lat[i], z: i, id: i }));\n\nconst TITLE_HEIGHT = 44;\nconst SUBTITLE_HEIGHT = 28;\nconst MARGIN = { left: 130, right: 70, top: 40, bottom: 80 };\n\n// MUI X Charts is SVG-only — there is no tile-layer support and network\n// fetches are forbidden offline, so a real streets/terrain basemap is not\n// achievable here. The lon/lat graticule (ChartsGrid) plus a compass rose\n// stand in as the spatial reference frame; this caption documents that\n// trade-off explicitly instead of silently omitting the basemap.\nconst BASEMAP_CAPTION =\n  \"No basemap tiles — MUI X is SVG-only; the lon/lat graticule provides spatial reference.\";\n\n// A small \"N\" compass rose drawn in the plot's top-right corner. Purely\n// decorative map framing, positioned via the real drawing-area geometry so\n// it never collides with the data area regardless of margin tuning.\nfunction CompassRose() {\n  const { left, top, width } = useDrawingArea();\n  const cx = left + width - 30;\n  const cy = top + 32;\n  return (\n    <g opacity={0.55}>\n      <line x1={cx} y1={cy + 14} x2={cx} y2={cy - 12} stroke={t.inkSoft} strokeWidth={1.5} />\n      <path d={`M ${cx} ${cy - 18} L ${cx - 5} ${cy - 9} L ${cx + 5} ${cy - 9} Z`} fill={t.inkSoft} />\n      <text x={cx} y={cy - 22} textAnchor=\"middle\" fontSize={11} fill={t.inkSoft}>\n        N\n      </text>\n    </g>\n  );\n}\n\n// The End waypoint renders as a red square (not a circle) so start/end are\n// distinguishable by shape as well as color, per the spec's example. The\n// underlying \"End\" scatter series keeps a small hidden marker (for legend +\n// tooltip) and this square is drawn on top at its exact pixel position.\nfunction EndMarker() {\n  const xScale = useXScale(\"lonAxis\");\n  const yScale = useYScale(\"latAxis\");\n  const cx = xScale(lon[N_POINTS - 1]);\n  const cy = yScale(lat[N_POINTS - 1]);\n  const size = 14;\n  return (\n    <rect\n      x={cx - size / 2}\n      y={cy - size / 2}\n      width={size}\n      height={size}\n      fill={t.palette[4]}\n      stroke={t.pageBg}\n      strokeWidth={1.5}\n    />\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) -----------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n\n  return (\n    <Box\n      sx={{\n        width,\n        height,\n        display: \"flex\",\n        flexDirection: \"column\",\n        paddingTop: \"20px\",\n      }}\n    >\n      <Typography\n        sx={{\n          color: t.ink,\n          fontSize: 22,\n          fontWeight: 500,\n          textAlign: \"center\",\n          lineHeight: 1.2,\n        }}\n      >\n        map-route-path · javascript · muix · anyplot.ai\n      </Typography>\n      <Typography\n        sx={{\n          color: t.inkSoft,\n          fontSize: 12,\n          textAlign: \"center\",\n          lineHeight: 1.2,\n          opacity: 0.85,\n        }}\n      >\n        {BASEMAP_CAPTION}\n      </Typography>\n      <ChartContainer\n        width={width}\n        height={height - TITLE_HEIGHT - SUBTITLE_HEIGHT}\n        margin={MARGIN}\n        skipAnimation\n        xAxis={[\n          {\n            id: \"lonAxis\",\n            data: lon,\n            scaleType: \"linear\",\n            label: \"Longitude (°)\",\n            valueFormatter: (v: number) => v.toFixed(3),\n            tickLabelStyle: { fontSize: 14 },\n            labelFontSize: 16,\n          },\n        ]}\n        yAxis={[\n          {\n            id: \"latAxis\",\n            scaleType: \"linear\",\n            label: \"Latitude (°)\",\n            valueFormatter: (v: number) => v.toFixed(3),\n            // MUI X reserves label clearance as `tickFontSize + tickSize + 10`\n            // (a fixed constant, not the tick label's actual text width), so a\n            // 6-digit coordinate tick (\"52.367\") needs a much larger tickFontSize\n            // than it's actually rendered at to avoid the axis title overlapping\n            // it — tickLabelStyle.fontSize below restores the real 14px size.\n            tickFontSize: 55,\n            tickLabelStyle: { fontSize: 14 },\n            labelFontSize: 16,\n          },\n        ]}\n        zAxis={[\n          {\n            id: \"seqAxis\",\n            min: 0,\n            max: N_POINTS - 1,\n            colorMap: { type: \"continuous\", color: [t.seq[0], t.seq[1]] },\n          },\n          // Start/End markers opt out of the sequence colorMap (which every\n          // scatter series would inherit by default via `defaultZAxisId`)\n          // so their fixed `color` below is used verbatim.\n          { id: \"plainAxis\" },\n        ]}\n        series={[\n          {\n            type: \"line\",\n            xAxisId: \"lonAxis\",\n            yAxisId: \"latAxis\",\n            data: lat,\n            showMark: false,\n            color: t.inkSoft,\n            curve: \"linear\",\n          },\n          {\n            type: \"scatter\",\n            xAxisId: \"lonAxis\",\n            yAxisId: \"latAxis\",\n            zAxisId: \"seqAxis\",\n            data: routePoints,\n            markerSize: 7,\n            valueFormatter: (v) => `waypoint ${v.z}`,\n          },\n          {\n            type: \"scatter\",\n            xAxisId: \"lonAxis\",\n            yAxisId: \"latAxis\",\n            zAxisId: \"plainAxis\",\n            data: [{ x: lon[0], y: lat[0], id: \"start\" }],\n            color: t.palette[0],\n            markerSize: 12,\n            label: \"Start\",\n          },\n          {\n            // markerSize kept small on purpose: the native circle only\n            // needs to exist for the legend swatch + tooltip hit target —\n            // the visible red *square* is drawn by <EndMarker /> on top.\n            type: \"scatter\",\n            xAxisId: \"lonAxis\",\n            yAxisId: \"latAxis\",\n            zAxisId: \"plainAxis\",\n            data: [{ x: lon[N_POINTS - 1], y: lat[N_POINTS - 1], id: \"end\" }],\n            color: t.palette[4],\n            markerSize: 8,\n            label: \"End\",\n          },\n        ]}\n        sx={{\n          \"& .MuiChartsLegend-label\": { fontSize: \"14px\" },\n          \"& .MuiLineElement-root\": { strokeWidth: 2, strokeOpacity: 0.6 },\n        }}\n      >\n        <ChartsGrid horizontal vertical />\n        <CompassRose />\n        <LinePlot />\n        <ScatterPlot />\n        <EndMarker />\n        <ChartsXAxis />\n        <ChartsYAxis />\n        <ChartsLegend position={{ horizontal: \"left\", vertical: \"top\" }} direction=\"row\" />\n        <ContinuousColorLegend\n          axisDirection=\"z\"\n          axisId=\"seqAxis\"\n          position={{ horizontal: \"left\", vertical: \"bottom\" }}\n          direction=\"row\"\n          minLabel=\"Early\"\n          maxLabel=\"Late\"\n          length={220}\n          thickness={10}\n        />\n        <ChartsTooltip trigger=\"item\" />\n      </ChartContainer>\n    </Box>\n  );\n}\n"}