Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies = [
dash = [
"dash>=4.0.0",
"plotly>=6.5.2",
"polars>=1.39.0",
"ssb-dash-components>=0.11.1",
]

Expand Down
180 changes: 180 additions & 0 deletions src/ssb_konjunk/dash/components/gen_vis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"""Dash component for generic visualization at SSB.

This module provides a dash all-in-one component that are intended to replace
some functions of myfame for generic visualizations.
It will only work in SSBs-developer environments.

The module expects the path to JSON-configuration file.

"dataset_1": {
"glob_pattern": "glob/to/files/*.parquet",
"index_col": "period", // The columns that contains the periods.
"index_pattern": "%Y-%m", // The pattern of the period column. See: https://strftime.org/
"groupby_col": "nar" // OPTIONAL: Columns that are supposed to be subset
"agg_type": "AVERAGE" // OPTIONAL: For now, only "AVERAGE" is allowed.
},
"dataset_2": ...

Example:
```
from ssb_konjunk.dash.gen_vis import GenVis
layout = GenVis("./data_setup.json")
# Or

from dash import html

layout = html.Div(
children = [
GenVis("./data_setup.json")
]
)
```


"""

import uuid

from dash import (
Input,
Output,
callback,
html,
)

from ssb_dash_components import Button

from .internal.loading_test import load_datasets
from .internal.tab_selector import TabSelector
from .internal.series_selector import SeriesSelector
from .internal.series_settings_display import SeriesSettingsDisplay
from .internal.graph_display import GraphDisplay
from .internal.graph_settings_display import GraphSettingsDisplay

class GenVis(html.Div):

class ids:
dropdown = lambda aio_id: {
"component": "MarkdownWithColorAIO",
"subcomponent": "dropdown",
"aio_id": aio_id,
}
markdown = lambda aio_id: {
"component": "MarkdownWithColorAIO",
"subcomponent": "markdown",
"aio_id": aio_id,
}

# Make the ids class a public class
ids = ids

# Define the arguments of the All-in-One component
def __init__(self, config_path: str, aio_id=None):
"""
Returns a component that can be used as its own page or as a component in other layouts.

Args:
config_path (str): Path to the config file.
aio_id (str | None): An optional id. Will be randomised if not provided.
"""
if aio_id is None:
aio_id = str(uuid.uuid4())

datasets = load_datasets(config_path)
select_data = {}
for key, value in datasets.items():
select_data[key] = value.get_entries()

super().__init__(
[
html.Div(
[
html.Div(
children=[
html.Div(
children=[
Button(
"Oppdater datasett",
id="update-dataset-button",
),
],
),
html.Div(
TabSelector(datasets, aio_id=aio_id, height="200px"),
),
],
),
html.Div(
children=[
html.H3("Velg serier"),
html.Div(
children=[SeriesSelector(datasets, aio_id=aio_id)],
),
],
),
html.Div(
children=[GraphSettingsDisplay(aio_id)],
),
],
style={
"display": "grid",
"gridTemplateColumns": "40% 45% 15%",
},
),
html.Div(
children=[
html.Div(
html.Div(
children=[
SeriesSettingsDisplay(datasets, aio_id),
GraphDisplay(datasets, aio_id),
],
id="graph-display",
style={
"height": "100px",
"width": "100%",
"display": "grid",
"gridTemplateColumns": "30% 70%",
},
)
),
],
style={
"gap": "30px",
},
),
],
style={"gap": "10px"},
)

@callback(
Output(SeriesSelector.ids.store(aio_id), "data"),
Input(TabSelector.ids.store(aio_id), "data"),
)
def update_selected(selected):
"""Move data from the file selector to the series selector"""
return selected

@callback(
Output(SeriesSettingsDisplay.ids.store(aio_id), "data"),
Input(SeriesSelector.ids.selected_store(aio_id), "data"),
)
def update_display(selected):
"""Move data from the series selcetor to series settings display"""
return selected

@callback(
Output(GraphDisplay.ids.series_store(aio_id), "data"),
Input(SeriesSettingsDisplay.ids.settings_store(aio_id), "data"),
)
def update_graph_series(selected):
"""Moves series settings to the graph display"""
return selected

@callback(
Output(GraphDisplay.ids.settings_store(aio_id), "data"),
Input(GraphSettingsDisplay.ids.settings_store(aio_id), "data"),
)
def update_graph_settings(settings):
"""Move general graph settings to the graph display"""
return settings
Empty file.
77 changes: 77 additions & 0 deletions src/ssb_konjunk/dash/components/internal/data_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from typing import Literal
from datetime import date
import polars as pl

from .loading_test import AGG_TYPES

class DataSource:
def __init__(self, filename: str, index_col: str, date_pattern: str) -> None:
self.dt_colname = "dt"
self.data = pl.read_parquet(filename).with_columns(
dt=pl.col(index_col).cast(pl.String).str.to_date(date_pattern)
)

def filter_dt(self, lower: date, highest: date):
return self.data.filter(pl.col(self.dt_colname).is_between(lower, highest))

def get_unique_dates(self) -> list[date]:
return self.data.get_column(self.dt_colname).unique().to_list()

def get_unique_groupby(self, groupby_col: str):
return self.data.get_column(groupby_col).unique().to_list()

def subset_group(self, groupby_col: str, filter_val):
return self.data.filter(pl.col(groupby_col) == filter_val)

def _set_base_year(
self,
data: pl.DataFrame,
year: str,
col: str,
method: Literal["discrete", "none"],
agg_type: AGG_TYPES,
):
# print(data)
filtered = data.filter(
(pl.col(self.dt_colname) < date(int(year), 12, 31))
& (pl.col(self.dt_colname) >= date(int(year), 1, 1))
)

selected_col = filtered.get_column(col).cast(pl.Float64)
if method == "discrete":
if agg_type == "AVERAGE":
index_factor = selected_col.mean()
else:
index_factor = selected_col.sum()
else:
index_factor = selected_col.mean()

altered = data.with_columns(
**{col: (pl.col(col).cast(pl.Float64) / index_factor) * 100}
)
# print(altered)
return altered

def set_base_year(
self,
data: pl.DataFrame,
year: str,
col: str,
group_col: str | None,
method: Literal["discrete", "none"],
agg_mapping: AGG_TYPES | dict[str, AGG_TYPES],
) -> pl.DataFrame:

if isinstance(agg_mapping, dict):
agg_type = agg_mapping.get(col, "AVERAGE")
elif isinstance(agg_mapping, str):
agg_type = agg_mapping
else:
raise ValueError("")

if group_col is not None:
return data.group_by(group_col).map_groups(
lambda x: self._set_base_year(x, year, col, method, agg_type)
)
else:
return self._set_base_year(data, year, col, method, agg_type)
Loading
Loading