{"spec_id":"mohr-circle","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nmohr-circle: Mohr's Circle for Stress Analysis\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-05-30\n\"\"\"\n\nimport importlib.util\nimport os\nimport sys\n\nimport numpy as np\n\n\n# Ensure we import the installed pygal package, not this file\npygal_spec = importlib.util.find_spec(\"pygal\")\nif pygal_spec and pygal_spec.origin != __file__:\n    import pygal\n    from pygal.style import Style\nelse:\n    _script_dir = os.path.dirname(os.path.abspath(__file__))\n    sys.path = [p for p in sys.path if os.path.abspath(p) != _script_dir]\n    try:\n        import pygal\n        from pygal.style import Style\n    finally:\n        sys.path.insert(0, _script_dir)\n\n# Theme tokens — Imprint palette chrome\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — 8 hues, theme-independent, hybrid-v3 sort\nIMPRINT_PALETTE = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\n# Data — concrete column under eccentric axial load + lateral shear\n# Differentiated stress state from sibling implementations\nsigma_x = 110  # Normal stress in x-direction (MPa) — axial + bending\nsigma_y = -10  # Normal stress in y-direction (MPa) — transverse compression\ntau_xy = 30  # Shear stress on xy-plane (MPa) — lateral force\n\n# Mohr's Circle parameters\ncenter = (sigma_x + sigma_y) / 2\nradius = np.sqrt(((sigma_x - sigma_y) / 2) ** 2 + tau_xy**2)\nsigma_1 = center + radius\nsigma_2 = center - radius\ntau_max = radius\ntheta_p2 = np.degrees(np.arctan2(tau_xy, (sigma_x - sigma_y) / 2))\n\n# Circle points (200 points for smooth curve)\ntheta = np.linspace(0, 2 * np.pi, 200)\ncircle_pts = [(float(center + radius * np.cos(t)), float(radius * np.sin(t))) for t in theta]\n\n# Reference stress points on the circle\npoint_a = (float(sigma_x), float(tau_xy))\npoint_b = (float(sigma_y), float(-tau_xy))\n\n# 2θp angle arc — smaller radius keeps arc fully inside circle, improves dark-theme visibility\narc_r = radius * 0.45\narc_angles = np.linspace(0, np.radians(theta_p2), 50)\narc_pts = [(float(center + arc_r * np.cos(a)), float(arc_r * np.sin(a))) for a in arc_angles]\n\n# Axis ranges — tight around circle with balanced padding\npadding = 18\nx_min = float(sigma_2 - padding)\nx_max = float(sigma_1 + padding)\ny_min = -(radius + padding)\ny_max = radius + padding\n\n# Reference lines through center (horizontal σ-axis + vertical at center)\nref_lines = [\n    (float(x_min), 0.0),\n    (float(x_max), 0.0),\n    None,\n    (float(center), float(y_min)),\n    (float(center), float(y_max)),\n]\n\n# colors[0] = INK_MUTED for structural reference lines (neutral anchor, theme-adaptive)\n# colors[1:] = Imprint palette, so Mohr's Circle = #009E73 (brand green, first categorical)\ncustom_colors = (INK_MUTED,) + IMPRINT_PALETTE[:5]\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=custom_colors,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    tooltip_font_size=30,\n    stroke_width=2.5,\n    opacity=0.95,\n    opacity_hover=1.0,\n)\n\n# Square canvas — equal aspect ratio ensures the circle appears as a true circle\nchart = pygal.XY(\n    width=2400,\n    height=2400,\n    style=custom_style,\n    title=\"mohr-circle · python · pygal · anyplot.ai\",\n    x_title=\"Normal Stress σ (MPa)\",\n    y_title=\"Shear Stress τ (MPa)\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=2,\n    dots_size=5,\n    stroke=True,\n    show_x_guides=True,\n    show_y_guides=True,\n    truncate_legend=-1,\n    range=(y_min, y_max),\n    xrange=(x_min, x_max),\n    x_value_formatter=lambda x: f\"{x:.0f}\",\n    y_value_formatter=lambda y: f\"{y:.0f} MPa\",\n    print_values=False,\n    js=[],\n)\n\n# Reference lines through center (neutral structural layer — drawn behind data)\nchart.add(\n    \"Reference axes\",\n    ref_lines,\n    stroke=True,\n    dots_size=0,\n    stroke_style={\"width\": 2, \"dasharray\": \"10, 6\"},\n    show_dots=False,\n    allow_interruptions=True,\n)\n\n# Mohr's Circle outline — first categorical series → brand green #009E73\nchart.add(\"Mohr's Circle\", circle_pts, stroke=True, dots_size=0, stroke_style={\"width\": 5}, fill=False)\n\n# Stress points A and B with diameter line\nchart.add(\n    f\"A({sigma_x}, {tau_xy})  B({sigma_y}, {int(-tau_xy)})\",\n    [\n        {\"value\": point_a, \"label\": f\"A: σx = {sigma_x} MPa, τxy = {tau_xy} MPa\"},\n        {\"value\": point_b, \"label\": f\"B: σy = {sigma_y} MPa, τxy = {int(-tau_xy)} MPa\"},\n    ],\n    stroke=True,\n    dots_size=14,\n    stroke_style={\"width\": 3, \"dasharray\": \"8, 5\"},\n)\n\n# Principal stresses on horizontal axis\nchart.add(\n    f\"σ₁ = {sigma_1:.1f}, σ₂ = {sigma_2:.1f} MPa\",\n    [\n        {\"value\": (float(sigma_1), 0.0), \"label\": f\"σ₁ = {sigma_1:.1f} MPa (max principal)\"},\n        {\"value\": (float(sigma_2), 0.0), \"label\": f\"σ₂ = {sigma_2:.1f} MPa (min principal)\"},\n    ],\n    stroke=False,\n    dots_size=16,\n)\n\n# Max shear stress at top and bottom of circle\nchart.add(\n    f\"τ_max = ±{tau_max:.1f} MPa\",\n    [\n        {\"value\": (float(center), float(tau_max)), \"label\": f\"τ_max = +{tau_max:.1f} MPa\"},\n        {\"value\": (float(center), float(-tau_max)), \"label\": f\"τ_max = −{tau_max:.1f} MPa\"},\n    ],\n    stroke=False,\n    dots_size=16,\n)\n\n# 2θp angle arc — bold stroke, higher width for dark-theme contrast\nchart.add(f\"2θp = {theta_p2:.1f}°\", arc_pts, stroke=True, dots_size=0, stroke_style={\"width\": 12})\n\n# Save — theme-suffixed filenames required by pipeline\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}