|
| 1 | +// anyplot.ai |
| 2 | +// gauge-basic: Basic Gauge Chart |
| 3 | +// Library: highcharts 12.6.0 | JavaScript 22.23.0 |
| 4 | +// Quality: 91/100 | Created: 2026-06-30 |
| 5 | +//# anyplot-orientation: square |
| 6 | +// anyplot.ai |
| 7 | +// gauge-basic: Basic Gauge Chart |
| 8 | +// Library: Highcharts 12.6.0 | Node 22 |
| 9 | +// License: Highcharts — commercial license, free for non-commercial use (highcharts.com/license) |
| 10 | +// Quality: pending | Created: 2026-06-30 |
| 11 | + |
| 12 | +const t = window.ANYPLOT_TOKENS; |
| 13 | + |
| 14 | +// --- Data (Customer Satisfaction Score) ------------------------------------ |
| 15 | +const MIN_VAL = 0; |
| 16 | +const MAX_VAL = 100; |
| 17 | +const CUR_VAL = 72; |
| 18 | +const THRESH_LO = 30; |
| 19 | +const THRESH_HI = 70; |
| 20 | + |
| 21 | +const arcRed = THRESH_LO - MIN_VAL; // 30 |
| 22 | +const arcAmber = THRESH_HI - THRESH_LO; // 40 |
| 23 | +const arcGreen = MAX_VAL - THRESH_HI; // 30 |
| 24 | + |
| 25 | +const TITLE = "gauge-basic · javascript · highcharts · anyplot.ai"; |
| 26 | +const titleFs = Math.max(15, Math.round(22 * Math.min(1, 67 / TITLE.length))) + "px"; |
| 27 | + |
| 28 | +// --- Chart ----------------------------------------------------------------- |
| 29 | +const chart = Highcharts.chart("container", { |
| 30 | + chart: { |
| 31 | + backgroundColor: "transparent", |
| 32 | + animation: false, |
| 33 | + style: { fontFamily: "inherit" }, |
| 34 | + margin: [60, 40, 140, 40] |
| 35 | + }, |
| 36 | + credits: { enabled: false }, |
| 37 | + colors: t.palette, |
| 38 | + title: { |
| 39 | + text: TITLE, |
| 40 | + style: { color: t.ink, fontSize: titleFs, fontWeight: "600" } |
| 41 | + }, |
| 42 | + plotOptions: { |
| 43 | + pie: { |
| 44 | + animation: false, |
| 45 | + startAngle: -90, |
| 46 | + endAngle: 90, |
| 47 | + center: ["50%", "70%"], |
| 48 | + dataLabels: { enabled: false }, |
| 49 | + borderWidth: 0 |
| 50 | + }, |
| 51 | + series: { animation: false } |
| 52 | + }, |
| 53 | + tooltip: { enabled: false }, |
| 54 | + legend: { enabled: false }, |
| 55 | + series: [{ |
| 56 | + type: "pie", |
| 57 | + name: "CSAT Zones", |
| 58 | + innerSize: "62%", |
| 59 | + size: "90%", |
| 60 | + data: [ |
| 61 | + { y: arcRed, color: "#AE3030" }, |
| 62 | + { y: arcAmber, color: "#DDCC77" }, |
| 63 | + { y: arcGreen, color: "#009E73" } |
| 64 | + ] |
| 65 | + }] |
| 66 | +}); |
| 67 | + |
| 68 | +// --- SVG overlays (needle, value, legend) ---------------------------------- |
| 69 | +const ps = chart.series[0]; |
| 70 | +const cx = chart.plotLeft + ps.center[0]; |
| 71 | +const cy = chart.plotTop + ps.center[1]; |
| 72 | +const outerR = ps.center[2] / 2; |
| 73 | +const innerR = ps.center[3] / 2; |
| 74 | + |
| 75 | +// Needle angle: -90° (left/MIN) to +90° (right/MAX), clockwise from 12 o'clock |
| 76 | +const pct = (CUR_VAL - MIN_VAL) / (MAX_VAL - MIN_VAL); |
| 77 | +const angleDeg = -90 + pct * 180; |
| 78 | +const angleRad = angleDeg * Math.PI / 180; |
| 79 | + |
| 80 | +// SVG coords: x = cx + r·sin(θ), y = cy - r·cos(θ) |
| 81 | +const needleLen = outerR * 0.82; |
| 82 | +const tipX = cx + needleLen * Math.sin(angleRad); |
| 83 | +const tipY = cy - needleLen * Math.cos(angleRad); |
| 84 | + |
| 85 | +// Needle line |
| 86 | +chart.renderer.path(["M", cx, cy, "L", tipX, tipY]) |
| 87 | + .attr({ stroke: t.ink, "stroke-width": 5, "stroke-linecap": "round", zIndex: 5 }) |
| 88 | + .add(); |
| 89 | + |
| 90 | +// Hub dot at pivot |
| 91 | +chart.renderer.circle(cx, cy, outerR * 0.065) |
| 92 | + .attr({ fill: t.ink, zIndex: 6 }) |
| 93 | + .add(); |
| 94 | + |
| 95 | +// Large value inside donut hole |
| 96 | +const valY = cy - innerR * 0.45; |
| 97 | +chart.renderer.text(CUR_VAL.toString(), cx, valY) |
| 98 | + .attr({ align: "center", zIndex: 5 }) |
| 99 | + .css({ fontSize: "66px", fontWeight: "700", color: t.ink, fontFamily: "inherit" }) |
| 100 | + .add(); |
| 101 | + |
| 102 | +chart.renderer.text("/ 100", cx, valY + 38) |
| 103 | + .attr({ align: "center", zIndex: 5 }) |
| 104 | + .css({ fontSize: "18px", color: t.inkSoft, fontFamily: "inherit" }) |
| 105 | + .add(); |
| 106 | + |
| 107 | +// Metric name below gauge |
| 108 | +chart.renderer.text("Customer Satisfaction Score", cx, cy + 52) |
| 109 | + .attr({ align: "center", zIndex: 5 }) |
| 110 | + .css({ fontSize: "18px", fontWeight: "600", color: t.ink, fontFamily: "inherit" }) |
| 111 | + .add(); |
| 112 | + |
| 113 | +// Zone legend (3 columns) |
| 114 | +const zoneBaseY = cy + 112; |
| 115 | +const colW = chart.chartWidth / 3; |
| 116 | +[ |
| 117 | + { range: "0 – 30", label: "Needs Improvement", color: "#AE3030" }, |
| 118 | + { range: "30 – 70", label: "Satisfactory", color: "#DDCC77" }, |
| 119 | + { range: "70 – 100", label: "Excellent", color: "#009E73" } |
| 120 | +].forEach((z, i) => { |
| 121 | + const zx = colW * i + colW / 2; |
| 122 | + chart.renderer.text(z.range, zx, zoneBaseY) |
| 123 | + .attr({ align: "center", zIndex: 5 }) |
| 124 | + .css({ fontSize: "15px", fontWeight: "700", color: z.color, fontFamily: "inherit" }) |
| 125 | + .add(); |
| 126 | + chart.renderer.text(z.label, zx, zoneBaseY + 24) |
| 127 | + .attr({ align: "center", zIndex: 5 }) |
| 128 | + .css({ fontSize: "14px", color: t.inkSoft, fontFamily: "inherit" }) |
| 129 | + .add(); |
| 130 | +}); |
0 commit comments