Source code for pyCreeper.crGraphs

"""

A module that lets you create pretty graphs with single-line function calls.

Useful constants:

* **SHOW_OUTPUT** - (default = False) Set to True to see output of the functions
* **BASE_FILE_PATH** - (default =  "./") File path that is pre-pended to any `filePath_` parameters of the functions
* **DPI** - (default = 100) DPI of created figures. Set to a higher number for a higher resolution.
* **DEBUG_LEVEL** - (default = 0) O - no debug messages, 1 - some debug messages, 2 - deep debug messages

To override the constants, set them after importing `crGraphs`, e.g.

.. code-block:: python

    from pyCreeper import crGraphs;
    crGaphs.DPI = 200;

Author: Lenka Pitonakova: contact@lenkaspace.net

"""
import pylab;
import matplotlib.pyplot as plt;
from matplotlib import font_manager as fm;
from copy import deepcopy;
import math;
import numpy;
import scipy;
import itertools;
import os;


from . import crHelpers;
from . import crData;
from . import crGraphStyle;


#-- constants
SHOW_OUTPUT = True;
BASE_FILE_PATH = "";
DPI = 100;

INVALID_VALUE = -999999;
DEBUG_LEVEL = 0;


[docs]def createBarPlot(data_, title_="", xLabel_ = "", yLabel_ = "", xTickLabels_=[], legendTitle_ = "", legendLabels_ = [], showConfidenceIntervals_=False, showAverages_=False, averagesIgnoreZeros_=False, barWidth_ = 0.8, yMin_=INVALID_VALUE, yMax_=INVALID_VALUE, yTicksStep_ = 0, yTicksStepMultiplier_ = 1, filePath_ = "", renderFigure_=True, figure_=None, subPlot_=111): """ Create bars for groups next to each other, grouped by group labels. Minimal example: .. code-block:: python profitData = [ [1, 2, 4, 3, 6, 8] ]; crGraphs.createBarPlot(profitData); :param `data_`: A 2D or a 3D list of numbers. The 0th dimension represents individual bar groups (colors). The 1st dimension represents data points along the x axis. Optional 3rd dimension is a list of values that each bar consists of. A bar then shows a median / average of that list :param `title_`: (optional, default = "") The figure title :param `xLabel_`: (optional, default = "") Label of the x-axis :param `yLabel_`: (optional, default = "") Label of the y-axis :param `xTickLabels_`: (optional, default = []) A 1D list of tick labels for the x-axis. :param `legendTitle_`: (optional, defalut = "") Text shown before legend :param `legendLabels_`: (optional, default = []) A 1D list of labels for the individual plot lines. Must be the same length as the 0th dimension of `data_`, i.e., each plot line must have a corresponding `legendLabel` :param `showConfidenceIntervals_`: (optional, default = False) A boolean that specified whether to show error bars. If True, `data_` must be a 3D list :param `showAverages_`: (optional, default = False) A boolean that specifies whether to show averages instead of medians. If True, `data_` must be a 3D list :param `averagesIgnoreZeros_`: (optional, default = False) If true, only non-zero values are considered when calculating the averages :param `yMin_`: (optional, default = `INVALID_VALUE`) Minimum value shown on the y-axis. If set to `INVALID_VALUE`, y-axis is displayed to fit the data :param `yMax_`: (optional, default = `INVALID_VALUE`) Maximum value shown on the y-axis. If set to `INVALID_VALUE`, y-axis is displayed to fit the data :param `yTicksStep_`: (optional, default = 0) A number that represents the different between each tick on the y-axis. If set to 0, y-axis is displayed to fit the data :param `yTicksStepMultiplier_`: (optional default = 1) A number by which each tick on the y axis is multiplied by. :param `filePath_`: (optional, default = "") If not empty, the figure will be saved to the file path specified. If empty, the figure will be displayed on screen instead. :param `renderFigure_`: (optional, default = True) If false, the figure will not be displayed or printed. Set to False when putting multiple figures together via the `figure_` parameter. :param `figure_`: (optional, default = None) If not None, the figure will be created into this figure (pylab.figure) :param `subPlot_`: (optional, default = 111) The subplot id of where to place the graph to :return: `pylab.figure` for the plot """ #-- test and pre-set data numOfDataDimensions = crData.getNumberOfListDimensions(data_); if (numOfDataDimensions < 2 or numOfDataDimensions > 3): raise ValueError("The data_ parameter must be a 2D or a 3D list.") if (len(xTickLabels_) > 0): crHelpers.checkListHasAtLeastLengthOfList(xTickLabels_, data_[0]); if (len(legendLabels_) > 0): crHelpers.checkListHasAtLeastLengthOfList(legendLabels_, data_); if (showConfidenceIntervals_ and numOfDataDimensions < 3): print ("!!! Cannot draw confidence intervals: each plot's data point represented by data_ must be a list, i.e., data_ must be a 3D list."); if (showAverages_ and numOfDataDimensions < 3): print ("!!! Cannot show averages: each plot's data point represented by data_ must be a list, i.e., data_ must be a 3D list."); #-- apply styles legendFontSize_ = getStyle().legendFontSize; size_ = getStyle().figureSize; legendPosition_ = getStyle().legendPosition; colors_ = getStyle().colors; numOfLegendColumns_ = getStyle().numOfLegendColumns; gridType_ = getStyle().gridType; #-- adjust bar width based on the number of bars that will be shown for one tick barWidth = barWidth_ / len(data_); fig, ax = createFigure(size_, title_, figure_, subPlot_, xLabel_, yLabel_, yStretchMultiply_=1.1); setTickLabels(ax,data_,xTickLabels_, 0.4); #-- plot bars next to each other xLocations = numpy.arange(len(data_[0])) # the x locations for the groups plots = []; maxVal = -9999999; minVal = 9999999; for i in range(len(data_)): #-- pick a color colorCode = 'b'; if (len(colors_) > i): colorCode = colors_[i]; barLocations = xLocations + (i - math.floor(len(data_)/2))*barWidth; if (numOfDataDimensions == 3): shownValues = []; for q in range(len(data_[i])): if (showAverages_): shownValues.append(crData.getAverageOfAList(data_[i][q], averagesIgnoreZeros_)); else: shownValues.append(numpy.median(data_[i][q])); for r in range(len(data_[i][q])): if (data_[i][q][r] > maxVal): maxVal = data_[i][q][r]; if (data_[i][q][r] < minVal): minVal = data_[i][q][r] if (showConfidenceIntervals_): dataDof = [(len(data_[i][q])-1) for q in range(len(shownValues))]; #degrees of freedom is sample size -1 dataStd = [numpy.std(data_[i][q]) for q in range(len(shownValues))]; plot = ax.bar(barLocations, shownValues, barWidth, color=colorCode, yerr=scipy.stats.t.ppf(0.95, dataDof)*dataStd); #yerr=stdData_[i] plots.append(plot); else: plot = ax.bar(barLocations, shownValues, barWidth, color=colorCode); plots.append(plot); else: if (maxVal < crData.getMaxValueInAList(data_[i])): maxVal = crData.getMaxValueInAList(data_[i]); if (minVal > crData.getMinValueInAList(data_[i])): minVal = crData.getMinValueInAList(data_[i]); plot = ax.bar(barLocations, data_[i], barWidth, color=colorCode); #yerr=stdData_[i] plots.append(plot); #-- grid if (gridType_ != crGraphStyle.GRID_TYPE.NONE): [gridAxis, gridWhich] = getStyle().getMatplotlibGridSettings(); ax.grid(which=gridWhich, axis=gridAxis, linestyle=":"); #-- adjust x axis and y axis limits: xMin_ = INVALID_VALUE; xMax_ = INVALID_VALUE; if (xMin_ == INVALID_VALUE): xMin_ = min(-0.5, 0 - barWidth_*0.75); if (xMax_ == INVALID_VALUE): xMax_ = max(len(data_[0]) - 0.25, len(data_[0]) - barWidth_*0.5); setFigureAxisLimits(ax, minVal, maxVal, xMin_, xMax_, yMin_, yMax_, yTicksStep_, yTicksStepMultiplier_); #-- show legend if (len(legendLabels_) > 0 and legendPosition_ != crGraphStyle.LEGEND_POSITION.NONE): def flip(items, ncol): return itertools.chain(*[items[i::ncol] for i in range(ncol)]) legendItems = []; for g in range(len(plots)): legendItems.append(plots[g][0]); legend = ax.legend(flip(legendItems, numOfLegendColumns_), flip(legendLabels_,numOfLegendColumns_),loc=legendPosition_.value, ncol=numOfLegendColumns_, title=legendTitle_, fancybox=True, framealpha=0.7) for t in legend.get_texts(): t.set_fontsize(legendFontSize_); legend.get_title().set_fontsize(legendFontSize_); legend._legend_box.align = "left" #-- display / print, return: renderFigure(fig, filePath_, renderFigure_); return fig;
#--------------------------------------------------------------------------------------------------------- Line plot
[docs]def createLinePlot(data_, title_="", xLabel_ = "", yLabel_ = "", xTickLabels_=[], xTickLabelPositions_=[], legendTitle_ = "", legendLabels_ = [], showBoxPlots_=False, showConfidenceIntervals_=False, showAverages_=False, averagesIgnoreZeros_=False, doWilcoxon_=False, xMin_=INVALID_VALUE, xMax_=INVALID_VALUE, xAxisGroupSize_ = 0, yMin_=INVALID_VALUE, yMax_=INVALID_VALUE, yTicksStep_ = 0, yTicksStepMultiplier_ = 1, useBoxPlotPadding_ = False, filePath_ = "", renderFigure_=True, figure_=None, subPlot_=111): """ Create one of a plot with a single or multiple lines. Optionally, each data point can have error bars or box plots around it. Minimal example: .. code-block:: python profitData = [ [1, 2, 4, 3, 6, 8] ]; crGraphs.createLinePlot(profitData); :param `data_`: A 2D or a 3D list of numbers. The 0th dimension represents individual lines. The 1st dimension represents data points on the line, ordered by x coordinate. Optional 3rd dimension is a list of values that each data point consists of. A data point is then a median of that list :param `title_`: (optional, default = "") The figure title :param `xLabel_`: (optional, default = "") Label of the x-axis :param `yLabel_`: (optional, default = "") Label of the y-axis :param `xTickLabels_`: (optional, default = []) A 1D list or numpy.array of tick labels for the x-axis. Make this a numpy.array when there are too many data points and x-axis values can be shown as numbers. :param `xTickLabelPositions_`: (optional, default numpy.array()) A 1D list of positions on x-axis where ticks should be shown. Can be used e.g. when there are too many data points than `xTickLabels` and `xTickLabels` needs to show string values. :param `legendTitle_`: (optional, defalut = "") Text shown before legend :param `legendLabels_`: (optional, default = []) A 1D list of labels for the individual plot lines. Must be the same length as the 0th dimension of `data_`, i.e., each plot line must have a corresponding `legendLabel` :param `showBoxPlots_`: (optional, default = False) A boolean that specified whether to show box plots around data points. If True, `data_` must be a 3D list :param `showConfidenceIntervals_`: (optional, default = False) A boolean that specified whether to show error bars around data points. If True, `data_` must be a 3D list :param `showAverages_`: (optional, default = False) A boolean that specifies whether to show averages instead of medians as data points. If True, `data_` must be a 3D list :param `averagesIgnoreZeros_`: (optional, default = False) If true, only non-zero values are considered when calculating the averages :param `doWilcoxon_`: (optional, default = False) A boolean that specified whether to perform `Wilcoxon signed-rank test <http://en.wikipedia.org/wiki/Wilcoxon_signed-rank_test>`_ between 2 plot lines. If True, the \* notation is used next to a tick label on the x-axis where there is significant difference with p=0.05. The \*\* notation is used when there is a significant different with p=0.01. This test can only be performed when `data_` has length of 2 and is a 3D list, i.e., if it containts data for 2 plot lines and each data point represents a list of values :param `xMin_`: (optional, default = `INVALID_VALUE`) Minimum value shown on the x-axis. If set to `INVALID_VALUE`, x-axis is displayed to fit the data :param `xMax_`: (optional, default = `INVALID_VALUE`) Maximum value shown on the x-axis. If set to `INVALID_VALUE`, x-axis is displayed to fit the data :param `xAxisGroupSize_`: (optional, default = 0) The number of data points that are joined by lines when they are next to each other on the x-axis. If set to 0, all data points are joined together. If set to > 0, groups of data points appear, with no joining lines between data points from different groups. :param `yMin_`: (optional, default = `INVALID_VALUE`) Minimum value shown on the y-axis. If set to `INVALID_VALUE`, y-axis is displayed to fit the data :param `yMax_`: (optional, default = `INVALID_VALUE`) Maximum value shown on the y-axis. If set to `INVALID_VALUE`, y-axis is displayed to fit the data :param `yTicksStep_`: (optional, default = 0) A number that represents the different between each tick on the y-axis. If set to 0, y-axis is displayed to fit the data :param `yTicksStepMultiplier_`: (optional default = 1) A number by which each tick on the y axis is multiplied by. :param `useBoxPlotPadding_`: (optional, default = False) Set this to true to add horizontal padding to the plot that matches padding of a box plot. E.g. when showing single data points next to similar box plot graphs, it might be desirable to keep the plot paddings the same. :param `filePath_`: (optional, default = "") If not empty, the figure will be saved to the file path specified. If empty, the figure will be displayed on screen instead. :param `renderFigure_`: (optional, default = True) If false, the figure will not be displayed or printed. Set to False when putting multiple figures together via the `figure_` parameter. :param `figure_`: (optional, default = None) If not None, the figure will be created into this figure (pylab.figure) :param `subPlot_`: (optional, default = 111) The subplot id of where to place the graph to :return: `pylab.figure` for the plot """ #-- test and pre-set data numOfDataDimensions = crData.getNumberOfListDimensions(data_); if (numOfDataDimensions < 2 or numOfDataDimensions > 3): raise ValueError("The data_ parameter must be a 2D or a 3D list.") xTickLabels = xTickLabels_.copy(); if (len(xTickLabels) == 0): xTickLabels = list(range(len(data_[0]))); if (len(legendLabels_) > 0): crHelpers.checkListHasAtLeastLengthOfList(legendLabels_, data_); #-- apply styles legendFontSize_ = getStyle().legendFontSize; size_ = getStyle().figureSize; lineWidth_ = getStyle().lineWidth; markerSize_= getStyle().markerSize; legendPosition_ = getStyle().legendPosition; gridType_ = getStyle().gridType; lineStyles_ = getStyle().lineStyles; markers_ = getStyle().markers; colors_ = getStyle().colors; numOfLegendColumns_ = getStyle().numOfLegendColumns; boxPlotWidth_ = getStyle().boxPlotWidth; if (showAverages_): showBoxPlots_ = False; if (doWilcoxon_): if (len(data_) != 2): doWilcoxon_ = False; print ("!!! Cannot perform Wilcoxon signed-rank test between more than 2 plot lines, i.e., the size of data_ must be 2."); elif (numOfDataDimensions < 3): doWilcoxon_ = False; print ("!!! Cannot perform Wilcoxon signed-rank test: each plot's data point represented by data_ must be a list, i.e., data_ must be a 3D list."); else: #-- make a bit more vertical space for wilcoxon markers size_ = (size_[0], size_[1] + 0.2) if (showBoxPlots_ and numOfDataDimensions < 3): print ("!!! Cannot draw box plots: each plot's data point represented by data_ must be a list, i.e., data_ must be a 3D list."); if (showConfidenceIntervals_ and numOfDataDimensions < 3): print ("!!! Cannot draw confidence intervals: each plot's data point represented by data_ must be a list, i.e., data_ must be a 3D list."); if (showAverages_ and numOfDataDimensions < 3): print ("!!! Cannot show averages: each plot's data point represented by data_ must be a list, i.e., data_ must be a 3D list."); #-- create the figure fig, ax = createFigure(size_, title_, figure_, subPlot_, xLabel_, yLabel_, 1.2, 1.05); #-- prepare x tick data, which has to be scalars xTickData = []; if (type(xTickLabels) == int): #-- xTickLabels are numbers, ok to use for plotting xTickData = xTickLabels; elif (isinstance(xTickLabels, numpy.ndarray) or len(xTickLabelPositions_) > 0): #-- xTickLabels is a numpy array, data is simply a list with length of the main data dimension xTickData = list(range(len(data_[0]))); else: #-- xTickLabels are strings, the xTickData must be an array from 0-length of xTickLabels xTickData = list(range(len(xTickLabels))); #-- prepare box plot width if ((showBoxPlots_ and boxPlotWidth_ == INVALID_VALUE) or showConfidenceIntervals_ or useBoxPlotPadding_): boxPlotWidth_ = abs(xTickData[-1] - xTickData[0]) / 20.0; #-- do the potting plots = []; maxVal = -9999999; minVal = 9999999; for i in range(len(data_)): if (DEBUG_LEVEL >= 1): print("[crData] processing data row {}".format(i)) legendLabel = " "; #-- choose a legend label if (len(legendLabels_) > i): legendLabel = legendLabels_[i]; #-- choose a marker marker = 'bs-' if (len(markers_) > i): marker = markers_[i]; #-- choose a color, default to marker color color = marker[0:1]; if (getStyle().wereColorsSetByUser and len(colors_) > i): color = colors_[i]; lineStyle = '-'; if (len(lineStyles_) > i): lineStyle = lineStyles_[i]; elif (lineWidth_ == 0): lineStyle = ''; #-- find out how many line segments numOfSegments = 0; lineSegmentLength = xAxisGroupSize_; if (xAxisGroupSize_ > 0 and xAxisGroupSize_ <= len(xTickData)): numOfSegments = int(math.ceil(len(xTickData) / xAxisGroupSize_)); else: numOfSegments = 1; lineSegmentLength = len(xTickData); #-- plot if (numOfDataDimensions == 3): #-- each element for a single data point is a list. #-- only plot median of data that is a list. Box plots can be added later if set #-- get medians one by one, as numpy can't deal with lists of different lengths dataPoints = []; for q in range(len(data_[i])): if (showAverages_): dataPoints.append(crData.getAverageOfAList(data_[i][q], averagesIgnoreZeros_)); else: dataPoints.append(numpy.median(data_[i][q])); if (doWilcoxon_ and i == 1): #-- do the Wilcoxon test on individual samples (that together form a median) and compare them to runs of previous data set: pVal = scipy.stats.wilcoxon(data_[i][q],data_[0][q])[1]; if (pVal < 0.01): xTickLabels[q] = str(xTickLabels[q]) + "\n**"; elif (pVal < 0.05): xTickLabels[q] = str(xTickLabels[q]) + "\n*"; #-- find data min/max if (showAverages_ and not showConfidenceIntervals_): #-- averages will need a different scale than when showing confidence intervals or box plots because they don't show all data if (dataPoints[q] > maxVal): maxVal = dataPoints[q]; if (dataPoints[q] < minVal): minVal = dataPoints[q] else: for r in range(len(data_[i][q])): if (data_[i][q][r] > maxVal): maxVal = data_[i][q][r]; if (data_[i][q][r] < minVal): minVal = data_[i][q][r] if (showConfidenceIntervals_): dataDof = [(len(data_[i][q])-1) for q in range(len(dataPoints))]; #degrees of freedom is sample size -1 dataStd = [numpy.std(data_[i][q]) for q in range(len(dataPoints))]; (_, caps, _) = plt.errorbar(xTickData, dataPoints, yerr=scipy.stats.t.ppf(0.95, dataDof)*dataStd, color=color, linewidth=0, elinewidth=lineWidth_, capsize=markerSize_-2, linestyle=lineStyle); for cap in caps: if (lineWidth_ == 0): cap.set_markeredgewidth(3); else: cap.set_markeredgewidth(lineWidth_); #-- draw, in line segments for seg in range(numOfSegments): segStart = seg * lineSegmentLength; segEnd = segStart + lineSegmentLength; if (segEnd > len(xTickData)): segEnd = len(xTickData-1); plot = pylab.plot(xTickData[segStart:segEnd], dataPoints[segStart:segEnd], marker, color=color, label = legendLabel, linewidth=lineWidth_, linestyle=lineStyle, markersize=markerSize_); else: #-- each element for a single data point is a single number. Plot directly from these numbers. if (maxVal < crData.getMaxValueInAList(data_[i])): maxVal = crData.getMaxValueInAList(data_[i]); if (minVal > crData.getMinValueInAList(data_[i])): minVal = crData.getMinValueInAList(data_[i]); #-- draw, in line segments for seg in range(numOfSegments): segStart = seg * lineSegmentLength; segEnd = segStart + lineSegmentLength; if (segEnd > len(xTickData)): segEnd = len(xTickData-1); plot = pylab.plot(xTickData[segStart:segEnd], data_[i][segStart:segEnd], marker, color=color, label = legendLabel, linewidth=lineWidth_, linestyle=lineStyle, markersize=markerSize_); plots.append(plot); #-- do box plots if (showBoxPlots_): boxPlot = pylab.boxplot(data_[i],positions=xTickData,widths=boxPlotWidth_); boxPlotLineWidth = getStyle().boxPlotLineWidth; pylab.setp(boxPlot['boxes'], color=color); pylab.setp(boxPlot['whiskers'], color=color); pylab.setp(boxPlot['medians'], color=color); pylab.setp(boxPlot['fliers'], markeredgecolor=color, marker="+", markerSize = max(6,markerSize_*0.7),markerEdgeWidth=max(1,boxPlotLineWidth)); pylab.setp(boxPlot['caps'], color=color); if (boxPlotLineWidth <= 0): boxPlotLineWidth = 1; for box in boxPlot['boxes']: box.set(linewidth=boxPlotLineWidth) for median in boxPlot['medians']: median.set(linewidth=boxPlotLineWidth) for cap in boxPlot['caps']: cap.set(linewidth=boxPlotLineWidth) for cap in boxPlot['whiskers']: cap.set(linewidth=boxPlotLineWidth) #-- apply custom x tick labels if (len(xTickLabelPositions_) > 0): #-- based on user-defined positions and labels ax.set_xticks(xTickLabelPositions_); ax.set_xticklabels(xTickLabels); elif (len(xTickLabels_) > 0): #-- based on user-defined labels if (isinstance(xTickLabels, numpy.ndarray)): ax.set_xticks(xTickLabels) else: plt.xticks(xTickData, xTickLabels); else: #-- automatic setTickLabels(ax,data_,xTickLabels_, 0.4); #-- grid if (gridType_ != crGraphStyle.GRID_TYPE.NONE): [gridAxis, gridWhich] = getStyle().getMatplotlibGridSettings(); ax.grid(which=gridWhich, axis=gridAxis, linestyle=":"); #-- adjust x axis and y axis limits: if (useBoxPlotPadding_ or showBoxPlots_ or showConfidenceIntervals_): #-- make space for box plots ax.set_xlim(xTickData[0]-2*boxPlotWidth_/3.0,xTickData[-1]+2*boxPlotWidth_/3.0); else: #-- make a little space ax.set_xlim(xTickData[0]-xTickData[-1]*0.01,xTickData[-1]+xTickData[-1]*0.01); if (DEBUG_LEVEL>=1): print("[crData] min data value={} max data value={}".format(minVal,maxVal)) setFigureAxisLimits(ax, minVal, maxVal, xMin_, xMax_, yMin_, yMax_, yTicksStep_, yTicksStepMultiplier_); #-- show legend if (len(legendLabels_) > 0 and legendPosition_ != crGraphStyle.LEGEND_POSITION.NONE): def flip(items, ncol): return itertools.chain(*[items[i::ncol] for i in range(ncol)]) legendItems = []; for g in range(len(plots)): legendItems.append(plots[g][0]); legend = ax.legend(flip(legendItems, numOfLegendColumns_), flip(legendLabels_,numOfLegendColumns_),loc=legendPosition_.value, ncol=numOfLegendColumns_, title=legendTitle_, fancybox=True, framealpha=0.7) for t in legend.get_texts(): t.set_fontsize(legendFontSize_); legend.get_title().set_fontsize(legendFontSize_); legend._legend_box.align = "left" #-- display / print, return: renderFigure(fig, filePath_, renderFigure_); return fig;
#--------------------------------------------------------------------------------------------------------- Matrix plot
[docs]def createMatrixPlot(data_=[[],[]], title_="", xLabel_ = "", yLabel_ = "", xTickLabels_ = [], yTickLabels_ = [], colorBarLabel_ = "", minValue_ = INVALID_VALUE, maxValue_ = INVALID_VALUE, annotateValues_=False, annotationStringAfter_="", annotationValues_=[[],[]], roundAnnotatedValues_=False, filePath_ = "", renderFigure_=True, figure_=None, subPlot_=111): """ Create 2D matrix plot where color gradient represents value on a 3rd dimension. Note: The figure size is set to (getStyle().figureSize[0]-1, getStyle().figureSize[1]+1) to get proper paddings and to match other figure types as close as possible. Minimal example: .. code-block:: python performanceData = [ [12,11,10,9], [11,10,7,6], [9,8,6,5], ]; crGraphs.createMatrixPlot(performanceData); :param `data_`: a 2D list of values, where the 0th dimension runs along the x axis and the 1st dimension along y axis, so [0,0] in the list specified a value for the bottom left square :param `title_`: (optional, default = "") The figure title :param `xLabel_`: (optional, default = "") Label of the x-axis :param `yLabel_`: (optional, default = "") Label of the y-axis :param `xTickLabels_`: (optional, default = []) Labels of the individual ticks of the x-axis. If empty, values 0-N are displayed :param `yTickLabels_`: (optional, default = []) Labels of the individual ticks of the y-axis. If empty, values 0-N are displayed :param `colorBarLabel_`: (optional, default = "") Label of color bar, displayed vertically :param `minValue_`: (optional, default = `INVALID_VALUE`) Minimum float value that the color map considers. If set to INVALID_VALUE, the value is automatically calculated from `data_` :param `maxValue_`: (optional, default = `INVALID_VALUE`) Maximum float value that the color map considers. If set to INVALID_VALUE, the value is automatically calculated from `data_` :param `annotateValues_`: (optional, default = False) If True, data values will be displayed in the matrix plot :param `annotationStringAfter_`: (optional, default = "") A string to append after each annotation value :param `annotationValues_`: (optional, default = [[],[]]) A 2D list of annotations in the matrix plot. If non-empty, must have the same dimensions as `data_` :param `roundAnnotatedValues_`: (optional, default = False) If True and if `annotationValues_` is empty, annotation numbers will be rounded :param `filePath_`: (optional, default = "") If not empty, the figure will be saved to the file path specified. If empty, the figure will be displayed on screen instead. :param `renderFigure_`: (optional, default = True) If false, the figure will not be displayed or printed. Set to False when putting multiple figures together via the `figure_` parameter. :param `figure_`: (optional, default = None) If not None, the figure will be created into this figure (pylab.figure) :param `subPlot_`: (optional, default = 111) The subplot id of where to place the graph to :return: `pylab.figure` for the plot """ #-- test and pre-set data crHelpers.checkVariableIsList(data_,2,True); crHelpers.checkVariableIsList(xTickLabels_); crHelpers.checkVariableIsList(yTickLabels_); crHelpers.checkVariableIsList(annotationValues_,2); if (len(xTickLabels_) > 0): crHelpers.checkListsHaveTheSameLength(data_[1], xTickLabels_); if (len(yTickLabels_) > 0): crHelpers.checkListsHaveTheSameLength(data_, yTickLabels_); if (len(annotationValues_[0]) == 0): annotationValues_ = deepcopy(data_); if (minValue_ == INVALID_VALUE): minValue_ = crData.getMinValueInAList(data_); if (maxValue_ == INVALID_VALUE): maxValue_ = crData.getMaxValueInAList(data_); #-- apply styles legendFontSize_ = getStyle().legendFontSize; tickFontSize_ = getStyle().tickFontSize; #-- create the figure fig, ax = createFigure((getStyle().figureSize[0]-1, getStyle().figureSize[1]+1), title_, figure_, subPlot_, xLabel_, yLabel_); # since matshow ignores the axis position / size, the only way to control the figure padding / size is via total figure size cax = ax.matshow(data_,cmap=getStyle().colorMap,origin='lower',vmin=minValue_,vmax=maxValue_, aspect=None); #-- set tick labels if (len(xTickLabels_) > 0): ax.set_xticklabels([''] + xTickLabels_); if (len(yTickLabels_) > 0): ax.set_yticklabels([''] + yTickLabels_); for t in ax.get_xticklabels(): t.set_fontsize(tickFontSize_); for t in ax.get_yticklabels(): t.set_fontsize(tickFontSize_); #-- make a colorbar for the ContourSet returned by the contourf call. cbar = fig.colorbar(cax) cbar.ax.set_ylabel(colorBarLabel_, size=legendFontSize_); for t in cbar.ax.get_yticklabels(): t.set_fontsize(tickFontSize_) #-- annotations if (annotateValues_): #-- the XY grid position has [0;0] in the bottom left corner gridStartX=0.0; gridStartY=0.0; gridEndX=1.0; gridEndY=1.0; gridStepX=(gridEndX-gridStartX)/len(data_[0]); gridStepY=(gridEndY-gridStartY)/len(data_); for y in range(len(data_)): for x in range(len(data_[0])): if (roundAnnotatedValues_): ax.annotate(str(math.ceil(annotationValues_[y][x] * 100) / 100.0) + annotationStringAfter_, xy=(gridStartX+x*gridStepX+gridStepX/2, gridStartY+y*gridStepY+gridStepY/2), size=tickFontSize_, xycoords='axes fraction',horizontalalignment='center', verticalalignment='center') else: ax.annotate(str(annotationValues_[y][x]) + annotationStringAfter_, xy=(gridStartX+x*gridStepX+gridStepX/2, gridStartY+y*gridStepY+gridStepY/2), size=tickFontSize_, xycoords='axes fraction',horizontalalignment='center', verticalalignment='center') #-- display / print, return: renderFigure(fig, filePath_, renderFigure_); return fig;
#--------------------------------------------------------------------------------------------------------- Pie chart
[docs]def createPieChart(data_=[], itemLabels_=[], title_="", itemColors_=[], showActualVals_=True, showPercentageVals_=False, showShadow_=False, filePath_ = "", renderFigure_=True, figure_=None, subPlot_=111): """ Create a pie chart. Note: If the figure is rendered (`renderFigure_` = True), the figure size is close to a square, with the width and height as specified in the style's figureSize. Minimal example: .. code-block:: python expenseCategories = ["Rent", "Food", "Travel", "Fun"]; expenses = [1000, 300, 500, 250]; crGraphs.createPieChart(expenses, expenseCategories); :param `data_`: A 1D list of values :param `itemLabels_`: A 1D list of value labels. Must be the same length as `data_` :param `title_`: (optional, default = "") The figure title :param `showActualVals_`: (optional, default = True) Boolean whetehr to show data values in the pie parts :param `showPercentageVals_`: (optional, default = False) Boolean whether to show percentages in the pie parts :param `showShadow_`: (optional, default = False) Boolean whether to show shadow underneath the pie :param `filePath_`: (optional, default = "") If not empty, the figure will be saved to the file path specified. If empty, the figure will be displayed on screen instead. :param `renderFigure_`: (optional, default = True) If false, the figure will not be displayed or printed. Set to False when putting multiple figures together via the `figure_` parameter. :param `figure_`: (optional, default = None) If not None, the figure will be created into this figure (pylab.figure) :param `subPlot_`: (optional, default = 111) The subplot id of where to place the graph to :return: `pylab.figure` for the plot """ #-- test and pre-set data crHelpers.checkVariableIsList(data_,1,True); crHelpers.checkVariableIsList(itemLabels_,True); crHelpers.checkListHasAtLeastLengthOfList(itemLabels_, data_); #-- set styles itemsFontSize_ = getStyle().labelFontSize; valuesFontSize_ = getStyle().tickFontSize; if (title_ != ""): getStyle().yOffset += 0.08; else: getStyle().yOffset += 0.04; #-- make item colors of the same length as data, wrapping around the style colors itemColors = []; styleColors = getStyle().colors; styleColorIndex = 0; for c in range(len(data_)): itemColors.append(styleColors[styleColorIndex]); styleColorIndex += 1; if (styleColorIndex == (len(styleColors))): styleColorIndex = 0; #-- def formatPieceNumber(val_): if (showActualVals_ and showPercentageVals_): val = int(val_/100.*numpy.sum(data_)) return '{p:.1f}% ({v:d})'.format(p=val_,v=val); if (showActualVals_): val=int(val_*sum(data_)/100.0) return '{v:d}'.format(v=val); elif (showPercentageVals_): return '{p:.2f}%'.format(p=val_); return ''; #-- create the graph size = getStyle().figureSize; fig, ax = createFigure(size, title_, figure_, subPlot_); ax.set_aspect(1) patches, texts, autotexts = ax.pie(data_, labels=itemLabels_, labeldistance=1.2, autopct=lambda val:formatPieceNumber(val), shadow=showShadow_, colors=itemColors); #-- setup fonts proptease = fm.FontProperties(); proptease.set_size(itemsFontSize_); plt.setp(texts, fontproperties=proptease); proptease.set_size(valuesFontSize_); plt.setp(autotexts, fontproperties=proptease); #-- display / print, return: renderFigure(fig, filePath_, renderFigure_); return fig;
#============================================================================ #============================== HELPER FUNCTIONS ============================ #============================================================================
[docs]def renderFigure(figure_, filePath_="", renderFigure_=True): """ A helper function that performs the current pylab's figure rendering into a file or on screen :param `filePath_`: (optional, default = "") If not empty, the figure will be saved to the file path specified. If empty, the figure will be displayed on screen instead. :param `renderFigure_`: (optional, default = True) If false, the figure will not be displayed or printed. """ if (renderFigure_): if (len(filePath_) > 0): filePath_ = BASE_FILE_PATH + filePath_; #-- create directories recursively if they don't exist directoryPath = filePath_.rsplit('/', 1)[0] os.makedirs(directoryPath,exist_ok=True) #-- pylab.savefig(filePath_, format='png') pylab.close(figure_) if (SHOW_OUTPUT == True): print("[crData] Saved " + filePath_); else: pylab.show()
[docs]def createFigure(size_, title_="", figure_=None, subPlot_=111, xLabel_="", yLabel_="", xStretchMultiply_=1.2, yStretchMultiply_=1.2): """ A helper function that creates a figure. :param `size_`: The figure size :param `title_`: (optional, default = "") The figure title :param `figure_`: (optional, default = None) If not None, the figure will be created into this figure (pylab.figure) :param `subPlot_`: (optional, default = 111) The subplot id of where to place the graph to :param `xLabel_`: (optional, default = "") Label of the x-axis :param `yLabel_`: (optional, default = "") Label of the y-axis :param `xStretchMultiply_`: (optional, default = 1.2) By how much to stretch the plot inside the figure in x direction. Enter 1.0 to leave the plot as is. :param `yStretchMultiply_`: (optional, default = 1.2) By how much to stretch the plot inside the figure in y direction. Enter 1.0 to leave the plot as is. :param `xOffset_`: (optional, default = 0) Horizontal offset of the plot :param `yOffset_`: (optional, default = 0) Vertical offset of the plot :return: (pylab.figure, pylab.ax) """ #-- test and pre-set data titleFontSize_ = getStyle().titleFontSize; labelFontSize_ = getStyle().labelFontSize; tickFontSize_ = getStyle().tickFontSize; if (figure_ == None): fig = pylab.figure(figsize=size_, dpi=DPI); else: fig = figure_; ax = fig.add_subplot(subPlot_); #-- specify title, stretch the plot xStretchMultiply_ = xStretchMultiply_-1; yStretchMultiply_ = yStretchMultiply_-1; yOffset = getStyle().yOffset; if (title_ != ""): fig.suptitle(title_, fontsize=titleFontSize_); yStretchMultiply_ -= 0.1; yOffset += -0.03; else: yOffset += -0.05; yStretchMultiply_ = 0.1; if (xLabel_ != ""): yStretchMultiply_ -= 0.1; if (yLabel_ != ""): xStretchMultiply_ -= 0.1; box = ax.get_position(); ax.set_position([box.x0 - box.width * (xStretchMultiply_/2) + getStyle().xOffset + 0.025, box.y0 - box.height*yStretchMultiply_/2 - yOffset, box.width*(1+xStretchMultiply_), box.height*(1+yStretchMultiply_)]); pylab.xlabel(xLabel_, size=labelFontSize_, labelpad=getStyle().xLabelPadding); pylab.ylabel(yLabel_, size=labelFontSize_, labelpad=getStyle().yLabelPadding); pylab.xticks(size=tickFontSize_); pylab.yticks(size=tickFontSize_); return (fig, ax)
[docs]def replaceInvalidWithDefaultValue(value_, defaultValue_): """ A helper function used when checking parameter values. If the parameter value (`value_`) is `crGraphs.INVALID_VALUE`, returns a specified `defaultValue_` instead. :param `value_`: the value :param `defaultValue_`: the value to use if `value_` == INVALID_VALUE """ if (value_ == INVALID_VALUE): return defaultValue_; else: return value_;
[docs]def setFigureAxisLimits(ax_, minDataValue_, maxDataValue_, xMin_=INVALID_VALUE, xMax_=INVALID_VALUE, yMin_=INVALID_VALUE, yMax_=INVALID_VALUE, yTicksStep_ = 0, yTicksStepMultiplier_ = 1): #-- determine the y span of data dataRange = maxDataValue_ - minDataValue_; if (yMax_ != INVALID_VALUE and yMin_ != INVALID_VALUE): dataRange = yMax_ - yMin_; if (dataRange == 0): dataRange = 1; plotYMin = minDataValue_ - dataRange*0.1; plotYMax = maxDataValue_ + dataRange*0.1; #-- recursively find the correct yTickStep based on the max value. The yTickStep should fit N times into maxDataValue_. # then annotate the y axis. N = 20; if (yTicksStep_ <= 0): yTicksStep_ = 0.00001; stop = False; while (stop == False): if (dataRange / yTicksStep_ <= N): stop = True; else: yTicksStep_ *= 10; #-- round to the closest multiply of N, where N fits yTickStep plotYMin = plotYMin - (plotYMin%yTicksStep_); if (xMin_ > INVALID_VALUE and xMax_ > INVALID_VALUE): ax_.set_xlim(xMin_, xMax_); if (yMin_ != INVALID_VALUE): plotYMin = yMin_; if (yMax_ != INVALID_VALUE): plotYMax = yMax_; #-- further adjust yTickStep in case more than M ticks would have to be shown M = 10; hasLotOfHorizontalTicks = False; if (plotYMin + M*yTicksStep_ <= plotYMax): hasLotOfHorizontalTicks = True; #-- if only major grid should be shown, remove each second yTick in cases when a lot of yTicks would be present if (hasLotOfHorizontalTicks) and (getStyle().gridType == crGraphStyle.GRID_TYPE.MAJOR or getStyle().gridType == crGraphStyle.GRID_TYPE.MAJOR_HORIZONTAL): yTicksStep_ *= 2; #-- make integers look like whole numbers if (yTicksStep_ >= 1): yTicksStep_ = (int)(yTicksStep_); #-- set the limit so that min / max values have some padding from the plot sides ax_.set_ylim(plotYMin - plotYMax*0.05, plotYMax+plotYMax*0.05); ticks = []; ticksLabels = []; start = plotYMin; stop = plotYMax; if (yMin_ == - yMax_*0.05): start = 0; ticks = numpy.arange(start, stop + yTicksStep_, yTicksStep_); ax_.set_yticks(ticks); if (yTicksStepMultiplier_ != 1): for t in range(len(ticks)): ticksLabels.append(ticks[t] * yTicksStepMultiplier_); ax_.set_yticklabels(ticksLabels); ticks = ticksLabels; ticksLabels = []; for t in range(len(ticks)): intVal = int(ticks[t]); if (abs(intVal) >= 1000): ticksLabels.append("{}k".format( intVal / 1000)); elif (yTicksStep_ >= 1): ticksLabels.append(intVal); else: ticksLabels.append( ((int)(ticks[t]*(1.0/yTicksStep_))) / (1.0/yTicksStep_) ); # assuring to weird trailing .000001 are shown if (hasLotOfHorizontalTicks and (getStyle().gridType == crGraphStyle.GRID_TYPE.FULL or getStyle().gridType == crGraphStyle.GRID_TYPE.HORIZONTAL)): if (t%2 == 1): ticksLabels[t] = ""; ax_.set_yticklabels(ticksLabels);
[docs]def setTickLabels(ax_, data_, xTickLabels_, minSpacingBetweenXLabels_): size_ = getStyle().figureSize; tickFontSize_ = getStyle().tickFontSize; labelFontSize_ = getStyle().labelFontSize; xLocations = numpy.arange(len(data_[0])) # the x locations for the ticks xTickLabels = xTickLabels_.copy(); #-- if user didn't define tick labels and too many tick labels would be produced automatically, adjust their frequency if (len(xTickLabels_) == 0): xTickLabels = []; xTickFrequency = max(1,math.floor(len(xLocations)/(size_[0]*0.9))); #print(xTickFrequency) spacing = (size_[0]*0.9) / len(xLocations); #print(spacing, minSpacingBetweenXLabels_); if (spacing < minSpacingBetweenXLabels_): #-- make sure the tick frequency is a nice round number allowedFrequences = [1,2,5,10,25,50,100,250,500,1000,2500,5000,10000,25000,50000,100000]; for f in allowedFrequences: if (xTickFrequency < f): xTickFrequency = f; break; else: xTickFrequency = 1; #-- create numbered ticks of proper frequency, also marking the last tick value for i in range(len(xLocations)): val = i+1 # making sure xticks start at 1 not 0 #-- each i ticks that are not too close to the end plus the first and last tick if ((val%xTickFrequency == 0 and val < len(xLocations) - xTickFrequency/2 ) or (val == 1) or (val == len(xLocations))): xTickLabels.append(val); else: xTickLabels.append(''); #-- apply the labels plt.xticks(xLocations, size=labelFontSize_); ax_.set_xticklabels(xTickLabels, size=tickFontSize_);
[docs]def getBrokenLinePlotParameters(yMin_, brokenLineY_, yMax_, brokenLinePlotHeightPercentage_ = 60): """ Get y-axis parameters of two plots, bottom and top, that can be combined together into a broken line plot. :param yMin_: Minimum value shown on the y-axis. :param brokenLineY_: Y-axis value at which the broken line will be drawn :param yMax_: Maximum value shown on the y-axis. :param brokenLinePlotHeightPercentage_: (optional, default = 60) The percentage (20-80) of the plot's height at which the broken line will be drawn :return: (bottom plot y min, bottom plot y max, top plot y min, top plot y max) """ crHelpers.checkVariableGreaterThanAnother(brokenLineY_,yMin_); crHelpers.checkVariableGreaterThanAnother(yMax_,yMin_); crHelpers.checkVariableGreaterThanAnother(yMax_,brokenLineY_); crHelpers.checkVariableBetweenValues(brokenLinePlotHeightPercentage_,20,80); brokenLinePlotHeightMultiplier = brokenLinePlotHeightPercentage_ / 100.0; zoomedInPlotRange = brokenLineY_ - yMin_; finalPlotRange = yMax_ - yMin_; zoomedInPlotYMax = zoomedInPlotRange * 1/brokenLinePlotHeightMultiplier; # the broken line breaks at N% of the figure height zoomedOutPlotYMin = yMax_ - ((finalPlotRange - brokenLineY_) * (1/(1-brokenLinePlotHeightMultiplier))) return (yMin_, zoomedInPlotYMax, zoomedOutPlotYMin, yMax_);
#============================================================================ #============================== STYLES ============================ #============================================================================ __style = crGraphStyle.crGraphStyle();
[docs]def getStyle(): """ :return: A :mod:`pyCreeper.crGraphStyle.crGraphStyle` instance. """ global __style; return __style;
[docs]def setStyle(style_): """ Set the style. :param style_: A :mod:`pyCreeper.crGraphStyle.crGraphStyle` instance """ global __style; if (style_ == None): __style = crGraphStyle.crGraphStyle(); if (isinstance(style_,crGraphStyle.crGraphStyle)): __style = style_; else: raise ValueError("crGraphs: style must of type crGraphStyle");