gov-uk-dashboards 26.25.0__py3-none-any.whl → 26.27.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.
@@ -95,8 +95,27 @@ class AFAccessibleColours(Enum):
95
95
  ORANGE = "#F46A25"
96
96
  DARK_GREY = "#3D3D3D"
97
97
  LIGHT_PURPLE = "#A285D1"
98
-
99
- CATEGORICAL = [DARK_BLUE, TURQUOISE, DARK_PINK, ORANGE, DARK_GREY, LIGHT_PURPLE]
98
+ MUSTARD = "#9C7A00"
99
+ CRIMSON = "#C62828"
100
+ OLIVE = "#7A9A01"
101
+ MAGENTA = "#D81B60"
102
+ BROWN = "#8C5A2B"
103
+ TEAL_BLUE = "#1F77B4"
104
+
105
+ CATEGORICAL = [
106
+ DARK_BLUE,
107
+ TURQUOISE,
108
+ DARK_PINK,
109
+ ORANGE,
110
+ DARK_GREY,
111
+ LIGHT_PURPLE,
112
+ MUSTARD,
113
+ CRIMSON,
114
+ OLIVE,
115
+ MAGENTA,
116
+ BROWN,
117
+ TEAL_BLUE,
118
+ ]
100
119
 
101
120
  # three shades of blue from dark to light to use for sequential data
102
121
  BLUE_SEQUENTIAL = ["#12436D", "#2073BC", "#6BACE6"]
@@ -624,8 +624,8 @@ class ContextCard:
624
624
  When True, comparison tags show the difference between the current VALUE and
625
625
  the comparison VALUE converted to weeks/days via `convert_days_to_weeks_and_days`.
626
626
  Tag prefix becomes:
627
- - "slower than ..." if percent-change is positive,
628
- - "faster than ..." if percent-change is negative,
627
+ - "longer than ..." if percent-change is positive,
628
+ - "shorter than ..." if percent-change is negative,
629
629
  - "unchanged from ..." if zero.
630
630
  Requires both current and comparison values to be present.
631
631
  NOTE: This cannot be True at the same time as `use_previous_value_rather_than_change`.
@@ -877,9 +877,9 @@ class ContextCard:
877
877
  )
878
878
 
879
879
  if percentage_change > 0:
880
- comparison_period_text_prefix = "slower than "
880
+ comparison_period_text_prefix = "longer than "
881
881
  elif percentage_change < 0:
882
- comparison_period_text_prefix = "faster than "
882
+ comparison_period_text_prefix = "shorter than "
883
883
  else:
884
884
  comparison_period_text_prefix = "unchanged from "
885
885
 
@@ -64,6 +64,7 @@ class TimeSeriesChart:
64
64
  filtered_df: pl.DataFrame,
65
65
  trace_name_list: list[str],
66
66
  dashed_trace_name_list: list[str] = None,
67
+ trace_colour_groups: list[str, str] = None,
67
68
  initially_hidden_traces: Optional[list[str]] = None,
68
69
  hover_data_for_traces_with_different_hover_for_last_point: Optional[
69
70
  HoverDataByTrace
@@ -99,6 +100,7 @@ class TimeSeriesChart:
99
100
  self.filtered_df = filtered_df
100
101
  self.trace_name_list = trace_name_list
101
102
  self.dashed_trace_name_list = dashed_trace_name_list
103
+ self.trace_colour_groups = trace_colour_groups
102
104
  self.initially_hidden_traces = initially_hidden_traces
103
105
  self.legend_dict = legend_dict
104
106
  self.trace_name_column = trace_name_column
@@ -125,6 +127,12 @@ class TimeSeriesChart:
125
127
  "triangle-up",
126
128
  "x",
127
129
  "triangle-down",
130
+ "cross",
131
+ "star",
132
+ "pentagon",
133
+ "hexagon",
134
+ "triangle-left",
135
+ "triangle-right",
128
136
  ]
129
137
  self.number_of_traces_colour_shift_dict = number_of_traces_colour_shift_dict
