{"spec_id":"streamline-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// streamline-basic: Basic Streamline Plot\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-09\n\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Vector field -------------------------------------------------------\n// Taylor-Green cellular flow, a divergence-free field with stream function\n// psi = sin(x) sin(y): u = sin(x)cos(y), v = -cos(x)sin(y). Streamlines are\n// closed loops nested around four alternating vortex cells over [-pi, pi]^2.\nconst PI = Math.PI;\n// Axis bounds are a clean round number (not the raw PI+margin), so ECharts'\n// boundary tick labels don't render at full float precision.\nconst AXIS_LIMIT = 3.2;\n\nconst velocity = (x, y) => [Math.sin(x) * Math.cos(y), -Math.cos(x) * Math.sin(y)];\n\nconst speedAt = (x, y) => {\n  const [u, v] = velocity(x, y);\n  return Math.sqrt(u * u + v * v);\n};\n\n// --- Integrate one streamline via RK4 from a seed point ------------------\nconst traceStreamline = (x0, y0, steps, dt) => {\n  let x = x0;\n  let y = y0;\n  const path = [[x, y]];\n  for (let i = 0; i < steps; i += 1) {\n    const [k1u, k1v] = velocity(x, y);\n    const [k2u, k2v] = velocity(x + (k1u * dt) / 2, y + (k1v * dt) / 2);\n    const [k3u, k3v] = velocity(x + (k2u * dt) / 2, y + (k2v * dt) / 2);\n    const [k4u, k4v] = velocity(x + k3u * dt, y + k3v * dt);\n    x += (dt / 6) * (k1u + 2 * k2u + 2 * k3u + k4u);\n    y += (dt / 6) * (k1v + 2 * k2v + 2 * k3v + k4v);\n    if (Math.abs(x) > PI || Math.abs(y) > PI) break;\n    path.push([x, y]);\n  }\n  return path;\n};\n\n// Seeds: one radial family per vortex cell, offset along x from each cell's\n// elliptic center (both the center and the cell boundary are stagnation\n// points, so mid-radius offsets trace the cleanest closed loops).\nconst cellCenters = [\n  [PI / 2, PI / 2],\n  [-PI / 2, PI / 2],\n  [-PI / 2, -PI / 2],\n  [PI / 2, -PI / 2],\n];\n// A near-boundary radius is included so seeds sweep from each cell's elliptic\n// center out toward the separatrix, showing more of the field topology.\nconst radii = [0.3, 0.6, 0.9, 1.2, 1.45];\nconst seeds = [];\ncellCenters.forEach(([cx, cy]) => {\n  radii.forEach((r) => {\n    seeds.push([cx + r, cy]);\n  });\n});\n\n// --- Color + width by mean speed along each streamline (imprint_seq) -----\nconst hexToRgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));\nconst seqLo = hexToRgb(t.seq[0]);\nconst seqHi = hexToRgb(t.seq[1]);\nconst lerp = (a, b, f) => a + (b - a) * f;\n\n// --- Directional arrowhead: a short segment at each path's midpoint, oriented\n// along the local flow direction, so circulation direction is visible per cell.\nconst arrowAt = (path) => {\n  const midIdx = Math.floor(path.length / 2);\n  const aheadIdx = Math.min(midIdx + 5, path.length - 1);\n  const [mx, my] = path[midIdx];\n  const [ax, ay] = path[aheadIdx];\n  const dx = ax - mx;\n  const dy = ay - my;\n  const dist = Math.hypot(dx, dy) || 1;\n  const ux = dx / dist;\n  const uy = dy / dist;\n  const halfLen = 0.11;\n  return [\n    [mx - ux * halfLen, my - uy * halfLen],\n    [mx + ux * halfLen, my + uy * halfLen],\n  ];\n};\n\nconst streamlines = [];\nconst arrows = [];\nseeds.forEach(([sx, sy]) => {\n  const path = traceStreamline(sx, sy, 650, 0.025);\n  const meanSpeed = path.reduce((sum, [px, py]) => sum + speedAt(px, py), 0) / path.length;\n  const f = Math.max(0, Math.min(1, meanSpeed));\n  const rgb = [0, 1, 2].map((i) => Math.round(lerp(seqLo[i], seqHi[i], f)));\n  const color = `rgb(${rgb.join(\",\")})`;\n  const width = lerp(1.6, 4, f);\n  streamlines.push({ coords: path, lineStyle: { color, width } });\n  arrows.push({ coords: arrowAt(path), lineStyle: { color, width } });\n});\n\n// --- Init ------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option ------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"streamline-basic · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 24,\n    textStyle: { color: t.ink, fontSize: 22 },\n  },\n  grid: { left: 90, right: 60, top: 110, bottom: 80 },\n  xAxis: {\n    type: \"value\",\n    min: -AXIS_LIMIT,\n    max: AXIS_LIMIT,\n    name: \"x\",\n    nameLocation: \"middle\",\n    nameGap: 40,\n    nameTextStyle: { color: t.inkSoft, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"value\",\n    min: -AXIS_LIMIT,\n    max: AXIS_LIMIT,\n    name: \"y\",\n    nameLocation: \"middle\",\n    nameGap: 50,\n    nameTextStyle: { color: t.inkSoft, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      type: \"lines\",\n      coordinateSystem: \"cartesian2d\",\n      polyline: true,\n      symbol: [\"none\", \"none\"],\n      data: streamlines,\n    },\n    {\n      type: \"lines\",\n      coordinateSystem: \"cartesian2d\",\n      symbol: [\"none\", \"arrow\"],\n      symbolSize: 14,\n      data: arrows,\n    },\n  ],\n});\n"}