gov-uk-dashboards 19.1.0__py3-none-any.whl → 21.0.0__py3-none-any.whl
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/components/helpers/display_chart_or_table_with_header.py +6 -0
- gov_uk_dashboards/components/helpers/get_chart_for_download.py +14 -1
- gov_uk_dashboards/components/plotly/time_series_chart.py +52 -7
- gov_uk_dashboards/lib/download_functions/convert_fig_to_image_and_download.py +1 -1
- gov_uk_dashboards/lib/download_functions/download_csv_with_headers.py +4 -3
- {gov_uk_dashboards-19.1.0.dist-info → gov_uk_dashboards-21.0.0.dist-info}/METADATA +1 -1
- {gov_uk_dashboards-19.1.0.dist-info → gov_uk_dashboards-21.0.0.dist-info}/RECORD +10 -10
- {gov_uk_dashboards-19.1.0.dist-info → gov_uk_dashboards-21.0.0.dist-info}/WHEEL +1 -1
- {gov_uk_dashboards-19.1.0.dist-info → gov_uk_dashboards-21.0.0.dist-info}/licenses/LICENSE +0 -0
- {gov_uk_dashboards-19.1.0.dist-info → gov_uk_dashboards-21.0.0.dist-info}/top_level.txt +0 -0
@@ -7,14 +7,18 @@ from gov_uk_dashboards.components.dash.download_button import (
|
|
7
7
|
create_download_button_with_icon,
|
8
8
|
)
|
9
9
|
from gov_uk_dashboards.components.dash.heading import HeadingSizes
|
10
|
+
from gov_uk_dashboards.components.dash.paragraph import paragraph
|
10
11
|
|
11
12
|
|
13
|
+
# pylint: disable=too-many-arguments
|
14
|
+
# pylint: disable=too-many-positional-arguments
|
12
15
|
def display_chart_or_table_with_header(
|
13
16
|
chart_or_table: Component,
|
14
17
|
heading: str = None,
|
15
18
|
sub_heading: str = None,
|
16
19
|
download_button_id: str = None,
|
17
20
|
download_data_button_id: str = None,
|
21
|
+
footnote: str = None,
|
18
22
|
) -> html.Div:
|
19
23
|
"""Generate the wrapping information/header for a chart or table.
|
20
24
|
|
@@ -25,6 +29,7 @@ def display_chart_or_table_with_header(
|
|
25
29
|
download_button_id (str, optional): id for download button if required. Defaults to None.
|
26
30
|
if None then the button will not be included.
|
27
31
|
download_data_button_id (str, optional): the id to be applied to the download data button.
|
32
|
+
footnote (str, optional): the footnote to be added to charts and downloads.
|
28
33
|
|
29
34
|
Returns:
|
30
35
|
html.Div: Div containing Header and chart/table.
|
@@ -50,6 +55,7 @@ def display_chart_or_table_with_header(
|
|
50
55
|
]
|
51
56
|
),
|
52
57
|
chart_or_table,
|
58
|
+
html.Div([paragraph(footnote)], style={"padding-top": "20px"}),
|
53
59
|
html.Div(
|
54
60
|
(
|
55
61
|
[
|
@@ -6,6 +6,7 @@ def get_chart_for_download(self, fig):
|
|
6
6
|
"""Returns a fig with title and subtitle for download as png"""
|
7
7
|
main_title = self.title_data[MAIN_TITLE]
|
8
8
|
subtitle = self.title_data[SUBTITLE]
|
9
|
+
footnote = self.footnote
|
9
10
|
|
10
11
|
fig.update_layout(
|
11
12
|
title={
|
@@ -16,6 +17,18 @@ def get_chart_for_download(self, fig):
|
|
16
17
|
title_subtitle={
|
17
18
|
"text": f"<b><span style='color: black; margin: 0px 0px 5px'>{subtitle}</span></b>"
|
18
19
|
},
|
19
|
-
|
20
|
+
annotations=[
|
21
|
+
{
|
22
|
+
"text": f"<span style='color: black; font-size: 18px;'>{footnote}</span>",
|
23
|
+
"x": -0.01,
|
24
|
+
"y": -0.4, # Bottom of the plotting area
|
25
|
+
"xref": "paper",
|
26
|
+
"yref": "paper",
|
27
|
+
"xanchor": "left",
|
28
|
+
"yanchor": "top",
|
29
|
+
"showarrow": False,
|
30
|
+
"align": "left",
|
31
|
+
}
|
32
|
+
],
|
20
33
|
)
|
21
34
|
return fig
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""get_time_series_chart function"""
|
2
2
|
|
3
3
|
from datetime import datetime, date
|
4
|
+
import json
|
4
5
|
from typing import Optional
|
5
6
|
from dateutil.relativedelta import relativedelta
|
6
7
|
from dash import html
|
@@ -58,7 +59,7 @@ class TimeSeriesChart:
|
|
58
59
|
def __init__(
|
59
60
|
self,
|
60
61
|
title_data: TitleDataStructure,
|
61
|
-
|
62
|
+
y_axis_column: str,
|
62
63
|
hover_data: HoverDataByTrace,
|
63
64
|
filtered_df: pl.DataFrame,
|
64
65
|
trace_name_list: list[str],
|
@@ -81,9 +82,10 @@ class TimeSeriesChart:
|
|
81
82
|
number_of_traces_colour_shift_dict: Optional[dict] = None,
|
82
83
|
additional_line: Optional[dict] = None,
|
83
84
|
hover_distance: Optional[int] = 1,
|
85
|
+
footnote: Optional[str] = None,
|
84
86
|
):
|
85
87
|
self.title_data = title_data
|
86
|
-
self.y_axis_column =
|
88
|
+
self.y_axis_column = y_axis_column
|
87
89
|
self.hover_data = hover_data
|
88
90
|
self.hover_data_for_traces_with_different_hover_for_last_point = (
|
89
91
|
hover_data_for_traces_with_different_hover_for_last_point
|
@@ -95,7 +97,7 @@ class TimeSeriesChart:
|
|
95
97
|
self.xaxis_tick_text_format = xaxis_tick_text_format
|
96
98
|
self.verticle_line_x_value_and_name = verticle_line_x_value_and_name
|
97
99
|
self.filled_traces_dict = filled_traces_dict
|
98
|
-
self.
|
100
|
+
self.trace_names_to_prevent_hover_of_first_point_list = (
|
99
101
|
trace_names_to_prevent_hover_of_first_point_list
|
100
102
|
)
|
101
103
|
self.x_axis_column = x_axis_column
|
@@ -111,10 +113,10 @@ class TimeSeriesChart:
|
|
111
113
|
self.hover_distance = hover_distance
|
112
114
|
self.colour_list = self._get_colour_list()
|
113
115
|
self.fig = self.create_time_series_chart()
|
116
|
+
self.footnote = footnote
|
114
117
|
|
115
118
|
def get_time_series_chart(self) -> html.Div:
|
116
119
|
"""Creates and returns time series chart for display on application.
|
117
|
-
|
118
120
|
Returns:
|
119
121
|
html.Div: Styled div containing title, subtile and chart.
|
120
122
|
"""
|
@@ -130,8 +132,52 @@ class TimeSeriesChart:
|
|
130
132
|
self.title_data[SUBTITLE],
|
131
133
|
self.download_chart_button_id,
|
132
134
|
self.download_data_button_id,
|
135
|
+
self.footnote,
|
133
136
|
)
|
134
137
|
|
138
|
+
def is_json_serializable(self, value):
|
139
|
+
"Determines whether value can be converted to json format."
|
140
|
+
# pylint: disable=duplicate-code
|
141
|
+
try:
|
142
|
+
json.dumps(value)
|
143
|
+
return True
|
144
|
+
except (TypeError, OverflowError):
|
145
|
+
return False
|
146
|
+
|
147
|
+
def to_dict(self):
|
148
|
+
"Converts class attributes to json format."
|
149
|
+
# pylint: disable=duplicate-code
|
150
|
+
result = {}
|
151
|
+
for k, v in self.__dict__.items():
|
152
|
+
if self.is_json_serializable(v):
|
153
|
+
result[k] = v
|
154
|
+
elif isinstance(v, pl.DataFrame):
|
155
|
+
result[k] = {"_type": "polars_df", "data": v.to_dicts()}
|
156
|
+
elif isinstance(v, pl.Series):
|
157
|
+
result[k] = {"_type": "series", "data": v.to_list()}
|
158
|
+
else:
|
159
|
+
result[k] = f"<<non-serializable: {type(v).__name__}>>"
|
160
|
+
return result
|
161
|
+
|
162
|
+
@classmethod
|
163
|
+
def from_dict(cls, data):
|
164
|
+
"Creates a class instance from dict of attributes."
|
165
|
+
restored = {}
|
166
|
+
for k, v in data.items():
|
167
|
+
if k in ["markers", "colour_list", "fig"]:
|
168
|
+
continue
|
169
|
+
if isinstance(v, dict) and "_type" in v:
|
170
|
+
if v["_type"] == "polars_df":
|
171
|
+
restored[k] = pl.DataFrame(v["data"])
|
172
|
+
elif v["_type"] == "polars_series":
|
173
|
+
restored[k] = pl.Series(v["data"])
|
174
|
+
else:
|
175
|
+
# Fallback for unknown _type
|
176
|
+
restored[k] = v.get("data", None)
|
177
|
+
else:
|
178
|
+
restored[k] = v
|
179
|
+
return cls(**restored)
|
180
|
+
|
135
181
|
def get_time_series_chart_for_download(self):
|
136
182
|
"""Return fig with title and subtitle for download as png"""
|
137
183
|
return get_chart_for_download(self, self.create_time_series_chart())
|
@@ -340,7 +386,6 @@ class TimeSeriesChart:
|
|
340
386
|
legendgroup: str,
|
341
387
|
):
|
342
388
|
"""Creates a trace for the plot.
|
343
|
-
|
344
389
|
Args:
|
345
390
|
df (pl.DataFrame): Dataframe to use to create trace. Must contain "Date valid" column,
|
346
391
|
y_value column and columns defined in self.hover_data[CUSTOM_DATA].
|
@@ -369,8 +414,8 @@ class TimeSeriesChart:
|
|
369
414
|
(
|
370
415
|
""
|
371
416
|
if i == 0
|
372
|
-
and self.
|
373
|
-
and trace_name in self.
|
417
|
+
and self.trace_names_to_prevent_hover_of_first_point_list is not None
|
418
|
+
and trace_name in self.trace_names_to_prevent_hover_of_first_point_list
|
374
419
|
else self._get_custom_hover_template(i, df, trace_name)
|
375
420
|
)
|
376
421
|
for i in range(df.shape[0]) # the number of rows in df
|
@@ -9,7 +9,7 @@ def convert_fig_to_image_and_download(fig: go.Figure, name: str):
|
|
9
9
|
"""Converts a given Plotly figure to a PNG image and returns a Dash component
|
10
10
|
for downloading the image.
|
11
11
|
"""
|
12
|
-
fig.update_layout(margin={"t": 150, "b":
|
12
|
+
fig.update_layout(margin={"t": 150, "b": 250, "l": 100, "r": 50})
|
13
13
|
|
14
14
|
filename = f"{name}-chart.png"
|
15
15
|
img_bytes = pio.to_image(fig, format="png", width=1600, height=800)
|
@@ -38,7 +38,7 @@ def download_csv_with_headers(
|
|
38
38
|
} # range is missing columns in first df compared to max columns across all dfs
|
39
39
|
|
40
40
|
subtitle = list_of_df_title_subtitle_dicts[0]["subtitle"]
|
41
|
-
|
41
|
+
footnote = list_of_df_title_subtitle_dicts[0]["footnote"]
|
42
42
|
header_data = [
|
43
43
|
{column_list[0]: "Date downloaded: " + get_todays_date_for_downloaded_csv()},
|
44
44
|
*(
|
@@ -53,6 +53,7 @@ def download_csv_with_headers(
|
|
53
53
|
), # Uses unpacking (*) to add the subtitle row if subtitle is not None. If subtitle is
|
54
54
|
# None, it unpacks an empty list, effectively skipping the row.
|
55
55
|
{column_list[0]: None}, # Blank row
|
56
|
+
*([{column_list[0]: footnote}] if footnote is not None else []),
|
56
57
|
{**column_dict, **blank_dict},
|
57
58
|
]
|
58
59
|
|
@@ -60,12 +61,11 @@ def download_csv_with_headers(
|
|
60
61
|
header_data = [{column_list[0]: sensitivity_label}] + header_data
|
61
62
|
|
62
63
|
pl.DataFrame(header_data).write_csv(csv_buffer, include_header=False)
|
63
|
-
|
64
64
|
for i, data in enumerate(list_of_df_title_subtitle_dicts):
|
65
65
|
df = data["df"]
|
66
66
|
title = data["title"]
|
67
67
|
subtitle = data["subtitle"]
|
68
|
-
|
68
|
+
footnote = data.get("footnote")
|
69
69
|
if i > 0 and title is not None:
|
70
70
|
column_dict = {column_name: column_name for column_name in list(df.columns)}
|
71
71
|
header_data = [
|
@@ -75,6 +75,7 @@ def download_csv_with_headers(
|
|
75
75
|
), # Uses unpacking (*) to add the subtitle row if subtitle is not None. If
|
76
76
|
# subtitle is None, it unpacks an empty list, effectively skipping the row.
|
77
77
|
{column_list[0]: None}, # Blank row
|
78
|
+
*([{column_list[0]: footnote}] if footnote is not None else []),
|
78
79
|
]
|
79
80
|
pl.DataFrame(header_data).write_csv(csv_buffer, include_header=False)
|
80
81
|
df.write_csv(csv_buffer, include_header=i > 0)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: gov_uk_dashboards
|
3
|
-
Version:
|
3
|
+
Version: 21.0.0
|
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
|
@@ -69,9 +69,9 @@ gov_uk_dashboards/components/dash/visualisation_commentary.py,sha256=jBy8qy2DWYq
|
|
69
69
|
gov_uk_dashboards/components/dash/visualisation_title.py,sha256=xXHsyVRIGkngBXCuhv8z0PmZLOeicjpMvmTbA5HhRvQ,561
|
70
70
|
gov_uk_dashboards/components/dash/warning_text.py,sha256=31XvPUINt2QsWIaaMnqEvn23G-MpzWbDvGnzYcXIyAY,1622
|
71
71
|
gov_uk_dashboards/components/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
72
|
-
gov_uk_dashboards/components/helpers/display_chart_or_table_with_header.py,sha256=
|
72
|
+
gov_uk_dashboards/components/helpers/display_chart_or_table_with_header.py,sha256=wDK8VUfaK4cfJDwafN_U1tf4R0kxzv9_V98vnXO-sio,3255
|
73
73
|
gov_uk_dashboards/components/helpers/generate_dash_graph_from_figure.py,sha256=sdhC6Mjfw6kqs1MDRDoMuOt8dNS9Bl1WEoJX9S5AssA,1813
|
74
|
-
gov_uk_dashboards/components/helpers/get_chart_for_download.py,sha256=
|
74
|
+
gov_uk_dashboards/components/helpers/get_chart_for_download.py,sha256=EJGShQ9r9OePnfUAcRdlTFw3FDPFQp4jDCYcVJB94nI,1113
|
75
75
|
gov_uk_dashboards/components/helpers/plotting_helper_functions.py,sha256=WUif1mlSgWPuTZptBqaElWpJTlitmuiJofMTpOe_Bsg,1833
|
76
76
|
gov_uk_dashboards/components/helpers/update_layout_bgcolor_margin.py,sha256=i7Nwp0CxFpkyQeR8KfOBVMBkzctG7hMpWI2OzgxB2jY,740
|
77
77
|
gov_uk_dashboards/components/leaflet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -81,7 +81,7 @@ gov_uk_dashboards/components/plotly/captioned_figure.py,sha256=T0sbtGTiJ79FXxVdP
|
|
81
81
|
gov_uk_dashboards/components/plotly/choropleth_map.py,sha256=U9RmS3MZGloQAt9HoSYh3Xad205DDfZOjz91ZD_ydbI,9849
|
82
82
|
gov_uk_dashboards/components/plotly/enums.py,sha256=ebHiFQljA7O4HJxKoslqlNXcAjUZi1askpTSwxKEcYQ,850
|
83
83
|
gov_uk_dashboards/components/plotly/stacked_barchart.py,sha256=61VgvGJhFX7C4QAk-A8ZF6DTNjDvZ7uU3vof54H5km4,14187
|
84
|
-
gov_uk_dashboards/components/plotly/time_series_chart.py,sha256=
|
84
|
+
gov_uk_dashboards/components/plotly/time_series_chart.py,sha256=6mfNPStKmP9T1OmKZtLg8NIWglLm5svggSDcEApKABU,24737
|
85
85
|
gov_uk_dashboards/figures/__init__.py,sha256=_snQeNlM81nNKERl4gg9ScH2HYbtwaBjl8yQonccx34,712
|
86
86
|
gov_uk_dashboards/figures/chart_data.py,sha256=fEsNkQFzXKIQ0h7aLBWq3J1qAxHbxvJeKU23JrVodVc,757
|
87
87
|
gov_uk_dashboards/figures/line_chart.py,sha256=rEB51_z9cPl7BcT94iA6ZsZXzecnVCnGpQWW_9SNGUY,2813
|
@@ -104,10 +104,10 @@ gov_uk_dashboards/lib/dap/get_dataframe_from_cds.py,sha256=OiusRCgYnkBjK_GZgYLGz
|
|
104
104
|
gov_uk_dashboards/lib/datetime_functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
105
105
|
gov_uk_dashboards/lib/datetime_functions/datetime_functions.py,sha256=BQgr8I_vFNYwLi-fE4YC6jZ5PpbPea2rnD_2a_lw0YE,12209
|
106
106
|
gov_uk_dashboards/lib/download_functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
107
|
-
gov_uk_dashboards/lib/download_functions/convert_fig_to_image_and_download.py,sha256
|
108
|
-
gov_uk_dashboards/lib/download_functions/download_csv_with_headers.py,sha256=
|
109
|
-
gov_uk_dashboards-
|
110
|
-
gov_uk_dashboards-
|
111
|
-
gov_uk_dashboards-
|
112
|
-
gov_uk_dashboards-
|
113
|
-
gov_uk_dashboards-
|
107
|
+
gov_uk_dashboards/lib/download_functions/convert_fig_to_image_and_download.py,sha256=-dkYbpTL6txftFfAZW-vfW_O9efb6Dse9WJ9MM6mAqw,534
|
108
|
+
gov_uk_dashboards/lib/download_functions/download_csv_with_headers.py,sha256=uKMxKrdhaLj1c0ZWAmc02uu8PICjbn1fWt9YBw8FGnQ,4408
|
109
|
+
gov_uk_dashboards-21.0.0.dist-info/licenses/LICENSE,sha256=GDiD7Y2Gx7JucPV1JfVySJeah-qiSyBPdpJ6RHCEHTc,1126
|
110
|
+
gov_uk_dashboards-21.0.0.dist-info/METADATA,sha256=zejLJS9ttCDHYzULcE7TglrqfUNUz4Sfsh3hRsvt2Ok,5917
|
111
|
+
gov_uk_dashboards-21.0.0.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
|
112
|
+
gov_uk_dashboards-21.0.0.dist-info/top_level.txt,sha256=gPaN1P3-H3Rgi2me6tt-fX_cxo19CZfA4PjlZPjGRpo,18
|
113
|
+
gov_uk_dashboards-21.0.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|