{"spec_id":"heatmap-annotated","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nheatmap-annotated: Annotated Heatmap\nLibrary: plotly 6.9.0 | Python 3.13.14\nQuality: 94/100 | Updated: 2026-08-05\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom scipy.cluster.hierarchy import leaves_list, linkage\nfrom scipy.spatial.distance import squareform\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Theme-adaptive chrome tokens (Imprint palette)\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nLIGHT_TEXT = \"#FFFDF6\"  # near-white text for saturated diverging-cmap extremes\n\n# Imprint diverging colormap for signed correlation data (midpoint = page bg)\nIMPRINT_DIV = [[0.0, \"#AE3030\"], [0.5, PAGE_BG], [1.0, \"#4467A3\"]]\n\n# Data: correlation matrix between daily weather-station metrics\nnp.random.seed(42)\n\nmetrics = [\n    \"Temperature\",\n    \"Humidity\",\n    \"Wind Speed\",\n    \"Precipitation\",\n    \"Pressure\",\n    \"UV Index\",\n    \"Cloud Cover\",\n    \"Visibility\",\n]\nn_metrics = len(metrics)\nn_days = 200\n\n# Simulate a seasonal cycle, then derive each metric from physically\n# plausible relationships (not a generic block-correlated matrix), so the\n# correlation structure reflects real weather dependencies.\nt = np.linspace(0, 4 * np.pi, n_days)\ntemperature = 20 + 10 * np.sin(t) + np.random.normal(0, 2, n_days)\nhumidity = 70 - 0.8 * temperature + np.random.normal(0, 8, n_days)\npressure = 1015 - 0.3 * temperature + np.random.normal(0, 4, n_days)\ncloud_cover = np.clip(50 + 0.6 * humidity + np.random.normal(0, 12, n_days), 0, 100)\nprecipitation = np.clip(0.4 * cloud_cover + 0.3 * humidity - 20 + np.random.normal(0, 15, n_days), 0, None)\nwind_speed = np.clip(50 - 0.05 * pressure + np.random.normal(0, 5, n_days), 0, None)\nuv_index = np.clip(9 - 0.07 * cloud_cover + 0.05 * temperature + np.random.normal(0, 1.5, n_days), 0, 11)\nvisibility = np.clip(20 - 0.1 * cloud_cover - 0.05 * precipitation + np.random.normal(0, 2, n_days), 0, 20)\n\ndata = np.column_stack([temperature, humidity, wind_speed, precipitation, pressure, uv_index, cloud_cover, visibility])\ncorrelation_matrix = np.round(np.corrcoef(data.T), 2)\n\n# Hierarchical-clustering reorder: group metrics with similar correlation\n# profiles adjacently (average-linkage on 1 - correlation as distance), so\n# the block structure of related weather metrics reads visually instead of\n# requiring the eye to scan the whole matrix for it.\ndistance = squareform(1 - correlation_matrix, checks=False)\norder = leaves_list(linkage(distance, method=\"average\"))\nmetrics = [metrics[i] for i in order]\ncorrelation_matrix = correlation_matrix[np.ix_(order, order)]\nn_metrics = len(metrics)\n\n# Locate the strongest off-diagonal relationship to give the plot an\n# explicit focal point (outlined cell + subtitle) beyond raw color scanning.\noff_diag = correlation_matrix.copy()\nnp.fill_diagonal(off_diag, 0)\npeak_row, peak_col = np.unravel_index(np.argmax(np.abs(off_diag)), off_diag.shape)\npeak_val = correlation_matrix[peak_row, peak_col]\npeak_relation = \"strongest positive\" if peak_val > 0 else \"strongest negative\"\n\n# Numeric cell coordinates (rather than category strings) give exact 0.5-cell\n# padding for the focal-point outline below; tick labels are remapped to the\n# metric names via tickvals/ticktext.\npositions = list(range(n_metrics))\n\n# Build the heatmap trace directly (rather than figure_factory) for full\n# control over the colorbar, hover template, and per-cell text contrast.\nfig = go.Figure(\n    data=go.Heatmap(\n        z=correlation_matrix,\n        x=positions,\n        y=positions,\n        colorscale=IMPRINT_DIV,\n        zmid=0,\n        zmin=-1,\n        zmax=1,\n        xgap=3,\n        ygap=3,\n        customdata=[[(metrics[col], metrics[row]) for col in range(n_metrics)] for row in range(n_metrics)],\n        hovertemplate=\"%{customdata[0]} vs %{customdata[1]}<br>Correlation: %{z:.2f}<extra></extra>\",\n        colorbar=dict(\n            title=dict(text=\"Correlation\", font=dict(size=14, color=INK)),\n            tickfont=dict(size=11, color=INK_SOFT),\n            outlinewidth=1,\n            outlinecolor=INK_SOFT,\n            thickness=28,\n            len=0.75,\n        ),\n    )\n)\n\n# Per-cell annotations with contrast-aware text color: saturated cells\n# (|corr| > 0.5, close to the diverging cmap's red/blue extremes) get a\n# near-white label; cells close to the theme-matched midpoint get the\n# theme's own ink color. The focal-point cell is additionally bolded.\nfor row in range(n_metrics):\n    for col in range(n_metrics):\n        val = correlation_matrix[row, col]\n        text_color = LIGHT_TEXT if abs(val) > 0.5 else INK\n        is_peak = (row, col) == (peak_row, peak_col)\n        label = f\"<b>{val:.2f}</b>\" if is_peak else f\"{val:.2f}\"\n        fig.add_annotation(\n            x=positions[col], y=positions[row], text=label, showarrow=False, font=dict(size=13, color=text_color)\n        )\n\n# Outline the strongest off-diagonal correlation cell so the plot has an\n# explicit focal point instead of relying on scanning color saturation alone.\nfig.add_shape(\n    type=\"rect\",\n    x0=peak_col - 0.5,\n    x1=peak_col + 0.5,\n    y0=peak_row - 0.5,\n    y1=peak_row + 0.5,\n    line=dict(color=INK, width=2.5),\n    fillcolor=\"rgba(0,0,0,0)\",\n)\n\ntitle = \"Weather Metrics Correlation · heatmap-annotated · python · plotly · anyplot.ai\"\ntitle_fontsize = round(16 * min(1.0, 67 / len(title)))\nsubtitle = f\"Strongest relationship: {metrics[peak_row]} vs {metrics[peak_col]} ({peak_relation}, r = {peak_val:.2f})\"\n\nfig.update_layout(\n    autosize=False,\n    width=600,\n    height=600,\n    margin=dict(l=120, r=110, t=95, b=115),\n    title=dict(\n        text=title,\n        subtitle=dict(text=subtitle, font=dict(size=12, color=INK_SOFT)),\n        font=dict(size=title_fontsize, color=INK),\n        x=0.5,\n        xanchor=\"center\",\n    ),\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font=dict(color=INK, family=\"Arial, sans-serif\"),\n    xaxis=dict(\n        tickmode=\"array\",\n        tickvals=positions,\n        ticktext=metrics,\n        tickfont=dict(size=11, color=INK_SOFT),\n        tickangle=45,\n        side=\"bottom\",\n        showgrid=False,\n        zeroline=False,\n        linecolor=INK_SOFT,\n        scaleanchor=\"y\",\n        constrain=\"domain\",\n    ),\n    yaxis=dict(\n        tickmode=\"array\",\n        tickvals=positions,\n        ticktext=metrics,\n        tickfont=dict(size=11, color=INK_SOFT),\n        autorange=\"reversed\",\n        showgrid=False,\n        zeroline=False,\n        linecolor=INK_SOFT,\n    ),\n)\n\n# Save PNG (square 2400x2400) and interactive HTML\nfig.write_image(f\"plot-{THEME}.png\", width=600, height=600, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}