130
138
  self.additional_line = additional_line
@@ -677,27 +685,97 @@ class TimeSeriesChart:
677
685
  return df_list
678
686
 
679
687
  def _get_colour_list(self):
680
- """Returns a list of colours."""
688
+ """Returns a list of colours (one per trace in trace_name_list).
689
+
690
+ If `trace_colour_groups` is provided, traces in the same group share a colour.
691
+ Traces not in any group get their own colour from the palette.
692
+ """
693
+ palette = self._get_base_palette()
694
+ palette = self._apply_colour_shift(palette)
695
+
696
+ groups = getattr(self, "trace_colour_groups", None) or []
697
+ if not groups:
698
+ return palette[: len(self.trace_name_list)]
699
+
700
+ trace_to_group_id = self._build_trace_to_group_id(groups)
701
+ return self._assign_colours_with_groups(palette, trace_to_group_id)
702
+
703
+ def _get_base_palette(self) -> list[str]:
681
704
  number_of_traces = len(self.trace_name_list)
682
705
  if number_of_traces == 2 and self.filled_traces_dict is None:
683
- colour_list = [
706
+ return [
684
707
  AFAccessibleColours.DARK_BLUE.value,
685
708
  AFAccessibleColours.ORANGE.value,
686
- ] # if 2 lines should use dark blue & orange as have highest contrast ratio
687
- else:
688
- colour_list = AFAccessibleColours.CATEGORICAL.value.copy()
709
+ ]
710
+ return AFAccessibleColours.CATEGORICAL.value.copy()
711
+
712
+ def _apply_colour_shift(self, palette: list[str]) -> list[str]:
713
+ """Apply number_of_traces_colour_shift_dict; may replace palette with explicit list."""
714
+ number_of_traces = len(self.trace_name_list)
689
715
  colour_shift_dict = (
690
716
  {"default": 0}
691
717
  if self.number_of_traces_colour_shift_dict is None
692
718
  else self.number_of_traces_colour_shift_dict
693
719
  )
694
-
695
720
  colour_shift_value = colour_shift_dict.get(
696
721
  number_of_traces, colour_shift_dict["default"]
697
722
  )
723
+
698
724
  if isinstance(colour_shift_value, list):
699
- return colour_shift_value # list of colours
700
- while colour_shift_value > 0:
701
- colour_list.append(colour_list.pop(0))
702
- colour_shift_value -= 1
703
- return colour_list
725
+ return colour_shift_value
726
+
727
+ # rotate left by colour_shift_value
728
+ shift = int(colour_shift_value)
729
+ if shift <= 0:
730
+ return palette
731
+
732
+ shift = shift % len(palette)
733
+ return palette[shift:] + palette[:shift]
734
+
735
+ def _build_trace_to_group_id(self, groups: list[list[str]]) -> dict[str, int]:
736
+ """Validate groups and return a mapping of trace -> group_id."""
737
+ trace_set = set(self.trace_name_list)
738
+ trace_to_group_id: dict[str, int] = {}
739
+
740
+ for group_id, group in enumerate(groups):
741
+ for trace in group:
742
+ if trace not in trace_set:
743
+ raise ValueError(
744
+ f"trace_colour_groups contains '{trace}', but it's not in trace_name_list."
745
+ )
746
+ if trace in trace_to_group_id:
747
+ raise ValueError(
748
+ f"Trace '{trace}' appears in more than one trace_colour_group."
749
+ )
750
+ trace_to_group_id[trace] = group_id
751
+
752
+ return trace_to_group_id
753
+
754
+ def _assign_colours_with_groups(
755
+ self,
756
+ palette: list[str],
757
+ trace_to_group_id: dict[str, int],
758
+ ) -> list[str]:
759
+ """Assign one colour per group (first-seen), and one per ungrouped trace."""
760
+ group_colour: dict[int, str] = {}
761
+ trace_colour: dict[str, str] = {}
762
+ colour_idx = 0
763
+
764
+ # assign colours to groups in order of first appearance in trace_name_list
765
+ for trace in self.trace_name_list:
766
+ group_id = trace_to_group_id.get(trace)
767
+ if group_id is not None and group_id not in group_colour:
768
+ group_colour[group_id] = palette[colour_idx % len(palette)]
769
+ colour_idx += 1
770
+
771
+ # apply group colours to grouped traces
772
+ for trace, group_id in trace_to_group_id.items():
773
+ trace_colour[trace] = group_colour[group_id]
774
+
775
+ # assign colours to ungrouped traces
776
+ for trace in self.trace_name_list:
777
+ if trace not in trace_colour:
778
+ trace_colour[trace] = palette[colour_idx % len(palette)]
779
+ colour_idx += 1
780
+
781
+ return [trace_colour[t] for t in self.trace_name_list]
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gov_uk_dashboards
3
- Version: 26.25.0
3
+ Version: 26.27.0
4
4
  Summary: Provides access to functionality common to creating a data dashboard.
