{"spec_id":"heatmap-rainflow","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nheatmap-rainflow: Rainflow Counting Matrix for Fatigue Analysis\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport seaborn as sns\nfrom matplotlib.colors import LinearSegmentedColormap, LogNorm\n\n\n# Theme tokens\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 continuous colormap — sequential (brand green → blue)\nimprint_seq = LinearSegmentedColormap.from_list(\"imprint_seq\", [\"#009E73\", \"#4467A3\"])\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data — simulated rainflow counting matrix for a steel shaft under variable-amplitude loading\nnp.random.seed(42)\n\nn_amp_bins = 20\nn_mean_bins = 20\namplitude_edges = np.linspace(5, 200, n_amp_bins + 1)\nmean_edges = np.linspace(-50, 250, n_mean_bins + 1)\namplitude_centers = (amplitude_edges[:-1] + amplitude_edges[1:]) / 2\nmean_centers = (mean_edges[:-1] + mean_edges[1:]) / 2\n\n# Realistic rainflow distribution: exponential decay with amplitude, Gaussian peak in mean\namp_idx, mean_idx = np.meshgrid(np.arange(n_amp_bins), np.arange(n_mean_bins), indexing=\"ij\")\ncycle_density = np.exp(-0.18 * amp_idx) * np.exp(-0.008 * (mean_idx - 10) ** 2)\ncycle_counts = cycle_density * 4000 + np.random.exponential(30, cycle_density.shape)\ncycle_counts = np.round(cycle_counts).astype(int)\n\n# Add sparsity at high amplitudes (realistic: rare high-stress cycles)\ncycle_counts[14:, :] = np.where(np.random.rand(n_amp_bins - 14, n_mean_bins) > 0.35, 0, cycle_counts[14:, :])\ncycle_counts[cycle_counts < 3] = 0\n\n# Bin labels (every other bin for readability)\namp_labels = [f\"{v:.0f}\" if i % 2 == 0 else \"\" for i, v in enumerate(amplitude_centers)]\nmean_labels = [f\"{v:.0f}\" if i % 2 == 0 else \"\" for i, v in enumerate(mean_centers)]\n\n# Plot — square canvas for symmetric 2D heatmap: 2400×2400 px\nfig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nmask = cycle_counts == 0\nsns.heatmap(\n    cycle_counts,\n    mask=mask,\n    norm=LogNorm(vmin=1, vmax=cycle_counts.max()),\n    cmap=imprint_seq,\n    xticklabels=mean_labels,\n    yticklabels=amp_labels,\n    linewidths=0.3,\n    linecolor=INK_SOFT,\n    cbar_kws={\"shrink\": 0.82},\n    ax=ax,\n)\n\n# Invert y-axis so amplitudes increase upward (standard engineering convention)\nax.invert_yaxis()\n\n# Zero-count bins show the page background\nax.set_facecolor(PAGE_BG)\n\n# Style\ntitle = \"heatmap-rainflow · python · seaborn · anyplot.ai\"\nax.set_xlabel(\"Mean Stress (MPa)\", fontsize=10, labelpad=12, color=INK)\nax.set_ylabel(\"Stress Amplitude (MPa)\", fontsize=10, labelpad=12, color=INK)\nax.set_title(title, fontsize=12, fontweight=\"medium\", pad=16, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\n\n# Colorbar styling\ncbar = ax.collections[0].colorbar\ncbar.ax.tick_params(labelsize=8, colors=INK_SOFT)\ncbar.set_label(\"Cycle Count (log scale)\", fontsize=10, labelpad=12, color=INK)\n\n# Annotate peak count region — creates a clear focal point for data storytelling\npeak_idx = np.unravel_index(cycle_counts.argmax(), cycle_counts.shape)\npeak_val = cycle_counts[peak_idx]\nax.annotate(\n    f\"Peak: {peak_val:,} cycles\",\n    xy=(peak_idx[1] + 0.5, peak_idx[0] + 0.5),\n    xytext=(peak_idx[1] + 4, peak_idx[0] + 4.5),\n    fontsize=8,\n    fontweight=\"semibold\",\n    color=INK,\n    bbox={\"boxstyle\": \"round,pad=0.3\", \"facecolor\": ELEVATED_BG, \"edgecolor\": INK_SOFT, \"alpha\": 0.9},\n    arrowprops={\"arrowstyle\": \"-|>\", \"color\": INK_SOFT, \"lw\": 1.5, \"connectionstyle\": \"arc3,rad=-0.15\"},\n    zorder=10,\n)\n\n# Remove spines\nsns.despine(ax=ax, left=True, bottom=True)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}