Repository: wright-group/WrightTools

Issue:
Colormaps. Set_under and Set_bad
`default` set_under and set_bad should be different colors of grey. 

`signed` should have a set_bad of grey. 

It seems like we are already doing this...
https://github.com/wright-group/WrightTools/blob/development/WrightTools/artists/_colors.py#L351-L353


Discussion:
If we decide on the colors, I am happy to implement this. I am currently thinking the following are decent options. What are other people's opinions?

For `default`
- under = 'grey'
- bad = 'grey'
- over = 'm'

For 'signed'
- under = 'c'
- bad = 'grey'
- over = 'm'

Perhaps 'under' and 'bad' should be different shades of grey 
I am fine with them being different shades. Any grey fans with the hex code
that we should use for the different shades?

On Wed, Jun 13, 2018 at 10:40 AM, Kyle Sunden <notifications@github.com>
wrote:

> Perhaps 'under' and 'bad' should be different shades of grey
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/wright-group/WrightTools/issues/462#issuecomment-396984090>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/APD6ES-o4K0U9RaDZPOloQhvEefHm544ks5t8TJYgaJpZM4R17tD>
> .
>


Resolving patch:
diff --git a/WrightTools/artists/_colors.py b/WrightTools/artists/_colors.py
--- a/WrightTools/artists/_colors.py
+++ b/WrightTools/artists/_colors.py
@@ -281,111 +281,6 @@ def get_color_cycle(n, cmap="rainbow", rotations=3):
     return out
 
 
-def plot_colorbar(
-    *,
-    cax=None,
-    cmap="default",
-    ticks=None,
-    clim=None,
-    vlim=None,
-    label=None,
-    tick_fontsize=14,
-    label_fontsize=18,
-    decimals=None,
-    orientation="vertical",
-    ticklocation="auto"
-):
-    """Easily add a colormap to an axis.
-
-    Parameters
-    ----------
-    cax : matplotlib axis (optional)
-        The axis to plot the colorbar on. Finds the current axis if none is
-        given.
-    cmap : string or LinearSegmentedColormap (optional)
-        The colormap to fill the colorbar with. Strings map as keys to the
-        WrightTools colormaps dictionary. Default is `default`.
-    ticks : 1D array-like (optional)
-        Ticks. Default is None.
-    clim : two element list (optional)
-        The true limits of the colorbar, in the same units as ticks. If None,
-        streaches the colorbar over the limits of ticks. Default is None.
-    vlim : two element list-like (optional)
-        The limits of the displayed colorbar, in the same units as ticks. If
-        None, displays over clim. Default is None.
-    label : str (optional)
-        Label. Default is None.
-    tick_fontsize : number (optional)
-        Fontsize. Default is 14.
-    label_fontsize : number (optional)
-        Label fontsize. Default is 18.
-    decimals : integer (optional)
-        Number of decimals to appear in tick labels. Default is None (best guess).
-    orientation : {'vertical', 'horizontal'} (optional)
-        Colorbar orientation. Default is vertical.
-    ticklocation : {'auto', 'left', 'right', 'top', 'bottom'} (optional)
-        Tick location. Default is auto.
-
-    Returns
-    -------
-    matplotlib.colorbar.ColorbarBase object
-        The created colorbar.
-    """
-    # parse cax
-    if cax is None:
-        cax = plt.gca()
-    # parse cmap
-    if isinstance(cmap, str):
-        cmap = colormaps[cmap]
-    # parse ticks
-    if ticks is None:
-        ticks = np.linspace(0, 1, 11)
-    # parse clim
-    if clim is None:
-        clim = [min(ticks), max(ticks)]
-    # parse clim
-    if vlim is None:
-        vlim = clim
-    # parse format
-    if isinstance(decimals, int):
-        format = "%.{0}f".format(decimals)
-    else:
-        magnitude = int(np.log10(max(vlim) - min(vlim)) - 0.99)
-        if 1 > magnitude > -3:
-            format = "%.{0}f".format(-magnitude + 1)
-        elif magnitude in (1, 2, 3):
-            format = "%i"
-        else:
-            # scientific notation
-            def fmt(x, _):
-                return "%.1f" % (x / float(10 ** magnitude))
-
-            format = matplotlib.ticker.FuncFormatter(fmt)
-            magnitude_label = r"  $\mathsf{\times 10^{%d}}$" % magnitude
-            if label is None:
-                label = magnitude_label
-            else:
-                label = " ".join([label, magnitude_label])
-    # make cbar
-    norm = matplotlib.colors.Normalize(vmin=vlim[0], vmax=vlim[1])
-    cbar = matplotlib.colorbar.ColorbarBase(
-        ax=cax,
-        cmap=cmap,
-        norm=norm,
-        ticks=ticks,
-        orientation=orientation,
-        ticklocation=ticklocation,
-        format=format,
-    )
-    # coerce properties
-    cbar.set_clim(clim[0], clim[1])
-    cbar.ax.tick_params(labelsize=tick_fontsize)
-    if label:
-        cbar.set_label(label, fontsize=label_fontsize)
-    # finish
-    return cbar
-
-
 # --- color maps ----------------------------------------------------------------------------------
 
 
