{"spec_id":"point-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// point-basic: Point Estimate Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-05\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Estimated effect of study interventions on exam score, points gained/lost\n// vs. a no-intervention baseline, with 95% confidence intervals. Sorted\n// descending by estimate so the chart reads top-to-bottom from strongest to\n// weakest effect.\nconst rows = [\n  { category: \"Tutoring\", estimate: 6.8, marginOfError: 1.4 },\n  { category: \"Study Group\", estimate: 4.1, marginOfError: 1.9 },\n  { category: \"Practice Tests\", estimate: 5.6, marginOfError: 1.1 },\n  { category: \"Flashcards\", estimate: 2.3, marginOfError: 1.6 },\n  { category: \"Online Course\", estimate: 3.4, marginOfError: 1.3 },\n  { category: \"Peer Review\", estimate: 1.2, marginOfError: 1.8 },\n  { category: \"Sleep Coaching\", estimate: -0.6, marginOfError: 1.5 },\n].sort((a, b) => b.estimate - a.estimate);\n\nconst categories = rows.map((r) => r.category);\nconst estimate = rows.map((r) => r.estimate);\nconst lowerBound = rows.map((r) => r.estimate - r.marginOfError);\nconst upperBound = rows.map((r) => r.estimate + r.marginOfError);\n// An interval crossing zero is not statistically significant — render it in a\n// muted tone (same hue, lower opacity) so the eye separates the two groups\n// without leaning on the reference line alone.\nconst isSignificant = rows.map((_, i) => lowerBound[i] > 0 || upperBound[i] < 0);\n\n// Highcharts core has no errorbar/columnrange series (those live in the\n// unloaded highcharts-more module) — draw the CI whiskers natively with the\n// SVGRenderer once the axes are laid out, keyed off the same pixel space the\n// scatter points use.\nconst intervalColor = Highcharts.color(t.palette[0]).setOpacity(0.55).get();\nconst intervalColorMuted = Highcharts.color(t.palette[0])\n  .setOpacity(0.3)\n  .get();\nconst markerColorMuted = Highcharts.color(t.palette[0]).setOpacity(0.5).get();\nconst capHalfPx = 9;\n\n// --- Chart -------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    inverted: true,\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    events: {\n      load() {\n        const chart = this;\n        const xAxis = chart.xAxis[0];\n        const yAxis = chart.yAxis[0];\n        categories.forEach((_, i) => {\n          const rowPixel = xAxis.toPixels(i);\n          const lowPixel = yAxis.toPixels(lowerBound[i]);\n          const highPixel = yAxis.toPixels(upperBound[i]);\n          const stroke = isSignificant[i] ? intervalColor : intervalColorMuted;\n          chart.renderer\n            .path([\"M\", lowPixel, rowPixel, \"L\", highPixel, rowPixel])\n            .attr({ \"stroke-width\": 3, stroke, zIndex: 4 })\n            .add();\n          [lowPixel, highPixel].forEach((xPixel) => {\n            chart.renderer\n              .path([\n                \"M\",\n                xPixel,\n                rowPixel - capHalfPx,\n                \"L\",\n                xPixel,\n                rowPixel + capHalfPx,\n              ])\n              .attr({ \"stroke-width\": 3, stroke, zIndex: 4 })\n              .add();\n          });\n        });\n      },\n    },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"point-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    categories,\n    minPadding: 0.08,\n    maxPadding: 0.08,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    title: {\n      text: \"Effect on Exam Score (points)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    minPadding: 0.06,\n    maxPadding: 0.06,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    plotLines: [\n      {\n        value: 0,\n        color: t.inkSoft,\n        width: 1.5,\n        dashStyle: \"ShortDash\",\n        zIndex: 3,\n      },\n    ],\n  },\n  legend: { enabled: false },\n  tooltip: {\n    formatter() {\n      const i = this.point.x;\n      return (\n        `<b>${categories[i]}</b><br/>` +\n        `Estimate: ${estimate[i].toFixed(1)}<br/>` +\n        `95% CI: [${lowerBound[i].toFixed(1)}, ${upperBound[i].toFixed(1)}]`\n      );\n    },\n  },\n  plotOptions: {\n    series: { animation: false },\n    scatter: {\n      marker: { radius: 8, symbol: \"circle\", lineWidth: 0 },\n    },\n  },\n  series: [\n    {\n      name: \"Estimate\",\n      data: estimate.map((v, i) => ({\n        x: i,\n        y: v,\n        marker: isSignificant[i] ? undefined : { fillColor: markerColorMuted },\n      })),\n      zIndex: 5,\n    },\n    {\n      // Invisible — only extends the value axis to cover the CI whiskers,\n      // which are custom-drawn (see chart.events.load) and outside any\n      // series Highcharts would otherwise use for axis-extent calculation.\n      name: \"CI bounds\",\n      data: [...lowerBound, ...upperBound].map((v, j) => [\n        j % categories.length,\n        v,\n      ]),\n      marker: { enabled: false },\n      lineWidth: 0,\n      enableMouseTracking: false,\n      showInLegend: false,\n    },\n  ],\n});\n"}