Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrap plot3d #471

Merged
merged 26 commits into from
Nov 3, 2020
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b1ad5e0
Wrap plot3d
weiji14 Jun 1, 2020
e38b3f9
Merge remote-tracking branch 'upstream/master' into mapping/plot3d
weiji14 Jun 1, 2020
e7f7679
Alias straight_lines(A), error_bars(E), close(L), position(D) for plot3d
weiji14 Jun 1, 2020
9dd3840
Merge branch 'master' into mapping/plot3d
weiji14 Jun 4, 2020
5a92360
Test zsize argument in plot3d
weiji14 Jun 4, 2020
b6d26ac
Merge branch 'master' into mapping/plot3d
weiji14 Jun 25, 2020
5569251
Merge branch 'master' into mapping/plot3d
weiji14 Oct 25, 2020
31e0fb7
Revert changes to test_plot.py
weiji14 Oct 25, 2020
af070de
Merge branch 'master' into mapping/plot3d
weiji14 Oct 25, 2020
ce3119d
Update aliases for plot3d
weiji14 Oct 25, 2020
79b07bf
Alias intensity (I), no_clip (N) and no_sort (Q) for plot3d
weiji14 Oct 25, 2020
8391169
Update plot3d baseline images with changed z-axis label orientation
weiji14 Oct 25, 2020
0906faf
Move zscale/zsize doc placement on basemap
weiji14 Oct 25, 2020
2a739d5
Refactor test_plot3d to avoid storing baseline images
weiji14 Oct 25, 2020
8c3bf7f
Alias zvalue (Z) for plot3d
weiji14 Oct 25, 2020
ff1c495
Add 3D Gallery example of Iris flower dataset
weiji14 Oct 25, 2020
bd07900
Plot symbols as 3-D cubes in gallery example
weiji14 Oct 25, 2020
cdcd259
Let plot3d accept record by record transparency
weiji14 Oct 26, 2020
413ff77
Use GitHub Action to upload diff images on test failure
weiji14 Oct 27, 2020
878a073
Change 3 plot3d_sizes tests to use cubes in unit inches
weiji14 Oct 27, 2020
9dc9533
Revert "Use GitHub Action to upload diff images on test failure"
weiji14 Oct 27, 2020
256a0a9
Add note to 3 tests about why inches is used instead of centimetres
weiji14 Oct 27, 2020
e47596f
Add link to wikipedia page of Iris flower dataset
weiji14 Oct 27, 2020
37578e1
Merge branch 'master' into mapping/plot3d
weiji14 Nov 3, 2020
40f7120
Use categorical color_model for scatter3d colormap
weiji14 Nov 3, 2020
e31344f
Fix typo on examples/gallery/plot/scatter3d.py
weiji14 Nov 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Plotting data and laying out the map:
Figure.coast
Figure.colorbar
Figure.plot
Figure.plot3d
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little off-topic, but should we sort all the methods alphabetically?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just for the Figure.* methods, but that's for a separate PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Figure.contour
Figure.grdcontour
Figure.grdimage
Expand Down
52 changes: 52 additions & 0 deletions examples/gallery/plot/scatter3d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
3D Scatter plots
----------------