5
- Author: Department for Levelling Up, Housing and Communities
5
+ Author: Ministry of Housing, Communities & Local Government
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE
8
8
  Requires-Dist: setuptools<81.0,>=59.8
@@ -1,5 +1,5 @@
1
1
  gov_uk_dashboards/__init__.py,sha256=OqwrcJmNWlyzGe8wEy_lkIgRihjvdtSYhcx43TEvpVU,1281
2
- gov_uk_dashboards/colours.py,sha256=ju_2FIeTARqG4BQo_2jIwo6trD5yOc8ulhaVj7KJ8hw,2858
2
+ gov_uk_dashboards/colours.py,sha256=igBgM9qj-gxRFfggnu_J6a1irRHfIn4D06WwfWvi7Gw,3155
3
3
  gov_uk_dashboards/constants.py,sha256=xsBbONNbsrmTfj2tD8VScmtlwB0ymQ1AfaxDXSJ7kWs,1807
4
4
  gov_uk_dashboards/log_kpi.py,sha256=xDhb-ex16MfQ1RHQF99huv2FNRu5EKM30bIXYqCnd6g,1023
5
5
  gov_uk_dashboards/symbols.py,sha256=Ca7kWylg5iLzYyninqsk7FPvefz1qvz8UEKmB1VYcxY,425
@@ -49,7 +49,7 @@ gov_uk_dashboards/components/dash/card_full_width.py,sha256=KnpkB3krgLxp1MoqEZaz
49
49
  gov_uk_dashboards/components/dash/collapsible_panel.py,sha256=6A90xiTLU0b5e4jaWcisTbsf_82nRBvGapbyETd5m70,1192
50
50
  gov_uk_dashboards/components/dash/comparison_la_filter_button.py,sha256=u53Vmuz4MJ0J8RSVXGEFvDgzXIp_-JpDDE4o9VijoDw,671
51
51
  gov_uk_dashboards/components/dash/context_banner.py,sha256=gy0qKhseiM1oUyRS1X8_HrJuah-WaWN6yeZOHoGzAl4,1009
52
- gov_uk_dashboards/components/dash/context_card.py,sha256=lE_srpi4HW06HzRKZFdWigLnLfZvgKqN6uOTXbgPPfI,39481
52
+ gov_uk_dashboards/components/dash/context_card.py,sha256=MvxIuYg3yK-ipTHAcNDXc2TiX-J2uPZv9M5IEeWbOmE,39483
53
53
  gov_uk_dashboards/components/dash/dashboard_container.py,sha256=KC2isR0NShxUYCl_pzDEAS4WK5pFrLMp4m2We3AqiwM,512
54
54
  gov_uk_dashboards/components/dash/data_quality_banner.py,sha256=VLuAndN9xVFuMWFLqoPlolAJt08H9_rQa5x7jqde_RQ,2945
55
55
  gov_uk_dashboards/components/dash/details.py,sha256=ENkSBKd6XZTq0X7Jn0-zPaO1qR4F7zPNX-RLAXKVvAI,1107
