{"spec_id":"boxen-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nboxen-basic: Basic Boxen Plot (Letter-Value Plot)\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\nimport sys\n\nimport numpy as np\nimport pandas as pd\n\n\n# Avoid shadowing by removing current directory from path during import\ncwd = os.getcwd()\nsys.path = [p for p in sys.path if os.path.abspath(p) != cwd]\n\nfrom plotnine import (\n    aes,\n    element_line,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_rect,\n    geom_segment,\n    ggplot,\n    labs,\n    scale_fill_manual,\n    scale_x_continuous,\n    theme,\n    theme_minimal,\n)\n\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\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1\n\n# Set seed for reproducibility\nnp.random.seed(42)\n\n# Generate data - server response times by endpoint (1000+ per category)\nn_per_group = 2000\nendpoints = [\"API\", \"Database\", \"Cache\", \"Auth\"]\n\ndata = []\nfor endpoint in endpoints:\n    if endpoint == \"API\":\n        # Right-skewed with some outliers\n        values = np.concatenate(\n            [\n                np.random.exponential(50, n_per_group - 20),\n                np.random.uniform(300, 500, 20),  # Outliers\n            ]\n        )\n    elif endpoint == \"Database\":\n        # Bimodal - some fast, some slow queries\n        values = np.concatenate(\n            [np.random.normal(30, 10, n_per_group // 2), np.random.normal(100, 20, n_per_group // 2)]\n        )\n    elif endpoint == \"Cache\":\n        # Fast and tight distribution\n        values = np.random.normal(15, 5, n_per_group)\n        values = np.maximum(values, 1)  # No negative response times\n    else:  # Auth\n        # Medium with heavy tail\n        values = np.random.gamma(3, 20, n_per_group)\n\n    for v in values:\n        data.append({\"endpoint\": endpoint, \"response_time\": v})\n\ndf = pd.DataFrame(data)\n\n# Compute letter values for each group\ncategories = df[\"endpoint\"].unique()\nbox_data = []\noutlier_data = []\nmedian_data = []\n\n# Width parameters\nbase_width = 0.8\nwidth_decay = 0.85  # Each nested level is 85% of previous width\n\nfor i, cat in enumerate(categories):\n    values = df[df[\"endpoint\"] == cat][\"response_time\"].values\n\n    # Calculate letter values (quantiles) inline\n    n = len(values)\n    k = min(max(3, int(np.floor(np.log2(n)) - 2)), 8)  # Adaptive levels, cap at 8\n\n    # Compute quantile depths\n    depths = [0.5]  # Start with median\n    for j in range(1, k):\n        depth = 0.5 ** (j + 1)\n        depths.append(0.5 - depth)\n        depths.append(0.5 + depth)\n\n    depths = sorted(set(depths))\n    quantiles = np.quantile(values, depths)\n\n    # Find median\n    median_idx = depths.index(0.5)\n    median_val = quantiles[median_idx]\n    median_data.append({\"x\": i, \"y\": median_val, \"endpoint\": cat})\n\n    # Create nested boxes from outer to inner\n    n_pairs = (len(depths) - 1) // 2\n    for level in range(n_pairs):\n        lower_idx = level\n        upper_idx = len(depths) - 1 - level\n        ymin = quantiles[lower_idx]\n        ymax = quantiles[upper_idx]\n        width = base_width * (width_decay**level)\n\n        box_data.append(\n            {\n                \"endpoint\": cat,\n                \"x\": i,\n                \"xmin\": i - width / 2,\n                \"xmax\": i + width / 2,\n                \"ymin\": ymin,\n                \"ymax\": ymax,\n                \"level\": level,\n            }\n        )\n\n    # Outliers beyond deepest letter value\n    lower_bound = quantiles[0]\n    upper_bound = quantiles[-1]\n    outliers = values[(values < lower_bound) | (values > upper_bound)]\n    for o in outliers:\n        outlier_data.append({\"x\": i, \"y\": o, \"endpoint\": cat})\n\nbox_df = pd.DataFrame(box_data)\noutlier_df = pd.DataFrame(outlier_data) if outlier_data else pd.DataFrame(columns=[\"x\", \"y\", \"endpoint\"])\nmedian_df = pd.DataFrame(median_data)\n\n# Color palette - high contrast gradient with brand color base\n# Using Okabe-Ito greens and blues for better contrast\nn_levels = box_df[\"level\"].max() + 1 if len(box_df) > 0 else 1\ncolors = []\nif n_levels == 1:\n    colors = [BRAND]\nelse:\n    # Create a gradient from dark brand to light, with stronger contrast\n    for i in range(n_levels):\n        t = i / max(n_levels - 1, 1)  # 0 to 1\n        # Interpolate from darker brand shade to lighter version\n        r = int(0 + t * (200 - 0))\n        g = int(158 + t * (220 - 158))\n        b = int(115 + t * (240 - 115))\n        colors.append(f\"#{r:02x}{g:02x}{b:02x}\")\n\n# Create the plot\nplot = (\n    ggplot()\n    + geom_rect(\n        data=box_df.sort_values(\"level\", ascending=False),  # Draw outer boxes first\n        mapping=aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\", fill=\"factor(level)\"),\n        color=INK_SOFT,\n        size=0.3,\n    )\n    + geom_segment(data=median_df, mapping=aes(x=\"x - 0.35\", xend=\"x + 0.35\", y=\"y\", yend=\"y\"), color=INK, size=1.5)\n    + scale_fill_manual(\n        values=colors,\n        name=\"Quantile Level\",\n        labels=[f\"{50 * (0.5 ** (i + 1)):.1f}%-{100 - 50 * (0.5 ** (i + 1)):.1f}%\" for i in range(n_levels)],\n    )\n    + labs(title=\"boxen-basic · plotnine · anyplot.ai\", x=\"Endpoint\", y=\"Response Time (ms)\")\n    + theme_minimal()\n    + theme(\n        figure_size=(16, 9),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major=element_line(color=INK, alpha=0.10, size=0.3),\n        panel_grid_minor=element_line(color=INK, alpha=0.05, size=0.2),\n        panel_border=element_rect(color=INK_SOFT, fill=None, size=0.3),\n        axis_line=element_line(color=INK_SOFT, size=0.3),\n        plot_title=element_text(size=24, color=INK, weight=\"bold\"),\n        axis_title=element_text(size=20, color=INK),\n        axis_text=element_text(size=16, color=INK_SOFT),\n        axis_text_x=element_text(size=18, color=INK_SOFT),\n        legend_title=element_text(size=16, color=INK),\n        legend_text=element_text(size=14, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_position=\"right\",\n    )\n)\n\n# Add outliers if present\nif len(outlier_df) > 0:\n    plot = plot + geom_point(data=outlier_df, mapping=aes(x=\"x\", y=\"y\"), color=BRAND, size=2, alpha=0.5)\n\n# Custom x-axis scale for category names\nplot = plot + scale_x_continuous(breaks=list(range(len(categories))), labels=list(categories))\n\n# Save the plot\nplot.save(f\"plot-{THEME}.png\", dpi=300, width=16, height=9)\n"}