{"spec_id":"bullet-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nbullet-basic: Basic Bullet Chart\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 83/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent self-import: remove this script's own directory from sys.path so that\n# \"import plotly\" resolves to the installed package, not this file.\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p) != _here]\n\nimport plotly.graph_objects as go\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Theme-adaptive chrome — Imprint palette\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Semantic status colors (Imprint palette positions 1 and 5)\nABOVE_TARGET = \"#009E73\"  # brand green → good / pass / above target\nBELOW_TARGET = \"#AE3030\"  # matte red → bad / fail / below target\n\n# Qualitative range band colors: poor → satisfactory → good\nif THEME == \"light\":\n    range_colors = [\"#8B9DAF\", \"#B4C0CA\", \"#D5DDE2\"]\nelse:\n    # Semi-transparent blue-gray overlays on dark bg\n    range_colors = [\"rgba(120,160,190,0.30)\", \"rgba(120,160,190,0.15)\", \"rgba(120,160,190,0.06)\"]\n\n# Data — KPI dashboard covering all three qualitative zones:\n# Revenue in \"good\" (above target), Profit in \"poor\" (far below target),\n# Customers and Satisfaction both in \"satisfactory\" (below target)\nmetrics = [\n    {\"label\": \"Revenue ($K)\", \"actual\": 275, \"target\": 250, \"ranges\": [150, 200, 300]},\n    {\"label\": \"Profit ($K)\", \"actual\": 41, \"target\": 80, \"ranges\": [50, 70, 120]},\n    {\"label\": \"Customers\", \"actual\": 320, \"target\": 400, \"ranges\": [200, 350, 500]},\n    {\"label\": \"Satisfaction\", \"actual\": 4.2, \"target\": 4.5, \"ranges\": [3.0, 4.0, 5.0]},\n]\n\nfig = go.Figure()\nn = len(metrics)\nspacing = 0.025\nrow_height = (1.0 - spacing * (n - 1)) / n\n\nfor i, m in enumerate(metrics):\n    y_end = 1.0 - i * (row_height + spacing)\n    y_start = max(0.0, y_end - row_height)\n    meets_target = m[\"actual\"] >= m[\"target\"]\n    bar_color = ABOVE_TARGET if meets_target else BELOW_TARGET\n\n    fig.add_trace(\n        go.Indicator(\n            mode=\"number+gauge\",\n            value=m[\"actual\"],\n            number={\"font\": {\"size\": 13, \"color\": bar_color, \"family\": \"Helvetica Neue, Arial, sans-serif\"}},\n            domain={\"x\": [0.17, 0.94], \"y\": [y_start, y_end]},\n            title={\n                \"text\": m[\"label\"],\n                \"font\": {\"size\": 11, \"color\": INK, \"family\": \"Helvetica Neue, Arial, sans-serif\"},\n                \"align\": \"left\",\n            },\n            gauge={\n                \"shape\": \"bullet\",\n                \"axis\": {\n                    \"range\": [0, m[\"ranges\"][-1]],\n                    \"tickfont\": {\"size\": 10, \"color\": INK_SOFT, \"family\": \"Helvetica Neue, Arial, sans-serif\"},\n                },\n                \"bar\": {\"color\": bar_color, \"thickness\": 0.4},\n                \"bgcolor\": PAGE_BG,\n                \"threshold\": {\"line\": {\"color\": INK, \"width\": 3}, \"thickness\": 0.8, \"value\": m[\"target\"]},\n                \"steps\": [\n                    {\"range\": [0, m[\"ranges\"][0]], \"color\": range_colors[0]},\n                    {\"range\": [m[\"ranges\"][0], m[\"ranges\"][1]], \"color\": range_colors[1]},\n                    {\"range\": [m[\"ranges\"][1], m[\"ranges\"][2]], \"color\": range_colors[2]},\n                ],\n            },\n        )\n    )\n\n# Status legend — centered below the chart as a footer (more connected to data)\nfig.add_annotation(\n    x=0.38,\n    y=-0.10,\n    xref=\"paper\",\n    yref=\"paper\",\n    text=\"▲ Above target\",\n    showarrow=False,\n    font={\"size\": 10, \"color\": ABOVE_TARGET, \"family\": \"Helvetica Neue, Arial, sans-serif\"},\n    xanchor=\"center\",\n)\nfig.add_annotation(\n    x=0.62,\n    y=-0.10,\n    xref=\"paper\",\n    yref=\"paper\",\n    text=\"▼ Below target\",\n    showarrow=False,\n    font={\"size\": 10, \"color\": BELOW_TARGET, \"family\": \"Helvetica Neue, Arial, sans-serif\"},\n    xanchor=\"center\",\n)\n\nfig.update_layout(\n    autosize=False,\n    title={\n        \"text\": \"bullet-basic · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 16, \"family\": \"Helvetica Neue, Arial, sans-serif\", \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK, \"family\": \"Helvetica Neue, Arial, sans-serif\"},\n    margin={\"l\": 40, \"r\": 40, \"t\": 60, \"b\": 72},\n    width=800,\n    height=450,\n)\n\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}