{"spec_id":"parallel-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// parallel-basic: Basic Parallel Coordinates Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.1\n// Quality: 90/100 | Created: 2026-07-24\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic, iris-inspired) ------------------------\n// Tiny fixed-seed LCG + Box-Muller so the sample is reproducible without\n// Math.random (the browser has no seeded RNG).\nlet lcgSeed = 42;\nfunction lcgRand() {\n  lcgSeed = (lcgSeed * 1103515245 + 12345) & 0x7fffffff;\n  return lcgSeed / 0x7fffffff;\n}\nfunction randNormal() {\n  const u1 = Math.max(lcgRand(), 1e-9);\n  const u2 = lcgRand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst dimensions = [\"Sepal Length\", \"Sepal Width\", \"Petal Length\", \"Petal Width\"];\n\n// Approximate per-species mean/sd for each dimension (cm), iris-inspired.\nconst speciesStats = [\n  { name: \"Setosa\", stats: [[5.0, 0.35], [3.42, 0.38], [1.46, 0.17], [0.24, 0.11]] },\n  { name: \"Versicolor\", stats: [[5.94, 0.52], [2.77, 0.31], [4.26, 0.47], [1.33, 0.2]] },\n  { name: \"Virginica\", stats: [[6.59, 0.64], [2.97, 0.32], [5.55, 0.55], [2.03, 0.27]] },\n];\nconst OBS_PER_SPECIES = 20;\n\nconst observations = [];\nspeciesStats.forEach((species, speciesIndex) => {\n  for (let i = 0; i < OBS_PER_SPECIES; i++) {\n    const raw = species.stats.map(([mean, sd]) => mean + sd * randNormal());\n    observations.push({ speciesIndex, raw });\n  }\n});\n\n// Min-max normalize each dimension independently so all axes share one 0-1\n// scale and can be compared side by side, per the spec's normalization note.\nconst mins = dimensions.map((_, d) => Math.min(...observations.map((o) => o.raw[d])));\nconst maxs = dimensions.map((_, d) => Math.max(...observations.map((o) => o.raw[d])));\nobservations.forEach((o) => {\n  o.normalized = o.raw.map((v, d) => (v - mins[d]) / (maxs[d] - mins[d]));\n});\n\nfunction hexToRgba(hex, alpha) {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nconst speciesColors = speciesStats.map((_, i) => t.palette[i % t.palette.length]);\n\n// Per-species mean profile (one point per dimension), drawn as a bolder line\n// on top of the faint individual traces so each species has a clear focal\n// line to anchor the eye, especially where Versicolor/Virginica overlap.\nconst meanProfiles = speciesStats.map((_, speciesIndex) => {\n  const speciesObs = observations.filter((o) => o.speciesIndex === speciesIndex);\n  return dimensions.map(\n    (_, d) => speciesObs.reduce((sum, o) => sum + o.normalized[d], 0) / speciesObs.length\n  );\n});\n\n// --- Mount -------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ---------------------------------------------------------------\n// One line dataset per observation (Chart.js has no native parallel-coords\n// type); category x-axis ticks stand in for the per-dimension axes, and a\n// shared normalized y-axis keeps every dimension comparable. A bold mean\n// line per species is appended last so it draws on top of the faint\n// individual traces (Chart.js draws line datasets in array order).\nconst individualDatasets = observations.map((o) => ({\n  data: o.normalized,\n  borderColor: hexToRgba(speciesColors[o.speciesIndex], 0.35),\n  borderWidth: 1.25,\n  pointRadius: 0,\n  pointHoverRadius: 0,\n  tension: 0,\n  fill: false,\n}));\nconst meanDatasets = meanProfiles.map((profile, speciesIndex) => ({\n  data: profile,\n  borderColor: speciesColors[speciesIndex],\n  borderWidth: 4,\n  pointRadius: 0,\n  pointHoverRadius: 0,\n  tension: 0,\n  fill: false,\n}));\n\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    labels: dimensions,\n    datasets: [...individualDatasets, ...meanDatasets],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"parallel-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n      },\n      legend: {\n        labels: {\n          color: t.ink,\n          font: { size: 16 },\n          boxWidth: 24,\n          generateLabels: () =>\n            speciesStats.map((species, i) => ({\n              text: species.name,\n              fillStyle: speciesColors[i],\n              strokeStyle: speciesColors[i],\n              lineWidth: 2,\n              datasetIndex: individualDatasets.length + i,\n            })),\n        },\n        onClick: () => {},\n      },\n      tooltip: { enabled: false },\n    },\n    scales: {\n      x: {\n        type: \"category\",\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.ink, lineWidth: 1.5, tickLength: 0 },\n      },\n      y: {\n        min: 0,\n        max: 1,\n        ticks: { color: t.inkSoft, font: { size: 14 }, stepSize: 0.25 },\n        grid: { display: false },\n        title: {\n          display: true,\n          text: \"Normalized Value (min–max scaled per dimension)\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n      },\n    },\n  },\n});\n"}