"""
Bar graph utilities using Plotly Express and Dash.
Provides a configurable bar plot builder and a Dash ``Graph`` factory.
"""
from plotly import express as px
import pandas as pd
from dash.dcc import Graph
[docs]
def bar_plot(
defaults: dict,
value_df: pd.DataFrame,
title: str,
x_name: str = None,
x_label: str = None,
sort_x: bool = None,
y_name: str = None,
y_label: str = None,
y_idx: int = 0,
barmode: str = 'relative',
color: bool = True,
color_col: str = None,
hide_legend=False,
color_discrete_map=False,
color_discrete_map_dict: dict = None,
width: int|None = None,
height: int|None = None) -> px.bar:
"""Draw a bar plot from the given input.
:param defaults: Dictionary of default values for the figure.
:param value_df: DataFrame containing the plot data.
:param title: Title for the figure.
:param x_name: Column to use for x-axis; if ``None``, index is reset and the new column is used.
:param x_label: Axis label to use for X regardless of ``x_name``.
:param sort_x: If ``True`` ascending, ``False`` descending, ``None`` leaves default order.
:param y_name: Column to use for y-axis; if ``None``, use ``y_idx``.
:param y_label: Axis label to use for Y regardless of ``y_name``/``y_idx``.
:param y_idx: Index of the column to use for y-axis when ``y_name`` is ``None``.
:param barmode: Plotly Express bar ``barmode``.
:param color: If ``True``, use column ``Color`` unless ``color_col`` is provided.
:param color_col: Explicit name of color column.
:param hide_legend: If ``True``, hides the legend.
:param color_discrete_map: If ``True``, use ``'identity'`` mapping or a provided dict.
:param color_discrete_map_dict: Explicit map for colors.
:param width: Figure width; if ``None``, derived from defaults and min width policy.
:param height: Figure height; if ``None``, derived from defaults.
:returns: A Plotly Express bar figure.
"""
colorval: str
if color_col is not None:
colorval = color_col
elif color:
colorval = 'Color'
else:
colorval = None
cdm_val: dict = None
if color_discrete_map_dict is not None:
cdm_val = color_discrete_map_dict
else:
if color_discrete_map:
cdm_val = 'identity'
if y_name is None:
y_name: str = value_df.columns[y_idx]
if x_name is None:
before: set = set(value_df.columns)
value_df = value_df.reset_index()
# Pick out the name of the new column
x_name = [c for c in value_df.columns if c not in before][0]
if height is None:
height: int = defaults['height']
if width is None:
width: int = defaults['width']
if 'min_width_per' in defaults and defaults['min_width_per'] > 0:
target_width = defaults['side_width'] + (defaults['min_width_per']*len(value_df[x_name].unique()))
if width < target_width:
width = target_width
cat_ord: dict = {}
if sort_x is not None:
cat_ord[x_name] = sorted(
list(value_df[x_name].values), reverse=(not sort_x))
figure: px.bar = px.bar(
value_df,
x=x_name, # 'Sample name',
y=y_name,
category_orders=cat_ord,
title=title,
color=colorval,
barmode=barmode,
color_discrete_map=cdm_val,
height=height,
width=width
)
if x_label is not None:
figure.update_layout(
xaxis_title=x_label
)
if y_label is not None:
figure.update_layout(
yaxis_title=y_label
)
figure.update_xaxes(type='category')
if hide_legend:
figure.update_layout(showlegend=False)
return figure
[docs]
def make_graph(graph_id: str, defaults: dict, dlname, *args, **kwargs) -> Graph:
"""Create a Dash ``Graph`` configured with a bar plot.
:param graph_id: Component ID for the ``Graph``.
:param defaults: Dictionary with ``config``, ``height``, ``width`` and related settings.
:param dlname: Name for the downloaded figure file.
:param args: Positional arguments forwarded to ``bar_plot``.
:param kwargs: Keyword arguments forwarded to ``bar_plot``.
:returns: Dash ``Graph`` instance.
"""
config=defaults['config']
config['toImageButtonOptions'] = config['toImageButtonOptions'].copy()
config['toImageButtonOptions']['filename'] = dlname
figure = bar_plot(
defaults,
*args,
**kwargs
)
figure.update_layout(hovermode='closest', dragmode=False)
return Graph(
id=graph_id,
config=config,
figure=figure
)