gov-uk-dashboards 13.4.2__tar.gz → 13.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gov_uk_dashboards-13.4.2/gov_uk_dashboards.egg-info → gov_uk_dashboards-13.5.1}/PKG-INFO +1 -1
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/choropleth_map.py +125 -95
- gov_uk_dashboards-13.5.1/gov_uk_dashboards/components/plotly/generate_dash_graph_from_figure.py +49 -0
- gov_uk_dashboards-13.5.1/gov_uk_dashboards/components/plotly/stacked_barchart.py +354 -0
- gov_uk_dashboards-13.5.1/gov_uk_dashboards/lib/plotting_helper_functions.py +29 -0
- gov_uk_dashboards-13.5.1/gov_uk_dashboards/lib/update_layout_bgcolor_margin.py +24 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1/gov_uk_dashboards.egg-info}/PKG-INFO +1 -1
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards.egg-info/SOURCES.txt +4 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/setup.py +1 -1
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/LICENSE +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/MANIFEST.in +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/README.md +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/__init__.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/__init__.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/attach-event-to-dash.js +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/dashboard.css +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/fonts/bold-affa96571d-v2.woff +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/fonts/bold-b542beb274-v2.woff2 +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/fonts/light-94a07e06a1-v2.woff2 +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/fonts/light-f591b13f7d-v2.woff +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/get_assets_folder.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/govuk-frontend-3.14.0.min.js +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/DLUHC_WHITE_Master_AW_sm.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/MHCLG-favicon.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/MHCLG_favicon.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/dcms_coatofarms.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/dluhc_favicon.ico +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/gov_favicon.ico +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/govuk-crest.svg +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/hm-government-logo.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/mhclg_coat_of_arms.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/mhclg_white_no_background.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/oflog/MedianAbsolute.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/oflog/how_to_1_selecting_data.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/oflog/how_to_2_viewing_tabs.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/oflog/how_to_3_viewing_charts.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/oflog/how_to_4_viewing_trends.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/images/oflog/how_to_5_viewing_tables.png +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/mobile-nav.js +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/topojson/usa_110m.json +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/topojson/world_110m.json +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/axes.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/colours.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/__init__.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/create_download_chart_button.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/create_download_data_button.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/display_chart_or_table_with_header.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/__init__.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/apply_and_reset_filters_buttons.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/banners.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/captioned_figure.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/card.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/card_full_width.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/collapsible_panel.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/comparison_la_filter_button.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/context_banner.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/dashboard_container.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/details.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/download_button.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/filter_panel.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/footer.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/graph.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/header.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/heading.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/html_list.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/key_value_pair.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/main_content.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/navbar.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/no_data_message.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/notification_banner.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/paragraph.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/phase_banner.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/row_component.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/side_navbar.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/table.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/tooltip.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/tooltip_title.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/visualisation_commentary.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/visualisation_title.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/warning_text.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/__init__.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/chart_data.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/enums/__init__.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/enums/dash_patterns.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/line_chart.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/styles/__init__.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/styles/line_style.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/formatting/__init__.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/formatting/human_readable.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/formatting/round_and_add_prefix_and_suffix.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/formatting/rounding.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/lib/__init__.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/lib/dap/__init__.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/lib/dap/dap_deployment.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/lib/dap/get_dataframe_from_cds.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/lib/enable_basic_auth.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/lib/http_headers.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/lib/logging.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/symbols.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/template.html +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/template.py +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards.egg-info/dependency_links.txt +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards.egg-info/requires.txt +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards.egg-info/top_level.txt +0 -0
- {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: gov_uk_dashboards
|
3
|
-
Version: 13.
|
3
|
+
Version: 13.5.1
|
4
4
|
Summary: Provides access to functionality common to creating a data dashboard.
|
5
5
|
Author: Department for Levelling Up, Housing and Communities
|
6
6
|
Description-Content-Type: text/markdown
|
@@ -3,6 +3,9 @@
|
|
3
3
|
import polars as pl
|
4
4
|
import plotly.graph_objects as go
|
5
5
|
from dash import dcc
|
6
|
+
|
7
|
+
from constants import DEFAULT_COLOURSCALE
|
8
|
+
|
6
9
|
from gov_uk_dashboards import colours
|
7
10
|
from gov_uk_dashboards.components.display_chart_or_table_with_header import (
|
8
11
|
display_chart_or_table_with_header,
|
@@ -11,8 +14,10 @@ from gov_uk_dashboards.components.display_chart_or_table_with_header import (
|
|
11
14
|
|
12
15
|
class ChoroplethMap:
|
13
16
|
"""Class for generating choropleth map charts.
|
14
|
-
Note: dataframe_function must contain columns: 'Region', 'Area_Code',
|
15
|
-
|
17
|
+
Note: dataframe_function must contain columns: 'Region', 'Area_Code',
|
18
|
+
discrete_category_column, column_to_plot, hover_data_list
|
19
|
+
If discrete_category_column & discrete_category_order are None,
|
20
|
+
the choropleth map will be a continuos one, otherwise discrete"""
|
16
21
|
|
17
22
|
# pylint: disable=too-many-instance-attributes
|
18
23
|
# pylint: disable=too-many-arguments
|
@@ -20,34 +25,42 @@ class ChoroplethMap:
|
|
20
25
|
# pylint: disable=too-few-public-methods
|
21
26
|
def __init__(
|
22
27
|
self,
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
map_name: str, # used for the id in the choropleth map and the data download button
|
29
|
+
get_dataframe: callable,
|
30
|
+
get_geos: callable,
|
31
|
+
region: str,
|
32
|
+
area_focus_level: str,
|
33
|
+
column_to_plot: str,
|
34
|
+
hover_header_list: list[str],
|
35
|
+
hover_data_list: list[str],
|
36
|
+
discrete_category_column: str = None,
|
37
|
+
discrete_category_order: list[str] = None,
|
38
|
+
legend_title_text: str = None,
|
33
39
|
**choropleth_properties,
|
34
40
|
):
|
35
|
-
self.
|
41
|
+
self.map_name = map_name
|
42
|
+
self.dataframe = get_dataframe()
|
43
|
+
self.geographic_boundaries = get_geos()
|
36
44
|
self.region = region
|
37
|
-
self.
|
38
|
-
self.boundaries_by_area = self._get_boundaries_by_area(self.dataframe)
|
39
|
-
self.category_column = category_column
|
45
|
+
self.area_focus_level = area_focus_level
|
40
46
|
self.column_to_plot = column_to_plot
|
41
|
-
self.df_dict = self._get_dataframe_dict_by_category()
|
42
|
-
self.desired_category_order = desired_category_order
|
43
|
-
self.colours_list = self._get_colour_list()
|
44
|
-
self.custom_data = custom_data
|
45
47
|
self.hover_header_list = hover_header_list
|
46
|
-
self.
|
48
|
+
self.hover_data_list = hover_data_list
|
49
|
+
|
50
|
+
self.discrete_category_column = discrete_category_column
|
47
51
|
self.legend_title_text = legend_title_text
|
48
|
-
self.
|
52
|
+
self.discrete_category_order = discrete_category_order
|
49
53
|
self.choropleth_properties = choropleth_properties
|
50
54
|
|
55
|
+
self.fig = go.Figure()
|
56
|
+
self.discrete_map = (
|
57
|
+
self.discrete_category_column is not None
|
58
|
+
and self.discrete_category_order is not None
|
59
|
+
)
|
60
|
+
if self.discrete_map:
|
61
|
+
self.df_dict = self._get_dataframe_dict_by_category()
|
62
|
+
self.colours_list = self._get_colour_list()
|
63
|
+
|
51
64
|
def get_choropleth_map(self):
|
52
65
|
"""Creates and returns choropleth map chart for display on application.
|
53
66
|
|
@@ -56,7 +69,7 @@ class ChoroplethMap:
|
|
56
69
|
"""
|
57
70
|
self._update_fig()
|
58
71
|
choropleth_map = dcc.Graph(
|
59
|
-
id="
|
72
|
+
id=f"{self.map_name}-choropleth",
|
60
73
|
responsive=True,
|
61
74
|
config={"topojsonURL": "/assets/topojson/", "displayModeBar": False},
|
62
75
|
figure=self.fig,
|
@@ -66,7 +79,7 @@ class ChoroplethMap:
|
|
66
79
|
}, # height hard-coded so that map always displays within tab
|
67
80
|
)
|
68
81
|
return display_chart_or_table_with_header(
|
69
|
-
choropleth_map, download_data_button_id=self.
|
82
|
+
choropleth_map, download_data_button_id=self.map_name
|
70
83
|
)
|
71
84
|
|
72
85
|
def _update_fig(self):
|
@@ -75,61 +88,10 @@ class ChoroplethMap:
|
|
75
88
|
self._crop_to_region()
|
76
89
|
self._remove_background_map()
|
77
90
|
|
78
|
-
def _get_dataframe_dict_by_category(self):
|
79
|
-
if self.region is not None and self.region != "England":
|
80
|
-
self.dataframe = self.dataframe.filter(pl.col("Region") == self.region)
|
81
|
-
else:
|
82
|
-
self.dataframe = self.dataframe
|
83
|
-
grouped_dfs_dict_keys_as_tuples = self.dataframe.partition_by(
|
84
|
-
self.category_column, as_dict=True
|
85
|
-
)
|
86
|
-
grouped_dfs_dict = {
|
87
|
-
key[0]: value for key, value in grouped_dfs_dict_keys_as_tuples.items()
|
88
|
-
}
|
89
|
-
return grouped_dfs_dict
|
90
|
-
|
91
|
-
def _get_boundaries_by_area(self, dataframe):
|
92
|
-
las_to_display = dataframe["Area_Code"].to_list()
|
93
|
-
filtered_boundaries = {
|
94
|
-
key: (
|
95
|
-
value
|
96
|
-
if key != "features"
|
97
|
-
else [
|
98
|
-
features
|
99
|
-
for features in value
|
100
|
-
if features["properties"]["geo_id"] in las_to_display
|
101
|
-
]
|
102
|
-
)
|
103
|
-
for key, value in self.geographic_boundaries.items()
|
104
|
-
}
|
105
|
-
return filtered_boundaries
|
106
|
-
|
107
|
-
def _add_traces(self):
|
108
|
-
for count, category in enumerate(self.desired_category_order):
|
109
|
-
if category not in self.df_dict:
|
110
|
-
df = self._create_df_for_empty_trace(category)
|
111
|
-
else:
|
112
|
-
df = self.df_dict[category]
|
113
|
-
colour = self.colours_list[count % len(self.colours_list)]
|
114
|
-
self.fig.add_trace(
|
115
|
-
self._create_choropleth_trace(
|
116
|
-
df,
|
117
|
-
colour,
|
118
|
-
)
|
119
|
-
)
|
120
|
-
|
121
|
-
def _create_df_for_empty_trace(self, category):
|
122
|
-
"""Method to create df where all columns are empty except for the category column to force
|
123
|
-
all legend items to always appear"""
|
124
|
-
columns = next(iter(self.df_dict.values())).columns
|
125
|
-
data = {col: [None] for col in columns}
|
126
|
-
data[self.category_column] = [category]
|
127
|
-
return pl.DataFrame(data)
|
128
|
-
|
129
91
|
def _create_choropleth_trace(
|
130
92
|
self,
|
131
93
|
dataframe,
|
132
|
-
|
94
|
+
colourscale,
|
133
95
|
column_to_plot=None,
|
134
96
|
is_missing_data=False,
|
135
97
|
marker=None,
|
@@ -151,6 +113,7 @@ class ChoroplethMap:
|
|
151
113
|
hovertemplate = (
|
152
114
|
"<b>%{customdata[0]}</b><br>" + "%{hovertext}<extra></extra>"
|
153
115
|
)
|
116
|
+
|
154
117
|
return go.Choropleth(
|
155
118
|
geojson=self.geographic_boundaries,
|
156
119
|
featureidkey="properties.geo_id",
|
@@ -158,48 +121,79 @@ class ChoroplethMap:
|
|
158
121
|
locationmode="geojson-id",
|
159
122
|
z=dataframe[column_to_plot],
|
160
123
|
hovertext=(
|
161
|
-
dataframe[
|
124
|
+
dataframe[self.area_focus_level]
|
162
125
|
if not is_missing_data
|
163
|
-
else ["No data available"] * len(dataframe[
|
126
|
+
else ["No data available"] * len(dataframe[self.area_focus_level])
|
164
127
|
),
|
165
128
|
customdata=(
|
166
|
-
dataframe[self.
|
129
|
+
dataframe[self.hover_data_list]
|
167
130
|
if not is_missing_data
|
168
|
-
else dataframe[[
|
131
|
+
else dataframe[[self.area_focus_level]]
|
169
132
|
),
|
133
|
+
colorbar=self._get_color_bar(),
|
170
134
|
hovertemplate=hovertemplate,
|
171
135
|
marker=marker,
|
172
136
|
marker_line_color=colours.GovUKColours.DARK_GREY.value,
|
173
|
-
showscale=
|
174
|
-
showlegend=
|
175
|
-
colorscale=
|
176
|
-
|
177
|
-
[1, colour],
|
178
|
-
], # dataframe is grouped by column_to_plot, hence only
|
179
|
-
# contains one value for column_to_plot- this ensures a discrete categorical colourscale
|
180
|
-
# for trace
|
181
|
-
name=(
|
182
|
-
dataframe[self.category_column][0]
|
183
|
-
if not is_missing_data
|
184
|
-
else "No data available"
|
185
|
-
),
|
137
|
+
showscale=self._get_scale(),
|
138
|
+
showlegend=self._get_legend(),
|
139
|
+
colorscale=colourscale,
|
140
|
+
name=self._get_trace_name(dataframe, is_missing_data),
|
186
141
|
**self.choropleth_properties,
|
187
142
|
)
|
188
143
|
|
144
|
+
def _add_traces(self):
|
145
|
+
if not self.discrete_map:
|
146
|
+
self.fig.add_trace(
|
147
|
+
self._create_choropleth_trace(self.dataframe, DEFAULT_COLOURSCALE)
|
148
|
+
)
|
149
|
+
else:
|
150
|
+
for count, category in enumerate(self.discrete_category_order):
|
151
|
+
if category not in self.df_dict:
|
152
|
+
df = self._create_df_for_empty_trace(category)
|
153
|
+
else:
|
154
|
+
df = self.df_dict[category]
|
155
|
+
colour = [
|
156
|
+
[0, self.colours_list[count % len(self.colours_list)]],
|
157
|
+
[1, self.colours_list[count % len(self.colours_list)]],
|
158
|
+
]
|
159
|
+
self.fig.add_trace(
|
160
|
+
self._create_choropleth_trace(
|
161
|
+
df,
|
162
|
+
colour,
|
163
|
+
)
|
164
|
+
)
|
165
|
+
|
189
166
|
def _handle_missing_data(self):
|
190
167
|
missing_data = self.dataframe.filter(pl.col(self.column_to_plot).is_null())
|
191
168
|
missing_data = missing_data.with_columns(pl.lit(0).alias("data"))
|
192
169
|
|
170
|
+
colour = [
|
171
|
+
[0, colours.GovUKColours.MID_GREY.value],
|
172
|
+
[1, colours.GovUKColours.MID_GREY.value],
|
173
|
+
]
|
193
174
|
if not missing_data.is_empty():
|
194
175
|
self.fig.add_trace(
|
195
176
|
self._create_choropleth_trace(
|
196
177
|
missing_data,
|
197
|
-
|
178
|
+
colour,
|
198
179
|
column_to_plot="data",
|
199
180
|
is_missing_data=True,
|
200
181
|
)
|
201
182
|
)
|
202
183
|
|
184
|
+
def _get_dataframe_dict_by_category(self):
|
185
|
+
if self.region is not None and self.region != "England":
|
186
|
+
self.dataframe = self.dataframe.filter(pl.col("Region") == self.region)
|
187
|
+
else:
|
188
|
+
self.dataframe = self.dataframe
|
189
|
+
grouped_dfs_dict_keys_as_tuples = self.dataframe.partition_by(
|
190
|
+
self.discrete_category_column, as_dict=True
|
191
|
+
)
|
192
|
+
grouped_dfs_dict = {
|
193
|
+
key[0]: value for key, value in grouped_dfs_dict_keys_as_tuples.items()
|
194
|
+
}
|
195
|
+
return grouped_dfs_dict
|
196
|
+
|
203
197
|
def _crop_to_region(self):
|
204
198
|
self.fig.update_layout(
|
205
199
|
{
|
@@ -216,6 +210,33 @@ class ChoroplethMap:
|
|
216
210
|
},
|
217
211
|
)
|
218
212
|
|
213
|
+
def _get_color_bar(self):
|
214
|
+
if self.discrete_map:
|
215
|
+
return None
|
216
|
+
|
217
|
+
return {
|
218
|
+
"title": self.column_to_plot,
|
219
|
+
"thickness": 20,
|
220
|
+
"len": 0.8,
|
221
|
+
"x": 0.9,
|
222
|
+
"y": 0.5,
|
223
|
+
}
|
224
|
+
|
225
|
+
def _get_scale(self):
|
226
|
+
return not self.discrete_map
|
227
|
+
|
228
|
+
def _get_legend(self):
|
229
|
+
return self.discrete_map
|
230
|
+
|
231
|
+
def _get_trace_name(self, dataframe, missing_data=False):
|
232
|
+
if self.discrete_category_column is None:
|
233
|
+
return None
|
234
|
+
|
235
|
+
if not missing_data:
|
236
|
+
return dataframe[self.discrete_category_column][0]
|
237
|
+
|
238
|
+
return "No data available"
|
239
|
+
|
219
240
|
def _remove_background_map(self):
|
220
241
|
self.fig.update_geos(
|
221
242
|
center={"lat": 53, "lon": -2},
|
@@ -226,6 +247,15 @@ class ChoroplethMap:
|
|
226
247
|
def _get_colour_list(self):
|
227
248
|
"""Amends colour list based on the number of categories"""
|
228
249
|
colour_list = ["#217847", "#23BBBE", "#8CCE69", "#FFEA80"]
|
229
|
-
|
250
|
+
|
251
|
+
if self.discrete_map and len(self.discrete_category_order) == 3:
|
230
252
|
colour_list.pop(1)
|
231
253
|
return colour_list
|
254
|
+
|
255
|
+
def _create_df_for_empty_trace(self, category):
|
256
|
+
"""Method to create df where all columns are empty except for the category column to force
|
257
|
+
all legend items to always appear"""
|
258
|
+
columns = next(iter(self.df_dict.values())).columns
|
259
|
+
data = {col: [None] for col in columns}
|
260
|
+
data[self.discrete_category_column] = [category]
|
261
|
+
return pl.DataFrame(data)
|
gov_uk_dashboards-13.5.1/gov_uk_dashboards/components/plotly/generate_dash_graph_from_figure.py
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
"""Creates a Dash Graph component from a given Plotly figure"""
|
2
|
+
|
3
|
+
from typing import Any, Dict, Union
|
4
|
+
import plotly.graph_objects as Figure
|
5
|
+
from dash import dcc
|
6
|
+
|
7
|
+
|
8
|
+
def generate_dash_graph_from_figure(
|
9
|
+
figure: Figure,
|
10
|
+
graph_name: str,
|
11
|
+
graph_style: Union[Dict[str, Any], None] = None,
|
12
|
+
double_click_attribute: Union[str, bool] = False,
|
13
|
+
class_name: str = "",
|
14
|
+
) -> dcc.Graph:
|
15
|
+
"""
|
16
|
+
Creates a Dash Graph component from a given Plotly figure. This function allows for the
|
17
|
+
customisation of the graph's appearance and behavior, and includes default pointer cursor,
|
18
|
+
from "default-cursor-graph" class.
|
19
|
+
Args:
|
20
|
+
- figure: Plotly.graph_objects.Figure instance to be displayed within the Dash Graph component.
|
21
|
+
- graph_name: A name for the id of the graph component.
|
22
|
+
- graph_style: An optional dictionary specifying CSS styles to apply to the graph component.
|
23
|
+
- double_click_attribute: Determines the action taken on double-clicking the graph.
|
24
|
+
Can be a boolean or a string specifying the mode.
|
25
|
+
- className (str): A string containing one or more CSS class names to apply to the graph.
|
26
|
+
Returns:
|
27
|
+
- A dash.dcc.Graph component configured with the provided parameters and styles.
|
28
|
+
"""
|
29
|
+
|
30
|
+
if not graph_style:
|
31
|
+
graph_style = {}
|
32
|
+
|
33
|
+
if "height" not in graph_style.keys():
|
34
|
+
graph_style["height"] = "450px"
|
35
|
+
|
36
|
+
figure.update_layout(dragmode=False)
|
37
|
+
# pylint: disable=duplicate-code
|
38
|
+
return dcc.Graph(
|
39
|
+
id=f"{graph_name}-graph",
|
40
|
+
responsive=True,
|
41
|
+
figure=figure,
|
42
|
+
style=graph_style,
|
43
|
+
config={
|
44
|
+
"displayModeBar": False,
|
45
|
+
"doubleClick": double_click_attribute,
|
46
|
+
"scrollZoom": False,
|
47
|
+
},
|
48
|
+
className="default-cursor-graph " + class_name,
|
49
|
+
)
|
@@ -0,0 +1,354 @@
|
|
1
|
+
"""stacked_barchart function"""
|
2
|
+
|
3
|
+
from enum import Enum
|
4
|
+
import math
|
5
|
+
from typing import Optional, TypedDict
|
6
|
+
from dash import html
|
7
|
+
import polars as pl
|
8
|
+
|
9
|
+
import plotly.graph_objects as go
|
10
|
+
|
11
|
+
from constants import (
|
12
|
+
CHART_LABEL_FONT_SIZE,
|
13
|
+
CUSTOM_DATA,
|
14
|
+
DATE_VALID,
|
15
|
+
FINANCIAL_YEAR_ENDING,
|
16
|
+
HOVER_TEXT_HEADERS,
|
17
|
+
MAIN_TITLE,
|
18
|
+
MEASURE,
|
19
|
+
SUBTITLE,
|
20
|
+
VALUE,
|
21
|
+
)
|
22
|
+
from gov_uk_dashboards.colours import AFAccessibleColours
|
23
|
+
from gov_uk_dashboards.components.display_chart_or_table_with_header import (
|
24
|
+
display_chart_or_table_with_header,
|
25
|
+
)
|
26
|
+
from gov_uk_dashboards.components.plotly.generate_dash_graph_from_figure import (
|
27
|
+
generate_dash_graph_from_figure,
|
28
|
+
)
|
29
|
+
from gov_uk_dashboards.lib.plotting_helper_functions import get_legend_configuration
|
30
|
+
from gov_uk_dashboards.formatting.human_readable import format_as_human_readable
|
31
|
+
|
32
|
+
from gov_uk_dashboards.lib.update_layout_bgcolor_margin import (
|
33
|
+
update_layout_bgcolor_margin,
|
34
|
+
)
|
35
|
+
|
36
|
+
|
37
|
+
class XAxisFormat(Enum):
|
38
|
+
"""Enum for date format on x axis"""
|
39
|
+
|
40
|
+
YEAR = "year"
|
41
|
+
MONTH_YEAR = "month_year"
|
42
|
+
FINANCIAL_QUARTER = "financial_quarter"
|
43
|
+
|
44
|
+
|
45
|
+
class TitleDataStructure(TypedDict):
|
46
|
+
"""A TypedDict representing the structure of title_data"""
|
47
|
+
|
48
|
+
MAIN_TITLE: str
|
49
|
+
SUBTITLE: str
|
50
|
+
|
51
|
+
|
52
|
+
class HoverDataStructure(TypedDict):
|
53
|
+
"""A TypedDict representing the structure of hover_data"""
|
54
|
+
|
55
|
+
CUSTOM_DATA: list[str]
|
56
|
+
HOVER_TEXT_HEADERS: list[str]
|
57
|
+
|
58
|
+
|
59
|
+
class HoverDataByTrace(TypedDict):
|
60
|
+
"""A TypedDict representing hover_data organized by tracename"""
|
61
|
+
|
62
|
+
tracename: dict[
|
63
|
+
str, HoverDataStructure
|
64
|
+
] # Each tracename maps to a HoverDataStructure
|
65
|
+
|
66
|
+
|
67
|
+
class StackedBarChart:
|
68
|
+
"""Class for use in generating stacked bar charts."""
|
69
|
+
|
70
|
+
# pylint: disable=too-many-instance-attributes
|
71
|
+
# pylint: disable=too-many-arguments
|
72
|
+
# pylint: disable=too-many-locals
|
73
|
+
# pylint: disable = too-many-positional-arguments
|
74
|
+
def __init__(
|
75
|
+
self,
|
76
|
+
title_data: TitleDataStructure,
|
77
|
+
y_column: str,
|
78
|
+
hover_data: HoverDataByTrace,
|
79
|
+
df: pl.DataFrame,
|
80
|
+
trace_name_list: list[str],
|
81
|
+
trace_name_column: Optional[str] = None,
|
82
|
+
xaxis_tick_text_format: XAxisFormat = XAxisFormat.YEAR.value,
|
83
|
+
line_trace_name: Optional[str] = None,
|
84
|
+
x_axis_column=DATE_VALID,
|
85
|
+
download_chart_button_id: Optional[str] = None,
|
86
|
+
download_data_button_id: Optional[str] = None,
|
87
|
+
):
|
88
|
+
"""Initializes the StackedBarChart instance.
|
89
|
+
To display the chart, call the `get_stacked_bar_chart()` method.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
title_data (TitleDataStructure): Data structure containing the chart title information.
|
93
|
+
y_column (str): The column name representing the Y-axis data.
|
94
|
+
hover_data (HoverDataByTrace): Data structure for hover information.
|
95
|
+
df (pl.DataFrame): The dataset for the chart.
|
96
|
+
trace_name_list (list[str]): List of trace names for the stacked bars.
|
97
|
+
trace_name_column (Optional[str], optional): Column name representing trace categories,
|
98
|
+
if applicable. Defaults to None.
|
99
|
+
xaxis_tick_text_format (XAxisFormat, optional): Format for X-axis tick labels.
|
100
|
+
Defaults to XAxisFormat.YEAR.value.
|
101
|
+
line_trace_name (Optional[str], optional): Name for an optional line trace overlay,
|
102
|
+
must be in MEASURE column of df, line_trace_name will display in legend.
|
103
|
+
Defaults to None.
|
104
|
+
x_axis_column (_type_, optional): The column used for the X-axis values.
|
105
|
+
Defaults to DATE_VALID.
|
106
|
+
download_chart_button_id (Optional[str], optional): ID for the chart download button,
|
107
|
+
if applicable. Defaults to None.
|
108
|
+
download_data_button_id (Optional[str], optional): ID for the data download button, if
|
109
|
+
applicable. Defaults to None.
|
110
|
+
"""
|
111
|
+
self.title_data = title_data
|
112
|
+
self.y_axis_column = y_column
|
113
|
+
self.hover_data = hover_data
|
114
|
+
self.df = df
|
115
|
+
self.trace_name_list = trace_name_list
|
116
|
+
self.trace_name_column = trace_name_column
|
117
|
+
self.xaxis_tick_text_format = xaxis_tick_text_format
|
118
|
+
self.line_trace_name = line_trace_name
|
119
|
+
self.x_axis_column = x_axis_column
|
120
|
+
self.download_chart_button_id = download_chart_button_id
|
121
|
+
self.download_data_button_id = download_data_button_id
|
122
|
+
self.fig = self.create_stacked_bar_chart()
|
123
|
+
|
124
|
+
def get_stacked_bar_chart(self) -> html.Div:
|
125
|
+
"""Creates and returns stacked bar chart for display on application.
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
html.Div: Styled div containing title, subtile and chart.
|
129
|
+
"""
|
130
|
+
graph_name = self.title_data[MAIN_TITLE].replace(" ", "-")
|
131
|
+
return display_chart_or_table_with_header(
|
132
|
+
generate_dash_graph_from_figure(
|
133
|
+
self.fig,
|
134
|
+
graph_name,
|
135
|
+
class_name="default-cursor-graph non-interactive-legend-cursor",
|
136
|
+
),
|
137
|
+
self.title_data[MAIN_TITLE],
|
138
|
+
self.title_data[SUBTITLE],
|
139
|
+
self.download_chart_button_id,
|
140
|
+
self.download_data_button_id,
|
141
|
+
)
|
142
|
+
|
143
|
+
def create_stacked_bar_chart(
|
144
|
+
self,
|
145
|
+
):
|
146
|
+
"""generates a stacked bar chart"""
|
147
|
+
# pylint: disable=too-many-locals
|
148
|
+
fig = go.Figure()
|
149
|
+
colour_list = (
|
150
|
+
AFAccessibleColours.CATEGORICAL.value
|
151
|
+
if len(self.trace_name_list) != 2
|
152
|
+
else [
|
153
|
+
AFAccessibleColours.DARK_BLUE.value,
|
154
|
+
AFAccessibleColours.ORANGE.value,
|
155
|
+
] # if 2 lines should use dark blue & orange as have highest contrast ratio
|
156
|
+
)
|
157
|
+
for _, (df, trace_name, colour) in enumerate(
|
158
|
+
zip(
|
159
|
+
self._get_df_list_for_time_series(),
|
160
|
+
self.trace_name_list,
|
161
|
+
colour_list,
|
162
|
+
)
|
163
|
+
):
|
164
|
+
fig.add_trace(
|
165
|
+
self.create_bar_chart_trace(
|
166
|
+
df.sort(self.x_axis_column),
|
167
|
+
trace_name,
|
168
|
+
hover_label=None,
|
169
|
+
colour=colour,
|
170
|
+
)
|
171
|
+
)
|
172
|
+
|
173
|
+
if self.line_trace_name is not None:
|
174
|
+
colour = AFAccessibleColours.CATEGORICAL.value[len(self.trace_name_list)]
|
175
|
+
df = self.df.filter(pl.col(MEASURE) == self.line_trace_name)
|
176
|
+
|
177
|
+
fig.add_trace(
|
178
|
+
go.Scatter(
|
179
|
+
x=df[FINANCIAL_YEAR_ENDING],
|
180
|
+
y=df[VALUE],
|
181
|
+
customdata=self._get_custom_data(self.line_trace_name, df),
|
182
|
+
mode="lines",
|
183
|
+
line={"color": colour, "width": 3},
|
184
|
+
name=self.line_trace_name,
|
185
|
+
hovertemplate=self._get_hover_template(self.line_trace_name),
|
186
|
+
legendrank=99999, # a high number to ensure it is bottom of the legend
|
187
|
+
)
|
188
|
+
)
|
189
|
+
|
190
|
+
max_y, min_y, tickvals, ticktext = _get_y_range_tickvals_and_ticktext(
|
191
|
+
self.df, "£", self.trace_name_list
|
192
|
+
)
|
193
|
+
update_layout_bgcolor_margin(fig, "#FFFFFF")
|
194
|
+
|
195
|
+
fig.update_layout(
|
196
|
+
legend=get_legend_configuration(),
|
197
|
+
font={"size": CHART_LABEL_FONT_SIZE},
|
198
|
+
yaxis={
|
199
|
+
"range": [min_y * 1.1, max_y * 1.1],
|
200
|
+
"tickmode": "array",
|
201
|
+
"tickvals": tickvals,
|
202
|
+
"ticktext": ticktext,
|
203
|
+
},
|
204
|
+
showlegend=True,
|
205
|
+
barmode="relative",
|
206
|
+
xaxis={"categoryorder": "category ascending"},
|
207
|
+
)
|
208
|
+
return fig
|
209
|
+
|
210
|
+
def _format_x_axis(self, fig):
|
211
|
+
tick_text, tick_values, range_x = self._get_x_axis_content()
|
212
|
+
fig.update_xaxes(
|
213
|
+
tickvals=tick_values,
|
214
|
+
ticktext=tick_text,
|
215
|
+
tickmode="array",
|
216
|
+
range=range_x,
|
217
|
+
)
|
218
|
+
|
219
|
+
return tick_values
|
220
|
+
|
221
|
+
def create_bar_chart_trace(
|
222
|
+
self,
|
223
|
+
df: pl.DataFrame,
|
224
|
+
trace_name: str,
|
225
|
+
hover_label: dict[str, str],
|
226
|
+
colour: str,
|
227
|
+
):
|
228
|
+
"""Creates a trace for the plot.
|
229
|
+
|
230
|
+
Args:
|
231
|
+
df (pl.DataFrame): Dataframe to use to create trace. Must contain x and y columns,
|
232
|
+
and columns defined in self.hover_data[CUSTOM_DATA].
|
233
|
+
trace_name (str): Name of trace.
|
234
|
+
hover_label (dict[str,str]): Properties for hoverlabel parameter.
|
235
|
+
colour (str): Colour for bar.
|
236
|
+
"""
|
237
|
+
|
238
|
+
return go.Bar(
|
239
|
+
x=df[self.x_axis_column],
|
240
|
+
y=df[self.y_axis_column],
|
241
|
+
name=trace_name,
|
242
|
+
hovertemplate=[
|
243
|
+
self._get_hover_template(trace_name) for i in range(df.shape[0])
|
244
|
+
],
|
245
|
+
customdata=self._get_custom_data(trace_name, df),
|
246
|
+
hoverlabel=hover_label,
|
247
|
+
marker={"color": colour},
|
248
|
+
)
|
249
|
+
|
250
|
+
def _get_hover_template(self, trace_name):
|
251
|
+
hover_text_headers = self.hover_data[trace_name][HOVER_TEXT_HEADERS]
|
252
|
+
hover_template = (
|
253
|
+
f"{trace_name}<br>"
|
254
|
+
f"{hover_text_headers[0]}"
|
255
|
+
": %{customdata[0]}<br>"
|
256
|
+
f"{hover_text_headers[1]}"
|
257
|
+
": %{customdata[1]}<extra></extra>"
|
258
|
+
)
|
259
|
+
return hover_template
|
260
|
+
|
261
|
+
def _get_custom_data(self, trace_name, df):
|
262
|
+
customdata = df[self.hover_data[trace_name][CUSTOM_DATA]]
|
263
|
+
return customdata
|
264
|
+
|
265
|
+
def _get_x_axis_content(self):
|
266
|
+
"""Generates tick text and values for the x-axis based on the unique years calculated from
|
267
|
+
the FINANCIAL_YEAR_ENDING column in the dataframe.
|
268
|
+
Returns:
|
269
|
+
tuple: A tuple containing tick_text, tick_values and range_x.
|
270
|
+
"""
|
271
|
+
if self.xaxis_tick_text_format == XAxisFormat.YEAR.value:
|
272
|
+
year_list = self.df[FINANCIAL_YEAR_ENDING].unique().to_list()
|
273
|
+
int_min_year = int(min(year_list))
|
274
|
+
int_max_year = int(max(year_list))
|
275
|
+
|
276
|
+
tick_text = []
|
277
|
+
year = int_min_year
|
278
|
+
while year <= int_max_year:
|
279
|
+
tick_text.append(str(year + 1))
|
280
|
+
year = year + 1
|
281
|
+
|
282
|
+
tick_values = tick_text
|
283
|
+
|
284
|
+
range_x = [
|
285
|
+
tick_values[0],
|
286
|
+
tick_values[-1],
|
287
|
+
]
|
288
|
+
else:
|
289
|
+
raise ValueError(
|
290
|
+
f"Invalid xaxis_tick_text_format: {self.xaxis_tick_text_format}"
|
291
|
+
)
|
292
|
+
return tick_text, tick_values, range_x
|
293
|
+
|
294
|
+
def _get_df_list_for_time_series(self) -> list[pl.DataFrame]:
|
295
|
+
if self.trace_name_column is not None:
|
296
|
+
df_list = [
|
297
|
+
self.df.filter(pl.col(self.trace_name_column) == trace_name)
|
298
|
+
for trace_name in self.trace_name_list
|
299
|
+
]
|
300
|
+
else:
|
301
|
+
df_list = [self.df]
|
302
|
+
return df_list
|
303
|
+
|
304
|
+
|
305
|
+
def _get_y_range_tickvals_and_ticktext(
|
306
|
+
dataframe: pl.DataFrame, tick_prefix: str, yaxis_with_values: list[str]
|
307
|
+
):
|
308
|
+
barchart_df = dataframe.pivot(
|
309
|
+
index=FINANCIAL_YEAR_ENDING, columns=MEASURE, values=VALUE
|
310
|
+
)
|
311
|
+
positive_sum = sum(
|
312
|
+
pl.when(pl.col(col) > 0).then(pl.col(col)).otherwise(0)
|
313
|
+
for col in yaxis_with_values
|
314
|
+
)
|
315
|
+
negative_sum = sum(
|
316
|
+
pl.when(pl.col(col) < 0).then(pl.col(col)).otherwise(0)
|
317
|
+
for col in yaxis_with_values
|
318
|
+
)
|
319
|
+
barchart_df = barchart_df.with_columns(positive_sum.alias("Positive sum"))
|
320
|
+
barchart_df = barchart_df.with_columns(negative_sum.alias("Negative sum"))
|
321
|
+
maxy = barchart_df.select([pl.col("Positive sum").max()]).item()
|
322
|
+
miny = barchart_df.select([pl.col("Negative sum").min()]).item()
|
323
|
+
tickvals = _generate_tickvals(maxy, miny)
|
324
|
+
ticktext = [format_as_human_readable(val, prefix=tick_prefix) for val in tickvals]
|
325
|
+
return tickvals[-1], tickvals[0], tickvals, ticktext
|
326
|
+
|
327
|
+
|
328
|
+
def _generate_tickvals(maxy, miny):
|
329
|
+
range_size = maxy - miny
|
330
|
+
|
331
|
+
# Determine the order of magnitude of the range
|
332
|
+
order = int(math.log10(range_size))
|
333
|
+
|
334
|
+
# Start with an initial step size
|
335
|
+
step_size = 10**order
|
336
|
+
|
337
|
+
# Calculate the number of ticks based on the step size
|
338
|
+
num_ticks = math.ceil(range_size / step_size)
|
339
|
+
|
340
|
+
# Adjust step size to ensure the number of ticks is between 6 and 10
|
341
|
+
while num_ticks < 6 or num_ticks > 10:
|
342
|
+
if num_ticks < 6: # Too few ticks -> decrease step size
|
343
|
+
step_size /= 2
|
344
|
+
elif num_ticks > 10: # Too many ticks -> increase step size
|
345
|
+
step_size *= 2
|
346
|
+
num_ticks = math.ceil(range_size / step_size)
|
347
|
+
|
348
|
+
# Adjust the start and end of the range to align with the step size
|
349
|
+
start = math.floor(miny / step_size) * step_size
|
350
|
+
end = math.ceil(maxy / step_size) * step_size
|
351
|
+
|
352
|
+
# Generate tick values
|
353
|
+
tickvals = list(range(int(start), int(end) + 1, int(step_size)))
|
354
|
+
return tickvals
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"""Helper functions for use to plot charts"""
|
2
|
+
|
3
|
+
|
4
|
+
from constants import (
|
5
|
+
CHART_LABEL_FONT_SIZE,
|
6
|
+
)
|
7
|
+
|
8
|
+
|
9
|
+
def get_legend_configuration(itemclick=True, itemdoubleclick=True):
|
10
|
+
"""
|
11
|
+
Returns the legend configuration for charts with customizable interaction settings.
|
12
|
+
Args:
|
13
|
+
itemclick (bool): Determines whether clicking on a legend item toggles its visibility.
|
14
|
+
Set to True by default, allowing click interactions.
|
15
|
+
itemdoubleclick (bool): Determines the behavior when double-clicking on a legend item.
|
16
|
+
Set to True by default, allowing double-click interactions.
|
17
|
+
Returns:
|
18
|
+
dict: A dictionary containing the configuration settings for the legend.
|
19
|
+
"""
|
20
|
+
return {
|
21
|
+
"x": 0,
|
22
|
+
"y": -0.22,
|
23
|
+
"yanchor": "top",
|
24
|
+
"traceorder": "normal",
|
25
|
+
"orientation": "v",
|
26
|
+
"font": {"size": CHART_LABEL_FONT_SIZE},
|
27
|
+
"itemclick": "toggle" if itemclick else False,
|
28
|
+
"itemdoubleclick": "toggle" if itemdoubleclick else False,
|
29
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
"""Function to update the background colour and margin for plots"""
|
2
|
+
|
3
|
+
import plotly.graph_objects as go
|
4
|
+
from gov_uk_dashboards import colours
|
5
|
+
|
6
|
+
|
7
|
+
def update_layout_bgcolor_margin(fig: go.Figure, colour: str):
|
8
|
+
"""update background colour and margin for plot"""
|
9
|
+
fig.update_layout(
|
10
|
+
plot_bgcolor=colour,
|
11
|
+
paper_bgcolor=colour,
|
12
|
+
yaxis_zerolinecolor=colour,
|
13
|
+
margin={"l": 0, "r": 0, "b": 0, "t": 0},
|
14
|
+
)
|
15
|
+
fig.update_xaxes(
|
16
|
+
gridcolor=colour,
|
17
|
+
zerolinecolor=colour,
|
18
|
+
ticks="outside",
|
19
|
+
tickcolor=colours.GovUKColours.MID_GREY.value,
|
20
|
+
)
|
21
|
+
fig.update_yaxes(
|
22
|
+
gridcolor=colours.GovUKColours.MID_GREY.value,
|
23
|
+
zerolinecolor=colours.GovUKColours.MID_GREY.value,
|
24
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: gov_uk_dashboards
|
3
|
-
Version: 13.
|
3
|
+
Version: 13.5.1
|
4
4
|
Summary: Provides access to functionality common to creating a data dashboard.
|
5
5
|
Author: Department for Levelling Up, Housing and Communities
|
6
6
|
Description-Content-Type: text/markdown
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards.egg-info/SOURCES.txt
RENAMED
@@ -60,6 +60,7 @@ gov_uk_dashboards/components/plotly/details.py
|
|
60
60
|
gov_uk_dashboards/components/plotly/download_button.py
|
61
61
|
gov_uk_dashboards/components/plotly/filter_panel.py
|
62
62
|
gov_uk_dashboards/components/plotly/footer.py
|
63
|
+
gov_uk_dashboards/components/plotly/generate_dash_graph_from_figure.py
|
63
64
|
gov_uk_dashboards/components/plotly/graph.py
|
64
65
|
gov_uk_dashboards/components/plotly/header.py
|
65
66
|
gov_uk_dashboards/components/plotly/heading.py
|
@@ -73,6 +74,7 @@ gov_uk_dashboards/components/plotly/paragraph.py
|
|
73
74
|
gov_uk_dashboards/components/plotly/phase_banner.py
|
74
75
|
gov_uk_dashboards/components/plotly/row_component.py
|
75
76
|
gov_uk_dashboards/components/plotly/side_navbar.py
|
77
|
+
gov_uk_dashboards/components/plotly/stacked_barchart.py
|
76
78
|
gov_uk_dashboards/components/plotly/table.py
|
77
79
|
gov_uk_dashboards/components/plotly/tooltip.py
|
78
80
|
gov_uk_dashboards/components/plotly/tooltip_title.py
|
@@ -94,6 +96,8 @@ gov_uk_dashboards/lib/__init__.py
|
|
94
96
|
gov_uk_dashboards/lib/enable_basic_auth.py
|
95
97
|
gov_uk_dashboards/lib/http_headers.py
|
96
98
|
gov_uk_dashboards/lib/logging.py
|
99
|
+
gov_uk_dashboards/lib/plotting_helper_functions.py
|
100
|
+
gov_uk_dashboards/lib/update_layout_bgcolor_margin.py
|
97
101
|
gov_uk_dashboards/lib/dap/__init__.py
|
98
102
|
gov_uk_dashboards/lib/dap/dap_deployment.py
|
99
103
|
gov_uk_dashboards/lib/dap/get_dataframe_from_cds.py
|
@@ -10,7 +10,7 @@ setup(
|
|
10
10
|
author="Department for Levelling Up, Housing and Communities",
|
11
11
|
description="Provides access to functionality common to creating a data dashboard.",
|
12
12
|
name="gov_uk_dashboards",
|
13
|
-
version="13.
|
13
|
+
version="13.5.1",
|
14
14
|
long_description=long_description,
|
15
15
|
long_description_content_type="text/markdown",
|
16
16
|
packages=find_packages(),
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/dashboard.css
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/get_assets_folder.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/assets/mobile-nav.js
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/banners.py
RENAMED
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/card.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/details.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/footer.py
RENAMED
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/graph.py
RENAMED
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/header.py
RENAMED
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/heading.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/navbar.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/table.py
RENAMED
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/components/plotly/tooltip.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/chart_data.py
RENAMED
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/enums/__init__.py
RENAMED
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/line_chart.py
RENAMED
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/styles/__init__.py
RENAMED
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/figures/styles/line_style.py
RENAMED
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/formatting/__init__.py
RENAMED
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/formatting/human_readable.py
RENAMED
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/formatting/rounding.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/lib/dap/dap_deployment.py
RENAMED
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards/lib/enable_basic_auth.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards.egg-info/requires.txt
RENAMED
File without changes
|
{gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.1}/gov_uk_dashboards.egg-info/top_level.txt
RENAMED
File without changes
|
File without changes
|