{"spec_id":"boxen-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nboxen-basic: Basic Boxen Plot (Letter-Value Plot)\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_rect,\n    geom_segment,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    scale_fill_manual,\n    scale_x_continuous,\n    theme,\n    theme_minimal,\n)\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\"\n\n# Data - Generate realistic response times for different server endpoints\nnp.random.seed(42)\nendpoints = [\"API Gateway\", \"Auth Service\", \"Database\", \"Cache Layer\"]\nn_per_group = 2000\n\ndata = []\ndistributions = {\n    \"API Gateway\": {\"base\": 45, \"scale\": 20, \"skew\": 0.5},\n    \"Auth Service\": {\"base\": 80, \"scale\": 35, \"skew\": 0.8},\n    \"Database\": {\"base\": 120, \"scale\": 50, \"skew\": 1.2},\n    \"Cache Layer\": {\"base\": 8, \"scale\": 5, \"skew\": 0.3},\n}\n\nfor endpoint in endpoints:\n    d = distributions[endpoint]\n    values = np.random.exponential(d[\"scale\"], n_per_group) + d[\"base\"]\n    slow_idx = np.random.choice(n_per_group, size=int(n_per_group * 0.05), replace=False)\n    values[slow_idx] = values[slow_idx] * np.random.uniform(2, 5, len(slow_idx))\n    data.extend([(endpoint, v) for v in values])\n\ndf = pd.DataFrame(data, columns=[\"endpoint\", \"response_time\"])\n\n# Letter value names and colors for legend (deepest at bottom for intuitive ordering)\nlevel_names = [\"50%\", \"75%\", \"87.5%\", \"93.75%\", \"96.875%\", \"98.4%\", \"99.2%\", \"99.6%\"]\nlevel_colors = [\"#306998\", \"#4A7FA8\", \"#6490B8\", \"#7EA1C8\", \"#98B2D8\", \"#B2C3E8\", \"#CCD4F8\", \"#E6E5FF\"]\n\n# Compute letter values and construct plot data inline\nbox_data = []\nmedian_data = []\noutlier_data = []\nmax_k = 0\n\nx_positions = {endpoint: i for i, endpoint in enumerate(endpoints)}\n\nfor endpoint in endpoints:\n    group_data = df[df[\"endpoint\"] == endpoint][\"response_time\"].values\n    sorted_vals = np.sort(group_data)\n    n = len(sorted_vals)\n\n    # Number of letter values based on data size\n    k = int(np.log2(n)) - 1\n    k = max(2, min(k, 8))\n    max_k = max(max_k, k)\n\n    x_pos = x_positions[endpoint]\n\n    # Calculate letter values and build boxes\n    for i in range(k):\n        depth = 0.5 ** (i + 1)\n        lower_q = depth\n        upper_q = 1 - depth\n\n        lower_val = np.percentile(sorted_vals, lower_q * 100)\n        upper_val = np.percentile(sorted_vals, upper_q * 100)\n\n        half_width = 0.4 * (0.85**i)\n        box_data.append(\n            {\n                \"x_min\": x_pos - half_width,\n                \"x_max\": x_pos + half_width,\n                \"y_min\": lower_val,\n                \"y_max\": upper_val,\n                \"level\": level_names[i],\n                \"endpoint\": endpoint,\n            }\n        )\n\n    # Calculate median line\n    median = np.median(sorted_vals)\n    median_data.append({\"x\": x_pos - 0.38, \"xend\": x_pos + 0.38, \"y\": median, \"endpoint\": endpoint})\n\n    # Calculate outliers beyond deepest letter value\n    deepest_depth = 0.5**k\n    deepest_lower = np.percentile(sorted_vals, deepest_depth * 100)\n    deepest_upper = np.percentile(sorted_vals, (1 - deepest_depth) * 100)\n    outliers = sorted_vals[(sorted_vals < deepest_lower) | (sorted_vals > deepest_upper)]\n\n    for o in outliers:\n        outlier_data.append({\"x\": x_pos, \"y\": o, \"endpoint\": endpoint})\n\nbox_df = pd.DataFrame(box_data)\nmedian_df = pd.DataFrame(median_data)\noutlier_df = pd.DataFrame(outlier_data) if outlier_data else pd.DataFrame(columns=[\"x\", \"y\", \"endpoint\"])\n\n# Plot\nplot = (\n    ggplot()\n    + geom_rect(\n        aes(xmin=\"x_min\", xmax=\"x_max\", ymin=\"y_min\", ymax=\"y_max\", fill=\"level\"),\n        data=box_df,\n        alpha=0.9,\n        color=INK_SOFT,\n        size=0.5,\n    )\n    + geom_segment(aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"y\"), data=median_df, color=\"#FFD43B\", size=3)\n    + scale_fill_manual(\n        values=dict(zip(level_names[:max_k], level_colors[:max_k], strict=False)), name=\"Quantile Range\"\n    )\n    + scale_x_continuous(breaks=[0, 1, 2, 3], labels=endpoints)\n    + labs(x=\"Server Endpoint\", y=\"Response Time (ms)\", title=\"boxen-basic · letsplot · anyplot.ai\")\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        panel_grid_major=element_rect(color=\"transparent\"),\n        panel_grid_minor=element_rect(color=\"transparent\"),\n        axis_title=element_text(size=20, color=INK),\n        axis_text=element_text(size=16, color=INK_SOFT),\n        axis_line=element_text(color=INK_SOFT),\n        plot_title=element_text(size=24, color=INK),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_title=element_text(size=18, color=INK),\n        legend_text=element_text(size=14, color=INK_SOFT),\n    )\n    + ggsize(1600, 900)\n)\n\n# Add outliers if present\nif not outlier_df.empty:\n    plot = plot + geom_point(aes(x=\"x\", y=\"y\"), data=outlier_df, color=\"#DC2626\", size=2, alpha=0.6)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=3)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}