{"spec_id":"gauge-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ngauge-basic: Basic Gauge Chart\nLibrary: letsplot 4.11.0 | Python 3.13.14\nQuality: 89/100 | Updated: 2026-06-30\n\"\"\"\n\nimport math\nimport os\n\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_polygon,\n    geom_segment,\n    geom_text,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    scale_fill_manual,\n    theme,\n    xlim,\n    ylim,\n)\n\n\nLetsPlot.setup_html()\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\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# Imprint palette — semantic exception for gauge zones\n# bad/low → matte red (#AE3030), caution/medium → amber (#DDCC77), good/high → brand green (#009E73)\nzone_colors_map = {\"Low\": \"#AE3030\", \"Medium\": \"#DDCC77\", \"High\": \"#009E73\"}\n\n# Sales performance gauge data\nvalue = 72\nmin_value = 0\nmax_value = 100\nthresholds = [30, 70]\n\n# Gauge geometry parameters\ninner_radius = 0.5\nouter_radius = 1.0\nn_points = 50\n\nzone_boundaries = [min_value] + thresholds + [max_value]\nzone_names = [\"Low\", \"Medium\", \"High\"]\n\n# Build arc polygon for each zone (semi-circle: 180° → 0°)\npolygons_data = []\nfor i in range(len(zone_boundaries) - 1):\n    start_val = zone_boundaries[i]\n    end_val = zone_boundaries[i + 1]\n\n    start_ratio = (start_val - min_value) / (max_value - min_value)\n    end_ratio = (end_val - min_value) / (max_value - min_value)\n    start_angle = 180 - start_ratio * 180\n    end_angle = 180 - end_ratio * 180\n\n    angles = [math.radians(end_angle + (start_angle - end_angle) * j / n_points) for j in range(n_points + 1)]\n\n    x_outer = [outer_radius * math.cos(a) for a in angles]\n    y_outer = [outer_radius * math.sin(a) for a in angles]\n    x_inner = [inner_radius * math.cos(a) for a in reversed(angles)]\n    y_inner = [inner_radius * math.sin(a) for a in reversed(angles)]\n\n    for j, (x, y) in enumerate(zip(x_outer + x_inner, y_outer + y_inner, strict=True)):\n        polygons_data.append({\"x\": x, \"y\": y, \"zone\": zone_names[i], \"order\": j})\n\ndf_polygons = pd.DataFrame(polygons_data)\n\n# Needle\nneedle_ratio = (value - min_value) / (max_value - min_value)\nneedle_angle = math.radians(180 - needle_ratio * 180)\nneedle_length = 0.85\ndf_needle = pd.DataFrame(\n    {\n        \"x\": [0],\n        \"y\": [0],\n        \"xend\": [needle_length * math.cos(needle_angle)],\n        \"yend\": [needle_length * math.sin(needle_angle)],\n    }\n)\n\n# Pivot circle\ncircle_r = 0.08\ncircle_angles = [2 * math.pi * i / 30 for i in range(31)]\ndf_circle = pd.DataFrame(\n    {\"x\": [circle_r * math.cos(a) for a in circle_angles], \"y\": [circle_r * math.sin(a) for a in circle_angles]}\n)\n\n# Text elements\ndf_label = pd.DataFrame({\"x\": [0], \"y\": [-0.22], \"label\": [f\"{value}%\"]})\ndf_subtitle = pd.DataFrame({\"x\": [0], \"y\": [-0.40], \"label\": [\"Sales Performance Score\"]})\ndf_min_max = pd.DataFrame({\"x\": [-1.08, 1.08], \"y\": [-0.08, -0.08], \"label\": [str(min_value), str(max_value)]})\n\nzone_label_radius = 0.73\ndf_zone_labels_rows = []\nfor i, name in enumerate(zone_names):\n    start_val = zone_boundaries[i]\n    end_val = zone_boundaries[i + 1]\n    mid_ratio = ((start_val + end_val) / 2 - min_value) / (max_value - min_value)\n    mid_angle = math.radians(180 - mid_ratio * 180)\n    df_zone_labels_rows.append(\n        {\"x\": zone_label_radius * math.cos(mid_angle), \"y\": zone_label_radius * math.sin(mid_angle), \"label\": name}\n    )\ndf_zone_labels = pd.DataFrame(df_zone_labels_rows)\n\nplot = (\n    ggplot()\n    + geom_polygon(aes(x=\"x\", y=\"y\", fill=\"zone\", group=\"zone\"), data=df_polygons, color=PAGE_BG, size=0.75, alpha=0.92)\n    + scale_fill_manual(values=zone_colors_map)\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), data=df_needle, color=INK, size=2.5)\n    + geom_polygon(aes(x=\"x\", y=\"y\"), data=df_circle, fill=INK, color=INK)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_label, size=14, color=INK, fontface=\"bold\")\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_subtitle, size=7, color=INK_MUTED)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_min_max, size=7, color=INK_SOFT)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_zone_labels, size=7, color=INK_SOFT, fontface=\"bold\")\n    + labs(title=\"gauge-basic · python · letsplot · anyplot.ai\")\n    + xlim(-1.4, 1.4)\n    + ylim(-0.55, 1.15)\n    + theme(\n        axis_title=element_blank(),\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        axis_line=element_blank(),\n        panel_grid=element_blank(),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        legend_position=\"none\",\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n    )\n    + ggsize(800, 450)\n)\n\nggsave(plot, f\"plot-{THEME}.png\", scale=4, path=\".\")\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}