The :meth:`pygmt.Figure.plot3d` method can be used to plot symbols in 3D.
In the example below, we show how the Iris flower dataset can be visualized
seisman marked this conversation as resolved.
Show resolved Hide resolved
using a perspective 3-dimensional plot. The ``region`` argument has to be set
to include the :math:`x`, :math:`y`, :math:`z` axis limits in the form of
(xmin, xmax, ymin, ymax, zmin, zmax), and this can be done automatically using
:meth:`pygmt.info`. To include the z-axis stick, ``frame`` needs to be set as a
minimum to something like ``frame=["WsNeZ", "zaf"]``. Use ``perspective`` to
control the azimuth and elevation angle of the view, and ``zscale`` to adjust
the vertical exaggeration factor.
"""

import pandas as pd
import pygmt

# Load sample iris data, and convert 'species' column to categorical dtype
df = pd.read_csv("https://github.com/mwaskom/seaborn-data/raw/master/iris.csv")
df["species"] = df.species.astype(dtype="category")

# Use pygmt.info to get region bounds (xmin, xmax, ymin, ymax, zmin, zmax)
# The below example will return a numpy array like [0., 3., 4., 8., 1., 7.]
region = pygmt.info(
table=df[["petal_width", "sepal_length", "petal_length"]], # x, y, z columns
per_column=True, # report output as a numpy array
spacing="1/2/0.5", # rounds x, y and z intervals by 1, 2 and 0.5 respectively
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that we should use spacing=[1, 2, 0.5] here, but unfortunately, it's unimplemented in pygmt.info().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, forgot to handle that in #575. Should handle that (list inputs) when we complete the documentation for info.

)

# Make our 3D scatter plot, coloring each of the 3 species differently
fig = pygmt.Figure()
pygmt.makecpt(cmap="cubhelix", series=(0, 3, 1))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makecpt command produces a discrete CPT (see the figure below). It doesn't make sense for the categorical data here.
image.

Instead, we should use makecpt -F+c, which produces categorical CPT.

image

But the colorbar annotations don't make sense to me. Perhaps another GMT bug.

Note: the gridlines are not shown in GMT 6.1.1. It's a new future in GMT master (see GenericMappingTools/gmt#3993), but the gridlines in XZ and YZ planes may also have bugs. Will report to GMT later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@weiji14 weiji14 Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, we should use makecpt -F+c, which produces categorical CPT.
But the colorbar annotations don't make sense to me. Perhaps another GMT bug.

Ah yes, forgot about -F+c! Actually, the Iris species categories are nominal (discrete) rather than ordinal (i.e. ranked 1st, 2nd, 3rd), so a legend would make more sense here than a colorbar. Another thought I had was to label each species using text plotted in 3D, but we haven't implemented that 'feature' yet.

So for now, I think we can just use -F+c here and ignore the colorbar, though we should add another categorical gallery example to close #244? I'd like to avoid adding a legend since it requires a for-loop to make the labels (and this complicates the example).

The wall frames look good though, too bad we need to wait for GMT 6.2.0! But good that we found two bugs to report to upstream GMT 😆

Copy link
Member Author

@weiji14 weiji14 Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, makecpt -F is not aliased yet. Do we want to do that here or in a separate PR?

Edit: Opened up #676 to complete documentation of makecpt.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, with #676 merged, I've updated the code to use pygmt.makecpt(..., color_model="+c", ...)

pygmt.makecpt(cmap="cubhelix", color_model="+c", series=(0, 3, 1))

In the future when we bump the minimum GMT version to 6.2.0 which would include the new feature at GenericMappingTools/gmt#4390, we can do the following:

pygmt.makecpt(cmap="cubhelix", color_model="+cSetosa,Versicolor,Virginica", series=(0, 3, 1))
fig.plot3d(...)
fig.colorbar()
fig.show()

which would produce a nice colorbar ;)

fig.plot3d(
x=df.petal_width,
y=df.sepal_length,
z=df.petal_length,
sizes=0.1 * df.sepal_width, # Vary each symbol size according to a data column
color=df.species.cat.codes.astype(int), # Points colored by categorical number code
cmap=True, # Use colormap created by makecpt
region=region, # (xmin, xmax, ymin, ymax, zmin, zmax)
frame=[
"WsNeZ3", # z axis label positioned on 3rd corner
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about adding a title here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title looks like it is placed too high without the gridlines at the back, doesn't look very nice:

tmp_iris

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree

'xafg+l"Petal Width"',
'yafg+l"Sepal Length"',
'zafg+l"Petal Length"',
],
style="uc", # 3D cUbe, with size in centimeter units
perspective=[315, 25], # Azimuth NorthWest (315°), at elevation 25°
zscale=1.5, # Vertical exaggeration factor
)
fig.show()
197 changes: 192 additions & 5 deletions pygmt/base_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,189 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs):
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("plot", arg_str)

@fmt_docstring
@use_alias(
A="straight_line",
B="frame",
C="cmap",
D="offset",
G="color",
I="intensity",
J="projection",
Jz="zscale",
JZ="zsize",
L="close",
N="no_clip",
Q="no_sort",
weiji14 marked this conversation as resolved.
Show resolved Hide resolved
R="region",
S="style",
V="verbose",
W="pen",
X="xshift",
Y="yshift",
Z="zvalue",
i="columns",
weiji14 marked this conversation as resolved.
Show resolved Hide resolved
l="label",
p="perspective",
t="transparency",
)
@kwargs_to_strings(R="sequence", i="sequence_comma", p="sequence")
def plot3d(
self, x=None, y=None, z=None, data=None, sizes=None, direction=None, **kwargs
):
"""
Plot lines, polygons, and symbols in 3-D

