import itertools from packaging.version import Version import warnings import numpy as np import pandas as pd from shapely import wkt from shapely.affinity import rotate from shapely.geometry import ( MultiPolygon, Polygon, LineString, LinearRing, Point, MultiPoint, MultiLineString, GeometryCollection, box, ) from geopandas import GeoDataFrame, GeoSeries, read_file from geopandas.datasets import get_path import geopandas._compat as compat 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): if compat.USE_PYGEOS: s = GeoSeries([wkt.loads("POLYGON EMPTY")]) s = GeoSeries( [Polygon([(0, 0), (1, 0), (1, 1)]), wkt.loads("POLYGON EMPTY")] ) ax = s.plot() assert len(ax.collections) == 1 if not compat.USE_PYGEOS: 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) 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_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_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() # ) class TestGeographicAspect: def setup_class(self): pth = get_path("naturalearth_lowres") df = read_file(pth) self.north = df.loc[df.continent == "North America"] self.north_proj = self.north.to_crs("ESRI:102008") bounds = self.north.total_bounds y_coord = np.mean([bounds[1], bounds[3]]) self.exp = 1 / np.cos(y_coord * np.pi / 180) 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 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") pth = get_path("naturalearth_lowres") cls.df = read_file(pth) cls.df["NEGATIVES"] = np.linspace(-10, 10, len(cls.df.index)) cls.df["low_vals"] = np.linspace(0, 0.3, cls.df.shape[0]) cls.df["mid_vals"] = np.linspace(0.3, 0.7, cls.df.shape[0]) cls.df["high_vals"] = np.linspace(0.7, 1.0, cls.df.shape[0]) cls.df.loc[cls.df.index[:20:2], "high_vals"] = np.nan cls.nybb = read_file(get_path("nybb")) cls.nybb["vals"] = [0.001, 0.002, 0.003, 0.004, 0.005] def test_legend(self): with warnings.catch_warnings(record=True) as _: # don't print warning # warning coming from scipy.stats ax = self.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(self.df["pop_est"], k=3)).split("\n")[4:] ] assert labels == expected def test_bin_labels(self): ax = self.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): with pytest.raises(ValueError): self.df.plot( column="pop_est", scheme="QUANTILES", k=3, cmap="OrRd", legend=True, legend_kwds={"labels": ["foo", "bar"]}, ) def test_negative_legend(self): ax = self.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): ax = self.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): ax = self.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): ax = self.df.plot(column="NEGATIVES", scheme=scheme, k=3, legend=True) assert len(ax.get_legend().get_texts()) == 3 def test_schemes(self): # test if all available classifiers pass for scheme in self.classifiers: self.df.plot(column="pop_est", scheme=scheme, legend=True) def test_classification_kwds(self): ax = self.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(self.df["pop_est"], pct=[50, 100])).split( "\n" )[4:] ] assert labels == expected def test_invalid_scheme(self): with pytest.raises(ValueError): scheme = "invalid_scheme_*#&)(*#" self.df.plot( column="gdp_md_est", scheme=scheme, k=3, cmap="OrRd", legend=True ) def test_cax_legend_passing(self): """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 = self.df.plot(column="pop_est", cmap="OrRd", legend=True, cax=cax) def test_cax_legend_height(self): """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 = self.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(), "").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 = self.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): bins = np.arange(1, 11) / 10 ax = self.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 = self.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 = self.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): ax = self.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 = self.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 geopandas.plotting import _plot_point_collection, plot_point_collection from matplotlib.collections import PathCollection 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") # check FutureWarning with pytest.warns(FutureWarning): plot_point_collection(ax, self.points) 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 geopandas.plotting import ( _plot_linestring_collection, plot_linestring_collection, ) from matplotlib.collections import LineCollection 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") # check FutureWarning with pytest.warns(FutureWarning): plot_linestring_collection(ax, self.lines) 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 geopandas.plotting import _plot_polygon_collection, plot_polygon_collection from matplotlib.collections import PatchCollection 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") # check FutureWarning with pytest.warns(FutureWarning): plot_polygon_collection(ax, self.polygons) 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 geopandas.plotting import _PolygonPatch from matplotlib.patches import PathPatch 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, "")