{"spec_id":"heatmap-periodic-table","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nheatmap-periodic-table: Periodic Table Property Heatmap\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 90/100 | Created: 2026-06-15\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent local seaborn.py from shadowing the installed package\n_script_dir = os.path.dirname(os.path.abspath(__file__))\nfor _p in (_script_dir, \"\", \".\"):\n    if _p in sys.path:\n        sys.path.remove(_p)\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport seaborn as sns\nfrom matplotlib.colors import LinearSegmentedColormap, Normalize\n\n\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\"\nGREY_TILE = \"#C8C7C0\" if THEME == \"light\" else \"#3A3A37\"\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    },\n)\n\n# Data: (symbol, atomic_number, period, group, pauling_electronegativity)\n# Lanthanides/actinides use period=8/9 so they land at grid rows 8/9\n# (gap row 7 creates visual separation from the main body)\nelements = [\n    (\"H\", 1, 1, 1, 2.20),\n    (\"He\", 2, 1, 18, None),\n    (\"Li\", 3, 2, 1, 0.98),\n    (\"Be\", 4, 2, 2, 1.57),\n    (\"B\", 5, 2, 13, 2.04),\n    (\"C\", 6, 2, 14, 2.55),\n    (\"N\", 7, 2, 15, 3.04),\n    (\"O\", 8, 2, 16, 3.44),\n    (\"F\", 9, 2, 17, 3.98),\n    (\"Ne\", 10, 2, 18, None),\n    (\"Na\", 11, 3, 1, 0.93),\n    (\"Mg\", 12, 3, 2, 1.31),\n    (\"Al\", 13, 3, 13, 1.61),\n    (\"Si\", 14, 3, 14, 1.90),\n    (\"P\", 15, 3, 15, 2.19),\n    (\"S\", 16, 3, 16, 2.58),\n    (\"Cl\", 17, 3, 17, 3.16),\n    (\"Ar\", 18, 3, 18, None),\n    (\"K\", 19, 4, 1, 0.82),\n    (\"Ca\", 20, 4, 2, 1.00),\n    (\"Sc\", 21, 4, 3, 1.36),\n    (\"Ti\", 22, 4, 4, 1.54),\n    (\"V\", 23, 4, 5, 1.63),\n    (\"Cr\", 24, 4, 6, 1.66),\n    (\"Mn\", 25, 4, 7, 1.55),\n    (\"Fe\", 26, 4, 8, 1.83),\n    (\"Co\", 27, 4, 9, 1.88),\n    (\"Ni\", 28, 4, 10, 1.91),\n    (\"Cu\", 29, 4, 11, 1.90),\n    (\"Zn\", 30, 4, 12, 1.65),\n    (\"Ga\", 31, 4, 13, 1.81),\n    (\"Ge\", 32, 4, 14, 2.01),\n    (\"As\", 33, 4, 15, 2.18),\n    (\"Se\", 34, 4, 16, 2.55),\n    (\"Br\", 35, 4, 17, 2.96),\n    (\"Kr\", 36, 4, 18, 3.00),\n    (\"Rb\", 37, 5, 1, 0.82),\n    (\"Sr\", 38, 5, 2, 0.95),\n    (\"Y\", 39, 5, 3, 1.22),\n    (\"Zr\", 40, 5, 4, 1.33),\n    (\"Nb\", 41, 5, 5, 1.60),\n    (\"Mo\", 42, 5, 6, 2.16),\n    (\"Tc\", 43, 5, 7, 1.90),\n    (\"Ru\", 44, 5, 8, 2.20),\n    (\"Rh\", 45, 5, 9, 2.28),\n    (\"Pd\", 46, 5, 10, 2.20),\n    (\"Ag\", 47, 5, 11, 1.93),\n    (\"Cd\", 48, 5, 12, 1.69),\n    (\"In\", 49, 5, 13, 1.78),\n    (\"Sn\", 50, 5, 14, 1.96),\n    (\"Sb\", 51, 5, 15, 2.05),\n    (\"Te\", 52, 5, 16, 2.10),\n    (\"I\", 53, 5, 17, 2.66),\n    (\"Xe\", 54, 5, 18, 2.60),\n    (\"Cs\", 55, 6, 1, 0.79),\n    (\"Ba\", 56, 6, 2, 0.89),\n    # period 6, group 3 left empty (lanthanide placeholder added separately)\n    (\"Hf\", 72, 6, 4, 1.30),\n    (\"Ta\", 73, 6, 5, 1.50),\n    (\"W\", 74, 6, 6, 2.36),\n    (\"Re\", 75, 6, 7, 1.90),\n    (\"Os\", 76, 6, 8, 2.20),\n    (\"Ir\", 77, 6, 9, 2.20),\n    (\"Pt\", 78, 6, 10, 2.28),\n    (\"Au\", 79, 6, 11, 2.54),\n    (\"Hg\", 80, 6, 12, 2.00),\n    (\"Tl\", 81, 6, 13, 1.62),\n    (\"Pb\", 82, 6, 14, 2.33),\n    (\"Bi\", 83, 6, 15, 2.02),\n    (\"Po\", 84, 6, 16, 2.00),\n    (\"At\", 85, 6, 17, 2.20),\n    (\"Rn\", 86, 6, 18, None),\n    (\"Fr\", 87, 7, 1, 0.70),\n    (\"Ra\", 88, 7, 2, 0.90),\n    # period 7, group 3 left empty (actinide placeholder added separately)\n    (\"Rf\", 104, 7, 4, None),\n    (\"Db\", 105, 7, 5, None),\n    (\"Sg\", 106, 7, 6, None),\n    (\"Bh\", 107, 7, 7, None),\n    (\"Hs\", 108, 7, 8, None),\n    (\"Mt\", 109, 7, 9, None),\n    (\"Ds\", 110, 7, 10, None),\n    (\"Rg\", 111, 7, 11, None),\n    (\"Cn\", 112, 7, 12, None),\n    (\"Nh\", 113, 7, 13, None),\n    (\"Fl\", 114, 7, 14, None),\n    (\"Mc\", 115, 7, 15, None),\n    (\"Lv\", 116, 7, 16, None),\n    (\"Ts\", 117, 7, 17, None),\n    (\"Og\", 118, 7, 18, None),\n    # Lanthanides (La–Lu) in detached f-block row 8\n    (\"La\", 57, 8, 3, 1.10),\n    (\"Ce\", 58, 8, 4, 1.12),\n    (\"Pr\", 59, 8, 5, 1.13),\n    (\"Nd\", 60, 8, 6, 1.14),\n    (\"Pm\", 61, 8, 7, 1.13),\n    (\"Sm\", 62, 8, 8, 1.17),\n    (\"Eu\", 63, 8, 9, 1.20),\n    (\"Gd\", 64, 8, 10, 1.20),\n    (\"Tb\", 65, 8, 11, 1.10),\n    (\"Dy\", 66, 8, 12, 1.22),\n    (\"Ho\", 67, 8, 13, 1.23),\n    (\"Er\", 68, 8, 14, 1.24),\n    (\"Tm\", 69, 8, 15, 1.25),\n    (\"Yb\", 70, 8, 16, 1.10),\n    (\"Lu\", 71, 8, 17, 1.27),\n    # Actinides (Ac–Lr) in detached f-block row 9\n    (\"Ac\", 89, 9, 3, 1.10),\n    (\"Th\", 90, 9, 4, 1.30),\n    (\"Pa\", 91, 9, 5, 1.50),\n    (\"U\", 92, 9, 6, 1.38),\n    (\"Np\", 93, 9, 7, 1.36),\n    (\"Pu\", 94, 9, 8, 1.28),\n    (\"Am\", 95, 9, 9, 1.30),\n    (\"Cm\", 96, 9, 10, 1.30),\n    (\"Bk\", 97, 9, 11, 1.30),\n    (\"Cf\", 98, 9, 12, 1.30),\n    (\"Es\", 99, 9, 13, 1.30),\n    (\"Fm\", 100, 9, 14, 1.30),\n    (\"Md\", 101, 9, 15, 1.30),\n    (\"No\", 102, 9, 16, 1.30),\n    (\"Lr\", 103, 9, 17, None),\n]\n\n# Build 10×18 grid: periods 1–7 → rows 0–6; row 7 = gap; rows 8–9 = f-block\nn_rows, n_cols = 10, 18\ngrid_values = np.full((n_rows, n_cols), np.nan)\ncell_info = {}\n\nfor sym, anum, period, group, en in elements:\n    row = period - 1 if period <= 7 else period\n    col = group - 1\n    cell_info[(row, col)] = (sym, anum, en)\n    if en is not None:\n        grid_values[row, col] = en\n\n# Placeholder tiles at group 3 for periods 6 and 7 (f-block reference)\ncell_info[(5, 2)] = (\"*\", None, None)\ncell_info[(6, 2)] = (\"**\", None, None)\n\n# Imprint sequential colormap: green (#009E73) → blue (#4467A3)\nimprint_seq = LinearSegmentedColormap.from_list(\"imprint_seq\", [\"#009E73\", \"#4467A3\"])\nnorm = Normalize(vmin=0.7, vmax=4.0)\n\n# Build seaborn annot array: symbols for EN-valued cells (seaborn handles coloring via annot)\nsymbol_grid = np.full((n_rows, n_cols), \"\", dtype=object)\nfor (row, col), (sym, _anum, en) in cell_info.items():\n    if en is not None:\n        symbol_grid[row, col] = sym\n\n# Pre-compute WCAG luminance-based text colors for all EN-valued cells\nlum_map = {}\nfor (row, col), (_sym, _anum, en) in cell_info.items():\n    if en is not None:\n        rgba = imprint_seq(norm(en))\n        r, g, b = rgba[0], rgba[1], rgba[2]\n        r_l = r / 12.92 if r <= 0.04045 else ((r + 0.055) / 1.055) ** 2.4\n        g_l = g / 12.92 if g <= 0.04045 else ((g + 0.055) / 1.055) ** 2.4\n        b_l = b / 12.92 if b <= 0.04045 else ((b + 0.055) / 1.055) ** 2.4\n        lum = 0.2126 * r_l + 0.7152 * g_l + 0.0722 * b_l\n        lum_map[(row, col)] = \"#1A1A17\" if lum > 0.20 else \"#F0EFE8\"\n\n# Canvas: landscape 3200×1800 (periodic table is 18:10, not square)\ntitle = \"Electronegativity Trends · heatmap-periodic-table · python · seaborn · anyplot.ai\"\ntitle_fs = max(8, round(12 * 67 / len(title)))\n\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Plot — use seaborn annot parameter with custom symbol array (idiomatic seaborn annotation)\nsns.heatmap(\n    grid_values,\n    ax=ax,\n    cmap=imprint_seq,\n    mask=np.isnan(grid_values),\n    linewidths=0.5,\n    linecolor=PAGE_BG,\n    vmin=0.7,\n    vmax=4.0,\n    cbar=True,\n    cbar_kws={\"shrink\": 0.55, \"pad\": 0.02, \"aspect\": 18},\n    annot=symbol_grid,\n    fmt=\"\",\n    annot_kws={\"fontsize\": 5.0, \"fontweight\": \"bold\"},\n    xticklabels=False,\n    yticklabels=False,\n)\n\n# Update text colors for seaborn-generated symbol annotations via WCAG luminance\nfor txt_obj in ax.texts:\n    x, y = txt_obj.get_position()\n    row, col = int(y), int(x)\n    txt_obj.set_color(lum_map.get((row, col), INK_SOFT))\n\n# Style: grey tiles for elements with no defined electronegativity (noble gases, synthetic)\nfor (row, col), (_sym, _anum, en) in cell_info.items():\n    if en is None:\n        rect = plt.Rectangle((col, row), 1, 1, facecolor=GREY_TILE, edgecolor=PAGE_BG, linewidth=0.5, zorder=2)\n        ax.add_patch(rect)\n\n# Text annotations: atomic numbers (all cells) + symbols for masked cells\nfor (row, col), (sym, anum, en) in cell_info.items():\n    txt_col = lum_map.get((row, col), INK_SOFT)\n\n    # Atomic number top-left corner — increased to 3.5pt for readability\n    if anum is not None:\n        ax.text(col + 0.08, row + 0.15, str(anum), ha=\"left\", va=\"top\", fontsize=3.5, color=txt_col, zorder=3)\n\n    # Symbol for masked cells (seaborn annot skips masked/NaN cells)\n    if en is None:\n        sym_fs = 5.0 if sym not in (\"*\", \"**\") else 3.8\n        ax.text(\n            col + 0.5,\n            row + 0.5,\n            sym,\n            ha=\"center\",\n            va=\"center\",\n            fontsize=sym_fs,\n            color=txt_col,\n            fontweight=\"bold\",\n            zorder=3,\n        )\n\n# EN value labels for max (F: 3.98) and min (Cs: 0.79, Fr: 0.70) — trend storytelling\nfor ann_row, ann_col, ann_val in [(1, 16, \"3.98\"), (5, 0, \"0.79\"), (6, 0, \"0.70\")]:\n    txt_col = lum_map.get((ann_row, ann_col), INK)\n    ax.text(ann_col + 0.5, ann_row + 0.76, ann_val, ha=\"center\", va=\"center\", fontsize=2.5, color=txt_col, zorder=4)\n\n# Lanthanides / Actinides group labels in the empty left columns of f-block rows\nax.text(1.0, 8.5, \"Lanthanides\", ha=\"center\", va=\"center\", fontsize=2.8, color=INK_SOFT, style=\"italic\", zorder=3)\nax.text(1.0, 9.5, \"Actinides\", ha=\"center\", va=\"center\", fontsize=2.8, color=INK_SOFT, style=\"italic\", zorder=3)\n\n# Colorbar styling\ncbar = ax.collections[0].colorbar\ncbar.set_label(\"Electronegativity (Pauling scale)\", color=INK, fontsize=7, labelpad=5)\ncbar.ax.tick_params(colors=INK_SOFT, labelsize=6)\nfor spine in cbar.ax.spines.values():\n    spine.set_edgecolor(INK_SOFT)\n\n# Axes cleanup\nfor spine in ax.spines.values():\n    spine.set_visible(False)\nax.set_xlabel(\"\")\nax.set_ylabel(\"\")\nax.set_title(title, fontsize=title_fs, color=INK, fontweight=\"medium\", pad=8)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}