Takes a matrix, (x,y,z) triplets, or a file name as input and plots
lines, polygons, or symbols at those locations in 3-D.

Must provide either *data* or *x*, *y* and *z*.

If providing data through *x*, *y* and *z*, *color* can be a 1d array
that will be mapped to a colormap.

If a symbol is selected and no symbol size given, then plot3d will
interpret the fourth column of the input data as symbol size. Symbols
whose size is <= 0 are skipped. If no symbols are specified then the
symbol code (see *style* below) must be present as last column in the
input. If *style* is not used, a line connecting the data points will
be drawn instead. To explicitly close polygons, use *close*. Select a
fill with *color*. If *color* is set, *pen* will control whether the
polygon outline is drawn or not. If a symbol is selected, *color* and
*pen* determines the fill and outline/no outline, respectively.

Full option list at :gmt-docs:`plot3d.html`

{aliases}

Parameters
----------
x/y/z : float or 1d arrays
The x, y, and z coordinates, or arrays of x, y and z coordinates of
the data points
data : str or 2d array
Either a data file name or a 2d numpy array with the tabular data.
Use option *columns* (i) to choose which columns are x, y, z,
color, and size, respectively.
sizes : 1d array
The sizes of the data points in units specified in *style* (S).
Only valid if using *x*, *y* and *z*.
direction : list of two 1d arrays
If plotting vectors (using ``style='V'`` or ``style='v'``), then
should be a list of two 1d arrays with the vector directions. These
can be angle and length, azimuth and length, or x and y components,
depending on the style options chosen.
{J}
zscale/zsize : float or str
Set z-axis scaling or z-axis size.
{R}
straight_line : bool or str
``[m|p|x|y]``.
By default, geographic line segments are drawn as great circle
arcs. To draw them as straight lines, use *straight_line*.
Alternatively, add **m** to draw the line by first following a
meridian, then a parallel. Or append **p** to start following a
parallel, then a meridian. (This can be practical to draw a line
along parallels, for example). For Cartesian data, points are
simply connected, unless you append **x** or **y** to draw
stair-case curves that whose first move is along *x* or *y*,
respectively. **Note**: The **straight_line** option requires
constant *z*-coordinates.
{B}
{CPT}
offset : str
``dx/dy[/dz]``.
Offset the plot symbol or line locations by the given amounts
*dx/dy*[*dz*] [Default is no offset].
{G}
intensity : float or bool
Provide an *intens* value (nominally in the -1 to +1 range) to
modulate the fill color by simulating illumination [None]. If
using ``intensity=True``, we will instead read *intens* from the
first data column after the symbol parameters (if given).
close : str
``[+b|d|D][+xl|r|x0][+yl|r|y0][+ppen]``.
Force closed polygons. Full documentation is at
:gmt-docs:`plot3d.html#l`.
no_clip : bool or str
``[c|r]``.
Do NOT clip symbols that fall outside map border [Default plots
points whose coordinates are strictly inside the map border only].
The option does not apply to lines and polygons which are always
clipped to the map region. For periodic (360-longitude) maps we
must plot all symbols twice in case they are clipped by the
repeating boundary. ``no_clip=True`` will turn off clipping and not
plot repeating symbols. Use ``no_clip="r"`` to turn off clipping
but retain the plotting of such repeating symbols, or use
``no_clip="c"`` to retain clipping but turn off plotting of
repeating symbols.
no_sort : bool
Turn off the automatic sorting of items based on their distance
from the viewer. The default is to sort the items so that items in
the foreground are plotted after items in the background.
style : str
Plot symbols. Full documentation is at :gmt-docs:`plot3d.html#s`.
{U}
{V}
{W}
{XY}
zvalue : str
``value|file``.
Instead of specifying a symbol or polygon fill and outline color
via **color** and **pen**, give both a *value* via **zvalue** and a
color lookup table via **cmap**. Alternatively, give the name of a
*file* with one z-value (read from the last column) for each
polygon in the input data. To apply it to the fill color, use
``color='+z'``. To apply it to the pen color, append **+z** to
**pen**.
label : str
Add a legend entry for the symbol or line being plotted.
{p}
{t}
*transparency* can also be a 1d array to set varying transparency
for symbols.

