{"spec_id":"gauge-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\ngauge-basic: Basic Gauge Chart\nLibrary: plotnine 0.15.7 | Python 3.13.14\nQuality: 88/100 | Created: 2026-06-30\n\"\"\"\n\nimport os\nimport sys\n\n\n# Avoid name collision: drop this script's directory from sys.path\n# so `from plotnine import ...` resolves to the installed package.\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 numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_polygon,\n    geom_segment,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_identity,\n    scale_fill_manual,\n    theme,\n    theme_void,\n)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\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# Imprint palette semantic anchors for traffic-light zones\nZONE_BAD = \"#AE3030\"  # Imprint position 5 — matte red (semantic bad/loss)\nZONE_WARN = \"#DDCC77\"  # Imprint amber warning anchor\nZONE_GOOD = \"#009E73\"  # Imprint position 1 — brand green\nZONE_COLORS = [ZONE_BAD, ZONE_WARN, ZONE_GOOD]\n\n# Zone label text colors that contrast against each zone fill\n# Data colors are theme-stable, so these text colors are static too\nZONE_LABEL_COLORS = [\"#FAF8F1\", \"#1A1A17\", \"#FAF8F1\"]\nZONE_NAMES = [\"Low\", \"Mid\", \"High\"]\n\n# Data\nvalue = 72\nmin_value = 0\nmax_value = 100\nthresholds = [30, 70]\n\n# Geometry parameters\ninner_radius = 0.70\nouter_radius = 1.00\nstart_angle = np.pi\nend_angle = 0.0\n\n# Zone arc polygons\nzone_bounds = [min_value, *thresholds, max_value]\nzone_records = []\nfor i in range(len(ZONE_COLORS)):\n    start_pct = (zone_bounds[i] - min_value) / (max_value - min_value)\n    end_pct = (zone_bounds[i + 1] - min_value) / (max_value - min_value)\n    start_ang = start_angle - start_pct * (start_angle - end_angle)\n    end_ang = start_angle - end_pct * (start_angle - end_angle)\n    n_pts = 60\n    angles_outer = np.linspace(start_ang, end_ang, n_pts)\n    angles_inner = np.linspace(end_ang, start_ang, n_pts)\n    xs = np.concatenate([outer_radius * np.cos(angles_outer), inner_radius * np.cos(angles_inner)])\n    ys = np.concatenate([outer_radius * np.sin(angles_outer), inner_radius * np.sin(angles_inner)])\n    for j in range(len(xs)):\n        zone_records.append({\"x\": xs[j], \"y\": ys[j], \"zone\": str(i)})\n\ndf_zones = pd.DataFrame(zone_records)\n\n# Zone labels at arc midpoints — plotnine geom_text + scale_color_identity\narc_mid_radius = (inner_radius + outer_radius) / 2  # 0.85\nzone_label_rows = []\nfor i in range(len(ZONE_COLORS)):\n    mid_pct = (zone_bounds[i] + zone_bounds[i + 1]) / 2 / (max_value - min_value)\n    ang = start_angle - mid_pct * (start_angle - end_angle)\n    zone_label_rows.append(\n        {\n            \"x\": arc_mid_radius * np.cos(ang),\n            \"y\": arc_mid_radius * np.sin(ang),\n            \"label\": ZONE_NAMES[i],\n            \"text_color\": ZONE_LABEL_COLORS[i],\n        }\n    )\ndf_zone_labels = pd.DataFrame(zone_label_rows)\n\n# Needle pointing to current value\nvalue_pct = (value - min_value) / (max_value - min_value)\nneedle_angle = start_angle - value_pct * (start_angle - end_angle)\nneedle_length = inner_radius * 0.92\ndf_needle = pd.DataFrame(\n    {\"x\": [0], \"y\": [0], \"xend\": [needle_length * np.cos(needle_angle)], \"yend\": [needle_length * np.sin(needle_angle)]}\n)\n\n# Tick marks and labels\nmajor_ticks = [0, 25, 50, 75, 100]\nminor_ticks = [t for t in range(0, 101, 5) if t not in major_ticks]\n\nminor_tick_records = []\nfor tv in minor_ticks:\n    pct = (tv - min_value) / (max_value - min_value)\n    ang = start_angle - pct * (start_angle - end_angle)\n    minor_tick_records.append(\n        {\n            \"x\": (outer_radius * 1.02) * np.cos(ang),\n            \"y\": (outer_radius * 1.02) * np.sin(ang),\n            \"xend\": (outer_radius * 1.05) * np.cos(ang),\n            \"yend\": (outer_radius * 1.05) * np.sin(ang),\n        }\n    )\n\nmajor_tick_records = []\nlabel_records = []\nfor tv in major_ticks:\n    pct = (tv - min_value) / (max_value - min_value)\n    ang = start_angle - pct * (start_angle - end_angle)\n    major_tick_records.append(\n        {\n            \"x\": (outer_radius * 1.02) * np.cos(ang),\n            \"y\": (outer_radius * 1.02) * np.sin(ang),\n            \"xend\": (outer_radius * 1.10) * np.cos(ang),\n            \"yend\": (outer_radius * 1.10) * np.sin(ang),\n        }\n    )\n    label_records.append(\n        {\"x\": (outer_radius * 1.20) * np.cos(ang), \"y\": (outer_radius * 1.20) * np.sin(ang), \"label\": str(tv)}\n    )\n\ndf_minor_ticks = pd.DataFrame(minor_tick_records)\ndf_major_ticks = pd.DataFrame(major_tick_records)\ndf_labels = pd.DataFrame(label_records)\n\n# Center cap (two-layer for visual definition)\ndf_cap_outer = pd.DataFrame({\"x\": [0], \"y\": [0]})\ndf_cap_inner = pd.DataFrame({\"x\": [0], \"y\": [0]})\n\n# Value display and context label\ndf_value = pd.DataFrame({\"x\": [0], \"y\": [-0.28], \"label\": [str(value)]})\ndf_context = pd.DataFrame({\"x\": [0], \"y\": [-0.50], \"label\": [\"Current Sales\"]})\n\n# Build plot — idiomatic plotnine: grammar-of-graphics layers + labs() title\n# scale_color_identity maps zone label hex colors directly from data column\nplot = (\n    ggplot()\n    + geom_polygon(aes(x=\"x\", y=\"y\", fill=\"zone\", group=\"zone\"), data=df_zones, color=PAGE_BG, size=0.6)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\", color=\"text_color\"), data=df_zone_labels, size=8, fontweight=\"bold\")\n    + scale_color_identity()\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), data=df_minor_ticks, color=INK_SOFT, size=0.4)\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), data=df_major_ticks, color=INK, size=0.8)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_labels, color=INK_SOFT, size=9, fontweight=\"bold\")\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), data=df_needle, color=INK, size=2.0, lineend=\"round\")\n    + geom_point(aes(x=\"x\", y=\"y\"), data=df_cap_outer, color=INK, size=7)\n    + geom_point(aes(x=\"x\", y=\"y\"), data=df_cap_inner, color=PAGE_BG, size=2.5)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_value, color=ZONE_GOOD, size=28, fontweight=\"bold\")\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_context, color=INK_MUTED, size=10)\n    + scale_fill_manual(values=ZONE_COLORS, guide=None)\n    + coord_fixed(ratio=1, xlim=(-1.40, 1.40), ylim=(-0.65, 1.50))\n    + labs(title=\"gauge-basic · python · plotnine · anyplot.ai\")\n    + theme_void()\n    + theme(\n        figure_size=(8, 4.5),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_title=element_text(color=INK, size=12, ha=\"center\"),\n        legend_position=\"none\",\n        axis_text=element_blank(),\n        axis_title=element_blank(),\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}