{"spec_id":"scatter-ashby-material","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nscatter-ashby-material: Ashby Material Selection Chart\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom scipy.spatial import ConvexHull\n\n\n# Theme tokens (Imprint palette — see prompts/default-style-guide.md)\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\nMINOR_GRID = \"rgba(26,26,23,0.07)\" if THEME == \"light\" else \"rgba(240,239,232,0.07)\"\nGUIDE_LINE = \"rgba(26,26,23,0.20)\" if THEME == \"light\" else \"rgba(240,239,232,0.20)\"\n\n# Imprint categorical palette — canonical order, 7 families\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\n# Data — Density (kg/m³) vs Young's Modulus (GPa) for material families\nnp.random.seed(42)\n\nfamilies = {\n    \"Metals\": {\n        \"density\": (5000, 11000),\n        \"modulus\": (50, 220),\n        \"n\": 30,\n        \"color\": IMPRINT_PALETTE[0],\n        \"corr\": 0.4,\n        \"symbol\": \"circle\",\n    },\n    \"Ceramics\": {\n        \"density\": (2200, 4000),\n        \"modulus\": (180, 500),\n        \"n\": 25,\n        \"color\": IMPRINT_PALETTE[1],\n        \"corr\": 0.3,\n        \"symbol\": \"square\",\n    },\n    \"Polymers\": {\n        \"density\": (900, 1500),\n        \"modulus\": (0.2, 4),\n        \"n\": 25,\n        \"color\": IMPRINT_PALETTE[2],\n        \"corr\": 0.5,\n        \"symbol\": \"diamond\",\n    },\n    \"Composites\": {\n        \"density\": (1400, 2200),\n        \"modulus\": (10, 180),\n        \"n\": 22,\n        \"color\": IMPRINT_PALETTE[3],\n        \"corr\": 0.6,\n        \"symbol\": \"triangle-up\",\n    },\n    \"Elastomers\": {\n        \"density\": (700, 1500),\n        \"modulus\": (0.001, 0.1),\n        \"n\": 20,\n        \"color\": IMPRINT_PALETTE[4],\n        \"corr\": 0.3,\n        \"symbol\": \"cross\",\n    },\n    \"Foams\": {\n        \"density\": (20, 300),\n        \"modulus\": (0.001, 1),\n        \"n\": 20,\n        \"color\": IMPRINT_PALETTE[5],\n        \"corr\": 0.7,\n        \"symbol\": \"star\",\n    },\n    \"Natural Materials\": {\n        \"density\": (150, 1300),\n        \"modulus\": (0.5, 20),\n        \"n\": 18,\n        \"color\": IMPRINT_PALETTE[6],\n        \"corr\": 0.5,\n        \"symbol\": \"x\",\n    },\n}\n\n# Generate log-uniform data with realistic intra-family correlations\ndata = {}\nfor family, props in families.items():\n    log_d_min = np.log10(props[\"density\"][0])\n    log_d_max = np.log10(props[\"density\"][1])\n    log_m_min = np.log10(props[\"modulus\"][0])\n    log_m_max = np.log10(props[\"modulus\"][1])\n    n = props[\"n\"]\n    r = props[\"corr\"]\n    mean = [0.5 * (log_d_min + log_d_max), 0.5 * (log_m_min + log_m_max)]\n    std_d = (log_d_max - log_d_min) / 4\n    std_m = (log_m_max - log_m_min) / 4\n    cov = [[std_d**2, r * std_d * std_m], [r * std_d * std_m, std_m**2]]\n    pts = np.random.multivariate_normal(mean, cov, n)\n    log_density = np.clip(pts[:, 0], log_d_min, log_d_max)\n    log_modulus = np.clip(pts[:, 1], log_m_min, log_m_max)\n    data[family] = {\"density\": 10**log_density, \"modulus\": 10**log_modulus}\n\n# Plot\nfig = go.Figure()\n\nfor family, props in families.items():\n    d = data[family][\"density\"]\n    m = data[family][\"modulus\"]\n    color = props[\"color\"]\n    symbol = props[\"symbol\"]\n\n    r_val = int(color[1:3], 16)\n    g_val = int(color[3:5], 16)\n    b_val = int(color[5:7], 16)\n    fill_color = f\"rgba({r_val}, {g_val}, {b_val}, 0.15)\"\n\n    # Convex hull envelope for each family region\n    log_pts = np.column_stack([np.log10(d), np.log10(m)])\n    if len(log_pts) >= 3:\n        hull = ConvexHull(log_pts)\n        hull_indices = np.append(hull.vertices, hull.vertices[0])\n        hull_d = 10 ** log_pts[hull_indices, 0]\n        hull_m = 10 ** log_pts[hull_indices, 1]\n        fig.add_trace(\n            go.Scatter(\n                x=hull_d,\n                y=hull_m,\n                mode=\"lines\",\n                line={\"color\": color, \"width\": 2},\n                fill=\"toself\",\n                fillcolor=fill_color,\n                showlegend=False,\n                hoverinfo=\"skip\",\n            )\n        )\n\n    # Scatter points — distinct shape per family (7 series requires redundant encoding)\n    e_over_rho = m / (d / 1000)\n    fig.add_trace(\n        go.Scatter(\n            x=d,\n            y=m,\n            mode=\"markers\",\n            name=family,\n            legendgroup=family,\n            marker={\n                \"size\": 12,\n                \"color\": color,\n                \"symbol\": symbol,\n                \"line\": {\"width\": 1.5, \"color\": PAGE_BG},\n                \"opacity\": 0.85,\n            },\n            customdata=np.column_stack([e_over_rho]),\n            hovertemplate=(\n                f\"<b>{family}</b><br>\"\n                \"Density: %{x:.0f} kg/m³<br>\"\n                \"Modulus: %{y:.3g} GPa<br>\"\n                \"E/ρ: %{customdata[0]:.3g} GPa·m³/Mg\"\n                \"<extra></extra>\"\n            ),\n        )\n    )\n\n    # Family label at log-space centroid\n    centroid_d = 10 ** np.mean(np.log10(d))\n    centroid_m = 10 ** np.mean(np.log10(m))\n    # Adjust labels to avoid crowding — Composites lower, Metals further left\n    if family == \"Composites\":\n        centroid_m /= 2.0\n    if family == \"Metals\":\n        centroid_d /= 1.8\n    fig.add_annotation(\n        x=np.log10(centroid_d),\n        y=np.log10(centroid_m),\n        xref=\"x\",\n        yref=\"y\",\n        text=f\"<b>{family}</b>\",\n        showarrow=False,\n        font={\"size\": 12, \"color\": INK, \"family\": \"Arial, Helvetica, sans-serif\"},\n        bgcolor=ELEVATED_BG,\n        borderpad=5,\n        bordercolor=INK_SOFT,\n        borderwidth=1,\n    )\n\n# Performance index guide lines: E/rho = constant (lightweight stiffness)\nguide_values = [0.001, 0.01, 0.1, 1, 10]\ndensity_range = np.array([10, 20000])\nfor gv in guide_values:\n    modulus_line = gv * density_range\n    mask = (modulus_line >= 0.0005) & (modulus_line <= 1000)\n    if mask.any():\n        fig.add_trace(\n            go.Scatter(\n                x=density_range[mask],\n                y=modulus_line[mask],\n                mode=\"lines\",\n                line={\"color\": GUIDE_LINE, \"width\": 1.2, \"dash\": \"dot\"},\n                showlegend=False,\n                hoverinfo=\"skip\",\n            )\n        )\n\n# Guide line label\nfig.add_annotation(\n    x=np.log10(40),\n    y=np.log10(40 * 1),\n    xref=\"x\",\n    yref=\"y\",\n    text=\"<i>E/ρ = const</i>\",\n    showarrow=False,\n    font={\"size\": 12, \"color\": INK_SOFT, \"family\": \"Arial, Helvetica, sans-serif\"},\n    bgcolor=ELEVATED_BG,\n    borderpad=4,\n    textangle=-38,\n)\n\n# Title length scaling (floor 11px per plotly family rule)\ntitle = \"scatter-ashby-material · python · plotly · anyplot.ai\"\ntitle_fontsize = max(11, round(16 * min(1.0, 67 / len(title))))\n\n# Layout — canvas 800×450 @ scale=4 → 3200×1800\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    template=\"plotly_white\",\n    margin={\"l\": 80, \"r\": 60, \"t\": 100, \"b\": 80},\n    title={\n        \"text\": title,\n        \"font\": {\"size\": title_fontsize, \"color\": INK, \"family\": \"Arial, Helvetica, sans-serif\"},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Density (kg/m³)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"type\": \"log\",\n        \"showgrid\": True,\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"showline\": True,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"mirror\": False,\n        \"range\": [np.log10(10), np.log10(20000)],\n        \"dtick\": 1,\n        \"minor\": {\"showgrid\": True, \"gridcolor\": MINOR_GRID},\n        \"zerolinecolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Young's Modulus (GPa)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"type\": \"log\",\n        \"showgrid\": True,\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"showline\": True,\n        \"linecolor\": INK_SOFT,\n        \"linewidth\": 1,\n        \"mirror\": False,\n        \"range\": [np.log10(0.0005), np.log10(1000)],\n        \"minor\": {\"showgrid\": True, \"gridcolor\": MINOR_GRID},\n        \"zerolinecolor\": INK_SOFT,\n    },\n    legend={\n        \"title\": {\"text\": \"Material Family\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n        \"x\": 0.01,\n        \"y\": 0.99,\n        \"xanchor\": \"left\",\n        \"yanchor\": \"top\",\n        \"itemsizing\": \"constant\",\n    },\n    font={\"family\": \"Arial, Helvetica, sans-serif\", \"color\": INK},\n)\n\n# Save — canvas: 3200×1800 (landscape)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}