{"spec_id":"flamegraph-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nflamegraph-basic: Flame Graph for Performance Profiling\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-08\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    coord_cartesian,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_rect,\n    geom_segment,\n    geom_text,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_fill_identity,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_void,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\")\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\n\n# Data - simulated CPU profiling stacks with sample counts\nnp.random.seed(42)\n\nstacks = {\n    \"main\": 950,\n    \"main;process_request\": 800,\n    \"main;process_request;parse_input\": 180,\n    \"main;process_request;parse_input;tokenize\": 120,\n    \"main;process_request;parse_input;validate\": 55,\n    \"main;process_request;compute\": 420,\n    \"main;process_request;compute;matrix_mult\": 210,\n    \"main;process_request;compute;matrix_mult;dot_product\": 160,\n    \"main;process_request;compute;transform\": 130,\n    \"main;process_request;compute;transform;normalize\": 80,\n    \"main;process_request;compute;transform;scale\": 45,\n    \"main;process_request;compute;aggregate\": 70,\n    \"main;process_request;serialize\": 190,\n    \"main;process_request;serialize;to_json\": 110,\n    \"main;process_request;serialize;compress\": 72,\n    \"main;init_config\": 90,\n    \"main;init_config;load_file\": 55,\n    \"main;init_config;parse_yaml\": 40,\n    \"main;cleanup\": 50,\n    \"main;cleanup;flush_cache\": 35,\n    \"main;cleanup;close_conn\": 22,\n}\n\n# Build child map and stack positions\ntotal_samples = stacks[\"main\"]\n\nchildren_map = {}\nfor stack_path in stacks:\n    parts = stack_path.split(\";\")\n    if len(parts) > 1:\n        parent = \";\".join(parts[:-1])\n        children_map.setdefault(parent, []).append((stack_path, stacks[stack_path]))\n\npositions = {\"main\": (0.0, float(total_samples))}\nqueue = [\"main\"]\nwhile queue:\n    current = queue.pop(0)\n    parent_xmin, parent_xmax = positions[current]\n    if current in children_map:\n        kids = sorted(children_map[current], key=lambda x: x[0])\n        child_total = sum(s for _, s in kids)\n        parent_samples = stacks[current]\n        self_time = parent_samples - child_total\n        parent_width = parent_xmax - parent_xmin\n        bar_scale = parent_width / parent_samples\n        x_cursor = parent_xmin + (self_time * bar_scale * 0.5 if self_time > 0 else 0)\n        for child_path, child_samples in kids:\n            child_width = child_samples * bar_scale\n            positions[child_path] = (x_cursor, x_cursor + child_width)\n            x_cursor += child_width\n            queue.append(child_path)\n\n# Identify the hottest code path (widest bar at each depth from root)\nhot_path = {\"main\"}\ncurrent_path = \"main\"\nwhile current_path in children_map:\n    hottest = max(children_map[current_path], key=lambda x: x[1])\n    hot_path.add(hottest[0])\n    current_path = hottest[0]\n\n# Build rectangles\nrecords = []\nmax_depth = 0\nfor stack_path, samples in stacks.items():\n    parts = stack_path.split(\";\")\n    depth = len(parts) - 1\n    max_depth = max(max_depth, depth)\n    xmin, xmax = positions[stack_path]\n    records.append(\n        {\n            \"xmin\": xmin,\n            \"xmax\": xmax,\n            \"ymin\": depth,\n            \"ymax\": depth + 1.0,\n            \"func\": parts[-1],\n            \"depth\": depth,\n            \"samples\": samples,\n            \"pct\": round(samples / total_samples * 100, 1),\n            \"stack\": stack_path,\n            \"is_hot\": stack_path in hot_path,\n        }\n    )\n\ndf = pd.DataFrame(records)\n\n# Warm flame palette (spec calls for warm yellows/oranges/reds — semantic exception).\n# Hot path uses saturated steps; non-hot path uses mid-luminance dusty warms that\n# stay readable on both #FAF8F1 and #1A1A17 surfaces. Data colors are identical\n# across themes — only chrome (text/borders/background) flips.\nflame_hot = [\"#FFD54F\", \"#FFA726\", \"#FB8C00\", \"#EF5350\", \"#D32F2F\"]\nflame_cool = [\"#E8C580\", \"#DBA76A\", \"#CB8956\", \"#B87047\", \"#A2563B\"]\ndf[\"color\"] = df.apply(\n    lambda r: (\n        flame_hot[min(r[\"depth\"], len(flame_hot) - 1)]\n        if r[\"is_hot\"]\n        else flame_cool[min(r[\"depth\"], len(flame_cool) - 1)]\n    ),\n    axis=1,\n)\n\n# Label: only render when the bar is wide enough to fully contain the function name\n# (per spec: \"Include function name labels inside bars when the bar is wide enough\n# to fit the text\"). Calibrated for geom_text size=7 in this coord system — each\n# character occupies ~13 sample-units of width when rendered.\nchar_width_units = total_samples * 0.013\ndf[\"label\"] = df.apply(\n    lambda r: r[\"func\"] if (r[\"xmax\"] - r[\"xmin\"]) >= len(r[\"func\"]) * char_width_units else \"\", axis=1\n)\n\ndf[\"label_x\"] = (df[\"xmin\"] + df[\"xmax\"]) / 2\ndf[\"label_y\"] = (df[\"ymin\"] + df[\"ymax\"]) / 2\n\n# Layered rendering: cool bars first, hot bars on top\ndf_cool = df[~df[\"is_hot\"]].copy()\ndf_hot = df[df[\"is_hot\"]].copy()\n\n# Depth separator lines (drawn in PAGE_BG so they read as subtle gaps on either theme)\ndepth_lines = pd.DataFrame(\n    {\n        \"y\": [float(d) for d in range(1, max_depth + 1)],\n        \"xstart\": [0.0] * max_depth,\n        \"xend\": [float(total_samples)] * max_depth,\n    }\n)\n\n# Plot\nplot = (\n    ggplot()\n    + geom_rect(\n        aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\", fill=\"color\"),\n        data=df_cool,\n        color=PAGE_BG,\n        size=0.3,\n        tooltips=layer_tooltips()\n        .title(\"@func\")\n        .line(\"Samples: @samples\")\n        .line(\"Percentage: @pct%\")\n        .line(\"Stack: @stack\"),\n    )\n    + geom_rect(\n        aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\", fill=\"color\"),\n        data=df_hot,\n        color=INK,\n        size=0.7,\n        tooltips=layer_tooltips()\n        .title(\"@func\")\n        .line(\"Samples: @samples\")\n        .line(\"Percentage: @pct%\")\n        .line(\"Stack: @stack\"),\n    )\n    + geom_segment(aes(x=\"xstart\", xend=\"xend\", y=\"y\", yend=\"y\"), data=depth_lines, color=PAGE_BG, size=0.35, alpha=0.9)\n    + geom_text(\n        aes(x=\"label_x\", y=\"label_y\", label=\"label\"),\n        data=df,\n        size=7,\n        color=\"#1A1A17\",\n        fontface=\"bold\",\n        label_padding=0.15,\n    )\n    + scale_fill_identity()\n    + scale_x_continuous(expand=[0.005, 0])\n    + scale_y_continuous(expand=[0.02, 0])\n    + coord_cartesian(ylim=[-0.1, max_depth + 1.15])\n    + labs(title=\"flamegraph-basic · python · letsplot · anyplot.ai\")\n    + theme_void()\n    + theme(\n        plot_title=element_text(size=16, face=\"bold\", color=INK, hjust=0.5),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        axis_line=element_blank(),\n        plot_margin=[22, 16, 10, 16],\n    )\n    + ggsize(800, 450)\n)\n\n# Save - canvas: ggsize(800, 450) * scale=4 -> 3200x1800 px\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}