gov-uk-dashboards 16.2.0__py3-none-any.whl → 17.1.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.
Files changed (21) hide show
  1. gov_uk_dashboards/assets/custom_map_style_functions.js +44 -0
  2. gov_uk_dashboards/components/dash/__init__.py +0 -1
  3. gov_uk_dashboards/components/dash/download_button.py +28 -24
  4. gov_uk_dashboards/components/helpers/display_chart_or_table_with_header.py +17 -22
  5. gov_uk_dashboards/components/helpers/get_chart_for_download.py +21 -0
  6. gov_uk_dashboards/components/leaflet/__init__.py +0 -0
  7. gov_uk_dashboards/components/leaflet/leaflet_choropleth_map.py +174 -0
  8. gov_uk_dashboards/components/plotly/stacked_barchart.py +7 -0
  9. gov_uk_dashboards/components/plotly/time_series_chart.py +7 -0
  10. gov_uk_dashboards/constants.py +6 -0
  11. gov_uk_dashboards/formatting/number_formatting.py +24 -0
  12. gov_uk_dashboards/lib/download_functions/__init__.py +0 -0
  13. gov_uk_dashboards/lib/download_functions/convert_fig_to_image_and_download.py +16 -0
  14. gov_uk_dashboards/lib/download_functions/download_csv_with_headers.py +114 -0
  15. {gov_uk_dashboards-16.2.0.dist-info → gov_uk_dashboards-17.1.0.dist-info}/METADATA +1 -1
  16. {gov_uk_dashboards-16.2.0.dist-info → gov_uk_dashboards-17.1.0.dist-info}/RECORD +19 -13
  17. {gov_uk_dashboards-16.2.0.dist-info → gov_uk_dashboards-17.1.0.dist-info}/WHEEL +1 -1
  18. gov_uk_dashboards/components/dash/create_download_chart_button.py +0 -25
  19. gov_uk_dashboards/components/dash/create_download_data_button.py +0 -25
  20. {gov_uk_dashboards-16.2.0.dist-info → gov_uk_dashboards-17.1.0.dist-info}/licenses/LICENSE +0 -0
  21. {gov_uk_dashboards-16.2.0.dist-info → gov_uk_dashboards-17.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,44 @@
1
+ window.myNamespace = Object.assign({}, window.myNamespace, {
2
+ mapColorScaleFunctions: {
3
+ continuousColorScale: function(feature, context) {
4
+ const {
5
+ colorscale,
6
+ colorProp,
7
+ style,
8
+ min,
9
+ max
10
+ } = context.hideout;
11
+ const value = feature.properties[colorProp];
12
+ const colors = Array.from(colorscale); // defensive copy
13
+ if (value === null || value === undefined) {
14
+ return {
15
+ ...style,
16
+ fillColor: "#b1b4b6"
17
+ };
18
+ }
19
+ // Normalize value to 0-1
20
+ const t = (value - min) / (max - min);
21
+
22
+ // Helper: interpolate between two hex colors
23
+ function interpolateColor(color1, color2, t) {
24
+ const c1 = parseInt(color1.slice(1), 16);
25
+ const c2 = parseInt(color2.slice(1), 16);
26
+ const r = Math.round(((c2 >> 16) - (c1 >> 16)) * t + (c1 >> 16));
27
+ const g = Math.round((((c2 >> 8) & 0xFF) - ((c1 >> 8) & 0xFF)) * t + ((c1 >> 8) & 0xFF));
28
+ const b = Math.round(((c2 & 0xFF) - (c1 & 0xFF)) * t + (c1 & 0xFF));
29
+ return `rgb(${r},${g},${b})`;
30
+ }
31
+
32
+ // Find segment and interpolate
33
+ const n = colors.length - 1;
34
+ const idx = Math.min(Math.floor(t * n), n - 1);
35
+ const local_t = (t * n) - idx;
36
+ const fillColor = interpolateColor(colors[idx], colors[idx + 1], local_t);
37
+
38
+ return {
39
+ ...style,
40
+ fillColor: fillColor
41
+ };
42
+ }
43
+ }
44
+ });
@@ -68,7 +68,6 @@ from .card import card, empty_card
68
68
  from .card_full_width import card_full_width
69
69
  from .apply_and_reset_filters_buttons import apply_and_reset_filters_buttons
70
70
  from .download_button import download_button
71
- from .download_button import download_button_with_icon
72
71
  from .collapsible_panel import collapsible_panel
73
72
  from .dashboard_container import dashboard_container
74
73
  from .details import details
@@ -2,6 +2,8 @@
2
2
  import warnings
3
3
  from dash import html
4
4
 
5
+ from gov_uk_dashboards.constants import DOWNLOAD_BUTTON_CLASSES
6
+
5
7
 
6
8
  def download_button(button_text: str, button_id: str = "download-button"):
7
9
  """
@@ -13,7 +15,7 @@ def download_button(button_text: str, button_id: str = "download-button"):
13
15
  """
14
16
  warnings.warn(
15
17
  "Note there is an alternative function to download_button() called "
16
- "download_button_with_icon() which includes a download icon and improved styling.",
18
+ "create_download_button_with_icon() which includes a download icon and improved styling.",
17
19
  Warning,
18
20
  stacklevel=2,
19
21
  )
@@ -32,31 +34,33 @@ def download_button(button_text: str, button_id: str = "download-button"):
32
34
  )
