Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -48,6 +48,7 @@ Plotting tabular data
:toctree: generated

Figure.contour
Figure.fill_between
Figure.histogram
Figure.meca
Figure.plot
Expand Down
60 changes: 24 additions & 36 deletions examples/gallery/lines/fill_areas_between_curves.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,69 @@
"""
Fill area between curves
========================
Using the ``fill_between`` parameter of the :meth:`pygmt.Figure.plot` method it is
possible to fill the area between two curves y1 and y2. Different fills (colors or
patterns) can be used for the areas y1 > y2 and y1 < y2. Optionally, the curves can be
drawn.
To plot an anomaly along a track use :meth:`pygmt.Figure.wiggle` and see the gallery
example :doc:`Wiggle along tracks </gallery/lines/wiggle>`.
The :meth:`pygmt.Figure.fill_between` method fills the area between two curves y1 and
y2. Different fills (colors or patterns) can be used for the areas y1 > y2 and
y1 < y2. Optionally, the curves can be drawn.
"""

# %%
import numpy as np
import pandas as pd
import pygmt

# Generate some test data and create a pandas DataFrame
# Generate some test data
x = np.arange(-10, 10.2, 0.1)
y1 = np.sin(x * 3)
y2 = np.sin(x / 2)

data_df = pd.DataFrame({"x": x, "y1": y1, "y2": y2})


# %%
# Fill the areas between the two curves using the ``fill_between`` parameter. Use the
# ``fill`` parameter and the modifier **+g** for ``fill_between`` to set different fills
# for areas with y1 > y2 and y1 < y2, respectively. Use the ``label`` parameter and the
# modifier **+l** for ``fill_between`` to set the corresponding legend entries.
# Fill the areas between the two curves. Use the ``fill`` and ``fill2`` parameters to
# set different fills for areas with y1 > y2 and y1 < y2, respectively. Use the
# ``label`` and ``label2`` parameters to set the corresponding legend entries.

fig = pygmt.Figure()
fig.basemap(region=[-10, 10, -5, 5], projection="X15c/5c", frame=True)

