{"spec_id":"bullet-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// bullet-basic: Basic Bullet Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-08-24\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\nconst isDark = window.ANYPLOT_THEME === \"dark\";\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Quarterly KPI dashboard — every metric normalised to \"% of target achieved\"\n// so the four bullets share one common scale, as recommended for comparison.\nconst labels = [\"Revenue Growth\", \"Customer Retention\", \"NPS Score\", \"On-Time Delivery\"];\nconst actual = [82, 91, 68, 95];\nconst target = [90, 85, 75, 90];\nconst ranges = [50, 75, 100]; // poor / satisfactory / good thresholds, shared by all rows\n\nconst bandPoor = labels.map(() => ranges[0]);\nconst bandSatisfactory = labels.map(() => ranges[1] - ranges[0]);\nconst bandGood = labels.map(() => ranges[2] - ranges[1]);\nconst targetPoints = target.map((value, index) => [value, index]);\n\n// Band opacity needs a higher floor in dark theme, or the lightest (\"Good\")\n// band nearly disappears against the near-black page background.\nconst bandOpacity = isDark ? [0.3, 0.2, 0.14] : [0.22, 0.13, 0.06];\n\n// Callout: highlight the metric with the widest actual-vs-target gap.\nconst gaps = actual.map((value, index) => value - target[index]);\nconst widestGapIndex = gaps.reduce(\n  (best, gap, index) => (Math.abs(gap) > Math.abs(gaps[best]) ? index : best),\n  0\n);\nconst widestGapText = `Widest gap vs. target: ${labels[widestGapIndex]} (${\n  gaps[widestGapIndex] > 0 ? \"+\" : \"\"\n}${gaps[widestGapIndex]} pts)`;\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: \"Quarterly KPI Dashboard · bullet-basic · javascript · echarts · anyplot.ai\",\n    subtext: widestGapText,\n    left: \"center\",\n    top: 20,\n    textStyle: { color: t.ink, fontSize: 24, fontWeight: 500 },\n    subtextStyle: { color: t.inkSoft, fontSize: 14 },\n  },\n  legend: {\n    top: 104,\n    left: \"center\",\n    data: [\"Poor\", \"Satisfactory\", \"Good\", \"Actual\", \"Target\"],\n    itemWidth: 14,\n    itemHeight: 14,\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n  },\n  grid: { left: 200, right: 90, top: 180, bottom: 110 },\n  xAxis: {\n    type: \"value\",\n    min: 0,\n    max: 100,\n    name: \"% of Target\",\n    nameLocation: \"middle\",\n    nameGap: 34,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    axisLabel: { color: t.inkSoft, fontSize: 14, formatter: \"{value}%\" },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"category\",\n    data: labels,\n    inverse: true,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  series: [\n    {\n      name: \"Poor\",\n      type: \"bar\",\n      stack: \"range\",\n      barWidth: \"55%\",\n      silent: true,\n      data: bandPoor,\n      itemStyle: { color: t.ink, opacity: bandOpacity[0] },\n      z: 1,\n    },\n    {\n      name: \"Satisfactory\",\n      type: \"bar\",\n      stack: \"range\",\n      barWidth: \"55%\",\n      silent: true,\n      data: bandSatisfactory,\n      itemStyle: { color: t.ink, opacity: bandOpacity[1] },\n      z: 1,\n    },\n    {\n      name: \"Good\",\n      type: \"bar\",\n      stack: \"range\",\n      barWidth: \"55%\",\n      silent: true,\n      data: bandGood,\n      itemStyle: { color: t.ink, opacity: bandOpacity[2] },\n      z: 1,\n    },\n    {\n      name: \"Actual\",\n      type: \"bar\",\n      barWidth: \"22%\",\n      barGap: \"-100%\",\n      data: actual,\n      itemStyle: { color: t.palette[0] },\n      label: {\n        show: true,\n        position: \"right\",\n        color: t.ink,\n        fontSize: 14,\n        formatter: (p) => `${p.value}%`,\n      },\n      z: 2,\n    },\n    {\n      name: \"Target\",\n      type: \"scatter\",\n      symbol: \"rect\",\n      symbolSize: [6, 95],\n      data: targetPoints,\n      itemStyle: { color: t.ink },\n      z: 3,\n    },\n  ],\n});\n"}