"""
kwargs = self._preprocess(**kwargs)

kind = data_kind(data, x, y, z)

extra_arrays = []
if "S" in kwargs and kwargs["S"][0] in "vV" and direction is not None:
extra_arrays.extend(direction)
if "G" in kwargs and not isinstance(kwargs["G"], str):
if kind != "vectors":
raise GMTInvalidInput(
"Can't use arrays for color if data is matrix or file."
)
extra_arrays.append(kwargs["G"])
del kwargs["G"]
if sizes is not None:
if kind != "vectors":
raise GMTInvalidInput(
"Can't use arrays for sizes if data is matrix or file."
)
extra_arrays.append(sizes)

seisman marked this conversation as resolved.
Show resolved Hide resolved
if "t" in kwargs and is_nonstr_iter(kwargs["t"]):
extra_arrays.append(kwargs["t"])
kwargs["t"] = ""

with Session() as lib:
# Choose how data will be passed in to the module
if kind == "file":
file_context = dummy_context(data)
elif kind == "matrix":
file_context = lib.virtualfile_from_matrix(data)
elif kind == "vectors":
file_context = lib.virtualfile_from_vectors(
np.atleast_1d(x), np.atleast_1d(y), np.atleast_1d(z), *extra_arrays
)

with file_context as fname:
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("plot3d", arg_str)

@fmt_docstring
@use_alias(
R="region",
Expand Down Expand Up @@ -921,6 +1104,8 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs):
@use_alias(
R="region",
J="projection",
Jz="zscale",
JZ="zsize",
B="frame",
L="map_scale",
Td="rose",
Expand All @@ -935,12 +1120,12 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs):
@kwargs_to_strings(R="sequence", p="sequence")
def basemap(self, **kwargs):
"""
Produce a basemap for the figure.
Plot base maps and frames for the figure.

Several map projections are available, and the user may specify
separate tick-mark intervals for boundary annotation, ticking, and
[optionally] gridlines. A simple map scale or directional rose may also
be plotted.
Creates a basic or fancy basemap with axes, fill, and titles. Several
map projections are available, and the user may specify separate
tick-mark intervals for boundary annotation, ticking, and [optionally]
gridlines. A simple map scale or directional rose may also be plotted.

At least one of the options *frame*, *map_scale*, *rose* or *compass*
must be specified.
Expand All @@ -952,6 +1137,8 @@ def basemap(self, **kwargs):
Parameters
----------
{J}
zscale/zsize : float or str
Set z-axis scaling or z-axis size.
{R}
{B}
map_scale : str
Expand Down
2 changes: 1 addition & 1 deletion pygmt/tests/test_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def test_plot_varying_transparency():

@check_figures_equal()
def test_plot_sizes_colors_transparencies():
"Plot the data using z as transparency"
"Plot the data with varying sizes and colors using z as transparency"
x = np.arange(1.0, 10.0)
y = np.arange(1.0, 10.0)
color = np.arange(1, 10) * 0.15
Expand Down
Loading