mirror of
https://github.com/wassname/geopandas.git
synced 2026-09-10 12:00:21 +08:00
Merge pull request #267 from IamJeffG/259-plotting-collections
Implement plotting using matplotlib Collections
This commit is contained in:
+321
-133
@@ -3,55 +3,206 @@ from __future__ import print_function
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from six import next
|
||||
from six.moves import xrange
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
|
||||
def plot_polygon(ax, poly, facecolor='red', edgecolor='black', alpha=0.5, linewidth=1.0, **kwargs):
|
||||
""" Plot a single Polygon geometry """
|
||||
def _flatten_multi_geoms(geoms, colors):
|
||||
"""
|
||||
Returns Series like geoms and colors, except that any Multi geometries
|
||||
are split into their components and colors are repeated for all component
|
||||
in the same Multi geometry. Maintains 1:1 matching of geometry to color.
|
||||
|
||||
"Colors" are treated opaquely and so can actually contain any values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
components : list of geometry
|
||||
|
||||
component_colors : list of whatever type `colors` contains
|
||||
"""
|
||||
components, component_colors = [], []
|
||||
|
||||
# precondition, so zip can't short-circuit
|
||||
assert len(geoms) == len(colors)
|
||||
for geom, color in zip(geoms, colors):
|
||||
if geom.type.startswith('Multi'):
|
||||
for poly in geom:
|
||||
components.append(poly)
|
||||
# repeat same color for all components
|
||||
component_colors.append(color)
|
||||
else:
|
||||
components.append(geom)
|
||||
component_colors.append(color)
|
||||
return components, component_colors
|
||||
|
||||
|
||||
def plot_polygon_collection(ax, geoms, colors_or_values, plot_values,
|
||||
vmin=None, vmax=None, cmap=None,
|
||||
edgecolor='black', alpha=0.5, linewidth=1.0, **kwargs):
|
||||
"""
|
||||
Plots a collection of Polygon and MultiPolygon geometries to `ax`
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
ax : matplotlib.axes.Axes
|
||||
where shapes will be plotted
|
||||
|
||||
geoms : a sequence of `N` Polygons and/or MultiPolygons (can be mixed)
|
||||
|
||||
colors_or_values : a sequence of `N` values or RGBA tuples
|
||||
It should have 1:1 correspondence with the geometries (not their components).
|
||||
|
||||
plot_values : bool
|
||||
If True, `colors_or_values` is interpreted as a list of values, and will
|
||||
be mapped to colors using vmin/vmax/cmap (which become required).
|
||||
Otherwise `colors_or_values` is interpreted as a list of colors.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
collection : matplotlib.collections.Collection that was plotted
|
||||
"""
|
||||
|
||||
from descartes.patch import PolygonPatch
|
||||
a = np.asarray(poly.exterior)
|
||||
if poly.has_z:
|
||||
poly = Polygon(zip(*poly.exterior.xy))
|
||||
from matplotlib.collections import PatchCollection
|
||||
|
||||
# without Descartes, we could make a Patch of exterior
|
||||
ax.add_patch(PolygonPatch(poly, facecolor=facecolor, linewidth=0, alpha=alpha)) # linewidth=0 because boundaries are drawn separately
|
||||
ax.plot(a[:, 0], a[:, 1], color=edgecolor, linewidth=linewidth, **kwargs)
|
||||
for p in poly.interiors:
|
||||
x, y = zip(*p.coords)
|
||||
ax.plot(x, y, color=edgecolor, linewidth=linewidth)
|
||||
components, component_colors_or_values = _flatten_multi_geoms(
|
||||
geoms, colors_or_values)
|
||||
|
||||
# PatchCollection does not accept some kwargs.
|
||||
if 'markersize' in kwargs:
|
||||
del kwargs['markersize']
|
||||
collection = PatchCollection([PolygonPatch(poly) for poly in components],
|
||||
linewidth=linewidth, edgecolor=edgecolor,
|
||||
alpha=alpha, **kwargs)
|
||||
|
||||
if plot_values:
|
||||
collection.set_array(np.array(component_colors_or_values))
|
||||
collection.set_cmap(cmap)
|
||||
collection.set_clim(vmin, vmax)
|
||||
else:
|
||||
# set_color magically sets the correct combination of facecolor and
|
||||
# edgecolor, based on collection type.
|
||||
collection.set_color(component_colors_or_values)
|
||||
|
||||
# If the user set facecolor and/or edgecolor explicitly, the previous
|
||||
# call to set_color might have overridden it (remember, the 'color' may
|
||||
# have come from plot_series, not from the user). The user should be
|
||||
# able to override matplotlib's default behavior, by setting them again
|
||||
# after set_color.
|
||||
if 'facecolor' in kwargs:
|
||||
collection.set_facecolor(kwargs['facecolor'])
|
||||
if edgecolor:
|
||||
collection.set_edgecolor(edgecolor)
|
||||
|
||||
ax.add_collection(collection, autolim=True)
|
||||
ax.autoscale_view()
|
||||
return collection
|
||||
|
||||
|
||||
def plot_multipolygon(ax, geom, facecolor='red', edgecolor='black', alpha=0.5, linewidth=1.0, **kwargs):
|
||||
""" Can safely call with either Polygon or Multipolygon geometry
|
||||
def plot_linestring_collection(ax, geoms, colors_or_values, plot_values,
|
||||
vmin=None, vmax=None, cmap=None,
|
||||
linewidth=1.0, **kwargs):
|
||||
"""
|
||||
if geom.type == 'Polygon':
|
||||
plot_polygon(ax, geom, facecolor=facecolor, edgecolor=edgecolor, alpha=alpha, linewidth=linewidth, **kwargs)
|
||||
elif geom.type == 'MultiPolygon':
|
||||
for poly in geom.geoms:
|
||||
plot_polygon(ax, poly, facecolor=facecolor, edgecolor=edgecolor, alpha=alpha, linewidth=linewidth, **kwargs)
|
||||
Plots a collection of LineString and MultiLineString geometries to `ax`
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
def plot_linestring(ax, geom, color='black', linewidth=1.0, **kwargs):
|
||||
""" Plot a single LineString geometry """
|
||||
a = np.array(geom)
|
||||
ax.plot(a[:, 0], a[:, 1], color=color, linewidth=linewidth, **kwargs)
|
||||
ax : matplotlib.axes.Axes
|
||||
where shapes will be plotted
|
||||
|
||||
geoms : a sequence of `N` LineStrings and/or MultiLineStrings (can be mixed)
|
||||
|
||||
def plot_multilinestring(ax, geom, color='red', linewidth=1.0, **kwargs):
|
||||
""" Can safely call with either LineString or MultiLineString geometry
|
||||
colors_or_values : a sequence of `N` values or RGBA tuples
|
||||
It should have 1:1 correspondence with the geometries (not their components).
|
||||
|
||||
plot_values : bool
|
||||
If True, `colors_or_values` is interpreted as a list of values, and will
|
||||
be mapped to colors using vmin/vmax/cmap (which become required).
|
||||
Otherwise `colors_or_values` is interpreted as a list of colors.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
collection : matplotlib.collections.Collection that was plotted
|
||||
"""
|
||||
if geom.type == 'LineString':
|
||||
plot_linestring(ax, geom, color=color, linewidth=linewidth, **kwargs)
|
||||
elif geom.type == 'MultiLineString':
|
||||
for line in geom.geoms:
|
||||
plot_linestring(ax, line, color=color, linewidth=linewidth, **kwargs)
|
||||
|
||||
from matplotlib.collections import LineCollection
|
||||
|
||||
components, component_colors_or_values = _flatten_multi_geoms(
|
||||
geoms, colors_or_values)
|
||||
|
||||
# LineCollection does not accept some kwargs.
|
||||
if 'markersize' in kwargs:
|
||||
del kwargs['markersize']
|
||||
segments = [np.array(linestring)[:, :2] for linestring in components]
|
||||
collection = LineCollection(segments,
|
||||
linewidth=linewidth, **kwargs)
|
||||
|
||||
if plot_values:
|
||||
collection.set_array(np.array(component_colors_or_values))
|
||||
collection.set_cmap(cmap)
|
||||
collection.set_clim(vmin, vmax)
|
||||
else:
|
||||
# set_color magically sets the correct combination of facecolor and
|
||||
# edgecolor, based on collection type.
|
||||
collection.set_color(component_colors_or_values)
|
||||
|
||||
# If the user set facecolor and/or edgecolor explicitly, the previous
|
||||
# call to set_color might have overridden it (remember, the 'color' may
|
||||
# have come from plot_series, not from the user). The user should be
|
||||
# able to override matplotlib's default behavior, by setting them again
|
||||
# after set_color.
|
||||
if 'facecolor' in kwargs:
|
||||
collection.set_facecolor(kwargs['facecolor'])
|
||||
|
||||
if 'edgecolor' in kwargs:
|
||||
collection.set_edgecolor(kwargs['edgecolor'])
|
||||
|
||||
ax.add_collection(collection, autolim=True)
|
||||
ax.autoscale_view()
|
||||
return collection
|
||||
|
||||
|
||||
def plot_point(ax, pt, marker='o', markersize=2, color='black', **kwargs):
|
||||
""" Plot a single Point geometry """
|
||||
ax.plot(pt.x, pt.y, marker=marker, markersize=markersize, color=color, **kwargs)
|
||||
def plot_point_collection(ax, geoms, colors_or_values,
|
||||
vmin=None, vmax=None, cmap=None,
|
||||
marker='o', markersize=2, **kwargs):
|
||||
"""
|
||||
Plots a collection of Point geometries to `ax`
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
ax : matplotlib.axes.Axes
|
||||
where shapes will be plotted
|
||||
|
||||
geoms : sequence of `N` Points
|
||||
|
||||
colors_or_values : sequence of color or sequence of numbers
|
||||
can be a sequence of color specifications of length `N` or a sequence
|
||||
of `N` numbers to be mapped to colors using vmin, vmax, and cmap.
|
||||
|
||||
Returns
|
||||
-------
|
||||
collection : matplotlib.collections.Collection that was plotted
|
||||
"""
|
||||
x = [p.x for p in geoms]
|
||||
y = [p.y for p in geoms]
|
||||
|
||||
# matplotlib ax.scatter requires RGBA color specifications to be a single 2D
|
||||
# array, NOT merely a list of 1D arrays. This reshapes that if necessary,
|
||||
# having no effect on 1D arrays of values.
|
||||
colors_or_values = np.array([element
|
||||
for _, element in enumerate(colors_or_values)])
|
||||
collection = ax.scatter(x, y, c=colors_or_values,
|
||||
vmin=vmin, vmax=vmax, cmap=cmap,
|
||||
marker=marker, s=markersize, **kwargs)
|
||||
return collection
|
||||
|
||||
|
||||
def gencolor(N, colormap='Set1'):
|
||||
@@ -130,21 +281,52 @@ def plot_series(s, cmap='Set1', color=None, ax=None, linewidth=1.0,
|
||||
if ax is None:
|
||||
fig, ax = plt.subplots(figsize=figsize)
|
||||
ax.set_aspect('equal')
|
||||
color_generator = gencolor(len(s), colormap=cmap)
|
||||
for geom in s:
|
||||
if color is None:
|
||||
col = next(color_generator)
|
||||
|
||||
num_geoms = len(s.index)
|
||||
if color:
|
||||
colors = np.array([color] * num_geoms)
|
||||
else:
|
||||
color_generator = gencolor(len(s), colormap=cmap)
|
||||
colors = np.array([next(color_generator) for _ in xrange(num_geoms)])
|
||||
|
||||
# plot all Polygons and all MultiPolygon components in the same collection
|
||||
poly_idx = np.array(
|
||||
(s.geometry.type == 'Polygon') | (s.geometry.type == 'MultiPolygon'))
|
||||
polys = s.geometry[poly_idx]
|
||||
if not polys.empty:
|
||||
# Legacy behavior applies alpha to fill but not to edges. This requires
|
||||
# plotting them separately (at big performance expense).
|
||||
if linewidth > 0 and color_kwds.get('alpha', 0.5) < 1.0:
|
||||
# Plot the fill with default or user-specified alpha, but do not
|
||||
# draw outlines.
|
||||
plot_polygon_collection(ax, polys, colors[poly_idx], False,
|
||||
linewidth=0, **color_kwds)
|
||||
# Draw the edges, fully opaque, but no facecolor.
|
||||
edges_kwds = color_kwds.copy()
|
||||
edges_kwds['alpha'] = 1
|
||||
edges_kwds['facecolor'] = 'none'
|
||||
plot_polygon_collection(ax, polys, colors[poly_idx], False,
|
||||
linewidth=linewidth, **edges_kwds)
|
||||
else:
|
||||
col = color
|
||||
if geom.type == 'Polygon' or geom.type == 'MultiPolygon':
|
||||
if 'facecolor' in color_kwds:
|
||||
plot_multipolygon(ax, geom, linewidth=linewidth, **color_kwds)
|
||||
else:
|
||||
plot_multipolygon(ax, geom, facecolor=col, linewidth=linewidth, **color_kwds)
|
||||
elif geom.type == 'LineString' or geom.type == 'MultiLineString':
|
||||
plot_multilinestring(ax, geom, color=col, linewidth=linewidth, **color_kwds)
|
||||
elif geom.type == 'Point':
|
||||
plot_point(ax, geom, color=col, **color_kwds)
|
||||
# Optimization: if no alpha on fill, or if no edges, we can plot
|
||||
# everything in one go.
|
||||
plot_polygon_collection(ax, polys, colors[poly_idx], False,
|
||||
linewidth=linewidth, **color_kwds)
|
||||
|
||||
# plot all LineStrings and MultiLineString components in same collection
|
||||
line_idx = np.array(
|
||||
(s.geometry.type == 'LineString') |
|
||||
(s.geometry.type == 'MultiLineString'))
|
||||
lines = s.geometry[line_idx]
|
||||
if not lines.empty:
|
||||
plot_linestring_collection(ax, lines, colors[line_idx], False,
|
||||
linewidth=linewidth, **color_kwds)
|
||||
|
||||
point_idx = np.array(s.geometry.type == 'Point')
|
||||
points = s.geometry[point_idx]
|
||||
if not points.empty:
|
||||
plot_point_collection(ax, points, colors[point_idx], **color_kwds)
|
||||
|
||||
plt.draw()
|
||||
return ax
|
||||
|
||||
@@ -169,7 +351,7 @@ def plot_dataframe(s, column=None, cmap=None, color=None, linewidth=1.0,
|
||||
geometries can be plotted.
|
||||
|
||||
column : str (default None)
|
||||
The name of the column to be plotted.
|
||||
The name of the column to be plotted. Ignored if `color` is also set.
|
||||
|
||||
categorical : bool (default False)
|
||||
If False, cmap will reflect numerical values of the
|
||||
@@ -186,8 +368,7 @@ def plot_dataframe(s, column=None, cmap=None, color=None, linewidth=1.0,
|
||||
Line width for geometries.
|
||||
|
||||
legend : bool (default False)
|
||||
Plot a legend (Experimental; currently for categorical
|
||||
plots only)
|
||||
Plot a legend. Ignored if no `column` is given, or if `color` is given.
|
||||
|
||||
ax : matplotlib.pyplot.Artist (default None)
|
||||
axes on which to draw the plot
|
||||
@@ -228,6 +409,10 @@ def plot_dataframe(s, column=None, cmap=None, color=None, linewidth=1.0,
|
||||
warnings.warn("'axes' is deprecated, please use 'ax' instead "
|
||||
"(for consistency with pandas)", FutureWarning)
|
||||
ax = color_kwds.pop('axes')
|
||||
if column and color:
|
||||
warnings.warn("Only specify one of 'column' or 'color'. Using 'color'.",
|
||||
SyntaxWarning)
|
||||
column = None
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.lines import Line2D
|
||||
@@ -238,52 +423,94 @@ def plot_dataframe(s, column=None, cmap=None, color=None, linewidth=1.0,
|
||||
return plot_series(s.geometry, cmap=cmap, color=color,
|
||||
ax=ax, linewidth=linewidth, figsize=figsize,
|
||||
**color_kwds)
|
||||
|
||||
if s[column].dtype is np.dtype('O'):
|
||||
categorical = True
|
||||
|
||||
# Define `values` as a Series
|
||||
if categorical:
|
||||
if cmap is None:
|
||||
cmap = 'Set1'
|
||||
categories = list(set(s[column].values))
|
||||
categories.sort()
|
||||
valuemap = dict([(k, v) for (v, k) in enumerate(categories)])
|
||||
values = np.array([valuemap[k] for k in s[column]])
|
||||
else:
|
||||
if s[column].dtype is np.dtype('O'):
|
||||
categorical = True
|
||||
if categorical:
|
||||
if cmap is None:
|
||||
cmap = 'Set1'
|
||||
categories = list(set(s[column].values))
|
||||
categories.sort()
|
||||
valuemap = dict([(k, v) for (v, k) in enumerate(categories)])
|
||||
values = [valuemap[k] for k in s[column]]
|
||||
values = s[column]
|
||||
if scheme is not None:
|
||||
binning = __pysal_choro(values, scheme, k=k)
|
||||
values = np.array(binning.yb)
|
||||
# set categorical to True for creating the legend
|
||||
categorical = True
|
||||
binedges = [binning.yb.min()] + binning.bins.tolist()
|
||||
categories = ['{0:.2f} - {1:.2f}'.format(binedges[i], binedges[i+1])
|
||||
for i in range(len(binedges)-1)]
|
||||
if ax is None:
|
||||
fig, ax = plt.subplots(figsize=figsize)
|
||||
ax.set_aspect('equal')
|
||||
|
||||
mn = values.min() if vmin is None else vmin
|
||||
mx = values.max() if vmax is None else vmax
|
||||
|
||||
# plot all Polygons and all MultiPolygon components in the same collection
|
||||
poly_idx = np.array(
|
||||
(s.geometry.type == 'Polygon') | (s.geometry.type == 'MultiPolygon'))
|
||||
polys = s.geometry[poly_idx]
|
||||
if not polys.empty:
|
||||
# Legacy behavior applies alpha to fill but not to edges. This requires
|
||||
# plotting them separately (at big performance expense).
|
||||
if linewidth > 0 and color_kwds.get('alpha', 0.5) < 1.0:
|
||||
# Plot the fill with default or user-specified alpha, but do not
|
||||
# draw outlines.
|
||||
plot_polygon_collection(ax, polys, values[poly_idx], True,
|
||||
vmin=mn, vmax=mx, cmap=cmap,
|
||||
linewidth=0, **color_kwds)
|
||||
# Draw the edges, fully opaque, but no facecolor.
|
||||
edges_kwds = color_kwds.copy()
|
||||
edges_kwds['alpha'] = 1
|
||||
edges_kwds['facecolor'] = 'none'
|
||||
# Setting plot_values=False would cause the array values' colors to
|
||||
# override edgecolor. By setting color instead, matplotlib will
|
||||
# respect edgecolor if set.
|
||||
plot_polygon_collection(ax, polys, ['black'] * len(polys), False,
|
||||
linewidth=linewidth, **edges_kwds)
|
||||
else:
|
||||
values = s[column]
|
||||
if scheme is not None:
|
||||
binning = __pysal_choro(values, scheme, k=k)
|
||||
values = binning.yb
|
||||
# set categorical to True for creating the legend
|
||||
categorical = True
|
||||
binedges = [binning.yb.min()] + binning.bins.tolist()
|
||||
categories = ['{0:.2f} - {1:.2f}'.format(binedges[i], binedges[i+1])
|
||||
for i in range(len(binedges)-1)]
|
||||
cmap = norm_cmap(values, cmap, Normalize, cm, vmin=vmin, vmax=vmax)
|
||||
if ax is None:
|
||||
fig, ax = plt.subplots(figsize=figsize)
|
||||
ax.set_aspect('equal')
|
||||
for geom, value in zip(s.geometry, values):
|
||||
if color is None:
|
||||
col = cmap.to_rgba(value)
|
||||
else:
|
||||
col = color
|
||||
if geom.type == 'Polygon' or geom.type == 'MultiPolygon':
|
||||
plot_multipolygon(ax, geom, facecolor=col, linewidth=linewidth, **color_kwds)
|
||||
elif geom.type == 'LineString' or geom.type == 'MultiLineString':
|
||||
plot_multilinestring(ax, geom, color=col, linewidth=linewidth, **color_kwds)
|
||||
elif geom.type == 'Point':
|
||||
plot_point(ax, geom, color=col, **color_kwds)
|
||||
if legend:
|
||||
if categorical:
|
||||
patches = []
|
||||
for value, cat in enumerate(categories):
|
||||
patches.append(Line2D([0], [0], linestyle="none",
|
||||
marker="o", alpha=color_kwds.get('alpha', 0.5),
|
||||
markersize=10, markerfacecolor=cmap.to_rgba(value)))
|
||||
ax.legend(patches, categories, numpoints=1, loc='best')
|
||||
else:
|
||||
# TODO: show a colorbar
|
||||
raise NotImplementedError
|
||||
# Optimization: if no alpha on fill, or if no edges, we can plot
|
||||
# everything in one go.
|
||||
plot_polygon_collection(ax, polys, values[poly_idx], True,
|
||||
vmin=mn, vmax=mx, cmap=cmap,
|
||||
linewidth=linewidth, **color_kwds)
|
||||
|
||||
# plot all LineStrings and MultiLineString components in same collection
|
||||
line_idx = np.array(
|
||||
(s.geometry.type == 'LineString') |
|
||||
(s.geometry.type == 'MultiLineString'))
|
||||
lines = s.geometry[line_idx]
|
||||
if not lines.empty:
|
||||
plot_linestring_collection(ax, lines, values[line_idx], True,
|
||||
vmin=mn, vmax=mx, cmap=cmap,
|
||||
linewidth=linewidth, **color_kwds)
|
||||
|
||||
point_idx = np.array(s.geometry.type == 'Point')
|
||||
points = s.geometry[point_idx]
|
||||
if not points.empty:
|
||||
plot_point_collection(ax, points, values[point_idx],
|
||||
vmin=mn, vmax=mx, cmap=cmap, **color_kwds)
|
||||
|
||||
if legend and not color:
|
||||
norm = Normalize(vmin=mn, vmax=mx)
|
||||
n_cmap = cm.ScalarMappable(norm=norm, cmap=cmap)
|
||||
if categorical:
|
||||
patches = []
|
||||
for value, cat in enumerate(categories):
|
||||
patches.append(Line2D([0], [0], linestyle="none",
|
||||
marker="o", alpha=color_kwds.get('alpha', 0.5),
|
||||
markersize=10, markerfacecolor=n_cmap.to_rgba(value)))
|
||||
ax.legend(patches, categories, numpoints=1, loc='best')
|
||||
else:
|
||||
n_cmap.set_array([])
|
||||
ax.get_figure().colorbar(n_cmap)
|
||||
|
||||
plt.draw()
|
||||
return ax
|
||||
|
||||
@@ -325,42 +552,3 @@ def __pysal_choro(values, scheme, k=5):
|
||||
return binning
|
||||
except ImportError:
|
||||
raise ImportError("PySAL is required to use the 'scheme' keyword")
|
||||
|
||||
|
||||
def norm_cmap(values, cmap, normalize, cm, vmin=None, vmax=None):
|
||||
|
||||
""" Normalize and set colormap
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
values
|
||||
Series or array to be normalized
|
||||
|
||||
cmap
|
||||
matplotlib Colormap
|
||||
|
||||
normalize
|
||||
matplotlib.colors.Normalize
|
||||
|
||||
cm
|
||||
matplotlib.cm
|
||||
|
||||
vmin
|
||||
Minimum value of colormap. If None, uses min(values).
|
||||
|
||||
vmax
|
||||
Maximum value of colormap. If None, uses max(values).
|
||||
|
||||
Returns
|
||||
-------
|
||||
n_cmap
|
||||
mapping of normalized values to colormap (cmap)
|
||||
|
||||
"""
|
||||
|
||||
mn = min(values) if vmin is None else vmin
|
||||
mx = max(values) if vmax is None else vmax
|
||||
norm = normalize(vmin=mn, vmax=mx)
|
||||
n_cmap = cm.ScalarMappable(norm=norm, cmap=cmap)
|
||||
return n_cmap
|
||||
|
||||
+216
-162
@@ -1,119 +1,20 @@
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
import itertools
|
||||
import numpy as np
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from distutils.version import LooseVersion
|
||||
import warnings
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use('Agg', warn=False)
|
||||
from matplotlib.pyplot import Artist, savefig, clf, cm, get_cmap
|
||||
from matplotlib.testing.noseclasses import ImageComparisonFailure
|
||||
from matplotlib.testing.compare import compare_images
|
||||
from numpy import cos, sin, pi
|
||||
from shapely.geometry import Polygon, LineString, Point
|
||||
from matplotlib.pyplot import get_cmap
|
||||
from shapely.affinity import rotate
|
||||
from shapely.geometry import MultiPolygon, Polygon, LineString, Point
|
||||
from six.moves import xrange
|
||||
from .util import unittest
|
||||
|
||||
from geopandas import GeoSeries, GeoDataFrame, read_file
|
||||
|
||||
|
||||
# If set to True, generate images rather than perform tests (all tests will pass!)
|
||||
GENERATE_BASELINE = False
|
||||
|
||||
BASELINE_DIR = os.path.join(os.path.dirname(__file__), 'baseline_images', 'test_plotting')
|
||||
|
||||
TRAVIS = bool(os.environ.get('TRAVIS', False))
|
||||
MPL_DEV = matplotlib.__version__ > LooseVersion('1.5.1')
|
||||
|
||||
class TestImageComparisons(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.tempdir = tempfile.mkdtemp()
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
shutil.rmtree(cls.tempdir)
|
||||
return
|
||||
|
||||
def _compare_images(self, ax, filename, tol=10):
|
||||
""" Helper method to do the comparisons """
|
||||
assert isinstance(ax, Artist)
|
||||
if GENERATE_BASELINE:
|
||||
savefig(os.path.join(BASELINE_DIR, filename))
|
||||
savefig(os.path.join(self.tempdir, filename))
|
||||
err = compare_images(os.path.join(BASELINE_DIR, filename),
|
||||
os.path.join(self.tempdir, filename),
|
||||
tol, in_decorator=True)
|
||||
if err:
|
||||
raise ImageComparisonFailure('images not close: %(actual)s '
|
||||
'vs. %(expected)s '
|
||||
'(RMS %(rms).3f)' % err)
|
||||
|
||||
@unittest.skipIf(MPL_DEV, 'Skip for development version of matplotlib')
|
||||
def test_poly_plot(self):
|
||||
""" Test plotting a simple series of polygons """
|
||||
clf()
|
||||
filename = 'poly_plot.png'
|
||||
t1 = Polygon([(0, 0), (1, 0), (1, 1)])
|
||||
t2 = Polygon([(1, 0), (2, 0), (2, 1)])
|
||||
polys = GeoSeries([t1, t2])
|
||||
ax = polys.plot()
|
||||
self._compare_images(ax=ax, filename=filename)
|
||||
|
||||
@unittest.skipIf(MPL_DEV, 'Skip for development version of matplotlib')
|
||||
def test_point_plot(self):
|
||||
""" Test plotting a simple series of points """
|
||||
clf()
|
||||
filename = 'points_plot.png'
|
||||
N = 10
|
||||
points = GeoSeries(Point(i, i) for i in xrange(N))
|
||||
ax = points.plot()
|
||||
self._compare_images(ax=ax, filename=filename)
|
||||
|
||||
@unittest.skipIf(MPL_DEV, 'Skip for development version of matplotlib')
|
||||
def test_line_plot(self):
|
||||
""" Test plotting a simple series of lines """
|
||||
clf()
|
||||
filename = 'lines_plot.png'
|
||||
N = 10
|
||||
lines = GeoSeries([LineString([(0, i), (9, i)]) for i in xrange(N)])
|
||||
ax = lines.plot()
|
||||
self._compare_images(ax=ax, filename=filename)
|
||||
|
||||
@unittest.skipIf(TRAVIS, 'Skip on Travis (fails even though it passes locally)')
|
||||
def test_plot_GeoDataFrame_with_kwargs(self):
|
||||
"""
|
||||
Test plotting a simple GeoDataFrame consisting of a series of polygons
|
||||
with increasing values using various extra kwargs.
|
||||
"""
|
||||
clf()
|
||||
filename = 'poly_plot_with_kwargs.png'
|
||||
ts = np.linspace(0, 2*pi, 10, endpoint=False)
|
||||
|
||||
# Build GeoDataFrame from a series of triangles wrapping around in a ring
|
||||
# and a second column containing a list of increasing values.
|
||||
r1 = 1.0 # radius of inner ring boundary
|
||||
r2 = 1.5 # radius of outer ring boundary
|
||||
|
||||
def make_triangle(t0, t1):
|
||||
return Polygon([(r1*cos(t0), r1*sin(t0)),
|
||||
(r2*cos(t0), r2*sin(t0)),
|
||||
(r1*cos(t1), r1*sin(t1))])
|
||||
|
||||
polys = GeoSeries([make_triangle(t0, t1) for t0, t1 in zip(ts, ts[1:])])
|
||||
values = np.arange(len(polys))
|
||||
df = GeoDataFrame({'geometry': polys, 'values': values})
|
||||
|
||||
# Plot the GeoDataFrame using various keyword arguments to see if they are honoured
|
||||
ax = df.plot(column='values', cmap=cm.RdBu, vmin=+2, vmax=None, figsize=(8, 4))
|
||||
self._compare_images(ax=ax, filename=filename)
|
||||
|
||||
|
||||
|
||||
class TestPointPlotting(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@@ -123,7 +24,14 @@ class TestPointPlotting(unittest.TestCase):
|
||||
values = np.arange(self.N)
|
||||
self.df = GeoDataFrame({'geometry': self.points, 'values': values})
|
||||
|
||||
@unittest.skipIf(MPL_DEV, 'Skip for development version of matplotlib')
|
||||
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 -> max 9 different colors
|
||||
@@ -132,21 +40,19 @@ class TestPointPlotting(unittest.TestCase):
|
||||
ax = self.points.plot()
|
||||
cmap = get_cmap('Set1', 9)
|
||||
expected_colors = cmap(list(range(9))*2)
|
||||
_check_colors(ax.get_lines(), expected_colors)
|
||||
_check_colors(self.N, ax.collections[0], expected_colors)
|
||||
|
||||
# GeoDataFrame -> uses 'jet' instead of 'Set1'
|
||||
ax = self.df.plot()
|
||||
cmap = get_cmap('jet', 9)
|
||||
expected_colors = cmap(list(range(9))*2)
|
||||
_check_colors(ax.get_lines(), expected_colors)
|
||||
|
||||
## with specifying values
|
||||
_check_colors(self.N, ax.collections[0], expected_colors)
|
||||
|
||||
## with specifying values -> different colors for all 10 values
|
||||
ax = self.df.plot(column='values')
|
||||
cmap = get_cmap('jet')
|
||||
expected_colors = cmap(np.arange(self.N)/(self.N-1))
|
||||
|
||||
_check_colors(ax.get_lines(), expected_colors)
|
||||
_check_colors(self.N, ax.collections[0], expected_colors)
|
||||
|
||||
def test_colormap(self):
|
||||
|
||||
@@ -156,44 +62,72 @@ class TestPointPlotting(unittest.TestCase):
|
||||
ax = self.points.plot(cmap='RdYlGn')
|
||||
cmap = get_cmap('RdYlGn', 9)
|
||||
expected_colors = cmap(list(range(9))*2)
|
||||
_check_colors(ax.get_lines(), expected_colors)
|
||||
_check_colors(self.N, ax.collections[0], expected_colors)
|
||||
|
||||
# GeoDataFrame -> same as GeoSeries in this case
|
||||
ax = self.df.plot(cmap='RdYlGn')
|
||||
_check_colors(ax.get_lines(), expected_colors)
|
||||
|
||||
## with specifying values
|
||||
_check_colors(self.N, ax.collections[0], expected_colors)
|
||||
|
||||
## with specifying values -> different colors for all 10 values
|
||||
ax = self.df.plot(column='values', cmap='RdYlGn')
|
||||
cmap = get_cmap('RdYlGn')
|
||||
expected_colors = cmap(np.arange(self.N)/(self.N-1))
|
||||
_check_colors(ax.get_lines(), expected_colors)
|
||||
_check_colors(self.N, ax.collections[0], expected_colors)
|
||||
|
||||
def test_single_color(self):
|
||||
|
||||
ax = self.points.plot(color='green')
|
||||
_check_colors(ax.get_lines(), ['green']*self.N)
|
||||
_check_colors(self.N, ax.collections[0], ['green']*self.N)
|
||||
|
||||
ax = self.df.plot(color='green')
|
||||
_check_colors(ax.get_lines(), ['green']*self.N)
|
||||
_check_colors(self.N, ax.collections[0], ['green']*self.N)
|
||||
|
||||
ax = self.df.plot(column='values', color='green')
|
||||
_check_colors(ax.get_lines(), ['green']*self.N)
|
||||
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], ['green']*self.N)
|
||||
|
||||
def test_style_kwargs(self):
|
||||
|
||||
# markersize
|
||||
ax = self.points.plot(markersize=10)
|
||||
ms = [l.get_markersize() for l in ax.get_lines()]
|
||||
assert ms == [10] * self.N
|
||||
assert ax.collections[0].get_sizes() == [10]
|
||||
|
||||
ax = self.df.plot(markersize=10)
|
||||
ms = [l.get_markersize() for l in ax.get_lines()]
|
||||
assert ms == [10] * self.N
|
||||
assert ax.collections[0].get_sizes() == [10]
|
||||
|
||||
ax = self.df.plot(column='values', markersize=10)
|
||||
ms = [l.get_markersize() for l in ax.get_lines()]
|
||||
assert ms == [10] * self.N
|
||||
assert ax.collections[0].get_sizes() == [10]
|
||||
|
||||
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 = ax.get_figure().axes[1].collections[0].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[0].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])
|
||||
|
||||
|
||||
class TestLineStringPlotting(unittest.TestCase):
|
||||
@@ -202,34 +136,52 @@ class TestLineStringPlotting(unittest.TestCase):
|
||||
|
||||
self.N = 10
|
||||
values = np.arange(self.N)
|
||||
self.lines = GeoSeries([LineString([(0, i), (9, i)]) for i in xrange(self.N)])
|
||||
self.lines = GeoSeries([LineString([(0, i), (4, i+0.5), (9, i)])
|
||||
for i in xrange(self.N)],
|
||||
index=list('ABCDEFGHIJ'))
|
||||
self.df = GeoDataFrame({'geometry': self.lines, 'values': values})
|
||||
|
||||
def test_single_color(self):
|
||||
|
||||
ax = self.lines.plot(color='green')
|
||||
_check_colors(ax.get_lines(), ['green']*self.N)
|
||||
_check_colors(self.N, ax.collections[0], ['green']*self.N)
|
||||
|
||||
ax = self.df.plot(color='green')
|
||||
_check_colors(ax.get_lines(), ['green']*self.N)
|
||||
_check_colors(self.N, ax.collections[0], ['green']*self.N)
|
||||
|
||||
ax = self.df.plot(column='values', color='green')
|
||||
_check_colors(ax.get_lines(), ['green']*self.N)
|
||||
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], ['green']*self.N)
|
||||
|
||||
def test_style_kwargs(self):
|
||||
|
||||
def linestyle_tuple_to_string(tup):
|
||||
""" Converts a linestyle of the form `(offset, onoffseq)`, as
|
||||
documented in `Collections.set_linestyle`, to a string
|
||||
representation, namely one of:
|
||||
{ 'dashed', 'dotted', 'dashdot', 'solid' }.
|
||||
"""
|
||||
from matplotlib.backend_bases import GraphicsContextBase
|
||||
reverse_idx = dict((v, k)
|
||||
for k, v in GraphicsContextBase.dashd.items())
|
||||
return reverse_idx[tup]
|
||||
|
||||
# linestyle
|
||||
ax = self.lines.plot(linestyle='dashed')
|
||||
ls = [l.get_linestyle() for l in ax.get_lines()]
|
||||
assert ls == ['--'] * self.N
|
||||
ls = [linestyle_tuple_to_string(l)
|
||||
for l in ax.collections[0].get_linestyles()]
|
||||
assert ls == ['dashed']
|
||||
|
||||
ax = self.df.plot(linestyle='dashed')
|
||||
ls = [l.get_linestyle() for l in ax.get_lines()]
|
||||
assert ls == ['--'] * self.N
|
||||
ls = [linestyle_tuple_to_string(l)
|
||||
for l in ax.collections[0].get_linestyles()]
|
||||
assert ls == ['dashed']
|
||||
|
||||
ax = self.df.plot(column='values', linestyle='dashed')
|
||||
ls = [l.get_linestyle() for l in ax.get_lines()]
|
||||
assert ls == ['--'] * self.N
|
||||
ls = [linestyle_tuple_to_string(l)
|
||||
for l in ax.collections[0].get_linestyles()]
|
||||
assert ls == ['dashed']
|
||||
|
||||
|
||||
class TestPolygonPlotting(unittest.TestCase):
|
||||
@@ -238,36 +190,121 @@ class TestPolygonPlotting(unittest.TestCase):
|
||||
|
||||
t1 = Polygon([(0, 0), (1, 0), (1, 1)])
|
||||
t2 = Polygon([(1, 0), (2, 0), (2, 1)])
|
||||
self.polys = GeoSeries([t1, t2])
|
||||
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]})
|
||||
return
|
||||
|
||||
def test_single_color(self):
|
||||
|
||||
ax = self.polys.plot(color='green')
|
||||
_check_colors(ax.patches, ['green']*2, alpha=0.5)
|
||||
_check_colors(2, ax.collections[0], ['green']*2, alpha=0.5)
|
||||
|
||||
ax = self.df.plot(color='green')
|
||||
_check_colors(ax.patches, ['green']*2, alpha=0.5)
|
||||
_check_colors(2, ax.collections[0], ['green']*2, alpha=0.5)
|
||||
|
||||
ax = self.df.plot(column='values', color='green')
|
||||
_check_colors(ax.patches, ['green']*2, alpha=0.5)
|
||||
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], ['green']*2, alpha=0.5)
|
||||
|
||||
def test_optimization(self):
|
||||
|
||||
# when linewidth=0 or no alpha, we don't have to plot polys twice
|
||||
|
||||
ax = self.polys.plot(linewidth=0)
|
||||
assert len(ax.collections) == 1 # only plotted once
|
||||
|
||||
ax = self.polys.plot(alpha=1)
|
||||
assert len(ax.collections) == 1 # only plotted once
|
||||
|
||||
ax = self.df.plot(linewidth=0)
|
||||
assert len(ax.collections) == 1 # only plotted once
|
||||
|
||||
ax = self.df.plot(alpha=1)
|
||||
assert len(ax.collections) == 1 # only plotted once
|
||||
|
||||
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)
|
||||
cmap = get_cmap('Set1', 2)
|
||||
self.assertEqual(ax.patches[0].get_facecolor(), ax.patches[1].get_facecolor())
|
||||
actual_colors = ax.collections[0].get_facecolors()
|
||||
np.testing.assert_array_equal(actual_colors[0], actual_colors[1])
|
||||
|
||||
def test_facecolor(self):
|
||||
t1 = Polygon([(0, 0), (1, 0), (1, 1)])
|
||||
t2 = Polygon([(1, 0), (2, 0), (2, 1)])
|
||||
polys = GeoSeries([t1, t2])
|
||||
df = GeoDataFrame({'geometry': polys, 'values': [0, 1]})
|
||||
def test_style_kwargs(self):
|
||||
|
||||
ax = polys.plot(facecolor='k')
|
||||
_check_colors(ax.patches, ['k']*2, alpha=0.5)
|
||||
# facecolor overrides default cmap when color is not set
|
||||
ax = self.polys.plot(facecolor='k')
|
||||
_check_colors(2, ax.collections[0], ['k']*2, alpha=0.5)
|
||||
|
||||
# facecolor overrides more general-purpose color when both are set
|
||||
ax = self.polys.plot(color='red', facecolor='k')
|
||||
_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, 0.5)],
|
||||
ax.collections[0].get_edgecolors())
|
||||
|
||||
ax = self.df.plot('values', edgecolor='red')
|
||||
np.testing.assert_array_equal([(1, 0, 0, 0.5)],
|
||||
ax.collections[0].get_edgecolors())
|
||||
|
||||
def test_multipolygons(self):
|
||||
|
||||
# MultiPolygons
|
||||
ax = self.df2.plot()
|
||||
assert len(ax.collections[0].get_paths()) == 4
|
||||
cmap = get_cmap('jet', 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], expected_colors, alpha=0.5)
|
||||
|
||||
ax = self.df2.plot('values')
|
||||
## specifying values -> same as without values in this case.
|
||||
_check_colors(4, ax.collections[0], expected_colors, alpha=0.5)
|
||||
|
||||
|
||||
class TestNonuniformGeometryPlotting(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
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]})
|
||||
return
|
||||
|
||||
def test_colormap(self):
|
||||
|
||||
ax = self.series.plot(cmap='RdYlGn')
|
||||
cmap = get_cmap('RdYlGn', 3)
|
||||
# polygon gets extra alpha. See #266
|
||||
_check_colors(1, ax.collections[0], [cmap(0)], alpha=0.5)
|
||||
# N.B. ax.collections[1] contains the edges of the polygon
|
||||
_check_colors(1, ax.collections[2], [cmap(1)], alpha=1) # line
|
||||
_check_colors(1, ax.collections[3], [cmap(2)], alpha=1) # point
|
||||
|
||||
def test_style_kwargs(self):
|
||||
|
||||
# markersize -> only the Point gets it
|
||||
ax = self.series.plot(markersize=10)
|
||||
assert ax.collections[3].get_sizes() == [10]
|
||||
|
||||
ax = self.df.plot(markersize=10)
|
||||
assert ax.collections[3].get_sizes() == [10]
|
||||
|
||||
|
||||
class TestPySALPlotting(unittest.TestCase):
|
||||
@@ -284,7 +321,7 @@ class TestPySALPlotting(unittest.TestCase):
|
||||
|
||||
def test_legend(self):
|
||||
ax = self.tracts.plot(column='CRIME', scheme='QUANTILES', k=3,
|
||||
cmap='OrRd', legend=True)
|
||||
cmap='OrRd', legend=True)
|
||||
|
||||
labels = [t.get_text() for t in ax.get_legend().get_texts()]
|
||||
expected = [u'0.00 - 26.07', u'26.07 - 41.97', u'41.97 - 68.89']
|
||||
@@ -297,20 +334,37 @@ class TestPySALPlotting(unittest.TestCase):
|
||||
cmap='OrRd', legend=True)
|
||||
|
||||
|
||||
def _check_colors(collection, expected_colors, alpha=None):
|
||||
def _check_colors(N, collection, expected_colors, alpha=None):
|
||||
""" Asserts that the members of `collection` match the `expected_colors` (in order)
|
||||
|
||||
from matplotlib.lines import Line2D
|
||||
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 `collecton` is assumed to be set in its own
|
||||
facecolor RGBA tuples.)
|
||||
"""
|
||||
import matplotlib.colors as colors
|
||||
conv = colors.colorConverter
|
||||
|
||||
for patch, color in zip(collection, expected_colors):
|
||||
if isinstance(patch, Line2D):
|
||||
# points/lines
|
||||
result = patch.get_color()
|
||||
else:
|
||||
# polygons
|
||||
result = patch.get_facecolor()
|
||||
assert conv.to_rgba(result) == conv.to_rgba(color, alpha=alpha)
|
||||
# Convert 2D numpy array to a list of RGBA tuples.
|
||||
actual_colors = list(collection.get_facecolors())
|
||||
actual_colors = map(tuple, actual_colors)
|
||||
all_actual_colors = list(itertools.islice(
|
||||
itertools.cycle(actual_colors), N))
|
||||
|
||||
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))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user