{"spec_id":"mohr-circle","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nmohr-circle: Mohr's Circle for Stress Analysis\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 88/100 | Created: 2026-05-30\n\"\"\"\n\nimport os\nimport sys\n\nimport numpy as np\nimport pandas as pd\n\n\n# Work around naming conflict: plotnine.py vs plotnine package\nscript_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\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_manual,\n    theme,\n)\n\n\n# Theme tokens — Imprint palette\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\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nBRAND = IMPRINT_PALETTE[0]  # Mohr's circle — always first categorical series\n\n# Stress state: steel shaft under bending + torsion (MPa)\nsigma_x = 80.0\nsigma_y = 20.0\ntau_xy = 40.0\n\n# Derived stress quantities\nsigma_c = (sigma_x + sigma_y) / 2\nradius = np.sqrt(((sigma_x - sigma_y) / 2) ** 2 + tau_xy**2)\nsigma_1 = sigma_c + radius\nsigma_2 = sigma_c - radius\ntau_max = radius\ntwo_theta_p = np.degrees(np.arctan2(tau_xy, (sigma_x - sigma_y) / 2))\n\n# Mohr's circle path\nangles = np.linspace(0, 2 * np.pi, 361)\ncircle_df = pd.DataFrame({\"sigma\": sigma_c + radius * np.cos(angles), \"tau\": radius * np.sin(angles)})\n\n# Angle arc from σ-axis to line CA (visualising 2θp)\nangle_A = np.arctan2(tau_xy, sigma_x - sigma_c)\narc_r = radius * 0.34\narc_df = pd.DataFrame(\n    {\"sigma\": sigma_c + arc_r * np.cos(np.linspace(0, angle_A, 60)), \"tau\": arc_r * np.sin(np.linspace(0, angle_A, 60))}\n)\n\n# Reference lines: horizontal σ-axis and vertical through center\npad = 18.0\nref_df = pd.DataFrame(\n    {\n        \"x\": [sigma_2 - pad, sigma_c],\n        \"xend\": [sigma_1 + pad, sigma_c],\n        \"y\": [0.0, -tau_max - pad],\n        \"yend\": [0.0, tau_max + pad],\n    }\n)\n\n# Diameter from A(σx, τxy) to B(σy, −τxy)\ndiam_df = pd.DataFrame({\"x\": [sigma_x], \"xend\": [sigma_y], \"y\": [tau_xy], \"yend\": [-tau_xy]})\n\n# Key points with engineering roles\npoints_df = pd.DataFrame(\n    {\n        \"sigma\": [sigma_x, sigma_y, sigma_1, sigma_2, sigma_c, sigma_c],\n        \"tau\": [tau_xy, -tau_xy, 0.0, 0.0, tau_max, -tau_max],\n        \"role\": [\"Stress State\", \"Stress State\", \"Principal Stress\", \"Principal Stress\", \"Max Shear\", \"Max Shear\"],\n    }\n)\n\ncenter_df = pd.DataFrame({\"sigma\": [sigma_c], \"tau\": [0.0]})\n\n# Accent segment along x-axis between σ2 and σ1 — draws eye to the principal stress result\nspan_df = pd.DataFrame({\"x\": [sigma_2], \"xend\": [sigma_1], \"y\": [0.0], \"yend\": [0.0]})\n\n# 2θp arc label — bisecting angle, outside the arc\nangle_mid = angle_A / 2\narc_lbl_df = pd.DataFrame(\n    {\n        \"sigma\": [sigma_c + arc_r * 1.9 * np.cos(angle_mid)],\n        \"tau\": [arc_r * 1.9 * np.sin(angle_mid)],\n        \"label\": [f\"2θp ≈ {two_theta_p:.1f}°\"],\n    }\n)\n\n# Labels left-aligned (text extends rightward from anchor)\nlabels_left = pd.DataFrame(\n    {\n        \"sigma\": [sigma_x + 4.0, sigma_1 + 4.0, sigma_c + 4.0, sigma_c + 4.0],\n        \"tau\": [tau_xy + 3.5, 3.5, tau_max + 4.5, -tau_max - 6.0],\n        \"label\": [\n            f\"A ({sigma_x:.0f}, {tau_xy:.0f})\",\n            f\"σ1 = {sigma_1:.0f} MPa\",\n            f\"τmax = {tau_max:.0f} MPa\",\n            f\"−τmax = {-tau_max:.0f} MPa\",\n        ],\n    }\n)\n\n# Labels right-aligned (text extends leftward from anchor)\nlabels_right = pd.DataFrame(\n    {\n        \"sigma\": [sigma_y - 4.0, sigma_2 - 4.0],\n        \"tau\": [-tau_xy - 6.0, 3.5],\n        \"label\": [f\"B ({sigma_y:.0f}, {-tau_xy:.0f})\", f\"σ2 = {sigma_2:.0f} MPa\"],\n    }\n)\n\ntitle = \"mohr-circle · python · plotnine · anyplot.ai\"\n\nanyplot_theme = theme(\n    figure_size=(6, 6),\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major=element_line(color=INK, size=0.3, alpha=0.12),\n    panel_grid_minor=element_blank(),\n    panel_border=element_blank(),\n    axis_title=element_text(color=INK, size=10),\n    axis_text=element_text(color=INK_SOFT, size=8),\n    axis_line=element_line(color=INK_SOFT, size=0.5),\n    plot_title=element_text(color=INK, size=12, ha=\"center\"),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=8),\n    legend_title=element_blank(),\n    legend_position=\"bottom\",\n)\n\nplot = (\n    ggplot(circle_df, aes(x=\"sigma\", y=\"tau\"))\n    # Accent segment from σ2 to σ1 — guides eye to the principal stress result\n    + geom_segment(data=span_df, mapping=aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"yend\"), color=BRAND, size=4.0, alpha=0.25)\n    # Dashed reference lines through center\n    + geom_segment(\n        data=ref_df, mapping=aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"yend\"), color=INK_MUTED, size=0.5, linetype=\"dashed\"\n    )\n    # Diameter line from A to B\n    + geom_segment(\n        data=diam_df, mapping=aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"yend\"), color=INK_SOFT, size=0.8, alpha=0.7\n    )\n    # Mohr's circle — Imprint position 1 (brand green)\n    + geom_path(color=BRAND, size=1.4)\n    # Angle arc annotating 2θp\n    + geom_path(data=arc_df, color=INK_SOFT, size=0.8)\n    # Key points colored by engineering role\n    + geom_point(data=points_df, mapping=aes(color=\"role\"), size=4.0)\n    + scale_color_manual(\n        values={\n            \"Stress State\": IMPRINT_PALETTE[1],\n            \"Principal Stress\": IMPRINT_PALETTE[2],\n            \"Max Shear\": IMPRINT_PALETTE[4],\n        },\n        name=\"\",\n    )\n    # Center mark C\n    + geom_point(data=center_df, color=INK, size=2.5)\n    # Left-aligned labels\n    + geom_text(data=labels_left, mapping=aes(label=\"label\"), ha=\"left\", size=3.8, color=INK)\n    # Right-aligned labels\n    + geom_text(data=labels_right, mapping=aes(label=\"label\"), ha=\"right\", size=3.8, color=INK)\n    # Angle arc label\n    + geom_text(data=arc_lbl_df, mapping=aes(label=\"label\"), ha=\"left\", size=3.5, color=INK_SOFT)\n    + labs(x=\"Normal Stress σ (MPa)\", y=\"Shear Stress τ (MPa)\", title=title)\n    + coord_fixed(ratio=1, xlim=(-18, 120), ylim=(-70, 70))\n    + anyplot_theme\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\")\n"}