{"spec_id":"line-multi","library":"d3","language":"javascript","code":"// anyplot.ai\n// line-multi: Multi-Line Comparison Plot\n// Library: d3 7.9.0 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-08-05\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\nconst margin = { top: 130, right: 140, bottom: 80, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// Average monthly temperature (°C) across four cities with distinct climates\nconst months = [\n  \"Jan\",\n  \"Feb\",\n  \"Mar\",\n  \"Apr\",\n  \"May\",\n  \"Jun\",\n  \"Jul\",\n  \"Aug\",\n  \"Sep\",\n  \"Oct\",\n  \"Nov\",\n  \"Dec\",\n];\n\nconst series = [\n  {\n    name: \"Reykjavik\",\n    color: t.palette[0],\n    values: [0.4, 1.0, 1.9, 4.6, 8.1, 10.8, 12.9, 12.6, 9.7, 5.9, 2.7, 0.9],\n  },\n  {\n    name: \"London\",\n    color: t.palette[1],\n    values: [5.2, 5.5, 7.8, 10.5, 13.9, 17.0, 19.2, 18.9, 16.1, 12.2, 8.3, 5.7],\n  },\n  {\n    name: \"Cairo\",\n    color: t.palette[2],\n    values: [\n      14.2, 15.4, 18.1, 22.3, 26.1, 28.3, 29.2, 29.0, 27.1, 24.0, 19.6, 15.3,\n    ],\n  },\n  {\n    name: \"Singapore\",\n    color: t.palette[3],\n    values: [\n      26.9, 27.3, 27.9, 28.2, 28.4, 28.3, 27.9, 27.8, 27.6, 27.6, 27.2, 26.9,\n    ],\n  },\n];\n\nconst allValues = series.flatMap((s) => s.values);\nconst yDomain = [\n  Math.floor(Math.min(...allValues)) - 2,\n  Math.ceil(Math.max(...allValues)) + 2,\n];\n\n// Scales\nconst x = d3.scalePoint().domain(months).range([0, iw]).padding(0.5);\nconst y = d3.scaleLinear().domain(yDomain).nice().range([ih, 0]);\n\n// SVG mount\nconst svg = d3\n  .select(\"#container\")\n  .append(\"svg\")\n  .attr(\"width\", width)\n  .attr(\"height\", height);\nconst g = svg\n  .append(\"g\")\n  .attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// Plot-area panel — subtle elevated backdrop for depth beyond the flat page background\ng.append(\"rect\")\n  .attr(\"x\", 0)\n  .attr(\"y\", 0)\n  .attr(\"width\", iw)\n  .attr(\"height\", ih)\n  .attr(\"rx\", 8)\n  .attr(\"fill\", t.elevatedBg)\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1);\n\n// Y-axis grid lines (horizontal, subtle)\ny.ticks(7).forEach((tick) => {\n  g.append(\"line\")\n    .attr(\"x1\", 0)\n    .attr(\"x2\", iw)\n    .attr(\"y1\", y(tick))\n    .attr(\"y2\", y(tick))\n    .attr(\"stroke\", t.grid)\n    .attr(\"stroke-width\", 1);\n});\n\n// Lines — curveMonotoneX gives a smooth, overshoot-free interpolation between\n// the sparse monthly points, reading as a considered curve rather than a raw\n// point-to-point polyline\nconst line = d3\n  .line()\n  .x((d) => x(d.month))\n  .y((d) => y(d.value))\n  .curve(d3.curveMonotoneX);\n\nseries.forEach((s) => {\n  const points = months.map((m, i) => ({ month: m, value: s.values[i] }));\n\n  g.append(\"path\")\n    .datum(points)\n    .attr(\"d\", line)\n    .attr(\"fill\", \"none\")\n    .attr(\"stroke\", s.color)\n    .attr(\"stroke-width\", 3)\n    .attr(\"stroke-linejoin\", \"round\")\n    .attr(\"stroke-linecap\", \"round\");\n\n  g.selectAll(`.dot-${s.name}`)\n    .data(points)\n    .join(\"circle\")\n    .attr(\"cx\", (d) => x(d.month))\n    .attr(\"cy\", (d) => y(d.value))\n    .attr(\"r\", 6)\n    .attr(\"fill\", s.color)\n    .attr(\"stroke\", t.pageBg)\n    .attr(\"stroke-width\", 1.5);\n\n  // Direct end-of-line label, positioned via the y-scale — gives each line\n  // its own focal point at a glance instead of relying solely on the legend\n  const last = points[points.length - 1];\n  g.append(\"text\")\n    .attr(\"x\", x(last.month) + 14)\n    .attr(\"y\", y(last.value))\n    .attr(\"dy\", \"0.35em\")\n    .attr(\"fill\", s.color)\n    .style(\"font-size\", \"13px\")\n    .style(\"font-weight\", \"600\")\n    .text(s.name);\n});\n\n// X axis (bottom)\nconst xAxisEl = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x));\nxAxisEl.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\nxAxisEl.selectAll(\"line\").attr(\"stroke\", t.inkSoft);\nxAxisEl.select(\".domain\").attr(\"stroke\", t.inkSoft);\n\n// Y axis (left)\nconst yAxisEl = g.append(\"g\").call(d3.axisLeft(y).ticks(7));\nyAxisEl.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\nyAxisEl.selectAll(\"line\").attr(\"stroke\", t.inkSoft);\nyAxisEl.select(\".domain\").attr(\"stroke\", t.inkSoft);\n\n// Y axis label\nsvg\n  .append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -(margin.top + ih / 2))\n  .attr(\"y\", 24)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .style(\"font-weight\", \"500\")\n  .text(\"Avg. Temperature (°C)\");\n\n// X axis label\nsvg\n  .append(\"text\")\n  .attr(\"x\", margin.left + iw / 2)\n  .attr(\"y\", height - 22)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .style(\"font-weight\", \"500\")\n  .text(\"Month\");\n\n// Legend — horizontal row below the title\nconst legendY = 92;\nconst legendStartX = margin.left;\nconst legendGap = 300;\n\nseries.forEach((s, i) => {\n  const lx = legendStartX + i * legendGap;\n  svg\n    .append(\"line\")\n    .attr(\"x1\", lx)\n    .attr(\"x2\", lx + 28)\n    .attr(\"y1\", legendY)\n    .attr(\"y2\", legendY)\n    .attr(\"stroke\", s.color)\n    .attr(\"stroke-width\", 3)\n    .attr(\"stroke-linecap\", \"round\");\n  svg\n    .append(\"circle\")\n    .attr(\"cx\", lx + 14)\n    .attr(\"cy\", legendY)\n    .attr(\"r\", 6)\n    .attr(\"fill\", s.color)\n    .attr(\"stroke\", t.pageBg)\n    .attr(\"stroke-width\", 1.5);\n  svg\n    .append(\"text\")\n    .attr(\"x\", lx + 38)\n    .attr(\"y\", legendY + 5)\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"14px\")\n    .style(\"font-weight\", \"500\")\n    .text(s.name);\n});\n\n// Title\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 44)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"18px\")\n  .style(\"font-weight\", \"700\")\n  .style(\"letter-spacing\", \"0.2px\")\n  .text(\n    \"Monthly Average Temperatures by City · line-multi · javascript · d3 · anyplot.ai\",\n  );\n"}