gov-uk-dashboards 21.2.2__py3-none-any.whl → 26.26.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/__init__.py +1 -1
- gov_uk_dashboards/assets/__init__.py +1 -0
- gov_uk_dashboards/assets/dashboard.css +177 -0
- gov_uk_dashboards/assets/download-map.js +39 -0
- gov_uk_dashboards/assets/get_assets_folder.py +1 -0
- gov_uk_dashboards/assets/images/CHASE_icon.svg +17 -0
- gov_uk_dashboards/assets/images/explore_data_logo.svg +87 -0
- gov_uk_dashboards/assets/index.html +3 -0
- gov_uk_dashboards/assets/register_maps +15 -0
- gov_uk_dashboards/assets/scripts.js +4 -0
- gov_uk_dashboards/colours.py +23 -0
- gov_uk_dashboards/components/__init__.py +1 -0
- gov_uk_dashboards/components/dash/__init__.py +1 -3
- gov_uk_dashboards/components/dash/apply_and_reset_filters_buttons.py +1 -0
- gov_uk_dashboards/components/dash/banners.py +21 -0
- gov_uk_dashboards/components/dash/card.py +1 -0
- gov_uk_dashboards/components/dash/card_full_width.py +1 -0
- gov_uk_dashboards/components/dash/collapsible_panel.py +1 -0
- gov_uk_dashboards/components/dash/comparison_la_filter_button.py +1 -0
- gov_uk_dashboards/components/dash/context_banner.py +2 -1
- gov_uk_dashboards/components/dash/context_card.py +978 -0
- gov_uk_dashboards/components/dash/data_quality_banner.py +91 -0
- gov_uk_dashboards/components/dash/details.py +1 -0
- gov_uk_dashboards/components/dash/download_button.py +22 -36
- gov_uk_dashboards/components/dash/filter_panel.py +1 -0
- gov_uk_dashboards/components/dash/footer.py +81 -27
- gov_uk_dashboards/components/dash/graph.py +1 -0
- gov_uk_dashboards/components/dash/green_button.py +25 -0
- gov_uk_dashboards/components/dash/header.py +62 -9
- gov_uk_dashboards/components/dash/heading.py +8 -5
- gov_uk_dashboards/components/dash/home_page_link_button.py +9 -8
- gov_uk_dashboards/components/dash/html_list.py +1 -0
- gov_uk_dashboards/components/dash/key_value_pair.py +1 -0
- gov_uk_dashboards/components/dash/main_content.py +25 -2
- gov_uk_dashboards/components/dash/notification_banner.py +9 -5
- gov_uk_dashboards/components/dash/paragraph.py +1 -0
- gov_uk_dashboards/components/dash/phase_banner.py +7 -4
- gov_uk_dashboards/components/dash/row_component.py +1 -0
- gov_uk_dashboards/components/dash/table.py +62 -124
- gov_uk_dashboards/components/dash/tooltip.py +2 -1
- gov_uk_dashboards/components/dash/tooltip_title.py +2 -1
- gov_uk_dashboards/components/dash/visualisation_commentary.py +1 -0
- gov_uk_dashboards/components/dash/visualisation_title.py +1 -0
- gov_uk_dashboards/components/dash/warning_text.py +1 -0
- gov_uk_dashboards/components/helpers/display_chart_or_table_with_header.py +61 -12
- gov_uk_dashboards/components/helpers/get_chart_for_download.py +18 -15
- gov_uk_dashboards/components/helpers/plotting_helper_functions.py +0 -1
- gov_uk_dashboards/components/leaflet/leaflet_choropleth_map.py +108 -31
- gov_uk_dashboards/components/plotly/captioned_figure.py +6 -3
- gov_uk_dashboards/components/plotly/enums.py +2 -0
- gov_uk_dashboards/components/plotly/stacked_barchart.py +166 -73
- gov_uk_dashboards/components/plotly/time_series_chart.py +159 -20
- gov_uk_dashboards/constants.py +35 -1
- gov_uk_dashboards/figures/__init__.py +4 -2
- gov_uk_dashboards/figures/enums/__init__.py +1 -0
- gov_uk_dashboards/figures/enums/dash_patterns.py +1 -0
- gov_uk_dashboards/figures/line_chart.py +71 -71
- gov_uk_dashboards/figures/styles/__init__.py +1 -0
- gov_uk_dashboards/figures/styles/line_style.py +1 -0
- gov_uk_dashboards/formatting/human_readable.py +1 -0
- gov_uk_dashboards/formatting/number_formatting.py +14 -0
- gov_uk_dashboards/formatting/round_and_add_prefix_and_suffix.py +1 -0
- gov_uk_dashboards/formatting/text_functions.py +11 -0
- gov_uk_dashboards/lib/dap/dap_deployment.py +1 -0
- gov_uk_dashboards/lib/dap/get_dataframe_from_cds.py +96 -95
- gov_uk_dashboards/lib/datetime_functions/datetime_functions.py +118 -0
- gov_uk_dashboards/lib/download_functions/download_csv_with_headers.py +106 -83
- gov_uk_dashboards/lib/http_headers.py +10 -2
- gov_uk_dashboards/lib/logging.py +1 -0
- gov_uk_dashboards/lib/testing_functions/__init__.py +0 -0
- gov_uk_dashboards/lib/testing_functions/barchart_data_test_assertions.py +48 -0
- gov_uk_dashboards/lib/testing_functions/data_test_assertions.py +124 -0
- gov_uk_dashboards/lib/testing_functions/data_test_helper_functions.py +257 -0
- gov_uk_dashboards/lib/testing_functions/timeseries_data_test_assertions.py +29 -0
- gov_uk_dashboards/lib/warning_text_sensitive.py +44 -0
- gov_uk_dashboards/log_kpi.py +37 -0
- gov_uk_dashboards/symbols.py +1 -0
- gov_uk_dashboards/template.html +37 -0
- gov_uk_dashboards/template.py +14 -3
- {gov_uk_dashboards-21.2.2.dist-info → gov_uk_dashboards-26.26.0.dist-info}/METADATA +6 -7
- gov_uk_dashboards-26.26.0.dist-info/RECORD +128 -0
- {gov_uk_dashboards-21.2.2.dist-info → gov_uk_dashboards-26.26.0.dist-info}/WHEEL +1 -1
- gov_uk_dashboards/axes.py +0 -21
- gov_uk_dashboards/figures/chart_data.py +0 -24
- gov_uk_dashboards-21.2.2.dist-info/RECORD +0 -113
- {gov_uk_dashboards-21.2.2.dist-info → gov_uk_dashboards-26.26.0.dist-info}/licenses/LICENSE +0 -0
- {gov_uk_dashboards-21.2.2.dist-info → gov_uk_dashboards-26.26.0.dist-info}/top_level.txt +0 -0
|
@@ -63,6 +63,9 @@ class TimeSeriesChart:
|
|
|
63
63
|
hover_data: HoverDataByTrace,
|
|
64
64
|
filtered_df: pl.DataFrame,
|
|
65
65
|
trace_name_list: list[str],
|
|
66
|
+
dashed_trace_name_list: list[str] = None,
|
|
67
|
+
trace_colour_groups: list[str, str] = None,
|
|
68
|
+
initially_hidden_traces: Optional[list[str]] = None,
|
|
66
69
|
hover_data_for_traces_with_different_hover_for_last_point: Optional[
|
|
67
70
|
HoverDataByTrace
|
|
68
71
|
] = None,
|
|
@@ -79,11 +82,15 @@ class TimeSeriesChart:
|
|
|
79
82
|
x_axis_title: Optional[str] = None,
|
|
80
83
|
download_chart_button_id: Optional[str] = None,
|
|
81
84
|
download_data_button_id: Optional[str] = None,
|
|
85
|
+
download_all_data_button_id: Optional[str] = None,
|
|
86
|
+
alternative_data_button_text: Optional[str] = None,
|
|
87
|
+
alternative_all_data_button_text: Optional[str] = None,
|
|
82
88
|
number_of_traces_colour_shift_dict: Optional[dict] = None,
|
|
83
89
|
additional_line: Optional[dict] = None,
|
|
84
90
|
hover_distance: Optional[int] = 1,
|
|
85
91
|
footnote: Optional[str] = None,
|
|
86
|
-
|
|
92
|
+
stacked: Optional[bool] = False,
|
|
93
|
+
): # pylint: disable=duplicate-code
|
|
87
94
|
self.title_data = title_data
|
|
88
95
|
self.y_axis_column = y_axis_column
|
|
89
96
|
self.hover_data = hover_data
|
|
@@ -92,6 +99,9 @@ class TimeSeriesChart:
|
|
|
92
99
|
)
|
|
93
100
|
self.filtered_df = filtered_df
|
|
94
101
|
self.trace_name_list = trace_name_list
|
|
102
|
+
self.dashed_trace_name_list = dashed_trace_name_list
|
|
103
|
+
self.trace_colour_groups = trace_colour_groups
|
|
104
|
+
self.initially_hidden_traces = initially_hidden_traces
|
|
95
105
|
self.legend_dict = legend_dict
|
|
96
106
|
self.trace_name_column = trace_name_column
|
|
97
107
|
self.xaxis_tick_text_format = xaxis_tick_text_format
|
|
@@ -107,6 +117,9 @@ class TimeSeriesChart:
|
|
|
107
117
|
self.x_axis_title = x_axis_title
|
|
108
118
|
self.download_chart_button_id = download_chart_button_id
|
|
109
119
|
self.download_data_button_id = download_data_button_id
|
|
120
|
+
self.download_all_data_button_id = download_all_data_button_id
|
|
121
|
+
self.alternative_data_button_text = alternative_data_button_text
|
|
122
|
+
self.alternative_all_data_button_text = alternative_all_data_button_text
|
|
110
123
|
self.markers = [
|
|
111
124
|
"square",
|
|
112
125
|
"diamond",
|
|
@@ -118,6 +131,7 @@ class TimeSeriesChart:
|
|
|
118
131
|
self.number_of_traces_colour_shift_dict = number_of_traces_colour_shift_dict
|
|
119
132
|
self.additional_line = additional_line
|
|
120
133
|
self.hover_distance = hover_distance
|
|
134
|
+
self.stacked = stacked
|
|
121
135
|
self.colour_list = self._get_colour_list()
|
|
122
136
|
self.fig = self.create_time_series_chart()
|
|
123
137
|
self.footnote = footnote
|
|
@@ -139,7 +153,10 @@ class TimeSeriesChart:
|
|
|
139
153
|
self.title_data[SUBTITLE],
|
|
140
154
|
self.download_chart_button_id,
|
|
141
155
|
self.download_data_button_id,
|
|
142
|
-
self.
|
|
156
|
+
download_all_data_button_id=self.download_all_data_button_id,
|
|
157
|
+
alternative_data_button_text=self.alternative_data_button_text,
|
|
158
|
+
alternative_all_data_button_text=self.alternative_all_data_button_text,
|
|
159
|
+
footnote=self.footnote,
|
|
143
160
|
)
|
|
144
161
|
|
|
145
162
|
def is_json_serializable(self, value):
|
|
@@ -220,7 +237,12 @@ class TimeSeriesChart:
|
|
|
220
237
|
)
|
|
221
238
|
fig.add_trace(trace_connector)
|
|
222
239
|
# pylint: disable=unused-variable
|
|
223
|
-
for i, (
|
|
240
|
+
for i, (
|
|
241
|
+
df,
|
|
242
|
+
trace_name,
|
|
243
|
+
colour,
|
|
244
|
+
marker,
|
|
245
|
+
) in enumerate(
|
|
224
246
|
zip(
|
|
225
247
|
self._get_df_list_for_time_series(),
|
|
226
248
|
self.trace_name_list,
|
|
@@ -231,15 +253,20 @@ class TimeSeriesChart:
|
|
|
231
253
|
if REMOVE_INITIAL_MARKER in df.columns and True in df.get_column(
|
|
232
254
|
REMOVE_INITIAL_MARKER
|
|
233
255
|
):
|
|
234
|
-
marker_sizes = [0] + [12] * (len(df.
|
|
256
|
+
marker_sizes = [0] + [12] * (len(df.with_row_index()) - 1)
|
|
235
257
|
else:
|
|
236
|
-
marker_sizes = [12] * (len(df.
|
|
258
|
+
marker_sizes = [12] * (len(df.with_row_index()))
|
|
237
259
|
legendgroup = self._get_legend_group(df)
|
|
238
260
|
fig.add_trace(
|
|
239
261
|
self.create_time_series_trace(
|
|
240
262
|
df.sort(self.x_axis_column),
|
|
241
263
|
trace_name,
|
|
242
|
-
line_style=
|
|
264
|
+
line_style=(
|
|
265
|
+
{"dash": "dot", "color": colour}
|
|
266
|
+
if self.dashed_trace_name_list is not None
|
|
267
|
+
and trace_name in self.dashed_trace_name_list
|
|
268
|
+
else {"dash": "solid", "color": colour}
|
|
269
|
+
),
|
|
243
270
|
marker={"symbol": marker, "size": marker_sizes, "opacity": 1},
|
|
244
271
|
legendgroup=legendgroup,
|
|
245
272
|
),
|
|
@@ -401,6 +428,14 @@ class TimeSeriesChart:
|
|
|
401
428
|
marker (dict[str,str]): Properties for marker parameter.
|
|
402
429
|
legendgroup (str): Name to group by in legend,
|
|
403
430
|
"""
|
|
431
|
+
if (
|
|
432
|
+
self.initially_hidden_traces is not None
|
|
433
|
+
and trace_name in self.initially_hidden_traces
|
|
434
|
+
):
|
|
435
|
+
visible = "legendonly"
|
|
436
|
+
else:
|
|
437
|
+
visible = True
|
|
438
|
+
|
|
404
439
|
return go.Scatter(
|
|
405
440
|
x=df[self.x_axis_column],
|
|
406
441
|
y=df[self.y_axis_column],
|
|
@@ -414,6 +449,8 @@ class TimeSeriesChart:
|
|
|
414
449
|
trace_name in self.legend_dict if self.legend_dict is not None else True
|
|
415
450
|
),
|
|
416
451
|
legendgroup=legendgroup,
|
|
452
|
+
stackgroup="one" if self.stacked else None,
|
|
453
|
+
visible=visible,
|
|
417
454
|
)
|
|
418
455
|
|
|
419
456
|
def _get_hover_template(self, df, trace_name):
|
|
@@ -486,6 +523,7 @@ class TimeSeriesChart:
|
|
|
486
523
|
return self.legend_dict[trace_name]
|
|
487
524
|
return trace_name
|
|
488
525
|
|
|
526
|
+
# pylint: disable=too-many-statements
|
|
489
527
|
def _get_x_axis_content(self):
|
|
490
528
|
"""Generates tick text and values for the x-axis based on the unique years calculated from
|
|
491
529
|
the DATE_VALID column in the dataframe.
|
|
@@ -585,6 +623,30 @@ class TimeSeriesChart:
|
|
|
585
623
|
]
|
|
586
624
|
|
|
587
625
|
range_x = [0.5, 4.5]
|
|
626
|
+
elif self.xaxis_tick_text_format == XAxisFormat.WEEK.value:
|
|
627
|
+
df = self.filtered_df.with_columns(
|
|
628
|
+
pl.col(self.x_axis_column)
|
|
629
|
+
.str.strptime(pl.Datetime, "%Y-%m-%d", strict=False)
|
|
630
|
+
.alias(self.x_axis_column)
|
|
631
|
+
).sort(self.x_axis_column)
|
|
632
|
+
|
|
633
|
+
start_datetime = df[self.x_axis_column].min() - relativedelta(weeks=1)
|
|
634
|
+
latest_datetime = df[self.x_axis_column].max() + relativedelta(weeks=1)
|
|
635
|
+
|
|
636
|
+
start_of_week = start_datetime - relativedelta(
|
|
637
|
+
days=start_datetime.weekday()
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
tick_values = []
|
|
641
|
+
tick_text = []
|
|
642
|
+
|
|
643
|
+
current = start_of_week
|
|
644
|
+
while current <= latest_datetime:
|
|
645
|
+
tick_values.append(current)
|
|
646
|
+
tick_text.append(current.strftime("%d %b %Y")) # e.g. "29 Sep 2025"
|
|
647
|
+
current += relativedelta(weeks=1)
|
|
648
|
+
|
|
649
|
+
range_x = [start_datetime, latest_datetime]
|
|
588
650
|
else:
|
|
589
651
|
raise ValueError(
|
|
590
652
|
f"Invalid xaxis_tick_text_format: {self.xaxis_tick_text_format}"
|
|
@@ -594,9 +656,16 @@ class TimeSeriesChart:
|
|
|
594
656
|
def _get_y_axis_range_max(self):
|
|
595
657
|
"""Get the y axis range maximum value to ensure there is an axis label greater than the
|
|
596
658
|
maximum y value."""
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
659
|
+
if self.stacked:
|
|
660
|
+
largest_y_value = (
|
|
661
|
+
self.filtered_df.group_by(self.x_axis_column) # group by date
|
|
662
|
+
.agg(pl.col(self.y_axis_column).sum()) # total per date
|
|
663
|
+
.select(pl.col(self.y_axis_column).max()) # largest daily total
|
|
664
|
+
.item() # extract scalar
|
|
665
|
+
)
|
|
666
|
+
else:
|
|
667
|
+
largest_y_value = self.filtered_df[self.y_axis_column].max()
|
|
668
|
+
y_axis_max = largest_y_value + (0.3 * largest_y_value)
|
|
600
669
|
return y_axis_max
|
|
601
670
|
|
|
602
671
|
def _get_df_list_for_time_series(self) -> list[pl.DataFrame]:
|
|
@@ -610,27 +679,97 @@ class TimeSeriesChart:
|
|
|
610
679
|
return df_list
|
|
611
680
|
|
|
612
681
|
def _get_colour_list(self):
|
|
613
|
-
"""Returns a list of colours.
|
|
682
|
+
"""Returns a list of colours (one per trace in trace_name_list).
|
|
683
|
+
|
|
684
|
+
If `trace_colour_groups` is provided, traces in the same group share a colour.
|
|
685
|
+
Traces not in any group get their own colour from the palette.
|
|
686
|
+
"""
|
|
687
|
+
palette = self._get_base_palette()
|
|
688
|
+
palette = self._apply_colour_shift(palette)
|
|
689
|
+
|
|
690
|
+
groups = getattr(self, "trace_colour_groups", None) or []
|
|
691
|
+
if not groups:
|
|
692
|
+
return palette[: len(self.trace_name_list)]
|
|
693
|
+
|
|
694
|
+
trace_to_group_id = self._build_trace_to_group_id(groups)
|
|
695
|
+
return self._assign_colours_with_groups(palette, trace_to_group_id)
|
|
696
|
+
|
|
697
|
+
def _get_base_palette(self) -> list[str]:
|
|
614
698
|
number_of_traces = len(self.trace_name_list)
|
|
615
699
|
if number_of_traces == 2 and self.filled_traces_dict is None:
|
|
616
|
-
|
|
700
|
+
return [
|
|
617
701
|
AFAccessibleColours.DARK_BLUE.value,
|
|
618
702
|
AFAccessibleColours.ORANGE.value,
|
|
619
|
-
]
|
|
620
|
-
|
|
621
|
-
|
|
703
|
+
]
|
|
704
|
+
return AFAccessibleColours.CATEGORICAL.value.copy()
|
|
705
|
+
|
|
706
|
+
def _apply_colour_shift(self, palette: list[str]) -> list[str]:
|
|
707
|
+
"""Apply number_of_traces_colour_shift_dict; may replace palette with explicit list."""
|
|
708
|
+
number_of_traces = len(self.trace_name_list)
|
|
622
709
|
colour_shift_dict = (
|
|
623
710
|
{"default": 0}
|
|
624
711
|
if self.number_of_traces_colour_shift_dict is None
|
|
625
712
|
else self.number_of_traces_colour_shift_dict
|
|
626
713
|
)
|
|
627
|
-
|
|
628
714
|
colour_shift_value = colour_shift_dict.get(
|
|
629
715
|
number_of_traces, colour_shift_dict["default"]
|
|
630
716
|
)
|
|
717
|
+
|
|
631
718
|
if isinstance(colour_shift_value, list):
|
|
632
|
-
return colour_shift_value
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
719
|
+
return colour_shift_value
|
|
720
|
+
|
|
721
|
+
# rotate left by colour_shift_value
|
|
722
|
+
shift = int(colour_shift_value)
|
|
723
|
+
if shift <= 0:
|
|
724
|
+
return palette
|
|
725
|
+
|
|
726
|
+
shift = shift % len(palette)
|
|
727
|
+
return palette[shift:] + palette[:shift]
|
|
728
|
+
|
|
729
|
+
def _build_trace_to_group_id(self, groups: list[list[str]]) -> dict[str, int]:
|
|
730
|
+
"""Validate groups and return a mapping of trace -> group_id."""
|
|
731
|
+
trace_set = set(self.trace_name_list)
|
|
732
|
+
trace_to_group_id: dict[str, int] = {}
|
|
733
|
+
|
|
734
|
+
for group_id, group in enumerate(groups):
|
|
735
|
+
for trace in group:
|
|
736
|
+
if trace not in trace_set:
|
|
737
|
+
raise ValueError(
|
|
738
|
+
f"trace_colour_groups contains '{trace}', but it's not in trace_name_list."
|
|
739
|
+
)
|
|
740
|
+
if trace in trace_to_group_id:
|
|
741
|
+
raise ValueError(
|
|
742
|
+
f"Trace '{trace}' appears in more than one trace_colour_group."
|
|
743
|
+
)
|
|
744
|
+
trace_to_group_id[trace] = group_id
|
|
745
|
+
|
|
746
|
+
return trace_to_group_id
|
|
747
|
+
|
|
748
|
+
def _assign_colours_with_groups(
|
|
749
|
+
self,
|
|
750
|
+
palette: list[str],
|
|
751
|
+
trace_to_group_id: dict[str, int],
|
|
752
|
+
) -> list[str]:
|
|
753
|
+
"""Assign one colour per group (first-seen), and one per ungrouped trace."""
|
|
754
|
+
group_colour: dict[int, str] = {}
|
|
755
|
+
trace_colour: dict[str, str] = {}
|
|
756
|
+
colour_idx = 0
|
|
757
|
+
|
|
758
|
+
# assign colours to groups in order of first appearance in trace_name_list
|
|
759
|
+
for trace in self.trace_name_list:
|
|
760
|
+
group_id = trace_to_group_id.get(trace)
|
|
761
|
+
if group_id is not None and group_id not in group_colour:
|
|
762
|
+
group_colour[group_id] = palette[colour_idx % len(palette)]
|
|
763
|
+
colour_idx += 1
|
|
764
|
+
|
|
765
|
+
# apply group colours to grouped traces
|
|
766
|
+
for trace, group_id in trace_to_group_id.items():
|
|
767
|
+
trace_colour[trace] = group_colour[group_id]
|
|
768
|
+
|
|
769
|
+
# assign colours to ungrouped traces
|
|
770
|
+
for trace in self.trace_name_list:
|
|
771
|
+
if trace not in trace_colour:
|
|
772
|
+
trace_colour[trace] = palette[colour_idx % len(palette)]
|
|
773
|
+
colour_idx += 1
|
|
774
|
+
|
|
775
|
+
return [trace_colour[t] for t in self.trace_name_list]
|
gov_uk_dashboards/constants.py
CHANGED
|
@@ -17,10 +17,44 @@ YEAR = "Year"
|
|
|
17
17
|
UNIT_SIZE = "Unit size"
|
|
18
18
|
DEFAULT_COLOURSCALE = "tealgrn"
|
|
19
19
|
|
|
20
|
-
LEGEND_SPACING = "\
|
|
20
|
+
LEGEND_SPACING = "\u00a0" * 5
|
|
21
21
|
|
|
22
22
|
DOWNLOAD_BUTTON_CLASSES = (
|
|
23
23
|
"govuk-button govuk-button--primary "
|
|
24
24
|
"govuk-!-margin-bottom-0 govuk-!-margin-top-4 "
|
|
25
25
|
"flex w-auto items-center gap-2 print:hidden"
|
|
26
26
|
)
|
|
27
|
+
|
|
28
|
+
PERCENTAGE_CHANGE_FROM_PREV_YEAR = "Percentage change from prev year"
|
|
29
|
+
PERCENTAGE_CHANGE_FROM_TWO_PREV_YEAR = "Percentage change from two prev year"
|
|
30
|
+
TWENTY_NINETEEN_VALUE = "twenty_nineteen_value"
|
|
31
|
+
|
|
32
|
+
LARGE_BOLD_FONT_STYLE = {"fontSize": "300%"}
|
|
33
|
+
CHANGED_FROM_GAP_STYLE = {"marginTop": "20px"}
|
|
34
|
+
|
|
35
|
+
PERIOD_END = "period_end"
|
|
36
|
+
YEAR_END = "year_end"
|
|
37
|
+
METRIC_VALUE = "metric_value"
|
|
38
|
+
LATEST_YEAR = "latest_year"
|
|
39
|
+
PREVIOUS_YEAR = "previous_year"
|
|
40
|
+
PREVIOUS_2YEAR = "previous_2year"
|
|
41
|
+
TWENTY_NINETEEN = "twenty_nineteen"
|
|
42
|
+
|
|
43
|
+
NOTIFICATION_STYLE_RED = {"borderColor": "#d4351c", "backgroundColor": "#d4351c"}
|
|
44
|
+
NOTIFICATION_STYLE_ORANGE = {"borderColor": "#f47738", "backgroundColor": "#f47738"}
|
|
45
|
+
NOTIFICATION_STYLE_YELLOW = {"borderColor": "#ffdd00", "backgroundColor": "#ffdd00"}
|
|
46
|
+
NOTIFICATION_STYLE_GREEN = {"borderColor": "#00703c", "backgroundColor": "#00703c"}
|
|
47
|
+
|
|
48
|
+
BANNER_STYLE = {
|
|
49
|
+
"display": "flex",
|
|
50
|
+
"visibility": "visible",
|
|
51
|
+
"margin": "-10px -15px 0px -15px",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
ERROR_MESSAGE_BANNER_STYLE = {
|
|
55
|
+
"width": "100%",
|
|
56
|
+
"paddingLeft": "30px",
|
|
57
|
+
"paddingRight": "30px",
|
|
58
|
+
"justifyContent": "left",
|
|
59
|
+
"backgroundColor": "#FFDD00",
|
|
60
|
+
}
|
|
@@ -11,7 +11,9 @@ Contains:
|
|
|
11
11
|
- LineStyle: Dataclass containing information on how to style a line on a line
|
|
12
12
|
chart.
|
|
13
13
|
"""
|
|
14
|
+
|
|
14
15
|
from . import enums
|
|
15
16
|
from . import styles
|
|
16
|
-
|
|
17
|
-
from .
|
|
17
|
+
|
|
18
|
+
# from .chart_data import ChartData
|
|
19
|
+
# from .line_chart import line_chart
|
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
"""Line chart function"""
|
|
2
|
-
from typing import Optional
|
|
3
|
-
import plotly.express as px
|
|
4
|
-
from gov_uk_dashboards.axes import calc_axis_range
|
|
5
|
-
from gov_uk_dashboards.colours import ONSAccessibleColours
|
|
6
|
-
from .styles import LineStyle
|
|
7
|
-
from .chart_data import ChartData
|
|
1
|
+
# """Line chart function"""
|
|
2
|
+
# from typing import Optional
|
|
3
|
+
# import plotly.express as px
|
|
4
|
+
# from gov_uk_dashboards.axes import calc_axis_range
|
|
5
|
+
# from gov_uk_dashboards.colours import ONSAccessibleColours
|
|
6
|
+
# from .styles import LineStyle
|
|
7
|
+
# from .chart_data import ChartData
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def line_chart(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
):
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
# def line_chart(
|
|
11
|
+
# data: ChartData,
|
|
12
|
+
# title: str,
|
|
13
|
+
# markers: bool = False,
|
|
14
|
+
# line_styles: Optional[dict[str, LineStyle]] = None,
|
|
15
|
+
# **px_line_kwargs,
|
|
16
|
+
# ):
|
|
17
|
+
# """
|
|
18
|
+
# Create and return a plotly express line chart with standard formatting.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# Dataframe should be sorted so x axis is in the correct order for plotting.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
# If no style information provided, lines will be plotted as solid lines
|
|
23
|
+
# using the ONSAcessibleColours enum for their colours.
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
# Args:
|
|
26
|
+
# data (ChartData): Data for the chart.
|
|
27
|
+
# title (str): Title to be shown above the chart.
|
|
28
|
+
# markers (bool, optional): Whether markers should be plotted for each point.
|
|
29
|
+
# Defaults to False.
|
|
30
|
+
# line_styles (dict[str, LineStyle], optional): A dictionary with keys that match
|
|
31
|
+
# the categories in the category column (if supplied), and values that are
|
|
32
|
+
# LineStyle data objects to set out the style of the corresponding line.
|
|
33
|
+
# Defaults to None.
|
|
34
|
+
# **px_line_kwargs: Any other keyword arguments to pass to the plotly express
|
|
35
|
+
# line graph function.
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
37
|
+
# Returns:
|
|
38
|
+
# plotly.Figure: The generated line chart figure object.
|
|
39
|
+
# """
|
|
40
|
+
# color_discrete_map = None
|
|
41
|
+
# line_dash_map = None
|
|
42
|
+
# labels = None
|
|
43
|
+
# if line_styles:
|
|
44
|
+
# color_discrete_map = {
|
|
45
|
+
# category: line_style.color for category, line_style in line_styles.items()
|
|
46
|
+
# }
|
|
47
|
+
# line_dash_map = {
|
|
48
|
+
# category: line_style.dash_pattern
|
|
49
|
+
# for category, line_style in line_styles.items()
|
|
50
|
+
# }
|
|
51
|
+
# # If line dashes are set, plotly express automatically appends the name of the dash style
|
|
52
|
+
# # to the label in the legend/hover data.
|
|
53
|
+
# # As this is normally not desired, labels are set manually to override this.
|
|
54
|
+
# labels = {category: category for category in line_styles}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
56
|
+
# linechart = px.line(
|
|
57
|
+
# data.dataframe,
|
|
58
|
+
# x=data.x_column,
|
|
59
|
+
# y=data.y_column,
|
|
60
|
+
# range_y=calc_axis_range(data.dataframe, data.y_column),
|
|
61
|
+
# color_discrete_map=color_discrete_map,
|
|
62
|
+
# line_dash_map=line_dash_map,
|
|
63
|
+
# line_dash=data.category_column,
|
|
64
|
+
# color=data.category_column,
|
|
65
|
+
# labels=labels,
|
|
66
|
+
# markers=markers,
|
|
67
|
+
# color_discrete_sequence=[colour.value for colour in ONSAccessibleColours]
|
|
68
|
+
# if not line_styles
|
|
69
|
+
# else None,
|
|
70
|
+
# **px_line_kwargs,
|
|
71
|
+
# )
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
# linechart.update_layout(
|
|
74
|
+
# title=title,
|
|
75
|
+
# paper_bgcolor="rgba(0,0,0,0)",
|
|
76
|
+
# plot_bgcolor="rgba(0,0,0,0)",
|
|
77
|
+
# xaxis_type="category",
|
|
78
|
+
# )
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
# return linechart
|
|
@@ -22,3 +22,17 @@ def format_number_into_thousands_or_millions(
|
|
|
22
22
|
else:
|
|
23
23
|
formatted_number = str(number)
|
|
24
24
|
return formatted_number
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def add_commas(number: int, remove_decimal_places: bool = False) -> str:
|
|
28
|
+
"""Formats large numbers with a string"""
|
|
29
|
+
if remove_decimal_places:
|
|
30
|
+
return f"{number:,.0f}"
|
|
31
|
+
return f"{number:,}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def format_percentage(percentage):
|
|
35
|
+
"""Formats percentages to remove decimal place on numbers 10 or more"""
|
|
36
|
+
if abs(percentage) < 10:
|
|
37
|
+
return percentage
|
|
38
|
+
return int(percentage)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Functions to format text"""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_id_from_string(string):
|
|
7
|
+
"""Function to create an id from a string. Remove non alphanumeric characters and replaces
|
|
8
|
+
spaces with dashes"""
|
|
9
|
+
if string is None:
|
|
10
|
+
return ""
|
|
11
|
+
return re.sub(r"[^a-z0-9]+", "-", string.lower()).strip("-")
|