diff --git a/README.md b/README.md index ce7a96f..532bfd8 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,9 @@ tax.scatter(points, marker='s', color='red', label="Red Squares") tax.legend() ``` -Most drawing functions can take standard matplotlib keyword arguments such as [linestyle](http://matplotlib.org/api/lines_api.html#matplotlib.lines.Line2D.set_linestyle) and linewidth. You can use LaTeX in titles and labels. +Most drawing functions can take standard matplotlib keyword arguments such as +[linestyle](http://matplotlib.org/api/lines_api.html#matplotlib.lines.Line2D.set_linestyle) +and linewidth. You can use LaTeX in titles and labels. If you need to act directly on the underyling matplotlib axes, you can access them: @@ -75,12 +77,17 @@ figure, tax = ternary.figure(ax=ax) ... ```` -`TernaryAxesSubplot` objects keep track of the scale and supply this parameter to other functions as needed. +`TernaryAxesSubplot` objects keep track of the scale, axes, and other parameters, +supplying them as needed to other functions. ## Simplex Boundary and Gridlines The following code draws a boundary for the simplex and gridlines. +<<<<<<< HEAD +![Ternary Plot -- Boundary and Gridlines](/readme_images/boundary_and_gridlines.png) +======= +>>>>>>> upstream ``` from matplotlib import pyplot @@ -92,7 +99,7 @@ figure, tax = ternary.figure(scale=scale) # Draw Boundary and Gridlines tax.boundary(color="black", linewidth=2.0) -tax.gridlines(color="blue", multiple=5) # Every 5th gridline, can be fractional +tax.gridlines(color="blue", multiple=5) # Every 5th gridline, can be a float # Set Axis labels and Title fontsize = 20 @@ -154,7 +161,7 @@ Curves can be plotted by specifying the points of the curve, just like matplotli ternary.plot(points) ``` -Points is a list of tuples or numpy arrays, e.g. [(0.5, 0.25, 0.25), (1./3, 1./3, 1./3)], e.g. as in the [sample data](/curve.txt). +Points is a list of tuples or numpy arrays, such as [(0.5, 0.25, 0.25), (1./3, 1./3, 1./3)], ``` import ternary @@ -166,7 +173,7 @@ tax.gridlines(multiple=0.2, color="black") tax.set_title("Plotting of sample trajectory data", fontsize=20) points = [] # Load some data, tuples (x,y,z) -with open("curve.txt") as handle: +with open("sample_data/curve.txt") as handle: for line in handle: points.append(map(float, line.split(' '))) # Plot the data @@ -258,8 +265,6 @@ import ternary scale = 60 figure, tax = ternary.figure(scale=scale) -tax.set_title("Scatter Plot", fontsize=20) - tax.heatmapf(shannon_entropy, boundary=True, style="triangular") tax.boundary(linewidth=2.0) tax.set_title("Shannon Entropy Heatmap") @@ -282,7 +287,7 @@ Make the heatmap as follows: ternary.heatmap(data, scale, ax=None, cmap=None) ``` -or +or on a `TernaryAxesSubplot` object ``` tax.heatmap(data, cmap=None) @@ -290,6 +295,7 @@ tax.heatmap(data, cmap=None) This can produces images such as: + ![Ternary Heatmap Examples](/readme_images/heatmap-dual_vs_triangular.png) ![Ternary Heatmap Examples](/readme_images/heatmap_rsp.png) diff --git a/examples.py b/examples.py index 3e55983..5adecef 100644 --- a/examples.py +++ b/examples.py @@ -68,6 +68,21 @@ def random_heatmap(scale=4): pyplot.show() if __name__ == '__main__': + # Show Coordinates + scale = 3 + figure, tax = ternary.figure(scale=scale, permutation="120") + points_lists = [[(0,0,3), (1,0,2), (2,0,1)], + [(3,0,0), (2,1,0), (1,2,0)], + [(0,3,0), (0,2,1), (0,1,2)], + [(1,1,1)]] + colors = ['b', 'r', 'g', 'black'] + markers = ['o', 'v', '*', 'd'] + for i, points in enumerate(points_lists): + for point in points: + tax.scatter([tuple(point)], color=colors[i], marker=markers[i]) + tax.annotate("".join(map(str, point)), tuple(point), color=colors[i]) + tax.gridlines(multiple=1.) + ## Boundary and Gridlines scale = 40 figure, ternary_ax = ternary.figure(scale=scale) diff --git a/ternary/heatmapping.py b/ternary/heatmapping.py index 061249b..77c5957 100644 --- a/ternary/heatmapping.py +++ b/ternary/heatmapping.py @@ -2,25 +2,29 @@ Various Heatmaps. """ +import functools import numpy from matplotlib import pyplot -from helpers import SQRT3, SQRT3OVER2, unzip, normalize, simplex_iterator +from helpers import SQRT3, SQRT3OVER2, unzip, normalize, simplex_iterator, project_point import plotting from colormapping import get_cmap, colormapper, colorbar_hack + +hexagon_deltas = generate_hexagon_deltas() + ### Heatmap Triangulation Coordinates ### ## Triangular Heatmaps ## -def blend_value(data, i, j, k=None, keys=None): +def blend_value(data, i, j, k, keys=None): """Computes the average value of the three vertices of a triangule in the simplex triangulation, where two of the vertices are on the lower horizontal.""" key_size = len(data.keys()[0]) if not keys: - keys = [(i, j, k), (i, j + 1, k - 1), (i + 1, j, k - 1)] + keys = triangle_coordinates(i, j, k) # Reduce key from (i, j, k) to (i, j) if necessary keys = [tuple(key[:key_size]) for key in keys] @@ -32,15 +36,15 @@ def blend_value(data, i, j, k=None, keys=None): value = None return value -def alt_blend_value(data, i, j, k=None): +def alt_blend_value(data, i, j, k): """Computes the average value of the three vertices of a triangule in the simplex triangulation, where two of the vertices are on the upper horizontal.""" - keys = [(i, j, k), (i, j + 1, k - 1), (i + 1, j - 1, k)] + keys = alt_triangle_coordinates(i, j, k) return blend_value(data, i, j, k, keys=keys) -def triangle_coordinates(i, j, k=None): +def triangle_coordinates(i, j, k): """ Computes coordinates of the constituent triangles of a triangulation for the simplex. These triangules are parallel to the lower axis on the lower side. @@ -51,13 +55,12 @@ def triangle_coordinates(i, j, k=None): Returns ------- - A numpy array of coordinates of the hexagon + A numpy array of coordinates of the hexagon (unprojected) """ - return [(i / 2. + j, i * SQRT3OVER2), (i / 2. + j + 1, i * SQRT3OVER2), - (i / 2. + j + 0.5, (i + 1) * SQRT3OVER2)] + return [(i, j, k), (i + 1, j, k - 1), (i, j + 1, k - 1)] -def alt_triangle_coordinates(i, j, k=None): +def alt_triangle_coordinates(i, j, k): """ Computes coordinates of the constituent triangles of a triangulation for the simplex. These triangules are parallel to the lower axis on the upper side. @@ -68,23 +71,40 @@ def alt_triangle_coordinates(i, j, k=None): Returns ------- - A numpy array of coordinates of the hexagon + A numpy array of coordinates of the hexagon (unprojected) """ - return [(i/2. + j + 1, i * SQRT3OVER2), - (i/2. + j + 1.5, (i + 1) * SQRT3OVER2), - (i/2. + j + 0.5, (i + 1) * SQRT3OVER2)] + return [(i, j + 1, k - 1), (i + 1, j + 1, k), (i + 1, j, k - 1)] ## Hexagonal Heatmaps ## -## Original Hexagonal heatmap code submitted by https://github.com/btweinstein -# Hexagonal heatmaps do no smooth the colors as in the triangular case. -_alpha = numpy.array([0, 1. / SQRT3]) -_deltaup = numpy.array([1. / 2., 1. / (2. * SQRT3)]) -_deltadown = numpy.array([1. / 2., - 1. / (2. * SQRT3)]) -_i_vec = numpy.array([1. / 2., SQRT3 / 2.]) -_i_vec_down = numpy.array([1. / 2., -SQRT3 / 2.]) -_deltaX_vec = numpy.array([1. / 2, 0]) +def generate_hexagon_deltas(): + """ + Generates a dictionary of the necessary additive vectors to generate the + heaxagon points for the haxagonal heatmap. + """ + + zero = numpy.array([0, 0, 0]) + alpha = numpy.array([-1./3, 2./3, 0]) + deltaup = numpy.array([1./3, 1./3, 0]) + deltadown = numpy.array([2./3, -1./3, 0]) + i_vec = numpy.array([0, 1./2, -1./2]) + i_vec_down = numpy.array([1./2, -1./2, 0]) + deltaX_vec = numpy.array([1./2, 0, -1./2]) + + d = dict() + # Corner Points + d["100"] = [zero, -deltaX_vec, -deltadown, -i_vec_down] + d["010"] = [zero, i_vec_down, -alpha, -i_vec] + d["001"] = [zero, i_vec, deltaup, deltaX_vec] + # On the Edges + d["011"] = [i_vec, deltaup, deltadown, -alpha, -i_vec] + d["101"] = [-deltaX_vec, -deltadown, alpha, deltaup, deltaX_vec] + d["110"] = [i_vec_down, -alpha, -deltaup, -deltadown, -i_vec_down] + # Interior point + d["111"] = [alpha, deltaup, deltadown, -alpha, -deltaup, -deltadown] + + return d def hexagon_coordinates(i, j, k): """ @@ -96,42 +116,29 @@ def hexagon_coordinates(i, j, k): Returns ------- - A numpy array of coordinates of the hexagon + A numpy array of coordinates of the hexagon (unprojected) """ - steps = i + j + k - ij = numpy.array([i / 2. + j, SQRT3 / 2 * i]) - - # Corner cases (literally) - if i == steps: # j == k == 0 - coords = [ij, ij + _i_vec_down / 2., ij - _alpha, ij - _i_vec / 2.] - elif k == steps: # i == j == 0 - coords = [ij, ij + _i_vec / 2., ij + _deltaup, ij + _deltaX_vec] - elif j == steps: # i == k == 0 - coords = [ij, ij - _deltaX_vec, ij - _deltadown, ij - _i_vec_down / 2.] - # Now the edges - elif i == 0: - coords = [ij - _deltaX_vec, ij - _deltadown, - ij + _alpha, ij + _deltaup, ij + _deltaX_vec] - elif j == 0: - coords = [ij + _i_vec / 2., ij + _deltaup, ij + _deltadown, - ij - _alpha, ij - _i_vec / 2.] - elif k == 0: - coords = [ij + _i_vec_down / 2., ij - _alpha, ij - _deltaup, - ij - _deltadown, ij - _i_vec_down / 2.] - # Must be an interior point - else: - coords = [ij + _alpha, ij + _deltaup, ij + _deltadown, - ij - _alpha, ij - _deltaup, ij - _deltadown] - - return numpy.array(coords) + signature = "" + for x in [i, j, k]: + if x == 0: + signature += "0" + else: + signature += "1" + deltas = hexagon_deltas[signature] + center = numpy.array([i, j, k]) + return numpy.array([center + x for x in deltas]) ## Heatmaps ## -def polygon_iterator(data, scale, style): - """Iterator for the vertices of the polygon to be colored and its color, +def polygon_generator(data, scale, style, permutation=None): + """Generator for the vertices of the polygon to be colored and its color, depending on style. Called by heatmap.""" + # We'll project the coordinates inside this function to prevent + # passing around permutation more than necessary + project = functools.partial(project_point, permutation=permutation) + for key, value in sorted(data.items()): if value is None: continue @@ -140,29 +147,30 @@ def polygon_iterator(data, scale, style): k = scale - i - j if style == 'h': vertices = hexagon_coordinates(i, j, k) - yield (vertices, value) + yield (map(project, vertices), value) elif style == 'd': # Upright triangles vertices = triangle_coordinates(i, j, k) - yield (vertices, value) + yield (map(project, vertices), value) # Upside-down triangles vertices = alt_triangle_coordinates(i, j, k) value = blend_value(data, i, j, k) - yield (vertices, value) + yield (map(project, vertices), value) elif style == 't': # Upright triangles vertices = triangle_coordinates(i, j, k) value = blend_value(data, i, j, k) - yield (vertices, value) + yield (map(project, vertices), value) # If not on the boundary add the upside-down triangle - if (j == 0) or (j == scale): + if i == scale: continue - vertices = alt_triangle_coordinates(i, j - 1, k + 1) + vertices = alt_triangle_coordinates(i, j, k) value = alt_blend_value(data, i, j, k) - yield (vertices, value) + yield (map(project, vertices), value) def heatmap(data, scale, vmin=None, vmax=None, cmap=None, ax=None, - scientific=False, style='triangular', colorbar=True): + scientific=False, style='triangular', colorbar=True, + permutation=None): """ Plots heatmap of given color values. @@ -187,6 +195,8 @@ def heatmap(data, scale, vmin=None, vmax=None, cmap=None, ax=None, The style of the heatmap, "triangular", "dual-triangular" or "hexagonal" colorbar: bool, True Show colorbar. + permutation: string, None + A permutation of the coordinates Returns ------- @@ -204,7 +214,8 @@ def heatmap(data, scale, vmin=None, vmax=None, cmap=None, ax=None, if style not in ["t", "h", 'd']: raise ValueError("Heatmap style must be 'triangular', 'dual-triangular', or 'hexagonal'") - vertices_values = polygon_iterator(data, scale, style=style) + vertices_values = polygon_generator(data, scale, style, + permutation=permutation) # Draw the polygons and color them for vertices, value in vertices_values: @@ -222,7 +233,8 @@ def heatmap(data, scale, vmin=None, vmax=None, cmap=None, ax=None, ## User Convenience Functions ## def heatmapf(func, scale=10, boundary=True, cmap=None, ax=None, - scientific=False, style='triangular', colorbar=True): + scientific=False, style='triangular', colorbar=True, + permutation=None): """ Computes func on heatmap partition coordinates and plots heatmap. In other words, computes the function on lattice points of the simplex (normalized @@ -246,6 +258,8 @@ def heatmapf(func, scale=10, boundary=True, cmap=None, ax=None, Whether to use scientific notation for colorbar numbers. colorbar: bool, True Show colorbar. + permutation: string, None + A permutation of the coordinates Returns ------- @@ -258,5 +272,6 @@ def heatmapf(func, scale=10, boundary=True, cmap=None, ax=None, data[(i, j)] = func(normalize([i, j, k])) # Pass everything to the heatmapper ax = heatmap(data, scale, cmap=cmap, ax=ax, style=style, - scientific=scientific, colorbar=colorbar) + scientific=scientific, colorbar=colorbar, + permutation=permutation) return ax diff --git a/ternary/helpers.py b/ternary/helpers.py index dbe04fd..c1f156a 100644 --- a/ternary/helpers.py +++ b/ternary/helpers.py @@ -13,6 +13,7 @@ ### Auxilliary Functions ### def unzip(l): + """[(a1, b1), ..., (an, bn)] ----> ([a1, ..., an], [b1, ..., bn])""" return zip(*l) def normalize(l): @@ -68,7 +69,17 @@ def simplex_iterator(scale, boundary=True): ## Ternary Projections ## -def project_point(p): +def permute_point(p, permutation=None): + """ + Permutes the point according to the permutation keyword argument. The + default permutation is "012" which does not change the order of the + coordinate. To rotate counterclockwise, use "120" and to rotate clockwise + use "201".""" + if not permutation: + return p + return [p[int(permutation[i])] for i in range(len(p))] + +def project_point(p, permutation=None): """ Maps (x,y,z) coordinates to planar simplex. @@ -76,14 +87,18 @@ def project_point(p): ---------- p: 3-tuple The point to be projected p = (x, y, z) + coordinate_order, string, None, equivalent to "012" + The order of the coordinates, counterclockwise from the origin """ - a, b, c = p - x = b + c/2. - y = SQRT3OVER2 * c - return (x, y) + permuted = permute_point(p, permutation=permutation) + a = permuted[0] + b = permuted[1] + x = a + b/2. + y = SQRT3OVER2 * b + return numpy.array([x, y]) -def project_sequence(s): +def project_sequence(s, permutation=None): """ Projects a point or sequence of points using `project_point` to lists xs, ys for plotting with Matplotlib. @@ -98,5 +113,5 @@ def project_sequence(s): xs, ys: The sequence of projected points in coordinates as two lists """ - xs, ys = unzip(map(project_point, s)) + xs, ys = unzip([project_point(p, permutation=permutation) for p in s]) return xs, ys diff --git a/ternary/lines.py b/ternary/lines.py index 6042982..d420480 100644 --- a/ternary/lines.py +++ b/ternary/lines.py @@ -12,7 +12,7 @@ ## Lines ## -def line(ax, p1, p2, **kwargs): +def line(ax, p1, p2, permutation=None, **kwargs): """ Draws a line on `ax` from p1 to p2. @@ -28,8 +28,8 @@ def line(ax, p1, p2, **kwargs): Any kwargs to pass through to Matplotlib. """ - pp1 = project_point(p1) - pp2 = project_point(p2) + pp1 = project_point(p1, permutation=permutation) + pp2 = project_point(p2, permutation=permutation) ax.add_line(Line2D((pp1[0], pp2[0]), (pp1[1], pp2[1]), **kwargs)) def horizontal_line(ax, scale, i, **kwargs): @@ -164,13 +164,14 @@ def gridlines(ax, scale, multiple=None, horizontal_kwargs=None, left_kwargs=None horizontal_kwargs = merge_dicts(kwargs, horizontal_kwargs) left_kwargs = merge_dicts(kwargs, left_kwargs) right_kwargs = merge_dicts(kwargs, right_kwargs) + if not multiple: + multiple = 1. ## Draw grid-lines - if multiple: - # Parallel to horizontal axis - for i in arange(0, scale, multiple): - horizontal_line(ax, scale, i, **horizontal_kwargs) - # Parallel to left and right axes - for i in arange(0, scale + multiple, multiple): - left_parallel_line(ax, scale, i, **left_kwargs) - right_parallel_line(ax, scale, i, **right_kwargs) + # Parallel to horizontal axis + for i in arange(0, scale, multiple): + horizontal_line(ax, scale, i, **horizontal_kwargs) + # Parallel to left and right axes + for i in arange(0, scale + multiple, multiple): + left_parallel_line(ax, scale, i, **left_kwargs) + right_parallel_line(ax, scale, i, **right_kwargs) return ax diff --git a/ternary/plotting.py b/ternary/plotting.py index b7e4d53..1b9a3fb 100644 --- a/ternary/plotting.py +++ b/ternary/plotting.py @@ -49,7 +49,7 @@ def clear_matplotlib_ticks(ax=None, axis="both"): ## Curve Plotting ## -def plot(points, ax=None, **kwargs): +def plot(points, ax=None, permutation=None, **kwargs): """ Analogous to maplotlib.plot. Plots trajectory points where each point is a tuple (x,y,z) satisfying x + y + z = scale (not checked). The tuples are @@ -66,11 +66,12 @@ def plot(points, ax=None, **kwargs): """ if not ax: fig, ax = pyplot.subplots() - xs, ys = project_sequence(points) + xs, ys = project_sequence(points, permutation=permutation) ax.plot(xs, ys, **kwargs) return ax -def plot_colored_trajectory(points, cmap=None, ax=None, **kwargs): +def plot_colored_trajectory(points, cmap=None, ax=None, permutation=None, + **kwargs): """ Plots trajectories with changing color, simlar to `plot`. Trajectory points are tuples (x,y,z) satisfying x + y + z = scale (not checked). The tuples are @@ -90,7 +91,7 @@ def plot_colored_trajectory(points, cmap=None, ax=None, **kwargs): if not ax: fig, ax = pyplot.subplots() cmap = get_cmap(cmap) - xs, ys = project_sequence(points) + xs, ys = project_sequence(points, permutation=permutation) # We want to color each segment independently...which is annoying. segments = [] @@ -112,7 +113,7 @@ def plot_colored_trajectory(points, cmap=None, ax=None, **kwargs): return ax -def scatter(points, ax=None, **kwargs): +def scatter(points, ax=None, permutation=None, **kwargs): """Plots trajectory points where each point satisfies x + y + z = scale. First argument is a list or numpy array of tuples of length 3. Parameters @@ -128,7 +129,7 @@ def scatter(points, ax=None, **kwargs): """ if not ax: fig, ax = pyplot.subplots() - xs, ys = project_sequence(points) + xs, ys = project_sequence(points, permutation=permutation) ax.scatter(xs, ys, **kwargs) return ax @@ -220,7 +221,7 @@ def left_axis_label(ax, label, position=None, rotation=60, offset=0.08, **kwargs """ if not position: - position = (2./5 , -offset, 3./5) + position = (-offset, 3./5, 2./5) set_ternary_axis_label(ax, label, position, rotation, horizontalalignment="center", **kwargs) @@ -242,7 +243,7 @@ def right_axis_label(ax, label, position=None, rotation=-60, offset=0.08, **kwar """ if not position: - position = (0, 2./5 + offset, 3./5) + position = (2./5 + offset, 3./5, 0) set_ternary_axis_label(ax, label, position, rotation, horizontalalignment="center", **kwargs) @@ -264,6 +265,6 @@ def bottom_axis_label(ax, label, position=None, rotation=0, offset=0.04, **kwarg """ if not position: - position = (1./2, 1./2, offset) + position = (1./2, offset, 1./2) set_ternary_axis_label(ax, label, position, rotation, horizontalalignment="center", **kwargs) diff --git a/ternary/ternary_axes_subplot.py b/ternary/ternary_axes_subplot.py index f429553..2e72a70 100644 --- a/ternary/ternary_axes_subplot.py +++ b/ternary/ternary_axes_subplot.py @@ -7,9 +7,10 @@ import heatmapping import lines import plotting +from helpers import project_point -def figure(ax=None, scale=None): +def figure(ax=None, scale=None, permutation=None): """ Wraps a Matplotlib AxesSubplot or generates a new one. Emulates matplotlib's > figure, ax = pyplot.subplots() @@ -22,7 +23,7 @@ def figure(ax=None, scale=None): The scale factor of the ternary plot """ - ternary_ax = TernaryAxesSubplot(ax=ax, scale=scale) + ternary_ax = TernaryAxesSubplot(ax=ax, scale=scale, permutation=permutation) return ternary_ax.get_figure(), ternary_ax @@ -33,13 +34,14 @@ class TernaryAxesSubplot(object): to ease the use of ternary plotting functions. """ - def __init__(self, ax=None, scale=None): + def __init__(self, ax=None, scale=None, permutation=None): if not scale: scale = 1.0 if ax: self.ax = ax else: _, self.ax = pyplot.subplots() + self._permutation = None self.set_scale(scale=scale) self._boundary_scale = scale @@ -60,18 +62,29 @@ def get_scale(self): def get_axes(self): return self.ax + def annotate(self, text, position, **kwargs): + ax = self.get_axes() + p = project_point(position) + ax.annotate(text, (p[0], p[1]), **kwargs) + def scatter(self, points, **kwargs): ax = self.get_axes() - plot_ = plotting.scatter(points, ax=ax, **kwargs) + permutation = self._permutation + plot_ = plotting.scatter(points, ax=ax, permutation=permutation, + **kwargs) return plot_ def plot(self, points, **kwargs): ax = self.get_axes() - plotting.plot(points, ax=ax, **kwargs) + permutation = self._permutation + plotting.plot(points, ax=ax, permutation=permutation, + **kwargs) def plot_colored_trajectory(self, points, cmap=None, **kwargs): ax = self.get_axes() - plotting.plot_colored_trajectory(points, cmap=cmap, ax=ax, **kwargs) + permutation = self._permutation + plotting.plot_colored_trajectory(points, cmap=cmap, ax=ax, + permutation=permutation, **kwargs) def clear_matplotlib_ticks(self, axis="both"): ax = self.get_axes() @@ -91,13 +104,15 @@ def bottom_axis_label(self, label, position=None, **kwargs): def heatmap(self, data, scale=None, cmap=None, scientific=False, style='triangular', colorbar=True): + permutation = self._permutation if not scale: scale = self.get_scale() if style.lower()[0] == 'd': self._boundary_scale = scale + 1 ax = self.get_axes() heatmapping.heatmap(data, scale, cmap=cmap, style=style, ax=ax, - scientific=scientific, colorbar=colorbar) + scientific=scientific, colorbar=colorbar, + permutation=permutation) def heatmapf(self, func, scale=None, cmap=None, boundary=True, style='triangular', colorbar=True, scientific=True): @@ -105,10 +120,11 @@ def heatmapf(self, func, scale=None, cmap=None, boundary=True, scale = self.get_scale() if style.lower()[0] == 'd': self._boundary_scale = scale + 1 + permutation = self._permutation ax = self.get_axes() heatmapping.heatmapf(func, scale, cmap=cmap, style=style, boundary=boundary, ax=ax, scientific=scientific, - colorbar=colorbar) + colorbar=colorbar, permutation=permutation) def line(self, p1, p2, **kwargs): ax = self.get_axes()