File size: 2,799 Bytes
eec71cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f6d385f
 
 
 
 
 
 
eec71cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f6d385f
 
 
 
 
 
eec71cb
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
"""Plot helpers used by the Shiny app.

The app is responsible for data filtering and column selection; these helpers only
turn a prepared DataFrame into Plotly figures. If the input DataFrame is empty,
the helpers return an empty figure to keep the UI responsive.
"""

from __future__ import annotations

from typing import Sequence

import pandas as pd
import plotly.express as px
from plotly.graph_objects import Figure


def format_metric_value(value: float) -> str:
    if pd.isna(value):
        return "N/A"
    if 0 <= value <= 1:
        return f"{value:.0%}"
    return f"{value:.2f}"


def format_raw_value(value: float) -> str:
    if pd.isna(value):
        return "N/A"
    return f"{value:.3f}"


def build_trend_plot(
    df: pd.DataFrame,
    *,
    metric_col: str,
    metric_label: str,
    title: str,
    order: Sequence[str] = (),
) -> Figure:
    """Line chart: metric over time for each occupation label."""
    if df.empty:
        return px.line()

    fig = px.line(
        df,
        x="year",
        y=metric_col,
        color="label",
        markers=True,
        category_orders={"label": list(order)},
        labels={
            "label": "Occupation",
            "year": "Year",
            metric_col: metric_label,
        },
    )
    fig.update_layout(
        hovermode="x unified",
        title=title,
        xaxis_showgrid=True,
        yaxis_showgrid=True,
        template="plotly_white",
    )
    return fig


def build_bar_plot(
    df: pd.DataFrame,
    *,
    percentile_col: str,
    raw_col: str,
    metric_label: str,
    title: str,
    order: Sequence[str] = (),
) -> Figure:
    """Horizontal bar chart: percentile ranks for the latest year."""
    if df.empty:
        return px.bar()

    # Always compare within the latest year available in the filtered data.
    latest = df["year"].max()
    latest_df = df[df["year"] == latest]

    order_list = list(order)
    if order_list:
        latest_df = latest_df.set_index("label").loc[order_list].reset_index()

    latest_df = latest_df.copy()
    latest_df["bar_label"] = latest_df.apply(
        lambda row: f"{format_raw_value(row[raw_col])} | {format_metric_value(row[percentile_col])}",
        axis=1,
    )

    fig = px.bar(
        latest_df,
        x=percentile_col,
        y="label",
        orientation="h",
        text="bar_label",
        category_orders={"label": order_list},
        labels={
            "label": "Occupation",
            percentile_col: f"{metric_label} (percentile rank)",
        },
    )
    fig.update_layout(
        title=title,
        xaxis_showgrid=True,
        yaxis_showgrid=True,
        template="plotly_white",
    )
    fig.update_traces(textposition="inside")
    fig.update_xaxes(range=[0, 1], tickformat=".0%")
    return fig