{"spec_id":"renko-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// renko-basic: Basic Renko Chart\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-02\n\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { 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 (in-memory, deterministic LCG PRNG — no fetch, no Math.random) ----\nlet seed = 42;\nfunction rand() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\n// Daily closes for a fictional solar-energy stock across four trend regimes\n// (uptrend, consolidation, downtrend, recovery) — the zigzag that a Renko\n// chart is built to filter down to a handful of clean bricks.\nconst REGIMES = [\n  { days: 60, drift: 0.34 },\n  { days: 50, drift: 0.0 },\n  { days: 70, drift: -0.3 },\n  { days: 60, drift: 0.26 },\n];\nconst closes = [64.0];\nconst dates = [new Date(2024, 0, 2)];\nfor (const regime of REGIMES) {\n  for (let d = 0; d < regime.days; d++) {\n    const noise = (rand() - 0.5) * 1.6;\n    const prev = closes[closes.length - 1];\n    closes.push(Math.max(prev + regime.drift + noise, 5));\n    const nextDate = new Date(dates[dates.length - 1]);\n    nextDate.setDate(nextDate.getDate() + 1);\n    dates.push(nextDate);\n  }\n}\n\n// --- Renko brick construction: a new brick is only drawn once price moves a\n// full BRICK_SIZE away from the last brick's boundary — this is what strips\n// time and minor noise out of the series, leaving only decisive moves.\nconst BRICK_SIZE = 1.5;\nconst bricks = []; // { base, direction: 1 | -1, date }\nlet anchor = Math.round(closes[0] / BRICK_SIZE) * BRICK_SIZE;\nfor (let i = 1; i < closes.length; i++) {\n  const price = closes[i];\n  while (price - anchor >= BRICK_SIZE) {\n    bricks.push({ base: anchor, direction: 1, date: dates[i] });\n    anchor += BRICK_SIZE;\n  }\n  while (anchor - price >= BRICK_SIZE) {\n    anchor -= BRICK_SIZE;\n    bricks.push({ base: anchor, direction: -1, date: dates[i] });\n  }\n}\n\nconst brickLabels = bricks.map((_, i) => `${i + 1}`);\nconst yMin = Math.min(...bricks.map((b) => b.base));\nconst yMax = Math.max(...bricks.map((b) => b.base + BRICK_SIZE));\nconst yPad = (yMax - yMin) * 0.08;\n\nconst fmtDate = (d) => d.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" });\nconst tickEvery = Math.ceil(bricks.length / 10);\n\n// --- Bricks: MUI X community has no native Renko series — draw uniform,\n// gapped rectangles against the shared band/linear scales via\n// useXScale/useYScale, the documented ChartContainer composition pattern for\n// chart types outside the community surface (same idiom as candlestick/OHLC).\n// Bullish bricks (price up) are brand green, bearish (price down) matte red —\n// the finance up/down semantic exception from the style guide. Each brick also\n// carries an ink-colored up/down triangle so direction reads from shape alone,\n// not just hue, for viewers who can't distinguish red from green.\nfunction Bricks() {\n  const xScale = useXScale(\"x\");\n  const yScale = useYScale(\"y\");\n  if (!xScale || !yScale) return null;\n  const bw = xScale.bandwidth();\n  const brickWidth = bw * 0.78;\n\n  return (\n    <g>\n      {bricks.map((brick, i) => {\n        const cx = xScale(brickLabels[i]) + bw / 2;\n        const color = brick.direction === 1 ? t.palette[0] : t.palette[4];\n        const yTop = yScale(brick.base + BRICK_SIZE);\n        const yBottom = yScale(brick.base);\n        const brickHeight = yBottom - yTop;\n        const cy = (yTop + yBottom) / 2;\n        const markSize = Math.min(brickWidth, brickHeight) * 0.4;\n        const showMark = markSize >= 6;\n        const points =\n          brick.direction === 1\n            ? `${cx},${cy - markSize / 2} ${cx - markSize / 2},${cy + markSize / 2} ${cx + markSize / 2},${cy + markSize / 2}`\n            : `${cx},${cy + markSize / 2} ${cx - markSize / 2},${cy - markSize / 2} ${cx + markSize / 2},${cy - markSize / 2}`;\n        return (\n          <g key={i}>\n            <rect\n              x={cx - brickWidth / 2}\n              y={yTop}\n              width={brickWidth}\n              height={brickHeight}\n              fill={color}\n              stroke={t.ink}\n              strokeOpacity={0.3}\n              strokeWidth={1}\n            />\n            {showMark && <polygon points={points} fill={t.ink} fillOpacity={0.8} />}\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n  const TITLE_H = 96;\n  const LEGEND_H = 52;\n  const chartH = H - TITLE_H - LEGEND_H;\n\n  const title = \"SolarGrid Energy Daily Close · renko-basic · javascript · muix · anyplot.ai\";\n  const titleSize = title.length > 67 ? Math.round((22 * 67) / title.length) : 22;\n  const subtitle = `${fmtDate(dates[0])} – ${fmtDate(dates[dates.length - 1])} · $${BRICK_SIZE.toFixed(2)} brick size · ${bricks.length} bricks`;\n\n  const legendItems = [\n    { label: \"Bullish brick (price up)\", color: t.palette[0] },\n    { label: \"Bearish brick (price down)\", color: t.palette[4] },\n  ];\n\n  return (\n    <Box\n      sx={{\n        width: W,\n        height: H,\n        bgcolor: t.pageBg,\n        display: \"flex\",\n        flexDirection: \"column\",\n        fontFamily: \"'Roboto', 'Helvetica Neue', Arial, sans-serif\",\n        boxSizing: \"border-box\",\n      }}\n    >\n      <Box\n        sx={{\n          height: TITLE_H,\n          display: \"flex\",\n          flexDirection: \"column\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          gap: \"6px\",\n        }}\n      >\n        <Typography sx={{ color: t.ink, fontSize: titleSize, fontWeight: 600 }}>{title}</Typography>\n        <Typography sx={{ color: t.inkSoft, fontSize: 13, fontWeight: 400, letterSpacing: \"0.03em\" }}>\n          {subtitle}\n        </Typography>\n      </Box>\n\n      <ChartContainer\n        width={W}\n        height={chartH}\n        skipAnimation\n        series={[]}\n        xAxis={[\n          {\n            id: \"x\",\n            scaleType: \"band\",\n            data: brickLabels,\n            label: \"Brick Sequence (estimated dates)\",\n            valueFormatter: (label) => fmtDate(bricks[Number(label) - 1].date),\n            tickLabelInterval: (_value, index) => index % tickEvery === 0,\n            tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n            labelStyle: { fontSize: 15, fill: t.ink },\n          },\n        ]}\n        yAxis={[\n          {\n            id: \"y\",\n            min: yMin - yPad,\n            max: yMax + yPad,\n            label: \"Price (USD)\",\n            valueFormatter: (v) => `$${v.toFixed(2)}`,\n            tickLabelStyle: { fontSize: 13, fill: t.inkSoft },\n            labelStyle: { fontSize: 15, fill: t.ink },\n          },\n        ]}\n        margin={{ top: 24, bottom: 64, left: 96, right: 32 }}\n        sx={{\n          \"& .MuiChartsAxis-line\": { stroke: t.inkSoft, strokeOpacity: 0.2 },\n          \"& .MuiChartsGrid-line\": { stroke: t.grid },\n        }}\n      >\n        <ChartsGrid horizontal />\n        <Bricks />\n        <ChartsReferenceLine\n          axisId=\"y\"\n          y={yMax}\n          label={`Swing high $${yMax.toFixed(2)}`}\n          labelAlign=\"end\"\n          lineStyle={{ stroke: t.amber, strokeDasharray: \"4 4\", strokeWidth: 1.5 }}\n          labelStyle={{ fill: t.amber, fontSize: 12, fontWeight: 600 }}\n        />\n        <ChartsReferenceLine\n          axisId=\"y\"\n          y={yMin}\n          label={`Swing low $${yMin.toFixed(2)}`}\n          labelAlign=\"end\"\n          lineStyle={{ stroke: t.amber, strokeDasharray: \"4 4\", strokeWidth: 1.5 }}\n          labelStyle={{ fill: t.amber, fontSize: 12, fontWeight: 600 }}\n        />\n        <ChartsXAxis axisId=\"x\" />\n        <ChartsYAxis axisId=\"y\" slotProps={{ axisLabel: { x: -64 } }} />\n      </ChartContainer>\n\n      <Box sx={{ height: LEGEND_H, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", gap: \"16px\" }}>\n        {legendItems.map((item) => (\n          <Box\n            key={item.label}\n            sx={{\n              display: \"flex\",\n              alignItems: \"center\",\n              gap: \"8px\",\n              padding: \"6px 16px\",\n              borderRadius: \"999px\",\n              border: `1px solid ${t.grid}`,\n              bgcolor: t.elevatedBg,\n            }}\n          >\n            <Box sx={{ width: 10, height: 10, borderRadius: \"2px\", bgcolor: item.color }} />\n            <Typography sx={{ color: t.inkSoft, fontSize: 13, fontWeight: 500 }}>{item.label}</Typography>\n          </Box>\n        ))}\n      </Box>\n    </Box>\n  );\n}\n"}