{"spec_id":"count-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\ncount-basic: Basic Count Plot\nLibrary: plotly 7.1.0 | Python 3.13.15\nQuality: 85/100 | Updated: 2026-09-27\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\n\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\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — ALWAYS first series\n\n# Data - Product category purchases with heavily skewed distribution\nnp.random.seed(42)\ncategories = [\"Electronics\", \"Clothing\", \"Home & Garden\", \"Sports\", \"Books\", \"Toys\", \"Beauty\"]\n# Generate raw purchase data with heavily right-skewed distribution\n# Electronics dominates, others taper off\nprobabilities = [0.40, 0.25, 0.15, 0.10, 0.05, 0.03, 0.02]\nraw_data = np.random.choice(categories, size=250, p=probabilities)\n\n# Count occurrences\nunique, counts = np.unique(raw_data, return_counts=True)\n# Sort by frequency (descending)\nsort_idx = np.argsort(counts)[::-1]\nsorted_categories = unique[sort_idx]\nsorted_counts = counts[sort_idx]\n\n# Each bar's own share of all observations\ntotal = sorted_counts.sum()\npercentages = sorted_counts / total * 100\n\nbar_hover = [\n    f\"{cat}<br>Count: {count}<br>Share: {pct:.1f}%\"\n    for cat, count, pct in zip(sorted_categories, sorted_counts, percentages, strict=True)\n]\nbar_labels = [f\"{count}<br>{pct:.1f}%\" for count, pct in zip(sorted_counts, percentages, strict=True)]\n\n# Title fontsize scales linearly with title length off the 67-char baseline,\n# both up (short titles) and down (long titles), clamped to a legible range\ntitle_text = \"count-basic · python · plotly · anyplot.ai\"\ntitle_fontsize = max(11, min(24, round(16 * 67 / len(title_text))))\n\nfig = go.Figure(\n    go.Bar(\n        x=sorted_categories,\n        y=sorted_counts,\n        marker=dict(color=BRAND, opacity=0.9, line=dict(color=INK_SOFT, width=1.5)),\n        text=bar_labels,\n        textposition=\"outside\",\n        textfont=dict(size=14, color=INK),\n        hovertext=bar_hover,\n        hoverinfo=\"text\",\n    )\n)\n\n# Layout — hard target 3200 x 1800 (see \"Canvas — hard rule\" in prompts/library/plotly.md)\nfig.update_layout(\n    autosize=False,\n    title=dict(text=title_text, font=dict(size=title_fontsize, color=INK), x=0.5, xanchor=\"center\"),\n    xaxis=dict(\n        title=dict(text=\"Product Category\", font=dict(size=14, color=INK)),\n        tickfont=dict(size=12, color=INK_SOFT),\n        showline=True,\n        linecolor=INK_SOFT,\n        zerolinecolor=INK_SOFT,\n    ),\n    yaxis=dict(\n        title=dict(text=\"Count (n)\", font=dict(size=14, color=INK)),\n        tickfont=dict(size=12, color=INK_SOFT),\n        gridcolor=GRID,\n        gridwidth=1,\n        showline=True,\n        linecolor=INK_SOFT,\n        zerolinecolor=INK_SOFT,\n        # Headroom above the tallest bar so the two-line count/share label\n        # (outside text) never clips against the title.\n        range=[0, float(sorted_counts.max()) * 1.22],\n    ),\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    bargap=0.3,\n    showlegend=False,  # single series — a legend would be redundant chrome\n    margin=dict(l=90, r=50, t=100, b=90),\n)\n\n# Save as PNG — hard target 3200 x 1800 (landscape)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\n\n# Save as HTML for interactivity\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}