{ "cells": [ { "cell_type": "markdown", "id": "fa336140", "metadata": { "nbsphinx": "hidden" }, "source": [ "This notebook is part of the *orix* documentation https://orix.readthedocs.io. Links to the documentation won’t work from the notebook." ] }, { "cell_type": "markdown", "id": "2e8bd570", "metadata": {}, "source": [ "# Stereographic projection\n", "\n", "In this tutorial, we will plot vectors in the stereographic projection.\n", "\n", "The stereographic projection maps a sphere onto a plane and preserves angles at which curves meet.\n", "In orix, the projection is used to project unit [Vector3d](../reference/generated/orix.vector.Vector3d.rst) objects onto the equatorial plane represented in spherical coordinates.\n", "These are the azimuth angle $\\phi$, in the range $[0^{\\circ}, 360^{\\circ}]$, and the polar angle $\\theta$, in the range $[0^{\\circ}, 90^{\\circ}]$ on the upper hemisphere and $[90^{\\circ}, 180^{\\circ}]$ on the lower hemisphere.\n", "The projection is implemented in [StereographicProjection](../reference/generated/orix.projections.StereographicProjection.rst), together with the [InverseStereographicProjection](../reference/generated/orix.projections.InverseStereographicProjection.rst).\n", "These are used in the [StereographicPlot](../reference/generated/orix.plot.StereographicPlot.rst), which extends Matplotlib's projections framework for plotting of `Vector3d` objects.\n", "\n", "The projection can be used \"from Matplotlib\", meaning that `Vector3d` objects or spherical coordinates ($\\phi$, $\\theta$) are passed to Matplotlib functions.\n", "While this is the most customizable way of plotting vectors, [Vector3d.scatter()](../reference/generated/orix.vector.Vector3d.scatter.rst) and [Vector3d.draw_circle()](../reference/generated/orix.vector.Vector3d.draw_circle.rst) methods are also provided for quick and easy plotting." ] }, { "cell_type": "code", "execution_count": null, "id": "2db2bd9f", "metadata": {}, "outputs": [], "source": [ "# Exchange \"inline\" for:\n", "# \"qt5\" for interactive plotting from the pyqt package\n", "# \"notebook\" for inline interactive plotting when running on Binder\n", "%matplotlib inline\n", "\n", "import tempfile\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from orix import plot # Register orix' projections with Matplotlib\n", "from orix.vector import Vector3d\n", "\n", "\n", "# We'll want our plots to look a bit larger than the default size\n", "plt.rcParams.update(\n", " {\n", " \"figure.figsize\": (10, 5),\n", " \"lines.markersize\": 10,\n", " \"font.size\": 20,\n", " \"axes.grid\": False,\n", " }\n", ")" ] }, { "cell_type": "markdown", "id": "30dc7a6a-131d-4754-94dd-596eff4554a7", "metadata": {}, "source": [ "## Plot vectors\n", "\n", "Plot three vectors on the upper hemisphere with [Vector3d.scatter()](../reference/generated/orix.vector.Vector3d.scatter.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "969ecff1", "metadata": {}, "outputs": [], "source": [ "v1 = Vector3d([[1, 0, 0], [0, 1, 0], [0, 0, 1]])\n", "v1.scatter()" ] }, { "cell_type": "markdown", "id": "75d763d5", "metadata": {}, "source": [ "The spherical coordinates $(\\phi, \\theta)$ can be viewed while moving the cursor over the equatorial plane when plotting interactively (with `qt5`, `notebook`, or similar backends).\n", "\n", "We can return the figure to save it or continue building upon it by passing `return_figure=True`" ] }, { "cell_type": "code", "execution_count": null, "id": "79c46318", "metadata": {}, "outputs": [], "source": [ "temp_dir = tempfile.mkdtemp() + \"/\" # Write to a temporary directory\n", "vector_file = temp_dir + \"vectors.png\"\n", "fig0 = v1.scatter(c=[\"r\", \"g\", \"b\"], return_figure=True)\n", "fig0.savefig(vector_file, bbox_inches=\"tight\", pad_inches=0)" ] }, { "cell_type": "markdown", "id": "33c2503e", "metadata": {}, "source": [ "From Matplotlib with [StereographicPlot.scatter()](../reference/generated/orix.plot.StereographicPlot.scatter.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "2efffe3c", "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(subplot_kw={\"projection\": \"stereographic\"})\n", "ax.scatter(v1, c=[\"r\", \"g\", \"b\"])" ] }, { "cell_type": "markdown", "id": "02c31caf", "metadata": {}, "source": [ "As usual with Matplotlib, the figure axes can be obtained from the returned figure" ] }, { "cell_type": "code", "execution_count": null, "id": "0655d90e", "metadata": {}, "outputs": [], "source": [ "fig0.axes[0]" ] }, { "cell_type": "markdown", "id": "04d23514", "metadata": {}, "source": [ "Let's turn on the azimuth $\\phi$ and polar $\\theta$ grid by updating the Matplotlib preferences" ] }, { "cell_type": "code", "execution_count": null, "id": "debb1d3c", "metadata": {}, "outputs": [], "source": [ "plt.rcParams[\"axes.grid\"] = True" ] }, { "cell_type": "markdown", "id": "ceb335ae", "metadata": {}, "source": [ "## Upper and/or lower hemisphere\n", "\n", "We can plot vectors impinging on the upper hemisphere and/or the lower hemisphere by passing \"upper\", \"lower\", or \"both\" to the `hemisphere` parameter in [Vector3d.scatter()](../reference/generated/orix.vector.Vector3d.scatter.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "bbf35de5", "metadata": {}, "outputs": [], "source": [ "v2 = Vector3d([[1, 1, 2], [1, 1, -1]])\n", "\n", "fig_kwargs = {\"figsize\": (5, 5)}\n", "labels = [\"x\", \"y\", None]\n", "v2.scatter(\n", " axes_labels=labels, show_hemisphere_label=True, figure_kwargs=fig_kwargs\n", ") # default hemisphere is \"upper\"\n", "v2.scatter(\n", " hemisphere=\"lower\",\n", " axes_labels=labels,\n", " show_hemisphere_label=True,\n", " figure_kwargs=fig_kwargs,\n", ")\n", "v2.scatter(hemisphere=\"both\", axes_labels=labels, c=[\"C0\", \"C1\"])" ] }, { "cell_type": "markdown", "id": "a8732363", "metadata": {}, "source": [ "When `reproject=True`, vectors impinging on the opposite hemisphere are reprojected onto the visible hemisphere after reflection in the projection plane." ] }, { "cell_type": "code", "execution_count": null, "id": "3e7c076d", "metadata": {}, "outputs": [], "source": [ "reproject_scatter_kwargs = {\"marker\": \"o\", \"fc\": \"None\", \"ec\": \"r\", \"s\": 150}\n", "v2.scatter(\n", " axes_labels=labels,\n", " show_hemisphere_label=True,\n", " figure_kwargs=fig_kwargs,\n", " reproject=True,\n", " reproject_scatter_kwargs=reproject_scatter_kwargs,\n", ")" ] }, { "cell_type": "markdown", "id": "e9c8c664", "metadata": {}, "source": [ "From Matplotlib by setting the [StereographicPlot.hemisphere](../reference/generated/orix.plot.StereographicPlot.hemisphere.rst) attribute. Remember to set the hemisphere *before* calling `scatter()`" ] }, { "cell_type": "code", "execution_count": null, "id": "0250ba7f", "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(ncols=2, subplot_kw={\"projection\": \"stereographic\"})\n", "\n", "ax[0].scatter(v2, c=\"C0\") # blue\n", "ax[0].show_hemisphere_label(color=\"C0\")\n", "ax[0].set_labels(zlabel=None)\n", "\n", "ax[1].hemisphere = \"lower\" # or \"upper\"\n", "ax[1].scatter(v2, c=\"C1\") # orange\n", "ax[1].show_hemisphere_label()\n", "ax[1].set_labels(\"RD\", \"TD\", \"ND\", size=15)" ] }, { "cell_type": "markdown", "id": "637daa9d", "metadata": {}, "source": [ "## Control grid\n", "\n", "Whether to show the grid or not can be set globally via [Matplotlib rcParams](https://matplotlib.org/stable/tutorials/introductory/customizing.html) as we did above, or controlled via the parameters `grid`, True/False, and `grid_resolution`, a tuple with (azimuth, polar) resolution in degrees, to [Vector3d.scatter()](../reference/generated/orix.vector.Vector3d.scatter.rst).\n", "Default grid resolution is $10^{\\circ}$ for both grids." ] }, { "cell_type": "code", "execution_count": null, "id": "694a6016", "metadata": {}, "outputs": [], "source": [ "v3 = Vector3d([[0, 0, 1], [1, 0, 1], [1, 1, 1]])\n", "v3.scatter(grid_resolution=(30, 15))" ] }, { "cell_type": "markdown", "id": "f2aaab56", "metadata": {}, "source": [ "These can also be set after the figure is created by returning the figure" ] }, { "cell_type": "code", "execution_count": null, "id": "d82125fa", "metadata": {}, "outputs": [], "source": [ "v4 = Vector3d(np.append(v3.data, -v3.data, axis=0))\n", "\n", "fig4 = v4.scatter(hemisphere=\"both\", return_figure=True)\n", "ax0, ax1 = fig4.axes\n", "ax0.stereographic_grid(azimuth_resolution=45)\n", "ax1.stereographic_grid(polar_resolution=5)" ] }, { "cell_type": "markdown", "id": "f2915559", "metadata": {}, "source": [ "We can also remove the grid if desirable" ] }, { "cell_type": "code", "execution_count": null, "id": "405e571a", "metadata": {}, "outputs": [], "source": [ "v4.scatter(hemisphere=\"both\", grid=False)" ] }, { "cell_type": "markdown", "id": "f269736b", "metadata": {}, "source": [ "From Matplotlib, the polar and azimuth grid resolution can be set either upon axis initialization or after the axis is created using [StereographicPlot.stereographic_grid()](../reference/generated/orix.plot.StereographicPlot.stereographic_grid.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "d7143bd4", "metadata": {}, "outputs": [], "source": [ "subplot_kw = {\n", " \"projection\": \"stereographic\",\n", " \"polar_resolution\": 10,\n", " \"azimuth_resolution\": 10,\n", "}\n", "fig, ax = plt.subplots(ncols=3, figsize=(15, 20), subplot_kw=subplot_kw)\n", "\n", "ax[0].scatter(v4)\n", "ax[0].show_hemisphere_label()\n", "\n", "ax[1].hemisphere = \"lower\"\n", "ax[1].show_hemisphere_label()\n", "ax[1].scatter(v4)\n", "ax[1].stereographic_grid(azimuth_resolution=90, polar_resolution=90)\n", "\n", "ax[2].scatter(v4)\n", "ax[2].stereographic_grid(False)" ] }, { "cell_type": "markdown", "id": "b41401bb", "metadata": {}, "source": [ "## Annotate vectors\n", "\n", "Vectors can be annotated by passing a list of strings to the `vector_labels` parameter in [Vector3d.scatter()](../reference/generated/orix.vector.Vector3d.scatter.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "a0ad745b", "metadata": {}, "outputs": [], "source": [ "v4.scatter(\n", " hemisphere=\"both\",\n", " vector_labels=[str(vi).replace(\" \", \"\") for vi in v4.data],\n", " text_kwargs={\"size\": 15, \"offset\": (0, 0.05)},\n", ")" ] }, { "cell_type": "markdown", "id": "f5ad903f-39a1-41b4-92b4-73eea1de6b24", "metadata": {}, "source": [ "If we have integer vectors, like here, we can get nicely formatted labels using [plot.format_labels()](../reference/generated/orix.plot.format_labels.rst)." ] }, { "cell_type": "code", "execution_count": null, "id": "882988c9-102e-4348-aafb-bb53ff39b448", "metadata": { "tags": [] }, "outputs": [], "source": [ "labels = plot.format_labels(v4.data, brackets=(\"[\", \"]\"))" ] }, { "cell_type": "markdown", "id": "7d1f28fb", "metadata": {}, "source": [ "From Matplotlib, by looping over the vectors and adding text markers using [StereographicPlot.text()](../reference/generated/orix.plot.StereographicPlot.text.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "a68fb09d", "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(ncols=2, subplot_kw={\"projection\": \"stereographic\"})\n", "\n", "ax[1].hemisphere = \"lower\"\n", "ax[0].show_hemisphere_label()\n", "ax[1].show_hemisphere_label()\n", "ax[0].scatter(v4)\n", "ax[1].scatter(v4)\n", "\n", "bbox = {\"boxstyle\": \"round,pad=0\", \"ec\": \"none\", \"fc\": \"w\", \"alpha\": 0.5}\n", "for i in range(v4.size):\n", " ax[0].text(v4[i], s=labels[i], size=15, offset=(0, 0.05), bbox=bbox)\n", " ax[1].text(\n", " v4[i], s=labels[i], size=15, va=\"top\", offset=(0, -0.06), bbox=bbox\n", " )" ] }, { "cell_type": "markdown", "id": "2a378c5d", "metadata": {}, "source": [ "## Pass spherical coordinates\n", "\n", "We can pass azimuth $\\phi$ and polar $\\theta$ angles instead of passing vectors.\n", "This only works in the \"from Matplotlib\" way" ] }, { "cell_type": "code", "execution_count": null, "id": "d746fe7d", "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(5, 5))\n", "ax = fig.add_subplot(projection=\"stereographic\")\n", "azimuth = np.deg2rad([0, 60, 180])\n", "polar = np.deg2rad([0, 60, 60])\n", "ax.scatter(azimuth, polar, c=[\"C0\", \"C1\", \"C2\"], s=200)\n", "ax.set_labels(\"RD\", \"TD\", None)\n", "ax.show_hemisphere_label()" ] }, { "cell_type": "markdown", "id": "a03fc417", "metadata": {}, "source": [ "Here, we also passed `None` to [StereographicPlot.set_labels()](../reference/generated/orix.plot.StereographicPlot.set_labels.rst) so that the Z axis label is not shown.\n", "\n", "Producing the exact same plot with `Vector3d.scatter()`" ] }, { "cell_type": "code", "execution_count": null, "id": "9b21afe4", "metadata": {}, "outputs": [], "source": [ "Vector3d.from_polar(azimuth=azimuth, polar=polar).scatter(\n", " axes_labels=[\"RD\", \"TD\", None],\n", " show_hemisphere_label=True,\n", " figure_kwargs={\"figsize\": (5, 5)},\n", " c=[\"C0\", \"C1\", \"C2\"],\n", " s=200,\n", ")" ] }, { "cell_type": "markdown", "id": "bc51dfd4", "metadata": {}, "source": [ "## Draw great and small circles\n", "\n", "We can draw the trace of a plane perpendicular to a vector using [Vector3d.draw_circle()](../reference/generated/orix.vector.Vector3d.draw_circle.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "c4a28a90", "metadata": {}, "outputs": [], "source": [ "v6 = Vector3d.from_polar(\n", " azimuth=[0, 60, 180], polar=[0, 60, 60], degrees=True\n", ")\n", "\n", "colors = [\"C0\", \"C1\", \"C2\"]\n", "fig6 = v6.scatter(\n", " c=colors,\n", " s=200,\n", " axes_labels=[\"RD\", \"TD\", None],\n", " show_hemisphere_label=True,\n", " return_figure=True,\n", ")\n", "v6.draw_circle(color=colors, linewidth=2, figure=fig6)" ] }, { "cell_type": "markdown", "id": "d6f40fd2", "metadata": {}, "source": [ "Let's also add the vector perpendicular to the last two vectors and its great circle.\n", "We can add to the previous figure by passing the returned figure into the `figure` parameter in [Vector3d.scatter()](../reference/generated/orix.vector.Vector3d.scatter.rst) and [Vector3d.draw_circle()](../reference/generated/orix.vector.Vector3d.draw_circle.rst)" ] }, { "cell_type": "code", "execution_count": null, "id": "4026c090", "metadata": { "nbsphinx-thumbnail": { "tooltip": "Visualizing 3D vectors in 2D" }, "tags": [ "nbsphinx-thumbnail" ] }, "outputs": [], "source": [ "v7 = v6[1].cross(v6[2])\n", "v7.scatter(c=\"xkcd:pink\", marker=\"p\", s=250, figure=fig6)\n", "v7.draw_circle(color=\"xkcd:pink\", linestyle=\"--\", linewidth=3, figure=fig6)\n", "\n", "# We must redraw the figure when plotting in non-interactive mode\n", "fig6" ] }, { "cell_type": "markdown", "id": "36c92e03", "metadata": {}, "source": [ "From Matplotlib using [StereograhicPlot.draw_circle()](../reference/generated/orix.plot.StereographicPlot.draw_circle.rst), also showing a small circle (not perpendicular, but at a $45^{\\circ}$ angle in this case)" ] }, { "cell_type": "code", "execution_count": null, "id": "aa8c9e6a", "metadata": {}, "outputs": [], "source": [ "fig = plt.figure(figsize=(5, 5))\n", "ax = fig.add_subplot(projection=\"stereographic\")\n", "colors = [\"C0\", \"C1\", \"C2\"]\n", "azimuth = np.deg2rad([0, 60, 180])\n", "polar = np.deg2rad([0, 60, 60])\n", "ax.scatter(azimuth, polar, c=colors, s=200)\n", "ax.set_labels(\"RD\", \"TD\", None)\n", "ax.show_hemisphere_label()\n", "ax.draw_circle(azimuth, polar, color=colors, linewidth=2)\n", "\n", "# Let's also add the vector perpendicular to the last two vectors and its\n", "# great circle\n", "v6 = Vector3d.from_polar(azimuth=azimuth[1:], polar=polar[1:])\n", "v7 = v6[0].cross(v6[1])\n", "ax.scatter(v7, c=\"xkcd:pink\", marker=\"p\", s=250)\n", "ax.draw_circle(\n", " v7,\n", " color=\"xkcd:pink\",\n", " linestyle=\"--\",\n", " linewidth=3,\n", " opening_angle=0.25 * np.pi,\n", ")" ] }, { "cell_type": "markdown", "id": "250cb58c-7dc3-4fe5-9f2f-a7032b990565", "metadata": {}, "source": [ "We can also draw the part of the circles only visible on the other hemisphere by passing `reproject=True`" ] }, { "cell_type": "code", "execution_count": null, "id": "51d8cbfc-15a9-43d2-9fc2-5fa5929f1726", "metadata": {}, "outputs": [], "source": [ "v8 = Vector3d([(1, 1, 1), (-1, 1, 1)])\n", "fig = v8.scatter(axes_labels=[\"X\", \"Y\"], return_figure=True, c=[\"C0\", \"C1\"])\n", "v8.draw_circle(\n", " reproject=True,\n", " figure=fig,\n", " color=[\"C0\", \"C1\"],\n", " opening_angle=np.deg2rad([90, 45]),\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "8112f4eb", "metadata": { "nbsphinx": "hidden" }, "outputs": [], "source": [ "# Remove files written to disk in this user guide\n", "import os\n", "\n", "os.remove(vector_file)\n", "os.rmdir(temp_dir)" ] } ], "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.11.6" } }, "nbformat": 4, "nbformat_minor": 5 }