@@ -568,6 +463,12 @@ def fmt(x, _):
 # enforce grey as 'bad' value for colormaps
 for cmap in colormaps.values():
     cmap.set_bad([0.75] * 3, 1)
+# enforce under and over for default colormap
+colormaps["default"].set_under([0.50] * 3, 1)
+colormaps["default"].set_over("m")
+# enforce under and over for signed colormap
+colormaps["signed"].set_under("c")
+colormaps["signed"].set_over("m")
 
 
 # a nice set of line colors
diff --git a/WrightTools/artists/_helpers.py b/WrightTools/artists/_helpers.py
--- a/WrightTools/artists/_helpers.py
+++ b/WrightTools/artists/_helpers.py
@@ -307,7 +307,7 @@ def create_figure(
     wt.artists.plot_margins
         Plot lines to visualize the figure edges, margins, and centers. For
         debug and design purposes.
-    wt.artsits.subplots_adjust
+    wt.artists.subplots_adjust
         Enforce margins for figure generated elsewhere.
 
     """
@@ -561,6 +561,9 @@ def plot_colorbar(
     decimals=None,
     orientation="vertical",
     ticklocation="auto",
+    extend="neither",
+    extendfrac=None,
+    extendrect=False,
 ):
     """Easily add a colormap to an axis.
 
@@ -592,6 +595,24 @@ def plot_colorbar(
         Colorbar orientation. Default is vertical.
     ticklocation : {'auto', 'left', 'right', 'top', 'bottom'} (optional)
         Tick location. Default is auto.
+    extend : {‘neither’, ‘both’, ‘min’, ‘max’} (optional)
+        If not ‘neither’, make pointed end(s) for out-of- range values.
+        These are set for a given colormap using the colormap set_under and set_over methods.
+    extendfrac :	{None, ‘auto’, length, lengths} (optional)
+        If set to None, both the minimum and maximum triangular colorbar extensions
+        have a length of 5% of the interior colorbar length (this is the default setting).
+        If set to ‘auto’, makes the triangular colorbar extensions the same lengths
+        as the interior boxes
+        (when spacing is set to ‘uniform’) or the same lengths as the respective adjacent
+        interior boxes (when spacing is set to ‘proportional’).
+        If a scalar, indicates the length of both the minimum and maximum triangular
+        colorbar extensions as a fraction of the interior colorbar length.
+        A two-element sequence of fractions may also be given, indicating the lengths
+        of the minimum and maximum colorbar extensions respectively as a fraction
+        of the interior colorbar length.
+    extendrect :	bool (optional)
+        If False the minimum and maximum colorbar extensions will be triangular (the default).
+        If True the extensions will be rectangular.
 
     Returns
     -------
@@ -643,6 +664,9 @@ def fmt(x, _):
         orientation=orientation,
         ticklocation=ticklocation,
         format=format,
+        extend=extend,
+        extendfrac=extendfrac,
+        extendrect=extendrect,
     )
     # coerce properties
     cbar.set_clim(clim[0], clim[1])
diff --git a/WrightTools/data/_data.py b/WrightTools/data/_data.py
--- a/WrightTools/data/_data.py
+++ b/WrightTools/data/_data.py
@@ -62,9 +62,7 @@ def __init__(self, *args, **kwargs):
 
     def __repr__(self):
         return "<WrightTools.Data '{0}' {1} at {2}>".format(
-            self.natural_name,
-            str(self.axis_names),
-            "::".join([self.filepath, self.name]),
+            self.natural_name, str(self.axis_names), "::".join([self.filepath, self.name])
         )
 
     @property
@@ -222,9 +220,7 @@ def print_leaves(prefix, lis, vline=True):
         else:
             # axes
             s = "axes: "
-            s += ", ".join(
-                ["{0} ({1})".format(a.expression, a.units) for a in self.axes]
-            )
+            s += ", ".join(["{0} ({1})".format(a.expression, a.units) for a in self.axes])
             print(prefix + "├── " + s)
             # channels
             s = "channels: "
@@ -342,12 +338,8 @@ def chop(self, *args, at={}, parent=None, verbose=True):
                 kwargs["signed"] = c.signed
                 kwargs.update(c.attrs)
                 data.create_channel(**kwargs)
-            new_axes = [
-                a.expression for a in kept_axes if a.expression not in at.keys()
-            ]
-            new_axis_units = [
-                a.units for a in kept_axes if a.expression not in at.keys()
-            ]
+            new_axes = [a.expression for a in kept_axes if a.expression not in at.keys()]
+            new_axis_units = [a.units for a in kept_axes if a.expression not in at.keys()]
             data.transform(*new_axes)
             for j, units in enumerate(new_axis_units):
                 data.axes[j].convert(units)
@@ -398,9 +390,7 @@ def collapse(self, axis, method="integrate"):
         # collapse --------------------------------------------------------------------------------
         for method, channel in zip(methods, self.channels):
             if method in ["int", "integrate"]:
-                channel[:] = np.trapz(
-                    y=channel[:], x=self._axes[axis_index][:], axis=axis_index
-                )
+                channel[:] = np.trapz(y=channel[:], x=self._axes[axis_index][:], axis=axis_index)
             elif method == "sum":
                 channel[:] = np.nansum(channel[:], axis=axis_index)
             elif method in ["max", "maximum"]:
@@ -497,9 +487,7 @@ def create_channel(self, name, values=None, shape=None, units=None, **kwargs):
         dataset_id = self.require_dataset(name=name, chunks=True, **require_kwargs).id
         channel = Channel(self, dataset_id, units=units, **kwargs)
         # finish
-        self.attrs["channel_names"] = np.append(
-            self.attrs["channel_names"], name.encode()
-        )
+        self.attrs["channel_names"] = np.append(self.attrs["channel_names"], name.encode())
         return channel
 
     def create_variable(self, name, values=None, shape=None, units=None, **kwargs):
@@ -538,9 +526,7 @@ def create_variable(self, name, values=None, shape=None, units=None, **kwargs):
         variable = Variable(self, id, units=units, **kwargs)
         # finish
         self.variables.append(variable)
-        self.attrs["variable_names"] = np.append(
-            self.attrs["variable_names"], name.encode()
-        )
+        self.attrs["variable_names"] = np.append(self.attrs["variable_names"], name.encode())
         return variable
 
     def flush(self):
@@ -696,19 +682,10 @@ def level(self, channel, axis, npts, *, verbose=True):
         # finish
         channel._null = 0
         if verbose:
-            print(
-                "channel {0} leveled along axis {1}".format(channel.natural_name, axis)
-            )
+            print("channel {0} leveled along axis {1}".format(channel.natural_name, axis))
 
     def map_variable(
-        self,
-        variable,
-        points,
-        input_units="same",
-        *,
-        name=None,
-        parent=None,
-        verbose=True
+        self, variable, points, input_units="same", *, name=None, parent=None, verbose=True
     ):
         """Map points of an axis to new points using linear interpolation.
 
@@ -776,9 +753,7 @@ def map_variable(
         if self.ndim == 1:
 
             def interpolate(dataset, points):
-                function = scipy.interpolate.interp1d(
-                    variable[:], dataset[:], bounds_error=False
-                )
+                function = scipy.interpolate.interp1d(variable[:], dataset[:], bounds_error=False)
                 return function(points)
 
         else:
@@ -787,9 +762,7 @@ def interpolate(dataset, points):
 
             def interpolate(dataset, points):
                 values = dataset.full.flatten()
-                function = scipy.interpolate.LinearNDInterpolator(
-                    pts, values, rescale=True
-                )
+                function = scipy.interpolate.LinearNDInterpolator(pts, values, rescale=True)
                 new = function(out_pts)
                 new.shape = out.shape
                 return new
@@ -905,9 +878,7 @@ def offset(
         elif isinstance(offset_axis, str):
             offset_axis_index = self.axis_names.index(offset_axis)
         else:
-            raise TypeError(
-                "offset_axis: expected {int, str}, got %s" % type(offset_axis)
-            )
+            raise TypeError("offset_axis: expected {int, str}, got %s" % type(offset_axis))
         # new points
         new_points = [a[:] for a in self._axes]
         old_offset_axis_points = self._axes[offset_axis_index][:]
@@ -943,9 +914,7 @@ def offset(
             # grid data
             tup = tuple([arr[i] for i in range(len(arr) - 1)])
             # note that rescale is crucial in this operation
-            out = griddata(
-                tup, arr[-1], new_xi, method=method, fill_value=np.nan, rescale=True
-            )
+            out = griddata(tup, arr[-1], new_xi, method=method, fill_value=np.nan, rescale=True)
             channel[:] = out
         self._axes[offset_axis_index][:] = new_offset_axis_points
         # transpose out
@@ -1180,8 +1149,7 @@ def smooth(self, factors, channel=None, verbose=True):
                 transpose_order = range(len(values.shape))
                 # replace axis_index with zero
                 transpose_order = [
-                    len(values.shape) - 1 if i == axis_index else i
-                    for i in transpose_order
+                    len(values.shape) - 1 if i == axis_index else i for i in transpose_order
                 ]
                 transpose_order[len(values.shape) - 1] = axis_index
                 values = values.transpose(transpose_order)
@@ -1192,9 +1160,7 @@ def smooth(self, factors, channel=None, verbose=True):
                 for index in np.ndindex(values[..., 0].shape):
                     current_slice = values[index]
                     temp_slice = np.pad(current_slice, int(factor), mode=str("edge"))
-                    values[index] = np.convolve(
-                        temp_slice, w / w.sum(), mode=str("valid")
-                    )
+                    values[index] = np.convolve(temp_slice, w / w.sum(), mode=str("valid"))
                 # transpose out
                 values = values.transpose(transpose_order)
             # return array to channel object
@@ -1202,15 +1168,7 @@ def smooth(self, factors, channel=None, verbose=True):
         if verbose:
             print("smoothed data")
 
-    def split(
-        self,
-        axis,
-        positions,
-        units="same",
-        direction="below",
-        parent=None,
-        verbose=True,
-    ):
+    def split(self, axis, positions, units="same", direction="below", parent=None, verbose=True):
         """
         Split the data object along a given axis, in units.
 
@@ -1279,9 +1237,7 @@ def split(
         if flip:
             indicies = [i - 1 for i in indicies]
         # process ---------------------------------------------------------------------------------
-        outs = wt_collection.Collection(
-            name="split", parent=parent, edit_local=parent is not None
-        )
+        outs = wt_collection.Collection(name="split", parent=parent, edit_local=parent is not None)
         start = 0
         stop = 0
         for i in range(len(indicies) + 1):
@@ -1311,9 +1267,7 @@ def split(
                     attrs = dict(ch.attrs)
                     attrs.pop("name", None)
                     attrs.pop("units", None)
-                    new_data.create_channel(
-                        ch.natural_name, ch[:][slc], ch.units, **attrs
-                    )
+                    new_data.create_channel(ch.natural_name, ch[:][slc], ch.units, **attrs)
             else:
                 attrs = dict(self.attrs)
                 attrs.pop("name", None)
@@ -1346,11 +1300,7 @@ def split(
                 if new_data is None:
                     print("  {0} : None".format(i))
                 elif len(new_data.shape) < len(self.shape):
-                    print(
-                        "  {0} : {1} {2}(constant)".format(
-                            i, axis.natural_name, axis.units
-                        )
-                    )
+                    print("  {0} : {1} {2}(constant)".format(i, axis.natural_name, axis.units))
                 else:
                     new_axis = new_data.axes[axis_index]
                     print(
@@ -1412,9 +1362,7 @@ def zoom(self, factor, order=1, verbose=True):
             axis[:] = scipy.ndimage.interpolation.zoom(axis[:], factor, order=order)
         # channels
         for channel in self.channels:
-            channel[:] = scipy.ndimage.interpolation.zoom(
-                channel[:], factor, order=order
-            )
+            channel[:] = scipy.ndimage.interpolation.zoom(channel[:], factor, order=order)
         # return
         if verbose:
             print("data zoomed to new shape:", self.shape)


Test patch: