{"spec_id":"streamline-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// streamline-basic: Basic Streamline Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-09-09\n\n//# anyplot-orientation: landscape\nconst THEME = window.ANYPLOT_THEME || \"light\";\nconst t = window.ANYPLOT_TOKENS;\nconst INK_MUTED = THEME === \"light\" ? \"#6B6A63\" : \"#A8A79F\";\n\n// --- Vector field: potential flow past a circular cylinder -----------------\n// Uniform stream (speed U) plus a doublet of strength that puts the\n// stagnation points on a cylinder of radius R centered at the origin.\nconst U = 1;\nconst R = 1;\n\nfunction velocity(x, y) {\n  const r2 = x * x + y * y;\n  const r4 = r2 * r2;\n  const u = U * (1 - (R * R * (x * x - y * y)) / r4);\n  const v = U * (-2 * R * R * x * y) / r4;\n  return [u, v];\n}\n\nfunction rk4Step(x, y, dt) {\n  const [k1u, k1v] = velocity(x, y);\n  const [k2u, k2v] = velocity(x + (dt / 2) * k1u, y + (dt / 2) * k1v);\n  const [k3u, k3v] = velocity(x + (dt / 2) * k2u, y + (dt / 2) * k2v);\n  const [k4u, k4v] = velocity(x + dt * k3u, y + dt * k3v);\n  const dx = (dt / 6) * (k1u + 2 * k2u + 2 * k3u + k4u);\n  const dy = (dt / 6) * (k1v + 2 * k2v + 2 * k3v + k4v);\n  const speed = Math.hypot(k1u, k1v);\n  return { x: x + dx, y: y + dy, speed };\n}\n\n// --- Trace one streamline downstream from a seed point ----------------------\nconst X_MIN = -3.2,\n  X_MAX = 3.2,\n  Y_LIMIT = 1.8;\nconst DT = 0.04;\nconst MAX_STEPS = 400;\n\nfunction traceStreamline(y0) {\n  let x = X_MIN;\n  let y = y0;\n  const points = [{ x, y }];\n  const speeds = [];\n  for (let step = 0; step < MAX_STEPS; step += 1) {\n    const next = rk4Step(x, y, DT);\n    speeds.push(next.speed);\n    x = next.x;\n    y = next.y;\n    points.push({ x, y });\n    if (x > X_MAX || Math.abs(y) > Y_LIMIT) break;\n  }\n  const avgSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length;\n  return { points, avgSpeed };\n}\n\n// Seed heights spanning the domain, skipping y=0 (the stagnation streamline\n// that runs into the cylinder surface and never reaches the far side).\nconst seeds = [-1.7, -1.5, -1.3, -1.1, -0.9, -0.7, -0.5, -0.3, -0.1, 0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3, 1.5, 1.7];\nconst streamlines = seeds.map(traceStreamline);\n\nconst minSpeed = Math.min(...streamlines.map((s) => s.avgSpeed));\nconst maxSpeed = Math.max(...streamlines.map((s) => s.avgSpeed));\n\nfunction hexToRgb(hex) {\n  const n = parseInt(hex.slice(1), 16);\n  return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };\n}\nconst seqLow = hexToRgb(t.seq[0]);\nconst seqHigh = hexToRgb(t.seq[1]);\nfunction speedColor(avgSpeed) {\n  const ratio = maxSpeed > minSpeed ? (avgSpeed - minSpeed) / (maxSpeed - minSpeed) : 0;\n  const r = Math.round(seqLow.r + (seqHigh.r - seqLow.r) * ratio);\n  const g = Math.round(seqLow.g + (seqHigh.g - seqLow.g) * ratio);\n  const b = Math.round(seqLow.b + (seqHigh.b - seqLow.b) * ratio);\n  return { color: `rgb(${r}, ${g}, ${b})`, ratio };\n}\n\n// --- Cylinder outline (drawn first, so streamlines render on top) ----------\nconst cylinderPoints = Array.from({ length: 65 }, (_, i) => {\n  const angle = (i / 64) * 2 * Math.PI;\n  return { x: R * Math.cos(angle), y: R * Math.sin(angle) };\n});\n\nconst datasets = [\n  {\n    label: \"Cylinder\",\n    data: cylinderPoints,\n    borderColor: INK_MUTED,\n    backgroundColor: THEME === \"light\" ? \"rgba(26, 26, 23, 0.12)\" : \"rgba(240, 239, 232, 0.12)\",\n    borderWidth: 2,\n    pointRadius: 0,\n    fill: true,\n    tension: 0,\n    order: 0,\n  },\n];\n\nstreamlines.forEach(({ points, avgSpeed }) => {\n  const { color, ratio } = speedColor(avgSpeed);\n  datasets.push({\n    label: \"Streamline\",\n    data: points,\n    borderColor: color,\n    borderWidth: 2 + 1.5 * ratio,\n    pointRadius: 0,\n    fill: false,\n    tension: 0,\n    order: 1,\n  });\n});\n\n// --- Mount -------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ---------------------------------------------------------------\nconst title = \"Flow Around a Cylinder · streamline-basic · javascript · chartjs · anyplot.ai\";\nconst titleFontSize = Math.max(14, Math.round(22 * (title.length > 67 ? 67 / title.length : 1)));\n\nnew Chart(canvas, {\n  type: \"line\",\n  data: { datasets },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: { display: true, text: title, color: t.ink, font: { size: titleFontSize } },\n      legend: { display: false },\n      tooltip: { enabled: false },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: X_MIN,\n        max: X_MAX,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"x\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        type: \"linear\",\n        min: -Y_LIMIT,\n        max: Y_LIMIT,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"y\", color: t.ink, font: { size: 16 } },\n      },\n    },\n  },\n});\n"}