{"spec_id":"polar-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// polar-basic: Basic Polar Chart\n// Library: d3 7.9.0 | JavaScript 22.23.1\n// Quality: 93/100 | Updated: 2026-07-25\n\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// Data — household energy consumption (kWh) by hour of day, a cyclical pattern\nconst hourlyConsumption = [\n  0.42, 0.38, 0.35, 0.33, 0.36, 0.48, 0.72, 1.15, 1.28, 0.95, 0.78, 0.71, 0.68,\n  0.65, 0.62, 0.66, 0.74, 0.98, 1.42, 1.85, 1.76, 1.34, 0.89, 0.58,\n];\nconst data = hourlyConsumption.map((value, hour) => ({ hour, value }));\n\nconst hourLabels = {\n  0: \"12am\",\n  3: \"3am\",\n  6: \"6am\",\n  9: \"9am\",\n  12: \"12pm\",\n  15: \"3pm\",\n  18: \"6pm\",\n  21: \"9pm\",\n};\n\n// Layout — circle centered in the square mount, room reserved for the title\n// and the hour labels ringed just outside the plot circle\nconst margin = { top: 170, right: 110, bottom: 110, left: 110 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\nconst cx = margin.left + iw / 2;\nconst cy = margin.top + ih / 2;\nconst outerRadius = Math.min(iw, ih) / 2 - 50;\nconst innerRadius = outerRadius * 0.13;\n\n// Scales — angle 0 at top (12 o'clock), increasing clockwise through the day.\n// A small inner-radius offset (donut-style start) keeps low overnight values\n// from compressing into the exact center.\nconst angle = (hour) => (hour / 24) * 2 * Math.PI;\nconst radiusScale = d3\n  .scaleLinear()\n  .domain([0, d3.max(data, (d) => d.value) * 1.1])\n  .range([innerRadius, outerRadius]);\nconst pointAt = (rad, r) => [r * Math.sin(rad), -r * Math.cos(rad)];\nconst anchorFor = (rad) => {\n  const s = Math.sin(rad);\n  return Math.abs(s) < 0.01 ? \"middle\" : s > 0 ? \"start\" : \"end\";\n};\nconst baselineFor = (rad) => {\n  const c = Math.cos(rad);\n  return Math.abs(c) < 0.01 ? \"middle\" : c > 0 ? \"hanging\" : \"auto\";\n};\n\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst plot = svg.append(\"g\").attr(\"transform\", `translate(${cx},${cy})`);\n\n// Radial gradient — subtle depth cue, fill grows denser toward the outer edge\nconst gradient = svg\n  .append(\"defs\")\n  .append(\"radialGradient\")\n  .attr(\"id\", \"areaFill\")\n  .attr(\"gradientUnits\", \"userSpaceOnUse\")\n  .attr(\"cx\", cx)\n  .attr(\"cy\", cy)\n  .attr(\"r\", outerRadius);\ngradient.append(\"stop\").attr(\"offset\", \"0%\").attr(\"stop-color\", t.palette[0]).attr(\"stop-opacity\", 0.05);\ngradient.append(\"stop\").attr(\"offset\", \"100%\").attr(\"stop-color\", t.palette[0]).attr(\"stop-opacity\", 0.28);\n\n// Radial gridlines (concentric circles) with value labels along the top spoke\nconst radiusTicks = radiusScale.ticks(4).filter((v) => v > 0);\nplot\n  .selectAll(\".radial-grid\")\n  .data(radiusTicks)\n  .join(\"circle\")\n  .attr(\"class\", \"radial-grid\")\n  .attr(\"r\", (d) => radiusScale(d))\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1);\n\nplot\n  .selectAll(\".radial-tick-label\")\n  .data(radiusTicks)\n  .join(\"text\")\n  .attr(\"class\", \"radial-tick-label\")\n  .attr(\"x\", 10)\n  .attr(\"y\", (d) => -radiusScale(d) - 6)\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"13px\")\n  .text((d, i) => (i === radiusTicks.length - 1 ? `${d.toFixed(1)} kWh` : d.toFixed(1)));\n\n// Angular gridlines (spokes) every 3 hours, drawn from the inner-radius hole\n// out to the rim, with hour labels ringed outside\nconst spokeHours = Object.keys(hourLabels).map(Number);\nplot\n  .selectAll(\".angular-grid\")\n  .data(spokeHours)\n  .join(\"line\")\n  .attr(\"class\", \"angular-grid\")\n  .attr(\"x1\", (h) => pointAt(angle(h), innerRadius)[0])\n  .attr(\"y1\", (h) => pointAt(angle(h), innerRadius)[1])\n  .attr(\"x2\", (h) => pointAt(angle(h), outerRadius)[0])\n  .attr(\"y2\", (h) => pointAt(angle(h), outerRadius)[1])\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1);\n\nplot\n  .selectAll(\".angular-label\")\n  .data(spokeHours)\n  .join(\"text\")\n  .attr(\"class\", \"angular-label\")\n  .attr(\"x\", (h) => pointAt(angle(h), outerRadius + 28)[0])\n  .attr(\"y\", (h) => pointAt(angle(h), outerRadius + 28)[1])\n  .attr(\"text-anchor\", (h) => anchorFor(angle(h)))\n  .attr(\"dominant-baseline\", (h) => baselineFor(angle(h)))\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"15px\")\n  .text((h) => hourLabels[h]);\n\n// Data — closed radial line with a gradient fill (denser toward the rim) and marker dots\nconst radialLine = d3\n  .lineRadial()\n  .angle((d) => angle(d.hour))\n  .radius((d) => radiusScale(d.value))\n  .curve(d3.curveLinearClosed);\n\nplot\n  .append(\"path\")\n  .datum(data)\n  .attr(\"d\", radialLine)\n  .attr(\"fill\", \"url(#areaFill)\")\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 3);\n\nplot\n  .selectAll(\".data-point\")\n  .data(data)\n  .join(\"circle\")\n  .attr(\"class\", \"data-point\")\n  .attr(\"cx\", (d) => pointAt(angle(d.hour), radiusScale(d.value))[0])\n  .attr(\"cy\", (d) => pointAt(angle(d.hour), radiusScale(d.value))[1])\n  .attr(\"r\", 6)\n  .attr(\"fill\", t.palette[0])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 1.5);\n\n// Highlight the evening peak — a soft halo behind a larger marker, plus a callout label\nconst peak = data.reduce((a, b) => (b.value > a.value ? b : a));\nconst peakAngle = angle(peak.hour);\nconst peakRadius = radiusScale(peak.value);\nconst [peakX, peakY] = pointAt(peakAngle, peakRadius);\n\nplot\n  .append(\"circle\")\n  .attr(\"cx\", peakX)\n  .attr(\"cy\", peakY)\n  .attr(\"r\", 15)\n  .attr(\"fill\", t.palette[0])\n  .attr(\"fill-opacity\", 0.18);\n\nplot\n  .append(\"circle\")\n  .attr(\"cx\", peakX)\n  .attr(\"cy\", peakY)\n  .attr(\"r\", 7.5)\n  .attr(\"fill\", t.palette[0])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 2);\n\nconst [peakLabelX, peakLabelY] = pointAt(peakAngle, peakRadius + 36);\nplot\n  .append(\"text\")\n  .attr(\"x\", peakLabelX)\n  .attr(\"y\", peakLabelY)\n  .attr(\"text-anchor\", anchorFor(peakAngle))\n  .attr(\"dominant-baseline\", baselineFor(peakAngle))\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"14px\")\n  .style(\"font-weight\", \"600\")\n  .text(`Peak · ${peak.value.toFixed(2)} kWh`);\n\n// Title\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 64)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"20px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"Household Energy Use by Hour · polar-basic · javascript · d3 · anyplot.ai\");\n"}