{"spec_id":"scatter-ashby-material","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nscatter-ashby-material: Ashby Material Selection Chart\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove the script's own directory from sys.path so that matplotlib.py (a sibling\n# implementation file) does not shadow the installed matplotlib package.\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _this_dir]\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\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\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — canonical order, 7 positions for 7 material families\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\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 — density (kg/m³) vs Young's modulus (GPa) for common engineering materials\nnp.random.seed(42)\n\nfamilies = {\n    \"Metals\": {\n        \"density\": (2700, 8900),\n        \"modulus\": (45, 400),\n        \"n\": 25,\n        \"materials\": [\n            (2700, 70),\n            (4500, 115),\n            (7800, 200),\n            (7200, 210),\n            (8900, 130),\n            (8500, 120),\n            (7900, 193),\n            (4400, 110),\n            (2800, 73),\n            (7100, 195),\n        ],\n    },\n    \"Polymers\": {\n        \"density\": (900, 1500),\n        \"modulus\": (0.2, 4.0),\n        \"n\": 20,\n        \"materials\": [\n            (950, 0.8),\n            (1050, 2.5),\n            (1200, 3.0),\n            (1400, 3.5),\n            (1140, 2.4),\n            (900, 1.3),\n            (1300, 2.8),\n            (1070, 2.0),\n        ],\n    },\n    \"Ceramics\": {\n        \"density\": (2200, 6000),\n        \"modulus\": (70, 450),\n        \"n\": 18,\n        \"materials\": [\n            (3980, 380),\n            (2200, 70),\n            (3200, 310),\n            (5700, 200),\n            (2500, 90),\n            (3900, 350),\n            (5000, 170),\n            (2650, 95),\n        ],\n    },\n    \"Composites\": {\n        \"density\": (1400, 2200),\n        \"modulus\": (15, 200),\n        \"n\": 15,\n        \"materials\": [\n            (1600, 140),\n            (1900, 45),\n            (1500, 180),\n            (2000, 30),\n            (1550, 70),\n            (1800, 50),\n            (1450, 120),\n            (1700, 60),\n        ],\n    },\n    \"Elastomers\": {\n        \"density\": (900, 1300),\n        \"modulus\": (0.002, 0.1),\n        \"n\": 12,\n        \"materials\": [\n            (920, 0.005),\n            (1100, 0.01),\n            (1200, 0.05),\n            (1000, 0.003),\n            (1050, 0.02),\n            (960, 0.008),\n            (1150, 0.04),\n            (1250, 0.08),\n        ],\n    },\n    \"Foams\": {\n        \"density\": (25, 300),\n        \"modulus\": (0.001, 0.3),\n        \"n\": 14,\n        \"materials\": [\n            (30, 0.001),\n            (60, 0.01),\n            (120, 0.05),\n            (200, 0.2),\n            (50, 0.005),\n            (100, 0.03),\n            (250, 0.25),\n            (150, 0.08),\n        ],\n    },\n    \"Natural Materials\": {\n        \"density\": (150, 1300),\n        \"modulus\": (0.1, 20),\n        \"n\": 12,\n        \"materials\": [(500, 12), (700, 14), (400, 8), (200, 1.0), (600, 10), (1200, 18), (350, 5), (800, 15)],\n    },\n}\n\nrows = []\nfor family, props in families.items():\n    for d, m in props[\"materials\"]:\n        rows.append({\"family\": family, \"density\": d, \"modulus\": m})\n    extra_n = props[\"n\"] - len(props[\"materials\"])\n    if extra_n > 0:\n        log_d_min, log_d_max = np.log10(props[\"density\"][0]), np.log10(props[\"density\"][1])\n        log_m_min, log_m_max = np.log10(props[\"modulus\"][0]), np.log10(props[\"modulus\"][1])\n        extra_d = 10 ** np.random.uniform(log_d_min, log_d_max, extra_n)\n        extra_m = 10 ** np.random.uniform(log_m_min, log_m_max, extra_n)\n        for d, m in zip(extra_d, extra_m, strict=True):\n            rows.append({\"family\": family, \"density\": d, \"modulus\": m})\n\ndf = pd.DataFrame(rows)\ndf[\"log_density\"] = np.log10(df[\"density\"])\ndf[\"log_modulus\"] = np.log10(df[\"modulus\"])\n\nfamily_order = list(families.keys())\npalette = dict(zip(family_order, IMPRINT_PALETTE, strict=False))\n\n# Canvas — landscape 3200×1800 px (figsize=(8, 4.5) × dpi=400, no bbox_inches=\"tight\")\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# KDE envelopes — seaborn's idiomatic family-region feature (operates in log-space)\nsns.kdeplot(\n    data=df,\n    x=\"log_density\",\n    y=\"log_modulus\",\n    hue=\"family\",\n    hue_order=family_order,\n    palette=palette,\n    levels=2,\n    thresh=0.3,\n    fill=True,\n    alpha=0.12,\n    linewidths=0,\n    ax=ax,\n    legend=False,\n    zorder=1,\n    common_norm=False,\n)\n\n# Scatter points\nsns.scatterplot(\n    data=df,\n    x=\"log_density\",\n    y=\"log_modulus\",\n    hue=\"family\",\n    hue_order=family_order,\n    palette=palette,\n    style=\"family\",\n    s=60,\n    alpha=0.75,\n    edgecolor=PAGE_BG,\n    linewidth=0.4,\n    ax=ax,\n    legend=False,\n    zorder=3,\n)\n\n# Performance index guide lines E/ρ = const — ratios 0.01, 1, 100 (Ashby chart signature)\nlog_d_range = np.linspace(np.log10(10), np.log10(20000), 200)\nguide_indices = [(0.01, \"E/ρ=0.01\"), (1.0, \"E/ρ=1\"), (100.0, \"E/ρ=100\")]\nfor ratio, label in guide_indices:\n    log_m_line = np.log10(ratio) + log_d_range\n    mask = (log_m_line >= np.log10(5e-4)) & (log_m_line <= np.log10(1000))\n    if mask.sum() > 0:\n        ax.plot(\n            log_d_range[mask], log_m_line[mask], color=INK_MUTED, linewidth=0.7, linestyle=\"--\", alpha=0.5, zorder=0\n        )\n        vis_d = log_d_range[mask]\n        vis_m = log_m_line[mask]\n        if len(vis_d) > 10:\n            idx = int(len(vis_d) * 0.12)\n            ax.text(\n                vis_d[idx],\n                vis_m[idx] + 0.1,\n                label,\n                fontsize=6,\n                color=INK_MUTED,\n                fontstyle=\"italic\",\n                rotation=30,\n                ha=\"center\",\n                va=\"bottom\",\n                zorder=0,\n            )\n\n# Lightweight & stiff directional annotation\nax.annotate(\n    \"Lightweight\\n& Stiff ↗\",\n    xy=(np.log10(200), np.log10(80)),\n    fontsize=6,\n    fontstyle=\"italic\",\n    color=INK_SOFT,\n    ha=\"center\",\n    va=\"center\",\n    zorder=4,\n    bbox={\n        \"boxstyle\": \"round,pad=0.4\",\n        \"facecolor\": ELEVATED_BG,\n        \"alpha\": 0.85,\n        \"edgecolor\": INK_SOFT,\n        \"linewidth\": 0.5,\n    },\n)\n\n# Family labels near cluster centroids\nlabel_offsets = {\n    \"Metals\": (0.3, 0.35),\n    \"Ceramics\": (-0.4, 0.35),\n    \"Composites\": (-0.35, -0.35),\n    \"Polymers\": (0.2, 0.0),\n    \"Elastomers\": (0.0, 0.0),\n    \"Foams\": (0.0, 0.0),\n    \"Natural Materials\": (-0.2, 0.2),\n}\n\nfor family in family_order:\n    subset = df[df[\"family\"] == family]\n    color = palette[family]\n    centroid_log_d = subset[\"log_density\"].mean()\n    centroid_log_m = subset[\"log_modulus\"].mean()\n    offset = label_offsets.get(family, (0, 0))\n    label_log_d = centroid_log_d + offset[0]\n    label_log_m = centroid_log_m + offset[1]\n    ax.annotate(\n        family,\n        xy=(centroid_log_d, centroid_log_m),\n        xytext=(label_log_d, label_log_m),\n        fontsize=7,\n        fontweight=\"bold\",\n        color=color,\n        ha=\"center\",\n        va=\"center\",\n        zorder=5,\n        bbox={\n            \"boxstyle\": \"round,pad=0.3\",\n            \"facecolor\": ELEVATED_BG,\n            \"alpha\": 0.9,\n            \"edgecolor\": color,\n            \"linewidth\": 0.5,\n        },\n        arrowprops={\"arrowstyle\": \"-\", \"color\": color, \"alpha\": 0.5, \"linewidth\": 0.8} if offset != (0, 0) else None,\n    )\n\n# Custom tick labels showing real values on log-transformed axes\ndensity_ticks = [10, 100, 1000, 10000]\nmodulus_ticks = [0.001, 0.01, 0.1, 1, 10, 100]\nax.set_xticks([np.log10(v) for v in density_ticks])\nax.set_xticklabels([str(v) for v in density_ticks])\nax.set_yticks([np.log10(v) for v in modulus_ticks])\nax.set_yticklabels([str(v) for v in modulus_ticks])\n\nax.set_xlabel(\"Density (kg/m³)\", fontsize=10, color=INK)\nax.set_ylabel(\"Young's Modulus (GPa)\", fontsize=10, color=INK)\nax.set_title(\"scatter-ashby-material · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\n\nax.yaxis.grid(True, alpha=0.15, linewidth=0.6, color=INK)\nax.xaxis.grid(True, alpha=0.15, linewidth=0.6, color=INK)\n\nsns.despine(ax=ax)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}