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.
- gov_uk_dashboards/assets/custom_map_style_functions.js +44 -0
- gov_uk_dashboards/components/leaflet/__init__.py +0 -0
- gov_uk_dashboards/components/leaflet/leaflet_choropleth_map.py +203 -0
- gov_uk_dashboards/formatting/number_formatting.py +24 -0
- {gov_uk_dashboards-17.0.0.dist-info → gov_uk_dashboards-17.2.0.dist-info}/METADATA +1 -1
- {gov_uk_dashboards-17.0.0.dist-info → gov_uk_dashboards-17.2.0.dist-info}/RECORD +9 -5
- {gov_uk_dashboards-17.0.0.dist-info → gov_uk_dashboards-17.2.0.dist-info}/WHEEL +1 -1
- {gov_uk_dashboards-17.0.0.dist-info → gov_uk_dashboards-17.2.0.dist-info}/licenses/LICENSE +0 -0
- {gov_uk_dashboards-17.0.0.dist-info → gov_uk_dashboards-17.2.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
|
+
});
|
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.
|
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.
|
106
|
-
gov_uk_dashboards-17.
|
107
|
-
gov_uk_dashboards-17.
|
108
|
-
gov_uk_dashboards-17.
|
109
|
-
gov_uk_dashboards-17.
|
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,,
|
File without changes
|
File without changes
|