gov-uk-dashboards 17.0.0__py3-none-any.whl → 17.2.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.
@@ -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
+ });
File without changes
@@ -0,0 +1,203 @@
1
+ """Leaflet choropleth map class"""
2
+
3
+ from typing import Optional
4
+ from dash_extensions.javascript import arrow_function, Namespace
5
+ import dash_leaflet as dl
6
+ from dash import html
7
+ import polars as pl
8
+
9
+ from gov_uk_dashboards.components.helpers.display_chart_or_table_with_header import (
10
+ display_chart_or_table_with_header,
11
+ )
12
+ from gov_uk_dashboards.formatting.number_formatting import (
13
+ format_number_into_thousands_or_millions,
14
+ )
15
+
16
+
17
+ class LeafletChoroplethMap:
18
+ """Class for generating leaflet choropleth map charts.
19
+ Note: dataframe_function must contain columns: 'Region', 'Area_Code',
20
+ column_to_plot, hover_text_columns
21
+ If color_scale_is_discrete is false, colour scale will be continuous, otherwise it will be
22
+ discrete"""
23
+
24
+ # pylint: disable=too-few-public-methods
25
+ # pylint: disable=too-many-instance-attributes
26
+ # pylint: disable=too-many-arguments
27
+ # pylint: disable=too-many-positional-arguments
28
+
29
+ def __init__(
30
+ self,
31
+ geojson: dict,
32
+ df: pl.DataFrame,
33
+ hover_text_columns: list[str],
34
+ title: str,
35
+ subtitle: Optional[str] = None,
36
+ download_chart_button_id: Optional[str] = None,
37
+ download_data_button_id: Optional[str] = None,
38
+ color_scale_is_discrete: bool = True,
39
+ show_tile_layer: bool = False,
40
+ ):
41
+ self.geojson_data = geojson
42
+ self.df = df
43
+ self.hover_text_columns = hover_text_columns
44
+ self.title = title
45
+ self.subtitle = subtitle
46
+ self.download_chart_button_id = download_chart_button_id
47
+ self.download_data_button_id = download_data_button_id
48
+ self.color_scale_is_discrete = color_scale_is_discrete
49
+ self.show_tile_layer = show_tile_layer
50
+ self._add_data_to_geojson()
51
+
52
+ def get_leaflet_choropleth_map(self):
53
+ """Creates and returns leaflet choropleth map chart for display on application.
54
+
55
+ Returns:
56
+ dl.Map: A dash leaflet map chart.
57
+ """
58
+ choropleth_map = dl.Map(
59
+ children=[
60
+ dl.TileLayer() if self.show_tile_layer else None,
61
+ self._get_colorbar(),
62
+ self._get_colorbar_title(),
63
+ self._get_dl_geojson(),
64
+ ],
65
+ center=[54.5, -2.5], # Centered on the UK
66
+ zoom=6.5,
67
+ minZoom=6.5,
68
+ maxZoom=6.5,
69
+ maxBounds=[[49.8, -10], [55.9, 1.8]],
70
+ scrollWheelZoom=False, # Disable zooming via mouse scroll
71
+ dragging=False, # Optional: prevent dragging too if you want
72
+ zoomControl=False, # Hide the zoom buttons (+/-)
73
+ doubleClickZoom=False, # Prevent double click zoom
74
+ touchZoom=False, # Prevent pinch zoom
75
+ attributionControl=False,
76
+ style={"width": "100%", "height": "800px", "background": "white"},
77
+ )
78
+ return display_chart_or_table_with_header(
79
+ choropleth_map,
80
+ self.title,
81
+ self.subtitle,
82
+ self.download_chart_button_id,
83
+ self.download_data_button_id,
84
+ )
85
+
86
+ def _add_data_to_geojson(self):
87
+ info_map = {
88
+ row["Area_Code"]: {
89
+ "value": row["Value"],
90
+ "region": row["Region"],
91
+ **{col: row[col] for col in self.hover_text_columns},
92
+ }
93
+ for row in self.df.iter_rows(named=True)
94
+ }
95
+
96
+ for feature in self.geojson_data["features"]:
97
+ region_code = feature["properties"]["geo_id"]
98
+ info = info_map.get(region_code)
99
+ if info:
100
+
101
+ feature["properties"]["density"] = info["value"]
102
+ feature["properties"]["region"] = info["region"]
103
+
104
+ tooltip_parts = [f"<b>{info['region']}</b>"]
105
+ if info["value"] is None:
106
+ tooltip_parts.append("<br>No data available")
107
+ else:
108
+ for col in self.hover_text_columns:
109
+ tooltip_parts.append(f"<br>{col}: {info[col]}")
110
+
111
+ feature["properties"]["tooltip"] = "".join(tooltip_parts)
112
+ else:
113
+ feature["properties"]["density"] = None
114
+ feature["properties"]["region"] = "Unknown"
115
+ feature["properties"]["tooltip"] = "No data available"
116
+
117
+ def _get_dl_geojson(self):
118
+ style_handle = self._get_style_handle()
119
+ colorscale = self._get_colorscale()
120
+ style = {
121
+ "weight": 2,
122
+ "opacity": 1,
123
+ "color": "white",
124
+ "fillOpacity": 0.7 if self.show_tile_layer else 1,
125
+ }
126
+ hover_style = arrow_function({"weight": 5, "color": "#666", "dashArray": ""})
127
+ return dl.GeoJSON(
128
+ data=self.geojson_data,
129
+ id="geojson",
130
+ zoomToBounds=True,
131
+ zoomToBoundsOnClick=True,
132
+ style=style_handle,
133
+ hoverStyle=hover_style,
134
+ hideout={
135
+ "colorscale": colorscale, # Use hex strings
136
+ "style": style,
137
+ "colorProp": "density",
138
+ "min": self.df["Value"].min(),
139
+ "max": self.df["Value"].max(),
140
+ },
141
+ )
142
+
143
+ def _get_style_handle(self):
144
+ ns = Namespace("myNamespace", "mapColorScaleFunctions")
145
+ if self.color_scale_is_discrete:
146
+ return ""
147
+ return ns("continuousColorScale")
148
+
149
+ def _get_colorscale(self):
150
+ if self.color_scale_is_discrete:
151
+ return ""
152
+ return ["#B0F2BC", "#257D98"]
153
+
154
+ def _get_colorbar(self):
155
+ min_value = self.df.select(pl.min("Value")).item()
156
+ colorbar_min = min(min_value, 0)
157
+ max_value = self.df.select(pl.max("Value")).item()
158
+ mid_value = (colorbar_min + max_value) / 2
159
+ quarter_value = (colorbar_min + max_value) / 4
160
+ three_quarter_value = 3 * (colorbar_min + max_value) / 4
161
+ tick_values = [
162
+ colorbar_min,
163
+ quarter_value,
164
+ mid_value,
165
+ three_quarter_value,
166
+ max_value,
167
+ ]
168
+ tick_text = [
169
+ format_number_into_thousands_or_millions(x) for x in tick_values
170
+ ] # Optional, for formatting
171
+
172
+ return dl.Colorbar(
173
+ colorscale=self._get_colorscale(),
174
+ width=20,
175
+ height=200,
176
+ min=colorbar_min,
177
+ max=max_value,
178
+ position="topleft",
179
+ style={
180
+ "backgroundColor": "white",
181
+ "padding": "5px",
182
+ "borderRadius": "4px",
183
+ "marginTop": "100px",
184
+ },
185
+ tickValues=tick_values,
186
+ tickText=tick_text, # Optional, makes labels look cleaner
187
+ )
188
+
189
+ def _get_colorbar_title(self):
190
+ return html.Div(
191
+ self.hover_text_columns[0],
192
+ style={
193
+ "position": "absolute",
194
+ "bottom": "700px", # Adjusted to place above the colorbar
195
+ "left": "10px", # Align with the left side of the colorbar
196
+ "background": "white",
197
+ "padding": "2px 6px",
198
+ "borderRadius": "5px",
199
+ "fontWeight": "bold",
200
+ "fontSize": "14px",
201
+ "zIndex": "999", # Ensure it appears above map elements
202
+ },
203
+ )
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gov_uk_dashboards
3
- Version: 17.0.0
3
+ Version: 17.2.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
@@ -7,6 +7,7 @@ gov_uk_dashboards/template.html,sha256=uFAki9bEpcei5dP_NZYP8dsxpxXpNIDQPn7ezsE7V
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
@@ -73,6 +74,8 @@ gov_uk_dashboards/components/helpers/generate_dash_graph_from_figure.py,sha256=s
73
74
  gov_uk_dashboards/components/helpers/get_chart_for_download.py,sha256=RS5SR2tCQPTkBpWaIr2mb98Yn6vAimBnAXpRhMx0kB4,669