@@ -90,7 +90,7 @@ gov_uk_dashboards/components/plotly/captioned_figure.py,sha256=XPY1MUbpKs0pEHvnU
90
90
  gov_uk_dashboards/components/plotly/choropleth_map.py,sha256=U9RmS3MZGloQAt9HoSYh3Xad205DDfZOjz91ZD_ydbI,9849
91
91
  gov_uk_dashboards/components/plotly/enums.py,sha256=ibzWxGd8E01swHGIdYIMGSLOZJh4Q8ON0Zyc92JBFno,892
92
92
  gov_uk_dashboards/components/plotly/stacked_barchart.py,sha256=oGgCA08Y-jHzWwOv3knfBPtFE8vPUwOs7E5PrqVExq0,19165
93
- gov_uk_dashboards/components/plotly/time_series_chart.py,sha256=XefuriycHQXI7ETWtkM0aMoUwK88ugo2iXiJ5X4IY1Q,27793
93
+ gov_uk_dashboards/components/plotly/time_series_chart.py,sha256=YZgBBBpR4KwxijShgmB8rBAaCDzPfkdz5B_hk6j_4PI,30816
94
94
  gov_uk_dashboards/figures/__init__.py,sha256=FusPSoLDgFFRC0v3t_agHkVPG66fIjlrPlPaCyFefno,718
95
95
  gov_uk_dashboards/figures/line_chart.py,sha256=3fr5HVwOnuuR6xZ5t0UXB0U2I803yhTjDZMkf1ttstY,2955
96
96
  gov_uk_dashboards/figures/enums/__init__.py,sha256=sZyTdNROAxAZa93OR_Wz4XYZtdFqLSLud0if6l5dscY,203
@@ -121,8 +121,8 @@ gov_uk_dashboards/lib/testing_functions/barchart_data_test_assertions.py,sha256=
121
121
  gov_uk_dashboards/lib/testing_functions/data_test_assertions.py,sha256=1Icy7NZXw6hI_-7QAUaHqjjPKViY0jcFEASlk6Ul2tg,4162
122
122
  gov_uk_dashboards/lib/testing_functions/data_test_helper_functions.py,sha256=JoptoXJORuIdrhweVlp9fhX-ew2GtIkctIotpHWBQDQ,9635
123
123
  gov_uk_dashboards/lib/testing_functions/timeseries_data_test_assertions.py,sha256=SJa3WLgFqf7Y1W0sxsOew_-4m3689cjynRjoCyBK5WQ,1240
124
- gov_uk_dashboards-26.25.0.dist-info/licenses/LICENSE,sha256=GDiD7Y2Gx7JucPV1JfVySJeah-qiSyBPdpJ6RHCEHTc,1126
125
- gov_uk_dashboards-26.25.0.dist-info/METADATA,sha256=qgMJmWFtHDfatFmtJM-VrDNyf3943tRAsdteFwTMD_g,5903
126
- gov_uk_dashboards-26.25.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
127
- gov_uk_dashboards-26.25.0.dist-info/top_level.txt,sha256=gPaN1P3-H3Rgi2me6tt-fX_cxo19CZfA4PjlZPjGRpo,18
128
- gov_uk_dashboards-26.25.0.dist-info/RECORD,,
124
+ gov_uk_dashboards-26.27.0.dist-info/licenses/LICENSE,sha256=GDiD7Y2Gx7JucPV1JfVySJeah-qiSyBPdpJ6RHCEHTc,1126
125
+ gov_uk_dashboards-26.27.0.dist-info/METADATA,sha256=15y3ZBsvY6flcLEKR92lDBB8_lfENQq08-I-pioiUSw,5902
126
+ gov_uk_dashboards-26.27.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
127
+ gov_uk_dashboards-26.27.0.dist-info/top_level.txt,sha256=gPaN1P3-H3Rgi2me6tt-fX_cxo19CZfA4PjlZPjGRpo,18
128
+ gov_uk_dashboards-26.27.0.dist-info/RECORD,,