mgplot.line_plot
line_plot.py: Plot a series or a dataframe with lines.
1""" 2line_plot.py: 3Plot a series or a dataframe with lines. 4""" 5 6# --- imports 7from typing import Any 8from collections.abc import Sequence 9import matplotlib.pyplot as plt 10from pandas import DataFrame, Period 11 12from mgplot.settings import DataT, get_setting 13from mgplot.finalise_plot import make_legend 14from mgplot.kw_type_checking import ( 15 report_kwargs, 16 validate_kwargs, 17 validate_expected, 18 ExpectedTypeDict, 19) 20from mgplot.utilities import ( 21 apply_defaults, 22 get_color_list, 23 get_axes, 24 annotate_series, 25 constrain_data, 26 check_clean_timeseries, 27) 28 29 30# --- constants 31DATA = "data" 32AX = "ax" 33STYLE, WIDTH, COLOR, ALPHA = "style", "width", "color", "alpha" 34ANNOTATE = "annotate" 35ROUNDING = "rounding" 36FONTSIZE = "fontsize" 37DROPNA = "dropna" 38DRAWSTYLE, MARKER, MARKERSIZE = "drawstyle", "marker", "markersize" 39PLOT_FROM = "plot_from" # used to constrain the data to a starting point 40LEGEND = "legend" 41 42LINE_KW_TYPES: ExpectedTypeDict = { 43 AX: (plt.Axes, type(None)), 44 STYLE: (str, Sequence, (str,)), 45 WIDTH: (float, int, Sequence, (float, int)), 46 COLOR: (str, Sequence, (str,)), 47 ALPHA: (float, Sequence, (float,)), 48 DRAWSTYLE: (str, Sequence, (str,), type(None)), 49 MARKER: (str, Sequence, (str,), type(None)), 50 MARKERSIZE: (float, Sequence, (float,), int, type(None)), 51 DROPNA: (bool, Sequence, (bool,)), 52 ANNOTATE: (bool, Sequence, (bool,)), 53 ROUNDING: (Sequence, (bool, int), int, bool, type(None)), 54 FONTSIZE: (Sequence, (str, int), str, int, type(None)), 55 PLOT_FROM: (int, Period, type(None)), 56 LEGEND: (dict, (str, object), bool, type(None)), 57} 58validate_expected(LINE_KW_TYPES, "line_plot") 59 60 61# --- functions 62def _get_style_width_color_etc( 63 item_count, num_data_points, **kwargs 64) -> tuple[dict[str, list | tuple], dict[str, Any]]: 65 """ 66 Get the plot-line attributes arguemnts. 67 Returns a dictionary of lists of attributes for each line, and 68 a modified kwargs dictionary. 69 """ 70 71 data_point_thresh = 151 72 defaults: dict[str, Any] = { 73 STYLE: "-", 74 WIDTH: ( 75 get_setting("line_normal") 76 if num_data_points > data_point_thresh 77 else get_setting("line_wide") 78 ), 79 COLOR: kwargs.get(COLOR, get_color_list(item_count)), 80 ALPHA: 1.0, 81 DRAWSTYLE: None, 82 MARKER: None, 83 MARKERSIZE: 10, 84 DROPNA: True, 85 ANNOTATE: False, 86 ROUNDING: True, 87 FONTSIZE: "small", 88 } 89 90 return apply_defaults(item_count, defaults, kwargs) 91 92 93def line_plot(data: DataT, **kwargs) -> plt.Axes: 94 """ 95 Build a single plot from the data passed in. 96 This can be a single- or multiple-line plot. 97 Return the axes object for the build. 98 99 Agruments: 100 - data: DataFrame | Series - data to plot 101 - kwargs: 102 - ax: plt.Axes | None - axes to plot on (optional) 103 - dropna: bool | list[bool] - whether to delete NAs frm the 104 data before plotting [optional] 105 - color: str | list[str] - line colors. 106 - width: float | list[float] - line widths [optional]. 107 - style: str | list[str] - line styles [optional]. 108 - alpha: float | list[float] - line transparencies [optional]. 109 - marker: str | list[str] - line markers [optional]. 110 - marker_size: float | list[float] - line marker sizes [optional]. 111 - annotate: bool | list[bool] - whether to annotate a series. 112 - rounding: int | bool | list[int | bool] - number of decimal places 113 to round an annotation. If True, a default between 0 and 2 is 114 used. 115 - fontsize: int | str | list[int | str] - font size for the 116 annotation. 117 - drawstyle: str | list[str] - matplotlib line draw styles. 118 119 Returns: 120 - axes: plt.Axes - the axes object for the plot 121 """ 122 123 # --- check the kwargs 124 me = "line_plot" 125 report_kwargs(called_from=me, **kwargs) 126 validate_kwargs(LINE_KW_TYPES, me, **kwargs) 127 128 # --- check the data 129 data = check_clean_timeseries(data, me) 130 df = DataFrame(data) # really we are only plotting DataFrames 131 df, kwargs = constrain_data(df, **kwargs) 132 133 # --- some special defaults 134 if len(df.columns) > 1: 135 # default to displaying a legend 136 kwargs["legend"] = kwargs.get("legend", True) 137 if len(df.columns) > 4: 138 # default to using a style for the lines 139 kwargs["style"] = kwargs.get("style", ["solid", "dashed", "dashdot", "dotted"]) 140 141 # --- Let's plot 142 axes, kwargs = get_axes(**kwargs) # get the axes to plot on 143 if df.empty or df.isna().all().all(): 144 # Note: finalise plot will ignore an empty axes object 145 print("Warning: No data to plot.") 146 return axes 147 148 # --- get the arguments for each line we will plot ... 149 item_count = len(df.columns) 150 num_data_points = len(df) 151 swce, kwargs = _get_style_width_color_etc(item_count, num_data_points, **kwargs) 152 153 for i, column in enumerate(df.columns): 154 series = df[column] 155 series = series.dropna() if DROPNA in swce and swce[DROPNA][i] else series 156 if series.empty or series.isna().all(): 157 continue 158 159 axes = series.plot( 160 ls=swce[STYLE][i], 161 lw=swce[WIDTH][i], 162 color=swce[COLOR][i], 163 alpha=swce[ALPHA][i], 164 marker=swce[MARKER][i], 165 ms=swce[MARKERSIZE][i], 166 drawstyle=swce[DRAWSTYLE][i], 167 ax=axes, 168 ) 169 170 if swce[ANNOTATE][i] is None or not swce[ANNOTATE][i]: 171 continue 172 173 annotate_series( 174 series, 175 axes, 176 rounding=swce[ROUNDING][i], 177 color=swce[COLOR][i], 178 fontsize=swce[FONTSIZE][i], 179 ) 180 181 # add a legend if requested 182 if len(df.columns) > 1: 183 kwargs[LEGEND] = kwargs.get(LEGEND, get_setting("legend")) 184 185 if LEGEND in kwargs: 186 make_legend(axes, kwargs[LEGEND]) 187 188 return axes
DATA =
'data'
AX =
'ax'
ANNOTATE =
'annotate'
ROUNDING =
'rounding'
FONTSIZE =
'fontsize'
DROPNA =
'dropna'
PLOT_FROM =
'plot_from'
LEGEND =
'legend'
LINE_KW_TYPES: mgplot.kw_type_checking.ExpectedTypeDict =
{'ax': (<class 'matplotlib.axes._axes.Axes'>, <class 'NoneType'>), 'style': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'width': (<class 'float'>, <class 'int'>, <class 'collections.abc.Sequence'>, (<class 'float'>, <class 'int'>)), 'color': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,)), 'alpha': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,)), 'drawstyle': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'marker': (<class 'str'>, <class 'collections.abc.Sequence'>, (<class 'str'>,), <class 'NoneType'>), 'markersize': (<class 'float'>, <class 'collections.abc.Sequence'>, (<class 'float'>,), <class 'int'>, <class 'NoneType'>), 'dropna': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'annotate': (<class 'bool'>, <class 'collections.abc.Sequence'>, (<class 'bool'>,)), 'rounding': (<class 'collections.abc.Sequence'>, (<class 'bool'>, <class 'int'>), <class 'int'>, <class 'bool'>, <class 'NoneType'>), 'fontsize': (<class 'collections.abc.Sequence'>, (<class 'str'>, <class 'int'>), <class 'str'>, <class 'int'>, <class 'NoneType'>), 'plot_from': (<class 'int'>, <class 'pandas._libs.tslibs.period.Period'>, <class 'NoneType'>), 'legend': (<class 'dict'>, (<class 'str'>, <class 'object'>), <class 'bool'>, <class 'NoneType'>)}
def
line_plot(data: ~DataT, **kwargs) -> matplotlib.axes._axes.Axes:
94def line_plot(data: DataT, **kwargs) -> plt.Axes: 95 """ 96 Build a single plot from the data passed in. 97 This can be a single- or multiple-line plot. 98 Return the axes object for the build. 99 100 Agruments: 101 - data: DataFrame | Series - data to plot 102 - kwargs: 103 - ax: plt.Axes | None - axes to plot on (optional) 104 - dropna: bool | list[bool] - whether to delete NAs frm the 105 data before plotting [optional] 106 - color: str | list[str] - line colors. 107 - width: float | list[float] - line widths [optional]. 108 - style: str | list[str] - line styles [optional]. 109 - alpha: float | list[float] - line transparencies [optional]. 110 - marker: str | list[str] - line markers [optional]. 111 - marker_size: float | list[float] - line marker sizes [optional]. 112 - annotate: bool | list[bool] - whether to annotate a series. 113 - rounding: int | bool | list[int | bool] - number of decimal places 114 to round an annotation. If True, a default between 0 and 2 is 115 used. 116 - fontsize: int | str | list[int | str] - font size for the 117 annotation. 118 - drawstyle: str | list[str] - matplotlib line draw styles. 119 120 Returns: 121 - axes: plt.Axes - the axes object for the plot 122 """ 123 124 # --- check the kwargs 125 me = "line_plot" 126 report_kwargs(called_from=me, **kwargs) 127 validate_kwargs(LINE_KW_TYPES, me, **kwargs) 128 129 # --- check the data 130 data = check_clean_timeseries(data, me) 131 df = DataFrame(data) # really we are only plotting DataFrames 132 df, kwargs = constrain_data(df, **kwargs) 133 134 # --- some special defaults 135 if len(df.columns) > 1: 136 # default to displaying a legend 137 kwargs["legend"] = kwargs.get("legend", True) 138 if len(df.columns) > 4: 139 # default to using a style for the lines 140 kwargs["style"] = kwargs.get("style", ["solid", "dashed", "dashdot", "dotted"]) 141 142 # --- Let's plot 143 axes, kwargs = get_axes(**kwargs) # get the axes to plot on 144 if df.empty or df.isna().all().all(): 145 # Note: finalise plot will ignore an empty axes object 146 print("Warning: No data to plot.") 147 return axes 148 149 # --- get the arguments for each line we will plot ... 150 item_count = len(df.columns) 151 num_data_points = len(df) 152 swce, kwargs = _get_style_width_color_etc(item_count, num_data_points, **kwargs) 153 154 for i, column in enumerate(df.columns): 155 series = df[column] 156 series = series.dropna() if DROPNA in swce and swce[DROPNA][i] else series 157 if series.empty or series.isna().all(): 158 continue 159 160 axes = series.plot( 161 ls=swce[STYLE][i], 162 lw=swce[WIDTH][i], 163 color=swce[COLOR][i], 164 alpha=swce[ALPHA][i], 165 marker=swce[MARKER][i], 166 ms=swce[MARKERSIZE][i], 167 drawstyle=swce[DRAWSTYLE][i], 168 ax=axes, 169 ) 170 171 if swce[ANNOTATE][i] is None or not swce[ANNOTATE][i]: 172 continue 173 174 annotate_series( 175 series, 176 axes, 177 rounding=swce[ROUNDING][i], 178 color=swce[COLOR][i], 179 fontsize=swce[FONTSIZE][i], 180 ) 181 182 # add a legend if requested 183 if len(df.columns) > 1: 184 kwargs[LEGEND] = kwargs.get(LEGEND, get_setting("legend")) 185 186 if LEGEND in kwargs: 187 make_legend(axes, kwargs[LEGEND]) 188 189 return axes
Build a single plot from the data passed in. This can be a single- or multiple-line plot. Return the axes object for the build.
Agruments:
- data: DataFrame | Series - data to plot
- kwargs:
- ax: plt.Axes | None - axes to plot on (optional)
- dropna: bool | list[bool] - whether to delete NAs frm the data before plotting [optional]
- color: str | list[str] - line colors.
- width: float | list[float] - line widths [optional].
- style: str | list[str] - line styles [optional].
- alpha: float | list[float] - line transparencies [optional].
- marker: str | list[str] - line markers [optional].
- marker_size: float | list[float] - line marker sizes [optional].
- annotate: bool | list[bool] - whether to annotate a series.
- rounding: int | bool | list[int | bool] - number of decimal places to round an annotation. If True, a default between 0 and 2 is used.
- fontsize: int | str | list[int | str] - font size for the annotation.
- drawstyle: str | list[str] - matplotlib line draw styles.
Returns:
- axes: plt.Axes - the axes object for the plot