Skip to content

Instantly share code, notes, and snippets.

@rutj3
Created April 25, 2025 13:19
Show Gist options
  • Save rutj3/1b25c86581efdeb544661d9adcad5993 to your computer and use it in GitHub Desktop.
Save rutj3/1b25c86581efdeb544661d9adcad5993 to your computer and use it in GitHub Desktop.
Ternary plot classic
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "806ea258-de01-4bd0-a733-39178ea7c88c",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"import numpy as np\n",
"from numpy import log, exp, sqrt, cos, sin\n",
"import numba\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib as mpl\n",
"from collections import defaultdict\n",
"import matplotlib.patheffects as PathEffects\n",
"plt.style.use(\"seaborn-v0_8\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58b46be3-c307-44f1-b35c-fd18890581f9",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"def get_geometric_props(fy, ft, fd):\n",
"\n",
" # # shirazi & boersma (1984) in mm\n",
" # clay_lo = 0\n",
" # clay_hi = 0.002\n",
" # silt_lo = 0.002\n",
" # silt_hi = 0.05\n",
" # sand_lo = 0.05\n",
" # sand_hi = 2.00\n",
" \n",
" # clay_avg = (clay_lo + clay_hi) / 2\n",
" # silt_avg = (silt_lo + silt_hi) / 2\n",
" # sand_avg = (sand_lo + sand_hi) / 2\n",
"\n",
" # boersma & hart (1988) in μm\n",
" clay_lo = 0.01\n",
" clay_hi = 2.0\n",
" silt_lo = 2.0\n",
" silt_hi = 50.0\n",
" sand_lo = 50.0\n",
" sand_hi = 2000.0\n",
" \n",
" clay_avg = sqrt(clay_lo * clay_hi)\n",
" silt_avg = sqrt(silt_lo * silt_hi)\n",
" sand_avg = sqrt(sand_lo * sand_hi)\n",
" \n",
" a = 0.01 * (fy * log(clay_avg) + ft * log(silt_avg) + fd * log(sand_avg))\n",
" b = sqrt(np.maximum((0.01 * (fy * log(clay_avg)**2 + ft * log(silt_avg)**2 + fd * log(sand_avg)**2)) - a**2, 0))\n",
" dg = exp(a)\n",
" σg = exp(b)\n",
"\n",
" return dg, σg"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9659879a-963e-4148-9c0b-18694887402a",
"metadata": {},
"outputs": [],
"source": [
"def get_cartesian_coords(clay, silt, sand):\n",
"\n",
" total = clay + silt + sand\n",
" \n",
" x = 0.5 * (2*silt+clay)/total\n",
" y = sqrt(3)/2 * clay / total\n",
"\n",
" return x, y\n",
"\n",
"def get_cartesian_coords_inverse(x, y):\n",
"\n",
" total = 100.\n",
"\n",
" clay = y * (2/sqrt(3)) * total\n",
"\n",
" silt = (((x*2) - clay/total) * total) / 2\n",
" sand = total - (clay+silt)\n",
"\n",
" return clay, silt, sand"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3446bc73-b1aa-4332-a3e2-522aa9b4e294",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"clay_boundaries = []\n",
"silt_boundaries = []\n",
"sand_boundaries = []\n",
"\n",
"line_step = 10\n",
"line_res = .01\n",
"\n",
"for i, fy in enumerate(range(0,100+line_step,line_step)):\n",
" \n",
" ft = np.arange(0, 100+line_res-fy, line_res)\n",
" fy = np.full_like(ft, fy)\n",
" fd = 100 - fy - ft\n",
" dg, σg = get_cartesian_coords(fy, ft, fd)\n",
" clay_boundaries.append((dg, σg))\n",
" \n",
"for i, ft in enumerate(range(0,100+line_step,line_step)):\n",
" \n",
" fy = np.arange(0, 100+line_res-ft, line_res)\n",
" ft = np.full_like(fy, ft)\n",
" fd = 100 - fy - ft\n",
" dg, σg = get_cartesian_coords(fy, ft, fd)\n",
" silt_boundaries.append((dg, σg))\n",
" \n",
"for i, fd in enumerate(range(0,100+line_step,line_step)):\n",
" \n",
" fy = np.arange(0,100+line_res-fd, line_res)\n",
" fd = np.full_like(fy, fd)\n",
" ft = 100 - fy - fd\n",
" \n",
" dg, σg = get_cartesian_coords(fy, ft, fd)\n",
" sand_boundaries.append((dg, σg))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "831a47c6-6cab-47b6-beac-0984fe4f25ad",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"# name, (fy, ft, fd) clay/silt/sand\n",
"class_nodes = { \n",
" \"b\": (0, 0, 100),\n",
" \"d\": (0, 30, 70),\n",
" \"f\": (0, 50, 50),\n",
" \"m\": (0, 80, 20),\n",
" \"q\": (0, 100, 0),\n",
" \"c\": (10, 0, 90),\n",
" \"g\": (20, 0, 80),\n",
" \"v\": (40, 40, 20),\n",
" \"w\": (40, 60, 0),\n",
" \"y\": (60, 40, 0),\n",
" \"z\": (100, 0, 0),\n",
" \"a\": (0, 15, 85),\n",
" \"e\": (15, 0, 85),\n",
" \"h\": (20, 28, 52),\n",
" \"i\": (7, 41, 52),\n",
" \"j\": (7, 50, 43),\n",
" \"k\": (27, 28, 45),\n",
" \"l\": (27, 50, 23),\n",
" \"n\": (27, 73, 0),\n",
" \"o\": (12, 88, 0),\n",
" \"p\": (12, 80, 8),\n",
" \"r\": (35, 0, 65),\n",
" \"s\": (35, 20, 45),\n",
" \"t\": (27, 53, 20),\n",
" \"u\": (40, 15, 45),\n",
" \"x\": (55, 0, 45), \n",
"}\n",
"\n",
"class_polys = {\n",
" \"1) sand\": [\"b\",\"c\",\"a\"],\n",
" \"2) loamy sand\": [\"a\",\"c\",\"e\",\"d\"],\n",
" \"3) sandy loam\": [\"d\",\"e\",\"g\",\"h\",\"i\",\"j\",\"f\"],\n",
" \"4) loam\": [\"j\",\"i\",\"h\",\"k\",\"l\"],\n",
" \"5) silt loam\": [\"f\",\"j\",\"l\",\"n\",\"o\",\"p\",\"m\"],\n",
" \"6) silt\": [\"m\",\"p\",\"o\",\"q\"],\n",
" \"7) sandy clay loam\": [\"g\",\"r\",\"s\",\"k\",\"h\"],\n",
" \"8) clay loam\": [\"k\",\"u\",\"v\",\"t\",\"l\"],\n",
" \"9) silt clay loam\": [\"t\",\"v\",\"w\",\"n\"],\n",
" \"10) sandy clay\": [\"r\",\"x\",\"u\",\"s\"],\n",
" \"11) silty clay\": [\"v\",\"y\",\"w\"],\n",
" \"12) clay\": [\"x\",\"z\",\"y\",\"v\",\"u\"],\n",
"}\n",
"\n",
"# clay/silt/sand\n",
"class_label_pos = {\n",
" \"1) sand\": (3, 3),\n",
" \"2) loamy sand\": (5, 13),\n",
" \"3) sandy loam\": (11, 23),\n",
" \"4) loam\": (16, 41),\n",
" \"5) silt loam\": (16, 65),\n",
" \"6) silt\": (5, 87),\n",
" \"7) sandy clay loam\": (26, 15),\n",
" \"8) clay loam\": (33, 33),\n",
" \"9) silt clay loam\": (33, 55),\n",
" \"10) sandy clay\": (41, 8),\n",
" \"11) silty clay\": (47, 47),\n",
" \"12) clay\": (67, 16),\n",
"}\n",
"\n",
"class_polys_full = {}\n",
"\n",
"for name, nodes in class_polys.items():\n",
" class_polys_full[name] = list(map(class_nodes.get, nodes))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2ddd2ed5-8b4a-4973-9ce2-8bd9e570d0fe",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"class_bounds = defaultdict(list)\n",
"\n",
"for name, nodes in class_polys_full.items():\n",
" \n",
" for i in range(len(nodes)):\n",
" fy1, ft1, fd1 = nodes[i-1]\n",
" fy2, ft2, fd2 = nodes[i]\n",
" \n",
" distance = sqrt((fy1-fy2)**2 + (ft1-ft2)**2 + (fd1-fd2)**2)\n",
" line_length = int(np.ceil(distance*10))\n",
" \n",
" fy = np.linspace(fy1, fy2, line_length)\n",
" ft = np.linspace(ft1, ft2, line_length)\n",
" fd = np.linspace(fd1, fd2, line_length)\n",
"\n",
" dg, σg = get_cartesian_coords(fy, ft, fd)\n",
" \n",
" class_bounds[name].append((dg, σg))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2d191d14-a8b5-4c71-b613-f91b21ce5e1a",
"metadata": {},
"outputs": [],
"source": [
"def annotate(\n",
" ax, label_min=20, label_max=80, label_step=20, position_offset=35, position_rotation_offset=0, txt_rotation_offset=0, \n",
" color=\"C0\", plot_marker=True, axis=\"Silt\", txt=\"{value:1.0f}%\", \n",
" coord_conversion=get_geometric_props, marker_angle=None, ha=\"center\", va=\"center\", align_with=\"boundary\",\n",
" debug=False,\n",
"):\n",
"\n",
" data_to_figure = ax.transData.transform\n",
" data_to_axes = lambda x: ax.transAxes.inverted().transform(data_to_figure(x))\n",
"\n",
" for value in range(label_min, label_max+1, label_step):\n",
"\n",
" if align_with == \"boundary\":\n",
" # finite difference along the boundary for angle\n",
" if axis.lower() == \"silt\":\n",
" ft0 = value\n",
" ft1 = max(ft0 - 0.01, 0)\n",
" ft2 = max(ft0 + 0.01, 0)\n",
" \n",
" fd0 = fd1 = fd2 = 0\n",
" \n",
" fy0 = 100 - ft0\n",
" fy1 = 100 - ft1\n",
" fy2 = 100 - ft2\n",
" \n",
" elif axis.lower() == \"clay\":\n",
" fy0 = value\n",
" fy1 = max(fy0 - 0.01, 0)\n",
" fy2 = max(fy0 + 0.01, 0)\n",
" \n",
" ft0 = ft1 = ft2 = 0\n",
" \n",
" fd0 = 100 - fy0\n",
" fd1 = 100 - fy1\n",
" fd2 = 100 - fy2\n",
" \n",
" elif axis.lower() == \"sand\":\n",
" fd0 = value\n",
" fd1 = max(fd0 - 0.01, 0)\n",
" fd2 = max(fd0 + 0.01, 0)\n",
" \n",
" ft0 = 100 - fd0\n",
" ft1 = 100 - fd1\n",
" ft2 = 100 - fd2\n",
" \n",
" fy0 = fy1 = fy2 = 0\n",
"\n",
" if align_with == \"grid\":\n",
" # finite difference along the gridline for angle\n",
" if axis.lower() == \"silt\":\n",
" ft0 = ft1 = ft2 = value\n",
" \n",
" fd0 = 0\n",
" fd1 = 0.1\n",
" fd2 = 0\n",
" \n",
" fy0 = 100 - ft0\n",
" fy1 = 99.9 - ft1\n",
" fy2 = 100 - ft2\n",
" \n",
" elif axis.lower() == \"clay\":\n",
" fy0 = fy1 = fy2 = value\n",
" \n",
" ft0 = 0\n",
" ft1 = 0\n",
" ft2 = 0.1\n",
" \n",
" fd0 = 100 - fy0\n",
" fd1 = 100 - fy1\n",
" fd2 = 99.9 - fy2\n",
" \n",
" elif axis.lower() == \"sand\":\n",
" fd0 = fd1 = fd2 = value\n",
" \n",
" ft0 = 100. - fd0\n",
" ft1 = 99.9 - fd1\n",
" ft2 = 100. - fd2\n",
" \n",
" fy0 = 0\n",
" fy1 = 0.1\n",
" fy2 = 0\n",
"\n",
" # clay/silt/sand to ternary \"data\" x/y\n",
" x0, y0 = coord_conversion(fy0, ft0, fd0)\n",
" x1, y1 = coord_conversion(fy1, ft1, fd1)\n",
" x2, y2 = coord_conversion(fy2, ft2, fd2)\n",
" \n",
" ax1, ay1 = data_to_axes((x1, y1))\n",
" ax2, ay2 = data_to_axes((x2, y2))\n",
" dx = (ax2 - ax1)\n",
" dy = (ay2 - ay1)\n",
" \n",
" # dx = (log(x2) - log(x1) ) / exp(1.4)\n",
" fx1, fy1 = data_to_figure((x1, y1))\n",
" fx2, fy2 = data_to_figure((x2, y2))\n",
" fdx = (fx2 - fx1)\n",
" fdy = (fy2 - fy1)\n",
"\n",
" angle_deg = np.rad2deg(np.arctan2(-dx, dy)) % 360 \n",
" angle_rad = np.deg2rad(angle_deg)\n",
"\n",
" fangle_deg = np.rad2deg(np.arctan2(-fdx, fdy)) % 360\n",
" fangle_rad = np.deg2rad(fangle_deg)\n",
"\n",
" if position_offset == 0:\n",
" sgn = 1\n",
" else:\n",
" sgn = np.sign(position_offset)\n",
" \n",
" label_offset = position_offset\n",
" px0 = (label_offset * cos(angle_rad + np.deg2rad(90) + np.deg2rad(position_rotation_offset)))\n",
" py0 = (label_offset * sin(angle_rad + np.deg2rad(90) + np.deg2rad(position_rotation_offset)))\n",
"\n",
" if plot_marker:\n",
" if marker_angle is None:\n",
" marker_angle = angle_deg + txt_rotation_offset\n",
" \n",
" ax.plot(x0, y0, marker=(1, 2, fangle_deg * sgn), mec=color, ms=10, mfc=\"none\", mew=1., clip_on=False)\n",
"\n",
" if debug is True:\n",
" bbox_props = dict(facecolor=\"none\", edgecolor=\"m\", lw=1)\n",
" else:\n",
" bbox_props = dict(facecolor=\"none\", edgecolor=\"none\", lw=0)\n",
"\n",
" # rotation is 0 for east, rotates CCW\n",
" # rotation is with respect for figure\n",
" ax.annotate(\n",
" txt.format(value=value, axis=axis),\n",
" rotation=fangle_deg + 90 + txt_rotation_offset, color=color,\n",
" # rotation_mode=\"default\",\n",
" rotation_mode=\"anchor\",\n",
" bbox=bbox_props, size=12,\n",
" weight=\"bold\", ha=ha, va=va,\n",
" xy=(x0, y0), xycoords='data',\n",
" xytext=(px0, py0), textcoords='offset pixels',\n",
" # path_effects=[PathEffects.withStroke(linewidth=1, foreground=\"k\", alpha=.4)],\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "94c948ca-0b6f-404e-b47c-6dceaba2f052",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"plot_param = \"mean\" # mean, std, blank\n",
"\n",
"clay_color = \"C3\"\n",
"silt_color = \"C1\"\n",
"sand_color = \"C2\"\n",
"\n",
"fig, ax = plt.subplots(\n",
" figsize=(8, 8*sqrt(3)/2), facecolor=\"w\", dpi=120, \n",
" subplot_kw=dict(xticks=[], yticks=[], facecolor=\"w\"),\n",
")\n",
"fig.subplots_adjust(left=0.05, right=0.95, bottom=0.1, top=1)\n",
"\n",
"axes_poly = [(0, 0, 100), (100, 0, 0), (0, 100, 0), (0, 0, 100)]\n",
"axes_poly_xy = [get_cartesian_coords(y,t,d) for y,t,d in axes_poly]\n",
"\n",
"if plot_param in [\"mean\", \"std\"]:\n",
" ax.add_patch(mpl.patches.Polygon(axes_poly_xy, facecolor=\"w\", edgecolor=\"none\", alpha=0.5, zorder=2))\n",
"else:\n",
" ax.add_patch(mpl.patches.Polygon(axes_poly_xy, facecolor=\"#F4F4F9\", edgecolor=\"none\"))\n",
"\n",
"yy, xx = np.mgrid[0:sqrt(3)/2:0.001, 0:1.0001:0.001]\n",
"cclay, ssilt, ssand = get_cartesian_coords_inverse(xx, yy)\n",
"\n",
"valid = (\n",
" (cclay >= 0) & (cclay <= 100) & \n",
" (ssilt >= 0) & (ssilt <= 100) & \n",
" (ssand >= 0) & (ssand <= 100) & \n",
" ((cclay + ssilt + ssand) >= 0) &\n",
" ((cclay + ssilt + ssand) <= 100)\n",
")\n",
"\n",
"cclay[~valid] = np.nan\n",
"ssilt[~valid] = np.nan\n",
"ssand[~valid] = np.nan\n",
"\n",
"dgg, σgg = get_geometric_props(cclay, ssilt, ssand)\n",
"cmap = mpl.colormaps[\"cividis\"].copy()\n",
"\n",
"if plot_param == \"std\":\n",
" \n",
" levels = np.linspace(1, 47, 11)\n",
" facecolors = cmap(np.linspace(0,1,len(levels)-1))\n",
" im = ax.contourf(xx, yy, σgg, colors=facecolors, levels=levels, extend=\"neither\", alpha=1, zorder=1)\n",
" cax = fig.add_axes((0.75, 0.67, 0.025, 0.25))\n",
" cb = fig.colorbar(im, cax=cax, shrink=0.5, ticks=levels[[0, 2, 4, 6, 8, 10]], format=\"{x:1.1f}\", drawedges=False, fraction=0)\n",
" cax.add_patch(mpl.patches.Rectangle((0,-0.1), 1, 1.1, facecolor=\"w\", edgecolor=\"none\", alpha=0.5, zorder=200, transform=cax.transAxes))\n",
" cb.set_label(\"Geometric\\nstandard\\ndeviation\\n[μm]\", rotation=0, labelpad=25, va=\"center\", size=11)\n",
"\n",
"elif plot_param == \"mean\":\n",
" \n",
" levels = np.geomspace(0.1, 1000, 11, endpoint=False)\n",
" # levels = np.geomspace(0.1, 1000, 13, endpoint=True)\n",
" facecolors = cmap(np.linspace(0,1,len(levels)-1))\n",
" im = ax.contourf(xx, yy, dgg, colors=facecolors, levels=levels, extend=\"neither\", alpha=1, zorder=1)\n",
" cax = fig.add_axes((0.75, 0.67, 0.025, 0.25))\n",
" cb = fig.colorbar(im, cax=cax, shrink=0.5, ticks=levels[[0, 2, 4, 6, 8, 10]], format=\"{x:1.1f}\", drawedges=False, fraction=0)\n",
" cax.add_patch(mpl.patches.Rectangle((0,-0.1), 1, 1.1, facecolor=\"w\", edgecolor=\"none\", alpha=0.5, zorder=200, transform=cax.transAxes))\n",
" cb.set_label(\"Geometric\\nmean\\ndiameter\\n[μm]\", rotation=0, labelpad=25, va=\"center\", size=11)\n",
"\n",
"else:\n",
" # hack to get the same axes scaling as the others???\n",
" levels = np.linspace(1, 47, 11)\n",
" facecolors = cmap(np.linspace(0,1,len(levels)-1))\n",
" im = ax.contourf([[0.,0.],[1, 1]], [[0., 0.],[0.866, 0.866]], [[1,46],[1,46]], colors=facecolors, levels=levels, extend=\"neither\", alpha=0, zorder=1)\n",
" \n",
"txt_legend1 = \"\"\n",
"txt_legend2 = \"\"\n",
"\n",
"for i, (class_name, (clay, silt)) in enumerate(class_label_pos.items()):\n",
" sand = 100 - (clay+silt)\n",
" x,y = get_cartesian_coords(clay, silt, sand)\n",
"\n",
" ax.text(\n",
" x, y, class_name.split(\")\")[0], weight=\"bold\", color=\"k\", \n",
" size=14, va=\"center\", ha=\"center\", alpha=0.5,\n",
" path_effects=[PathEffects.withStroke(linewidth=2, foreground=\"w\", alpha=.5)],\n",
" )\n",
"\n",
" txt_legend1 += f\"{class_name.split(\" \")[0]}\\n\"\n",
" txt_legend2 += f\"{class_name.title().split(\") \")[1]}\\n\"\n",
" \n",
"ax.text(\n",
" 0.07, 0.95, txt_legend1, va=\"top\", ha=\"right\", \n",
" size=11, weight=\"normal\", alpha=0.8, transform=ax.transAxes,\n",
" path_effects=[PathEffects.withStroke(linewidth=2, foreground=\"w\", alpha=.4)],\n",
")\n",
"\n",
"ax.text(\n",
" 0.075, 0.95, txt_legend2, va=\"top\", ha=\"left\", \n",
" size=11, weight=\"normal\", alpha=0.8, transform=ax.transAxes,\n",
" path_effects=[PathEffects.withStroke(linewidth=2, foreground=\"w\", alpha=.4)],\n",
")\n",
"\n",
"for boundaries, color in [\n",
" (clay_boundaries, clay_color), \n",
" (silt_boundaries, silt_color), \n",
" (sand_boundaries, sand_color),\n",
"]:\n",
" for dg, σg in boundaries:\n",
" ax.plot(dg, σg, \"-\", lw=.5, alpha=1, color=color)\n",
" \n",
"for name, nodes in class_bounds.items():\n",
" for dg, σg in nodes:\n",
" ax.plot(dg, σg, \"k-\", lw=1, clip_on=False)\n",
"\n",
"func = get_cartesian_coords\n",
"\n",
"lbl_min = 20\n",
"lbl_max = 100\n",
"lbl_step = 20\n",
"\n",
"debug = False\n",
"\n",
"annotate(\n",
" ax, label_min=lbl_min, label_max=lbl_max, label_step=lbl_step, position_offset=-15, ha=\"right\", va=\"center\", \n",
" position_rotation_offset=0, txt_rotation_offset=0, color=clay_color, plot_marker=True, axis=\"Clay\", \n",
" txt=\"{value:1.0f}%\", coord_conversion=func, marker_angle=None, align_with=\"grid\", debug=debug,\n",
")\n",
"annotate(\n",
" ax, label_min=lbl_min, label_max=lbl_max, label_step=lbl_step, position_offset=15, ha=\"left\", va=\"center\", \n",
" position_rotation_offset=0, txt_rotation_offset=0, color=silt_color,plot_marker=True, axis=\"Silt\", \n",
" txt=\"{value:1.0f}%\", coord_conversion=func, marker_angle=None, align_with=\"grid\", debug=debug,\n",
")\n",
"annotate(\n",
" ax, label_min=lbl_min, label_max=lbl_max, label_step=lbl_step, position_offset=15, ha=\"left\", va=\"center\", \n",
" position_rotation_offset=0, txt_rotation_offset=0, color=sand_color,plot_marker=True, axis=\"Sand\", \n",
" txt=\"{value:1.0f}%\", coord_conversion=func, marker_angle=None, align_with=\"grid\", debug=debug,\n",
")\n",
"\n",
"annotate(\n",
" ax, label_min=50, label_max=50, label_step=50, position_offset=25, ha=\"center\", va=\"center\", position_rotation_offset=90, \n",
" txt_rotation_offset=0, color=clay_color, plot_marker=False, axis=\"Clay\", txt=r\"{axis}$\\rightarrow$\", \n",
" coord_conversion=func, align_with=\"boundary\", debug=debug,\n",
")\n",
"\n",
"annotate(\n",
" ax, label_min=50, label_max=50, label_step=50, position_offset=25, ha=\"center\", va=\"center\", position_rotation_offset=90, \n",
" txt_rotation_offset=0, color=silt_color, plot_marker=False, axis=\"Silt\", txt=r\"{axis}$\\rightarrow$\", \n",
" coord_conversion=func, align_with=\"boundary\", debug=debug,\n",
")\n",
"annotate(\n",
" ax, label_min=50, label_max=50, label_step=50, position_offset=25, ha=\"center\", va=\"center\", position_rotation_offset=90, \n",
" txt_rotation_offset=180, color=sand_color, plot_marker=False, axis=\"Sand\", txt=r\"$\\leftarrow${axis}\", \n",
" coord_conversion=func, align_with=\"boundary\", debug=debug,\n",
")\n",
"\n",
"ax.grid(False)\n",
"ax.set_xticklabels(map(lambda x: f\"{x} mm\", ax.get_xticks()))\n",
"ax.set_yticklabels(map(lambda x: f\"{x:1.0f} mm\", ax.get_yticks()))\n",
"\n",
"if plot_param == \"mean\":\n",
" fn = r\"ternary_usda_soil_texture_mean_v3.png\"\n",
"elif plot_param == \"std\":\n",
" fn = r\"ternary_usda_soil_texture_std_v3.png\"\n",
"elif plot_param == \"blank\":\n",
" fn = r\"ternary_usda_soil_texture_v3.png\"\n",
"\n",
"fig.savefig(fn, dpi=120, bbox_inches=0, pad_inches=0)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment