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.
Files changed (87) hide show
  1. gov_uk_dashboards/__init__.py +1 -1
  2. gov_uk_dashboards/assets/__init__.py +1 -0
  3. gov_uk_dashboards/assets/dashboard.css +177 -0
  4. gov_uk_dashboards/assets/download-map.js +39 -0
  5. gov_uk_dashboards/assets/get_assets_folder.py +1 -0
  6. gov_uk_dashboards/assets/images/CHASE_icon.svg +17 -0
  7. gov_uk_dashboards/assets/images/explore_data_logo.svg +87 -0
  8. gov_uk_dashboards/assets/index.html +3 -0
  9. gov_uk_dashboards/assets/register_maps +15 -0
  10. gov_uk_dashboards/assets/scripts.js +4 -0
  11. gov_uk_dashboards/colours.py +23 -0
  12. gov_uk_dashboards/components/__init__.py +1 -0
  13. gov_uk_dashboards/components/dash/__init__.py +1 -3
  14. gov_uk_dashboards/components/dash/apply_and_reset_filters_buttons.py +1 -0
  15. gov_uk_dashboards/components/dash/banners.py +21 -0
  16. gov_uk_dashboards/components/dash/card.py +1 -0
  17. gov_uk_dashboards/components/dash/card_full_width.py +1 -0
  18. gov_uk_dashboards/components/dash/collapsible_panel.py +1 -0
  19. gov_uk_dashboards/components/dash/comparison_la_filter_button.py +1 -0
  20. gov_uk_dashboards/components/dash/context_banner.py +2 -1
  21. gov_uk_dashboards/components/dash/context_card.py +978 -0
  22. gov_uk_dashboards/components/dash/data_quality_banner.py +91 -0
  23. gov_uk_dashboards/components/dash/details.py +1 -0
  24. gov_uk_dashboards/components/dash/download_button.py +22 -36
  25. gov_uk_dashboards/components/dash/filter_panel.py +1 -0
  26. gov_uk_dashboards/components/dash/footer.py +81 -27
  27. gov_uk_dashboards/components/dash/graph.py +1 -0
  28. gov_uk_dashboards/components/dash/green_button.py +25 -0
  29. gov_uk_dashboards/components/dash/header.py +62 -9
  30. gov_uk_dashboards/components/dash/heading.py +8 -5
  31. gov_uk_dashboards/components/dash/home_page_link_button.py +9 -8
  32. gov_uk_dashboards/components/dash/html_list.py +1 -0
  33. gov_uk_dashboards/components/dash/key_value_pair.py +1 -0
  34. gov_uk_dashboards/components/dash/main_content.py +25 -2
  35. gov_uk_dashboards/components/dash/notification_banner.py +9 -5
  36. gov_uk_dashboards/components/dash/paragraph.py +1 -0
  37. gov_uk_dashboards/components/dash/phase_banner.py +7 -4
  38. gov_uk_dashboards/components/dash/row_component.py +1 -0
  39. gov_uk_dashboards/components/dash/table.py +62 -124
  40. gov_uk_dashboards/components/dash/tooltip.py +2 -1
  41. gov_uk_dashboards/components/dash/tooltip_title.py +2 -1
  42. gov_uk_dashboards/components/dash/visualisation_commentary.py +1 -0
  43. gov_uk_dashboards/components/dash/visualisation_title.py +1 -0
  44. gov_uk_dashboards/components/dash/warning_text.py +1 -0
  45. gov_uk_dashboards/components/helpers/display_chart_or_table_with_header.py +61 -12
  46. gov_uk_dashboards/components/helpers/get_chart_for_download.py +18 -15
  47. gov_uk_dashboards/components/helpers/plotting_helper_functions.py +0 -1
  48. gov_uk_dashboards/components/leaflet/leaflet_choropleth_map.py +108 -31
  49. gov_uk_dashboards/components/plotly/captioned_figure.py +6 -3
  50. gov_uk_dashboards/components/plotly/enums.py +2 -0
  51. gov_uk_dashboards/components/plotly/stacked_barchart.py +166 -73
  52. gov_uk_dashboards/components/plotly/time_series_chart.py +159 -20
  53. gov_uk_dashboards/constants.py +35 -1
  54. gov_uk_dashboards/figures/__init__.py +4 -2
  55. gov_uk_dashboards/figures/enums/__init__.py +1 -0
  56. gov_uk_dashboards/figures/enums/dash_patterns.py +1 -0
  57. gov_uk_dashboards/figures/line_chart.py +71 -71
  58. gov_uk_dashboards/figures/styles/__init__.py +1 -0
  59. gov_uk_dashboards/figures/styles/line_style.py +1 -0
  60. gov_uk_dashboards/formatting/human_readable.py +1 -0
  61. gov_uk_dashboards/formatting/number_formatting.py +14 -0
  62. gov_uk_dashboards/formatting/round_and_add_prefix_and_suffix.py +1 -0
  63. gov_uk_dashboards/formatting/text_functions.py +11 -0
  64. gov_uk_dashboards/lib/dap/dap_deployment.py +1 -0
  65. gov_uk_dashboards/lib/dap/get_dataframe_from_cds.py +96 -95
  66. gov_uk_dashboards/lib/datetime_functions/datetime_functions.py +118 -0
  67. gov_uk_dashboards/lib/download_functions/download_csv_with_headers.py +106 -83
  68. gov_uk_dashboards/lib/http_headers.py +10 -2
  69. gov_uk_dashboards/lib/logging.py +1 -0
  70. gov_uk_dashboards/lib/testing_functions/__init__.py +0 -0
  71. gov_uk_dashboards/lib/testing_functions/barchart_data_test_assertions.py +48 -0
  72. gov_uk_dashboards/lib/testing_functions/data_test_assertions.py +124 -0
  73. gov_uk_dashboards/lib/testing_functions/data_test_helper_functions.py +257 -0
  74. gov_uk_dashboards/lib/testing_functions/timeseries_data_test_assertions.py +29 -0
  75. gov_uk_dashboards/lib/warning_text_sensitive.py +44 -0
  76. gov_uk_dashboards/log_kpi.py +37 -0
  77. gov_uk_dashboards/symbols.py +1 -0
  78. gov_uk_dashboards/template.html +37 -0
  79. gov_uk_dashboards/template.py +14 -3
  80. {gov_uk_dashboards-21.2.2.dist-info → gov_uk_dashboards-26.26.0.dist-info}/METADATA +6 -7
  81. gov_uk_dashboards-26.26.0.dist-info/RECORD +128 -0
  82. {gov_uk_dashboards-21.2.2.dist-info → gov_uk_dashboards-26.26.0.dist-info}/WHEEL +1 -1
  83. gov_uk_dashboards/axes.py +0 -21
  84. gov_uk_dashboards/figures/chart_data.py +0 -24
  85. gov_uk_dashboards-21.2.2.dist-info/RECORD +0 -113
  86. {gov_uk_dashboards-21.2.2.dist-info → gov_uk_dashboards-26.26.0.dist-info}/licenses/LICENSE +0 -0
  87. {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.footnote,
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, (df, trace_name, colour, marker,) in enumerate(
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.with_row_count()) - 1)
256
+ marker_sizes = [0] + [12] * (len(df.with_row_index()) - 1)
235
257
  else:
