{"spec_id":"phase-diagram","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// phase-diagram: Phase Diagram (State Space Plot)\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-09-05\n\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: damped pendulum phase portrait -----------------------------------\n// d(theta)/dt = omega\n// d(omega)/dt = -2*zeta*omega0*omega - omega0^2*sin(theta)\n// RK4 integration, fixed dt, fully deterministic. Each trajectory uses its own\n// damping ratio to reveal contrasting qualitative behavior (slow lingering\n// spiral vs quick collapse) rather than three copies of the same regime.\nconst omega0 = 1.5;\nconst dt = 0.02;\n\nfunction derivatives(theta, omega, zeta) {\n  return [omega, -2 * zeta * omega0 * omega - omega0 * omega0 * Math.sin(theta)];\n}\n\n// Run each trajectory for ~5 damping time-constants so lightly-damped\n// (slow-converging) and heavily-damped (fast-converging) cases both settle\n// near the equilibrium visually, instead of using one fixed step count that\n// under-runs slow trajectories and over-runs fast ones. Clamped to the\n// spec's 200-2000 point guidance.\nfunction stepsFor(zeta) {\n  const timeConstant = 1 / (zeta * omega0);\n  return Math.min(2000, Math.max(200, Math.round((5 * timeConstant) / dt)));\n}\n\nfunction integrate(theta0, omegaState0, zeta) {\n  let theta = theta0;\n  let omega = omegaState0;\n  const points = [{ x: theta, y: omega }];\n  const steps = stepsFor(zeta);\n  for (let i = 0; i < steps; i++) {\n    const [k1t, k1o] = derivatives(theta, omega, zeta);\n    const [k2t, k2o] = derivatives(theta + (dt / 2) * k1t, omega + (dt / 2) * k1o, zeta);\n    const [k3t, k3o] = derivatives(theta + (dt / 2) * k2t, omega + (dt / 2) * k2o, zeta);\n    const [k4t, k4o] = derivatives(theta + dt * k3t, omega + dt * k3o, zeta);\n    theta += (dt / 6) * (k1t + 2 * k2t + 2 * k3t + k4t);\n    omega += (dt / 6) * (k1o + 2 * k2o + 2 * k3o + k4o);\n    points.push({ x: theta, y: omega });\n  }\n  return points;\n}\n\nconst initialConditions = [\n  { theta0: 2.6, omega0State: 0.0, zeta: 0.15, label: \"θ₀ = 2.6 rad, ω₀ = 0, ζ = 0.15\" },\n  { theta0: -2.4, omega0State: 1.6, zeta: 0.05, label: \"θ₀ = -2.4 rad, ω₀ = 1.6, ζ = 0.05\" },\n  { theta0: 1.0, omega0State: -2.0, zeta: 0.4, label: \"θ₀ = 1.0 rad, ω₀ = -2.0, ζ = 0.4\" },\n];\n\nconst trajectories = initialConditions.map((ic, i) => ({\n  color: t.palette[i],\n  label: ic.label,\n  points: integrate(ic.theta0, ic.omega0State, ic.zeta),\n}));\n\n// Convert a hex color to rgba() with a given alpha, so each trajectory's\n// segments can fade in from faint (start) to fully opaque (equilibrium) as a\n// time-direction cue, without changing hue between light/dark themes.\nfunction withAlpha(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\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Datasets ------------------------------------------------------------\nconst trajectoryDatasets = trajectories.map((traj) => ({\n  label: traj.label,\n  data: traj.points,\n  showLine: true,\n  borderColor: traj.color,\n  pointStyle: \"line\",\n  borderWidth: 3,\n  pointRadius: 0,\n  tension: 0,\n  segment: {\n    // Fade each segment in from 25% to 100% opacity along the trajectory so\n    // the flow direction toward the equilibrium is visible at a glance.\n    borderColor: (ctx) => {\n      const progress = ctx.p0DataIndex / (traj.points.length - 1);\n      return withAlpha(traj.color, 0.25 + 0.75 * progress);\n    },\n  },\n}));\n\nconst startDatasets = trajectories.map((traj) => ({\n  label: \"\",\n  data: [traj.points[0]],\n  showLine: false,\n  pointStyle: \"circle\",\n  pointRadius: 8,\n  backgroundColor: traj.color,\n  borderColor: t.pageBg,\n  borderWidth: 2,\n}));\n\nconst equilibriumDataset = {\n  label: \"Equilibrium (θ=0, ω=0)\",\n  data: [{ x: 0, y: 0 }],\n  showLine: false,\n  pointStyle: \"crossRot\",\n  pointRadius: 12,\n  borderWidth: 3,\n  backgroundColor: t.ink,\n  borderColor: t.ink,\n};\n\n// --- Chart -----------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: {\n    datasets: [...trajectoryDatasets, ...startDatasets, equilibriumDataset],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"phase-diagram · 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          usePointStyle: true,\n          filter: (item) => item.text !== \"\",\n        },\n      },\n    },\n    scales: {\n      x: {\n        min: -3.2,\n        max: 3.2,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Position θ (rad)\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        min: -3.2,\n        max: 3.2,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Angular velocity dθ/dt (rad/s)\", color: t.ink, font: { size: 16 } },\n      },\n    },\n  },\n});\n"}