# -*- coding: utf-8 -*-
"""
Utilities for helping to plot cellpy-data.
"""
import collections
import importlib
import itertools
import logging
import os
import sys
import warnings
from io import StringIO
from pathlib import Path
import matplotlib.pyplot as plt
from cellpy.parameters.internal_settings import (
get_headers_journal,
get_headers_normal,
get_headers_step_table,
get_headers_summary,
)
from cellpy.utils import helpers
[docs]
plotly_available = importlib.util.find_spec("plotly") is not None
[docs]
seaborn_available = importlib.util.find_spec("seaborn") is not None
# logger = logging.getLogger(__name__)
logging.captureWarnings(True)
[docs]
SYMBOL_DICT = {
"all": [
"s",
"o",
"v",
"^",
"<",
">",
"D",
"p",
"*",
"1",
"2",
".",
",",
"3",
"4",
"8",
"p",
"d",
"h",
"H",
"+",
"x",
"X",
"|",
"_",
],
"simple": ["s", "o", "v", "^", "<", ">", "*", "d"],
}
[docs]
COLOR_DICT = {
"classic": ["b", "g", "r", "c", "m", "y", "k"],
"grayscale": ["0.00", "0.40", "0.60", "0.70"],
"bmh": [
"#348ABD",
"#A60628",
"#7A68A6",
"#467821",
"#D55E00",
"#CC79A7",
"#56B4E9",
"#009E73",
"#F0E442",
"#0072B2",
],
"dark_background": [
"#8dd3c7",
"#feffb3",
"#bfbbd9",
"#fa8174",
"#81b1d2",
"#fdb462",
"#b3de69",
"#bc82bd",
"#ccebc4",
"#ffed6f",
],
"ggplot": [
"#E24A33",
"#348ABD",
"#988ED5",
"#777777",
"#FBC15E",
"#8EBA42",
"#FFB5B8",
],
"fivethirtyeight": ["#30a2da", "#fc4f30", "#e5ae38", "#6d904f", "#8b8b8b"],
"seaborn-colorblind": [
"#0072B2",
"#009E73",
"#D55E00",
"#CC79A7",
"#F0E442",
"#56B4E9",
],
"seaborn-deep": ["#4C72B0", "#55A868", "#C44E52", "#8172B2", "#CCB974", "#64B5CD"],
"seaborn-bright": [
"#003FFF",
"#03ED3A",
"#E8000B",
"#8A2BE2",
"#FFC400",
"#00D7FF",
],
"seaborn-muted": ["#4878CF", "#6ACC65", "#D65F5F", "#B47CC7", "#C4AD66", "#77BEDB"],
"seaborn-pastel": [
"#92C6FF",
"#97F0AA",
"#FF9F9A",
"#D0BBFF",
"#FFFEA3",
"#B0E0E6",
],
"seaborn-dark-palette": [
"#001C7F",
"#017517",
"#8C0900",
"#7600A1",
"#B8860B",
"#006374",
],
}
_hdr_summary = get_headers_summary()
_hdr_raw = get_headers_normal()
_hdr_steps = get_headers_step_table()
_hdr_journal = get_headers_journal()
[docs]
def create_colormarkerlist_for_journal(
journal, symbol_label="all", color_style_label="seaborn-colorblind"
):
"""Fetch lists with color names and marker types of correct length for a journal.
Args:
journal: cellpy journal
symbol_label: sub-set of markers to use
color_style_label: cmap to use for colors
Returns:
colors (list), markers (list)
"""
logging.debug("symbol_label: " + symbol_label)
logging.debug("color_style_label: " + color_style_label)
groups = journal.pages[_hdr_journal.group].unique()
sub_groups = journal.pages[_hdr_journal.subgroup].unique()
return create_colormarkerlist(groups, sub_groups, symbol_label, color_style_label)
[docs]
def create_colormarkerlist(
groups, sub_groups, symbol_label="all", color_style_label="seaborn-colorblind"
):
"""Fetch lists with color names and marker types of correct length.
Args:
groups: list of group numbers (used to generate the list of colors)
sub_groups: list of sub-group numbers (used to generate the list of markers).
symbol_label: sub-set of markers to use
color_style_label: cmap to use for colors
Returns:
colors (list), markers (list)
"""
symbol_list = SYMBOL_DICT[symbol_label]
color_list = COLOR_DICT[color_style_label]
# checking that we have enough colors and symbols (if not, then use cycler (e.g. reset))
color_cycler = itertools.cycle(color_list)
symbol_cycler = itertools.cycle(symbol_list)
_color_list = []
_symbol_list = []
for i in groups:
_color_list.append(next(color_cycler))
for i in sub_groups:
_symbol_list.append(next(symbol_cycler))
return _color_list, _symbol_list
[docs]
def create_col_info(c):
"""Create column information for summary plots.
Args:
c: cellpy object
Returns:
x_columns (tuple), y_cols (dict)
"""
# TODO: add support for more column sets and individual columns
hdr = c.headers_summary
_cap_cols = [hdr.charge_capacity_raw, hdr.discharge_capacity_raw]
_capacities_gravimetric = [col + "_gravimetric" for col in _cap_cols]
_capacities_gravimetric_split = (
_capacities_gravimetric
+ [col + "_cv" for col in _capacities_gravimetric]
+ [col + "_non_cv" for col in _capacities_gravimetric]
)
_capacities_areal = [col + "_areal" for col in _cap_cols]
_capacities_areal_split = (
_capacities_areal
+ [col + "_cv" for col in _capacities_areal]
+ [col + "_non_cv" for col in _capacities_areal]
)
x_columns = ([hdr.cycle_index, hdr.data_point, hdr.test_time, hdr.datetime],)
y_cols = dict(
voltages=[hdr.end_voltage_charge, hdr.end_voltage_discharge],
capacities_gravimetric=_capacities_gravimetric,
capacities_areal=_capacities_areal,
capacities=_cap_cols,
capacities_gravimetric_split_constant_voltage=_capacities_gravimetric_split,
capacities_areal_split_constant_voltage=_capacities_areal_split,
)
return x_columns, y_cols
[docs]
def create_label_dict(c):
"""Create label dictionary for summary plots.
Args:
c: cellpy object
Returns:
x_axis_labels (dict), y_axis_label (dict)
"""
hdr = c.headers_summary
x_axis_labels = {
hdr.cycle_index: "Cycle Number",
hdr.data_point: "Point",
hdr.test_time: f"Test Time ({c.cellpy_units.time})",
hdr.datetime: "Date",
}
_cap_gravimetric_label = (
f"Capacity ({c.cellpy_units.charge}/{c.cellpy_units.specific_gravimetric})"
)
_cap_areal_label = (
f"Capacity ({c.cellpy_units.charge}/{c.cellpy_units.specific_areal})"
)
_cap_label = f"Capacity ({c.cellpy_units.charge})"
y_axis_label = {
"voltages": f"Voltage ({c.cellpy_units.voltage})",
"capacities_gravimetric": _cap_gravimetric_label,
"capacities_areal": _cap_areal_label,
"capacities": _cap_label,
"capacities_gravimetric_split_constant_voltage": _cap_gravimetric_label,
"capacities_areal_split_constant_voltage": _cap_areal_label,
}
return x_axis_labels, y_axis_label
[docs]
def summary_plot(
c,
x: str = None,
y: str = "capacities_gravimetric",
height: int = 600,
markers: bool = True,
title=None,
x_range: list = None,
y_range: list = None,
split: bool = False,
interactive: bool = True,
share_y: bool = False,
rangeslider: bool = False,
**kwargs,
):
"""Create a summary plot. Currently only supports plotly.
Args:
c: cellpy object
x: x-axis column (default: 'cycle_index')
y: y-axis column or column set. Currently, the following predefined sets exists:
- "voltages", "capacities_gravimetric", "capacities_areal", "capacities",
"capacities_gravimetric_split_constant_voltage", "capacities_areal_split_constant_voltage"
height: height of the plot
markers: use markers
title: title of the plot
x_range: limits for x-axis
y_range: limits for y-axis
split: split the plot
interactive: use interactive plotting
rangeslider: add a range slider to the x-axis (only for plotly)
share_y (bool): share y-axis
**kwargs: additional parameters for the plotting backend
Returns:
``plotly`` figure or None
"""
if plotly_available and interactive:
import plotly.express as px
else:
warnings.warn(
"plotly not available, and it is currently the only supported backend"
)
return None
if title is None:
title = f"Summary <b>{c.cell_name}</b>"
if x is None:
x = "cycle_index"
x_columns, y_cols = create_col_info(c)
x_axis_labels, y_axis_label = create_label_dict(c)
# ------------------- main --------------------------------------------
y_header = "value"
color = "variable"
additional_kwargs = dict(
color=color,
height=height,
markers=markers,
title=title,
)
# filter on constant voltage vs constant current
if y.endswith("_split_constant_voltage"):
cap_type = (
"capacities_gravimetric"
if y.startswith("capacities_gravimetric")
else "capacities_areal"
)
column_set = y_cols[cap_type]
s = partition_summary_cv_steps(c, x, column_set, split, color, y_header)
if split:
additional_kwargs["facet_row"] = "row"
# simple case
else:
column_set = y_cols.get(y, y)
if isinstance(column_set, str):
column_set = [column_set]
summary = c.data.summary
summary = summary.reset_index()
s = summary.melt(x)
s = s.loc[s.variable.isin(column_set)]
s = s.reset_index(drop=True)
x_label = x_axis_labels.get(x, x)
y_label = y_axis_label.get(y, y)
fig = px.line(
s,
x=x,
y=y_header,
**additional_kwargs,
labels={
x: x_label,
y_header: y_label,
},
**kwargs,
)
if x_range is not None:
fig.update_layout(xaxis=dict(range=x_range))
if y_range is not None:
fig.update_layout(yaxis=dict(range=y_range))
elif split and not share_y:
fig.update_yaxes(matches=None)
if rangeslider:
fig.update_layout(xaxis_rangeslider_visible=True)
return fig
[docs]
def partition_summary_cv_steps(
c,
x: str,
column_set: list,
split: bool = False,
var_name: str = "variable",
value_name: str = "value",
):
"""Partition the summary data into CV and non-CV steps.
Args:
c: cellpy object
x: x-axis column name
column_set: names of columns to include
split: add additional column that can be used to split the data when plotting.
var_name: name of the variable column after melting
value_name: name of the value column after melting
Returns:
``pandas.DataFrame`` (melted with columns x, var_name, value_name, and optionally "row" if split is True)
"""
import pandas as pd
summary = c.data.summary
summary = summary[column_set]
summary_no_cv = c.make_summary(
selector_type="non-cv", create_copy=True
).data.summary[column_set]
summary_no_cv.columns = [col + "_non_cv" for col in summary_no_cv.columns]
summary_only_cv = c.make_summary(
selector_type="only-cv", create_copy=True
).data.summary[column_set]
summary_only_cv.columns = [col + "_cv" for col in summary_only_cv.columns]
if split:
id_vars = [x, "row"]
summary_no_cv["row"] = "without CV"
summary_only_cv["row"] = "with CV"
summary["row"] = "all"
else:
id_vars = x
summary_no_cv = summary_no_cv.reset_index()
summary_only_cv = summary_only_cv.reset_index()
summary = summary.reset_index()
summary_no_cv = summary_no_cv.melt(
id_vars, var_name=var_name, value_name=value_name
)
summary_only_cv = summary_only_cv.melt(
id_vars, var_name=var_name, value_name=value_name
)
summary = summary.melt(id_vars, var_name=var_name, value_name=value_name)
s = pd.concat([summary, summary_no_cv, summary_only_cv], axis=0)
s = s.reset_index(drop=True)
return s
[docs]
def raw_plot(
cell,
y=None,
y_label=None,
x=None,
x_label=None,
title=None,
interactive=True,
plot_type="voltage-current",
double_y=True,
return_matplotlib_figure=False,
**kwargs,
):
"""Plot raw data.
Args:
cell: cellpy object
y (str or list): y-axis column
y_label (str or list): label for y-axis
x (str): x-axis column
x_label (str): label for x-axis
title (str): title of the plot
interactive (bool): use interactive plotting
plot_type (str): type of plot (defaults to "voltage-current") (overrides given y if y is not None),
currently only "voltage-current" is supported.
double_y (bool): use double y-axis (only for matplotlib and when plot_type is used)
return_matplotlib_figure (bool): return a matplotlib figure (only for matplotlib)
**kwargs: additional parameters for the plotting backend
Returns:
``matplotlib`` figure or ``plotly`` figure
"""
_set_individual_y_labels = False
raw = cell.data.raw.copy()
if y is not None:
if y_label is None:
y_label = y
y = [y]
y_label = [y_label]
elif plot_type is not None:
# special pre-defined plot types
if plot_type == "voltage-current":
y1 = _hdr_raw["voltage_txt"]
y1_label = f"Voltage ({cell.data.raw_units.voltage})"
y2 = _hdr_raw["current_txt"]
y2_label = f"Current ({cell.data.raw_units.current})"
y = [y1, y2]
y_label = [y1_label, y2_label]
else:
warnings.warn(f"Plot type {plot_type} not supported")
return None
else:
# default to voltage if y is not given
y = [_hdr_raw["voltage_txt"]]
y_label = [f"Voltage ({cell.data.raw_units.voltage})"]
if x is None:
x, x_label = ("test_time_hrs", "Time (hours)")
if title is None:
title = f"{cell.cell_name}"
if x == "test_time_hrs":
raw["test_time_hrs"] = raw[_hdr_raw["test_time_txt"]] / 3600
if plotly_available and interactive:
title = f"<b>{title}</b>"
if len(y) == 1:
# single plot
import plotly.express as px
if x_label or y_label:
labels = {}
if x_label:
labels[x] = x_label
if y_label:
labels[y[0]] = y_label[0]
else:
labels = None
fig = px.line(raw, x=x, y=y[0], title=title, labels=labels, **kwargs)
else:
# double plot
from plotly.subplots import make_subplots
import plotly.graph_objects as go
fig = make_subplots(
rows=2,
cols=1,
shared_xaxes=True,
vertical_spacing=0.02,
x_title=x_label,
)
x_values = raw[x]
fig.add_trace(
go.Scatter(x=x_values, y=raw[y[0]], name=y_label[0]),
row=1,
col=1,
)
fig.add_trace(
go.Scatter(x=x_values, y=raw[y[1]], name=y_label[1]),
row=2,
col=1,
)
fig.update_layout(height=600, title_text=title)
if _set_individual_y_labels:
fig.update_yaxes(title_text=y_label[0], row=1, col=1)
fig.update_yaxes(title_text=y_label[1], row=2, col=1)
return fig
# default to a simple matplotlib figure
xlim = kwargs.get("xlim")
if len(y) == 1:
y = y[0]
y_label = y_label[0]
fig, ax = plt.subplots()
ax.plot(raw[x], raw[y])
ax.set_xlabel(x_label)
ax.set_ylabel(y_label)
ax.set_title(title)
ax.set_xlim(xlim)
if return_matplotlib_figure:
return fig
return
if not double_y:
fig, (ax_v, ax_c) = plt.subplots(nrows=2, ncols=1, figsize=(20, 8), sharex=True)
ax_v.plot(raw[x], raw[y[0]])
ax_c.plot(raw[x], raw[y[1]])
ax_v.set_ylabel(y_label[0])
ax_c.set_ylabel(y_label[1])
ax_c.set_xlabel(x_label)
ax_v.set_title(title)
ax_v.set_xlim(xlim)
else:
fig, ax_v = plt.subplots(figsize=(12, 4))
color = "tab:red"
ax_v.set_xlabel(x_label)
ax_v.set_ylabel(y_label[0], color=color)
ax_v.plot(raw[x], raw[y[0]], label=y_label[0], color=color)
ax_v.tick_params(axis="y", labelcolor=color)
ax_c = ax_v.twinx()
color = "tab:blue"
ax_c.set_ylabel(y_label[1], color=color)
ax_c.plot(raw[x], raw[y[1]], label=y_label[1], color=color)
ax_c.tick_params(axis="y", labelcolor=color)
ax_v.set_xlim(xlim)
fig.tight_layout()
if return_matplotlib_figure:
return fig
return
[docs]
def cycle_info_plot(
cell,
cycle=None,
get_axes=False,
interactive=True,
t_unit="hours",
v_unit="V",
i_unit="mA",
**kwargs,
):
"""Show raw data together with step and cycle information.
Args:
cell: cellpy object
cycle (int or list or tuple): cycle(s) to select (must be int for matplotlib)
get_axes (bool): return axes (for matplotlib) or figure (for plotly)
interactive (bool): use interactive plotting (if available)
t_unit (str): unit for x-axis (default: "hours")
v_unit (str): unit for y-axis (default: "V")
i_unit (str): unit for current (default: "mA")
**kwargs: parameters specific to plotting backend.
Returns:
``matplotlib.axes`` or None
"""
t_scaler = cell.unit_scaler_from_raw(t_unit, "time")
v_scaler = cell.unit_scaler_from_raw(v_unit, "voltage")
i_scaler = cell.unit_scaler_from_raw(i_unit, "current")
if plotly_available and interactive:
fig = _cycle_info_plot_plotly(
cell,
cycle,
get_axes,
t_scaler,
t_unit,
v_scaler,
v_unit,
i_scaler,
i_unit,
**kwargs,
)
if get_axes:
return fig
return fig
axes = _cycle_info_plot_matplotlib(
cell,
cycle,
get_axes,
t_scaler,
t_unit,
v_scaler,
v_unit,
i_scaler,
i_unit,
**kwargs,
)
if get_axes:
return axes
def _cycle_info_plot_plotly(
cell,
cycle,
get_axes,
t_scaler,
t_unit,
v_scaler,
v_unit,
i_scaler,
i_unit,
**kwargs,
):
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
if kwargs.get("xlim"):
logging.info("xlim is not supported for plotly yet")
raw_hdr = get_headers_normal()
step_hdr = get_headers_step_table()
data = cell.data.raw.copy()
table = cell.data.steps.copy()
if cycle is None:
cycle = list(data["cycle_index"].unique())
if not isinstance(cycle, (list, tuple)):
cycle = [cycle]
delta = "_delta"
v_delta = step_hdr["voltage"] + delta
i_delta = step_hdr["current"] + delta
c_delta = step_hdr["charge"] + delta
dc_delta = step_hdr["discharge"] + delta
cycle_ = step_hdr["cycle"]
step_ = step_hdr["step"]
type_ = step_hdr["type"]
time_hdr = raw_hdr["test_time_txt"]
cycle_hdr = raw_hdr["cycle_index_txt"]
step_number_hdr = raw_hdr["step_index_txt"]
current_hdr = raw_hdr["current_txt"]
voltage_hdr = raw_hdr["voltage_txt"]
data = data[
[
time_hdr,
cycle_hdr,
step_number_hdr,
current_hdr,
voltage_hdr,
]
]
table = table[
[
cycle_,
step_,
type_,
v_delta,
i_delta,
c_delta,
dc_delta,
]
]
m_cycle_data = data[cycle_hdr].isin(cycle)
data = data.loc[m_cycle_data, :]
data[time_hdr] = data[time_hdr] * t_scaler
data[voltage_hdr] = data[voltage_hdr] * v_scaler
data[current_hdr] = data[current_hdr] * i_scaler
data = data.merge(
table,
left_on=(cycle_hdr, step_number_hdr),
right_on=(cycle_, step_),
).sort_values(by=[time_hdr])
fig = go.Figure()
grouped_data = data.groupby(cycle_hdr)
for cycle_number, group in grouped_data:
x = group[time_hdr]
y = group[voltage_hdr]
s = group[step_number_hdr]
i = group[current_hdr]
st = group[type_]
dV = group[v_delta]
dI = group[i_delta]
dC = group[c_delta]
dDC = group[dc_delta]
fig.add_trace(
go.Scatter(
x=x,
y=y,
mode="lines",
name=f"cycle {cycle_number}",
customdata=np.stack((i, s, st, dV, dI, dC, dDC), axis=-1),
hovertemplate="<br>".join(
[
"<b>Time: %{x:.2f}" + f" {t_unit}" + "</b>",
" <b>Voltage:</b> %{y:.4f}" + f" {v_unit}",
" <b>Current:</b> %{customdata[0]:.4f}" + f" {i_unit}",
"<b>Step: %{customdata[1]} (%{customdata[2]})</b>",
" <b>ΔV:</b> %{customdata[3]:.2f}",
" <b>ΔI:</b> %{customdata[4]:.2f}",
" <b>ΔCh:</b> %{customdata[5]:.2f}",
" <b>ΔDCh:</b> %{customdata[6]:.2f}",
]
),
),
)
cell_name = kwargs.get("title", cell.cell_name)
title_start = f"<b>{cell_name}</b> Cycle"
if len(cycle) > 2:
if cycle[-1] - cycle[0] == len(cycle) - 1:
title = f"{title_start}s {cycle[0]} - {cycle[-1]}"
else:
title = f"{title_start}s {cycle}"
elif len(cycle) == 2:
title = f"{title_start}s {cycle[0]} and {cycle[1]}"
else:
title = f"{title_start} {cycle[0]}"
fig.update_layout(
title=title,
xaxis_title=f"Time ({t_unit})",
yaxis_title=f"Voltage ({v_unit})",
)
if get_axes:
return fig
fig.show()
def _plot_step(ax, x, y, color):
ax.plot(x, y, color=color, linewidth=3)
def _get_info(table, cycle, step):
# obs! hard-coded col-names. Please fix me.
m_table = (table.cycle == cycle) & (table.step == step)
p1, p2 = table.loc[m_table, ["point_min", "point_max"]].values[0]
c1, c2 = table.loc[m_table, ["current_min", "current_max"]].abs().values[0]
d_voltage, d_current = table.loc[
m_table, ["voltage_delta", "current_delta"]
].values[0]
d_discharge, d_charge = table.loc[
m_table, ["discharge_delta", "charge_delta"]
].values[0]
current_max = (c1 + c2) / 2
rate = table.loc[m_table, "rate_avr"].values[0]
step_type = table.loc[m_table, "type"].values[0]
return [step_type, rate, current_max, d_voltage, d_current, d_discharge, d_charge]
def _cycle_info_plot_matplotlib(
cell,
cycle,
get_axes,
t_scaler,
t_unit,
v_scaler,
v_unit,
i_scaler,
i_unit,
**kwargs,
):
# obs! hard-coded col-names. Please fix me.
if cycle is None:
warnings.warn("Only one cycle at a time is supported for matplotlib")
cycle = 1
if isinstance(cycle, (list, tuple)):
warnings.warn("Only one cycle at a time is supported for matplotlib")
cycle = cycle[0]
data = cell.data.raw
table = cell.data.steps
span_colors = ["#4682B4", "#FFA07A"]
voltage_color = "#008B8B"
current_color = "#CD5C5C"
m_cycle_data = data.cycle_index == cycle
all_steps = data[m_cycle_data]["step_index"].unique()
color = itertools.cycle(span_colors)
fig = plt.figure(figsize=(20, 8))
fig.suptitle(f"Cycle: {cycle}")
ax3 = plt.subplot2grid((8, 3), (0, 0), colspan=3, rowspan=1, fig=fig) # steps
ax4 = plt.subplot2grid((8, 3), (1, 0), colspan=3, rowspan=2, fig=fig) # info
ax1 = plt.subplot2grid((8, 3), (3, 0), colspan=3, rowspan=5, fig=fig) # data
ax2 = ax1.twinx()
ax1.set_xlabel(f"time ({t_unit})")
ax1.set_ylabel(f"voltage ({v_unit})", color=voltage_color)
ax2.set_ylabel(f"current ({i_unit})", color=current_color)
annotations_1 = [] # step number (IR)
annotations_2 = [] # step number
annotations_4 = [] # info
for i, s in enumerate(all_steps):
m = m_cycle_data & (data.step_index == s)
c = data.loc[m, "current"] * i_scaler
v = data.loc[m, "voltage"] * v_scaler
t = data.loc[m, "test_time"] * t_scaler
step_type, rate, current_max, dv, dc, d_discharge, d_charge = _get_info(
table, cycle, s
)
if len(t) > 1:
fcolor = next(color)
info_txt = f"{step_type}\ni = |{i_scaler * current_max:0.2f}| {i_unit}\n"
info_txt += f"delta V = {dv:0.2f} %\ndelta i = {dc:0.2f} %\n"
info_txt += f"delta C = {d_charge:0.2} %\ndelta DC = {d_discharge:0.2} %\n"
for ax in [ax2, ax3, ax4]:
ax.axvspan(t.iloc[0], t.iloc[-1], facecolor=fcolor, alpha=0.2)
_plot_step(ax1, t, v, voltage_color)
_plot_step(ax2, t, c, current_color)
annotations_1.append([f"{s}", t.mean()])
annotations_4.append([info_txt, t.mean()])
else:
info_txt = f"{s}({step_type})"
annotations_2.append([info_txt, t.mean()])
ax3.set_ylim(0, 1)
for s in annotations_1:
ax3.annotate(f"{s[0]}", (s[1], 0.2), ha="center")
for s in annotations_2:
ax3.annotate(f"{s[0]}", (s[1], 0.6), ha="center")
for s in annotations_4:
ax4.annotate(f"{s[0]}", (s[1], 0.0), ha="center")
for ax in [ax3, ax4]:
ax.axes.get_yaxis().set_visible(False)
ax.axes.get_xaxis().set_visible(False)
if x := kwargs.get("xlim"):
ax1.set_xlim(x)
ax2.set_xlim(x)
ax3.set_xlim(x)
ax4.set_xlim(x)
if get_axes:
return ax1, ax2, ax2, ax4
if __name__ == "__main__":
pass