236
- marker_sizes = [12] * (len(df.with_row_count()))
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={"dash": "solid", "color": colour},
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
- largest_number_of_weeks = self.filtered_df[self.y_axis_column].max()
598
-
599
- y_axis_max = largest_number_of_weeks + (0.3 * largest_number_of_weeks)
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
- colour_list = [
700
+ return [
617
701
  AFAccessibleColours.DARK_BLUE.value,
618
702
  AFAccessibleColours.ORANGE.value,
619
- ] # if 2 lines should use dark blue & orange as have highest contrast ratio
620
- else:
621
- colour_list = AFAccessibleColours.CATEGORICAL.value.copy()
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 # list of colours
633
- while colour_shift_value > 0:
634
- colour_list.append(colour_list.pop(0))
635
- colour_shift_value -= 1
636
- return colour_list
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]
@@ -17,10 +17,44 @@ YEAR = "Year"
17
17
  UNIT_SIZE = "Unit size"
18
18
  DEFAULT_COLOURSCALE = "tealgrn"
19
19
 
20
- LEGEND_SPACING = "\u00A0" * 5
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
- from .chart_data import ChartData
17
- from .line_chart import line_chart
17
+
18
+ # from .chart_data import ChartData
19
+ # from .line_chart import line_chart
@@ -3,4 +3,5 @@
3
3
  Contains:
4
4
  - DashPatterns: Sets out valid dash patterns used by plotly/plotly express.
5
5
  """
6
+
6
7
  from .dash_patterns import DashPatterns
@@ -1,4 +1,5 @@
1
1
  """DashPatterns enum"""
2
+
2
3
  from enum import Enum
3
4
 
4
5
 
@@ -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
- 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.
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
- Dataframe should be sorted so x axis is in the correct order for plotting.
20
+ # Dataframe should be sorted so x axis is in the correct order for plotting.
21
21
 
22
- If no style information provided, lines will be plotted as solid lines
23
- using the ONSAcessibleColours enum for their colours.
22
+ # If no style information provided, lines will be plotted as solid lines
23
+ # using the ONSAcessibleColours enum for their colours.
24
24
 
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.
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
- 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}
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
- 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
- )
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
- 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
- )
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
- return linechart
80
+ # return linechart
@@ -4,4 +4,5 @@ Contains:
4
4
  - LineStyle: Dataclass containing information on how to style a line on a line
5
5
  chart.
6
6
  """
7
+
7
8
  from .line_style import LineStyle
@@ -1,4 +1,5 @@
1
1
  """LineStyle dataclass"""
2
+
2
3
  from dataclasses import dataclass
3
4
  from typing import Union
4
5
  from ..enums.dash_patterns import DashPatterns
@@ -1,4 +1,5 @@
1
1
  """Functions for converting values to human readable format."""
2
+
2
3
  import math
3
4
  from typing import Optional
4
5
 
@@ -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)
@@ -2,6 +2,7 @@
2
2
  Function to add a prefix and suffix to a number and also show the number of decimal places
3
3
  specified.
4
4
  """
5
+
5
6
  import math
6
7
  import decimal
7
8
 
@@ -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("-")
@@ -2,6 +2,7 @@
2
2
  Scripts for deploying dashboard code to the DLUHC DAP Hosting Area.
3
3
  The DAP Hosting area is maintained by the DLUHC DAP Support team.
4
4
  """
5
+
5
6
  import subprocess
6
7
  import tempfile
7
8