fig.plot(
data=data_df,
fill="orange",
label="short > long",
fill_between="c+gsteelblue+lshort < long",
fig.fill_between(
x=x, y=y1, y2=y2, fill="orange", fill2="steelblue", label="y=y1", label2="y=y2"
)

fig.legend()

fig.show()


# %%
# In addition to filling the areas, we can draw the curves. Use the ``pen`` parameter
# and the modifier **+p** for ``fill_between`` to set different lines for the two
# curves y1 and y2, respectively.
# In addition to filling the areas, we can draw the curves. Use the ``pen`` and
# ``pen2`` parameters to set different lines for the two curves y1 and y2, respectively.

fig = pygmt.Figure()
fig.basemap(region=[-10, 10, -5, 5], projection="X15c/5c", frame=True)

fig.plot(
data=data_df,
fig.fill_between(
x=x,
y=y1,
y2=y2,
fill="p8",
fill2="p17",
pen="1p,black,solid",
fill_between="c+gp17+p1p,black,dashed",
pen2="1p,black,dashed",
)

fig.show()


# %%
# To compare a curve y1 to a horizontal line, append **+y** to ``fill_between`` and give
# the desired y-level.
# To compare a curve y1 to a horizontal line, pass the desired y-level to ``y2``.

fig = pygmt.Figure()
fig.basemap(region=[-10, 10, -5, 5], projection="X15c/5c", frame=True)

fig.plot(
data=data_df[["x", "y1"]],
fig.fill_between(
x=x,
y=y1,
y2=0.42,
fill="p8",
fill2="p17",
pen="1p,black,solid",
# Define a horizontal line at y=0.42
fill_between="c+gp17+p1p,black,dashed+y0.42",
pen2="1p,black,dashed",
)

fig.show()

# sphinx_gallery_thumbnail_number = 1
2 changes: 2 additions & 0 deletions pygmt/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from pygmt.src.colorbar import colorbar as _colorbar
from pygmt.src.contour import contour as _contour
from pygmt.src.directional_rose import directional_rose as _directional_rose
from pygmt.src.fill_between import fill_between as _fill_between
from pygmt.src.grdcontour import grdcontour as _grdcontour
from pygmt.src.grdimage import grdimage as _grdimage
from pygmt.src.grdview import grdview as _grdview
Expand Down Expand Up @@ -449,6 +450,7 @@ def _repr_html_(self) -> str:
colorbar = _colorbar
contour = _contour
directional_rose = _directional_rose
fill_between = _fill_between
grdcontour = _grdcontour
grdimage = _grdimage
grdview = _grdview
Expand Down
149 changes: 149 additions & 0 deletions pygmt/src/fill_between.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""
fill_between - Fill the area between two horizontal curves.
"""

from collections.abc import Sequence
from typing import Literal

import numpy as np
from pygmt.alias import Alias, AliasSystem
from pygmt.clib import Session
from pygmt.exceptions import GMTValueError
from pygmt.helpers import build_arg_list, fmt_docstring
from pygmt.params import Axis, Frame


@fmt_docstring
def fill_between( # noqa: PLR0913
self,
x: Sequence[float],
y: Sequence[float],
y2: float | Sequence[float] = 0,
fill: str | None = None,
pen: str | None = None,
label: str | None = None,
fill2: str | None = None,
pen2: str | None = None,
label2: str | None = None,
projection: str | None = None,
region: Sequence[float | str] | str | None = None,
frame: Frame | Axis | Literal["none"] | str | Sequence[str] | bool = False,
verbose: bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
transparency: float | Sequence[float] | bool | None = None,
):
"""
Fill the area between two horizontal curves.

This method is a high-level wrapper around :meth:`pygmt.Figure.plot` to fill the
area between a primary curve ``y(x)`` and a secondary curve ``y2(x)``. The ``y2``
parameter can be either a single value [Default is 0] or a sequence with the same
length as ``x`` and ``y``.

Parameters
----------
x
X-coordinates of the curves.
y
Y-coordinates of the primary curve.
y2
Y-coordinates of the secondary curve. It can be a scalar value for a horizontal
reference line, or a sequence with the same length as ``x`` and ``y``. Default
is 0.
fill
Fill for areas where the primary curve is greater than the secondary curve.
fill2
Fill for areas where the secondary curve is greater than the primary curve.
pen
Pen attributes for the primary curve.
pen2
Pen attributes for the secondary curve.
label
Label for the primary curve, to be displayed in the legend.
label2
Label for the secondary curve, to be displayed in the legend.
$projection
$region
$frame
$verbose
$panel
$perspective
$transparency

Examples
--------
>>> import numpy as np
>>> import pygmt
>>> x = np.linspace(0, 2 * np.pi, 200)
>>> fig = pygmt.Figure()
>>> fig.fill_between(
... x=x,
... y=np.sin(2 * x),
... y2=np.sin(3 * x),
... region=[0, 4 * np.pi, -1.2, 1.2],
... projection="X10c/4c",
... frame=True,
... fill="lightblue",
... pen="1p,blue",
... fill2="lightred",
... pen2="1p,red",
... )
>>> fig.show()
"""
self._activate_figure()
_x = np.atleast_1d(x)
_y = np.atleast_1d(y)
_y2 = np.atleast_1d(y2)

y2_is_scalar = np.ndim(y2) == 0

# Validate the lengths of the input arrays
npoints = _x.size
if npoints <= 1:
raise GMTValueError(
npoints,
description="size for 'x'/'y'",
reason="'x' and 'y' must be arrays with lengths greater than 1.",
)
if _y.size != npoints:
raise GMTValueError(
_y.size,
description="size for 'y'",
reason=f"'y' is expected to have length {npoints!r}.",
)
if not y2_is_scalar and _y2.size != npoints:
raise GMTValueError(
_y2.size,
description="size for 'y2'",
reason=f"'y2' is expected to be a scalar or have length {npoints!r}.",
)

data = {"x": _x, "y": _y} if y2_is_scalar else {"x": _x, "y": _y, "y2": _y2}

aliasdict = AliasSystem(
G=Alias(fill, name="fill"),
M=[
Alias("c"),
Alias(fill2, name="fill2", prefix="+g"),
Alias(pen2, name="pen2", prefix="+p"),
Alias(label2, name="label2", prefix="+l"),
Alias(y2 if y2_is_scalar else None, name="y2", prefix="+y"),
],
W=Alias(pen, name="pen"),
l=Alias(label, name="label"),
).add_common(
B=frame,
J=projection,
R=region,
V=verbose,
c=panel,
p=perspective,
t=transparency,
)

with Session() as lib:
with lib.virtualfile_in(data=data) as vintbl:
lib.call_module(
module="plot", args=build_arg_list(aliasdict, infile=vintbl)
)
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_fill_between_coregistered.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: a4e06de28387321846b5dc8308224bde
size: 28624
hash: md5
path: test_fill_between_coregistered.png
5 changes: 5 additions & 0 deletions pygmt/tests/baseline/test_fill_between_y2_scalar.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: 43a8d89f104ef27788da4ce04a31f941
size: 20863
hash: md5
path: test_fill_between_y2_scalar.png
95 changes: 95 additions & 0 deletions pygmt/tests/test_fill_between.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
Tests for Figure.fill_between.
"""

import numpy as np
import pytest
from pygmt import Figure
from pygmt.exceptions import GMTValueError


@pytest.fixture(scope="module", name="x")
def fixture_x():
"""
X-coordinates of the primary curve.
"""
return np.linspace(0, 4, 200)


@pytest.fixture(scope="module", name="y")
def fixture_y(x):
"""
Y-coordinates of the primary curve.
"""
return np.sin(5 * x)


@pytest.fixture(scope="module", name="y2")
def fixture_y2(x):
"""
Y-coordinates of the secondary curve.
"""
return 0.5 * np.cos(3 * x)


@pytest.mark.mpl_image_compare
def test_fill_between_y2_scalar(x, y):
"""
Fill between a curve and a horizontal reference level.
"""
fig = Figure()
fig.basemap(region=[0, 4, -1.2, 1.2], projection="X10c/5c", frame=True)
fig.fill_between(
x=x,
y=y,
y2=0,
fill="lightblue",
fill2="lightred",
pen="1p,blue",
pen2="1p,red",
label="y=sin(5x)",
label2="y=0",
)
fig.legend()
return fig


@pytest.mark.mpl_image_compare
def test_fill_between_coregistered(x, y, y2):
"""
Fill between two co-registered curves.
"""
fig = Figure()
fig.basemap(region=[0, 4, -1.2, 1.2], projection="X10c/5c", frame=True)
fig.fill_between(
x=x,
y=y,
y2=y2,
fill="lightgreen",
fill2="lightred",
pen="1p,green",
pen2="1p,red",
label="y=sin(5x)",
label2="y=0.5*cos(3x)",
)
fig.legend()
return fig


def test_fill_between_invalid_input():
"""
Test invalid input for fill_between.
"""
fig = Figure()
with pytest.raises(GMTValueError):
fig.fill_between(x=0, y=[1, 2])
with pytest.raises(GMTValueError):
fig.fill_between(x=[0, 1], y=1)
with pytest.raises(GMTValueError):
fig.fill_between(x=[0], y=[1])
with pytest.raises(GMTValueError):
fig.fill_between(x=[0, 1], y=[1])
with pytest.raises(GMTValueError):
fig.fill_between(x=[0, 1], y=[1, 2], y2=[0])
with pytest.raises(GMTValueError):
fig.fill_between(x=[0, 1], y=[1, 2], y2=[0, 1, 2])
Loading