{"spec_id":"dumbbell-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ndumbbell-basic: Basic Dumbbell Chart\nLibrary: letsplot 4.11.0 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-06-30\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_segment,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_color_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme tokens\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\"\nRULE = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# Imprint palette — position 1 = After (hero), position 2 = Before, position 5 = semantic decline\nBRAND = \"#009E73\"  # position 1 — After scores / positive change direction\nLAVENDER = \"#C475FD\"  # position 2 — Before scores\nDECLINE = \"#AE3030\"  # position 5 — semantic red for regressions\n\n# Data — Employee satisfaction scores before and after policy changes\ncategories = [\n    \"Engineering\",\n    \"Marketing\",\n    \"Sales\",\n    \"Customer Support\",\n    \"HR\",\n    \"Finance\",\n    \"Operations\",\n    \"Product\",\n    \"Legal\",\n    \"R&D\",\n]\nbefore_scores = [62, 58, 71, 55, 68, 64, 72, 73, 66, 61]\nafter_scores = [78, 72, 85, 80, 81, 70, 65, 88, 67, 75]\n\ndf = pd.DataFrame({\"category\": categories, \"before\": before_scores, \"after\": after_scores})\ndf[\"diff\"] = df[\"after\"] - df[\"before\"]\ndf = df.sort_values(\"diff\", ascending=True).reset_index(drop=True)\ndf[\"y_pos\"] = range(len(df))\n\ndf_improved = df[df[\"diff\"] > 0]\ndf_declined = df[df[\"diff\"] <= 0]\n\ndf_points = pd.concat(\n    [\n        pd.DataFrame(\n            {\n                \"y_pos\": df[\"y_pos\"],\n                \"value\": df[\"before\"],\n                \"period\": \"Before\",\n                \"category\": df[\"category\"].values,\n                \"diff\": df[\"diff\"].values,\n            }\n        ),\n        pd.DataFrame(\n            {\n                \"y_pos\": df[\"y_pos\"],\n                \"value\": df[\"after\"],\n                \"period\": \"After\",\n                \"category\": df[\"category\"].values,\n                \"diff\": df[\"diff\"].values,\n            }\n        ),\n    ]\n)\n\n# Scale title fontsize for longer-than-baseline title (floor: 11px)\ntitle = \"Employee Satisfaction · dumbbell-basic · python · letsplot · anyplot.ai\"\nn = len(title)\ntitle_size = max(11, round(16 * 67 / n)) if n > 67 else 16\n\n# Plot — horizontal dumbbell; segments color-coded by change direction\nplot = (\n    ggplot()\n    + geom_segment(\n        data=df_improved,\n        mapping=aes(x=\"before\", xend=\"after\", y=\"y_pos\", yend=\"y_pos\"),\n        color=BRAND,\n        size=1.2,\n        alpha=0.65,\n    )\n    + geom_segment(\n        data=df_declined,\n        mapping=aes(x=\"before\", xend=\"after\", y=\"y_pos\", yend=\"y_pos\"),\n        color=DECLINE,\n        size=1.2,\n        alpha=0.65,\n    )\n    + geom_point(\n        data=df_points,\n        mapping=aes(x=\"value\", y=\"y_pos\", color=\"period\"),\n        size=5,\n        tooltips=layer_tooltips().line(\"@category\").line(\"@period: @value\").line(\"Change: @diff\"),\n    )\n    + scale_color_manual(values=[BRAND, LAVENDER], name=\"Period\")\n    + scale_x_continuous(limits=[50, 95])\n    + scale_y_continuous(breaks=list(range(len(df))), labels=df[\"category\"].tolist())\n    + labs(x=\"Satisfaction Score\", y=\"Department\", title=title)\n    + ggsize(800, 450)\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        legend_background=element_rect(fill=ELEVATED_BG, color=\"transparent\"),\n        panel_grid_major_x=element_line(color=RULE, size=0.3),\n        panel_grid_minor_x=element_blank(),\n        panel_grid_major_y=element_blank(),\n        panel_grid_minor_y=element_blank(),\n        axis_line=element_line(color=INK_SOFT),\n        axis_ticks=element_blank(),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        plot_title=element_text(size=title_size, color=INK, hjust=0.5),\n        legend_title=element_text(size=10, color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_position=[0.92, 0.2],\n        legend_justification=[1, 0],\n    )\n)\n\n# Save\nggsave(plot, filename=f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, filename=f\"plot-{THEME}.html\", path=\".\")\n"}