Source code for imednet_streamlit.components.charts

"""Standard Altair chart wrappers for iMednet.

Provides accessible, brand-compliant bar, line, and pie charts with automatic
tabular data fallback for accessibility.
"""

from __future__ import annotations

from typing import Any, Literal

import altair as alt
import pandas as pd

# iMednet brand palette — used across all charts
PALETTE = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b"]
HIGH_CONTRAST_PALETTE = ["#000000", "#E69F00", "#56B4E9", "#009E73", "#D55E00", "#0072B2"]


def _get_palette() -> list[str]:
    """Get the current palette based on high contrast mode setting."""
    import streamlit as st

    try:
        if st.session_state.get("high_contrast", False):
            return HIGH_CONTRAST_PALETTE
    except Exception:  # noqa: S110
        pass
    return PALETTE


def _color_encoding(field: str) -> alt.Color:
    """Return an Altair color encoding using the current palette."""
    return alt.Color(f"{field}:N", scale=alt.Scale(range=_get_palette()))


[docs]def bar_chart( df: pd.DataFrame, x: str, y: str, color: str | None = None, title: str = "", x_title: str | None = None, y_title: str | None = None, ) -> alt.Chart: """Build a horizontal Altair bar chart. Args: df: Source DataFrame for the chart. x: Quantitative column for the x-axis. y: Categorical column for the y-axis. color: Optional categorical column for grouped colors. title: Optional chart title. x_title: Optional x-axis label override. y_title: Optional y-axis label override. Returns: Configured Altair chart object. """ chart = alt.Chart(df).mark_bar() if color: chart = chart.encode( x=alt.X(f"{x}:Q", title=x_title), y=alt.Y(f"{y}:N", title=y_title, sort="-x"), color=_color_encoding(color), tooltip=[alt.Tooltip(f"{y}:N"), alt.Tooltip(f"{x}:Q"), alt.Tooltip(f"{color}:N")], ) else: chart = chart.encode( x=alt.X(f"{x}:Q", title=x_title), y=alt.Y(f"{y}:N", title=y_title, sort="-x"), color=alt.value(_get_palette()[0]), tooltip=[alt.Tooltip(f"{y}:N"), alt.Tooltip(f"{x}:Q")], ) return chart.properties(width="container", title=title)
[docs]def line_chart( df: pd.DataFrame, x: str, y: str, color: str | None = None, title: str = "", ) -> alt.Chart: """Build an Altair temporal line chart. Args: df: Source DataFrame for the chart. x: Temporal column for the x-axis. y: Quantitative column for the y-axis. color: Optional categorical column for grouped lines. title: Optional chart title. Returns: Configured Altair chart object. """ chart = alt.Chart(df).mark_line(point=True) if color: chart = chart.encode( x=alt.X(f"{x}:T"), y=alt.Y(f"{y}:Q"), color=_color_encoding(color), tooltip=[alt.Tooltip(f"{x}:T"), alt.Tooltip(f"{y}:Q"), alt.Tooltip(f"{color}:N")], ) else: chart = chart.encode( x=alt.X(f"{x}:T"), y=alt.Y(f"{y}:Q"), color=alt.value(_get_palette()[0]), tooltip=[alt.Tooltip(f"{x}:T"), alt.Tooltip(f"{y}:Q")], ) return chart.properties(width="container", title=title)
[docs]def pie_chart( df: pd.DataFrame, theta: str, color: str, title: str = "", ) -> alt.Chart: """Build an Altair pie chart. Args: df: Source DataFrame for the chart. theta: Quantitative column used for slice size. color: Categorical column used for slice color. title: Optional chart title. Returns: Configured Altair chart object. """ return ( alt.Chart(df) .mark_arc() .encode( theta=alt.Theta(f"{theta}:Q"), color=_color_encoding(color), tooltip=[alt.Tooltip(f"{color}:N"), alt.Tooltip(f"{theta}:Q")], ) .properties(width="container", title=title, description=f"Pie chart for {title}") )
import streamlit as st
[docs]def render_accessible_chart( chart: alt.Chart, use_container_width: bool = True, theme: Literal["streamlit"] | None = "streamlit", **kwargs: Any, ) -> None: """Render an Altair chart with an accessible tabular data view.""" title = getattr(chart, "title", "Chart") if isinstance(title, dict) and "text" in title: title = title["text"] elif not isinstance(title, str): title = "Chart" df = getattr(chart, "data", pd.DataFrame()) # Ensure chart has a description for screen readers (ARIA label) if not hasattr(chart, "description") or not getattr(chart, "description", None): chart = chart.properties(description=f"Data visualization for {title}") st.altair_chart(chart, use_container_width=use_container_width, theme=theme, **kwargs) if isinstance(df, pd.DataFrame) and not df.empty: with st.expander(f"Tabular Data View: {title}", expanded=False): st.dataframe(df, use_container_width=use_container_width)