{"spec_id":"line-markers","library":"muix","language":"javascript","code":"// anyplot.ai\n// line-markers: Line Plot with Markers\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-05\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { ChartsText } from \"@mui/x-charts/ChartsText\";\nimport {\n  useXScale,\n  useYScale,\n  useDrawingArea,\n  getValueToPositionMapper,\n} 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 (in-memory, deterministic) ----------------------------------------\n// Torque-wrench QC readings at 14 sequential fastener checkpoints on two\n// assembly stations — sparse enough that each individual reading matters,\n// which is exactly why the markers (not just the trend line) carry meaning.\nconst checkpoints = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];\nconst stationA = [\n  42.6, 42.3, 42.7, 42.4, 42.8, 42.2, 42.5, 42.6, 42.3, 42.7, 42.4, 42.5, 42.6,\n  42.3,\n];\nconst stationB = [\n  41.9, 42.0, 41.7, 42.1, 41.8, 41.6, 41.9, 41.5, 41.8, 41.7, 41.9, 41.6, 41.8,\n  41.7,\n];\nconst TARGET_TORQUE = 42.5;\n\nconst TITLE =\n  \"Fastener Torque QC · line-markers · javascript · muix · anyplot.ai\";\n\n// Station A: solid filled circles — primary series, drawn above its line.\nfunction CircleMarks({ color }: { color: string }) {\n  const xScale = useXScale(\"checkpoint-axis\");\n  const yScale = useYScale(\"torque-axis\");\n  const toX = getValueToPositionMapper(xScale);\n  const r = 10;\n  return (\n    <g>\n      {checkpoints.map((x, i) => (\n        <circle\n          key={x}\n          cx={toX(x)}\n          cy={yScale(stationA[i])}\n          r={r}\n          fill={color}\n          stroke={t.pageBg}\n          strokeWidth={2}\n        />\n      ))}\n    </g>\n  );\n}\n\n// Station B: hollow diamonds — visually distinct shape + fill for the second series.\nfunction DiamondMarks({ color }: { color: string }) {\n  const xScale = useXScale(\"checkpoint-axis\");\n  const yScale = useYScale(\"torque-axis\");\n  const toX = getValueToPositionMapper(xScale);\n  const r = 11;\n  return (\n    <g>\n      {checkpoints.map((x, i) => {\n        const cx = toX(x);\n        const cy = yScale(stationB[i]);\n        const points = `${cx},${cy - r} ${cx + r},${cy} ${cx},${cy + r} ${cx - r},${cy}`;\n        return (\n          <polygon\n            key={x}\n            points={points}\n            fill={t.pageBg}\n            stroke={color}\n            strokeWidth={2.5}\n          />\n        );\n      })}\n    </g>\n  );\n}\n\n// Hand-rolled y-axis title: MUI X's built-in `yAxis.label` offsets itself from\n// a hardcoded tickFontSize guess rather than the tick labels' real measured\n// width, so a 4-digit \"42.6\"-style tick collides with it. A fixed-position\n// label sidesteps that collision.\nfunction YAxisTitle({ text }: { text: string }) {\n  const { top, height } = useDrawingArea();\n  return (\n    <ChartsText\n      x={22}\n      y={top + height / 2}\n      text={text}\n      fill={t.ink}\n      style={{\n        fontSize: 15,\n        angle: -90,\n        textAnchor: \"middle\",\n        dominantBaseline: \"auto\",\n      }}\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  const TITLE_H = 60;\n  const titleSize =\n    TITLE.length > 67 ? Math.max(16, Math.round((22 * 67) / TITLE.length)) : 22;\n\n  return (\n    <Box\n      sx={{\n        width,\n        height,\n        bgcolor: t.pageBg,\n        display: \"flex\",\n        flexDirection: \"column\",\n        boxSizing: \"border-box\",\n      }}\n    >\n      <Box\n        sx={{\n          height: TITLE_H,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n        }}\n      >\n        <Typography sx={{ color: t.ink, fontSize: titleSize, fontWeight: 600 }}>\n          {TITLE}\n        </Typography>\n      </Box>\n\n      <LineChart\n        width={width}\n        height={height - TITLE_H}\n        skipAnimation\n        xAxis={[\n          {\n            id: \"checkpoint-axis\",\n            scaleType: \"point\",\n            data: checkpoints,\n            label: \"QC Checkpoint (sequential reading)\",\n            tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n            labelStyle: { fontSize: 15, fill: t.ink },\n          },\n        ]}\n        yAxis={[\n          {\n            id: \"torque-axis\",\n            min: 41.0,\n            max: 43.2,\n            valueFormatter: (v: number) => v.toFixed(1),\n            tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n            labelStyle: { fontSize: 15, fill: t.ink },\n          },\n        ]}\n        series={[\n          {\n            id: \"stationA\",\n            data: stationA,\n            label: \"Station A\",\n            color: t.palette[0],\n            curve: \"linear\",\n            showMark: false,\n            xAxisId: \"checkpoint-axis\",\n            yAxisId: \"torque-axis\",\n          },\n          {\n            id: \"stationB\",\n            data: stationB,\n            label: \"Station B\",\n            color: t.palette[1],\n            curve: \"linear\",\n            showMark: false,\n            xAxisId: \"checkpoint-axis\",\n            yAxisId: \"torque-axis\",\n          },\n        ]}\n        grid={{ horizontal: true }}\n        margin={{ top: 24, right: 50, bottom: 108, left: 90 }}\n        sx={{\n          \"& .MuiLineElement-series-stationA\": { strokeWidth: 3 },\n          \"& .MuiLineElement-series-stationB\": {\n            strokeWidth: 2.25,\n            strokeDasharray: \"9 6\",\n          },\n          \"& .MuiChartsAxis-line\": { stroke: t.inkSoft, strokeOpacity: 0.4 },\n          \"& .MuiChartsAxis-tick\": { stroke: t.inkSoft, strokeOpacity: 0.4 },\n          \"& .MuiChartsGrid-line\": { stroke: t.grid },\n        }}\n        slotProps={{\n          legend: {\n            position: { vertical: \"bottom\", horizontal: \"middle\" },\n            direction: \"row\",\n            itemMarkWidth: 20,\n            itemMarkHeight: 3,\n            markGap: 8,\n            itemGap: 32,\n            labelStyle: { fontSize: 14, fill: t.inkSoft },\n          },\n        }}\n      >\n        <YAxisTitle text=\"Torque (N·m)\" />\n        <ChartsReferenceLine\n          y={TARGET_TORQUE}\n          label={`Target ${TARGET_TORQUE.toFixed(1)} N·m`}\n          labelAlign=\"end\"\n          lineStyle={{\n            stroke: t.inkSoft,\n            strokeDasharray: \"5 4\",\n            strokeWidth: 1.5,\n            opacity: 0.8,\n          }}\n          labelStyle={{ fontSize: 12, fill: t.inkSoft }}\n        />\n        <CircleMarks color={t.palette[0]} />\n        <DiamondMarks color={t.palette[1]} />\n      </LineChart>\n    </Box>\n  );\n}\n"}