33
35
 
34
36
 
35
- def download_button_with_icon(button_text: str, button_id: str = "download-button"):
36
- """
37
- Return a download button styled with additional classes and an SVG icon,
38
- aligned to the right.
37
+ def create_download_button_with_icon(
38
+ button_text: str, button_id_name: str
39
+ ) -> html.Button:
40
+ """Create a download button with icon, aligned to the left.
39
41
 
40
- Args:
41
- button_text (str): The text to display on the button.
42
- button_id (str, Optional): ID for the button, defaults to "download-button".
42
+ Parameters:
43
+ - button_id_name (str): A unique identifier for the button.
44
+
45
+ Returns:
46
+ - html.Button: Download button.
43
47
  """
44
- classes = (
45
- "govuk-button govuk-button--primary "
46
- "govuk-!-margin-bottom-0 govuk-!-margin-top-4 "
47
- "flex w-auto items-center gap-2 print:hidden"
48
- )
49
- return html.Div(
48
+ download_type = button_text.lower().replace(" ", "-")
49
+ return html.Button(
50
50
  [
51
- html.Button(
52
- [html.Div("", className="download-icon"), button_text],
53
- id=button_id,
54
- n_clicks=0,
55
- className=classes,
56
- type="submit",
57
- style={"display": "flex", "alignItems": "center", "gap": "8px"},
58
- ),
51
+ html.Div("", className="download-icon"),
52
+ button_text,
59
53
  ],
60
- className="govuk-button-group",
61
- style={"float": "right"},
54
+ id={
55
+ "download-type": download_type,
56
+ "name": button_id_name,
57
+ },
58
+ n_clicks=0,
59
+ className=DOWNLOAD_BUTTON_CLASSES,
60
+ type="submit",
61
+ style={
62
+ "display": "flex",
63
+ "alignItems": "center",
64
+ "gap": "8px",
65
+ },
62
66
  )
@@ -3,11 +3,8 @@
3
3
  from dash import html
4
4
  from dash.development.base_component import Component
5
5
 
6
- from gov_uk_dashboards.components.dash.create_download_chart_button import (
7
- create_download_chart_button,
8
- )
9
- from gov_uk_dashboards.components.dash.create_download_data_button import (
10
- create_download_data_button,
6
+ from gov_uk_dashboards.components.dash.download_button import (
7
+ create_download_button_with_icon,
11
8
  )
12
9
  from gov_uk_dashboards.components.dash.heading import HeadingSizes
13
10
 
@@ -36,11 +33,6 @@ def display_chart_or_table_with_header(
36
33
  [
37
34
  html.Div(
38
35
  [
39
- (
40
- create_download_chart_button(button_id_name=download_button_id)
41
- if download_button_id
42
- else None
43
- ),
44
36
  html.Div(
45
37
  [
46
38
  html.H2(
@@ -61,20 +53,23 @@ def display_chart_or_table_with_header(
61
53
  html.Div(
62
54
  (
63
55
  [
64
- html.H3(
65
- "Download this data",
66
- className=HeadingSizes.SMALL,
67
- style={
68
- **{"color": "black", "margin": "0px 0px 5px"},
69
- **{"padding-top": "20px"},
70
- },
71
- ),
72
- create_download_data_button(
73
- button_id_name=download_data_button_id
56
+ html.Div(
57
+ [
58
+ create_download_button_with_icon(
59
+ "Download chart", download_button_id
60
+ )
61
+ if download_button_id
62
+ else [],
63
+ create_download_button_with_icon(
64
+ "Download data", download_data_button_id
65
+ )
66
+ if download_data_button_id
67
+ else [],
68
+ ],
69
+ className="govuk-button-group",
70
+ style={"padding-top": "20px"},
74
71
  ),
75
72
  ]
76
- if download_data_button_id
77
- else []
78
73
  ),
79
74
  ),
80
75
  ],
@@ -0,0 +1,21 @@
1
+ """get_chart_for_download"""
2
+ from gov_uk_dashboards.constants import MAIN_TITLE, SUBTITLE
3
+
4
+
5
+ def get_chart_for_download(self, fig):
6
+ """Returns a fig with title and subtitle for download as png"""
7
+ main_title = self.title_data[MAIN_TITLE]
8
+ subtitle = self.title_data[SUBTITLE]
9
+
10
+ fig.update_layout(
11
+ title={
12
+ "text": f"<b><span style='color: black; margin: 0px 0px 5px'>{main_title}</span></b>",
13
+ "x": 0.01,
14
+ "xanchor": "left",
15
+ },
16
+ title_subtitle={
17
+ "text": f"<b><span style='color: black; margin: 0px 0px 5px'>{subtitle}</span></b>"
18
+ },
19
+ margin={"t": 100},
20
+ )
21
+ return fig
File without changes
@@ -0,0 +1,174 @@
1
+ """Leaflet choropleth map class"""
2
+
3
+ from dash_extensions.javascript import arrow_function, Namespace
4
+ import dash_leaflet as dl
5
+ from dash import html
6
+ import polars as pl
7
+
8
+ from gov_uk_dashboards.formatting.number_formatting import (
9
+ format_number_into_thousands_or_millions,
10
+ )
11
+
12
+
13
+ class LeafletChoroplethMap:
14
+ """Class for generating leaflet choropleth map charts.
15
+ Note: dataframe_function must contain columns: 'Region', 'Area_Code',
16
+ column_to_plot, hover_text_columns
17
+ If color_scale_is_discrete is false, colour scale will be continuous, otherwise it will be
18
+ discrete"""
19
+
20
+ # pylint: disable=too-few-public-methods
21
+
22
+ def __init__(
23
+ self,
24
+ get_geojson_function,
25
+ get_df_function,
26
+ hover_text_columns,
27
+ color_scale_is_discrete=True,
28
+ ):
29
+ self.geojson_data = get_geojson_function()
30
+ self.df = get_df_function()
31
+ self.hover_text_columns = hover_text_columns
32
+ self.color_scale_is_discrete = color_scale_is_discrete
33
+ self._add_data_to_geojson()
34
+
35
+ def get_leaflet_choropleth_map(self):
36
+ """Creates and returns leaflet choropleth map chart for display on application.
37
+
38
+ Returns:
39
+ dl.Map: A dash leaflet map chart.
40
+ """
41
+ return dl.Map(
42
+ children=[
43
+ dl.TileLayer(),
44
+ self._get_colorbar(),
45
+ self._get_colorbar_title(),
46
+ self._get_dl_geojson(),
47
+ ],
48
+ center=[54.5, -2.5], # Centered on the UK
49
+ zoom=6.5,
50
+ minZoom=6.5,
51
+ maxZoom=6.5,
52
+ maxBounds=[[49.8, -10], [55.9, 1.8]],
53
+ scrollWheelZoom=False, # Disable zooming via mouse scroll
54
+ dragging=False, # Optional: prevent dragging too if you want
55
+ zoomControl=False, # Hide the zoom buttons (+/-)
56
+ doubleClickZoom=False, # Prevent double click zoom
57
+ touchZoom=False, # Prevent pinch zoom
58
+ attributionControl=False,
59
+ style={"width": "100%", "height": "800px", "background": "white"},
60
+ )
61
+
62
+ def _add_data_to_geojson(self):
63
+ info_map = {
64
+ row["Area_Code"]: {
65
+ "value": row["Value"],
66
+ "region": row["Region"],
67
+ **{col: row[col] for col in self.hover_text_columns},
68
+ }
69
+ for row in self.df.iter_rows(named=True)
70
+ }
71
+
72
+ for feature in self.geojson_data["features"]:
73
+ region_code = feature["properties"]["geo_id"]
74
+ info = info_map.get(region_code)
75
+ if info:
76
+
77
+ feature["properties"]["density"] = info["value"]
78
+ feature["properties"]["region"] = info["region"]
79
+
80
+ tooltip_parts = [f"<b>{info['region']}</b>"]
81
+ if info["value"] is None:
82
+ tooltip_parts.append("<br>No data available")
83
+ else:
84
+ for col in self.hover_text_columns:
85
+ tooltip_parts.append(f"<br>{col}: {info[col]}")
86
+
87
+ feature["properties"]["tooltip"] = "".join(tooltip_parts)
88
+ else:
89
+ feature["properties"]["density"] = None
90
+ feature["properties"]["region"] = "Unknown"
91
+ feature["properties"]["tooltip"] = "No data available"
92
+
93
+ def _get_dl_geojson(self):
94
+ style_handle = self._get_style_handle()
95
+ colorscale = self._get_colorscale()
96
+ style = {"weight": 2, "opacity": 1, "color": "white", "fillOpacity": 1}
97
+ hover_style = arrow_function({"weight": 5, "color": "#666", "dashArray": ""})
98
+ return dl.GeoJSON(
99
+ data=self.geojson_data,
100
+ id="geojson",
101
+ zoomToBounds=True,
102
+ zoomToBoundsOnClick=True,
103
+ style=style_handle,
104
+ hoverStyle=hover_style,
105
+ hideout={
106
+ "colorscale": colorscale, # Use hex strings
107
+ "style": style,
108
+ "colorProp": "density",
109
+ "min": self.df["Value"].min(),
110
+ "max": self.df["Value"].max(),
111
+ },
112
+ )
113
+
114
+ def _get_style_handle(self):
115
+ ns = Namespace("myNamespace", "mapColorScaleFunctions")
116
+ if self.color_scale_is_discrete:
117
+ return ""
118
+ return ns("continuousColorScale")
119
+
120
+ def _get_colorscale(self):
121
+ if self.color_scale_is_discrete:
122
+ return ""
123
+ return ["#B0F2BC", "#257D98"]
124
+
125
+ def _get_colorbar(self):
126
+ min_value = self.df.select(pl.min("Value")).item()
127
+ colorbar_min = min(min_value, 0)
128
+ max_value = self.df.select(pl.max("Value")).item()
129
+ mid_value = (colorbar_min + max_value) / 2
130
+ quarter_value = (colorbar_min + max_value) / 4
131
+ three_quarter_value = 3 * (colorbar_min + max_value) / 4
132
+ tick_values = [
133
+ colorbar_min,
134
+ quarter_value,
135
+ mid_value,
136
+ three_quarter_value,
137
+ max_value,
138
+ ]
139
+ tick_text = [
140
+ format_number_into_thousands_or_millions(x) for x in tick_values
141
+ ] # Optional, for formatting
142
+
143
+ return dl.Colorbar(
144
+ colorscale=self._get_colorscale(),
145
+ width=20,
146
+ height=200,
147
+ min=colorbar_min,
148
+ max=max_value,
149
+ position="topleft",
150
+ style={
151
+ "backgroundColor": "white",
152
+ "padding": "5px",
153
+ "borderRadius": "4px",
154
+ "marginTop": "100px",
155
+ },
156
+ tickValues=tick_values,
157
+ tickText=tick_text, # Optional, makes labels look cleaner
158
+ )
159
+
160
+ def _get_colorbar_title(self):
161
+ return html.Div(
162
+ self.hover_text_columns[0],
163
+ style={
164
+ "position": "absolute",
165
+ "bottom": "700px", # Adjusted to place above the colorbar
166
+ "left": "10px", # Align with the left side of the colorbar
167
+ "background": "white",
168
+ "padding": "2px 6px",
169
+ "borderRadius": "5px",
170
+ "fontWeight": "bold",
171
+ "fontSize": "14px",
172
+ "zIndex": "999", # Ensure it appears above map elements
173
+ },
174
+ )
@@ -7,6 +7,9 @@ import polars as pl
7
7
 
8
8
  import plotly.graph_objects as go
9
9
 
10
+ from gov_uk_dashboards.components.helpers.get_chart_for_download import (
11
+ get_chart_for_download,
12
+ )
10
13
  from gov_uk_dashboards.constants import (
11
14
  CHART_LABEL_FONT_SIZE,
12
15
  CUSTOM_DATA,
@@ -117,6 +120,10 @@ class StackedBarChart:
117
120
  self.download_data_button_id,
118
121
  )
119
122
 
123
+ def get_stacked_bar_chart_for_download(self):
124
+ """Return fig with title and subtitle for download as png"""
125
+ return get_chart_for_download(self, self.create_stacked_bar_chart())
126
+
120
127
  def create_stacked_bar_chart(
121
128
  self,
122
129
  ):
@@ -30,6 +30,9 @@ from gov_uk_dashboards.components.helpers.plotting_helper_functions import (
30
30
  get_legend_configuration,
31
31
  get_rgba_from_hex_colour_and_alpha,
32
32
  )
33
+ from gov_uk_dashboards.components.helpers.get_chart_for_download import (
34
+ get_chart_for_download,
35
+ )
33
36
  from gov_uk_dashboards.components.helpers.update_layout_bgcolor_margin import (
34
37
  update_layout_bgcolor_margin,
35
38
  )
@@ -129,6 +132,10 @@ class TimeSeriesChart:
129
132
  self.download_data_button_id,
130
133
  )
131
134
 
135
+ def get_time_series_chart_for_download(self):
136
+ """Return fig with title and subtitle for download as png"""
137
+ return get_chart_for_download(self, self.create_time_series_chart())
138
+
132
139
  def create_time_series_chart(
133
140
  self,
134
141
  ):
@@ -18,3 +18,9 @@ UNIT_SIZE = "Unit size"
18
18
  DEFAULT_COLOURSCALE = "tealgrn"
19
19
 
20
20
  LEGEND_SPACING = "\u00A0" * 5
21
+
22
+ DOWNLOAD_BUTTON_CLASSES = (
23
+ "govuk-button govuk-button--primary "
24
+ "govuk-!-margin-bottom-0 govuk-!-margin-top-4 "
25
+ "flex w-auto items-center gap-2 print:hidden"
26
+ )
@@ -0,0 +1,24 @@
1
+ """Functions to add formatting to number values"""
2
+
3
+
4
+ def format_number_into_thousands_or_millions(
5
+ number: int, thousand_decimal_places: int = 0
6
+ ) -> str:
7
+ """Format number into thousands or millions, eg. 1,500 becomes 1.5k & 1,234,567 becomes 1.235m
8
+
9
+ Args:
10
+ number (int): Integer to format.
11
+ thousand_decimal_places (int or str): The number of decimal places to display for
12
+ 1_000<=number<1_000_000. Defaults to 0. If "default" is passed, number is simply
13
+ divided by 1_000.
14
+ """
15
+ if number >= 1_000_000:
16
+ formatted_number = f"{number / 1_000_000:.3f}m"
17
+ elif number >= 1_000:
18
+ if thousand_decimal_places == "default":
19
+ formatted_number = f"{number / 1_000}k"
20
+ else:
21
+ formatted_number = f"{number / 1_000:.{thousand_decimal_places}f}k"
22
+ else:
23
+ formatted_number = str(number)
24
+ return formatted_number
File without changes
@@ -0,0 +1,16 @@
1
+ """convert_fig_to_image_and_download"""
2
+
3
+ import plotly.graph_objects as go
4
+ import plotly.io as pio
5
+ from dash import dcc
6
+
7
+
8
+ def convert_fig_to_image_and_download(fig: go.Figure, name: str):
9
+ """Converts a given Plotly figure to a PNG image and returns a Dash component
10
+ for downloading the image.
11
+ """
12
+ fig.update_layout(margin={"t": 150, "b": 100, "l": 100, "r": 50})
13
+
14
+ filename = f"{name}-chart.png"
15
+ img_bytes = pio.to_image(fig, format="png", width=1600, height=800)
16
+ return dcc.send_bytes(img_bytes, filename)
@@ -0,0 +1,114 @@
1
+ """download_csv_with_headers"""
2
+ import io
3
+ import polars as pl
4
+ from dash import dcc
5
+ from gov_uk_dashboards.lib.datetime_functions.datetime_functions import (
6
+ get_todays_date_for_downloaded_csv,
7
+ )
8
+
9
+
10
+ def download_csv_with_headers(
11
+ list_of_df_title_subtitle_dicts: list[dict[str, str]],
12
+ name: str,
13
+ sensitivity_label: str,
14
+ additional_text: list[str] = None,
15
+ ): # pylint: disable=too-many-locals
16
+ """Adds a header above multiple dataframes,
17
+ separates them with blank rows, and downloads as CSV.
18
+
19
+ Args:
20
+ list_of_df_title_subtitle_dicts (list[dict[]]): List of dictionaries containing keys: "df",
21
+ "title" and "subtitle"
22
+ name (str): Filename for CSV.
23
+ sensitivity_label (str): Sensitivity label. Str or None.
24
+ additional_text (list[str]): Additional text to inlcude in headers after data downloaded.
25
+ Str or None.
26
+ """
27
+
28
+ csv_buffer = io.StringIO()
29
+
30
+ column_list = list(list_of_df_title_subtitle_dicts[0]["df"].columns)
31
+ column_dict = {column_name: column_name for column_name in column_list}
32
+ blank_dict = {
33
+ f"{i}": None
34
+ for i in range(
35
+ _get_number_of_max_columns_from_all_dfs(list_of_df_title_subtitle_dicts)
36
+ - len(column_list)
37
+ )
38
+ } # range is missing columns in first df compared to max columns across all dfs
39
+
40
+ subtitle = list_of_df_title_subtitle_dicts[0]["subtitle"]
41
+
42
+ header_data = [
43
+ {column_list[0]: "Date downloaded: " + get_todays_date_for_downloaded_csv()},
44
+ *(
45
+ [{column_list[0]: text} for text in additional_text]
46
+ + [{column_list[0]: None}]
47
+ if additional_text is not None
48
+ else []
49
+ ),
50
+ {column_list[0]: list_of_df_title_subtitle_dicts[0]["title"]},
51
+ *(
52
+ [{column_list[0]: subtitle}] if subtitle is not None else []
53
+ ), # Uses unpacking (*) to add the subtitle row if subtitle is not None. If subtitle is
54
+ # None, it unpacks an empty list, effectively skipping the row.
55
+ {column_list[0]: None}, # Blank row
56
+ {**column_dict, **blank_dict},
57
+ ]
58
+
59
+ if sensitivity_label:
60
+ header_data = [{column_list[0]: sensitivity_label}] + header_data
61
+
62
+ pl.DataFrame(header_data).write_csv(csv_buffer, include_header=False)
63
+
64
+ for i, data in enumerate(list_of_df_title_subtitle_dicts):
65
+ df = data["df"]
66
+ title = data["title"]
67
+ subtitle = data["subtitle"]
68
+
69
+ if i > 0 and title is not None:
70
+ column_dict = {column_name: column_name for column_name in list(df.columns)}
71
+ header_data = [
72
+ {column_list[0]: title},
73
+ *(
74
+ [{column_list[0]: subtitle}] if subtitle is not None else []
75
+ ), # Uses unpacking (*) to add the subtitle row if subtitle is not None. If
76
+ # subtitle is None, it unpacks an empty list, effectively skipping the row.
77
+ {column_list[0]: None}, # Blank row
78
+ ]
79
+ pl.DataFrame(header_data).write_csv(csv_buffer, include_header=False)
80
+ df.write_csv(csv_buffer, include_header=i > 0)
81
+
82
+ if i < len(list_of_df_title_subtitle_dicts) - 1:
83
+ blank_row = pl.DataFrame({df.columns[0]: [None]})
84
+ blank_row.write_csv(csv_buffer, include_header=False)
85
+
86
+ csv_buffer.seek(0)
87
+ csv_data = (
88
+ "\ufeff" + csv_buffer.getvalue()
89
+ ) # Adding \ufeff ensures the correct character encoding is detected for £
90
+
91
+ return dcc.send_string(csv_data, f"{name}.csv")
92
+
93
+
94
+ def _get_number_of_max_columns_from_all_dfs(list_of_df_title_subtitle_dicts):
95
+ max_columns = 0
96
+ index_of_max_cols = -1
97
+
98
+ for idx, dic in enumerate(list_of_df_title_subtitle_dicts):
99
+ # Get the DataFrame
100
+ df = dic["df"]
101
+
102
+ # Get the number of columns
103
+ num_columns = df.shape[1]
104
+
105
+ # Update if this DataFrame has more columns
106
+ if num_columns > max_columns:
107
+ max_columns = num_columns
108
+ index_of_max_cols = idx
109
+
110
+ max_columns = len(
111
+ list(list_of_df_title_subtitle_dicts[index_of_max_cols]["df"].columns)
112
+ )
113
+
114
+ return max_columns
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gov_uk_dashboards
3
- Version: 16.2.0
3
+ Version: 17.1.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
@@ -1,12 +1,13 @@
1
1
  gov_uk_dashboards/__init__.py,sha256=4y4hozrL4hzRUna8b22UQwtkZcvvCgeXsAtA6AJww8g,1299
2
2
  gov_uk_dashboards/axes.py,sha256=Oh8zyWzxwZWq8bQl-vJoXwTANZ7ACZiYPmIXlM62Fvk,712
3
3
  gov_uk_dashboards/colours.py,sha256=MezFI_3hgEuZ7f8F5TdtgaQO00B3G7ylRTCVq3SlSF4,2371
4
- gov_uk_dashboards/constants.py,sha256=kGM1gSoViFfYAYvVXg52fFqXvZ1Df5wEsamZMh0_h6w,511
4
+ gov_uk_dashboards/constants.py,sha256=O_JyW1XlNg_mHwmT2xfup6mOtfd9x44ky5APReFfOqo,686
5
5
  gov_uk_dashboards/symbols.py,sha256=6_lq8yl1l7sGUlOY29wip_PXeUUoM53_fakKY1omVwY,424
6
6
  gov_uk_dashboards/template.html,sha256=uFAki9bEpcei5dP_NZYP8dsxpxXpNIDQPn7ezsE7VXc,498
7
7
  gov_uk_dashboards/template.py,sha256=WNVkAW3LCNoUHcxQ1kiUPkGCKIan5DgZQaAA_ZbE5WA,534
8
8
  gov_uk_dashboards/assets/__init__.py,sha256=SwV4sjjMQmuZXloVcvf2H86XFiAd8xhPLJvaSMC2MQE,101
9
9
  gov_uk_dashboards/assets/attach-event-to-dash.js,sha256=mbea5TaxEnjMN-MebR3J0hVjOb1B27-zXwWw9ZYqBBw,562
10
+ gov_uk_dashboards/assets/custom_map_style_functions.js,sha256=1-jnhguDEn7-BnleGbSTB-dlaSO-6hPF-JL0d5EUnhU,1689
10
11
  gov_uk_dashboards/assets/dashboard.css,sha256=exnujRlse2xHOeuE_TAs84gITv5YMRzKJKEeSjoXO6U,202134
11
12
  gov_uk_dashboards/assets/get_assets_folder.py,sha256=ozYOmuO2r36vaTdWMU5cBuskl47RHBp8z9KIO06BXBM,171
12
13
  gov_uk_dashboards/assets/govuk-frontend-3.14.0.min.js,sha256=kKi_r2hAqumi2pKM43W8NKpR2UtksUww9lSQOjkOmMI,34806
@@ -34,7 +35,7 @@ gov_uk_dashboards/assets/images/oflog/how_to_5_viewing_tables.png,sha256=kC36T6T
34
35
  gov_uk_dashboards/assets/topojson/usa_110m.json,sha256=yX3AZ1tGULJmVFyWwMkb19WdZShJbvpa97uYvldMvTk,49104
35
36
  gov_uk_dashboards/assets/topojson/world_110m.json,sha256=11kV6qMchw32uXLJ5buGkQGXgl8z3P73QPOy9oz_6EM,136642
36
37
  gov_uk_dashboards/components/__init__.py,sha256=19fr4E5-FkG_rXAPwRc2Gh60ncgc7Ho1WdrMV5x9frw,187
37
- gov_uk_dashboards/components/dash/__init__.py,sha256=4msokzkqoNze17TC5FM1GeyLSKNmgFyO6Yn744qEyxM,4885
38
+ gov_uk_dashboards/components/dash/__init__.py,sha256=0Sunv0rmcn-LevR17i90Qr542BLek3EZwmiP_uaJJHA,4830
38
39
  gov_uk_dashboards/components/dash/apply_and_reset_filters_buttons.py,sha256=I7VoKOS_zIYdQvDP3SkAOntDMxZSQKCxnY80173EVz8,673
39
40
  gov_uk_dashboards/components/dash/banners.py,sha256=TDHdJQSb7mCqtmM6ylSnVh_Qpd2Aec1jSR8RfsjqQoE,958
40
41
  gov_uk_dashboards/components/dash/card.py,sha256=aErz9h536nYvpp9LZsFHo_Rn5rRXXfyGh358VcLksaE,420
@@ -42,11 +43,9 @@ gov_uk_dashboards/components/dash/card_full_width.py,sha256=ItwNgb5YQ1n6Fznvy_ng
42
43
  gov_uk_dashboards/components/dash/collapsible_panel.py,sha256=WrC5IFzM9mNavPfeSpBvMvYQfrmfhpTPCG1Ay70Jtw8,1191
43
44
  gov_uk_dashboards/components/dash/comparison_la_filter_button.py,sha256=mhtmYVyToabuYzal00cB07YfKSx_wjDfjB8W-IKDXPc,670
44
45
  gov_uk_dashboards/components/dash/context_banner.py,sha256=2o44AzS0pTYTY0r3OVbKW7dJg2lA1J8PW4RCeGQ15gg,1010
45
- gov_uk_dashboards/components/dash/create_download_chart_button.py,sha256=yb3TbMHxQmC7XFojwnZMBMpwMal9NUcT4LNt9ZccU6Q,711
46
- gov_uk_dashboards/components/dash/create_download_data_button.py,sha256=ng57WLkA5wyfrLTrO05VJ6LmC_gwgODQEYpMj-v-wO8,714
47
46
  gov_uk_dashboards/components/dash/dashboard_container.py,sha256=KC2isR0NShxUYCl_pzDEAS4WK5pFrLMp4m2We3AqiwM,512
48
47
  gov_uk_dashboards/components/dash/details.py,sha256=-Rgat95pCnU3fF4O0MkyymzMQPCZhRvuq0U0a5gHZBU,1106
49
- gov_uk_dashboards/components/dash/download_button.py,sha256=8jGAadB3f_wVYWz5wIkObh0Q4NeFESM6kNRUYx0TIRs,1882
48
+ gov_uk_dashboards/components/dash/download_button.py,sha256=PpvZMmf0R7qI5_xZgPve7JDCVONC07KrLJg8IpXoyCE,1781
50
49
  gov_uk_dashboards/components/dash/filter_panel.py,sha256=lQZYc5g7xUOpHfLWpPXiBKkIRKXoFb1p9NhJgS21tNo,1996
51
50
  gov_uk_dashboards/components/dash/footer.py,sha256=RnJKN1YTP88GuJ4e7ci2T-jDqIe0-jDuHAQ8OhSCK50,3155
52
51
  gov_uk_dashboards/components/dash/graph.py,sha256=bd49W5sVyhtWd4lNBfQST1RyLNlTLA0KRxS7jTgVMwE,886
@@ -70,16 +69,19 @@ gov_uk_dashboards/components/dash/visualisation_commentary.py,sha256=jBy8qy2DWYq
70
69
  gov_uk_dashboards/components/dash/visualisation_title.py,sha256=xXHsyVRIGkngBXCuhv8z0PmZLOeicjpMvmTbA5HhRvQ,561
71
70
  gov_uk_dashboards/components/dash/warning_text.py,sha256=31XvPUINt2QsWIaaMnqEvn23G-MpzWbDvGnzYcXIyAY,1622
72
71
  gov_uk_dashboards/components/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
- gov_uk_dashboards/components/helpers/display_chart_or_table_with_header.py,sha256=7Z52uZGrs15Q31OJjztnF0iqA5VRUrfJB_nPy6aZMMc,3060
72
+ gov_uk_dashboards/components/helpers/display_chart_or_table_with_header.py,sha256=4Kh_qaFZUqQrS08Bb7j9T-ltSVjLtI_EOnpXY-uMvVc,2918
74
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=RS5SR2tCQPTkBpWaIr2mb98Yn6vAimBnAXpRhMx0kB4,669
75
75
  gov_uk_dashboards/components/helpers/plotting_helper_functions.py,sha256=moD2tse2FqTBW2rOHOvZCL9BIlmDbyvU5233yFFu_aI,1635
76
76
  gov_uk_dashboards/components/helpers/update_layout_bgcolor_margin.py,sha256=i7Nwp0CxFpkyQeR8KfOBVMBkzctG7hMpWI2OzgxB2jY,740
77
+ gov_uk_dashboards/components/leaflet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
+ gov_uk_dashboards/components/leaflet/leaflet_choropleth_map.py,sha256=4ysc-FxRKGPSkNuJBDtjlEOGthC7EuIliaillqX2GeI,6221
77
79
  gov_uk_dashboards/components/plotly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
80
  gov_uk_dashboards/components/plotly/captioned_figure.py,sha256=T0sbtGTiJ79FXxVdPb__hqISuyTc3Dl11cKhgcuW-5U,2804
79
81
  gov_uk_dashboards/components/plotly/choropleth_map.py,sha256=U9RmS3MZGloQAt9HoSYh3Xad205DDfZOjz91ZD_ydbI,9849
80
82
  gov_uk_dashboards/components/plotly/enums.py,sha256=ebHiFQljA7O4HJxKoslqlNXcAjUZi1askpTSwxKEcYQ,850
81
- gov_uk_dashboards/components/plotly/stacked_barchart.py,sha256=_81WlxelE5Gxozj80uYRIkQJLyv2i6xZr2T0g3db4ks,12229
82
- gov_uk_dashboards/components/plotly/time_series_chart.py,sha256=Z8p4oQ6ohiv7qaIVC6oDUEdVteryvIr7YuzQL0RpYyU,22701
83
+ gov_uk_dashboards/components/plotly/stacked_barchart.py,sha256=-pFwY3vhjQrLR_innzbs4d-vqWSXE_eJS-ceP5wo_SI,12530
84
+ gov_uk_dashboards/components/plotly/time_series_chart.py,sha256=QD9HjXLjNIBA-NBZQUSL3TyIAvRGrBzwnYrRboROECg,23002
83
85
  gov_uk_dashboards/figures/__init__.py,sha256=_snQeNlM81nNKERl4gg9ScH2HYbtwaBjl8yQonccx34,712
84
86
  gov_uk_dashboards/figures/chart_data.py,sha256=fEsNkQFzXKIQ0h7aLBWq3J1qAxHbxvJeKU23JrVodVc,757
85
87
  gov_uk_dashboards/figures/line_chart.py,sha256=rEB51_z9cPl7BcT94iA6ZsZXzecnVCnGpQWW_9SNGUY,2813
@@ -89,6 +91,7 @@ gov_uk_dashboards/figures/styles/__init__.py,sha256=wVa8BU0TvXnanksOUVWN4utFKW4O
89
91
  gov_uk_dashboards/figures/styles/line_style.py,sha256=sXc9pGrAVxFrxqXSTsrw0hEgyhmhMOLhqhwx93J-PtI,559
90
92
  gov_uk_dashboards/formatting/__init__.py,sha256=bQk9_OEubUhuTRQjXUs4ZItgSY7yyDpwQcLauxf11Tk,460
91
93
  gov_uk_dashboards/formatting/human_readable.py,sha256=e-ROPS7oFLHCV58MXBIhLsZQut-F-bAYz0TOz33gX2M,1844
94
+ gov_uk_dashboards/formatting/number_formatting.py,sha256=9LVqKqx0mMQyv8XLI_im1pdASZDJxRjzRWtRN-g6kPs,913
92
95
  gov_uk_dashboards/formatting/round_and_add_prefix_and_suffix.py,sha256=_DboRfdvwb9Y62H9LFDFJ0ju4626z-_oFuwagRgyZDY,1456
93
96
  gov_uk_dashboards/formatting/rounding.py,sha256=Em1yri_j18IYHbZ64d3bhVKX-XEllRSM9FzAUo1o6fU,968
94
97
  gov_uk_dashboards/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -100,8 +103,11 @@ gov_uk_dashboards/lib/dap/dap_deployment.py,sha256=ZXixeOAtRNjMsPdGKLwwLNamlo0mi
100
103
  gov_uk_dashboards/lib/dap/get_dataframe_from_cds.py,sha256=OiusRCgYnkBjK_GZgYLGzNrxOGizYt8CgThiWRCVKK0,3921
101
104
  gov_uk_dashboards/lib/datetime_functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
105
  gov_uk_dashboards/lib/datetime_functions/datetime_functions.py,sha256=BQgr8I_vFNYwLi-fE4YC6jZ5PpbPea2rnD_2a_lw0YE,12209
103
- gov_uk_dashboards-16.2.0.dist-info/licenses/LICENSE,sha256=GDiD7Y2Gx7JucPV1JfVySJeah-qiSyBPdpJ6RHCEHTc,1126
104
- gov_uk_dashboards-16.2.0.dist-info/METADATA,sha256=XWp4ZyTku_CZxzeDiuTCSLoaKUYREIvZO6olgDyxhZA,5917
105
- gov_uk_dashboards-16.2.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
106
- gov_uk_dashboards-16.2.0.dist-info/top_level.txt,sha256=gPaN1P3-H3Rgi2me6tt-fX_cxo19CZfA4PjlZPjGRpo,18
107
- gov_uk_dashboards-16.2.0.dist-info/RECORD,,
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=JYDpWObBsiKG5Rtk2ElOTgHwfIR0cRmVocr8RlTfPZQ,534
108
+ gov_uk_dashboards/lib/download_functions/download_csv_with_headers.py,sha256=h50ejODCjoz9z-yqDt6nsE6jN6XxJN1DWH66CjLJiCk,4155
109
+ gov_uk_dashboards-17.1.0.dist-info/licenses/LICENSE,sha256=GDiD7Y2Gx7JucPV1JfVySJeah-qiSyBPdpJ6RHCEHTc,1126
110
+ gov_uk_dashboards-17.1.0.dist-info/METADATA,sha256=6wQUDbkJ1690nFpTKm5XfCPwIDhvWGgXONm6bI8xoA8,5917
111
+ gov_uk_dashboards-17.1.0.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
112
+ gov_uk_dashboards-17.1.0.dist-info/top_level.txt,sha256=gPaN1P3-H3Rgi2me6tt-fX_cxo19CZfA4PjlZPjGRpo,18
113
+ gov_uk_dashboards-17.1.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,25 +0,0 @@
1
- """create_download_chart_button"""
2
-
3
- from dash import html
4
- from gov_uk_dashboards.components.dash import download_button_with_icon
5
-
6
-
7
- def create_download_chart_button(button_id_name: str) -> html.Div:
8
- """Create a download button for charts, aligned to the right.
9
-
10
- Parameters:
11
- - button_id_name (str): A unique identifier for the button.
12
-
13
- Returns:
14
- - html.Div: A Div element containing a download button.
15
- """
16
- return html.Div(
17
- download_button_with_icon(
18
- button_text="Download chart",
19
- button_id={
20
- "download-type": "download-chart",
21
- "name": button_id_name,
22
- },
23
- ),
24
- style={"textAlign": "right"},
25
- )
@@ -1,25 +0,0 @@
1
- """create_download_data_button"""
2
-
3
- from dash import html
4
- from gov_uk_dashboards.components.dash import download_button_with_icon
5
-
6
-
7
- def create_download_data_button(button_id_name: str) -> html.Div:
8
- """Create a download button for data, aligned to the right.
9
-
10
- Parameters:
11
- - button_id_name (str): A unique identifier for the button.
12
-
13
- Returns:
14
- - html.Div: A Div element containing a download button.
15
- """
16
- return html.Div(
17
- download_button_with_icon(
18
- button_text=".csv",
19
- button_id={
20
- "download-type": "download-data",
21
- "name": button_id_name,
22
- },
23
- ),
24
- style={"float": "left", "margin-top": "-20px"},
25
- )