1963 lines
73 KiB
Python
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>")
|