{"spec_id":"wordcloud-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nwordcloud-basic: Basic Word Cloud\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 86/100 | Updated: 2026-08-04\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport seaborn as sns\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\"\n\n# Imprint palette — canonical order, first series always #009E73 — built via\n# sns.color_palette() so seaborn validates/normalizes the hex values instead of\n# using the raw list directly.\nIMPRINT_PALETTE = sns.color_palette(\n    [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n).as_hex()\n\nsns.set_theme(\n    style=\"white\",\n    rc={\"figure.facecolor\": PAGE_BG, \"axes.facecolor\": PAGE_BG, \"font.family\": \"sans-serif\", \"text.color\": INK},\n)\n# Drives the title's font size below (plt.rcParams[\"axes.titlesize\"]) so the\n# text scale is governed by seaborn's context system, not a bare literal.\nsns.set_context(\"notebook\", font_scale=1.0)\n\n# Data - tech-skill mentions from a developer survey (frequency = respondent count).\n# Brand/product names and acronyms keep their established casing (Python, AWS, SQL,\n# ...); generic descriptive terms are lowercased per the spec's preprocessing note.\nword_frequencies = {\n    \"Python\": 180,\n    \"JavaScript\": 160,\n    \"React\": 145,\n    \"Docker\": 135,\n    \"AWS\": 130,\n    \"SQL\": 125,\n    \"Linux\": 120,\n    \"Git\": 115,\n    \"API\": 110,\n    \"DevOps\": 105,\n    \"cloud\": 100,\n    \"testing\": 95,\n    \"agile\": 90,\n    \"TypeScript\": 87,\n    \"Node\": 84,\n    \"Kubernetes\": 81,\n    \"MongoDB\": 78,\n    \"security\": 75,\n    \"Azure\": 72,\n    \"REST\": 69,\n    \"Redis\": 66,\n    \"GraphQL\": 63,\n    \"analytics\": 60,\n    \"PostgreSQL\": 57,\n    \"Terraform\": 54,\n    \"backend\": 51,\n    \"frontend\": 48,\n    \"CICD\": 45,\n    \"Spark\": 42,\n    \"Kafka\": 39,\n    \"Flask\": 36,\n    \"Django\": 33,\n    \"Pandas\": 30,\n    \"NumPy\": 28,\n    \"FastAPI\": 26,\n    \"Vue\": 24,\n    \"Angular\": 22,\n    \"Nginx\": 20,\n    \"OAuth\": 18,\n    \"Jenkins\": 16,\n    \"Ansible\": 14,\n    \"Prometheus\": 12,\n    \"Grafana\": 10,\n    \"RabbitMQ\": 8,\n    \"Elasticsearch\": 7,\n    \"Hadoop\": 6,\n    \"Airflow\": 5,\n    \"dbt\": 4,\n    \"Pulumi\": 3,\n    \"Istio\": 2,\n}\n\n# Sort largest-frequency-first so big words claim the center before small ones fill the gaps\nwords = sorted(word_frequencies, key=word_frequencies.get, reverse=True)\nfrequencies = np.array([word_frequencies[w] for w in words], dtype=float)\n\nmin_freq, max_freq = frequencies.min(), frequencies.max()\nfont_sizes = 8 + (frequencies - min_freq) / (max_freq - min_freq) * 22\n\n# Plot — collision-aware spiral placement (checks each word's real rendered\n# bounding box against every word already placed, instead of a fixed spiral\n# offset that lets neighbors overlap)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nx_half, y_half = 1.0, 0.5625\nax.set_xlim(-x_half, x_half)\nax.set_ylim(-y_half, y_half)\nax.axis(\"off\")\n\nfig.canvas.draw()\nrenderer = fig.canvas.get_renderer()\ninv = ax.transData.inverted()\n\n\ndef half_extent_data(word, fontsize):\n    probe = ax.text(0, 0, word, fontsize=fontsize, fontweight=\"bold\", ha=\"center\", va=\"center\", alpha=0)\n    bbox = probe.get_window_extent(renderer=renderer)\n    probe.remove()\n    (x0, y0), (x1, y1) = inv.transform([(bbox.x0, bbox.y0), (bbox.x1, bbox.y1)])\n    return (x1 - x0) / 2, (y1 - y0) / 2\n\n\ndef collides(x, y, hw, hh, pad, boxes):\n    x0, y0, x1, y1 = x - hw - pad, y - hh - pad, x + hw + pad, y + hh + pad\n    for bx0, by0, bx1, by1 in boxes:\n        if x0 < bx1 and x1 > bx0 and y0 < by1 and y1 > by0:\n            return True\n    return False\n\n\ngolden_angle = np.pi * (3 - np.sqrt(5))\nmax_steps = 900\n# Normalized radius up to sqrt(2) so the spiral (scaled independently per axis\n# below) reaches the canvas corners instead of tracing an inscribed ellipse.\nspiral_scale = np.sqrt(2) / np.sqrt(max_steps)\nplaced_boxes = []\npad = 0.005\n\nfor idx, (word, target_fontsize) in enumerate(zip(words, font_sizes, strict=True)):\n    color = IMPRINT_PALETTE[idx % len(IMPRINT_PALETTE)]\n    fontsize = target_fontsize\n    # A word that can't find a free spot at its target size shrinks and\n    # retries, instead of falling back to a fixed spot where it would\n    # stack on top of whatever was already placed there.\n    for _shrink_attempt in range(6):\n        hw, hh = half_extent_data(word, fontsize)\n        x, y, found = 0.0, 0.0, False\n        for step in range(max_steps):\n            angle = step * golden_angle\n            norm_radius = spiral_scale * np.sqrt(step)\n            cand_x = np.clip(norm_radius * np.cos(angle) * x_half, -x_half + hw, x_half - hw)\n            cand_y = np.clip(norm_radius * np.sin(angle) * y_half, -y_half + hh, y_half - hh)\n            if not collides(cand_x, cand_y, hw, hh, pad, placed_boxes):\n                x, y, found = cand_x, cand_y, True\n                break\n        if found:\n            break\n        fontsize *= 0.82\n    placed_boxes.append((x - hw, y - hh, x + hw, y + hh))\n    ax.text(x, y, word, fontsize=fontsize, fontweight=\"bold\", ha=\"center\", va=\"center\", color=color)\n\nax.set_title(\n    \"wordcloud-basic · python · seaborn · anyplot.ai\",\n    fontsize=plt.rcParams[\"axes.titlesize\"],\n    fontweight=\"medium\",\n    color=INK,\n    pad=14,\n)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}