Files
california-equity-git/.venv/lib/python3.12/site-packages/geopandas/tests/test_plotting.py
2024-09-28 22:56:00 -07:00

1963 lines
73 KiB
Python

import itertools
import warnings
from packaging.version import Version
import numpy as np
import pandas as pd
from shapely.affinity import rotate
from shapely.geometry import (
GeometryCollection,
LinearRing,
LineString,
MultiLineString,
MultiPoint,
MultiPolygon,
Point,
Polygon,
box,
)
import geopandas._compat as compat
from geopandas import GeoDataFrame, GeoSeries, read_file
from geopandas.plotting import GeoplotAccessor
import pytest
matplotlib = pytest.importorskip("matplotlib")
matplotlib.use("Agg")
import matplotlib.pyplot as plt
try: # skipif and importorskip do not work for decorators
from matplotlib.testing.decorators import check_figures_equal
MPL_DECORATORS = True
except ImportError:
MPL_DECORATORS = False
@pytest.fixture(autouse=True)
def close_figures(request):
yield
plt.close("all")
try:
cycle = matplotlib.rcParams["axes.prop_cycle"].by_key()
MPL_DFT_COLOR = cycle["color"][0]
except KeyError:
MPL_DFT_COLOR = matplotlib.rcParams["axes.color_cycle"][0]
plt.rcParams.update({"figure.max_open_warning": 0})
class TestPointPlotting:
def setup_method(self):
self.N = 10
self.points = GeoSeries(Point(i, i) for i in range(self.N))
values = np.arange(self.N)
self.df = GeoDataFrame({"geometry": self.points, "values": values})
self.df["exp"] = (values * 10) ** 3
multipoint1 = MultiPoint(self.points)
multipoint2 = rotate(multipoint1, 90)
self.df2 = GeoDataFrame(
{"geometry": [multipoint1, multipoint2], "values": [0, 1]}
)
def test_figsize(self):
ax = self.points.plot(figsize=(1, 1))
np.testing.assert_array_equal(ax.figure.get_size_inches(), (1, 1))
ax = self.df.plot(figsize=(1, 1))
np.testing.assert_array_equal(ax.figure.get_size_inches(), (1, 1))
def test_default_colors(self):
# # without specifying values -> uniform color
# GeoSeries
ax = self.points.plot()
_check_colors(
self.N, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR] * self.N
)
# GeoDataFrame
ax = self.df.plot()
_check_colors(
self.N, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR] * self.N
)
# # with specifying values -> different colors for all 10 values
ax = self.df.plot(column="values")
cmap = plt.get_cmap()
expected_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, ax.collections[0].get_facecolors(), expected_colors)
def test_series_color_no_index(self):
# Color order with ordered index
colors_ord = pd.Series(["a", "b", "c", "a", "b", "c", "a", "b", "c", "a"])
# Plot using Series as color
ax1 = self.df.plot(colors_ord)
# Correct answer: Add as column to df and plot
self.df["colors_ord"] = colors_ord
ax2 = self.df.plot("colors_ord")
# Confirm out-of-order index re-sorted
point_colors1 = ax1.collections[0].get_facecolors()
point_colors2 = ax2.collections[0].get_facecolors()
np.testing.assert_array_equal(point_colors1[1], point_colors2[1])
def test_series_color_index(self):
# Color order with out-of-order index
colors_ord = pd.Series(
["a", "a", "a", "a", "b", "b", "b", "c", "c", "c"],
index=[0, 3, 6, 9, 1, 4, 7, 2, 5, 8],
)
# Plot using Series as color
ax1 = self.df.plot(colors_ord)
# Correct answer: Add as column to df and plot
self.df["colors_ord"] = colors_ord
ax2 = self.df.plot("colors_ord")
# Confirm out-of-order index re-sorted
point_colors1 = ax1.collections[0].get_facecolors()
point_colors2 = ax2.collections[0].get_facecolors()
np.testing.assert_array_equal(point_colors1[1], point_colors2[1])
def test_colormap(self):
# without specifying values but cmap specified -> no uniform color
# but different colors for all points
# GeoSeries
ax = self.points.plot(cmap="RdYlGn")
cmap = plt.get_cmap("RdYlGn")
exp_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, ax.collections[0].get_facecolors(), exp_colors)
ax = self.df.plot(cmap="RdYlGn")
_check_colors(self.N, ax.collections[0].get_facecolors(), exp_colors)
# # with specifying values -> different colors for all 10 values
ax = self.df.plot(column="values", cmap="RdYlGn")
cmap = plt.get_cmap("RdYlGn")
_check_colors(self.N, ax.collections[0].get_facecolors(), exp_colors)
# when using a cmap with specified lut -> limited number of different
# colors
ax = self.points.plot(cmap=plt.get_cmap("Set1", lut=5))
cmap = plt.get_cmap("Set1", lut=5)
exp_colors = cmap(list(range(5)) * 2)
_check_colors(self.N, ax.collections[0].get_facecolors(), exp_colors)
def test_single_color(self):
ax = self.points.plot(color="green")
_check_colors(self.N, ax.collections[0].get_facecolors(), ["green"] * self.N)
ax = self.df.plot(color="green")
_check_colors(self.N, ax.collections[0].get_facecolors(), ["green"] * self.N)
# check rgba tuple GH1178
ax = self.df.plot(color=(0.5, 0.5, 0.5))
_check_colors(
self.N, ax.collections[0].get_facecolors(), [(0.5, 0.5, 0.5)] * self.N
)
ax = self.df.plot(color=(0.5, 0.5, 0.5, 0.5))
_check_colors(
self.N, ax.collections[0].get_facecolors(), [(0.5, 0.5, 0.5, 0.5)] * self.N
)
with pytest.raises((ValueError, TypeError)):
self.df.plot(color="not color")
with warnings.catch_warnings(record=True) as _: # don't print warning
# 'color' overrides 'column'
ax = self.df.plot(column="values", color="green")
_check_colors(
self.N, ax.collections[0].get_facecolors(), ["green"] * self.N
)
def test_markersize(self):
ax = self.points.plot(markersize=10)
assert ax.collections[0].get_sizes() == [10]
ax = self.df.plot(markersize=10)
assert ax.collections[0].get_sizes() == [10]
ax = self.df.plot(column="values", markersize=10)
assert ax.collections[0].get_sizes() == [10]
ax = self.df.plot(markersize="values")
assert (ax.collections[0].get_sizes() == self.df["values"]).all()
ax = self.df.plot(column="values", markersize="values")
assert (ax.collections[0].get_sizes() == self.df["values"]).all()
def test_markerstyle(self):
ax = self.df2.plot(marker="+")
expected = _style_to_vertices("+")
np.testing.assert_array_equal(
expected, ax.collections[0].get_paths()[0].vertices
)
def test_style_kwargs(self):
ax = self.points.plot(edgecolors="k")
assert (ax.collections[0].get_edgecolor() == [0, 0, 0, 1]).all()
def test_style_kwargs_alpha(self):
ax = self.df.plot(alpha=0.7)
np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha())
try:
ax = self.df.plot(alpha=np.linspace(0, 0.0, 1.0, self.N))
except TypeError:
# no list allowed for alpha up to matplotlib 3.3
pass
else:
np.testing.assert_array_equal(
np.linspace(0, 0.0, 1.0, self.N), ax.collections[0].get_alpha()
)
# TODO temporary skip for mpl 3.8.0.dev
# (https://github.com/matplotlib/matplotlib/issues/25162)
@pytest.mark.skipif(
Version(matplotlib.__version__) >= Version("3.8.0.dev")
and Version(matplotlib.__version__) < Version("3.8.0"),
reason="failing with matplotlib dev",
)
def test_legend(self):
with warnings.catch_warnings(record=True) as _: # don't print warning
# legend ignored if color is given.
ax = self.df.plot(column="values", color="green", legend=True)
assert len(ax.get_figure().axes) == 1 # no separate legend axis
# legend ignored if no column is given.
ax = self.df.plot(legend=True)
assert len(ax.get_figure().axes) == 1 # no separate legend axis
# # Continuous legend
# the colorbar matches the Point colors
ax = self.df.plot(column="values", cmap="RdYlGn", legend=True)
point_colors = ax.collections[0].get_facecolors()
cbar_colors = _get_colorbar_ax(ax.get_figure()).collections[-1].get_facecolors()
# first point == bottom of colorbar
np.testing.assert_array_equal(point_colors[0], cbar_colors[0])
# last point == top of colorbar
np.testing.assert_array_equal(point_colors[-1], cbar_colors[-1])
# # Categorical legend
# the colorbar matches the Point colors
ax = self.df.plot(column="values", categorical=True, legend=True)
point_colors = ax.collections[0].get_facecolors()
cbar_colors = ax.get_legend().axes.collections[-1].get_facecolors()
# first point == bottom of colorbar
np.testing.assert_array_equal(point_colors[0], cbar_colors[0])
# last point == top of colorbar
np.testing.assert_array_equal(point_colors[-1], cbar_colors[-1])
# # Normalized legend
# the colorbar matches the Point colors
norm = matplotlib.colors.LogNorm(
vmin=self.df[1:].exp.min(), vmax=self.df[1:].exp.max()
)
ax = self.df[1:].plot(column="exp", cmap="RdYlGn", legend=True, norm=norm)
point_colors = ax.collections[0].get_facecolors()
cbar_colors = _get_colorbar_ax(ax.get_figure()).collections[-1].get_facecolors()
# first point == bottom of colorbar
np.testing.assert_array_equal(point_colors[0], cbar_colors[0])
# last point == top of colorbar
np.testing.assert_array_equal(point_colors[-1], cbar_colors[-1])
# colorbar generated proper long transition
assert cbar_colors.shape == (256, 4)
def test_subplots_norm(self):
# colors of subplots are the same as for plot (norm is applied)
cmap = matplotlib.cm.viridis_r
norm = matplotlib.colors.Normalize(vmin=0, vmax=20)
ax = self.df.plot(column="values", cmap=cmap, norm=norm)
actual_colors_orig = ax.collections[0].get_facecolors()
exp_colors = cmap(np.arange(10) / (20))
np.testing.assert_array_equal(exp_colors, actual_colors_orig)
fig, ax = plt.subplots()
self.df[1:].plot(column="values", ax=ax, norm=norm, cmap=cmap)
actual_colors_sub = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(actual_colors_orig[1], actual_colors_sub[0])
def test_empty_plot(self):
s = GeoSeries([Polygon()])
with pytest.warns(UserWarning):
ax = s.plot()
assert len(ax.collections) == 0
s = GeoSeries([])
with pytest.warns(UserWarning):
ax = s.plot()
assert len(ax.collections) == 0
df = GeoDataFrame([], columns=["geometry"])
with pytest.warns(UserWarning):
ax = df.plot()
assert len(ax.collections) == 0
def test_empty_geometry(self):
s = GeoSeries([Polygon([(0, 0), (1, 0), (1, 1)]), Polygon()])
ax = s.plot()
assert len(ax.collections) == 1
# more complex case with GEOMETRYCOLLECTION EMPTY, POINT EMPTY and NONE
poly = Polygon([(-1, -1), (-1, 2), (2, 2), (2, -1), (-1, -1)])
point = Point(0, 1)
point_ = Point(10, 10)
empty_point = Point()
gdf = GeoDataFrame(geometry=[point, empty_point, point_])
gdf["geometry"] = gdf.intersection(poly)
with warnings.catch_warnings():
# loc to add row calls concat internally, warning for pandas >=2.1
warnings.filterwarnings(
"ignore",
"The behavior of DataFrame concatenation with empty",
FutureWarning,
)
gdf.loc[3] = [None]
ax = gdf.plot()
assert len(ax.collections) == 1
@pytest.mark.parametrize(
"geoms",
[
[
box(0, 0, 1, 1),
box(7, 7, 8, 8),
],
[
LineString([(1, 1), (1, 2)]),
LineString([(7, 1), (7, 2)]),
],
[
Point(1, 1),
Point(7, 7),
],
],
)
def test_empty_geometry_colors(self, geoms):
s = GeoSeries(
geoms,
index=["r", "b"],
)
s2 = s.intersection(box(5, 0, 10, 10))
ax = s2.plot(color=["red", "blue"])
blue = np.array([0.0, 0.0, 1.0, 1.0])
if s.geom_type["r"] == "LineString":
np.testing.assert_array_equal(ax.get_children()[0].get_edgecolor()[0], blue)
else:
np.testing.assert_array_equal(ax.get_children()[0].get_facecolor()[0], blue)
def test_multipoints(self):
# MultiPoints
ax = self.df2.plot()
_check_colors(4, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR] * 4)
ax = self.df2.plot(column="values")
cmap = plt.get_cmap(lut=2)
expected_colors = [cmap(0)] * self.N + [cmap(1)] * self.N
_check_colors(20, ax.collections[0].get_facecolors(), expected_colors)
ax = self.df2.plot(color=["r", "b"])
# colors are repeated for all components within a MultiPolygon
_check_colors(20, ax.collections[0].get_facecolors(), ["r"] * 10 + ["b"] * 10)
def test_multipoints_alpha(self):
ax = self.df2.plot(alpha=0.7)
np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha())
try:
ax = self.df2.plot(alpha=[0.7, 0.2])
except TypeError:
# no list allowed for alpha up to matplotlib 3.3
pass
else:
np.testing.assert_array_equal(
[0.7] * 10 + [0.2] * 10, ax.collections[0].get_alpha()
)
def test_categories(self):
self.df["cats_object"] = ["cat1", "cat2"] * 5
self.df["nums"] = [1, 2] * 5
self.df["singlecat_object"] = ["cat2"] * 10
self.df["cats"] = pd.Categorical(["cat1", "cat2"] * 5)
self.df["singlecat"] = pd.Categorical(
["cat2"] * 10, categories=["cat1", "cat2"]
)
self.df["cats_ordered"] = pd.Categorical(
["cat2", "cat1"] * 5, categories=["cat2", "cat1"]
)
self.df["bool"] = [False, True] * 5
self.df["bool_extension"] = pd.array([False, True] * 5)
self.df["cats_string"] = pd.array(["cat1", "cat2"] * 5, dtype="string")
ax1 = self.df.plot("cats_object", legend=True)
ax2 = self.df.plot("cats", legend=True)
ax3 = self.df.plot("singlecat_object", categories=["cat1", "cat2"], legend=True)
ax4 = self.df.plot("singlecat", legend=True)
ax5 = self.df.plot("cats_ordered", legend=True)
ax6 = self.df.plot("nums", categories=[1, 2], legend=True)
ax7 = self.df.plot("bool", legend=True)
ax8 = self.df.plot("bool_extension", legend=True)
ax9 = self.df.plot("cats_string", legend=True)
point_colors1 = ax1.collections[0].get_facecolors()
for ax in [ax2, ax3, ax4, ax5, ax6, ax7, ax8, ax9]:
point_colors2 = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(point_colors1[1], point_colors2[1])
legend1 = [x.get_markerfacecolor() for x in ax1.get_legend().get_lines()]
for ax in [ax2, ax3, ax4, ax5, ax6, ax7, ax8, ax9]:
legend2 = [x.get_markerfacecolor() for x in ax.get_legend().get_lines()]
np.testing.assert_array_equal(legend1, legend2)
with pytest.raises(TypeError):
self.df.plot(column="cats_object", categories="non_list")
with pytest.raises(
ValueError, match="Column contains values not listed in categories."
):
self.df.plot(column="cats_object", categories=["cat1"])
with pytest.raises(
ValueError, match="Cannot specify 'categories' when column has"
):
self.df.plot(column="cats", categories=["cat1"])
def test_missing(self):
self.df.loc[0, "values"] = np.nan
ax = self.df.plot("values")
cmap = plt.get_cmap()
expected_colors = cmap(np.arange(self.N - 1) / (self.N - 2))
_check_colors(self.N - 1, ax.collections[0].get_facecolors(), expected_colors)
ax = self.df.plot("values", missing_kwds={"color": "r"})
cmap = plt.get_cmap()
expected_colors = cmap(np.arange(self.N - 1) / (self.N - 2))
_check_colors(1, ax.collections[1].get_facecolors(), ["r"])
_check_colors(self.N - 1, ax.collections[0].get_facecolors(), expected_colors)
ax = self.df.plot(
"values", missing_kwds={"color": "r"}, categorical=True, legend=True
)
_check_colors(1, ax.collections[1].get_facecolors(), ["r"])
point_colors = ax.collections[0].get_facecolors()
nan_color = ax.collections[1].get_facecolors()
leg_colors = ax.get_legend().axes.collections[0].get_facecolors()
leg_colors1 = ax.get_legend().axes.collections[1].get_facecolors()
np.testing.assert_array_equal(point_colors[0], leg_colors[0])
np.testing.assert_array_equal(nan_color[0], leg_colors1[0])
def test_no_missing_and_missing_kwds(self):
# GH2210
df = self.df.copy()
df["category"] = df["values"].astype("str")
df.plot("category", missing_kwds={"facecolor": "none"}, legend=True)
class TestPointZPlotting:
def setup_method(self):
self.N = 10
self.points = GeoSeries(Point(i, i, i) for i in range(self.N))
values = np.arange(self.N)
self.df = GeoDataFrame({"geometry": self.points, "values": values})
def test_plot(self):
# basic test that points with z coords don't break plotting
self.df.plot()
class TestLineStringPlotting:
def setup_method(self):
self.N = 10
values = np.arange(self.N)
self.lines = GeoSeries(
[LineString([(0, i), (4, i + 0.5), (9, i)]) for i in range(self.N)],
index=list("ABCDEFGHIJ"),
)
self.df = GeoDataFrame({"geometry": self.lines, "values": values})
multiline1 = MultiLineString(self.lines.loc["A":"B"].values)
multiline2 = MultiLineString(self.lines.loc["C":"D"].values)
self.df2 = GeoDataFrame(
{"geometry": [multiline1, multiline2], "values": [0, 1]}
)
self.linearrings = GeoSeries(
[LinearRing([(0, i), (4, i + 0.5), (9, i)]) for i in range(self.N)],
index=list("ABCDEFGHIJ"),
)
self.df3 = GeoDataFrame({"geometry": self.linearrings, "values": values})
def test_autolim_false(self):
"""Test linestring plot preserving axes limits."""
ax = self.lines[: self.N // 2].plot()
ylim = ax.get_ylim()
self.lines.plot(ax=ax, autolim=False)
assert ax.get_ylim() == ylim
ax = self.df[: self.N // 2].plot()
ylim = ax.get_ylim()
self.df.plot(ax=ax, autolim=False)
assert ax.get_ylim() == ylim
def test_autolim_true(self):
"""Test linestring plot autoscaling axes limits."""
ax = self.lines[: self.N // 2].plot()
ylim = ax.get_ylim()
self.lines.plot(ax=ax, autolim=True)
assert ax.get_ylim() != ylim
ax = self.df[: self.N // 2].plot()
ylim = ax.get_ylim()
self.df.plot(ax=ax, autolim=True)
assert ax.get_ylim() != ylim
def test_single_color(self):
ax = self.lines.plot(color="green")
_check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N)
ax = self.df.plot(color="green")
_check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N)
ax = self.linearrings.plot(color="green")
_check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N)
ax = self.df3.plot(color="green")
_check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N)
# check rgba tuple GH1178
ax = self.df.plot(color=(0.5, 0.5, 0.5, 0.5))
_check_colors(
self.N, ax.collections[0].get_colors(), [(0.5, 0.5, 0.5, 0.5)] * self.N
)
ax = self.df.plot(color=(0.5, 0.5, 0.5, 0.5))
_check_colors(
self.N, ax.collections[0].get_colors(), [(0.5, 0.5, 0.5, 0.5)] * self.N
)
with pytest.raises((TypeError, ValueError)):
self.df.plot(color="not color")
with warnings.catch_warnings(record=True) as _: # don't print warning
# 'color' overrides 'column'
ax = self.df.plot(column="values", color="green")
_check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N)
def test_style_kwargs_linestyle(self):
# single
for ax in [
self.lines.plot(linestyle=":", linewidth=1),
self.df.plot(linestyle=":", linewidth=1),
self.df.plot(column="values", linestyle=":", linewidth=1),
]:
assert [(0.0, [1.0, 1.65])] == ax.collections[0].get_linestyle()
# tuple
ax = self.lines.plot(linestyle=(0, (3, 10, 1, 15)), linewidth=1)
assert [(0, [3, 10, 1, 15])] == ax.collections[0].get_linestyle()
# multiple
ls = [("dashed", "dotted", "dashdot", "solid")[k % 4] for k in range(self.N)]
exp_ls = [_style_to_linestring_onoffseq(st, 1) for st in ls]
for ax in [
self.lines.plot(linestyle=ls, linewidth=1),
self.lines.plot(linestyles=ls, linewidth=1),
self.df.plot(linestyle=ls, linewidth=1),
self.df.plot(column="values", linestyle=ls, linewidth=1),
]:
assert exp_ls == ax.collections[0].get_linestyle()
def test_style_kwargs_linewidth(self):
# single
for ax in [
self.lines.plot(linewidth=2),
self.df.plot(linewidth=2),
self.df.plot(column="values", linewidth=2),
]:
np.testing.assert_array_equal([2], ax.collections[0].get_linewidths())
# multiple
lw = [(0, 1, 2, 5.5, 10)[k % 5] for k in range(self.N)]
for ax in [
self.lines.plot(linewidth=lw),
self.lines.plot(linewidths=lw),
self.df.plot(linewidth=lw),
self.df.plot(column="values", linewidth=lw),
]:
np.testing.assert_array_equal(lw, ax.collections[0].get_linewidths())
def test_style_kwargs_alpha(self):
ax = self.df.plot(alpha=0.7)
np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha())
try:
ax = self.df.plot(alpha=np.linspace(0, 0.0, 1.0, self.N))
except TypeError:
# no list allowed for alpha up to matplotlib 3.3
pass
else:
np.testing.assert_array_equal(
np.linspace(0, 0.0, 1.0, self.N), ax.collections[0].get_alpha()
)
def test_style_kwargs_path_effects(self):
from matplotlib.patheffects import withStroke
effects = [withStroke(linewidth=8, foreground="b")]
ax = self.df.plot(color="orange", path_effects=effects)
assert ax.collections[0].get_path_effects()[0].__dict__["_gc"] == {
"linewidth": 8,
"foreground": "b",
}
def test_subplots_norm(self):
# colors of subplots are the same as for plot (norm is applied)
cmap = matplotlib.cm.viridis_r
norm = matplotlib.colors.Normalize(vmin=0, vmax=20)
ax = self.df.plot(column="values", cmap=cmap, norm=norm)
actual_colors_orig = ax.collections[0].get_edgecolors()
exp_colors = cmap(np.arange(10) / (20))
np.testing.assert_array_equal(exp_colors, actual_colors_orig)
fig, ax = plt.subplots()
self.df[1:].plot(column="values", ax=ax, norm=norm, cmap=cmap)
actual_colors_sub = ax.collections[0].get_edgecolors()
np.testing.assert_array_equal(actual_colors_orig[1], actual_colors_sub[0])
def test_multilinestrings(self):
# MultiLineStrings
ax = self.df2.plot()
assert len(ax.collections[0].get_paths()) == 4
_check_colors(4, ax.collections[0].get_edgecolors(), [MPL_DFT_COLOR] * 4)
ax = self.df2.plot("values")
cmap = plt.get_cmap(lut=2)
# colors are repeated for all components within a MultiLineString
expected_colors = [cmap(0), cmap(0), cmap(1), cmap(1)]
_check_colors(4, ax.collections[0].get_edgecolors(), expected_colors)
ax = self.df2.plot(color=["r", "b"])
# colors are repeated for all components within a MultiLineString
_check_colors(4, ax.collections[0].get_edgecolors(), ["r", "r", "b", "b"])
class TestPolygonPlotting:
def setup_method(self):
t1 = Polygon([(0, 0), (1, 0), (1, 1)])
t2 = Polygon([(1, 0), (2, 0), (2, 1)])
self.polys = GeoSeries([t1, t2], index=list("AB"))
self.df = GeoDataFrame({"geometry": self.polys, "values": [0, 1]})
multipoly1 = MultiPolygon([t1, t2])
multipoly2 = rotate(multipoly1, 180)
self.df2 = GeoDataFrame(
{"geometry": [multipoly1, multipoly2], "values": [0, 1]}
)
t3 = Polygon([(2, 0), (3, 0), (3, 1)])
df_nan = GeoDataFrame({"geometry": t3, "values": [np.nan]})
self.df3 = pd.concat([self.df, df_nan])
def test_autolim_false(self):
"""Test polygon plot preserving axes limits."""
ax = self.polys[:1].plot()
xlim = ax.get_xlim()
self.polys.plot(ax=ax, autolim=False)
assert ax.get_xlim() == xlim
ax = self.df[:1].plot()
xlim = ax.get_xlim()
self.df.plot(ax=ax, autolim=False)
assert ax.get_xlim() == xlim
def test_autolim_true(self):
"""Test polygon plot autoscaling axes limits."""
ax = self.polys[:1].plot()
xlim = ax.get_xlim()
self.polys.plot(ax=ax, autolim=True)
assert ax.get_xlim() != xlim
ax = self.df[:1].plot()
xlim = ax.get_xlim()
self.df.plot(ax=ax, autolim=True)
assert ax.get_xlim() != xlim
def test_single_color(self):
ax = self.polys.plot(color="green")
_check_colors(2, ax.collections[0].get_facecolors(), ["green"] * 2)
# color only sets facecolor
assert len(ax.collections[0].get_edgecolors()) == 0
ax = self.df.plot(color="green")
_check_colors(2, ax.collections[0].get_facecolors(), ["green"] * 2)
assert len(ax.collections[0].get_edgecolors()) == 0
# check rgba tuple GH1178
ax = self.df.plot(color=(0.5, 0.5, 0.5))
_check_colors(2, ax.collections[0].get_facecolors(), [(0.5, 0.5, 0.5)] * 2)
ax = self.df.plot(color=(0.5, 0.5, 0.5, 0.5))
_check_colors(2, ax.collections[0].get_facecolors(), [(0.5, 0.5, 0.5, 0.5)] * 2)
with pytest.raises((TypeError, ValueError)):
self.df.plot(color="not color")
with warnings.catch_warnings(record=True) as _: # don't print warning
# 'color' overrides 'values'
ax = self.df.plot(column="values", color="green")
_check_colors(2, ax.collections[0].get_facecolors(), ["green"] * 2)
def test_vmin_vmax(self):
# when vmin == vmax, all polygons should be the same color
# non-categorical
ax = self.df.plot(column="values", categorical=False, vmin=0, vmax=0)
actual_colors = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(actual_colors[0], actual_colors[1])
# categorical
ax = self.df.plot(column="values", categorical=True, vmin=0, vmax=0)
actual_colors = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(actual_colors[0], actual_colors[1])
# vmin vmax set correctly for array with NaN (GitHub issue 877)
ax = self.df3.plot(column="values")
actual_colors = ax.collections[0].get_facecolors()
assert np.any(np.not_equal(actual_colors[0], actual_colors[1]))
def test_style_kwargs_color(self):
# facecolor overrides default cmap when color is not set
ax = self.polys.plot(facecolor="k")
_check_colors(2, ax.collections[0].get_facecolors(), ["k"] * 2)
# facecolor overrides more general-purpose color when both are set
ax = self.polys.plot(color="red", facecolor="k")
# TODO with new implementation, color overrides facecolor
# _check_colors(2, ax.collections[0], ['k']*2, alpha=0.5)
# edgecolor
ax = self.polys.plot(edgecolor="red")
np.testing.assert_array_equal(
[(1, 0, 0, 1)], ax.collections[0].get_edgecolors()
)
ax = self.df.plot("values", edgecolor="red")
np.testing.assert_array_equal(
[(1, 0, 0, 1)], ax.collections[0].get_edgecolors()
)
# alpha sets both edge and face
ax = self.polys.plot(facecolor="g", edgecolor="r", alpha=0.4)
_check_colors(2, ax.collections[0].get_facecolors(), ["g"] * 2, alpha=0.4)
_check_colors(2, ax.collections[0].get_edgecolors(), ["r"] * 2, alpha=0.4)
# check rgba tuple GH1178 for face and edge
ax = self.df.plot(facecolor=(0.5, 0.5, 0.5), edgecolor=(0.4, 0.5, 0.6))
_check_colors(2, ax.collections[0].get_facecolors(), [(0.5, 0.5, 0.5)] * 2)
_check_colors(2, ax.collections[0].get_edgecolors(), [(0.4, 0.5, 0.6)] * 2)
ax = self.df.plot(
facecolor=(0.5, 0.5, 0.5, 0.5), edgecolor=(0.4, 0.5, 0.6, 0.5)
)
_check_colors(2, ax.collections[0].get_facecolors(), [(0.5, 0.5, 0.5, 0.5)] * 2)
_check_colors(2, ax.collections[0].get_edgecolors(), [(0.4, 0.5, 0.6, 0.5)] * 2)
def test_style_kwargs_linestyle(self):
# single
ax = self.df.plot(linestyle=":", linewidth=1)
assert [(0.0, [1.0, 1.65])] == ax.collections[0].get_linestyle()
# tuple
ax = self.df.plot(linestyle=(0, (3, 10, 1, 15)), linewidth=1)
assert [(0, [3, 10, 1, 15])] == ax.collections[0].get_linestyle()
# multiple
ls = ["dashed", "dotted"]
exp_ls = [_style_to_linestring_onoffseq(st, 1) for st in ls]
for ax in [
self.df.plot(linestyle=ls, linewidth=1),
self.df.plot(linestyles=ls, linewidth=1),
]:
assert exp_ls == ax.collections[0].get_linestyle()
def test_style_kwargs_linewidth(self):
# single
ax = self.df.plot(linewidth=2)
np.testing.assert_array_equal([2], ax.collections[0].get_linewidths())
# multiple
for ax in [self.df.plot(linewidth=[2, 4]), self.df.plot(linewidths=[2, 4])]:
np.testing.assert_array_equal([2, 4], ax.collections[0].get_linewidths())
# alpha
ax = self.df.plot(alpha=0.7)
np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha())
try:
ax = self.df.plot(alpha=[0.7, 0.2])
except TypeError:
# no list allowed for alpha up to matplotlib 3.3
pass
else:
np.testing.assert_array_equal([0.7, 0.2], ax.collections[0].get_alpha())
def test_legend_kwargs(self):
categories = list(self.df["values"].unique())
prefix = "LABEL_FOR_"
ax = self.df.plot(
column="values",
categorical=True,
categories=categories,
legend=True,
legend_kwds={
"labels": [prefix + str(c) for c in categories],
"frameon": False,
},
)
assert len(categories) == len(ax.get_legend().get_texts())
assert ax.get_legend().get_texts()[0].get_text().startswith(prefix)
assert ax.get_legend().get_frame_on() is False
def test_colorbar_kwargs(self):
# Test if kwargs are passed to colorbar
label_txt = "colorbar test"
ax = self.df.plot(
column="values",
categorical=False,
legend=True,
legend_kwds={"label": label_txt},
)
cax = _get_colorbar_ax(ax.get_figure())
assert cax.get_ylabel() == label_txt
ax = self.df.plot(
column="values",
categorical=False,
legend=True,
legend_kwds={"label": label_txt, "orientation": "horizontal"},
)
cax = _get_colorbar_ax(ax.get_figure())
assert cax.get_xlabel() == label_txt
def test_fmt_ignore(self):
# test if fmt is removed if scheme is not passed (it would raise Error)
# GH #1253
self.df.plot(
column="values",
categorical=True,
legend=True,
legend_kwds={"fmt": "{:.0f}"},
)
self.df.plot(column="values", legend=True, legend_kwds={"fmt": "{:.0f}"})
def test_multipolygons_color(self):
# MultiPolygons
ax = self.df2.plot()
assert len(ax.collections[0].get_paths()) == 4
_check_colors(4, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR] * 4)
ax = self.df2.plot("values")
cmap = plt.get_cmap(lut=2)
# colors are repeated for all components within a MultiPolygon
expected_colors = [cmap(0), cmap(0), cmap(1), cmap(1)]
_check_colors(4, ax.collections[0].get_facecolors(), expected_colors)
ax = self.df2.plot(color=["r", "b"])
# colors are repeated for all components within a MultiPolygon
_check_colors(4, ax.collections[0].get_facecolors(), ["r", "r", "b", "b"])
def test_multipolygons_linestyle(self):
# single
ax = self.df2.plot(linestyle=":", linewidth=1)
assert [(0.0, [1.0, 1.65])] == ax.collections[0].get_linestyle()
# tuple
ax = self.df2.plot(linestyle=(0, (3, 10, 1, 15)), linewidth=1)
assert [(0, [3, 10, 1, 15])] == ax.collections[0].get_linestyle()
# multiple
ls = ["dashed", "dotted"]
exp_ls = [_style_to_linestring_onoffseq(st, 1) for st in ls for i in range(2)]
for ax in [
self.df2.plot(linestyle=ls, linewidth=1),
self.df2.plot(linestyles=ls, linewidth=1),
]:
assert exp_ls == ax.collections[0].get_linestyle()
def test_multipolygons_linewidth(self):
# single
ax = self.df2.plot(linewidth=2)
np.testing.assert_array_equal([2], ax.collections[0].get_linewidths())
# multiple
for ax in [self.df2.plot(linewidth=[2, 4]), self.df2.plot(linewidths=[2, 4])]:
np.testing.assert_array_equal(
[2, 2, 4, 4], ax.collections[0].get_linewidths()
)
def test_multipolygons_alpha(self):
ax = self.df2.plot(alpha=0.7)
np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha())
try:
ax = self.df2.plot(alpha=[0.7, 0.2])
except TypeError:
# no list allowed for alpha up to matplotlib 3.3
pass
else:
np.testing.assert_array_equal(
[0.7, 0.7, 0.2, 0.2], ax.collections[0].get_alpha()
)
def test_subplots_norm(self):
# colors of subplots are the same as for plot (norm is applied)
cmap = matplotlib.cm.viridis_r
norm = matplotlib.colors.Normalize(vmin=0, vmax=10)
ax = self.df.plot(column="values", cmap=cmap, norm=norm)
actual_colors_orig = ax.collections[0].get_facecolors()
exp_colors = cmap(np.arange(2) / (10))
np.testing.assert_array_equal(exp_colors, actual_colors_orig)
fig, ax = plt.subplots()
self.df[1:].plot(column="values", ax=ax, norm=norm, cmap=cmap)
actual_colors_sub = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(actual_colors_orig[1], actual_colors_sub[0])
class TestPolygonZPlotting:
def setup_method(self):
t1 = Polygon([(0, 0, 0), (1, 0, 0), (1, 1, 1)])
t2 = Polygon([(1, 0, 0), (2, 0, 0), (2, 1, 1)])
self.polys = GeoSeries([t1, t2], index=list("AB"))
self.df = GeoDataFrame({"geometry": self.polys, "values": [0, 1]})
multipoly1 = MultiPolygon([t1, t2])
multipoly2 = rotate(multipoly1, 180)
self.df2 = GeoDataFrame(
{"geometry": [multipoly1, multipoly2], "values": [0, 1]}
)
def test_plot(self):
# basic test that points with z coords don't break plotting
self.df.plot()
class TestColorParamArray:
def setup_method(self):
geom = []
color = []
for a, b in [(0, 2), (4, 6)]:
b = box(a, a, b, b)
geom += [b, b.buffer(0.8).exterior, b.centroid]
color += ["red", "green", "blue"]
self.gdf = GeoDataFrame({"geometry": geom, "color_rgba": color})
self.mgdf = self.gdf.dissolve(self.gdf.geom_type)
def test_color_single(self):
ax = self.gdf.plot(color=self.gdf["color_rgba"])
_check_colors(
4,
np.concatenate([c.get_edgecolor() for c in ax.collections]),
["green"] * 2 + ["blue"] * 2,
)
_check_colors(
4,
np.concatenate([c.get_facecolor() for c in ax.collections]),
["red"] * 2 + ["blue"] * 2,
)
def test_color_multi(self):
ax = self.mgdf.plot(color=self.mgdf["color_rgba"])
_check_colors(
4,
np.concatenate([c.get_edgecolor() for c in ax.collections]),
["green"] * 2 + ["blue"] * 2,
)
_check_colors(
4,
np.concatenate([c.get_facecolor() for c in ax.collections]),
["red"] * 2 + ["blue"] * 2,
)
class TestGeometryCollectionPlotting:
def setup_method(self):
coll1 = GeometryCollection(
[
Polygon([(1, 0), (2, 0), (2, 1)]),
MultiLineString([((0.5, 0.5), (1, 1)), ((1, 0.5), (1.5, 1))]),
]
)
coll2 = GeometryCollection(
[Point(0.75, 0.25), Polygon([(2, 2), (3, 2), (2, 3)])]
)
self.series = GeoSeries([coll1, coll2])
self.df = GeoDataFrame({"geometry": self.series, "values": [1, 2]})
def test_colors(self):
# default uniform color
ax = self.series.plot()
_check_colors(
2, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR] * 2
) # poly
_check_colors(
2, ax.collections[1].get_edgecolors(), [MPL_DFT_COLOR] * 2
) # line
_check_colors(1, ax.collections[2].get_facecolors(), [MPL_DFT_COLOR]) # point
def test_values(self):
ax = self.df.plot("values")
cmap = plt.get_cmap()
exp_colors = cmap([0.0, 1.0])
_check_colors(2, ax.collections[0].get_facecolors(), exp_colors) # poly
_check_colors(
2, ax.collections[1].get_edgecolors(), [exp_colors[0]] * 2
) # line
_check_colors(1, ax.collections[2].get_facecolors(), [exp_colors[1]]) # point
class TestNonuniformGeometryPlotting:
def setup_method(self):
pytest.importorskip("matplotlib")
poly = Polygon([(1, 0), (2, 0), (2, 1)])
line = LineString([(0.5, 0.5), (1, 1), (1, 0.5), (1.5, 1)])
point = Point(0.75, 0.25)
self.series = GeoSeries([poly, line, point])
self.df = GeoDataFrame({"geometry": self.series, "values": [1, 2, 3]})
def test_colors(self):
# default uniform color
ax = self.series.plot()
_check_colors(1, ax.collections[0].get_facecolors(), [MPL_DFT_COLOR])
_check_colors(1, ax.collections[1].get_edgecolors(), [MPL_DFT_COLOR])
_check_colors(1, ax.collections[2].get_facecolors(), [MPL_DFT_COLOR])
# colormap: different colors
ax = self.series.plot(cmap="RdYlGn")
cmap = plt.get_cmap("RdYlGn")
exp_colors = cmap(np.arange(3) / (3 - 1))
_check_colors(1, ax.collections[0].get_facecolors(), [exp_colors[0]])
_check_colors(1, ax.collections[1].get_edgecolors(), [exp_colors[1]])
_check_colors(1, ax.collections[2].get_facecolors(), [exp_colors[2]])
def test_style_kwargs(self):
ax = self.series.plot(markersize=10)
assert ax.collections[2].get_sizes() == [10]
ax = self.df.plot(markersize=10)
assert ax.collections[2].get_sizes() == [10]
def test_style_kwargs_linestyle(self):
# single
for ax in [
self.series.plot(linestyle=":", linewidth=1),
self.df.plot(linestyle=":", linewidth=1),
]:
assert [(0.0, [1.0, 1.65])] == ax.collections[0].get_linestyle()
# tuple
ax = self.series.plot(linestyle=(0, (3, 10, 1, 15)), linewidth=1)
assert [(0, [3, 10, 1, 15])] == ax.collections[0].get_linestyle()
@pytest.mark.skip(
reason="array-like style_kwds not supported for mixed geometry types (#1379)"
)
def test_style_kwargs_linestyle_listlike(self):
# multiple
ls = ["solid", "dotted", "dashdot"]
exp_ls = [_style_to_linestring_onoffseq(style, 1) for style in ls]
for ax in [
self.series.plot(linestyle=ls, linewidth=1),
self.series.plot(linestyles=ls, linewidth=1),
self.df.plot(linestyles=ls, linewidth=1),
]:
assert exp_ls == ax.collections[0].get_linestyle()
def test_style_kwargs_linewidth(self):
# single
ax = self.df.plot(linewidth=2)
np.testing.assert_array_equal([2], ax.collections[0].get_linewidths())
@pytest.mark.skip(
reason="array-like style_kwds not supported for mixed geometry types (#1379)"
)
def test_style_kwargs_linewidth_listlike(self):
# multiple
for ax in [
self.series.plot(linewidths=[2, 4, 5.5]),
self.series.plot(linewidths=[2, 4, 5.5]),
self.df.plot(linewidths=[2, 4, 5.5]),
]:
np.testing.assert_array_equal(
[2, 4, 5.5], ax.collections[0].get_linewidths()
)
def test_style_kwargs_alpha(self):
ax = self.df.plot(alpha=0.7)
np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha())
# TODO splitting array-like arguments for the different plot types
# is not yet supported - https://github.com/geopandas/geopandas/issues/1379
# try:
# ax = self.df.plot(alpha=[0.7, 0.2, 0.9])
# except TypeError:
# # no list allowed for alpha up to matplotlib 3.3
# pass
# else:
# np.testing.assert_array_equal(
# [0.7, 0.2, 0.9], ax.collections[0].get_alpha()
# )
@pytest.fixture(scope="class")
def _setup_class_geographic_aspect(naturalearth_lowres, request):
"""Attach naturalearth_lowres class attribute for unittest style setup_method"""
df = read_file(naturalearth_lowres)
request.cls.north = df.loc[df.continent == "North America"]
request.cls.north_proj = request.cls.north.to_crs("ESRI:102008")
bounds = request.cls.north.total_bounds
y_coord = np.mean([bounds[1], bounds[3]])
request.cls.exp = 1 / np.cos(y_coord * np.pi / 180)
@pytest.mark.usefixtures("_setup_class_geographic_aspect")
@pytest.mark.skipif(not compat.HAS_PYPROJ, reason="pyproj not available")
class TestGeographicAspect:
def test_auto(self):
ax = self.north.geometry.plot()
assert ax.get_aspect() == self.exp
ax2 = self.north_proj.geometry.plot()
assert ax2.get_aspect() in ["equal", 1.0]
ax = self.north.plot()
assert ax.get_aspect() == self.exp
ax2 = self.north_proj.plot()
assert ax2.get_aspect() in ["equal", 1.0]
ax3 = self.north.plot("pop_est")
assert ax3.get_aspect() == self.exp
ax4 = self.north_proj.plot("pop_est")
assert ax4.get_aspect() in ["equal", 1.0]
def test_manual(self):
ax = self.north.geometry.plot(aspect="equal")
assert ax.get_aspect() in ["equal", 1.0]
self.north.geometry.plot(ax=ax, aspect=None)
assert ax.get_aspect() in ["equal", 1.0]
ax2 = self.north.geometry.plot(aspect=0.5)
assert ax2.get_aspect() == 0.5
self.north.geometry.plot(ax=ax2, aspect=None)
assert ax2.get_aspect() == 0.5
ax3 = self.north_proj.geometry.plot(aspect=0.5)
assert ax3.get_aspect() == 0.5
self.north_proj.geometry.plot(ax=ax3, aspect=None)
assert ax3.get_aspect() == 0.5
ax = self.north.plot(aspect="equal")
assert ax.get_aspect() in ["equal", 1.0]
self.north.plot(ax=ax, aspect=None)
assert ax.get_aspect() in ["equal", 1.0]
ax2 = self.north.plot(aspect=0.5)
assert ax2.get_aspect() == 0.5
self.north.plot(ax=ax2, aspect=None)
assert ax2.get_aspect() == 0.5
ax3 = self.north_proj.plot(aspect=0.5)
assert ax3.get_aspect() == 0.5
self.north_proj.plot(ax=ax3, aspect=None)
assert ax3.get_aspect() == 0.5
ax = self.north.plot("pop_est", aspect="equal")
assert ax.get_aspect() in ["equal", 1.0]
self.north.plot("pop_est", ax=ax, aspect=None)
assert ax.get_aspect() in ["equal", 1.0]
ax2 = self.north.plot("pop_est", aspect=0.5)
assert ax2.get_aspect() == 0.5
self.north.plot("pop_est", ax=ax2, aspect=None)
assert ax2.get_aspect() == 0.5
ax3 = self.north_proj.plot("pop_est", aspect=0.5)
assert ax3.get_aspect() == 0.5
self.north_proj.plot("pop_est", ax=ax3, aspect=None)
assert ax3.get_aspect() == 0.5
@pytest.mark.filterwarnings(
"ignore:Numba not installed. Using slow pure python version.:UserWarning"
)
class TestMapclassifyPlotting:
@classmethod
def setup_class(cls):
try:
import mapclassify
except ImportError:
pytest.importorskip("mapclassify")
cls.mc = mapclassify
cls.classifiers = list(mapclassify.classifiers.CLASSIFIERS)
cls.classifiers.remove("UserDefined")
@pytest.fixture
def df(self, naturalearth_lowres):
# version of naturalearth_lowres for mapclassify plotting tests
df = read_file(naturalearth_lowres)
df["NEGATIVES"] = np.linspace(-10, 10, len(df.index))
df["low_vals"] = np.linspace(0, 0.3, df.shape[0])
df["mid_vals"] = np.linspace(0.3, 0.7, df.shape[0])
df["high_vals"] = np.linspace(0.7, 1.0, df.shape[0])
df.loc[df.index[:20:2], "high_vals"] = np.nan
return df
@pytest.fixture
def nybb(self, nybb_filename):
# version of nybb for mapclassify plotting tests
df = read_file(nybb_filename)
df["vals"] = [0.001, 0.002, 0.003, 0.004, 0.005]
return df
def test_legend(self, df):
with warnings.catch_warnings(record=True) as _: # don't print warning
# warning coming from scipy.stats
ax = df.plot(
column="pop_est", scheme="QUANTILES", k=3, cmap="OrRd", legend=True
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = [
s.split("|")[0][1:-2]
for s in str(self.mc.Quantiles(df["pop_est"], k=3)).split("\n")[4:]
]
assert labels == expected
def test_bin_labels(self, df):
ax = df.plot(
column="pop_est",
scheme="QUANTILES",
k=3,
cmap="OrRd",
legend=True,
legend_kwds={"labels": ["foo", "bar", "baz"]},
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = ["foo", "bar", "baz"]
assert labels == expected
def test_invalid_labels_length(self, df):
with pytest.raises(ValueError):
df.plot(
column="pop_est",
scheme="QUANTILES",
k=3,
cmap="OrRd",
legend=True,
legend_kwds={"labels": ["foo", "bar"]},
)
def test_negative_legend(self, df):
ax = df.plot(
column="NEGATIVES", scheme="FISHER_JENKS", k=3, cmap="OrRd", legend=True
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = ["-10.00, -3.41", " -3.41, 3.30", " 3.30, 10.00"]
assert labels == expected
def test_fmt(self, df):
ax = df.plot(
column="NEGATIVES",
scheme="FISHER_JENKS",
k=3,
cmap="OrRd",
legend=True,
legend_kwds={"fmt": "{:.0f}"},
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = ["-10, -3", " -3, 3", " 3, 10"]
assert labels == expected
def test_interval(self, df):
ax = df.plot(
column="NEGATIVES",
scheme="FISHER_JENKS",
k=3,
cmap="OrRd",
legend=True,
legend_kwds={"interval": True},
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = ["[-10.00, -3.41]", "( -3.41, 3.30]", "( 3.30, 10.00]"]
assert labels == expected
@pytest.mark.parametrize("scheme", ["FISHER_JENKS", "FISHERJENKS"])
def test_scheme_name_compat(self, scheme, df):
ax = df.plot(column="NEGATIVES", scheme=scheme, k=3, legend=True)
assert len(ax.get_legend().get_texts()) == 3
def test_schemes(self, df):
# test if all available classifiers pass
for scheme in self.classifiers:
df.plot(column="pop_est", scheme=scheme, legend=True)
def test_classification_kwds(self, df):
ax = df.plot(
column="pop_est",
scheme="percentiles",
k=3,
classification_kwds={"pct": [50, 100]},
cmap="OrRd",
legend=True,
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = [
s.split("|")[0][1:-2]
for s in str(self.mc.Percentiles(df["pop_est"], pct=[50, 100])).split("\n")[
4:
]
]
assert labels == expected
def test_invalid_scheme(self, df):
with pytest.raises(ValueError):
scheme = "invalid_scheme_*#&)(*#"
df.plot(column="gdp_md_est", scheme=scheme, k=3, cmap="OrRd", legend=True)
def test_cax_legend_passing(self, df):
"""Pass a 'cax' argument to 'df.plot(.)', that is valid only if 'ax' is
passed as well (if not, a new figure is created ad hoc, and 'cax' is
ignored)
"""
ax = plt.axes()
from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.1)
with pytest.raises(ValueError):
ax = df.plot(column="pop_est", cmap="OrRd", legend=True, cax=cax)
def test_cax_legend_height(self, df):
"""Pass a cax argument to 'df.plot(.)', the legend location must be
aligned with those of main plot
"""
# base case
with warnings.catch_warnings(record=True) as _: # don't print warning
ax = df.plot(column="pop_est", cmap="OrRd", legend=True)
plot_height = _get_ax(ax.get_figure(), "").get_position().height
legend_height = _get_ax(ax.get_figure(), "<colorbar>").get_position().height
assert abs(plot_height - legend_height) >= 1e-6
# fix heights with cax argument
fig, ax2 = plt.subplots()
from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax2)
cax = divider.append_axes("right", size="5%", pad=0.1, label="fixed_colorbar")
with warnings.catch_warnings(record=True) as _:
ax2 = df.plot(column="pop_est", cmap="OrRd", legend=True, cax=cax, ax=ax2)
plot_height = _get_ax(fig, "").get_position().height
legend_height = _get_ax(fig, "fixed_colorbar").get_position().height
assert abs(plot_height - legend_height) < 1e-6
def test_empty_bins(self, df):
bins = np.arange(1, 11) / 10
ax = df.plot(
"low_vals",
scheme="UserDefined",
classification_kwds={"bins": bins},
legend=True,
)
expected = np.array(
[
[0.281412, 0.155834, 0.469201, 1.0],
[0.267004, 0.004874, 0.329415, 1.0],
[0.244972, 0.287675, 0.53726, 1.0],
]
)
assert all(
(z == expected).all(axis=1).any()
for z in ax.collections[0].get_facecolors()
)
labels = [
"0.00, 0.10",
"0.10, 0.20",
"0.20, 0.30",
"0.30, 0.40",
"0.40, 0.50",
"0.50, 0.60",
"0.60, 0.70",
"0.70, 0.80",
"0.80, 0.90",
"0.90, 1.00",
]
legend = [t.get_text() for t in ax.get_legend().get_texts()]
assert labels == legend
legend_colors_exp = [
(0.267004, 0.004874, 0.329415, 1.0),
(0.281412, 0.155834, 0.469201, 1.0),
(0.244972, 0.287675, 0.53726, 1.0),
(0.190631, 0.407061, 0.556089, 1.0),
(0.147607, 0.511733, 0.557049, 1.0),
(0.119699, 0.61849, 0.536347, 1.0),
(0.20803, 0.718701, 0.472873, 1.0),
(0.430983, 0.808473, 0.346476, 1.0),
(0.709898, 0.868751, 0.169257, 1.0),
(0.993248, 0.906157, 0.143936, 1.0),
]
assert [
line.get_markerfacecolor() for line in ax.get_legend().get_lines()
] == legend_colors_exp
ax2 = df.plot(
"mid_vals",
scheme="UserDefined",
classification_kwds={"bins": bins},
legend=True,
)
expected = np.array(
[
[0.244972, 0.287675, 0.53726, 1.0],
[0.190631, 0.407061, 0.556089, 1.0],
[0.147607, 0.511733, 0.557049, 1.0],
[0.119699, 0.61849, 0.536347, 1.0],
[0.20803, 0.718701, 0.472873, 1.0],
]
)
assert all(
(z == expected).all(axis=1).any()
for z in ax2.collections[0].get_facecolors()
)
labels = [
"-inf, 0.10",
"0.10, 0.20",
"0.20, 0.30",
"0.30, 0.40",
"0.40, 0.50",
"0.50, 0.60",
"0.60, 0.70",
"0.70, 0.80",
"0.80, 0.90",
"0.90, 1.00",
]
legend = [t.get_text() for t in ax2.get_legend().get_texts()]
assert labels == legend
assert [
line.get_markerfacecolor() for line in ax2.get_legend().get_lines()
] == legend_colors_exp
ax3 = df.plot(
"high_vals",
scheme="UserDefined",
classification_kwds={"bins": bins},
legend=True,
)
expected = np.array(
[
[0.709898, 0.868751, 0.169257, 1.0],
[0.993248, 0.906157, 0.143936, 1.0],
[0.430983, 0.808473, 0.346476, 1.0],
]
)
assert all(
(z == expected).all(axis=1).any()
for z in ax3.collections[0].get_facecolors()
)
legend = [t.get_text() for t in ax3.get_legend().get_texts()]
assert labels == legend
assert [
line.get_markerfacecolor() for line in ax3.get_legend().get_lines()
] == legend_colors_exp
def test_equally_formatted_bins(self, nybb):
ax = nybb.plot(
"vals",
scheme="quantiles",
legend=True,
)
labels = [t.get_text() for t in ax.get_legend().get_texts()]
expected = [
"0.00, 0.00",
"0.00, 0.00",
"0.00, 0.00",
"0.00, 0.00",
"0.00, 0.01",
]
assert labels == expected
ax2 = nybb.plot(
"vals", scheme="quantiles", legend=True, legend_kwds={"fmt": "{:.3f}"}
)
labels = [t.get_text() for t in ax2.get_legend().get_texts()]
expected = [
"0.001, 0.002",
"0.002, 0.003",
"0.003, 0.003",
"0.003, 0.004",
"0.004, 0.005",
]
assert labels == expected
class TestPlotCollections:
def setup_method(self):
self.N = 3
self.values = np.arange(self.N)
self.points = GeoSeries(Point(i, i) for i in range(self.N))
self.lines = GeoSeries(
[LineString([(0, i), (4, i + 0.5), (9, i)]) for i in range(self.N)]
)
self.polygons = GeoSeries(
[Polygon([(0, i), (4, i + 0.5), (9, i)]) for i in range(self.N)]
)
def test_points(self):
from matplotlib.collections import PathCollection
from geopandas.plotting import _plot_point_collection
fig, ax = plt.subplots()
coll = _plot_point_collection(ax, self.points)
assert isinstance(coll, PathCollection)
ax.cla()
# default: single default matplotlib color
coll = _plot_point_collection(ax, self.points)
_check_colors(self.N, coll.get_facecolors(), [MPL_DFT_COLOR] * self.N)
# edgecolor depends on matplotlib version
# _check_colors(self.N, coll.get_edgecolors(), [MPL_DFT_COLOR]*self.N)
ax.cla()
# specify single other color
coll = _plot_point_collection(ax, self.points, color="g")
_check_colors(self.N, coll.get_facecolors(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolors(), ["g"] * self.N)
ax.cla()
# specify edgecolor/facecolor
coll = _plot_point_collection(ax, self.points, facecolor="g", edgecolor="r")
_check_colors(self.N, coll.get_facecolors(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolors(), ["r"] * self.N)
ax.cla()
# list of colors
coll = _plot_point_collection(ax, self.points, color=["r", "g", "b"])
_check_colors(self.N, coll.get_facecolors(), ["r", "g", "b"])
_check_colors(self.N, coll.get_edgecolors(), ["r", "g", "b"])
ax.cla()
coll = _plot_point_collection(
ax,
self.points,
color=[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)],
)
_check_colors(
self.N,
coll.get_facecolors(),
[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)],
)
_check_colors(
self.N,
coll.get_edgecolors(),
[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)],
)
ax.cla()
# not a color
with pytest.raises((TypeError, ValueError)):
_plot_point_collection(ax, self.points, color="not color")
def test_points_values(self):
from geopandas.plotting import _plot_point_collection
# default colormap
fig, ax = plt.subplots()
coll = _plot_point_collection(ax, self.points, self.values)
fig.canvas.draw_idle()
cmap = plt.get_cmap()
expected_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, coll.get_facecolors(), expected_colors)
# edgecolor depends on matplotlib version
# _check_colors(self.N, coll.get_edgecolors(), expected_colors)
def test_linestrings(self):
from matplotlib.collections import LineCollection
from geopandas.plotting import _plot_linestring_collection
fig, ax = plt.subplots()
coll = _plot_linestring_collection(ax, self.lines)
assert isinstance(coll, LineCollection)
ax.cla()
# default: single default matplotlib color
coll = _plot_linestring_collection(ax, self.lines)
_check_colors(self.N, coll.get_color(), [MPL_DFT_COLOR] * self.N)
ax.cla()
# specify single other color
coll = _plot_linestring_collection(ax, self.lines, color="g")
_check_colors(self.N, coll.get_colors(), ["g"] * self.N)
ax.cla()
# specify edgecolor / facecolor
coll = _plot_linestring_collection(ax, self.lines, facecolor="g", edgecolor="r")
_check_colors(self.N, coll.get_facecolors(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolors(), ["r"] * self.N)
ax.cla()
# list of colors
coll = _plot_linestring_collection(ax, self.lines, color=["r", "g", "b"])
_check_colors(self.N, coll.get_colors(), ["r", "g", "b"])
ax.cla()
coll = _plot_linestring_collection(
ax,
self.lines,
color=[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)],
)
_check_colors(
self.N,
coll.get_colors(),
[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)],
)
ax.cla()
# pass through of kwargs
coll = _plot_linestring_collection(ax, self.lines, linestyle="--", linewidth=1)
exp_ls = _style_to_linestring_onoffseq("dashed", 1)
res_ls = coll.get_linestyle()[0]
assert res_ls[0] == exp_ls[0]
assert res_ls[1] == exp_ls[1]
ax.cla()
# not a color
with pytest.raises((TypeError, ValueError)):
_plot_linestring_collection(ax, self.lines, color="not color")
def test_linestrings_values(self):
from geopandas.plotting import _plot_linestring_collection
fig, ax = plt.subplots()
# default colormap
coll = _plot_linestring_collection(ax, self.lines, self.values)
fig.canvas.draw_idle()
cmap = plt.get_cmap()
expected_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, coll.get_color(), expected_colors)
ax.cla()
# specify colormap
coll = _plot_linestring_collection(ax, self.lines, self.values, cmap="RdBu")
fig.canvas.draw_idle()
cmap = plt.get_cmap("RdBu")
expected_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, coll.get_color(), expected_colors)
ax.cla()
# specify vmin/vmax
coll = _plot_linestring_collection(ax, self.lines, self.values, vmin=3, vmax=5)
fig.canvas.draw_idle()
cmap = plt.get_cmap()
expected_colors = [cmap(0)]
_check_colors(self.N, coll.get_color(), expected_colors * 3)
ax.cla()
def test_polygons(self):
from matplotlib.collections import PatchCollection
from geopandas.plotting import _plot_polygon_collection
fig, ax = plt.subplots()
coll = _plot_polygon_collection(ax, self.polygons)
assert isinstance(coll, PatchCollection)
ax.cla()
# default: single default matplotlib color
coll = _plot_polygon_collection(ax, self.polygons)
_check_colors(self.N, coll.get_facecolor(), [MPL_DFT_COLOR] * self.N)
assert len(coll.get_edgecolor()) == 0
ax.cla()
# default: color sets both facecolor and edgecolor
coll = _plot_polygon_collection(ax, self.polygons, color="g")
_check_colors(self.N, coll.get_facecolor(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ["g"] * self.N)
ax.cla()
# default: color can be passed as a list
coll = _plot_polygon_collection(ax, self.polygons, color=["g", "b", "r"])
_check_colors(self.N, coll.get_facecolor(), ["g", "b", "r"])
_check_colors(self.N, coll.get_edgecolor(), ["g", "b", "r"])
ax.cla()
coll = _plot_polygon_collection(
ax,
self.polygons,
color=[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)],
)
_check_colors(
self.N,
coll.get_facecolor(),
[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)],
)
_check_colors(
self.N,
coll.get_edgecolor(),
[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)],
)
ax.cla()
# only setting facecolor keeps default for edgecolor
coll = _plot_polygon_collection(ax, self.polygons, facecolor="g")
_check_colors(self.N, coll.get_facecolor(), ["g"] * self.N)
assert len(coll.get_edgecolor()) == 0
ax.cla()
# custom facecolor and edgecolor
coll = _plot_polygon_collection(ax, self.polygons, facecolor="g", edgecolor="r")
_check_colors(self.N, coll.get_facecolor(), ["g"] * self.N)
_check_colors(self.N, coll.get_edgecolor(), ["r"] * self.N)
ax.cla()
# not a color
with pytest.raises((TypeError, ValueError)):
_plot_polygon_collection(ax, self.polygons, color="not color")
def test_polygons_values(self):
from geopandas.plotting import _plot_polygon_collection
fig, ax = plt.subplots()
# default colormap, edge is still black by default
coll = _plot_polygon_collection(ax, self.polygons, self.values)
fig.canvas.draw_idle()
cmap = plt.get_cmap()
exp_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, coll.get_facecolor(), exp_colors)
# edgecolor depends on matplotlib version
# _check_colors(self.N, coll.get_edgecolor(), ['k'] * self.N)
ax.cla()
# specify colormap
coll = _plot_polygon_collection(ax, self.polygons, self.values, cmap="RdBu")
fig.canvas.draw_idle()
cmap = plt.get_cmap("RdBu")
exp_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, coll.get_facecolor(), exp_colors)
ax.cla()
# specify vmin/vmax
coll = _plot_polygon_collection(ax, self.polygons, self.values, vmin=3, vmax=5)
fig.canvas.draw_idle()
cmap = plt.get_cmap()
exp_colors = [cmap(0)]
_check_colors(self.N, coll.get_facecolor(), exp_colors * 3)
ax.cla()
# override edgecolor
coll = _plot_polygon_collection(ax, self.polygons, self.values, edgecolor="g")
fig.canvas.draw_idle()
cmap = plt.get_cmap()
exp_colors = cmap(np.arange(self.N) / (self.N - 1))
_check_colors(self.N, coll.get_facecolor(), exp_colors)
_check_colors(self.N, coll.get_edgecolor(), ["g"] * self.N)
ax.cla()
class TestGeoplotAccessor:
def setup_method(self):
geometries = [Polygon([(0, 0), (1, 0), (1, 1)]), Point(1, 3)]
x = [1, 2]
y = [10, 20]
self.gdf = GeoDataFrame(
{"geometry": geometries, "x": x, "y": y}, crs="EPSG:4326"
)
self.df = pd.DataFrame({"x": x, "y": y})
def compare_figures(self, kind, fig_test, fig_ref, kwargs):
"""Compare Figures."""
ax_pandas_1 = fig_test.subplots()
self.df.plot(kind=kind, ax=ax_pandas_1, **kwargs)
ax_geopandas_1 = fig_ref.subplots()
self.gdf.plot(kind=kind, ax=ax_geopandas_1, **kwargs)
ax_pandas_2 = fig_test.subplots()
getattr(self.df.plot, kind)(ax=ax_pandas_2, **kwargs)
ax_geopandas_2 = fig_ref.subplots()
getattr(self.gdf.plot, kind)(ax=ax_geopandas_2, **kwargs)
_pandas_kinds = GeoplotAccessor._pandas_kinds
if MPL_DECORATORS:
@pytest.mark.parametrize("kind", _pandas_kinds)
@check_figures_equal(extensions=["png", "pdf"])
def test_pandas_kind(self, kind, fig_test, fig_ref):
"""Test Pandas kind."""
import importlib
_scipy_dependent_kinds = ["kde", "density"] # Needs scipy
_y_kinds = ["pie"] # Needs y
_xy_kinds = ["scatter", "hexbin"] # Needs x & y
kwargs = {}
if kind in _scipy_dependent_kinds:
if not importlib.util.find_spec("scipy"):
with pytest.raises(
ModuleNotFoundError, match="No module named 'scipy'"
):
self.gdf.plot(kind=kind)
return
elif kind in _y_kinds:
kwargs = {"y": "y"}
elif kind in _xy_kinds:
kwargs = {"x": "x", "y": "y"}
if kind == "hexbin": # increase gridsize to reduce duration
kwargs["gridsize"] = 10
self.compare_figures(kind, fig_test, fig_ref, kwargs)
plt.close("all")
@check_figures_equal(extensions=["png", "pdf"])
def test_geo_kind(self, fig_test, fig_ref):
"""Test Geo kind."""
ax1 = fig_test.subplots()
self.gdf.plot(ax=ax1)
ax2 = fig_ref.subplots()
getattr(self.gdf.plot, "geo")(ax=ax2)
plt.close("all")
def test_invalid_kind(self):
"""Test invalid kinds."""
with pytest.raises(ValueError, match="error is not a valid plot kind"):
self.gdf.plot(kind="error")
with pytest.raises(
AttributeError,
match="'GeoplotAccessor' object has no attribute 'error'",
):
self.gdf.plot.error()
def test_column_values():
"""
Check that the dataframe plot method returns same values with an
input string (column in df), pd.Series, or np.array
"""
# Build test data
t1 = Polygon([(0, 0), (1, 0), (1, 1)])
t2 = Polygon([(1, 0), (2, 0), (2, 1)])
polys = GeoSeries([t1, t2], index=list("AB"))
df = GeoDataFrame({"geometry": polys, "values": [0, 1]})
# Test with continuous values
ax = df.plot(column="values")
colors = ax.collections[0].get_facecolors()
ax = df.plot(column=df["values"])
colors_series = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(colors, colors_series)
ax = df.plot(column=df["values"].values)
colors_array = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(colors, colors_array)
# Test with categorical values
ax = df.plot(column="values", categorical=True)
colors = ax.collections[0].get_facecolors()
ax = df.plot(column=df["values"], categorical=True)
colors_series = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(colors, colors_series)
ax = df.plot(column=df["values"].values, categorical=True)
colors_array = ax.collections[0].get_facecolors()
np.testing.assert_array_equal(colors, colors_array)
# Check raised error: is df rows number equal to column length?
with pytest.raises(ValueError, match="different number of rows"):
ax = df.plot(column=np.array([1, 2, 3]))
def test_polygon_patch():
# test adapted from descartes by Sean Gillies
# (BSD license, https://pypi.org/project/descartes).
from matplotlib.patches import PathPatch
from geopandas.plotting import _PolygonPatch
polygon = (
Point(0, 0).buffer(10.0).difference(MultiPoint([(-5, 0), (5, 0)]).buffer(3.0))
)
patch = _PolygonPatch(polygon)
assert isinstance(patch, PathPatch)
path = patch.get_path()
if compat.GEOS_GE_390:
assert len(path.vertices) == len(path.codes) == 195
else:
assert len(path.vertices) == len(path.codes) == 198
def _check_colors(N, actual_colors, expected_colors, alpha=None):
"""
Asserts that the members of `collection` match the `expected_colors`
(in order)
Parameters
----------
N : int
The number of geometries believed to be in collection.
matplotlib.collection is implemented such that the number of geoms in
`collection` doesn't have to match the number of colors assignments in
the collection: the colors will cycle to meet the needs of the geoms.
`N` helps us resolve this.
collection : matplotlib.collections.Collection
The colors of this collection's patches are read from
`collection.get_facecolors()`
expected_colors : sequence of RGBA tuples
alpha : float (optional)
If set, this alpha transparency will be applied to the
`expected_colors`. (Any transparency on the `collection` is assumed
to be set in its own facecolor RGBA tuples.)
"""
from matplotlib import colors
conv = colors.colorConverter
# Convert 2D numpy array to a list of RGBA tuples.
actual_colors = map(tuple, actual_colors)
all_actual_colors = list(itertools.islice(itertools.cycle(actual_colors), N))
assert len(all_actual_colors) == len(
expected_colors
), "Different lengths of actual and expected colors!"
for actual, expected in zip(all_actual_colors, expected_colors):
assert actual == conv.to_rgba(expected, alpha=alpha), "{} != {}".format(
actual, conv.to_rgba(expected, alpha=alpha)
)
def _style_to_linestring_onoffseq(linestyle, linewidth):
"""Converts a linestyle string representation, namely one of:
['dashed', 'dotted', 'dashdot', 'solid'],
documented in `Collections.set_linestyle`,
to the form `onoffseq`.
"""
offset, dashes = matplotlib.lines._get_dash_pattern(linestyle)
return matplotlib.lines._scale_dashes(offset, dashes, linewidth)
def _style_to_vertices(markerstyle):
"""Converts a markerstyle string to a path."""
# TODO: Vertices values are twice the actual path; unclear, why.
path = matplotlib.markers.MarkerStyle(markerstyle).get_path()
return path.vertices / 2
def _get_ax(fig, label):
"""
Helper function to not rely on the order of `fig.axes`.
Previously, we did `fig.axes[1]`, but in matplotlib 3.4 the order switched
and the colorbar ax was first and subplot ax second.
"""
for ax in fig.axes:
if ax.get_label() == label:
return ax
raise ValueError("no ax found with label {0}".format(label))
def _get_colorbar_ax(fig):
return _get_ax(fig, "<colorbar>")