def legend(*args, **kwargs): """ @param args: @type args: @param kwargs: Any keyword arguments to matplotlib's plt.legend() Optional 'facecolor' keyword to change the facecolor of the legend @type kwargs: @return: @rtype: """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) facecolor = kwargs.pop("facecolor", light_grey) kwargs.setdefault("frameon", True) kwargs.setdefault("scatterpoints", True) legend = ax.legend(*args, **kwargs) try: rect = legend.get_frame() rect.set_facecolor(facecolor) rect.set_linewidth(0.0) # Change the label colors in the legend to almost black # Change the legend label colors to almost black, too texts = legend.texts for t in texts: t.set_color(almost_black) except AttributeError: # There are no labled objects pass return legend
def scatter(*args, **kwargs): """ This will plot a scatterplot of x and y, iterating over the ColorBrewer "Set2" color cycle unless a color is specified. The symbols produced are empty circles, with the outline in the color specified by either 'color' or 'edgecolor'. If you want to fill the circle, specify 'facecolor'. Besides the matplotlib scatter(), will also take the parameter @param show_ticks: Whether or not to show the x and y axis ticks """ # Force 'color' to indicate the edge color, so the middle of the # scatter patches are empty. Can specify ax, args, kwargs = utils.maybe_get_ax(*args, **kwargs) if 'color' not in kwargs: # Assume that color means the edge color. You can assign the color_cycle = cycle(mpl.rcParams['axes.color_cycle']) kwargs['color'] = next(color_cycle) kwargs.setdefault('edgecolor', almost_black) kwargs.setdefault('alpha', 0.5) lw = utils.maybe_get_linewidth(**kwargs) kwargs['lw'] = lw show_ticks = kwargs.pop('show_ticks', False) scatterpoints = ax.scatter(*args, **kwargs) utils.remove_chartjunk(ax, ['top', 'right'], show_ticks=show_ticks) return ax
def hist(*args, **kwargs): """ Plots a histogram of the provided data. Can provide optional argument "grid='x'" or "grid='y'" to draw a white grid over the histogram. Almost like "erasing" some of the plot, but it adds more information! """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) color_cycle = ax._get_lines.color_cycle color = next(color_cycle) # Reassign the default colors to Set2 by Colorbrewer if 'color' not in kwargs: kwargs['color'] = color if 'facecolor' not in kwargs: kwargs['facecolor'] = color if 'edgecolor' not in kwargs: kwargs['edgecolor'] = 'white' show_ticks = kwargs.pop('show_ticks', False) # If no grid specified, don't draw one. grid = kwargs.pop('grid', None) # print 'hist kwargs', kwargs patches = ax.hist(*args, **kwargs) remove_chartjunk(ax, ['top', 'right'], grid=grid, show_ticks=show_ticks) return ax
def scatter(*args, **kwargs): """ This will plot a scatterplot of x and y, iterating over the ColorBrewer "Set2" color cycle unless a color is specified. The symbols produced are empty circles, with the outline in the color specified by either 'color' or 'edgecolor'. If you want to fill the circle, specify 'facecolor'. Besides the matplotlib scatter(), will also take the parameter @param show_ticks: Whether or not to show the x and y axis ticks """ # Force 'color' to indicate the edge color, so the middle of the # scatter patches are empty. Can specify ax, args, kwargs = utils.maybe_get_ax(*args, **kwargs) if 'color' not in kwargs: # Assume that color means the edge color. You can assign the color_cycle = ax._get_lines.color_cycle kwargs['color'] = next(color_cycle) if 'edgecolor' not in kwargs: kwargs['edgecolor'] = almost_black if 'alpha' not in kwargs: kwargs['alpha'] = 0.5 lw = utils.maybe_get_linewidth(**kwargs) kwargs['lw'] = lw show_ticks = kwargs.pop('show_ticks', False) scatterpoints = ax.scatter(*args, **kwargs) utils.remove_chartjunk(ax, ['top', 'right'], show_ticks=show_ticks) return ax
def hist(*args, **kwargs): """ Plots a histogram of the provided data. Can provide optional argument "grid='x'" or "grid='y'" to draw a white grid over the histogram. Almost like "erasing" some of the plot, but it adds more information! """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) color_cycle = ax._get_lines.color_cycle # Reassign the default colors to Set2 by Colorbrewer if iterable(args[0]): if isinstance(args[0], list): ncolors = len(args[0]) else: if len(args[0].shape) == 2: ncolors = args[0].shape[1] else: ncolors = 1 kwargs.setdefault('color', [next(color_cycle) for _ in range(ncolors)]) else: kwargs.setdefault('color', next(color_cycle)) kwargs.setdefault('edgecolor', 'white') show_ticks = kwargs.pop('show_ticks', False) # If no grid specified, don't draw one. grid = kwargs.pop('grid', None) # print 'hist kwargs', kwargs patches = ax.hist(*args, **kwargs) remove_chartjunk(ax, ['top', 'right'], grid=grid, show_ticks=show_ticks) return ax
def plot(*args, **kwargs): ax, args, kwargs = maybe_get_ax(*args, **kwargs) show_ticks = kwargs.pop('show_ticks', False) lines = ax.plot(*args, **kwargs) remove_chartjunk(ax, ['top', 'right'], show_ticks=show_ticks) return lines
def legend(*args, **kwargs): """ @param args: @type args: @param kwargs: Any keyword arguments to matplotlib's plt.legend() Optional 'facecolor' keyword to change the facecolor of the legend @type kwargs: @return: @rtype: """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) facecolor = kwargs.pop('facecolor', light_grey) if 'frameon' not in kwargs: kwargs['frameon'] = True if 'scatterpoints' not in kwargs: kwargs['scatterpoints'] = True legend = ax.legend(**kwargs) try: rect = legend.get_frame() rect.set_facecolor(facecolor) rect.set_linewidth(0.0) # Change the label colors in the legend to almost black # Change the legend label colors to almost black, too texts = legend.texts for t in texts: t.set_color(almost_black) except AttributeError: # There are no labled objects pass return legend
def stackplot(*args, **kwargs): ax, args, kwargs = maybe_get_ax(*args, **kwargs) lw = maybe_get_linewidth(**kwargs) kwargs['linewidths'] = lw kwargs.setdefault('edgecolor', almost_black) show_ticks = kwargs.pop('show_ticks', False) lines = ax.stackplot(*args, **kwargs) remove_chartjunk(ax, ['top', 'right'], show_ticks=show_ticks) return lines
def plot(*args, **kwargs): ax, args, kwargs = maybe_get_ax(*args, **kwargs) if 'color' not in kwargs: # if no color is specified, cycle over the ones in this axis color_cycle = ax._get_lines.color_cycle kwargs['color'] = next(color_cycle) if 'linewidth' not in kwargs: kwargs['linewidth'] = 0.75 show_ticks = kwargs.pop('show_ticks', False) lines = ax.plot(*args, **kwargs) remove_chartjunk(ax, ['top', 'right'], show_ticks=show_ticks) return lines
def boxplot(*args, **kwargs): """ Create a box-and-whisker plot showing the mean, 25th percentile, and 75th percentile. The difference from matplotlib is only the left axis line is shown, and ticklabels labeling each category of data can be added. @param ax: @param x: @param kwargs: Besides xticklabels, which is a prettyplotlib-specific argument which will label each individual boxplot, any argument for matplotlib.pyplot.boxplot will be accepted: http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.boxplot @return: """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) # If no ticklabels are specified, don't draw any xticklabels = kwargs.pop('xticklabels', None) fontsize = kwargs.pop('fontsize', 10) kwargs.setdefault('widths', 0.15) bp = ax.boxplot(*args, **kwargs) if xticklabels: ax.xaxis.set_ticklabels(xticklabels, fontsize=fontsize) show_caps = kwargs.pop('show_caps', True) show_ticks = kwargs.pop('show_ticks', False) remove_chartjunk(ax, ['top', 'right', 'bottom'], show_ticks=show_ticks) linewidth = 0.75 blue = colors.set1[1] red = colors.set1[0] plt.setp(bp['boxes'], color=blue, linewidth=linewidth) plt.setp(bp['medians'], color=red) plt.setp(bp['whiskers'], color=blue, linestyle='solid', linewidth=linewidth) plt.setp(bp['fliers'], color=blue) if show_caps: plt.setp(bp['caps'], color=blue, linewidth=linewidth) else: plt.setp(bp['caps'], color='none') ax.spines['left']._linewidth = 0.5 return ax
def fill_betweenx(*args, **kwargs): ax, args, kwargs = maybe_get_ax(*args, **kwargs) lw = maybe_get_linewidth(**kwargs) kwargs['linewidths'] = lw if 'color' not in kwargs: # if no color is specified, cycle over the ones in this axis color_cycle = cycle(mpl.rcParams['axes.color_cycle']) kwargs['color'] = next(color_cycle) kwargs.setdefault('edgecolor', almost_black) show_ticks = kwargs.pop('show_ticks', False) lines = ax.fill_betweenx(*args, **kwargs) remove_chartjunk(ax, ['top', 'right'], show_ticks=show_ticks) return ax
def fill_between(*args, **kwargs): ax, args, kwargs = maybe_get_ax(*args, **kwargs) lw = maybe_get_linewidth(**kwargs) kwargs['linewidths'] = lw if 'color' not in kwargs: # if no color is specified, cycle over the ones in this axis color_cycle = ax._get_lines.color_cycle kwargs['color'] = next(color_cycle) kwargs.setdefault('edgecolor', almost_black) show_ticks = kwargs.pop('show_ticks', False) lines = ax.fill_between(*args, **kwargs) remove_chartjunk(ax, ['top', 'right'], show_ticks=show_ticks) return lines
def boxplot(*args, **kwargs): """ Create a box-and-whisker plot showing the mean, 25th percentile, and 75th percentile. The difference from matplotlib is only the left axis line is shown, and ticklabels labeling each category of data can be added. @param ax: @param x: @param kwargs: Besides xticklabels, which is a prettyplotlib-specific argument which will label each individual boxplot, any argument for matplotlib.pyplot.boxplot will be accepted: http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.boxplot @return: """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) # If no ticklabels are specified, don't draw any xticklabels = kwargs.pop('xticklabels', None) fontsize = kwargs.pop('fontsize', 10) if 'widths' not in kwargs: kwargs['widths'] = 0.15 bp = ax.boxplot(*args, **kwargs) if xticklabels: ax.xaxis.set_ticklabels(xticklabels, fontsize=fontsize) show_caps = kwargs.pop('show_caps', True) show_ticks = kwargs.pop('show_ticks', False) remove_chartjunk(ax, ['top', 'right', 'bottom'], show_ticks=show_ticks) linewidth = 0.75 blue = colors.set1[1] red = colors.set1[0] plt.setp(bp['boxes'], color=blue, linewidth=linewidth) plt.setp(bp['medians'], color=red) plt.setp(bp['whiskers'], color=blue, linestyle='solid', linewidth=linewidth) plt.setp(bp['fliers'], color=blue) if show_caps: plt.setp(bp['caps'], color=blue, linewidth=linewidth) else: plt.setp(bp['caps'], color='none') ax.spines['left']._linewidth = 0.5 return ax
def fill_between(*args, **kwargs): ax, args, kwargs = maybe_get_ax(*args, **kwargs) lw = maybe_get_linewidth(**kwargs) kwargs["linewidths"] = lw if "color" not in kwargs: # if no color is specified, cycle over the ones in this axis color_cycle = ax._get_lines.color_cycle kwargs["color"] = next(color_cycle) if "edgecolor" not in kwargs: kwargs["edgecolor"] = almost_black show_ticks = kwargs.pop("show_ticks", False) lines = ax.fill_between(*args, **kwargs) remove_chartjunk(ax, ["top", "right"], show_ticks=show_ticks) return ax
def boxplot(*args, **kwargs): """ Create a box-and-whisker plot showing the mean, 25th percentile, and 75th percentile. The difference from matplotlib is only the left axis line is shown, and ticklabels labeling each category of data can be added. @param ax: @param x: @param kwargs: Besides xticklabels, which is a prettyplotlib-specific argument which will label each individual boxplot, any argument for matplotlib.pyplot.boxplot will be accepted: http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.boxplot @return: """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) # If no ticklabels are specified, don't draw any xticklabels = kwargs.pop("xticklabels", None) fontsize = kwargs.pop("fontsize", 10) kwargs.setdefault("widths", 0.15) bp = ax.boxplot(*args, **kwargs) if xticklabels: ax.xaxis.set_ticklabels(xticklabels, fontsize=fontsize) show_caps = kwargs.pop("show_caps", True) show_ticks = kwargs.pop("show_ticks", False) remove_chartjunk(ax, ["top", "right", "bottom"], show_ticks=show_ticks) linewidth = 0.75 blue = colors.set1[1] red = colors.set1[0] plt.setp(bp["boxes"], color=blue, linewidth=linewidth) plt.setp(bp["medians"], color=red) plt.setp(bp["whiskers"], color=blue, linestyle="solid", linewidth=linewidth) plt.setp(bp["fliers"], color=blue) if show_caps: plt.setp(bp["caps"], color=blue, linewidth=linewidth) else: plt.setp(bp["caps"], color="none") ax.spines["left"]._linewidth = 0.5 return bp
def eventplot(*args, **kwargs): ax, args, kwargs = maybe_get_ax(*args, **kwargs) show_ticks = kwargs.pop('show_ticks', False) alpha = kwargs.pop('alpha', 1.0) if len(args) > 0: positions = args[0] else: positions = kwargs['positions'] if any(iterable(p) for p in positions): size = len(positions) else: size = 1 kwargs.setdefault('colors', [c + (alpha,) for c in set2[:size]]) event_collections = ax.eventplot(*args, **kwargs) remove_chartjunk(ax, ['top', 'right'], show_ticks=show_ticks) return event_collections
def eventplot(*args, **kwargs): ax, args, kwargs = maybe_get_ax(*args, **kwargs) show_ticks = kwargs.pop('show_ticks', False) alpha = kwargs.pop('alpha', 1.0) if len(args) > 0: positions = args[0] else: positions = kwargs['positions'] if any(iterable(p) for p in positions): size = len(positions) else: size = 1 kwargs.setdefault('colors', [c + (alpha, ) for c in set2[:size]]) event_collections = ax.eventplot(*args, **kwargs) remove_chartjunk(ax, ['top', 'right'], show_ticks=show_ticks) return event_collections
def beeswarm(*args, **kwargs): """ Create a R-like beeswarm plot showing the mean and datapoints. The difference from matplotlib is only the left axis line is shown, and ticklabels labeling each category of data can be added. @param ax: @param x: @param kwargs: Besides xticklabels, which is a prettyplotlib-specific argument which will label each individual beeswarm, many arguments for matplotlib.pyplot.boxplot will be accepted: http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.boxplot Additional arguments include: *median_color* : (default gray) The color of median lines *median_width* : (default 2) Median line width *colors* : (default None) Colors to use when painting a dataseries, for example list1 = [1,2,3] list2 = [5,6,7] ppl.beeswarm([list1, list2], colors=["red", "blue"], xticklabels=["data1", "data2"]) @return: """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) # If no ticklabels are specified, don't draw any xticklabels = kwargs.pop('xticklabels', None) colors = kwargs.pop('colors', None) fontsize = kwargs.pop('fontsize', 10) gray = _colors.set1[8] red = _colors.set1[0] blue = kwargs.pop('color', _colors.set1[1]) kwargs.setdefault('widths', 0.25) kwargs.setdefault('sym', "o") bp = _beeswarm(ax, *args, **kwargs) kwargs.setdefault("median_color", gray) kwargs.setdefault("median_linewidth", 2) if xticklabels: ax.xaxis.set_ticklabels(xticklabels, fontsize=fontsize) show_caps = kwargs.pop('show_caps', True) show_ticks = kwargs.pop('show_ticks', False) remove_chartjunk(ax, ['top', 'right', 'bottom'], show_ticks=show_ticks) linewidth = 0.75 plt.setp(bp['boxes'], color=blue, linewidth=linewidth) plt.setp(bp['medians'], color=kwargs.pop("median_color"), linewidth=kwargs.pop("median_linewidth")) #plt.setp(bp['whiskers'], color=blue, linestyle='solid', # linewidth=linewidth) for color, flier in zip(colors, bp['fliers']): plt.setp(flier, color=color) #if show_caps: # plt.setp(bp['caps'], color=blue, linewidth=linewidth) #else: # plt.setp(bp['caps'], color='none') ax.spines['left']._linewidth = 0.5 return bp
def bar(*args, **kwargs): """ Creates a bar plot, with white outlines and a fill color that defaults to the first teal-ish green in ColorBrewer's Set2. Optionally accepts grid='y' or grid='x' to draw a white grid over the bars, to show the scale. Almost like "erasing" some of the plot, but it adds more information! Can also add an annotation of the height of the barplots directly onto the bars with the `annotate` parameter, which can either be True, which will annotate the values, or a list of strings, which will annotate with the supplied strings. Can support stacked bars with the value of each stack shown on the stack (Added by Salil Banerjee) @param ax: matplotlib.axes instance @param left: Vector of values of where to put the left side of the bar @param height: Vector of values of the bar heights @param kwargs: Besides xticklabels, which is a prettyplotlib-specific argument, any additional arguments to matplotlib.bar(): http://matplotlib .org/api/axes_api.html#matplotlib.axes.Axes.bar is accepted. """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) kwargs.setdefault('color', set2[0]) kwargs.setdefault('edgecolor', 'white') middle = 0.4 if 'width' not in kwargs else kwargs['width']/2.0 # Check if data contains stacks stacked = kwargs.pop('stacked',False) # Check if stack text should be included stack_text = kwargs.pop('stack_text',False) # Get legend if available legend = kwargs.pop('legend',False) left = args[0] height = np.array(args[1]) # Label each individual bar, if xticklabels is provided xtickabels = kwargs.pop('xticklabels', None) # left+0.4 is the center of the bar xticks = np.array(left) + middle # Whether or not to annotate each bar with the height value annotate = kwargs.pop('annotate', False) show_ticks = kwargs.pop('show_ticks', False) # If no grid specified, don't draw one. grid = kwargs.pop('grid', None) cmap = kwargs.pop('cmap', False) if cmap: kwargs['edgecolor'] = almost_black if not stacked: kwargs['color'] = getcolors(cmap, height, 0) # Check if stacked and plot data accordingly color = kwargs.get('color', None) if stacked: num_stacks, num_data = height.shape bottom = np.zeros(num_data) for i in np.arange(num_stacks): lst = list(args) lst[1] = height[i] args = tuple(lst) # make sure number of user specified colors equals to the stacks if not color or len(color) != num_stacks: if cmap: kwargs['color'] = getcolors(cmap, height[i], i) else: kwargs['color'] = set2[i] else: kwargs['color'] = color[i] kwargs['bottom'] = bottom rectangles = ax.bar(*args, **kwargs) bottom += height[i] else: rectangles = ax.bar(*args, **kwargs) # add legend if isinstance(legend, collections.Iterable): ax.legend(legend,loc='upper center',bbox_to_anchor=(0.5,1.11), ncol=5) # add whitespace padding on left xmin, xmax = ax.get_xlim() xmin -= 0.2 if stacked: xmax = num_data ax.set_xlim(xmin, xmax) # If the user is only plotting one bar, make it an iterable if not isinstance(height, collections.Iterable): height = [height] # If there are negative counts, remove the bottom axes # and add a line at y=0 if any(h < 0 for h in height.tolist()): axes_to_remove = ['top', 'right', 'bottom'] ax.hlines(y=0, xmin=xmin, xmax=xmax, linewidths=0.75) else: axes_to_remove = ['top', 'right'] # Remove excess axes remove_chartjunk(ax, axes_to_remove, grid=grid, show_ticks=show_ticks) if stacked: data = height height = height.sum(axis=0) # Add the xticklabels if they are there if xtickabels is not None: ax.set_xticks(xticks) ax.set_xticklabels(xtickabels) if annotate or isinstance(annotate, collections.Iterable): annotate_yrange_factor = 0.025 ymin, ymax = ax.get_ylim() yrange = ymax - ymin # Reset ymax and ymin so there's enough room to see the annotation of # the top-most if ymax > 0: ymax += yrange * 0.1 if ymin < 0: ymin -= yrange * 0.1 ax.set_ylim(ymin, ymax) yrange = ymax - ymin offset_ = yrange * annotate_yrange_factor if isinstance(annotate, collections.Iterable): annotations = map(str, annotate) else: annotations = ['%.3f' % h if type(h) is np.float_ else str(h) for h in height] for x, h, annotation in zip(xticks, height, annotations): # Adjust the offset to account for negative bars offset = offset_ if h >= 0 else -1 * offset_ verticalalignment = 'bottom' if h >= 0 else 'top' # Finally, add the text to the axes ax.annotate(annotation, (x, h + offset), verticalalignment=verticalalignment, horizontalalignment='center', color=almost_black) # Text for each block of stack # This was partially inspired by the following article by Tableau software # http://www.tableausoftware.com/about/blog/2014/1/new-whitepaper-survey-data-less-ugly-more-understandable-27812 if stack_text: bottom = np.zeros(num_data) max_h = max(height) for i in np.arange(num_stacks): for x, d, b in zip(xticks, data[i], bottom): if (d*100.0/max_h) > 4.0: ax.text(x,b+d/2.0,d, ha='center', va='center', color=almost_black) bottom += data[i] return rectangles
def barh(*args, **kwargs): """ Creates a bar plot, with white outlines and a fill color that defaults to the first teal-ish green in ColorBrewer's Set2. Optionally accepts grid='y' or grid='x' to draw a white grid over the bars, to show the scale. Almost like "erasing" some of the plot, but it adds more information! Can also add an annotation of the width of the barplots directly onto the bars with the `annotate` parameter, which can either be True, which will annotate the values, or a list of strings, which will annotate with the supplied strings. Can support stacked bars with the value of each stack shown on the stack (Added by Salil Banerjee) @param ax: matplotlib.axes instance @param top: Vector of values of where to put the top side of the bar @param width: Vector of values of the bar widths @param ytickabels: Vector of labels of the bar widths @param kwargs: Any additional arguments to matplotlib.bar() """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) if 'color' not in kwargs: kwargs['color'] = set2[0] if 'edgecolor' not in kwargs: kwargs['edgecolor'] = 'white' if 'width' in kwargs: # Find the middle of the bar middle = kwargs['width'] / 2.0 else: middle = 0.4 # Check if data contains stacks stacked = kwargs.pop('stacked', False) # Check if stack text should be included stack_text = kwargs.pop('stack_text', False) # Get legend if available legend = kwargs.pop('legend', False) top = args[0] width = np.array(args[1]) # Label each individual bar, if xticklabels is provided ytickabels = kwargs.pop('yticklabels', None) # left+0.4 is the center of the bar yticks = np.array(top) + middle # Whether or not to annotate each bar with the width value annotate = kwargs.pop('annotate', False) # If no grid specified, don't draw one. grid = kwargs.pop('grid', None) # Check if stacked and plot data accordingly if stacked: num_stacks, num_data = width.shape left = np.zeros(num_data) for i in np.arange(num_stacks): lst = list(args) lst[1] = width[i] args = tuple(lst) kwargs['color'] = set2[i] kwargs['left'] = left rectangles = ax.barh(*args, **kwargs) left += width[i] else: rectangles = ax.barh(*args, **kwargs) # add legend if isinstance(legend, collections.Iterable): ax.legend(legend, loc='upper center', bbox_to_anchor=(0.5, 1.11), ncol=5) # add whitespace padding on left ymin, ymax = ax.get_ylim() ymin -= 0.2 if stacked: ymax = num_data ax.set_ylim(ymin, ymax) # If there are negative counts, remove the bottom axes # and add a line at y=0 if any(w < 0 for w in width.tolist()): axes_to_remove = ['top', 'right', 'bottom'] ax.vlines(x=0, ymin=ymin, ymax=ymax, linewidths=0.75) #ax.hlines(y=0, xmin=xmin, xmax=xmax, # linewidths=0.75) else: axes_to_remove = ['top', 'right'] #Remove excess axes remove_chartjunk(ax, axes_to_remove, grid=grid) if stacked: data = width width = width.sum(axis=0) # Add the yticklabels if they are there if ytickabels is not None: ax.set_yticks(yticks) ax.set_yticklabels(ytickabels) if annotate or isinstance(annotate, collections.Iterable): annotate_xrange_factor = 0.050 xmin, xmax = ax.get_xlim() xrange = xmax - xmin # Reset ymax and ymin so there's enough room to see the annotation of # the top-most if xmax > 0: xmax += xrange * 0.1 if xmin < 0: xmin -= xrange * 0.1 ax.set_xlim(xmin, xmax) xrange = xmax - xmin offset_ = xrange * annotate_xrange_factor if isinstance(annotate, collections.Iterable): annotations = map(str, annotate) else: annotations = [ '%.3f' % w if type(w) is np.float_ else str(w) for w in width ] for y, w, annotation in zip(yticks, width, annotations): # Adjust the offset to account for negative bars offset = offset_ if w >= 0 else -1 * offset_ # Finally, add the text to the axes ax.annotate(annotation, (w + offset, y), verticalalignment='center', horizontalalignment='center', color=almost_black) # Text for each block of stack # This was partially inspired by the following article by Tableau software # http://www.tableausoftware.com/about/blog/2014/1/new-whitepaper-survey-data-less-ugly-more-understandable-27812 if stack_text: left = np.zeros(num_data) max_w = max(width) for i in np.arange(num_stacks): for y, d, l in zip(yticks, data[i], left): if (d * 100.0 / max_w) > 2.0: ax.text(l + d / 2.0, y, d, ha='center', va='center', color=almost_black) left += data[i] return rectangles
def barh(*args, **kwargs): """ Creates a bar plot, with white outlines and a fill color that defaults to the first teal-ish green in ColorBrewer's Set2. Optionally accepts grid='y' or grid='x' to draw a white grid over the bars, to show the scale. Almost like "erasing" some of the plot, but it adds more information! Can also add an annotation of the width of the barplots directly onto the bars with the `annotate` parameter, which can either be True, which will annotate the values, or a list of strings, which will annotate with the supplied strings. Can support stacked bars with the value of each stack shown on the stack (Added by Salil Banerjee) @param ax: matplotlib.axes instance @param top: Vector of values of where to put the top side of the bar @param width: Vector of values of the bar widths @param ytickabels: Vector of labels of the bar widths @param kwargs: Any additional arguments to matplotlib.bar() """ ax, args, kwargs = maybe_get_ax(*args, **kwargs) if 'color' not in kwargs: kwargs['color'] = set2[0] if 'edgecolor' not in kwargs: kwargs['edgecolor'] = 'white' if 'width' in kwargs: # Find the middle of the bar middle = kwargs['width']/2.0 else: middle = 0.4 # Check if data contains stacks stacked = kwargs.pop('stacked',False) # Check if stack text should be included stack_text = kwargs.pop('stack_text',False) # Get legend if available legend = kwargs.pop('legend',False) top = args[0] width = np.array(args[1]) # Label each individual bar, if xticklabels is provided ytickabels = kwargs.pop('yticklabels', None) # left+0.4 is the center of the bar yticks = np.array(top) + middle # Whether or not to annotate each bar with the width value annotate = kwargs.pop('annotate', False) # If no grid specified, don't draw one. grid = kwargs.pop('grid', None) # Check if stacked and plot data accordingly if stacked: num_stacks, num_data = width.shape left = np.zeros(num_data) for i in np.arange(num_stacks): lst = list(args) lst[1] = width[i] args = tuple(lst) kwargs['color'] = set2[i] kwargs['left'] = left rectangles = ax.barh(*args, **kwargs) left += width[i] else: rectangles = ax.barh(*args, **kwargs) # add legend if isinstance(legend, collections.Iterable): ax.legend(legend,loc='upper center',bbox_to_anchor=(0.5,1.11), ncol=5) # add whitespace padding on left ymin, ymax = ax.get_ylim() ymin -= 0.2 if stacked: ymax = num_data ax.set_ylim(ymin, ymax) # If there are negative counts, remove the bottom axes # and add a line at y=0 if any(w < 0 for w in width.tolist()): axes_to_remove = ['top', 'right', 'bottom'] ax.vlines(x=0, ymin=ymin, ymax=ymax, linewidths=0.75) #ax.hlines(y=0, xmin=xmin, xmax=xmax, # linewidths=0.75) else: axes_to_remove = ['top', 'right'] #Remove excess axes remove_chartjunk(ax, axes_to_remove, grid=grid) if stacked: data = width width = width.sum(axis=0) # Add the yticklabels if they are there if ytickabels is not None: ax.set_yticks(yticks) ax.set_yticklabels(ytickabels) if annotate or isinstance(annotate, collections.Iterable): annotate_xrange_factor = 0.050 xmin, xmax = ax.get_xlim() xrange = xmax - xmin # Reset ymax and ymin so there's enough room to see the annotation of # the top-most if xmax > 0: xmax += xrange * 0.1 if xmin < 0: xmin -= xrange * 0.1 ax.set_xlim(xmin, xmax) xrange = xmax - xmin offset_ = xrange * annotate_xrange_factor if isinstance(annotate, collections.Iterable): annotations = map(str, annotate) else: annotations = ['%.3f' % w if type(w) is np.float_ else str(w) for w in width] for y, w, annotation in zip(yticks, width, annotations): # Adjust the offset to account for negative bars offset = offset_ if w >= 0 else -1 * offset_ # Finally, add the text to the axes ax.annotate(annotation, (w + offset, y), verticalalignment='center', horizontalalignment='center', color=almost_black) # Text for each block of stack # This was partially inspired by the following article by Tableau software # http://www.tableausoftware.com/about/blog/2014/1/new-whitepaper-survey-data-less-ugly-more-understandable-27812 if stack_text: left = np.zeros(num_data) max_w = max(width) for i in np.arange(num_stacks): for y, d, l in zip(yticks, data[i], left): if (d*100.0/max_w) > 2.0: ax.text(l+d/2.0,y,d, ha='center', va='center', color=almost_black) left += data[i] return rectangles