{"spec_id":"parallel-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// parallel-basic: Basic Parallel Coordinates Plot\n// Library: d3 7.9.0 | JavaScript 22.23.1\n// Quality: 89/100 | Created: 2026-07-24\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 150, right: 230, bottom: 60, left: 70 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// Cloud compute instance specs by family — vCPU/memory/storage grow together\n// within a family, while price and network throughput separate the families.\n// Each axis is scaled to its own min/max (not a shared scale), which is the\n// standard normalization for parallel coordinates: every dimension has its\n// own units, so a fixed 0-1 range per axis is what makes them comparable.\nconst dimensions = [\n  { key: \"vcpu\", label: \"vCPUs\", format: d3.format(\"d\") },\n  { key: \"memory\", label: \"Memory (GB)\", format: d3.format(\",d\") },\n  { key: \"storage\", label: \"Storage (GB)\", format: d3.format(\",d\") },\n  { key: \"price\", label: \"Price ($/hr)\", format: (d) => `$${d3.format(\".2f\")(d)}` },\n  { key: \"network\", label: \"Network (Gbps)\", format: d3.format(\",d\") },\n];\n\nconst data = [\n  // General Purpose\n  { category: \"General Purpose\", vcpu: 2, memory: 8, storage: 80, price: 0.08, network: 5 },\n  { category: \"General Purpose\", vcpu: 4, memory: 16, storage: 160, price: 0.16, network: 10 },\n  { category: \"General Purpose\", vcpu: 8, memory: 32, storage: 320, price: 0.32, network: 10 },\n  { category: \"General Purpose\", vcpu: 16, memory: 64, storage: 640, price: 0.64, network: 15 },\n  { category: \"General Purpose\", vcpu: 32, memory: 128, storage: 1280, price: 1.28, network: 20 },\n  { category: \"General Purpose\", vcpu: 48, memory: 192, storage: 1920, price: 1.92, network: 25 },\n  // Compute Optimized\n  { category: \"Compute Optimized\", vcpu: 4, memory: 8, storage: 50, price: 0.14, network: 10 },\n  { category: \"Compute Optimized\", vcpu: 8, memory: 16, storage: 100, price: 0.28, network: 15 },\n  { category: \"Compute Optimized\", vcpu: 16, memory: 32, storage: 200, price: 0.56, network: 20 },\n  { category: \"Compute Optimized\", vcpu: 32, memory: 64, storage: 400, price: 1.12, network: 25 },\n  { category: \"Compute Optimized\", vcpu: 48, memory: 96, storage: 600, price: 1.68, network: 30 },\n  { category: \"Compute Optimized\", vcpu: 64, memory: 128, storage: 800, price: 2.24, network: 40 },\n  // Memory Optimized\n  { category: \"Memory Optimized\", vcpu: 2, memory: 16, storage: 100, price: 0.13, network: 10 },\n  { category: \"Memory Optimized\", vcpu: 4, memory: 32, storage: 200, price: 0.26, network: 10 },\n  { category: \"Memory Optimized\", vcpu: 8, memory: 64, storage: 400, price: 0.52, network: 15 },\n  { category: \"Memory Optimized\", vcpu: 16, memory: 128, storage: 800, price: 1.04, network: 20 },\n  { category: \"Memory Optimized\", vcpu: 32, memory: 256, storage: 1600, price: 2.08, network: 25 },\n  { category: \"Memory Optimized\", vcpu: 48, memory: 384, storage: 2400, price: 3.12, network: 30 },\n  // GPU Accelerated\n  { category: \"GPU Accelerated\", vcpu: 8, memory: 32, storage: 200, price: 0.9, network: 25 },\n  { category: \"GPU Accelerated\", vcpu: 16, memory: 64, storage: 400, price: 1.8, network: 50 },\n  { category: \"GPU Accelerated\", vcpu: 32, memory: 128, storage: 800, price: 3.6, network: 100 },\n  { category: \"GPU Accelerated\", vcpu: 64, memory: 256, storage: 1600, price: 7.2, network: 100 },\n  { category: \"GPU Accelerated\", vcpu: 96, memory: 384, storage: 2400, price: 10.8, network: 200 },\n  { category: \"GPU Accelerated\", vcpu: 128, memory: 512, storage: 3200, price: 14.4, network: 200 },\n];\n\nconst categories = [\"General Purpose\", \"Compute Optimized\", \"Memory Optimized\", \"GPU Accelerated\"];\n\n// GPU Accelerated is the natural focal outlier (it dominates every axis), so it\n// gets a bolder, more opaque stroke; the other three families overlap heavily in\n// the low-value band and are dimmed/thinned to stay traceable without a hairball.\nconst FOCAL_CATEGORY = \"GPU Accelerated\";\nconst TITLE_FONT = \"Georgia, 'Times New Roman', serif\";\n\n// SVG mount\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// Scales — one shared x position per dimension, one independent y scale per dimension\nconst xScale = d3.scalePoint().domain(dimensions.map((d) => d.key)).range([0, iw]);\nconst yScales = Object.fromEntries(\n  dimensions.map((d) => [\n    d.key,\n    d3.scaleLinear().domain(d3.extent(data, (row) => row[d.key])).nice().range([ih, 0]),\n  ]),\n);\nconst colorScale = d3.scaleOrdinal().domain(categories).range(t.palette.slice(0, categories.length));\n\n// Data lines — one path per observation, transparent enough to read overlaps\nconst lineGen = d3.line();\ng.selectAll(\".instance-line\")\n  .data(data)\n  .join(\"path\")\n  .attr(\"class\", \"instance-line\")\n  .attr(\"d\", (row) => lineGen(dimensions.map((d) => [xScale(d.key), yScales[d.key](row[d.key])])))\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", (row) => colorScale(row.category))\n  .attr(\"stroke-width\", (row) => (row.category === FOCAL_CATEGORY ? 3.25 : 2))\n  .attr(\"stroke-opacity\", (row) => (row.category === FOCAL_CATEGORY ? 0.85 : 0.48));\n\n// Axes — one vertical d3.axisLeft per dimension, plus the dimension label above it\ndimensions.forEach((dim) => {\n  const axisG = g.append(\"g\")\n    .attr(\"transform\", `translate(${xScale(dim.key)},0)`)\n    .call(d3.axisLeft(yScales[dim.key]).ticks(5).tickFormat(dim.format));\n  axisG.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"13px\");\n  axisG.selectAll(\"line\").attr(\"stroke\", t.grid);\n  axisG.select(\".domain\").attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1.5);\n\n  g.append(\"text\")\n    .attr(\"x\", xScale(dim.key)).attr(\"y\", -22)\n    .attr(\"text-anchor\", \"middle\")\n    .attr(\"fill\", t.ink).style(\"font-size\", \"16px\").style(\"font-weight\", \"600\")\n    .text(dim.label);\n});\n\n// Legend — instance family swatches in the right margin\nconst legend = svg.append(\"g\").attr(\"transform\", `translate(${margin.left + iw + 44},${margin.top + 12})`);\nlegend.append(\"text\")\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"13px\").style(\"font-weight\", \"600\")\n  .text(\"Instance Family\");\ncategories.forEach((cat, i) => {\n  const row = legend.append(\"g\").attr(\"transform\", `translate(0,${30 + i * 32})`);\n  row.append(\"rect\")\n    .attr(\"width\", 16).attr(\"height\", 16).attr(\"rx\", 3)\n    .attr(\"fill\", colorScale(cat));\n  row.append(\"text\")\n    .attr(\"x\", 24).attr(\"y\", 13)\n    .attr(\"fill\", t.ink).style(\"font-size\", \"14px\")\n    .style(\"font-weight\", cat === FOCAL_CATEGORY ? \"700\" : \"400\")\n    .text(cat);\n});\n\n// Title\nconst title = \"Cloud Instance Specs · parallel-basic · javascript · d3 · anyplot.ai\";\nconst titleFontSize = Math.max(15, Math.round(24 * 67 / title.length));\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 56)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-family\", TITLE_FONT)\n  .style(\"font-size\", `${titleFontSize}px`)\n  .style(\"font-weight\", \"600\")\n  .text(title);\n"}