{"spec_id":"rug-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nrug-basic: Basic Rug Plot\nLibrary: pygal 3.1.3 | Python 3.13.14\nQuality: 87/100 | Updated: 2026-07-25\n\"\"\"\n\nimport os\nimport sys\n\n\n# Pop script directory so local pygal.py doesn't shadow the installed package\n_script_dir = sys.path.pop(0)\nimport cairosvg\nimport pygal\nfrom pygal.style import Style\n\n\nsys.path.insert(0, _script_dir)\n\nimport numpy as np\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"  # Imprint palette position 1\n\n# Data - API response times (ms) with realistic multi-modal distribution\nnp.random.seed(42)\nvalues = np.concatenate(\n    [\n        np.random.normal(150, 20, 60),  # Typical fast responses\n        np.random.normal(250, 30, 25),  # Medium responses\n        np.random.normal(400, 15, 10),  # Slow outlier cluster\n        np.random.uniform(50, 100, 5),  # Very fast cache hits\n    ]\n)\nvalues = np.clip(values, 30, 500)\nvalues = np.sort(values)\n\n# Small vertical jitter (tick length stays fixed, only the baseline shifts) so\n# overlapping ticks in the densest cluster stay visually separable instead of\n# fusing into one solid block, while adding a subtle non-default texture.\njitter = np.random.uniform(-0.003, 0.003, size=len(values))\n\n# Style - each rug tick is added as its own series (pygal has no native rug\n# mark), so the color tuple must repeat brand green once per point. Pygal\n# auto-darkens colors when the list runs out (`Style.get_colors`), which would\n# otherwise fade the ticks from green toward black across the x-axis.\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=(BRAND,) * len(values),\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=5,\n    opacity=0.7,\n)\n\n# Plot - XY chart used as rug plot\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=\"rug-basic · python · pygal · anyplot.ai\",\n    x_title=\"Response Time (ms)\",\n    y_title=None,\n    show_legend=False,\n    show_dots=False,\n    stroke=True,\n    show_x_guides=False,\n    show_y_guides=False,\n    show_y_labels=False,\n    print_values=False,\n    explicit_size=True,\n    margin=60,\n    margin_top=100,\n    margin_bottom=200,\n    xrange=(30, 520),\n    range=(0, 0.1),\n)\n\n# Rug ticks - short vertical marks along the x-axis, filling most of the range.\n# Tick length stays fixed (0.09); only the baseline is jittered per point.\ntick_length = 0.09\n\nfor val, dy in zip(values, jitter, strict=True):\n    chart.add(\n        f\"{val:.1f} ms\", [(float(val), dy), (float(val), dy + tick_length)], stroke_style={\"width\": 5}, show_dots=False\n    )\n\n# Post-process SVG: pygal draws a full border rect around the plot area with no\n# built-in option to suppress it, so hide the rect stroke via CSS injection.\n# It also draws a full-height axis baseline (`.axis.x path.line`) at the plot's\n# left edge regardless of the data range, which floats above the jittered tick\n# band - suppress every path in the x-axis group the same way.\n_svg = chart.render()\n_frame_css = (\n    b'<style type=\"text/css\">'\n    b\".graph .plot .background{stroke:none!important}\"\n    b\".graph .plot rect.background{stroke:none!important}\"\n    b\".graph .axis.x path{stroke:none!important}\"\n    b\"</style>\"\n)\n_svg_patched = (\n    _svg.replace(b\"</defs>\", _frame_css + b\"</defs>\", 1)\n    if b\"</defs>\" in _svg\n    else _svg.replace(b\"</svg>\", _frame_css + b\"</svg>\", 1)\n)\n\n# Save\ncairosvg.svg2png(bytestring=_svg_patched, write_to=f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(_svg_patched)\n"}