Skip to content

Commit a01ea4d

Browse files
weiji14seisman
andauthored
Wrap plot3d (#471)
Wrapping the `plot3d` function! Original GMT `plot3d` function can be found at https://docs.generic-mapping-tools.org/latest/plot3d.html. Current implementation is mostly copy-modify-pasted from `plot`, including the tests. Also added extra aliases to the `basemap` function to make it 3D capable. * Alias straight_line(A), close(L), offset(D) for plot3d * Test zsize argument in plot3d * Update aliases for plot3d Reorder aliases alphabetically and add common aliases verbose (V), xshift (X), yshift (Y) and transparency (t). * Alias intensity (I), no_clip (N) and no_sort (Q) for plot3d * Refactor test_plot3d to avoid storing baseline images * Alias zvalue (Z) for plot3d * Add 3D Gallery example of Iris flower dataset * Plot symbols as 3-D cubes in gallery example * Let plot3d accept record by record transparency * Change 3 plot3d_sizes tests to use cubes in unit inches See if this can resolve the Windows test failures * Add note to 3 tests about why inches is used instead of centimetres * Add link to wikipedia page of Iris flower dataset * Use categorical color_model for scatter3d colormap Co-authored-by: Dongdong Tian <[email protected]>
1 parent 0ea9886 commit a01ea4d

File tree

5 files changed

+835
-6
lines changed

5 files changed

+835
-6
lines changed

doc/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Plotting data and laying out the map:
2626
Figure.coast
2727
Figure.colorbar
2828
Figure.plot
29+
Figure.plot3d
2930
Figure.contour
3031
Figure.grdcontour
3132
Figure.grdimage

examples/gallery/plot/scatter3d.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
3D Scatter plots
3+
----------------
4+
5+
The :meth:`pygmt.Figure.plot3d` method can be used to plot symbols in 3D.
6+
In the example below, we show how the
7+
`Iris flower dataset <https://en.wikipedia.org/wiki/Iris_flower_data_set/>`__
8+
can be visualized using a perspective 3-dimensional plot. The ``region``
9+
argument has to include the :math:`x`, :math:`y`, :math:`z` axis limits in the
10+
form of (xmin, xmax, ymin, ymax, zmin, zmax), which can be done automatically
11+
using :meth:`pygmt.info`. To include the z-axis stick, set ``frame`` as a
12+
minimum to something like ``frame=["WsNeZ", "zaf"]``. Use ``perspective`` to
13+
control the azimuth and elevation angle of the view, and ``zscale`` to adjust
14+
the vertical exaggeration factor.
15+
"""
16+
17+
import pandas as pd
18+
import pygmt
19+
20+
# Load sample iris data, and convert 'species' column to categorical dtype
21+
df = pd.read_csv("https://github.com/mwaskom/seaborn-data/raw/master/iris.csv")
22+
df["species"] = df.species.astype(dtype="category")
23+
24+
# Use pygmt.info to get region bounds (xmin, xmax, ymin, ymax, zmin, zmax)
25+
# The below example will return a numpy array like [0., 3., 4., 8., 1., 7.]
26+
region = pygmt.info(
27+
table=df[["petal_width", "sepal_length", "petal_length"]], # x, y, z columns
28+
per_column=True, # report output as a numpy array
29+
spacing="1/2/0.5", # rounds x, y and z intervals by 1, 2 and 0.5 respectively
30+
)
31+
32+
# Make our 3D scatter plot, coloring each of the 3 species differently
33+
fig = pygmt.Figure()
34+
pygmt.makecpt(cmap="cubhelix", color_model="+c", series=(0, 3, 1))
35+
fig.plot3d(
36+
x=df.petal_width,
37+
y=df.sepal_length,
38+
z=df.petal_length,
39+
sizes=0.1 * df.sepal_width, # Vary each symbol size according to a data column
40+
color=df.species.cat.codes.astype(int), # Points colored by categorical number code
41+
cmap=True, # Use colormap created by makecpt
42+
region=region, # (xmin, xmax, ymin, ymax, zmin, zmax)
43+
frame=[
44+
"WsNeZ3", # z axis label positioned on 3rd corner
45+
'xafg+l"Petal Width"',
46+
'yafg+l"Sepal Length"',
47+
'zafg+l"Petal Length"',
48+
],
49+
style="uc", # 3D cUbe, with size in centimeter units
50+
perspective=[315, 25], # Azimuth NorthWest (315°), at elevation 25°
51+
zscale=1.5, # Vertical exaggeration factor
52+
)
53+
fig.show()

pygmt/base_plotting.py

Lines changed: 192 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,189 @@ def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs):
816816
arg_str = " ".join([fname, build_arg_string(kwargs)])
817817
lib.call_module("plot", arg_str)
818818

819+
@fmt_docstring
820+
@use_alias(
821+
A="straight_line",
822+
B="frame",
823+
C="cmap",
824+
D="offset",
825+
G="color",
826+
I="intensity",
827+
J="projection",
828+
Jz="zscale",
829+
JZ="zsize",
830+
L="close",
831+
N="no_clip",
832+
Q="no_sort",
833+
R="region",
834+
S="style",
835+
V="verbose",
836+
W="pen",
837+
X="xshift",
838+
Y="yshift",
839+
Z="zvalue",
840+
i="columns",
841+
l="label",
842+
p="perspective",
843+
t="transparency",
844+
)
845+
@kwargs_to_strings(R="sequence", i="sequence_comma", p="sequence")
846+
def plot3d(
847+
self, x=None, y=None, z=None, data=None, sizes=None, direction=None, **kwargs
848+
):
849+
"""
850+
Plot lines, polygons, and symbols in 3-D
851+
852+
Takes a matrix, (x,y,z) triplets, or a file name as input and plots
853+
lines, polygons, or symbols at those locations in 3-D.
854+
855+
Must provide either *data* or *x*, *y* and *z*.
856+
857+
If providing data through *x*, *y* and *z*, *color* can be a 1d array
858+
that will be mapped to a colormap.
859+
860+
If a symbol is selected and no symbol size given, then plot3d will
861+
interpret the fourth column of the input data as symbol size. Symbols
862+
whose size is <= 0 are skipped. If no symbols are specified then the
863+
symbol code (see *style* below) must be present as last column in the
864+
input. If *style* is not used, a line connecting the data points will
865+
be drawn instead. To explicitly close polygons, use *close*. Select a
866+
fill with *color*. If *color* is set, *pen* will control whether the
867+
polygon outline is drawn or not. If a symbol is selected, *color* and
868+
*pen* determines the fill and outline/no outline, respectively.
869+
870+
Full option list at :gmt-docs:`plot3d.html`
871+
872+
{aliases}
873+
874+
Parameters
875+
----------
876+
x/y/z : float or 1d arrays
877+
The x, y, and z coordinates, or arrays of x, y and z coordinates of
878+
the data points
879+
data : str or 2d array
880+
Either a data file name or a 2d numpy array with the tabular data.
881+
Use option *columns* (i) to choose which columns are x, y, z,
882+
color, and size, respectively.
883+
sizes : 1d array
884+
The sizes of the data points in units specified in *style* (S).
885+
Only valid if using *x*, *y* and *z*.
886+
direction : list of two 1d arrays
887+
If plotting vectors (using ``style='V'`` or ``style='v'``), then
888+
should be a list of two 1d arrays with the vector directions. These
889+
can be angle and length, azimuth and length, or x and y components,
890+
depending on the style options chosen.
891+
{J}
892+
zscale/zsize : float or str
893+
Set z-axis scaling or z-axis size.
894+
{R}
895+
straight_line : bool or str
896+
``[m|p|x|y]``.
897+
By default, geographic line segments are drawn as great circle
898+
arcs. To draw them as straight lines, use *straight_line*.
899+
Alternatively, add **m** to draw the line by first following a
900+
meridian, then a parallel. Or append **p** to start following a
901+
parallel, then a meridian. (This can be practical to draw a line
902+
along parallels, for example). For Cartesian data, points are
903+
simply connected, unless you append **x** or **y** to draw
904+
stair-case curves that whose first move is along *x* or *y*,
905+
respectively. **Note**: The **straight_line** option requires
906+
constant *z*-coordinates.
907+
{B}
908+
{CPT}
909+
offset : str
910+
``dx/dy[/dz]``.
911+
Offset the plot symbol or line locations by the given amounts
912+
*dx/dy*[*dz*] [Default is no offset].
913+
{G}
914+
intensity : float or bool
915+
Provide an *intens* value (nominally in the -1 to +1 range) to
916+
modulate the fill color by simulating illumination [None]. If
917+
using ``intensity=True``, we will instead read *intens* from the
918+
first data column after the symbol parameters (if given).
919+
close : str
920+
``[+b|d|D][+xl|r|x0][+yl|r|y0][+ppen]``.
921+
Force closed polygons. Full documentation is at
922+
:gmt-docs:`plot3d.html#l`.
923+
no_clip : bool or str
924+
``[c|r]``.
925+
Do NOT clip symbols that fall outside map border [Default plots
926+
points whose coordinates are strictly inside the map border only].
927+
The option does not apply to lines and polygons which are always
928+
clipped to the map region. For periodic (360-longitude) maps we
929+
must plot all symbols twice in case they are clipped by the
930+
repeating boundary. ``no_clip=True`` will turn off clipping and not
931+
plot repeating symbols. Use ``no_clip="r"`` to turn off clipping
932+
but retain the plotting of such repeating symbols, or use
933+
``no_clip="c"`` to retain clipping but turn off plotting of
934+
repeating symbols.
935+
no_sort : bool
936+
Turn off the automatic sorting of items based on their distance
937+
from the viewer. The default is to sort the items so that items in
938+
the foreground are plotted after items in the background.
939+
style : str
940+
Plot symbols. Full documentation is at :gmt-docs:`plot3d.html#s`.
941+
{U}
942+
{V}
943+
{W}
944+
{XY}
945+
zvalue : str
946+
``value|file``.
947+
Instead of specifying a symbol or polygon fill and outline color
948+
via **color** and **pen**, give both a *value* via **zvalue** and a
949+
color lookup table via **cmap**. Alternatively, give the name of a
950+
*file* with one z-value (read from the last column) for each
951+
polygon in the input data. To apply it to the fill color, use
952+
``color='+z'``. To apply it to the pen color, append **+z** to
953+
**pen**.
954+
label : str
955+
Add a legend entry for the symbol or line being plotted.
956+
{p}
957+
{t}
958+
*transparency* can also be a 1d array to set varying transparency
959+
for symbols.
960+
961+
"""
962+
kwargs = self._preprocess(**kwargs)
963+
964+
kind = data_kind(data, x, y, z)
965+
966+
extra_arrays = []
967+
if "S" in kwargs and kwargs["S"][0] in "vV" and direction is not None:
968+
extra_arrays.extend(direction)
969+
if "G" in kwargs and not isinstance(kwargs["G"], str):
970+
if kind != "vectors":
971+
raise GMTInvalidInput(
972+
"Can't use arrays for color if data is matrix or file."
973+
)
974+
extra_arrays.append(kwargs["G"])
975+
del kwargs["G"]
976+
if sizes is not None:
977+
if kind != "vectors":
978+
raise GMTInvalidInput(
979+
"Can't use arrays for sizes if data is matrix or file."
980+
)
981+
extra_arrays.append(sizes)
982+
983+
if "t" in kwargs and is_nonstr_iter(kwargs["t"]):
984+
extra_arrays.append(kwargs["t"])
985+
kwargs["t"] = ""
986+
987+
with Session() as lib:
988+
# Choose how data will be passed in to the module
989+
if kind == "file":
990+
file_context = dummy_context(data)
991+
elif kind == "matrix":
992+
file_context = lib.virtualfile_from_matrix(data)
993+
elif kind == "vectors":
994+
file_context = lib.virtualfile_from_vectors(
995+
np.atleast_1d(x), np.atleast_1d(y), np.atleast_1d(z), *extra_arrays
996+
)
997+
998+
with file_context as fname:
999+
arg_str = " ".join([fname, build_arg_string(kwargs)])
1000+
lib.call_module("plot3d", arg_str)
1001+
8191002
@fmt_docstring
8201003
@use_alias(
8211004
R="region",
@@ -921,6 +1104,8 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs):
9211104
@use_alias(
9221105
R="region",
9231106
J="projection",
1107+
Jz="zscale",
1108+
JZ="zsize",
9241109
B="frame",
9251110
L="map_scale",
9261111
Td="rose",
@@ -935,12 +1120,12 @@ def contour(self, x=None, y=None, z=None, data=None, **kwargs):
9351120
@kwargs_to_strings(R="sequence", p="sequence")
9361121
def basemap(self, **kwargs):
9371122
"""
938-
Produce a basemap for the figure.
1123+
Plot base maps and frames for the figure.
9391124
940-
Several map projections are available, and the user may specify
941-
separate tick-mark intervals for boundary annotation, ticking, and
942-
[optionally] gridlines. A simple map scale or directional rose may also
943-
be plotted.
1125+
Creates a basic or fancy basemap with axes, fill, and titles. Several
1126+
map projections are available, and the user may specify separate
1127+
tick-mark intervals for boundary annotation, ticking, and [optionally]
1128+
gridlines. A simple map scale or directional rose may also be plotted.
9441129
9451130
At least one of the options *frame*, *map_scale*, *rose* or *compass*
9461131
must be specified.
@@ -952,6 +1137,8 @@ def basemap(self, **kwargs):
9521137
Parameters
9531138
----------
9541139
{J}
1140+
zscale/zsize : float or str
1141+
Set z-axis scaling or z-axis size.
9551142
{R}
9561143
{B}
9571144
map_scale : str

pygmt/tests/test_plot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ def test_plot_varying_transparency():
269269

270270
@check_figures_equal()
271271
def test_plot_sizes_colors_transparencies():
272-
"Plot the data using z as transparency"
272+
"Plot the data with varying sizes and colors using z as transparency"
273273
x = np.arange(1.0, 10.0)
274274
y = np.arange(1.0, 10.0)
275275
color = np.arange(1, 10) * 0.15

0 commit comments

Comments
 (0)