{"spec_id":"ternary-basic","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nternary-basic: Basic Ternary Plot\nLibrary: matplotlib 3.11.1 | Python 3.13.14\nQuality: 84/100 | Updated: 2026-08-04\n\"\"\"\n\nimport os\nimport sys\n\n\nif sys.path and (sys.path[0] == \"\" or sys.path[0].endswith(\"/python\")):\n    sys.path.pop(0)\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.patches import Polygon\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"\n\n# Data - Soil composition samples (sand, silt, clay summing to 100%)\nnp.random.seed(42)\nn_points = 60\n\n# Generate compositions with better edge/corner coverage using Dirichlet\n# Lower alpha values concentrate points at edges; higher values favor center\n# Use alpha=1 for more uniform distribution across the simplex\nraw = np.random.dirichlet([1, 1, 1], n_points) * 100\nsand = raw[:, 0]\nsilt = raw[:, 1]\nclay = raw[:, 2]\n\n# Convert ternary coordinates to Cartesian (equilateral triangle)\n# Triangle: Sand at top (0.5, sqrt(3)/2), Silt at bottom-left (0, 0), Clay at bottom-right (1, 0)\nsqrt3_2 = np.sqrt(3) / 2\ntotal = sand + silt + clay\nx_points = 0.5 * (2 * clay + sand) / total\ny_points = sqrt3_2 * sand / total\n\n\ndef to_xy(a_sand, b_silt, c_clay):\n    t = a_sand + b_silt + c_clay\n    return 0.5 * (2 * c_clay + a_sand) / t, sqrt3_2 * a_sand / t\n\n\n# USDA-style \"Loam\" classification zone (sand 23-52%, silt 28-50%, clay 7-27%)\nloam_vertices = [(23, 50, 27), (45, 28, 27), (52, 28, 20), (52, 41, 7), (43, 50, 7)]\nloam_xy = [to_xy(*v) for v in loam_vertices]\nloam_cx = sum(p[0] for p in loam_xy) / len(loam_xy)\nloam_cy = sum(p[1] for p in loam_xy) / len(loam_xy)\n\n# Create figure (square format for triangle) — 6x6in @ dpi=400 -> exactly 2400x2400px\nfig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Draw triangle outline\ntriangle_x = [0.5, 0, 1, 0.5]\ntriangle_y = [sqrt3_2, 0, 0, sqrt3_2]\nax.plot(triangle_x, triangle_y, color=INK_SOFT, linewidth=1.5)\n\n# Draw grid lines at 20% intervals\ngrid_levels = [0.2, 0.4, 0.6, 0.8]\nfor level in grid_levels:\n    # Lines parallel to bottom edge (constant sand)\n    a_val = level\n    x1 = 0.5 * (2 * (1 - a_val) + a_val)\n    y1 = sqrt3_2 * a_val\n    x2 = 0.5 * (2 * 0 + a_val)\n    y2 = sqrt3_2 * a_val\n    ax.plot([x1, x2], [y1, y2], color=INK, alpha=0.15, linewidth=0.8, linestyle=\"--\")\n\n    # Lines parallel to right edge (constant silt)\n    b_val = level\n    x1 = 0.5 * (2 * (1 - b_val) + 0)\n    y1 = sqrt3_2 * 0\n    x2 = 0.5 * (2 * 0 + (1 - b_val))\n    y2 = sqrt3_2 * (1 - b_val)\n    ax.plot([x1, x2], [y1, y2], color=INK, alpha=0.15, linewidth=0.8, linestyle=\"--\")\n\n    # Lines parallel to left edge (constant clay)\n    c_val = level\n    x1 = 0.5 * (2 * c_val + 0)\n    y1 = sqrt3_2 * 0\n    x2 = 0.5 * (2 * c_val + (1 - c_val))\n    y2 = sqrt3_2 * (1 - c_val)\n    ax.plot([x1, x2], [y1, y2], color=INK, alpha=0.15, linewidth=0.8, linestyle=\"--\")\n\n# Add tick labels at 20% intervals along each edge\ntick_fontsize = 10\nfor level in [0, 20, 40, 60, 80, 100]:\n    frac = level / 100\n\n    # Sand axis (A) - along left edge from bottom to top\n    x_tick = 0.5 * (2 * 0 + frac)\n    y_tick = sqrt3_2 * frac\n    ax.text(x_tick - 0.06, y_tick, f\"{level}\", fontsize=tick_fontsize, ha=\"right\", va=\"center\", color=INK_SOFT)\n\n    # Silt axis (B) - along bottom edge from right to left\n    x_tick = 0.5 * (2 * (1 - frac) + 0)\n    y_tick = 0\n    ax.text(x_tick, y_tick - 0.05, f\"{level}\", fontsize=tick_fontsize, ha=\"center\", va=\"top\", color=INK_SOFT)\n\n    # Clay axis (C) - along right edge from top to bottom\n    x_tick = 0.5 * (2 * frac + (1 - frac))\n    y_tick = sqrt3_2 * (1 - frac)\n    ax.text(x_tick + 0.06, y_tick, f\"{level}\", fontsize=tick_fontsize, ha=\"left\", va=\"center\", color=INK_SOFT)\n\n# Highlight the \"Loam\" soil-classification zone with a filled Polygon patch\nloam_patch = Polygon(\n    loam_xy, closed=True, facecolor=\"#BD8233\", alpha=0.18, edgecolor=\"#BD8233\", linewidth=1.0, linestyle=\":\", zorder=2\n)\nax.add_patch(loam_patch)\nax.text(\n    loam_cx,\n    loam_cy,\n    \"Loam\",\n    fontsize=11,\n    ha=\"center\",\n    va=\"center\",\n    fontstyle=\"italic\",\n    color=INK_SOFT,\n    alpha=0.9,\n    zorder=3,\n)\n\n# Plot data points\nax.scatter(x_points, y_points, s=130, color=BRAND, alpha=0.75, edgecolors=PAGE_BG, linewidth=0.8, zorder=5)\n\n# Add vertex labels with component names\nlabel_fontsize = 16\nax.text(\n    0.5, sqrt3_2 + 0.12, \"Sand (%)\", fontsize=label_fontsize, ha=\"center\", va=\"bottom\", fontweight=\"bold\", color=INK\n)\nax.text(-0.1, -0.1, \"Silt (%)\", fontsize=label_fontsize, ha=\"right\", va=\"top\", fontweight=\"bold\", color=INK)\nax.text(1.1, -0.1, \"Clay (%)\", fontsize=label_fontsize, ha=\"left\", va=\"top\", fontweight=\"bold\", color=INK)\n\n# Title\nax.set_title(\"ternary-basic · python · matplotlib · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK, pad=14)\n\n# Clean up axes\nax.set_aspect(\"equal\")\nax.axis(\"off\")\n\n# Adjust limits to prevent clipping\nax.set_xlim(-0.28, 1.28)\nax.set_ylim(-0.22, 1.15)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}