{"spec_id":"scatter-ashby-material","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nscatter-ashby-material: Ashby Material Selection Chart\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_abline,\n    geom_point,\n    geom_polygon,\n    geom_segment,\n    geom_text,\n    ggplot,\n    ggsave,\n    ggsize,\n    guides,\n    labs,\n    layer_tooltips,\n    scale_color_manual,\n    scale_fill_manual,\n    scale_shape_manual,\n    scale_x_log10,\n    scale_y_log10,\n    theme,\n    theme_minimal,\n)\n\n\nLetsPlot.setup_html()\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID_COLOR = \"#D8D7D0\" if THEME == \"light\" else \"#3A3A36\"\nFILL_ALPHA = 0.22 if THEME == \"light\" else 0.30\n\n# Imprint palette — 7 canonical positions for 7 material families (insertion order: Metals first)\nIMPRINT_7 = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\n# Data — Density (kg/m^3) vs Young's modulus (GPa) for common engineering materials\nnp.random.seed(42)\n\nfamilies = {\n    \"Metals\": {\n        \"density\": [2700, 4500, 7800, 7900, 8900, 8400, 11340, 7200, 19300, 7300],\n        \"modulus\": [69, 116, 200, 193, 117, 200, 16, 170, 79, 105],\n        \"names\": [\n            \"Aluminum\",\n            \"Titanium\",\n            \"Steel\",\n            \"Stainless Steel\",\n            \"Copper\",\n            \"Nickel Alloy\",\n            \"Lead\",\n            \"Cast Iron\",\n            \"Gold\",\n            \"Tin Alloy\",\n        ],\n    },\n    \"Polymers\": {\n        \"density\": [950, 1400, 1200, 1050, 1300, 1150, 1420, 1780],\n        \"modulus\": [0.9, 2.8, 3.5, 2.3, 3.0, 1.3, 3.3, 3.9],\n        \"names\": [\"Polyethylene\", \"PVC\", \"Nylon\", \"Polypropylene\", \"PET\", \"Polystyrene\", \"Acetal\", \"PEEK\"],\n    },\n    \"Ceramics\": {\n        \"density\": [3980, 3200, 2200, 5680, 2500, 3100, 6000, 3900],\n        \"modulus\": [380, 310, 70, 210, 65, 270, 200, 350],\n        \"names\": [\n            \"Alumina\",\n            \"Silicon Nitride\",\n            \"Glass\",\n            \"Zirconia\",\n            \"Porcelain\",\n            \"Silicon Carbide\",\n            \"Tungsten Carbide\",\n            \"Sapphire\",\n        ],\n    },\n    \"Composites\": {\n        \"density\": [1600, 2000, 1500, 1800, 1550, 1400, 1700, 1900],\n        \"modulus\": [140, 45, 70, 25, 60, 30, 90, 50],\n        \"names\": [\n            \"CFRP\",\n            \"GFRP\",\n            \"Aramid/Epoxy\",\n            \"Sheet Molding\",\n            \"Carbon/PEEK\",\n            \"Flax/Epoxy\",\n            \"Boron/Epoxy\",\n            \"Glass/Vinyl Ester\",\n        ],\n    },\n    \"Elastomers\": {\n        \"density\": [920, 1250, 1100, 1500, 1050, 1150, 1300],\n        \"modulus\": [0.005, 0.01, 0.003, 0.02, 0.002, 0.008, 0.015],\n        \"names\": [\"Natural Rubber\", \"Neoprene\", \"Silicone\", \"Fluorocarbon\", \"Butyl Rubber\", \"EPDM\", \"Polyurethane\"],\n    },\n    \"Foams\": {\n        \"density\": [30, 60, 120, 200, 45, 100, 160],\n        \"modulus\": [0.001, 0.01, 0.05, 0.2, 0.005, 0.03, 0.1],\n        \"names\": [\n            \"Polyurethane Foam\",\n            \"Polystyrene Foam\",\n            \"PVC Foam\",\n            \"Metallic Foam\",\n            \"PE Foam\",\n            \"Phenolic Foam\",\n            \"Syntactic Foam\",\n        ],\n    },\n    \"Natural Materials\": {\n        \"density\": [600, 700, 500, 1500, 1000, 800, 650],\n        \"modulus\": [12, 14, 9, 30, 5, 10, 11],\n        \"names\": [\"Oak\", \"Maple\", \"Balsa\", \"Bone\", \"Cork\", \"Bamboo\", \"Pine\"],\n    },\n}\n\n# Build dataframe with small scatter noise for visual spread\nrows = []\nfor family, props in families.items():\n    for d, m, name in zip(props[\"density\"], props[\"modulus\"], props[\"names\"], strict=True):\n        noise_d = np.exp(np.random.normal(0, 0.05))\n        noise_m = np.exp(np.random.normal(0, 0.05))\n        rows.append({\"material\": name, \"family\": family, \"density\": d * noise_d, \"modulus\": m * noise_m})\n\ndf = pd.DataFrame(rows)\n\n# Elliptical envelopes per family (eigendecomposition in log space)\nenvelope_rows = []\nn_pts = 80\ntheta = np.linspace(0, 2 * np.pi, n_pts, endpoint=False)\n# Tighter scales for Ceramics/Composites to reduce their overlap\nenvelope_scales = {\"Foams\": 1.8, \"Elastomers\": 2.2, \"Ceramics\": 1.9, \"Composites\": 1.9}\ndefault_scale = 2.4\nfor family in df[\"family\"].unique():\n    sub = df[df[\"family\"] == family]\n    log_x = np.log10(sub[\"density\"].values)\n    log_y = np.log10(sub[\"modulus\"].values)\n    cx, cy = log_x.mean(), log_y.mean()\n    dx, dy = log_x - cx, log_y - cy\n    cov = np.array([[np.mean(dx * dx), np.mean(dx * dy)], [np.mean(dx * dy), np.mean(dy * dy)]])\n    eigvals, eigvecs = np.linalg.eigh(cov)\n    scale = envelope_scales.get(family, default_scale)\n    for t in theta:\n        pt = eigvecs @ (np.sqrt(np.maximum(eigvals, 0.01)) * scale * np.array([np.cos(t), np.sin(t)]))\n        envelope_rows.append({\"family\": family, \"density\": 10 ** (cx + pt[0]), \"modulus\": 10 ** (cy + pt[1])})\n\ndf_envelopes = pd.DataFrame(envelope_rows)\n\n# Label positions with manual offsets to avoid crowding\nlabel_offsets = {\n    \"Metals\": (0.15, 0.2),\n    \"Ceramics\": (-0.3, 0.45),\n    \"Composites\": (0.35, 0.15),\n    \"Polymers\": (0.3, -0.25),\n    \"Elastomers\": (0.0, -0.15),\n    \"Foams\": (-0.15, 0.15),\n    \"Natural Materials\": (-0.35, -0.15),\n}\nlabel_rows = []\nfor family in df[\"family\"].unique():\n    sub = df[df[\"family\"] == family]\n    log_cx = np.log10(sub[\"density\"]).mean()\n    log_cy = np.log10(sub[\"modulus\"]).mean()\n    off_x, off_y = label_offsets.get(family, (0, 0))\n    label_rows.append({\"family\": family, \"density\": 10 ** (log_cx + off_x), \"modulus\": 10 ** (log_cy + off_y)})\n\ndf_labels = pd.DataFrame(label_rows)\n\n# Leader lines connecting displaced labels to their envelope centres\nleader_rows = []\nfor family in df[\"family\"].unique():\n    sub = df[df[\"family\"] == family]\n    log_cx = np.log10(sub[\"density\"]).mean()\n    log_cy = np.log10(sub[\"modulus\"]).mean()\n    off_x, off_y = label_offsets.get(family, (0, 0))\n    if abs(off_x) > 0.2 or abs(off_y) > 0.2:\n        leader_rows.append(\n            {\"x\": 10**log_cx, \"y\": 10**log_cy, \"xend\": 10 ** (log_cx + off_x), \"yend\": 10 ** (log_cy + off_y)}\n        )\n\ndf_leaders = pd.DataFrame(leader_rows)\n\n# Performance index guide line annotation positions\nguide_df = pd.DataFrame(\n    {\"density\": [35, 35, 35], \"modulus\": [0.035, 0.35, 3.5], \"label\": [\"E/ρ = 10⁻³\", \"E/ρ = 10⁻²\", \"E/ρ = 10⁻¹\"]}\n)\n\ntitle = \"scatter-ashby-material · python · letsplot · anyplot.ai\"\n\n# Plot\nplot = (\n    ggplot()\n    # Performance index guide lines (E/rho = constant, slope=1 on log-log axes)\n    + geom_abline(intercept=np.log10(0.001), slope=1, color=INK_MUTED, size=0.4, linetype=\"dashed\")\n    + geom_abline(intercept=np.log10(0.01), slope=1, color=INK_MUTED, size=0.4, linetype=\"dashed\")\n    + geom_abline(intercept=np.log10(0.1), slope=1, color=INK_MUTED, size=0.4, linetype=\"dashed\")\n    # Guide line labels (same muted color, angled to follow the slope)\n    + geom_text(\n        data=guide_df, mapping=aes(x=\"density\", y=\"modulus\", label=\"label\"), size=5, color=INK_MUTED, angle=38, hjust=0\n    )\n    # Elliptical envelopes per family\n    + geom_polygon(data=df_envelopes, mapping=aes(x=\"density\", y=\"modulus\", fill=\"family\"), alpha=FILL_ALPHA)\n    # Individual material data points with interactive tooltips; shape encodes family for CVD safety\n    + geom_point(\n        data=df,\n        mapping=aes(x=\"density\", y=\"modulus\", color=\"family\", shape=\"family\"),\n        size=3.5,\n        alpha=0.88,\n        tooltips=layer_tooltips()\n        .format(\"density\", \".0f\")\n        .format(\"modulus\", \".3g\")\n        .title(\"@material\")\n        .line(\"Family|@family\")\n        .line(\"Density|@{density} kg/m³\")\n        .line(\"Modulus|@{modulus} GPa\")\n        .min_width(180),\n    )\n    # Dotted leader lines from envelope centre to displaced labels\n    + geom_segment(\n        data=df_leaders,\n        mapping=aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"),\n        color=INK_MUTED,\n        size=0.4,\n        linetype=\"dotted\",\n    )\n    # Bold family name labels\n    + geom_text(\n        data=df_labels,\n        mapping=aes(x=\"density\", y=\"modulus\", label=\"family\"),\n        size=5,\n        color=INK,\n        fontface=\"bold\",\n        label_padding=0.3,\n    )\n    + scale_x_log10(name=\"Density (kg/m³)\")\n    + scale_y_log10(name=\"Young’s Modulus (GPa)\")\n    + scale_color_manual(values=IMPRINT_7, name=\"Material Family\")\n    + scale_fill_manual(values=IMPRINT_7)\n    + scale_shape_manual(values=[16, 17, 15, 3, 8, 5, 4], name=\"Material Family\")\n    + guides(fill=\"none\")\n    + labs(title=title, subtitle=\"Young’s Modulus vs Density — Material Selection Landscape\")\n    + theme_minimal()\n    + theme(\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n        plot_subtitle=element_text(size=11, color=INK_SOFT),\n        plot_margin=[30, 40, 20, 20],\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        legend_title=element_text(size=10, face=\"bold\", color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        panel_grid_major=element_line(size=0.25, color=GRID_COLOR),\n        panel_grid_minor=element_blank(),\n        legend_position=\"right\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=\"transparent\"),\n        axis_line=element_line(color=INK_SOFT, size=0.3),\n    )\n    + ggsize(800, 450)\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}