GenBlocks
Menu
Browse
  • Blocks
  • Images
Agents
  • Connect
Categories
  • All
  • Banners
  • Blog
  • Cards
  • Charts
  • Contact
  • CTA
  • Dashboard
  • FAQ
  • Features
  • Footer
  • Forms & Auth
  • Gallery
  • Hero
  • Integrations
  • Navbar
  • Newsletter & Waitlist
  • Onboarding
  • Pricing
  • Social proof
  • States
  • Stats
  • Tables
  • Team
  • Testimonials
  • Timeline
Source
GenBlocks
Log in
Log inSign up
  1. Blocks

© 2026 GenBlocks. All rights reserved.

AboutImpressumPrivacyTerms

Interactive preview, sandboxed: nothing you type leaves this page.

Files
import { useState } from "react";type Point = { hour: number; value: number };const historyValues = [64, 62, 59, 56, 53, 50, 47, 45, 43, 42, 41, 42, 45, 50, 56, 62, 66, 68, 67, 64, 61, 58, 56, 55, 54];const forecastValues = [54, 53, 51, 48, 45, 43, 42, 41, 42, 46, 52, 59, 65, 68, 67, 64, 60, 56, 52, 48, 45, 43, 42, 43, 46];const historical: Point[] = historyValues.map((value, index) => ({ hour: index - 24, value }));const forecast: Point[] = forecastValues.map((value, index) => ({ hour: index, value }));const allValues = new Map<number, number>([  ...historical.map(({ hour, value }) => [hour, value] as [number, number]),  ...forecast.map(({ hour, value }) => [hour, value] as [number, number]),]);function hourLabel(hour: number) {  if (hour === 0) return "Now";  return `${hour > 0 ? "+" : "−"}${Math.abs(hour)}h`;}function ChartSvg({ compact, selected, onSelect }: { compact: boolean; selected: number; onSelect: (hour: number) => void }) {  const width = compact ? 360 : 760;  const height = compact ? 380 : 400;  const plot = compact    ? { left: 43, right: 350, top: 34, bottom: 298 }    : { left: 70, right: 744, top: 38, bottom: 310 };  const x = (hour: number) => plot.left + ((hour + 24) / 48) * (plot.right - plot.left);  const y = (value: number) => plot.bottom - ((value - 30) / 40) * (plot.bottom - plot.top);  const range = (hour: number) => hour === 0 ? 2 : 2 + (Math.abs(hour) / 24) * 3;  const upper = forecast.map((point) => `${x(point.hour)},${y(Math.min(70, point.value + range(point.hour)))}`).join(" ");  const lower = [...forecast].reverse().map((point) => `${x(point.hour)},${y(Math.max(30, point.value - range(point.hour)))}`).join(" ");  const linePoints = (points: Point[]) => points.map((point) => `${x(point.hour)},${y(point.value)}`).join(" ");  const xTicks = compact ? [-24, -16, -8, 0, 8, 16, 24] : [-24, -18, -12, -6, 0, 6, 12, 18, 24];  const yTicks = [30, 40, 50, 60, 70];  const selectedValue = allValues.get(selected) ?? 54;  const selectedIsForecast = selected > 0;  function moveFromPointer(event: React.PointerEvent<SVGSVGElement>) {    const bounds = event.currentTarget.getBoundingClientRect();    const localX = ((event.clientX - bounds.left) / bounds.width) * width;    const fractionalHour = ((localX - plot.left) / (plot.right - plot.left)) * 48 - 24;    onSelect(Math.max(-24, Math.min(24, Math.round(fractionalHour))));  }  function handleKey(event: React.KeyboardEvent<SVGSVGElement>) {    if (event.key === "ArrowRight" || event.key === "ArrowUp") {      event.preventDefault();      onSelect(Math.min(24, selected + 1));    } else if (event.key === "ArrowLeft" || event.key === "ArrowDown") {      event.preventDefault();      onSelect(Math.max(-24, selected - 1));    } else if (event.key === "Home") {      event.preventDefault();      onSelect(-24);    } else if (event.key === "End") {      event.preventDefault();      onSelect(24);    }  }  return (    <svg      className="block h-auto w-full touch-pan-y rounded-lg focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-emerald-700 dark:focus-visible:outline-emerald-300"      viewBox={`0 0 ${width} ${height}`}      role="group"      aria-label="Renewable share line chart, 24 hours measured history and 24 hours forecast"      aria-describedby="chart-instructions"      tabIndex={0}      onKeyDown={handleKey}      onPointerMove={moveFromPointer}      onPointerDown={moveFromPointer}    >      <title>Renewable share across the past and next 24 hours</title>      <desc>Measured history is a solid green line. The forecast is dashed with a shaded uncertainty range. A dashed horizontal line marks 50 percent. Use left and right arrow keys to inspect each hour.</desc>      {yTicks.map((tick) => (        <g key={tick}>          <line            x1={plot.left}            x2={plot.right}            y1={y(tick)}            y2={y(tick)}            stroke={tick === 50 ? "#64748b" : "#dbe3e0"}            strokeWidth={tick === 50 ? 1.5 : 1}            strokeDasharray={tick === 50 ? "6 5" : undefined}            className={tick === 50 ? "dark:opacity-90" : "dark:stroke-neutral-700"}          />          <text x={plot.left - 10} y={y(tick) + 5} textAnchor="end" className="fill-slate-600 dark:fill-neutral-300" fontSize={compact ? 12 : 14}>            {tick}%          </text>        </g>      ))}      <text        x={compact ? 12 : 18}        y={(plot.top + plot.bottom) / 2}        transform={`rotate(-90 ${compact ? 12 : 18} ${(plot.top + plot.bottom) / 2})`}        textAnchor="middle"        className="fill-slate-700 dark:fill-neutral-300"        fontSize={compact ? 12 : 14}        fontWeight="600"      >Renewable share</text>      <polygon points={`${upper} ${lower}`} fill="#34a982" fillOpacity="0.17" />      <line x1={x(0)} x2={x(0)} y1={plot.top} y2={plot.bottom} stroke="#0f766e" strokeWidth="1.5" strokeDasharray="4 5" />      <polyline points={linePoints(historical)} fill="none" stroke="#126b50" strokeWidth={compact ? 3 : 3.5} strokeLinejoin="round" strokeLinecap="round" />      <polyline points={linePoints(forecast)} fill="none" stroke="#126b50" strokeWidth={compact ? 3 : 3.5} strokeDasharray="8 6" strokeLinejoin="round" strokeLinecap="round" />      <circle cx={x(0)} cy={y(54)} r={compact ? 4 : 5} fill="#fff" stroke="#126b50" strokeWidth="3" />      <text x={x(0) + (compact ? 5 : 9)} y={plot.top - 12} className="fill-emerald-800 dark:fill-emerald-300" fontSize={compact ? 11 : 13} fontWeight="700">NOW</text>      {xTicks.map((tick) => (        <g key={tick}>          <line x1={x(tick)} x2={x(tick)} y1={plot.bottom} y2={plot.bottom + 5} className="stroke-slate-500 dark:stroke-neutral-400" />          <text x={x(tick)} y={plot.bottom + (compact ? 22 : 24)} textAnchor="middle" className="fill-slate-600 dark:fill-neutral-300" fontSize={compact ? 12 : 14} fontWeight={tick === 0 ? 700 : 400}>            {tick === 0 ? "Now" : `${tick > 0 ? "+" : "−"}${Math.abs(tick)}h`}          </text>        </g>      ))}      <text x={(plot.left + plot.right) / 2} y={height - 12} textAnchor="middle" className="fill-slate-700 dark:fill-neutral-300" fontSize={compact ? 12 : 14} fontWeight="600">Time from now (hours)</text>      <line x1={x(selected)} x2={x(selected)} y1={plot.top} y2={plot.bottom} stroke="#0f766e" strokeWidth="1.5" strokeOpacity="0.8" />      <circle cx={x(selected)} cy={y(selectedValue)} r={compact ? 6 : 7} fill={selectedIsForecast ? "#fff" : "#126b50"} stroke="#126b50" strokeWidth="3" />      <circle cx={x(selected)} cy={y(selectedValue)} r={compact ? 10 : 11} fill="none" stroke="#126b50" strokeOpacity="0.22" strokeWidth="2" />    </svg>  );}export default function Block() {  const [selected, setSelected] = useState(0);  const value = allValues.get(selected) ?? 54;  const uncertainty = selected > 0 ? Math.round(2 + (selected / 24) * 3) : 0;  return (    <section className="mx-auto w-full max-w-5xl rounded-2xl border border-slate-200 bg-white px-4 py-6 text-slate-950 shadow-sm sm:px-7 sm:py-8 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-100">      <header className="flex flex-col gap-5 border-b border-slate-200 pb-5 sm:flex-row sm:items-end sm:justify-between dark:border-neutral-800">        <div>          <p className="mb-2 text-xs font-semibold uppercase tracking-[0.16em] text-emerald-800 dark:text-emerald-300">Ember Grid <span className="px-1 text-slate-400">/</span> National grid forecast</p>          <h1 className="text-2xl font-semibold tracking-tight sm:text-3xl">Renewable share</h1>          <p className="mt-1 text-sm text-slate-700 dark:text-neutral-300">48-hour outlook <span className="px-1 text-slate-400">·</span> measured through now, forecast ahead</p>        </div>        <div className="flex items-end justify-between gap-6 sm:justify-end">          <div>            <p className="text-xs font-medium text-slate-700 dark:text-neutral-300">{selected === 0 ? "At present" : `${hourLabel(selected)} ${selected > 0 ? "forecast" : "measured"}`}</p>            <p className="mt-0.5 text-3xl font-semibold tabular-nums tracking-tight text-emerald-800 dark:text-emerald-300">{value}<span className="ml-0.5 text-xl">%</span></p>          </div>          {selected > 0 && <p className="max-w-28 pb-1 text-right text-xs leading-5 text-slate-700 dark:text-neutral-300">Forecast range<br /><span className="font-semibold tabular-nums text-slate-900 dark:text-neutral-100">{Math.max(30, value - uncertainty)}–{Math.min(70, value + uncertainty)}%</span></p>}        </div>      </header>      <div className="mt-4 flex flex-wrap items-center gap-x-5 gap-y-2 text-xs font-medium text-slate-700 dark:text-neutral-300" aria-label="Chart legend">        <span className="inline-flex items-center gap-2"><span className="h-0.5 w-6 bg-emerald-800 dark:bg-emerald-400" aria-hidden="true" />Measured</span>        <span className="inline-flex items-center gap-2"><span className="h-0.5 w-6 border-t-2 border-dashed border-emerald-800 dark:border-emerald-400" aria-hidden="true" />Forecast</span>        <span className="inline-flex items-center gap-2"><span className="h-3 w-6 rounded-sm border border-emerald-700/30 bg-emerald-600/20" aria-hidden="true" />Forecast range</span>        <span className="inline-flex items-center gap-2"><span className="h-0 w-6 border-t border-dashed border-slate-500 dark:border-neutral-400" aria-hidden="true" />50% reference</span>      </div>      <div className="mt-3">        <p id="chart-instructions" className="sr-only">Focus the chart, then use the left and right arrow keys to move hour by hour. Home and End move to the beginning and end. Moving the pointer also updates the readout.</p>        <p className="sr-only" aria-live="polite" aria-atomic="true">{selected === 0 ? `Now, measured renewable share ${value} percent.` : `${hourLabel(selected)}, ${selected > 0 ? "forecast" : "measured"} renewable share ${value} percent${selected > 0 ? `, range ${Math.max(30, value - uncertainty)} to ${Math.min(70, value + uncertainty)} percent` : ""}.`}</p>        <div className="hidden sm:block"><ChartSvg compact={false} selected={selected} onSelect={setSelected} /></div>        <div className="sm:hidden"><ChartSvg compact selected={selected} onSelect={setSelected} /></div>      </div>    </section>  );}

Design a standalone SVG line-chart block for Ember Grid

Category
Charts
Date
23 Oct 2026
Model
gpt-6-luna
Source
AI-generated
Trace
34f9e72f410a6a75cc08a60325957109

Judge

Render
92
Accessibility
91
Visual
83
Copy
92
Compliance
96

Harness

assets
ok
safety
ok
imports
ok
ast
ok
Types
ok
Lint> genblocks-sandbox@0.0.0 lint > eslint . /tmp/tmpyfaxskm7/scripts/inline.mjs 25:24 error 'process' is not defined no-undef 190:3 error 'process' is not defined no-undef 190:47 error 'Buffer' is not defined no-undef /tmp/tmpyfaxskm7/scripts/scan.mjs 23:14 error 'process' is not defined no-undef 24:14 error 'process' is not defined no-undef 264:21 error 'URL' is not defined no-undef 353:3 error 'co
failed
Build
ok
bundle
ok
Render
ok
Desktop
ok
Laptop
ok
Tablet
ok
Phone
ok
Touch targets
ok
Keyboard focus
ok
Reduced motion
ok
Accessibility
ok
Accessibility, dark
ok
preview
ok
Sign in to report