File size: 4,852 Bytes
ec6e9a7
 
 
 
 
 
 
 
 
3e12d11
 
ec6e9a7
3e12d11
 
ec6e9a7
3e12d11
ec6e9a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3e12d11
 
 
 
 
 
 
 
ec6e9a7
3e12d11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ec6e9a7
3e12d11
ec6e9a7
68167e5
3e12d11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ec6e9a7
3e12d11
 
 
ec6e9a7
 
 
 
3e12d11
ec6e9a7
 
3e12d11
 
ec6e9a7
3e12d11
ec6e9a7
 
 
3e12d11
ec6e9a7
 
 
 
3e12d11
ec6e9a7
 
 
 
 
 
 
 
 
 
3e12d11
 
746aaaa
 
3e12d11
 
 
ec6e9a7
 
 
 
 
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""Plotly figure helpers used by the Shiny app.

The app passes a filtered pipeline DataFrame into these helpers and receives
back a fully configured Plotly figure (including placeholders for empty
selections).
"""

from __future__ import annotations

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots

from .config import AGE_ORDER


def _placeholder(msg: str) -> go.Figure:
    """Return a simple placeholder figure (used when there is nothing to plot)."""
    fig = make_subplots(rows=1, cols=1)
    fig.add_annotation(
        text=msg,
        xref="paper",
        yref="paper",
        x=0.5,
        y=0.5,
        showarrow=False,
        font=dict(size=16),
    )
    fig.update_xaxes(visible=False)
    fig.update_yaxes(visible=False)
    fig.update_layout(height=700, width=1200, margin=dict(t=80), showlegend=False)
    return fig


def employment_multi_plot(
    df: pd.DataFrame,
    *,
    level: str | int | None = None,
    empty_message: str = "Select an occupation to load the charts.",
    no_age_message: str = "No age data available for this selection.",
) -> go.Figure:
    """Build the multi-panel employment chart.

    Expects `df` to contain (at minimum) the columns: `year`, `age`, `label`,
    and `employment`.
    """
    if df is None or df.empty:
        return _placeholder(empty_message)

    # Order ages by AGE_ORDER, append any extra groups alphabetically.
    unique_ages = [a for a in df["age"].dropna().unique() if a]
    age_set = set(unique_ages)
    age_groups = [age for age in AGE_ORDER if age in age_set]
    age_groups.extend(sorted(age_set - set(AGE_ORDER)))

    if not age_groups:
        return _placeholder(no_age_message)

    n_rows = len(age_groups)
    ssyk_level = (
        f"(🇸🇪 SSYK 2012, Level {level})" if level is not None else "(SSYK 2012)"
    )

    subplot_titles = [
        (
            f"<b>Employed Persons Aged {age} Years by Occupation</b><br>"
            f"<span style='font-size:13px; color:#6b7280;'display:inline-block;'>"
            f"{ssyk_level}"
            f"</span>"
        )
        for age in age_groups
    ]

    occupations = sorted(df["label"].dropna().unique())
    palette = px.colors.qualitative.Plotly
    occ_color_map = {
        occ: palette[i % len(palette)] for i, occ in enumerate(occupations)
    }

    fig = make_subplots(
        rows=n_rows,
        cols=1,
        shared_xaxes=False,
        vertical_spacing=0.03,
        subplot_titles=subplot_titles,
    )

    for i, age in enumerate(age_groups, start=1):
        df_age = df[df["age"] == age]
        df_plot = df_age.groupby(["year", "label"], as_index=False)["employment"].sum()

        for occ_title, sub in df_plot.groupby("label"):
            fig.add_trace(
                go.Scatter(
                    x=sub["year"],
                    y=sub["employment"],
                    mode="lines+markers",
                    showlegend=(i == 1),
                    name=occ_title,
                    line=dict(color=occ_color_map[occ_title], width=3),
                    hovertemplate=f"Age: {age}<br>Year: %{{x}}<br>Count: %{{y:,}}<extra>{occ_title}</extra>",
                ),
                row=i,
                col=1,
            )

        fig.update_yaxes(
            title_text="Number of Employed Persons",
            tickformat=",",
            rangemode="tozero",
            row=i,
            col=1,
        )
        fig.update_xaxes(
            title_text="Year",
            tickmode="linear",
            dtick=1,
            row=i,
            col=1,
        )

    BASE_PLOT_WIDTH = 1200
    LEFT_LEGEND_MARGIN = 260

    fig.update_annotations(yshift=36)
    fig.update_layout(
        height=700 * n_rows,
        width=BASE_PLOT_WIDTH + LEFT_LEGEND_MARGIN,
        legend_traceorder="normal",
        legend=dict(
            title="<b>Occupation Title(s)</b><br>",
            orientation="v",
            x=-0.1,  # left edge of plotting area
            xanchor="right",  # legend sits just outside-left
            y=0.98,
            yanchor="top",
            itemsizing="constant",
            itemwidth=35,  # keeps items compact
            tracegroupgap=6,
            bordercolor="rgba(0,0,0,0.15)",
            borderwidth=1,
            bgcolor="rgba(255,255,255,0.85)",
            font=dict(size=12),
            indentation=10,
            yref="paper",
        ),
        margin=dict(
            t=170,
            l=LEFT_LEGEND_MARGIN,
            r=60,
            b=60,
        ),
        xaxis_showgrid=True,
        yaxis_showgrid=True,
        template="plotly_white",
    )

    return fig


def multi_plot(df: pd.DataFrame) -> go.Figure:
    """Backwards-compatible alias for `employment_multi_plot`."""
    return employment_multi_plot(df)