{ "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 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=dict(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 fig1 above" ] }, { "cell_type": "code", "execution_count": null, "id": "0655d90e", "metadata": {}, "outputs": [], "source": [ "fig0.axes[0].name" ] }, { "cell_type": "markdown", "id": "04d23514", "metadata": {}, "source": [ "Let's turn on the azimuth and polar 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 = dict(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 = dict(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=dict(projection=\"stereographic\"))\n", "\n", "ax[0].scatter(v2, c=\"C0\") # blue\n", "ax[0].show_hemisphere_label(color=\"C0\") # Pass keyword arguments to text()\n", "ax[0].set_labels(zlabel=None)\n", "\n", "ax[1].hemisphere = \"lower\" # /\"south\", or \"upper\"/\"north\"\n", "ax[1].scatter(v2, c=\"C1\") # orange\n", "ax[1].show_hemisphere_label()\n", "ax[1].set_labels(\n", " \"RD\", \"TD\", \"ND\", size=15\n", ") # Pass keyword arguments to text()" ] }, { "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 = dict(\n", " projection=\"stereographic\", polar_resolution=10, 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=30, polar_resolution=15)\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=dict(size=15),\n", ")" ] }, { "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=dict(projection=\"stereographic\"))\n", "\n", "format_vector = lambda v: str(v.data[0]).replace(\" \", \"\")\n", "\n", "ax[0].scatter(v4)\n", "ax[0].show_hemisphere_label()\n", "for vi in v4:\n", " ax[0].text(vi, s=format_vector(vi), size=15)\n", "\n", "ax[1].hemisphere = \"lower\"\n", "ax[1].scatter(v4)\n", "ax[1].show_hemisphere_label()\n", "for vi in v4[:2]:\n", " ax[1].text(vi, s=format_vector(vi), size=15)\n", "for vi in v4[2:]:\n", " ax[1].text(vi, s=format_vector(vi), size=15)" ] }, { "cell_type": "markdown", "id": "2a378c5d", "metadata": {}, "source": [ "## Pass spherical coordinates\n", "\n", "We can pass azimuth and polar 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, ax = plt.subplots(\n", " figsize=(5, 5), subplot_kw=dict(projection=\"stereographic\")\n", ")\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=dict(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, ax = plt.subplots(\n", " figsize=(5, 5), subplot_kw=dict(projection=\"stereographic\")\n", ")\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.10.8" } }, "nbformat": 4, "nbformat_minor": 5 }