74
75
  gov_uk_dashboards/components/helpers/plotting_helper_functions.py,sha256=moD2tse2FqTBW2rOHOvZCL9BIlmDbyvU5233yFFu_aI,1635
75
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=UFLRIr2j_GTap3MzL89DUjpdnKf7OI_kaw91VeF0ztI,7310
76
79
  gov_uk_dashboards/components/plotly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
80
  gov_uk_dashboards/components/plotly/captioned_figure.py,sha256=T0sbtGTiJ79FXxVdPb__hqISuyTc3Dl11cKhgcuW-5U,2804
78
81
  gov_uk_dashboards/components/plotly/choropleth_map.py,sha256=U9RmS3MZGloQAt9HoSYh3Xad205DDfZOjz91ZD_ydbI,9849
@@ -88,6 +91,7 @@ gov_uk_dashboards/figures/styles/__init__.py,sha256=wVa8BU0TvXnanksOUVWN4utFKW4O
88
91
  gov_uk_dashboards/figures/styles/line_style.py,sha256=sXc9pGrAVxFrxqXSTsrw0hEgyhmhMOLhqhwx93J-PtI,559
89
92
  gov_uk_dashboards/formatting/__init__.py,sha256=bQk9_OEubUhuTRQjXUs4ZItgSY7yyDpwQcLauxf11Tk,460
