{"spec_id":"wordcloud-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nwordcloud-basic: Basic Word Cloud\nLibrary: altair 6.2.2 | Python 3.13.14\nQuality: 85/100 | Updated: 2026-08-04\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent local file from shadowing the altair package\nscript_dir = os.path.dirname(os.path.abspath(__file__)) if __file__ else os.getcwd()\nif script_dir in sys.path:\n    sys.path.remove(script_dir)\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Background\" + \"Theme-adaptive Chrome\")\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# Imprint palette — color encodes topic, not ordinal position (see style guide)\nCATEGORY_COLORS = {\"Languages\": \"#009E73\", \"Data & AI\": \"#C475FD\", \"Cloud & Infra\": \"#4467A3\", \"Practices\": \"#BD8233\"}\n\n# Data: terms mined from developer conference talk titles, grouped by topic\nword_data = [\n    (\"Python\", 100, \"Languages\"),\n    (\"Analytics\", 92, \"Data & AI\"),\n    (\"Kubernetes\", 86, \"Cloud & Infra\"),\n    (\"JavaScript\", 80, \"Languages\"),\n    (\"DevOps\", 74, \"Practices\"),\n    (\"Docker\", 70, \"Cloud & Infra\"),\n    (\"Security\", 66, \"Practices\"),\n    (\"AWS\", 62, \"Cloud & Infra\"),\n    (\"Database\", 58, \"Data & AI\"),\n    (\"API\", 56, \"Practices\"),\n    (\"Machine Learning\", 54, \"Data & AI\"),\n    (\"TypeScript\", 50, \"Languages\"),\n    (\"AI\", 48, \"Data & AI\"),\n    (\"Agile\", 46, \"Practices\"),\n    (\"Testing\", 44, \"Practices\"),\n    (\"Terraform\", 42, \"Cloud & Infra\"),\n    (\"Microservices\", 38, \"Practices\"),\n    (\"Rust\", 36, \"Languages\"),\n    (\"Git\", 34, \"Practices\"),\n    (\"Azure\", 32, \"Cloud & Infra\"),\n    (\"GraphQL\", 30, \"Data & AI\"),\n    (\"Go\", 28, \"Languages\"),\n    (\"Scalability\", 26, \"Practices\"),\n    (\"Java\", 25, \"Languages\"),\n    (\"GCP\", 24, \"Cloud & Infra\"),\n    (\"NoSQL\", 23, \"Data & AI\"),\n    (\"Serverless\", 22, \"Cloud & Infra\"),\n    (\"CI/CD\", 21, \"Practices\"),\n    (\"Data Pipeline\", 20, \"Data & AI\"),\n    (\"Swift\", 19, \"Languages\"),\n    (\"Monitoring\", 18, \"Practices\"),\n    (\"Redis\", 17, \"Data & AI\"),\n    (\"Networking\", 16, \"Cloud & Infra\"),\n    (\"Kotlin\", 15, \"Languages\"),\n    (\"Automation\", 15, \"Practices\"),\n    (\"PostgreSQL\", 14, \"Data & AI\"),\n    (\"Load Balancing\", 13, \"Cloud & Infra\"),\n    (\"Refactoring\", 13, \"Practices\"),\n    (\"C++\", 12, \"Languages\"),\n    (\"Observability\", 11, \"Practices\"),\n    (\"Caching\", 11, \"Cloud & Infra\"),\n    (\"Neural Networks\", 10, \"Data & AI\"),\n    (\"Deployment\", 10, \"Practices\"),\n    (\"Elixir\", 9, \"Languages\"),\n    (\"Firewall\", 9, \"Cloud & Infra\"),\n]\n\n# Canvas — altair inner-view dims tuned within the library prompt's ±20px\n# allowance to leave more spiral-packing room (prompts/library/altair.md \"Canvas\")\ncanvas_w = 640\ncanvas_h = 340\n\n# Scale frequencies to font sizes\nfrequencies = [freq for _, freq, _ in word_data]\nmin_freq, max_freq = min(frequencies), max(frequencies)\nmin_size, max_size = 9, 32\n\n# Build data with spiral positioning; a handful of lower-frequency words\n# rotate 90° (a classic word-cloud technique) to fill the vertical gaps a\n# purely horizontal layout leaves behind\nwords_list = []\nx_positions = []\ny_positions = []\nfont_sizes = []\ncategories = []\nangles = []\nplaced_boxes = []\n\nsorted_words = sorted(word_data, key=lambda w: w[1], reverse=True)\naspect = canvas_w / canvas_h\n\nfor i, (word, freq, category) in enumerate(sorted_words):\n    size = min_size + (freq - min_freq) / (max_freq - min_freq) * (max_size - min_size)\n    rotate = i >= 3 and i % 4 == 0\n    angle = 90 if rotate else 0\n\n    text_w = len(word) * size * 0.6\n    text_h = size * 1.3\n    box_w, box_h = (text_h, text_w) if rotate else (text_w, text_h)\n    padding = 6\n\n    cx, cy = canvas_w / 2, canvas_h / 2\n    theta = 0.0\n    radius = 0.0\n    found_x, found_y = cx, cy\n    found_box = (cx - box_w / 2, cy - box_h / 2, box_w, box_h)\n\n    for _ in range(8000):\n        # Elliptical spiral matching the inner-view aspect ratio\n        x = cx + radius * aspect * np.cos(theta) - box_w / 2\n        y = cy + radius * np.sin(theta) - box_h / 2\n\n        if 15 < x < canvas_w - box_w - 15 and 15 < y < canvas_h - box_h - 15:\n            box = (x, y, box_w, box_h)\n\n            has_overlap = False\n            for px, py, pw, ph in placed_boxes:\n                if not (\n                    x + box_w + padding < px\n                    or px + pw + padding < x\n                    or y + box_h + padding < py\n                    or py + ph + padding < y\n                ):\n                    has_overlap = True\n                    break\n\n            if not has_overlap:\n                found_x = x + box_w / 2\n                found_y = y + box_h / 2\n                found_box = box\n                break\n\n        theta += 0.22\n        radius += 0.9\n\n    placed_boxes.append(found_box)\n    words_list.append(word)\n    x_positions.append(found_x)\n    y_positions.append(found_y)\n    font_sizes.append(size)\n    categories.append(category)\n    angles.append(angle)\n\n# Assemble data\ndf = pd.DataFrame(\n    {\n        \"word\": words_list,\n        \"x\": x_positions,\n        \"y\": y_positions,\n        \"size\": font_sizes,\n        \"category\": categories,\n        \"angle\": angles,\n    }\n)\ncategory_order = list(CATEGORY_COLORS.keys())\ncategory_range = list(CATEGORY_COLORS.values())\n\n# Title — mandated format, fontsize scaled off the 67-char baseline\ntitle_text = \"wordcloud-basic · python · altair · anyplot.ai\"\ntitle_ratio = 67 / len(title_text) if len(title_text) > 67 else 1.0\ntitle_fontsize = max(11, round(16 * title_ratio))\n\n# Chart — text marks sized by frequency, colored by topic, some rotated\nchart = (\n    alt.Chart(df)\n    .mark_text(fontWeight=\"bold\", align=\"center\", baseline=\"middle\")\n    .encode(\n        x=alt.X(\"x:Q\", scale=alt.Scale(domain=[0, canvas_w]), axis=None),\n        y=alt.Y(\"y:Q\", scale=alt.Scale(domain=[0, canvas_h]), axis=None),\n        text=\"word:N\",\n        size=alt.Size(\"size:Q\", scale=None, legend=None),\n        angle=alt.Angle(\"angle:Q\", scale=None),\n        color=alt.Color(\n            \"category:N\", scale=alt.Scale(domain=category_order, range=category_range), legend=alt.Legend(title=\"Topic\")\n        ),\n        tooltip=[\"word:N\", \"category:N\", alt.Tooltip(\"size:Q\", title=\"Font size (freq-scaled)\")],\n    )\n    .properties(\n        width=canvas_w,\n        height=canvas_h,\n        padding={\"left\": 0, \"right\": 0, \"top\": 0, \"bottom\": 0},\n        background=PAGE_BG,\n        title=alt.Title(title_text, fontSize=title_fontsize, anchor=\"middle\", color=INK),\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0, continuousWidth=canvas_w, continuousHeight=canvas_h)\n    .configure_title(color=INK)\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=10,\n    )\n)\n\n# Save — pad (never crop) up to the exact canonical target (prompts/library/altair.md \"Canvas\")\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\nTARGET_W, TARGET_H = 3200, 1800\nimg = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\nw, h = img.size\nif w > TARGET_W or h > TARGET_H:\n    raise SystemExit(\n        f\"altair vl-convert produced {w}x{h}, exceeds target {TARGET_W}x{TARGET_H}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif w < TARGET_W or h < TARGET_H:\n    canvas = Image.new(\"RGB\", (TARGET_W, TARGET_H), PAGE_BG)\n    canvas.paste(img, ((TARGET_W - w) // 2, (TARGET_H - h) // 2))\n    canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}