diff --git a/plotter/__init__.py b/plotter/__init__.py new file mode 100644 index 0000000..fcd7619 --- /dev/null +++ b/plotter/__init__.py @@ -0,0 +1 @@ +from . import animations, auxillary, colours, interactive, make, plots \ No newline at end of file diff --git a/plotter/make.py b/plotter/make.py new file mode 100644 index 0000000..7659202 --- /dev/null +++ b/plotter/make.py @@ -0,0 +1,161 @@ +import plotter.plots as plo +import plotter.auxillary as aux +import numpy as np + +def bar_plot(data: list, groups: list, attributes=['attr1'], options={}): + ''' Generic function to make a bar plot. + + groups: List of strings, name of the groups. + attributes: List of strings, name of the individual attribute to be plotted per group. Length of attributes must equal length of internal lists of data. + data: List of either data, or list of lists containing data for different attributes. All internal lists must be the same length, and the same length as attributes. Length of data must be the same as groups.''' + + default_options = { + 'hide_x_ticks': True, + 'bar.spacing': 0.25, + 'bar.border': False, + 'bar.bottom': 0, + 'data.transpose': True, + 'stack.add': True, + 'stack.border': False, + + 'colours': None, + 'labels': None, + 'markers': [None], + + 'xlabel': '', + 'xunit': '', + 'ylabel': '', + 'yunit': '', + } + + + options = aux.update_options(options=options, default_options=default_options) + + + # Transpose the data + if options['data.transpose']: + data = np.array(data).T.tolist() + + fig, ax = plo.prepare_plot(options=options) + + + # First check if there are several bars grouped together to calculate the offsets (from lists) + + x = np.arange(len(groups)) + bar_width = 1/len(attributes)*(1-options['bar.spacing']) if isinstance(data[0], list) else (1-options['bar.spacing']) + + # Create the stacked subsets of data + max_stacked = find_max_number_of_stacked_plots(data) + stacked = initialised_stacked(data=data, max_stacked=max_stacked, groups=groups) + data, stacked = populate_stacked_data(data, stacked, options=options) + + + # TODO: Add option to pass own colors + if options['colours']: + colours = options['colours'] + else: + colours = ['blue', 'red', 'green', 'yellow', 'purple'] + + for i, (y, attr) in enumerate(zip(data, attributes)): + offset = bar_width*i + colour = colours[i] + + y = [val-options['bar.bottom'] for val in y] + #y += options['bar.bottom'] + ax.bar(x + offset, y, width=bar_width, label=attr, zorder=0, color=colour, bottom=options['bar.bottom']) + + if options['bar.border']: + ax.bar(x + offset, y, width=bar_width, label=attr, fill=False, zorder=1, edgecolor='black', bottom=options['bar.bottom']) + + + hatches = ['////', '.', 'o', 'x', '*'] + for i, stack in enumerate(stacked): + + hatch = hatches[i] + + # TODO Right now, more than two stacks will be plotted wrongly because the bottom-parameter is not correct for the third stack. NEeds to update this bottom-parameter to be additive of all previous ones. + for j, (y, bottom, attr) in enumerate(zip(stack, data, attributes)): + + colour = colours[j] + offset = bar_width*j + + + ax.bar(x + offset, y, width=bar_width, label=None, bottom=bottom, fill=False, hatch=hatch, lw=0, zorder=0, edgecolor=colour) + + if options['stack.border']: + ax.bar(x + offset, y, width=bar_width, label=None, bottom=bottom, fill=False, zorder=1, edgecolor='black', lw=1) + + + + + + + if not options['labels']: + options['labels'] = attributes + + + fig, ax = plo.adjust_plot(fig=fig, ax=ax, options=options) + + ax.set_xticks(x+(bar_width*(len(attributes)-1)/2), groups) + + + return fig, ax + +def find_max_number_of_stacked_plots(data): + + max = 0 + + for d in data: + if isinstance(d, list): + for i in d: + if isinstance(i, tuple): + if len(i)-1 > max: + max = len(i)-1 + + elif isinstance(d, tuple): + if len(d)-1 > max: + max = len(d)-1 + + return max + + +def initialised_stacked(data, max_stacked, groups): + stacked = [] + for i in range(max_stacked): + subset = [] + for i in range(len(data)): + subsubset = [] + for j in range(len(groups)): + subsubset.append(0) + subset.append(subsubset) + + stacked.append(subset) + + return stacked + +def populate_stacked_data(data, stacked, options): + + for i, d in enumerate(data): + if isinstance(d, list): + for j, dd in enumerate(d): + if isinstance(dd, tuple): + for k, ddd in enumerate(dd): + if k == 0: + data[i][j] = ddd + zeroth = ddd + else: + if not options['stack.add']: + ddd = ddd - zeroth + + stacked[k-1][i][j] = ddd + + + elif isinstance(d, tuple): + for k, ddd in enumerate(dd): + if k == 0: + data[i][j] = ddd + else: + stacked[k-1][i][j] = ddd + + + return data, stacked diff --git a/plotter/plots.py b/plotter/plots.py index aa30b20..c3d0e75 100644 --- a/plotter/plots.py +++ b/plotter/plots.py @@ -8,6 +8,7 @@ import itertools from matplotlib.lines import Line2D from matplotlib.ticker import (MultipleLocator) from matplotlib.patches import Rectangle +#from mpl_toolkits.mplot3d import Axes3D # To create insets from mpl_toolkits.axes_grid1.inset_locator import (inset_axes, InsetPosition, BboxPatch, BboxConnector) @@ -47,7 +48,8 @@ def prepare_plot(options={}): 'nrows': 1, 'ncols': 1, 'grid_ratio_height': None, - 'grid_ratio_width': None + 'grid_ratio_width': None, + '3d_projection': False } format_params = aux.update_options(options=format_params, default_options=default_format_params) @@ -68,7 +70,13 @@ def prepare_plot(options={}): format_params['width'], format_params['height'] = scale_figure(format_params=format_params, width=format_params['width'], height=format_params['height']) if format_params['nrows'] == 1 and format_params['ncols'] == 1: - fig, ax = plt.subplots(figsize=(format_params['width'], format_params['height']), dpi=format_params['dpi']) + if format_params['3d_projection']: + # FIXME This does not work at all... + fig, _ = plt.subplots(figsize=(format_params['width'], format_params['height']), ncols=2, dpi=format_params['dpi']) + ax = fig.add_subplot(121, projection='3d') + ax2 = fig.add_subplot(311, projection='3d') + else: + fig, ax = plt.subplots(figsize=(format_params['width'], format_params['height']), dpi=format_params['dpi']) return fig, ax @@ -107,6 +115,7 @@ def adjust_plot(fig, ax, options): 'colours': None, 'palettes': None, 'backgrounds': [], 'legend': False, 'legend_position': ['lower center', (0.5, -0.1)], 'legend_ncol': 1, # Toggles on/off legend. Specifices legend position and the number of columns the legend should appear as. + 'markers': [None], 'subplots_adjust': {'left': None, 'right': None, 'top': None, 'bottom': None, 'wspace': None, 'hspace': None}, # Adjustment of the Axes-object within the Figure-object. Fraction of the Figure-object the left, bottom, right and top edges of the Axes-object will start. 'x_tick_params': {'direction': 'in', 'which': 'both', 'top': True, 'bottom': True}, 'y_tick_params': {'direction': 'in', 'which': 'both', 'left': True, 'right': True}, @@ -232,7 +241,6 @@ def adjust_plot(fig, ax, options): active_labels.append(label) - ax.legend(active_markers, active_labels, frameon=False, loc=options['legend_position'][0], bbox_to_anchor=options['legend_position'][1], ncol=options['legend_ncol']) #fig.legend(handles=patches, loc=options['legend_position'][0], bbox_to_anchor=options['legend_position'][1], frameon=False) @@ -428,4 +436,4 @@ def connect_inset(parent_axes, inset_axes, loc1a=1, loc1b=1, loc2a=2, loc2b=2, * - \ No newline at end of file +