90
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
91
95
  gov_uk_dashboards/formatting/round_and_add_prefix_and_suffix.py,sha256=_DboRfdvwb9Y62H9LFDFJ0ju4626z-_oFuwagRgyZDY,1456
92
96
  gov_uk_dashboards/formatting/rounding.py,sha256=Em1yri_j18IYHbZ64d3bhVKX-XEllRSM9FzAUo1o6fU,968
93
97
  gov_uk_dashboards/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -102,8 +106,8 @@ gov_uk_dashboards/lib/datetime_functions/datetime_functions.py,sha256=BQgr8I_vFN
102
106
  gov_uk_dashboards/lib/download_functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
107
  gov_uk_dashboards/lib/download_functions/convert_fig_to_image_and_download.py,sha256=JYDpWObBsiKG5Rtk2ElOTgHwfIR0cRmVocr8RlTfPZQ,534
104
108
  gov_uk_dashboards/lib/download_functions/download_csv_with_headers.py,sha256=h50ejODCjoz9z-yqDt6nsE6jN6XxJN1DWH66CjLJiCk,4155
105
- gov_uk_dashboards-17.0.0.dist-info/licenses/LICENSE,sha256=GDiD7Y2Gx7JucPV1JfVySJeah-qiSyBPdpJ6RHCEHTc,1126
106
- gov_uk_dashboards-17.0.0.dist-info/METADATA,sha256=353hurz1uYu_BGDPevUVpUaCmxmoc1BI_NUXobU0aN8,5917
107
- gov_uk_dashboards-17.0.0.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
108
- gov_uk_dashboards-17.0.0.dist-info/top_level.txt,sha256=gPaN1P3-H3Rgi2me6tt-fX_cxo19CZfA4PjlZPjGRpo,18
109
- gov_uk_dashboards-17.0.0.dist-info/RECORD,,
109
+ gov_uk_dashboards-17.2.0.dist-info/licenses/LICENSE,sha256=GDiD7Y2Gx7JucPV1JfVySJeah-qiSyBPdpJ6RHCEHTc,1126
110
+ gov_uk_dashboards-17.2.0.dist-info/METADATA,sha256=XZrUEgGdjxi9hrX_3QcyRXoYCVJmYGxz0ZgtpQOHJ2Y,5917
111
+ gov_uk_dashboards-17.2.0.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
112
+ gov_uk_dashboards-17.2.0.dist-info/top_level.txt,sha256=gPaN1P3-H3Rgi2me6tt-fX_cxo19CZfA4PjlZPjGRpo,18
113
+ gov_uk_dashboards-17.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.0)
2
+ Generator: setuptools (80.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5