diff --git a/book/_toc.yml b/book/_toc.yml index 5421fc9..771fae1 100644 --- a/book/_toc.yml +++ b/book/_toc.yml @@ -13,3 +13,4 @@ parts: - file: markdown - file: notebooks - file: markdown-notebooks + - file: first-figure diff --git a/book/first-figure.ipynb b/book/first-figure.ipynb new file mode 100644 index 0000000..121242a --- /dev/null +++ b/book/first-figure.ipynb @@ -0,0 +1,595 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "79c5f84e-3c5b-49f0-80a6-d4bd43aed2f8", + "metadata": {}, + "source": [ + "# Anatomy of a PyGMT figure\n", + "\n", + "This tutorial will cover the fundamental concepts behind making figures with PyGMT: importing the package, creating a blank figure, drawing coastlines, drawing a map frame, choosing a projection, and adding some data to the map.\n", + "\n", + "Let's get started!" + ] + }, + { + "cell_type": "markdown", + "id": "dc74cdd5-e7b0-4365-a3c2-97d71d9f22f1", + "metadata": {}, + "source": [ + "## Importing \n", + "\n", + "First thing to do is load PyGMT (`import`) so that we can access its functionality. PyGMT has a flat package layout, meaning that you can access everything in it with a single `import`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19ef3de2-9333-4b0e-81ac-e518415ab9fe", + "metadata": {}, + "outputs": [], + "source": [ + "import pygmt" + ] + }, + { + "cell_type": "markdown", + "id": "1faeb548-5d01-47e7-a91b-0df582a330b1", + "metadata": {}, + "source": [ + ":::{tip}\n", + "In Jupyter, you can find out what PyGMT has to offer by typing into a code cell `pygmt.` and hitting the TAB key. This will give you a menu of all our functions.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "81e6ae36-f6b1-49d2-af5a-44d591366a1a", + "metadata": {}, + "source": [ + "## Creating a figure\n", + "\n", + "All plotting is handled by the `pygmt.Figure` class. Here is a good analogy for it:\n", + "\n", + "> `pygmt.Figure` is a blank canvas onto which you can lay down plot elements in order.\n", + "\n", + "Here is how you can create a figure:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "024a9121-9c6d-4378-ba1b-d25d676c8df0", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()" + ] + }, + { + "cell_type": "markdown", + "id": "7e966881-5ee7-43dc-a288-d550901dd0d7", + "metadata": {}, + "source": [ + "Now that we have a blank canvas in the `fig` variable, we can start laying down plot elements that we want to show. We'll start by putting down some coast lines around Japan." + ] + }, + { + "cell_type": "markdown", + "id": "bed48601-63c5-485e-89a6-e55f37e26bdb", + "metadata": {}, + "source": [ + "## Drawing coastlines\n", + "\n", + "Before we can actually include anything in our figure, we need to specify the geographic bounding box that contains the data/features we want to plot. This bounding box is referenced throughout PyGMT as the **region** of a figure and it has the format of a list containing the **West, East, South, and North** (WESN) coordinates of the bounding box." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "def9a5de-1756-4bd3-8b3e-81507e42fdfc", + "metadata": {}, + "outputs": [], + "source": [ + "region = [125, 155, 30, 55]" + ] + }, + { + "cell_type": "markdown", + "id": "1f2db4e2-c7f5-4ff6-9aa8-8ae1d14f109f", + "metadata": {}, + "source": [ + "Now that our region is defined, we can lay down the coastlines for this region using the `coast` method of `pygmt.Figure`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d384b527-8ed5-4eb2-8708-d478d7cbcca8", + "metadata": {}, + "outputs": [], + "source": [ + "fig.coast(region=region, shorelines=True)" + ] + }, + { + "cell_type": "markdown", + "id": "93df5653-88cc-48e7-a667-a411e1ea8b69", + "metadata": {}, + "source": [ + "And to see what the figure looks like, we call the `show` method of `pygmt.Figure`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11881d49-cb8b-48df-ab86-c377e34ac7ed", + "metadata": {}, + "outputs": [], + "source": [ + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "692368d0-f1ff-4ebf-8759-37b10adb764b", + "metadata": {}, + "source": [ + ":::{seealso}\n", + "On Jupyter, `show` will embed a PNG of the figure directly into the notebook. But it can also open a PDF in an external viewer, which is probably what you want if you're using a plain Python script. See the documentation for [`pygmt.Figure.show`](https://www.pygmt.org/v0.6.1/api/generated/pygmt.Figure.show.html#pygmt.Figure.show) for more information.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "92171f29-21f1-4966-bc5d-0b596cba7462", + "metadata": {}, + "source": [ + "Beyond the outlines, we can also color the land and water regions to make them stand out. Lets start with the water." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b891be6c-db39-4dee-84cc-4df98c79ab6a", + "metadata": {}, + "outputs": [], + "source": [ + "fig.coast(water=\"lightblue\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "1576b6d6-e3f2-489f-bd9d-4838a07928a7", + "metadata": {}, + "source": [ + "And now add the land in a light green color." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b715796-fa3b-45ba-bd10-7f1fa086ec0d", + "metadata": {}, + "outputs": [], + "source": [ + "fig.coast(land=\"lightgreen\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "8e150571-07b7-4140-af7a-78cd8244a357", + "metadata": {}, + "source": [ + "A few things to note here:\n", + "\n", + "1. We added the colored land and water on top of what was already on our canvas (the shorelines), which means that they are still there but we don't see them because they are below the solid colors.\n", + "1. We didn't need to provide a `region` this time around because PyGMT remembers the last region that was provided. But you could provide one if you want to use a different value.\n", + "\n", + "If we want to have a figure with the shorelines laid out on top of the solid colors, we can make a new figure and add them in the correct order." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "db6723b4-1fa4-459c-8e7c-40af6301e09a", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "# First draw the solid colors\n", + "fig.coast(region=region, land=\"lightgreen\", water=\"lightblue\") # Pass region to the first plot element\n", + "# Then lay down the shorelines on top\n", + "fig.coast(shorelines=True)\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "a3615a91-697b-4cc5-9043-b0d2d729c1e2", + "metadata": {}, + "source": [ + "Since these are both part of the same method (`coast`) we can actually combine both into the same call and PyGMT will know that it needs to plot the shorelines on top." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29d50636-c98d-4fd2-96d5-70354aea65ae", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "fig.coast(region=region, shorelines=True, land=\"lightgreen\", water=\"lightblue\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "793e6eb3-cb67-4482-9214-d15aaccdd14f", + "metadata": {}, + "source": [ + "Alright, now we have a lovely figure with colored land and water plus some shorelines. But what are the coordinates associated with this map? Lets add a map frame to find out." + ] + }, + { + "cell_type": "markdown", + "id": "bb465476-c73c-4e13-9da8-82982037b6f9", + "metadata": {}, + "source": [ + "## Drawing a map frame\n", + "\n", + "Adding a nice frame with coordinates, ticks, and labels is one of the jobs of the `basemap` method of `pygmt.Figure`. Here, we'll use it to add automatic annotations (`\"a\"`) around the figure we just made above. It will be the last item we lay on our figure canvas to guarantee that it sits above any other plot elements." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "683d3190-0c5d-4240-af56-3f71e171cd9a", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "fig.coast(region=region, shorelines=True, land=\"lightgreen\", water=\"lightblue\")\n", + "fig.basemap(frame=\"a\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "62da8cfa-cbc7-4c26-830a-ab313ef62f92", + "metadata": {}, + "source": [ + "Notice that the coordinates are automatically recognized as longitude and latitude and the tick spacing is chosen sensibly as well. \n", + "\n", + "There are many different ways in which we can customize the frame, from the ticks to the interval to the labels. Here we'll only cover a few of the most common things you'd want to do. Starting with..." + ] + }, + { + "cell_type": "markdown", + "id": "476a5c34-d5ba-419d-822c-c87818fbae43", + "metadata": {}, + "source": [ + "## Adding minor ticks\n", + "\n", + "The ticks with annotations are known as \"major ticks\" (controlled by the `\"a\"`) value. You can also add automatic \"minor ticks\" which have a smaller interval and won't have annotations by adding `\"f\"` to the `frame` argument." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2806711-a174-472d-84c7-5cb02d941f32", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "fig.coast(region=region, shorelines=True, land=\"lightgreen\", water=\"lightblue\")\n", + "fig.basemap(frame=\"af\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "b3e7f5be-0126-48f5-8356-b00ad891523a", + "metadata": {}, + "source": [ + "The frame is now set to have both annotations and minor ticks, both of which are optional (so `frame=\"f\"` would mean only having minor un-annotated ticks). Try it out!" + ] + }, + { + "cell_type": "markdown", + "id": "8b252f24-bd39-43c0-82ab-cfb815ca10c2", + "metadata": {}, + "source": [ + "## Adding grid lines\n", + "\n", + "Grid lines are enabled by adding `\"g\"` to `frame`, just like we did for minor ticks. Again you can mix and match the three arguments `\"a\"`, `\"f\"`, and `\"g\"`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58cda76a-5d1d-4fc9-9740-1007c2bcbb5f", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "fig.coast(region=region, shorelines=True, land=\"lightgreen\", water=\"lightblue\")\n", + "fig.basemap(frame=\"afg\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "9bd6b281-c1eb-4ac4-b343-3e3bbf360247", + "metadata": {}, + "source": [ + "By default the spacing of the grid lines is the same as the annotated major ticks.\n", + "\n", + ":::{note}\n", + "Adding a frame with grid lines before you plot the colored land and water or an image will hide the grid lines beneath the subsequent plot. Make sure you put the call to `basemap` last to avoid this.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "08910ea2-f2a1-4673-a11e-78e2a64dbeaf", + "metadata": {}, + "source": [ + "## Adding a title\n", + "\n", + "To add a title to the figure, we need to pass in more than one argument to `frame`. We can do this by passing it a list instead of a single string. The extra argument for adding a title is a string with the format `\"+tMy title goes here\"`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8fdc242-9747-484d-aef2-ec618ad33579", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "fig.coast(region=region, shorelines=True, land=\"lightgreen\", water=\"lightblue\")\n", + "fig.basemap(frame=[\"afg\", \"+tCoastlines around Japan\"])\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "57568142-fe18-45a4-8b5f-32b566b7be8e", + "metadata": {}, + "source": [ + "## Choosing a projection\n", + "\n", + "By default, PyGMT will use a Mercator projection if the region seems to be geographic longitude and latitude. Many other projections are also supported, which may often be better suited for your plots (particularly around the polar regions or for larger global maps).\n", + "\n", + "For our case, let's go with a [Cassini projection](https://www.pygmt.org/v0.6.1/projections/cyl/cyl_cassini.html#sphx-glr-projections-cyl-cyl-cassini-py). To specify this, we need to pass the `projection` argument to the first plot method we call. The projection specification is a string starting with a 1-letter code for the projection followed by the projection arguments (particular to each projection) and finishing off with the physical width of the figure (in centimeters or inches, usually). For the Cassini projection, this is what it would look like: `projection=\"C142.5/40/15c\"` in which `C` is for Cassini projection, `142.5` is the central longitude of the projection set to the center of our region, `40` is the same for the central latitude, and `15c` means the plot will be 15 centimeters wide on the page (this influences the relative size of fonts and tick labels). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0f88d938-9451-46cc-9f49-0e4e57bfb2be", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "fig.coast(\n", + " projection=\"C140/40/15c\", \n", + " region=region, \n", + " land=\"lightgreen\", \n", + " water=\"lightblue\",\n", + " shorelines=True,\n", + ")\n", + "fig.basemap(frame=[\"afg\", \"+tCoastlines around Japan\"])\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "3a16f6ec-03e6-48dc-b6af-04234858a5d9", + "metadata": {}, + "source": [ + ":::{seealso}\n", + "The [Projections Gallery](https://www.pygmt.org/v0.6.1/projections/index.html) has examples of each projection along with a description of their parameters, properties, and use cases.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "688e32af-2f0e-4c9e-bacb-2b1324df50de", + "metadata": {}, + "source": [ + "## Adding some data to the map\n", + "\n", + "Finally, we have a great base map onto which we can plot some data. PyGMT offers a variety of example datasets that we can easily fetch to try things out. Since we're looking at Japan, we can use the `japan-quakes` data to get a table of earthquake hypocenters and magnitudes around this area. The data are returned in a `pandas.DataFrame` to make it easier to handle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12555f0a-363b-4235-a2d4-d5eeda1f4cb0", + "metadata": {}, + "outputs": [], + "source": [ + "data = pygmt.datasets.load_sample_data(name=\"japan_quakes\")\n", + "data" + ] + }, + { + "cell_type": "markdown", + "id": "56ed9a71-f19c-47de-a2f4-342e25b84141", + "metadata": {}, + "source": [ + ":::{seealso}\n", + "Use function [`pygmt.datasets.list_sample_data`](https://www.pygmt.org/v0.6.1/api/generated/pygmt.datasets.list_sample_data.html#pygmt.datasets.list_sample_data) to get a list of all datasets available.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "4d876269-58fc-4cae-9d13-75672639b590", + "metadata": {}, + "source": [ + "To add point and line data to a figure, use the `plot` method of `pygmt.Figure`. Lets start by plotting the earthquake epicenters. To do this, we'll pass the data longitude and latitude coordinates as the `x` and `y` arguments to `plot`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e67e865c-2fd6-4470-86e0-2eea9a448599", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "fig.coast(\n", + " projection=\"C140/40/15c\", \n", + " region=region, \n", + " land=\"lightgreen\", \n", + " water=\"lightblue\",\n", + " shorelines=True,\n", + ")\n", + "fig.plot(x=data.longitude, y=data.latitude)\n", + "fig.basemap(frame=[\"afg\", \"+tEarthquakes around Japan\"])\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "d46dd1e0-442c-4378-8a51-ba88b125c142", + "metadata": {}, + "source": [ + "By default, PyGMT will plot lines connecting each of the points in our data. This is probably not what we want to do given that our data are epicenters. Instead, lets plot using **circles**. This requires passing in the `style` argument to `plot`, which should be a string like `c0.3c` with the first `c` specifying a circle and `0.3c` specifying that circles should be 0.3 centimeters in size. We'll also pass in `color` to make the circles opaque with a given color (otherwise they would be just the outlines by default)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c82d18de-7760-4e43-9932-b15469aa199c", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "fig.coast(\n", + " projection=\"C140/40/15c\", \n", + " region=region, \n", + " land=\"lightgreen\", \n", + " water=\"lightblue\",\n", + " shorelines=True,\n", + ")\n", + "fig.plot(x=data.longitude, y=data.latitude, style=\"c0.3c\", color=\"white\")\n", + "fig.basemap(frame=[\"afg\", \"+tEarthquakes around Japan\"])\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "f935e455-cc67-46b4-8f98-28cb16673ab4", + "metadata": {}, + "source": [ + ":::{tip}\n", + "Pass `pen` to `plot` to control how the outline of the circles are drawn. For example, `pen=\"black\"` will draw a black outline.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "2135181f-1ea7-4bd8-b7be-0c9b4beb6726", + "metadata": {}, + "source": [ + "It would be great to also represent the depth of the earthquakes somehow. We can do this by encoding the depth information as the color of the circles. We need to make 2 changes to the code above to achieve this:\n", + "\n", + "1. Configure a \"color palette table (CPT)\" that maps colors to data values. In matplotlib, this is known as a colormap or `cmap`.\n", + "2. Tell `plot` to use the depth values and the CPT/cmap created to pain the circles.\n", + "\n", + "Step 1 is done using `pygmt.makecpt`, which takes a colormap name through the `cmap` argument and the min/max values of the data through the `series` argument.\n", + "\n", + "Step 2 is done by passing in the `data.depth_km` values to `color` and `cmap=True`, telling PyGMT to use the current CPT created through `makecpt`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9f84eda-22f6-4617-88dd-25062072d980", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "fig.coast(\n", + " projection=\"C140/40/15c\", \n", + " region=region, \n", + " land=\"lightgreen\", \n", + " water=\"lightblue\",\n", + " shorelines=True,\n", + ")\n", + "pygmt.makecpt(cmap=\"plasma\", series=[data.depth_km.min(), data.depth_km.max()])\n", + "fig.plot(x=data.longitude, y=data.latitude, style=\"c0.3c\", color=data.depth_km, cmap=True)\n", + "fig.basemap(frame=[\"afg\", \"+tEarthquakes around Japan\"])\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "1c9b1e55-47a9-4abf-9605-fb2d25ff301d", + "metadata": {}, + "source": [ + ":::{seealso}\n", + "The GMT documentation has a [list of all available colormaps/CPTs](https://docs.generic-mapping-tools.org/6.3/cookbook/cpts.html). There are many to choose from!\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "id": "66584e59-ba73-4e28-a232-9f74fe6a229c", + "metadata": {}, + "source": [ + "As a final step, we need to add a colorbar to the figure so we know what value each color represents. This is done using the `colorbar` method of `pygmt.Figure`, which takes a `frame` argument much like the one we use to set the map frame. Only this time, it sets the frame of the colorbar. To make our colorbar look nice, we'll set the frame to have automatically annotated major ticks and a label on the y-axis specifying the variable and units. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a586a12-d3c1-4688-aa5a-692d1f9a66f1", + "metadata": {}, + "outputs": [], + "source": [ + "fig = pygmt.Figure()\n", + "fig.coast(\n", + " projection=\"C140/40/15c\", \n", + " region=region, \n", + " land=\"lightgreen\", \n", + " water=\"lightblue\",\n", + " shorelines=True,\n", + ")\n", + "pygmt.makecpt(cmap=\"plasma\", series=[data.depth_km.min(), data.depth_km.max()])\n", + "fig.plot(x=data.longitude, y=data.latitude, style=\"c0.3c\", color=data.depth_km, cmap=True)\n", + "fig.basemap(frame=[\"afg\", \"+tEarthquakes around Japan\"])\n", + "fig.colorbar(frame=[\"a\", \"y+lDepth (km)\"])\n", + "fig.show()" + ] + } + ], + "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.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}