{"spec_id":"arc-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// arc-basic: Basic Arc Diagram\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-08-24\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: dialogue exchanges between characters in a novel -----------------\n// Nodes are ordered by first appearance; edges are dialogue exchanges with a\n// weight (exchange count) that drives arc thickness, opacity and color.\nconst nodes = [\n  \"Elena\",\n  \"Marcus\",\n  \"Ines\",\n  \"Devon\",\n  \"Priya\",\n  \"Callum\",\n  \"Sara\",\n  \"Tobias\",\n  \"Naomi\",\n  \"Aiden\",\n  \"Ruth\",\n  \"Felix\",\n  \"Lena\",\n  \"Omar\",\n];\n\nconst edges = [\n  [0, 1, 9],\n  [0, 2, 4],\n  [1, 2, 6],\n  [1, 3, 3],\n  [2, 3, 5],\n  [2, 4, 2],\n  [3, 4, 7],\n  [3, 5, 2],\n  [4, 5, 8],\n  [4, 6, 3],\n  [5, 6, 4],\n  [5, 7, 2],\n  [6, 7, 6],\n  [6, 8, 3],\n  [7, 8, 5],\n  [7, 9, 2],\n  [8, 9, 4],\n  [8, 10, 3],\n  [9, 10, 7],\n  [9, 11, 2],\n  [10, 11, 5],\n  [10, 12, 3],\n  [11, 12, 6],\n  [11, 13, 2],\n  [12, 13, 8],\n  [0, 6, 3],\n  [1, 8, 2],\n  [3, 10, 2],\n  [5, 12, 3],\n  [2, 13, 2],\n];\n\nconst maxWeight = Math.max(...edges.map((e) => e[2]));\nconst maxSpan = Math.max(...edges.map(([a, b]) => Math.abs(a - b)));\n\n// --- Color helpers: continuous imprint_seq gradient by edge weight ----------\nfunction hexToRgb(hex) {\n  const n = parseInt(hex.slice(1), 16);\n  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\nfunction lerpColor(c0, c1, f) {\n  const [r0, g0, b0] = hexToRgb(c0);\n  const [r1, g1, b1] = hexToRgb(c1);\n  const r = Math.round(r0 + (r1 - r0) * f);\n  const g = Math.round(g0 + (g1 - g0) * f);\n  const b = Math.round(b0 + (b1 - b0) * f);\n  return `rgb(${r}, ${g}, ${b})`;\n}\nfunction seqColor(f) {\n  return lerpColor(t.seq[0], t.seq[1], f);\n}\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Custom plugin: draws the arcs between nodes -----------------------------\nconst arcDiagramPlugin = {\n  id: \"arcDiagram\",\n  afterDatasetsDraw(chart) {\n    const { ctx, chartArea, scales } = chart;\n    const xScale = scales.x;\n    const yScale = scales.y;\n    const baselineY = yScale.getPixelForValue(0);\n    const maxAvailable = baselineY - chartArea.top - 24;\n\n    ctx.save();\n    edges.forEach(([a, b, weight]) => {\n      const span = Math.abs(a - b);\n      const x1 = xScale.getPixelForValue(a);\n      const x2 = xScale.getPixelForValue(b);\n      const xMid = (x1 + x2) / 2;\n      const height = (span / maxSpan) * maxAvailable;\n      // Quadratic Bezier apex sits halfway to the control point, so the\n      // control point must overshoot the desired visual peak by 2x.\n      const yPeak = baselineY - height * 2;\n      const norm = weight / maxWeight;\n\n      ctx.beginPath();\n      ctx.moveTo(x1, baselineY);\n      ctx.quadraticCurveTo(xMid, yPeak, x2, baselineY);\n      ctx.lineWidth = 2.0 + norm * 4.0;\n      ctx.strokeStyle = seqColor(norm);\n      ctx.globalAlpha = 0.45 + norm * 0.3;\n      ctx.stroke();\n    });\n    ctx.restore();\n  },\n};\n\n// --- Chart ---------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: {\n    datasets: [\n      {\n        label: \"Characters\",\n        data: nodes.map((_, i) => ({ x: i, y: 0 })),\n        backgroundColor: t.palette[0],\n        borderColor: t.ink,\n        borderWidth: 2,\n        pointRadius: 10,\n        pointHoverRadius: 10,\n        order: 0,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: {\n      padding: { top: 40, bottom: 10, left: 20, right: 20 },\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: \"arc-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n        padding: { bottom: 24 },\n      },\n      legend: { display: false },\n      tooltip: { enabled: false },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: -0.6,\n        max: nodes.length - 0.4,\n        offset: false,\n        grid: { display: false },\n        border: { display: false },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          stepSize: 1,\n          autoSkip: false,\n          maxRotation: 45,\n          minRotation: 45,\n          callback: (value) => nodes[value] ?? \"\",\n        },\n      },\n      y: {\n        type: \"linear\",\n        min: -0.08,\n        max: 1,\n        display: false,\n      },\n    },\n  },\n  plugins: [arcDiagramPlugin],\n});\n"}