Source code for mplsoccer.bumpy_chart

"""A Python module for plotting bump chart.

Author: Anmol_Durgapal(@slothfulwave612)
"""

import warnings

import matplotlib.pyplot as plt
# import required packages/modules
import numpy as np
from matplotlib import patches
from matplotlib.path import Path

from mplsoccer.utils import set_labels

# ignore UserWarning
warnings.simplefilter("ignore", UserWarning)

__all__ = ['Bumpy']


[docs] class Bumpy: """ A class for plotting bump-charts in Matplotlib Parameters ---------- background_color : str, default "#1B1B1B" The background-color of the plot. scatter : bool, default True To plot scatter points or not. "value" --> scatter point for highlighted attribute. scatter_color : str, default "#4F535C" Color value for our scatter points. line_color : str, default None Color value for the connecting lines. if None --> takes the same color as scatter_color. scatter_points : str, default 'o' Type of marker user wants to plot. scatter_primary : str, default None Type of marker user wants to plot for highlighted attribute. scatter_size : float, default 100 Size of the scatter_points. ticklabel_size : float, default 13 Fontsize of the ticklabel. curviness : float, default 0.85 Value of the curved line. rotate_xticks : float, default 0 Rotation of xticklabels in degrees. rotate_yticks : float, default 0 Rotation of yticklabels. show_right : bool, default False yticklabels to be shown at the right y-axis or not. label_size : float, default 20 Fontsize of the x and y labels. labelpad : float, default 20 Padding between labels and ticklables. alignment_xvalue : float, default 0.035 Value for alignment of x-label. alignment_yvalue : float, default 0.16 Value for alignment of y-label label_color : str, default "#FFFFFF" Color value for labels. plot_labels : bool, default True To plot the labels. """ def __init__(self, background_color="#1B1B1B", scatter=True, scatter_color="#4F535C", line_color=None, scatter_points='o', scatter_primary=None, scatter_size=100, ticklabel_size=13, curviness=0.85, rotate_xticks=0, rotate_yticks=0, show_right=False, label_size=20, labelpad=20, alignment_xvalue=0.035, alignment_yvalue=0.16, label_color='#F2F2F2', plot_labels=True): self.background_color = background_color self.scatter = scatter self.scatter_color = scatter_color self.scatter_points = scatter_points self.scatter_size = scatter_size self.ticklabel_size = ticklabel_size self.curviness = curviness self.rotate_xticks = rotate_xticks self.rotate_yticks = rotate_yticks self.show_right = show_right self.label_size = label_size self.labelpad = labelpad self.align_xval = alignment_xvalue self.align_yval = alignment_yvalue self.label_color = label_color self.plot_labels = plot_labels if line_color is None: self.line_color = scatter_color else: self.line_color = line_color if scatter_primary is None: self.scatter_primary = self.scatter_points else: self.scatter_primary = scatter_primary def __repr__(self): return (f'{self.__class__.__name__}(' f'background_color={self.background_color}, ' f'scatter={self.scatter}, ' f'scatter_color={self.scatter_color}, ' f'scatter_points={self.scatter_points}, ' f'scatter_size={self.scatter_size}, ' f'ticklabel_size={self.ticklabel_size}, ' f'curviness={self.curviness}) ' f'rotate_xticks={self.rotate_xticks}) ' f'rotate_yticks={self.rotate_yticks}) ' f'show_right={self.show_right}) ' f'label_size={self.label_size}) ' f'labelpad={self.labelpad}) ' f'align_xval={self.align_xval}) ' f'align_yval={self.align_yval}) ' f'label_color={self.label_color}) ' f'plot_labels={self.plot_labels}) ')
[docs] def plot(self, x_list, y_list, values, highlight_dict, figsize=(12, 8), lw=2, secondary_alpha=1, x_label=None, y_label=None, xlim=None, ylim=None, ax=None, upside_down=False, **kwargs): """ Function to plot bumpy-chart. Parameters ---------- x_list : sequence of float/str xticklabel values(serial-wise order from left to right). y_list : sequence of float/str yticklabel values(serial-wise order from top to bottom). values : dict Containing key as team-name and value as list of rank for that team. highlight_dict : dict Containing key as the team-name to be highlighted with their corresponding color. figsize : tuple, default (12,8) Size of the plot. Defaults to (12,8). lw : int, default 2 Line-width for the lines in the plot. secondary_alpha : float, default 1 Alpha value for non-shaded lines/markers. x_label, y_label : str, default None x-label and y-label name xlim, ylim: tuple, default None Limit for x-axis and y-axis respectively. ax : axes.Axes object, default None axes object on which chart will be plotted. upside_down : bool, default False To plot chart upside down. **kwargs : All other keyword arguments are passed for setting ticklabels and labels. Returns ------- If ax=None returns a matplotlib Figure and Axes. Else the settings are applied on an existing axis and returns None. """ if ax is None: # create subplot fig, ax = plt.subplots(figsize=figsize, facecolor=self.background_color) ax.set_facecolor(self.background_color) return_figax = True else: return_figax = False # length of values dict len_y = len(y_list) # iterate thorugh the dictionary and plot the chart for key, value in values.items(): # find value in highlight_dict if highlight_dict.get(key): line_color = highlight_dict[key] # fetch the required color color = line_color zorder = 3 alpha = 1 marker = self.scatter_primary else: color = self.scatter_color line_color = self.line_color zorder = 2 alpha = secondary_alpha marker = self.scatter_points # to plot upside down bumpy chart if upside_down: if len_y % 2 == 0: add_value = 0 else: add_value = 1 # y-coordinate to plot scatter points y = np.array(value) + add_value # coordinates for bezier curve verts = [(i + d, vij + add_value) for i, vij in enumerate(value) for d in (-self.curviness, 0, self.curviness)][1: -1] else: if len_y % 2 == 0: add_value = 1 else: add_value = 0 # y-coordinate to plot scatter points y = len_y - np.array(value) + add_value # coordinates for bezier curve verts = [(i + d, len_y - vij + add_value) for i, vij in enumerate(value) for d in (-self.curviness, 0, self.curviness)][1: -1] # plot scatter-points if self.scatter != "value": ax.scatter( np.arange(len(value)), y, marker=marker, color=color, s=self.scatter_size, alpha=alpha, zorder=zorder ) elif self.scatter == "value" and highlight_dict.get(key): ax.scatter( np.arange(len(value)), y, marker=marker, color=color, s=self.scatter_size, zorder=zorder ) # create bezier curves codes = [Path.MOVETO] + [Path.CURVE4] * (len(verts) - 1) path = Path(verts, codes) patch = patches.PathPatch(path, facecolor='none', lw=lw, edgecolor=line_color, zorder=zorder, alpha=alpha) ax.add_patch(patch) # plot labels if self.plot_labels: if upside_down: y_list = y_list[::-1] self.__add_labels( x_list, y_list, ax=ax, x_label=x_label, y_label=y_label, **kwargs ) # xlim and ylim if xlim is not None: ax.set(xlim=xlim) elif ylim is not None: ax.set(ylim=ylim) if return_figax: return fig, ax return None
def __add_labels(self, x_list, y_list, ax, x_label, y_label, **kwargs): """ Function to add labels and titles to the plot. Parameters ---------- x_list : sequence of float/str xticklabel values(serial-wise order from left to right). y_list : sequence of float/str yticklabel values(serial-wise order from top to bottom). ax : axes.Axes object axes object on which chart will be plotted. x_label, y_label : str x-label and y-label name **kwargs : All other keyword arguments are passed on to set ticklabels and labels. """ # remove spines ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) ax.spines['left'].set_visible(False) ax.spines['bottom'].set_visible(False) # get labels for x and y axis x_labels = set_labels(ax=ax, label_value=x_list, label_axis='x') y_labels = set_labels(ax=ax, label_value=y_list, label_axis='y') # set ticklabels ax.set_xticklabels(x_labels, fontsize=self.ticklabel_size, rotation=self.rotate_xticks, **kwargs) ax.set_yticklabels(y_labels, fontsize=self.ticklabel_size, rotation=self.rotate_yticks, **kwargs) # set x and y axis labels ax.set_xlabel( x_label, fontsize=self.label_size, labelpad=self.labelpad, x=self.align_xval, **kwargs ) ax.set_ylabel( y_label, fontsize=self.label_size, labelpad=self.labelpad, y=self.align_yval, **kwargs ) ax.xaxis.label.set_color(self.label_color) ax.yaxis.label.set_color(self.label_color) # remove tick marks ax.tick_params(axis='both', which='both', length=0, colors=self.label_color) if self.show_right: ax.tick_params( direction='out', axis='y', which='both', labelleft=True, labelright=True, right=True, left=True ) # __str__ is the same as __repr__ __str__ = __repr__