Repository: GenericMappingTools/pygmt

Issue:
Suggestion for improvement of gallery examples regarding categorical colormaps
In two gallery examples categorical data (without order) is displayed by using color-coding for the categories:
- [Color points by categories](https://www.pygmt.org/dev/gallery/symbols/points_categorical.html#sphx-glr-gallery-symbols-points-categorical-py)
- [3D Scatter plots](https://www.pygmt.org/dev/gallery/3d_plots/scatter3d.html#sphx-glr-gallery-3d-plots-scatter3d-py)

The data (csv file) is loaded into a _pandas_ dataframe and the column of interest is converted to categorical data type. A discrete colormap in categorical format is set up via the `pygmt.makecpt()` function (`+c` through `color_model`). For plotting the symbols with the corresponding fill color the categorical number code is passed through the `color` parameter of the `pygmt.Figure.plot()` method.

I recommend to clarify that by default the labels for the colorbar must be given in **alphabetical** order. If one wants labels in non-alphabetical order, e. g. increasing or decreasing order for displaying categorical data with order, the categorical number code has to be adjusted by the user. For instance, to get '**p**oor, **f**air, **g**ood' instead of '**f**air, **g**ood, **p**oor' (dataframe 'df' with column 'quality'):
``df.quality = df.quality.cat.set_categories(['poor','fair','good'], ordered = True)``
The label order (comma-separated list) passed through `color_model` in the `pygmt.makecpt()` function has no effect on the plotting itself, but affects only the colorbar labels.



Discussion:
welcome[bot]: 💖 Thanks for opening this pull request! 💖

Please make sure you read our [contributing guidelines](https://github.com/GenericMappingTools/pygmt/blob/main/CONTRIBUTING.md) and abide by our [code of conduct](https://github.com/GenericMappingTools/pygmt/blob/main/CODE_OF_CONDUCT.md).

A few things to keep in mind:

* If you need help writing tests, take a look at the existing ones for inspiration. If you don't know where to start, let us know and we'll walk you through it.
* All new features should be documented. It helps to write the docstrings for your functions/classes before writing the code. This will help you think about your code design and results in better code.
* No matter what, we are really grateful that you put in the effort to do this! 🎉

yvonnefroehlich: I altered the code as suggested by @seisman in #1933 [comment](https://github.com/GenericMappingTools/pygmt/issues/1933#issuecomment-1139275569).

yvonnefroehlich: r

Patch:
diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml
index 697b6b57..0a886cc5 100644
--- a/.github/workflows/check-links.yml
+++ b/.github/workflows/check-links.yml
@@ -26,7 +26,7 @@ jobs:
         path: documentation
 
     - name: Link Checker
-      uses: lycheeverse/lychee-action@v1.4.1
+      uses: lycheeverse/lychee-action@v1.5.0
       with:
         # 429: Too many requests
         args: >
diff --git a/.github/workflows/ci_docs.yml b/.github/workflows/ci_docs.yml
index 60ec8863..6280a912 100644
--- a/.github/workflows/ci_docs.yml
+++ b/.github/workflows/ci_docs.yml
@@ -72,7 +72,7 @@ jobs:
         run: |
           mamba install gmt=6.3.0 numpy pandas xarray netCDF4 packaging \
                         build ipython make myst-parser geopandas \
-                        sphinx sphinx-copybutton sphinx-gallery sphinx_rtd_theme
+                        sphinx sphinx-copybutton sphinx-design sphinx-gallery sphinx_rtd_theme
 
       # Show installed pkg information for postmortem diagnostic
       - name: List installed packages
@@ -80,7 +80,7 @@ jobs:
 
       # Download cached remote files (artifacts) from GitHub
       - name: Download remote data from GitHub
-        uses: dawidd6/action-download-artifact@v2.19.0
+        uses: dawidd6/action-download-artifact@v2.21.0
         with:
           workflow: cache_data.yaml
           workflow_conclusion: success
diff --git a/doc/_static/style.css b/doc/_static/style.css
index 6d241ec3..77546baa 100644
--- a/doc/_static/style.css
+++ b/doc/_static/style.css
@@ -203,37 +203,3 @@ a.copybtn {
 .sphx-glr-single-img {
     max-width: 80%!important;
 }
-
-.team-row {
-    display: flex;
-    flex-wrap: wrap;
-}
-
-.team-card {
-    background-color: #fff;
-    background-clip: border-box;
-    border: 1px solid rgba(0,0,0,.125);
-    border-radius: 0.5rem;
-    margin: 0.25em;
-    flex: 0 1 175px;
-}
-
-.team-img {
-    border-radius: 0.5rem;
-    width: 100%;
-    height: 20vh!important;
-    object-fit: cover;
-}
-
-.team-card-body {
-    padding: 1.25rem;
-    height: 135px;
-}
-
-.team-card-text {
-    display: flex;
-    margin-block-start: 0em;
-    margin-block-end: 0.2em;
-    margin-inline-start: 0px;
-    margin-inline-end: 0px;
-}
diff --git a/doc/conf.py b/doc/conf.py
index 109d48c6..fc5cefa6 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -27,8 +27,9 @@ extensions = [
     "sphinx.ext.extlinks",
     "sphinx.ext.intersphinx",
     "sphinx.ext.napoleon",
-    "sphinx_gallery.gen_gallery",
     "sphinx_copybutton",
+    "sphinx_design",
+    "sphinx_gallery.gen_gallery",
 ]
 
 # Autosummary pages will be generated by sphinx-autogen instead of sphinx-build
@@ -36,6 +37,8 @@ autosummary_generate = []
 
 # Auto-generate header anchors with MyST parser
 myst_heading_anchors = 4
+# Allow code fences using colons
+myst_enable_extensions = ["colon_fence"]
 
 # Make the list of returns arguments and attributes render the same as the
 # parameters list
diff --git a/doc/contributing.md b/doc/contributing.md
index d29d6391..14680920 100644
--- a/doc/contributing.md
+++ b/doc/contributing.md
@@ -315,6 +315,30 @@ This will build the HTML files in `doc/_build/html`.
 Open `doc/_build/html/index.html` in your browser to view the pages. Follow the
 [pull request workflow](contributing.md#pull-request-workflow) to submit your changes for review.
 
+### Adding example code
+
+Many of the PyGMT functions have example code in their documentation. To contribute an
+example, add an "Example" header and put the example code below it. Have all lines 
+begin with `>>>`.  To keep this example code from being run during testing, add the code
+`__doctest_skip__ = [function name]` to the top of the module. 
+
+**Inline code example**
+
+Below the import statements at the top of the file
+
+``
+__doctest_skip__ = ["module_name"]
+``
+
+At the end of the function's docstring
+
+    Example
+    -------
+    >>> import pygmt
+    >>> # Comment describing what is happening
+    >>> Code example
+
+
 ### Contributing Gallery Plots
 
 The gallery and tutorials are managed by
diff --git a/doc/external_resources.md b/doc/external_resources.md
index 8416b3c5..13b7c99a 100644
--- a/doc/external_resources.md
+++ b/doc/external_resources.md
@@ -10,6 +10,7 @@ to submit a pull request with your recommended addition to the
 
 ## Tutorials
 
+- [2022 EGU SC5.2: Crafting beautiful maps with PyGMT](https://github.com/GenericMappingTools/egu22pygmt) by Wei Ji Leong, Leonardo Uieda, Max Jones, Andre Belem
 - [2021 PyGMT course at the UAF Geophysical Institute](https://github.com/liamtoney/gi-pygmt-2021) by Liam Toney
 - [2021 Remote Online Sessions for Emerging Seismologists (ROSES): Unit 5 - Mapping](https://www.youtube.com/watch?v=Zvcy7VDuhiw) by Liam Toney
 - [2020 Remote Online Sessions for Emerging Seismologists (ROSES): Unit 8 - PyGMT](https://www.iris.edu/hq/inclass/lesson/728) by Liam Toney
diff --git a/doc/install.rst b/doc/install.rst
index 7dd3e872..ac19df0e 100644
--- a/doc/install.rst
+++ b/doc/install.rst
@@ -137,7 +137,7 @@ This installs the latest stable release from
 Alternatively, you can install the latest development version from
 `TestPyPI <https://test.pypi.org/project/pygmt>`__::
 
-    pip install --pre --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple pygmt
+    pip install --pre --extra-index-url https://test.pypi.org/simple/ pygmt
 
 To upgrade the installed stable release or development version to be the latest
 one, just add ``--upgrade`` to the corresponding command above.
diff --git a/doc/team.md b/doc/team.md
index 30236584..9e7112e5 100644
--- a/doc/team.md
+++ b/doc/team.md
@@ -22,78 +22,91 @@ responsibilities. Distinguished Contributors who are also active maintainers of
 the PyGMT project and have responsibilities detailed in the
 {doc}`maintenance` have 'Maintainer' listed below their names.
 
-```{raw} html
-
-    <div class="team-row">
-    <div class="team-card">
-        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/3974108?v=4" alt="Card image">
-        <div class="team-card-body">
-            <h6 class="team-card-text">Dongdong Tian</h6>
-            <h7 class="team-card-text">Maintainer</h7>
-            <h7 class="team-card-text"><a href="https://github.com/seisman" >@seisman</a></h7>
-        </div>
-    </div>
-    <div class="team-card">
-        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/50591376?v=4" alt="Card image">
-        <div class="team-card-body">
-            <h6 class="team-card-text">Jiayuan Yao</h6>
-            <h7 class="team-card-text"><a href="https://github.com/core-man" >@core-man</a></h7>
-        </div>
-    </div>
-    <div class="team-card">
-        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/290082?v=4" alt="Card image">
-        <div class="team-card-body">
-            <h6 class="team-card-text">Leonardo Uieda</h6>
-            <h7 class="team-card-text">Founder</h7>
-            <h7 class="team-card-text"><a href="https://github.com/leouieda" >@leouieda</a></h7>
-        </div>
-    </div>
-    <div class="team-card">
-        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/38269494?v=4" alt="Card image">
-        <div class="team-card-body">
-            <h6 class="team-card-text">Liam Toney</h6>
-            <h7 class="team-card-text"><a href="https://github.com/liamtoney" >@liamtoney</a></h7>
-        </div>
-    </div>
-    <div class="team-card">
-        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/14077947?v=4" alt="Card image">
-        <div class="team-card-body">
-            <h6 class="team-card-text">Meghan Jones</h6>
-            <h7 class="team-card-text">Maintainer</h7>
-            <h7class="team-card-text"><a href="https://github.com/meghanrjones" >@meghanrjones</a></h7>
-        </div>
-    </div>
-    <div class="team-card">
-        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/23025878?v=4" alt="Card image">
-        <div class="team-card-body">
-            <h6 class="team-card-text">Michael Grund</h6>
-            <h7 class="team-card-text">Maintainer</h7>
-            <h7class="team-card-text"><a href="https://github.com/michaelgrund" >@michaelgrund</a></h7>
-        </div>
-    </div>
-    <div class="team-card">
-        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/26473567?v=4" alt="Card image">
-        <div class="team-card-body">
-            <h6 class="team-card-text">Paul Wessel</h6>
-            <h7 class="team-card-text">Founder</h7>
-            <h7class="team-card-text"><a href="https://github.com/PaulWessel" >@PaulWessel</a></h7>
-        </div>
-    </div>
-    <div class="team-card">
-        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/23487320?v=4" alt="Card image">
-        <div class="team-card-body">
-            <h6 class="team-card-text">Wei Ji Leong</h6>
-            <h7 class="team-card-text">Maintainer</h7>
-            <h7class="team-card-text"><a href="https://github.com/weiji14" >@weiji14</a></h7>
-        </div>
-    </div>
-    <div class="team-card">
-        <img class="card-img-top team-img" src="https://avatars.githubusercontent.com/u/29518865?v=4" alt="Card image">
-        <div class="team-card-body">
-            <h6 class="team-card-text">Will Schlitzer</h6>
-            <h7 class="team-card-text">Maintainer</h7>
-            <p class="team-card-text"><a href="https://github.com/willschlitzer" >@willschlitzer</a></h7>
-        </div>
-    </div>
-    </div>
-```
+:::::{grid} 2 3 3 4
+
+::::{grid-item-card} Dongdong Tian
+:margin: 0 3 0 0
+:text-align: center
+:img-top: https://avatars.githubusercontent.com/u/3974108?v=4
+
+[@seisman](https://github.com/seisman)
++++
+{bdg-primary}`Maintainer`
+::::
+
+::::{grid-item-card} Jiayuan Yao
+:margin: 0 3 0 0
+:text-align: center
+:img-top: https://avatars.githubusercontent.com/u/50591376?v=4
+
+[@core-man](https://github.com/core-man)
+::::
+
+::::{grid-item-card} Leonardo Uieda
+:margin: 0 3 0 0
+:text-align: center
+:img-top: https://avatars.githubusercontent.com/u/290082?v=4
+
+[@leouieda](https://github.com/leouieda)
++++
+{bdg-success}`Founder`
+::::
+
+::::{grid-item-card} Liam Toney
+:margin: 0 3 0 0
+:text-align: center
+:img-top: https://avatars.githubusercontent.com/u/38269494?v=4
+
+[@liamtoney](https://github.com/liamtoney)
+::::
+
+::::{grid-item-card} Max Jones
+:margin: 0 3 0 0
+:text-align: center
+:img-top: https://avatars.githubusercontent.com/u/14077947?v=4
+
+[@meghanrjones](https://github.com/meghanrjones)
++++
+{bdg-primary}`Maintainer`
+::::
+
+::::{grid-item-card} Michael Grund
+:margin: 0 3 0 0
+:text-align: center
+:img-top: https://avatars.githubusercontent.com/u/23025878?v=4
+
+[@michaelgrund](https://github.com/michaelgrund)
++++
+{bdg-primary}`Maintainer`
+::::
+
+::::{grid-item-card} Paul Wessel
+:margin: 0 3 0 0
+:text-align: center
+:img-top: https://avatars.githubusercontent.com/u/26473567?v=4
+
+[@PaulWessel](https://github.com/PaulWessel)
++++
+{bdg-success}`Founder`
+::::
+
+::::{grid-item-card} Wei Ji Leong
+:margin: 0 3 0 0
+:text-align: center
+:img-top: https://avatars.githubusercontent.com/u/23487320?v=4
+
+[@weiji14](https://github.com/weiji14)
++++
+{bdg-primary}`Maintainer`
+::::
+
+::::{grid-item-card} Will Schlitzer
+:margin: 0 3 0 0
+:text-align: center
+:img-top: https://avatars.githubusercontent.com/u/29518865?v=4
+
+[@willschlitzer](https://github.com/willschlitzer)
++++
+{bdg-primary}`Maintainer`
+::::
+:::::
diff --git a/environment.yml b/environment.yml
index fc56d1a4..86f7e39c 100644
--- a/environment.yml
+++ b/environment.yml
@@ -36,5 +36,6 @@ dependencies:
     - myst-parser
     - sphinx
     - sphinx-copybutton
+    - sphinx-design
     - sphinx-gallery
     - sphinx_rtd_theme
diff --git a/examples/gallery/3d_plots/scatter3d.py b/examples/gallery/3d_plots/scatter3d.py
index 36839f33..20197504 100644
--- a/examples/gallery/3d_plots/scatter3d.py
+++ b/examples/gallery/3d_plots/scatter3d.py
@@ -17,9 +17,20 @@ the vertical exaggeration factor.
 import pandas as pd
 import pygmt
 
-# Load sample iris data and convert 'species' column to categorical dtype
+# Load sample iris data
 df = pd.read_csv("https://github.com/mwaskom/seaborn-data/raw/master/iris.csv")
+# Convert 'species' column to categorical dtype
+# By default, pandas sorts the individual categories in an alphabetical order.
+# For a non-alphabetical order, you have to manually adjust the list of
+# categories. For handling and manipulating categorical data in pandas,
+# have a look at:
+# https://pandas.pydata.org/docs/user_guide/categorical.html
 df.species = df.species.astype(dtype="category")
+# Make a list of the individual categories of the 'species' column
+# ['setosa', 'versicolor', 'virginica']
+# They are (corresponding to the categorical number code) by default in
+# alphabetical order and later used for the colorbar labels
+labels = list(df.species.cat.categories)
 
 # Use pygmt.info to get region bounds (xmin, xmax, ymin, ymax, zmin, zmax)
 # The below example will return a numpy array [0.0, 3.0, 4.0, 8.0, 1.0, 7.0]
@@ -35,12 +46,18 @@ region = pygmt.info(
 fig = pygmt.Figure()
 
 # Define a colormap to be used for three categories, define the range of the
-# new discrete CPT using series=(lowest_value, highest_value, interval), use
-# color_model="+cSetosa,Versicolor,Virginica" to write the discrete color
+# new discrete CPT using series=(lowest_value, highest_value, interval),
+# use color_model="+csetosa,versicolor,virginica" to write the discrete color
 # palette "cubhelix" in categorical format and add the species names as
 # annotations for the colorbar
 pygmt.makecpt(
-    cmap="cubhelix", color_model="+cSetosa,Versicolor,Virginica", series=(0, 2, 1)
+    cmap="cubhelix",
+    # Use the minimum and maximum of the categorical number code
+    # to set the lowest_value and the highest_value of the CPT
+    series=(df.species.cat.codes.min(), df.species.cat.codes.max(), 1),
+    # convert ['setosa', 'versicolor', 'virginica'] to
+    # 'setosa,versicolor,virginica'
+    color_model="+c" + ",".join(labels),
 )
 
 fig.plot3d(
diff --git a/examples/gallery/symbols/points_categorical.py b/examples/gallery/symbols/points_categorical.py
index 38340bbf..82ffadcf 100644
--- a/examples/gallery/symbols/points_categorical.py
+++ b/examples/gallery/symbols/points_categorical.py
@@ -14,9 +14,20 @@ method.
 import pandas as pd
 import pygmt
 
-# Load sample penguins data and convert 'species' column to categorical dtype
+# Load sample penguins data
 df = pd.read_csv("https://github.com/mwaskom/seaborn-data/raw/master/penguins.csv")
+# Convert 'species' column to categorical dtype
+# By default, pandas sorts the individual categories in an alphabetical order.
+# For a non-alphabetical order, you have to manually adjust the list of
+# categories. For handling and manipulating categorical data in pandas,
+# have a look at:
+# https://pandas.pydata.org/docs/user_guide/categorical.html
 df.species = df.species.astype(dtype="category")
+# Make a list of the individual categories of the 'species' column
+# ['Adelie', 'Chinstrap', 'Gentoo']
+# They are (corresponding to the categorical number code) by default in
+# alphabetical order and later used for the colorbar labels
+labels = list(df.species.cat.categories)
 
 # Use pygmt.info to get region bounds (xmin, xmax, ymin, ymax)
 # The below example will return a numpy array like [30.0, 60.0, 12.0, 22.0]
@@ -48,7 +59,14 @@ fig.basemap(
 # use color_model="+cAdelie,Chinstrap,Gentoo" to write the discrete color
 # palette "inferno" in categorical format and add the species names as
 # annotations for the colorbar
-pygmt.makecpt(cmap="inferno", series=(0, 2, 1), color_model="+cAdelie,Chinstrap,Gentoo")
+pygmt.makecpt(
+    cmap="inferno",
+    # Use the minimum and maximum of the categorical number code
+    # to set the lowest_value and the highest_value of the CPT
+    series=(df.species.cat.codes.min(), df.species.cat.codes.max(), 1),
+    # convert ['Adelie', 'Chinstrap', 'Gentoo'] to 'Adelie,Chinstrap,Gentoo'
+    color_model="+c" + ",".join(labels),
+)
 
 fig.plot(
     # Use bill length and bill depth as x and y data input, respectively
diff --git a/pygmt/datasets/samples.py b/pygmt/datasets/samples.py
index 7a03a43f..15b3410e 100644
--- a/pygmt/datasets/samples.py
+++ b/pygmt/datasets/samples.py
@@ -32,6 +32,7 @@ def list_sample_data():
         "mars_shape": "Table of topographic signature of the hemispheric dichotomy of "
         " Mars from Smith and Zuber (1996)",
         "ocean_ridge_points": "Table of ocean ridge points for the entire world",
+        "notre_dame_topography": "Table 5.11 in Davis: Statistics and Data Analysis in Geology",
         "usgs_quakes": "Table of global earthquakes from the USGS",
     }
     return names
@@ -73,6 +74,7 @@ def load_sample_data(name):
         "japan_quakes": load_japan_quakes,
         "mars_shape": load_mars_shape,
         "ocean_ridge_points": load_ocean_ridge_points,
+        "notre_dame_topography": _load_notre_dame_topography,
         "usgs_quakes": load_usgs_quakes,
     }
 
@@ -348,6 +350,19 @@ def load_mars_shape(**kwargs):
     return data
 
 
+def _load_notre_dame_topography(**kwargs):  # pylint: disable=unused-argument
+    """
+    Load Table 5.11 in Davis: Statistics and Data Analysis in Geology.
+
+    Returns
+    -------
+    data : pandas.DataFrame
+        The data table with columns "x", "y", and "z".
+    """
+    fname = which("@Table_5_11.txt", download="c")
+    return pd.read_csv(fname, sep=r"\s+", header=None, names=["x", "y", "z"])
+
+
 def _load_earth_relief_holes(**kwargs):  # pylint: disable=unused-argument
     """
     Loads the remote file @earth_relief_20m_holes.grd.
diff --git a/pygmt/src/grdtrack.py b/pygmt/src/grdtrack.py
index f0b9306c..edabd455 100644
--- a/pygmt/src/grdtrack.py
+++ b/pygmt/src/grdtrack.py
@@ -1,7 +1,10 @@
 """
 grdtrack - Sample grids at specified (x,y) locations.
 """
+import warnings
+
 import pandas as pd
+import xarray as xr
 from pygmt.clib import Session
 from pygmt.exceptions import GMTInvalidInput
 from pygmt.helpers import (
@@ -11,6 +14,7 @@ from pygmt.helpers import (
     kwargs_to_strings,
     use_alias,
 )
+from pygmt.src.which import which
 
 __doctest_skip__ = ["grdtrack"]
 
@@ -43,7 +47,7 @@ __doctest_skip__ = ["grdtrack"]
     w="wrap",
 )
 @kwargs_to_strings(R="sequence", S="sequence", i="sequence_comma", o="sequence_comma")
-def grdtrack(points, grid, newcolname=None, outfile=None, **kwargs):
+def grdtrack(grid, points=None, newcolname=None, outfile=None, **kwargs):
     r"""
     Sample grids at specified (x,y) locations.
 
@@ -67,14 +71,14 @@ def grdtrack(points, grid, newcolname=None, outfile=None, **kwargs):
 
     Parameters
     ----------
-    points : str or {table-like}
-        Pass in either a file name to an ASCII data table, a 2D
-        {table-classes}.
-
     grid : xarray.DataArray or str
         Gridded array from which to sample values from, or a filename (netcdf
         format).
 
+    points : str or {table-like}
+        Pass in either a file name to an ASCII data table, a 2D
+        {table-classes}.
+
     newcolname : str
         Required if ``points`` is a :class:`pandas.DataFrame`. The name for the
         new column in the track :class:`pandas.DataFrame` table where the
@@ -283,26 +287,65 @@ def grdtrack(points, grid, newcolname=None, outfile=None, **kwargs):
     ...     points=points, grid=grid, newcolname="bathymetry"
     ... )
     """
+    # pylint: disable=too-many-branches
+    if points is not None and kwargs.get("E") is not None:
+        raise GMTInvalidInput("Can't set both 'points' and 'profile'.")
+
+    if points is None and kwargs.get("E") is None:
+        raise GMTInvalidInput("Must give 'points' or set 'profile'.")
+
     if hasattr(points, "columns") and newcolname is None:
         raise GMTInvalidInput("Please pass in a str to 'newcolname'")
 
+    # Backward compatibility with old parameter order "points, grid".
+    # deprecated_version="0.7.0", remove_version="v0.9.0"
+    is_a_grid = True
+    if not isinstance(grid, (xr.DataArray, str)):
+        is_a_grid = False
+    elif isinstance(grid, str):
+        try:
+            xr.open_dataarray(which(grid, download="a"), engine="netcdf4").close()
+            is_a_grid = True
+        except (ValueError, OSError):
+            is_a_grid = False
+    if not is_a_grid:
+        msg = (
+            "Positional parameters 'points, grid' of pygmt.grdtrack() has changed "
+            "to 'grid, points=None' since v0.7.0. It's likely that you're NOT "
+            "passing a valid grid as the first positional argument or "
+            "are passing an invalid grid to the 'grid' parameter. "
+            "Please check the order of arguments with the latest documentation. "
+            "This warning will be removed in v0.9.0."
+        )
+        grid, points = points, grid
+        warnings.warn(msg, category=FutureWarning, stacklevel=1)
+
     with GMTTempFile(suffix=".csv") as tmpfile:
         with Session() as lib:
-            # Choose how data will be passed into the module
-            table_context = lib.virtualfile_from_data(check_kind="vector", data=points)
             # Store the xarray.DataArray grid in virtualfile
             grid_context = lib.virtualfile_from_data(check_kind="raster", data=grid)
 
-            # Run grdtrack on the temporary (csv) points table
-            # and (netcdf) grid virtualfile
-            with table_context as csvfile:
-                with grid_context as grdfile:
-                    kwargs.update({"G": grdfile})
-                    if outfile is None:  # Output to tmpfile if outfile is not set
-                        outfile = tmpfile.name
+            with grid_context as grdfile:
+                kwargs.update({"G": grdfile})
+                if outfile is None:  # Output to tmpfile if outfile is not set
+                    outfile = tmpfile.name
+
+                if points is not None:
+                    # Choose how data will be passed into the module
+                    table_context = lib.virtualfile_from_data(
+                        check_kind="vector", data=points
+                    )
+                    with table_context as csvfile:
+                        lib.call_module(
+                            module="grdtrack",
+                            args=build_arg_string(
+                                kwargs, infile=csvfile, outfile=outfile
+                            ),
+                        )
+                else:
                     lib.call_module(
                         module="grdtrack",
-                        args=build_arg_string(kwargs, infile=csvfile, outfile=outfile),
+                        args=build_arg_string(kwargs, outfile=outfile),
                     )
 
         # Read temporary csv output to a pandas table
diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py
index d09d8569..44d2998e 100644
--- a/pygmt/src/meca.py
+++ b/pygmt/src/meca.py
@@ -376,7 +376,7 @@ def meca(
                         len(longitude) != len(depth)
                     ):
                         raise GMTError(
-                            "Unequal number of focal mechanism " "locations supplied."
+                            "Unequal number of focal mechanism locations supplied."
                         )
 
             # values are ok, so build the 2D array


Test patch:
diff --git a/.github/workflows/ci_tests.yaml b/.github/workflows/ci_tests.yaml
index e847f09c..968ab9de 100644
--- a/.github/workflows/ci_tests.yaml
+++ b/.github/workflows/ci_tests.yaml
@@ -105,7 +105,7 @@ jobs:
 
       # Download cached remote files (artifacts) from GitHub
       - name: Download remote data from GitHub
-        uses: dawidd6/action-download-artifact@v2.19.0
+        uses: dawidd6/action-download-artifact@v2.21.0
         with:
           workflow: cache_data.yaml
           workflow_conclusion: success
diff --git a/.github/workflows/ci_tests_dev.yaml b/.github/workflows/ci_tests_dev.yaml
index c748420a..acc0a4c4 100644
--- a/.github/workflows/ci_tests_dev.yaml
+++ b/.github/workflows/ci_tests_dev.yaml
@@ -137,7 +137,7 @@ jobs:
 
       # Download cached remote files (artifacts) from GitHub
       - name: Download remote data from GitHub
-        uses: dawidd6/action-download-artifact@v2.19.0
+        uses: dawidd6/action-download-artifact@v2.21.0
         with:
           workflow: cache_data.yaml
           workflow_conclusion: success
diff --git a/pygmt/tests/test_datasets_samples.py b/pygmt/tests/test_datasets_samples.py
index 857c6760..c66ccc3f 100644
--- a/pygmt/tests/test_datasets_samples.py
+++ b/pygmt/tests/test_datasets_samples.py
@@ -148,6 +148,21 @@ def test_hotspots():
     assert isinstance(data, pd.DataFrame)
 
 
+def test_load_notre_dame_topography():
+    """
+    Check that the @Table_5_11.txt dataset loads without errors.
+    """
+    data = load_sample_data(name="notre_dame_topography")
+    assert data.shape == (52, 3)
+    summary = data.describe()
+    assert summary.loc["min", "x"] == 0.2
+    assert summary.loc["max", "x"] == 6.3
+    assert summary.loc["min", "y"] == 0
+    assert summary.loc["max", "y"] == 6.2
+    assert summary.loc["min", "z"] == 690
+    assert summary.loc["max", "z"] == 960
+
+
 def test_earth_relief_holes():
     """
     Check that the @earth_relief_20m_holes.grd dataset loads without errors.
diff --git a/pygmt/tests/test_grdtrack.py b/pygmt/tests/test_grdtrack.py
index 5c6ecc45..f343e142 100644
--- a/pygmt/tests/test_grdtrack.py
+++ b/pygmt/tests/test_grdtrack.py
@@ -99,6 +99,29 @@ def test_grdtrack_input_csvfile_and_ncfile_to_dataframe(expected_array):
     npt.assert_allclose(np.array(output), expected_array)
 
 
+def test_grdtrack_profile(dataarray):
+    """
+    Run grdtrack by passing a profile.
+    """
+    output = grdtrack(grid=dataarray, profile="-51/-17/-54/-19")
+    assert isinstance(output, pd.DataFrame)
+    npt.assert_allclose(
+        np.array(output),
+        np.array(
+            [
+                [-51.0, -17.0, 669.671875],
+                [-51.42430204, -17.28838525, 847.40745877],
+                [-51.85009439, -17.57598444, 885.30534844],
+                [-52.27733766, -17.86273467, 829.85423488],
+                [-52.70599151, -18.14857333, 776.83702212],
+                [-53.13601473, -18.43343819, 631.07867839],
+                [-53.56736521, -18.7172675, 504.28037216],
+                [-54.0, -19.0, 486.10351562],
+            ]
+        ),
+    )
+
+
 def test_grdtrack_wrong_kind_of_points_input(dataarray, dataframe):
     """
     Run grdtrack using points input that is not a pandas.DataFrame (matrix) or
@@ -137,3 +160,34 @@ def test_grdtrack_without_outfile_setting(dataarray, dataframe):
     """
     with pytest.raises(GMTInvalidInput):
         grdtrack(points=dataframe, grid=dataarray)
+
+
+def test_grdtrack_no_points_and_profile(dataarray):
+    """
+    Run grdtrack but don't set 'points' and 'profile'.
+    """
+    with pytest.raises(GMTInvalidInput):
+        grdtrack(grid=dataarray)
+
+
+def test_grdtrack_set_points_and_profile(dataarray, dataframe):
+    """
+    Run grdtrack but set both 'points' and 'profile'.
+    """
+    with pytest.raises(GMTInvalidInput):
+        grdtrack(grid=dataarray, points=dataframe, profile="BL/TR")
+
+
+def test_grdtrack_old_parameter_order(dataframe, dataarray, expected_array):
+    """
+    Run grdtrack with the old parameter order 'points, grid'.
+
+    This test should be removed in v0.9.0.
+    """
+    for points in (POINTS_DATA, dataframe):
+        for grid in ("@static_earth_relief.nc", dataarray):
+            with pytest.warns(expected_warning=FutureWarning) as record:
+                output = grdtrack(points, grid)
+                assert len(record) == 1
+                assert isinstance(output, pd.DataFrame)
+                npt.assert_allclose(np.array(output), expected_array)