gov-uk-dashboards 13.4.2__tar.gz → 13.5.0__tar.gz

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 (105) hide show
  1. {gov_uk_dashboards-13.4.2/gov_uk_dashboards.egg-info → gov_uk_dashboards-13.5.0}/PKG-INFO +1 -1
  2. gov_uk_dashboards-13.5.0/gov_uk_dashboards/components/plotly/generate_dash_graph_from_figure.py +49 -0
  3. gov_uk_dashboards-13.5.0/gov_uk_dashboards/components/plotly/stacked_barchart.py +354 -0
  4. gov_uk_dashboards-13.5.0/gov_uk_dashboards/lib/plotting_helper_functions.py +29 -0
  5. gov_uk_dashboards-13.5.0/gov_uk_dashboards/lib/update_layout_bgcolor_margin.py +24 -0
  6. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0/gov_uk_dashboards.egg-info}/PKG-INFO +1 -1
  7. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards.egg-info/SOURCES.txt +4 -0
  8. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/setup.py +1 -1
  9. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/LICENSE +0 -0
  10. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/MANIFEST.in +0 -0
  11. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/README.md +0 -0
  12. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/__init__.py +0 -0
  13. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/__init__.py +0 -0
  14. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/attach-event-to-dash.js +0 -0
  15. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/dashboard.css +0 -0
  16. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/fonts/bold-affa96571d-v2.woff +0 -0
  17. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/fonts/bold-b542beb274-v2.woff2 +0 -0
  18. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/fonts/light-94a07e06a1-v2.woff2 +0 -0
  19. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/fonts/light-f591b13f7d-v2.woff +0 -0
  20. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/get_assets_folder.py +0 -0
  21. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/govuk-frontend-3.14.0.min.js +0 -0
  22. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/DLUHC_WHITE_Master_AW_sm.png +0 -0
  23. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/MHCLG-favicon.png +0 -0
  24. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/MHCLG_favicon.png +0 -0
  25. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/dcms_coatofarms.png +0 -0
  26. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/dluhc_favicon.ico +0 -0
  27. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/gov_favicon.ico +0 -0
  28. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/govuk-crest.svg +0 -0
  29. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/hm-government-logo.png +0 -0
  30. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/mhclg_coat_of_arms.png +0 -0
  31. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/mhclg_white_no_background.png +0 -0
  32. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/oflog/MedianAbsolute.png +0 -0
  33. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/oflog/how_to_1_selecting_data.png +0 -0
  34. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/oflog/how_to_2_viewing_tabs.png +0 -0
  35. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/oflog/how_to_3_viewing_charts.png +0 -0
  36. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/oflog/how_to_4_viewing_trends.png +0 -0
  37. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/images/oflog/how_to_5_viewing_tables.png +0 -0
  38. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/mobile-nav.js +0 -0
  39. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/topojson/usa_110m.json +0 -0
  40. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/assets/topojson/world_110m.json +0 -0
  41. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/axes.py +0 -0
  42. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/colours.py +0 -0
  43. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/__init__.py +0 -0
  44. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/create_download_chart_button.py +0 -0
  45. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/create_download_data_button.py +0 -0
  46. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/display_chart_or_table_with_header.py +0 -0
  47. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/__init__.py +0 -0
  48. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/apply_and_reset_filters_buttons.py +0 -0
  49. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/banners.py +0 -0
  50. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/captioned_figure.py +0 -0
  51. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/card.py +0 -0
  52. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/card_full_width.py +0 -0
  53. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/choropleth_map.py +0 -0
  54. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/collapsible_panel.py +0 -0
  55. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/comparison_la_filter_button.py +0 -0
  56. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/context_banner.py +0 -0
  57. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/dashboard_container.py +0 -0
  58. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/details.py +0 -0
  59. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/download_button.py +0 -0
  60. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/filter_panel.py +0 -0
  61. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/footer.py +0 -0
  62. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/graph.py +0 -0
  63. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/header.py +0 -0
  64. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/heading.py +0 -0
  65. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/html_list.py +0 -0
  66. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/key_value_pair.py +0 -0
  67. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/main_content.py +0 -0
  68. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/navbar.py +0 -0
  69. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/no_data_message.py +0 -0
  70. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/notification_banner.py +0 -0
  71. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/paragraph.py +0 -0
  72. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/phase_banner.py +0 -0
  73. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/row_component.py +0 -0
  74. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/side_navbar.py +0 -0
  75. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/table.py +0 -0
  76. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/tooltip.py +0 -0
  77. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/tooltip_title.py +0 -0
  78. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/visualisation_commentary.py +0 -0
  79. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/visualisation_title.py +0 -0
  80. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/components/plotly/warning_text.py +0 -0
  81. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/figures/__init__.py +0 -0
  82. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/figures/chart_data.py +0 -0
  83. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/figures/enums/__init__.py +0 -0
  84. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/figures/enums/dash_patterns.py +0 -0
  85. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/figures/line_chart.py +0 -0
  86. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/figures/styles/__init__.py +0 -0
  87. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/figures/styles/line_style.py +0 -0
  88. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/formatting/__init__.py +0 -0
  89. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/formatting/human_readable.py +0 -0
  90. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/formatting/round_and_add_prefix_and_suffix.py +0 -0
  91. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/formatting/rounding.py +0 -0
  92. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/lib/__init__.py +0 -0
  93. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/lib/dap/__init__.py +0 -0
  94. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/lib/dap/dap_deployment.py +0 -0
  95. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/lib/dap/get_dataframe_from_cds.py +0 -0
  96. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/lib/enable_basic_auth.py +0 -0
  97. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/lib/http_headers.py +0 -0
  98. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/lib/logging.py +0 -0
  99. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/symbols.py +0 -0
  100. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/template.html +0 -0
  101. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards/template.py +0 -0
  102. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards.egg-info/dependency_links.txt +0 -0
  103. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards.egg-info/requires.txt +0 -0
  104. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/gov_uk_dashboards.egg-info/top_level.txt +0 -0
  105. {gov_uk_dashboards-13.4.2 → gov_uk_dashboards-13.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gov_uk_dashboards
3
- Version: 13.4.2
3
+ Version: 13.5.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
@@ -0,0 +1,49 @@
1
+ """Creates a Dash Graph component from a given Plotly figure"""
2
+
3
+ from typing import Any, Dict, Union
4
+ import plotly.graph_objects as Figure
5
+ from dash import dcc
6
+
7
+
8
+ def generate_dash_graph_from_figure(
9
+ figure: Figure,
10
+ graph_name: str,
11
+ graph_style: Union[Dict[str, Any], None] = None,
12
+ double_click_attribute: Union[str, bool] = False,
13
+ class_name: str = "",
14
+ ) -> dcc.Graph:
15
+ """
16
+ Creates a Dash Graph component from a given Plotly figure. This function allows for the
17
+ customisation of the graph's appearance and behavior, and includes default pointer cursor,
18
+ from "default-cursor-graph" class.
19
+ Args:
20
+ - figure: Plotly.graph_objects.Figure instance to be displayed within the Dash Graph component.
21
+ - graph_name: A name for the id of the graph component.
22
+ - graph_style: An optional dictionary specifying CSS styles to apply to the graph component.
23
+ - double_click_attribute: Determines the action taken on double-clicking the graph.
24
+ Can be a boolean or a string specifying the mode.
25
+ - className (str): A string containing one or more CSS class names to apply to the graph.
26
+ Returns:
27
+ - A dash.dcc.Graph component configured with the provided parameters and styles.
28
+ """
29
+
30
+ if not graph_style:
31
+ graph_style = {}
32
+
33
+ if "height" not in graph_style.keys():
34
+ graph_style["height"] = "450px"
35
+
36
+ figure.update_layout(dragmode=False)
37
+ # pylint: disable=duplicate-code
38
+ return dcc.Graph(
39
+ id=f"{graph_name}-graph",
40
+ responsive=True,
41
+ figure=figure,
42
+ style=graph_style,
43
+ config={
44
+ "displayModeBar": False,
45
+ "doubleClick": double_click_attribute,
46
+ "scrollZoom": False,
47
+ },
48
+ className="default-cursor-graph " + class_name,
49
+ )
@@ -0,0 +1,354 @@
1
+ """stacked_barchart function"""
2
+
3
+ from enum import Enum
4
+ import math
5
+ from typing import Optional, TypedDict
6
+ from dash import html
7
+ import polars as pl
8
+
9
+ import plotly.graph_objects as go
10
+
11
+ from constants import (
12
+ CHART_LABEL_FONT_SIZE,
13
+ CUSTOM_DATA,
14
+ DATE_VALID,
15
+ FINANCIAL_YEAR_ENDING,
16
+ HOVER_TEXT_HEADERS,
17
+ MAIN_TITLE,
18
+ MEASURE,
19
+ SUBTITLE,
20
+ VALUE,
21
+ )
22
+ from gov_uk_dashboards.colours import AFAccessibleColours
23
+ from gov_uk_dashboards.components.display_chart_or_table_with_header import (
24
+ display_chart_or_table_with_header,
25
+ )
26
+ from gov_uk_dashboards.components.plotly.generate_dash_graph_from_figure import (
27
+ generate_dash_graph_from_figure,
28
+ )
29
+ from gov_uk_dashboards.lib.plotting_helper_functions import get_legend_configuration
30
+ from gov_uk_dashboards.formatting.human_readable import format_as_human_readable
31
+
32
+ from gov_uk_dashboards.lib.update_layout_bgcolor_margin import (
33
+ update_layout_bgcolor_margin,
34
+ )
35
+
36
+
37
+ class XAxisFormat(Enum):
38
+ """Enum for date format on x axis"""
39
+
40
+ YEAR = "year"
41
+ MONTH_YEAR = "month_year"
42
+ FINANCIAL_QUARTER = "financial_quarter"
43
+
44
+
45
+ class TitleDataStructure(TypedDict):
46
+ """A TypedDict representing the structure of title_data"""
47
+
48
+ MAIN_TITLE: str
49
+ SUBTITLE: str
50
+
51
+
52
+ class HoverDataStructure(TypedDict):
53
+ """A TypedDict representing the structure of hover_data"""
54
+
55
+ CUSTOM_DATA: list[str]
56
+ HOVER_TEXT_HEADERS: list[str]
57
+
58
+
59
+ class HoverDataByTrace(TypedDict):
60
+ """A TypedDict representing hover_data organized by tracename"""
61
+
62
+ tracename: dict[
63
+ str, HoverDataStructure
64
+ ] # Each tracename maps to a HoverDataStructure
65
+
66
+
67
+ class StackedBarChart:
68
+ """Class for use in generating stacked bar charts."""
69
+
70
+ # pylint: disable=too-many-instance-attributes
71
+ # pylint: disable=too-many-arguments
72
+ # pylint: disable=too-many-locals
73
+ # pylint: disable = too-many-positional-arguments
74
+ def __init__(
75
+ self,
76
+ title_data: TitleDataStructure,
77
+ y_column: str,
78
+ hover_data: HoverDataByTrace,
79
+ df: pl.DataFrame,
80
+ trace_name_list: list[str],
81
+ trace_name_column: Optional[str] = None,
82
+ xaxis_tick_text_format: XAxisFormat = XAxisFormat.YEAR.value,
83
+ line_trace_name: Optional[str] = None,
84
+ x_axis_column=DATE_VALID,
85
+ download_chart_button_id: Optional[str] = None,
86
+ download_data_button_id: Optional[str] = None,
87
+ ):
88
+ """Initializes the StackedBarChart instance.
89
+ To display the chart, call the `get_stacked_bar_chart()` method.
90
+
91
+ Args:
92
+ title_data (TitleDataStructure): Data structure containing the chart title information.
93
+ y_column (str): The column name representing the Y-axis data.
94
+ hover_data (HoverDataByTrace): Data structure for hover information.
95
+ df (pl.DataFrame): The dataset for the chart.
96
+ trace_name_list (list[str]): List of trace names for the stacked bars.
97
+ trace_name_column (Optional[str], optional): Column name representing trace categories,
98
+ if applicable. Defaults to None.
99
+ xaxis_tick_text_format (XAxisFormat, optional): Format for X-axis tick labels.
100
+ Defaults to XAxisFormat.YEAR.value.
101
+ line_trace_name (Optional[str], optional): Name for an optional line trace overlay,
102
+ must be in MEASURE column of df, line_trace_name will display in legend.
103
+ Defaults to None.
104
+ x_axis_column (_type_, optional): The column used for the X-axis values.
105
+ Defaults to DATE_VALID.
106
+ download_chart_button_id (Optional[str], optional): ID for the chart download button,
107
+ if applicable. Defaults to None.
108
+ download_data_button_id (Optional[str], optional): ID for the data download button, if
109
+ applicable. Defaults to None.
110
+ """
111
+ self.title_data = title_data
112
+ self.y_axis_column = y_column
113
+ self.hover_data = hover_data
114
+ self.df = df
115
+ self.trace_name_list = trace_name_list
116
+ self.trace_name_column = trace_name_column
117
+ self.xaxis_tick_text_format = xaxis_tick_text_format
118
+ self.line_trace_name = line_trace_name
119
+ self.x_axis_column = x_axis_column
120
+ self.download_chart_button_id = download_chart_button_id
121
+ self.download_data_button_id = download_data_button_id
122
+ self.fig = self.create_stacked_bar_chart()
123
+
124
+ def get_stacked_bar_chart(self) -> html.Div:
125
+ """Creates and returns stacked bar chart for display on application.
126
+
127
+ Returns:
128
+ html.Div: Styled div containing title, subtile and chart.
129
+ """
130
+ graph_name = self.title_data[MAIN_TITLE].replace(" ", "-")
131
+ return display_chart_or_table_with_header(
132
+ generate_dash_graph_from_figure(
133
+ self.fig,
134
+ graph_name,
135
+ class_name="default-cursor-graph non-interactive-legend-cursor",
136
+ ),
137
+ self.title_data[MAIN_TITLE],
138
+ self.title_data[SUBTITLE],
139
+ self.download_chart_button_id,
140
+ self.download_data_button_id,
141
+ )
142
+
143
+ def create_stacked_bar_chart(
144
+ self,
145
+ ):
146
+ """generates a stacked bar chart"""
147
+ # pylint: disable=too-many-locals
148
+ fig = go.Figure()
149
+ colour_list = (
150
+ AFAccessibleColours.CATEGORICAL.value
151
+ if len(self.trace_name_list) != 2
152
+ else [
153
+ AFAccessibleColours.DARK_BLUE.value,
154
+ AFAccessibleColours.ORANGE.value,
155
+ ] # if 2 lines should use dark blue & orange as have highest contrast ratio
156
+ )
157
+ for _, (df, trace_name, colour) in enumerate(
158
+ zip(
159
+ self._get_df_list_for_time_series(),
160
+ self.trace_name_list,
161
+ colour_list,
162
+ )
163
+ ):
164
+ fig.add_trace(
165
+ self.create_bar_chart_trace(
166
+ df.sort(self.x_axis_column),
167
+ trace_name,
168
+ hover_label=None,
169
+ colour=colour,
170
+ )
171
+ )
172
+
173
+ if self.line_trace_name is not None:
174
+ colour = AFAccessibleColours.CATEGORICAL.value[len(self.trace_name_list)]
175
+ df = self.df.filter(pl.col(MEASURE) == self.line_trace_name)
176
+
177
+ fig.add_trace(
178
+ go.Scatter(
179
+ x=df[FINANCIAL_YEAR_ENDING],
180
+ y=df[VALUE],
181
+ customdata=self._get_custom_data(self.line_trace_name, df),
182
+ mode="lines",
183
+ line={"color": colour, "width": 3},
184
+ name=self.line_trace_name,
185
+ hovertemplate=self._get_hover_template(self.line_trace_name),
186
+ legendrank=99999, # a high number to ensure it is bottom of the legend
187
+ )
188
+ )
189
+
190
+ max_y, min_y, tickvals, ticktext = _get_y_range_tickvals_and_ticktext(
191
+ self.df, "£", self.trace_name_list
192
+ )
193
+ update_layout_bgcolor_margin(fig, "#FFFFFF")
194
+
195
+ fig.update_layout(
196
+ legend=get_legend_configuration(),
197
+ font={"size": CHART_LABEL_FONT_SIZE},
198
+ yaxis={
199
+ "range": [min_y * 1.1, max_y * 1.1],
200
+ "tickmode": "array",
201
+ "tickvals": tickvals,
202
+ "ticktext": ticktext,
203
+ },
204
+ showlegend=True,
205
+ barmode="relative",
206
+ xaxis={"categoryorder": "category ascending"},
207
+ )
208
+ return fig
209
+
210
+ def _format_x_axis(self, fig):
211
+ tick_text, tick_values, range_x = self._get_x_axis_content()
212
+ fig.update_xaxes(
213
+ tickvals=tick_values,
214
+ ticktext=tick_text,
215
+ tickmode="array",
216
+ range=range_x,
217
+ )
218
+
219
+ return tick_values
220
+
221
+ def create_bar_chart_trace(
222
+ self,
223
+ df: pl.DataFrame,
224
+ trace_name: str,
225
+ hover_label: dict[str, str],
226
+ colour: str,
227
+ ):
228
+ """Creates a trace for the plot.
229
+
230
+ Args:
231
+ df (pl.DataFrame): Dataframe to use to create trace. Must contain x and y columns,
232
+ and columns defined in self.hover_data[CUSTOM_DATA].
233
+ trace_name (str): Name of trace.
234
+ hover_label (dict[str,str]): Properties for hoverlabel parameter.
235
+ colour (str): Colour for bar.
236
+ """
237
+
238
+ return go.Bar(
239
+ x=df[self.x_axis_column],
240
+ y=df[self.y_axis_column],
241
+ name=trace_name,
242
+ hovertemplate=[
243
+ self._get_hover_template(trace_name) for i in range(df.shape[0])
244
+ ],
245
+ customdata=self._get_custom_data(trace_name, df),
246
+ hoverlabel=hover_label,
247
+ marker={"color": colour},
248
+ )
249
+
250
+ def _get_hover_template(self, trace_name):
251
+ hover_text_headers = self.hover_data[trace_name][HOVER_TEXT_HEADERS]
252
+ hover_template = (
253
+ f"{trace_name}<br>"
254
+ f"{hover_text_headers[0]}"
255
+ ": %{customdata[0]}<br>"
256
+ f"{hover_text_headers[1]}"
257
+ ": %{customdata[1]}<extra></extra>"
258
+ )
259
+ return hover_template
260
+
261
+ def _get_custom_data(self, trace_name, df):
262
+ customdata = df[self.hover_data[trace_name][CUSTOM_DATA]]
263
+ return customdata
264
+
265
+ def _get_x_axis_content(self):
266
+ """Generates tick text and values for the x-axis based on the unique years calculated from
267
+ the FINANCIAL_YEAR_ENDING column in the dataframe.
268
+ Returns:
269
+ tuple: A tuple containing tick_text, tick_values and range_x.
270
+ """
271
+ if self.xaxis_tick_text_format == XAxisFormat.YEAR.value:
272
+ year_list = self.df[FINANCIAL_YEAR_ENDING].unique().to_list()
273
+ int_min_year = int(min(year_list))
274
+ int_max_year = int(max(year_list))
275
+
276
+ tick_text = []
277
+ year = int_min_year
278
+ while year <= int_max_year:
279
+ tick_text.append(str(year + 1))
280
+ year = year + 1
281
+
282
+ tick_values = tick_text
283
+
284
+ range_x = [
285
+ tick_values[0],
286
+ tick_values[-1],
287
+ ]
288
+ else:
289
+ raise ValueError(
290
+ f"Invalid xaxis_tick_text_format: {self.xaxis_tick_text_format}"
291
+ )
292
+ return tick_text, tick_values, range_x
293
+
294
+ def _get_df_list_for_time_series(self) -> list[pl.DataFrame]:
295
+ if self.trace_name_column is not None:
296
+ df_list = [
297
+ self.df.filter(pl.col(self.trace_name_column) == trace_name)
298
+ for trace_name in self.trace_name_list
299
+ ]
300
+ else:
301
+ df_list = [self.df]
302
+ return df_list
303
+
304
+
305
+ def _get_y_range_tickvals_and_ticktext(
306
+ dataframe: pl.DataFrame, tick_prefix: str, yaxis_with_values: list[str]
307
+ ):
308
+ barchart_df = dataframe.pivot(
309
+ index=FINANCIAL_YEAR_ENDING, columns=MEASURE, values=VALUE
310
+ )
311
+ positive_sum = sum(
312
+ pl.when(pl.col(col) > 0).then(pl.col(col)).otherwise(0)
313
+ for col in yaxis_with_values
314
+ )
315
+ negative_sum = sum(
316
+ pl.when(pl.col(col) < 0).then(pl.col(col)).otherwise(0)
317
+ for col in yaxis_with_values
318
+ )
319
+ barchart_df = barchart_df.with_columns(positive_sum.alias("Positive sum"))
320
+ barchart_df = barchart_df.with_columns(negative_sum.alias("Negative sum"))
321
+ maxy = barchart_df.select([pl.col("Positive sum").max()]).item()
322
+ miny = barchart_df.select([pl.col("Negative sum").min()]).item()
323
+ tickvals = _generate_tickvals(maxy, miny)
324
+ ticktext = [format_as_human_readable(val, prefix=tick_prefix) for val in tickvals]
325
+ return tickvals[-1], tickvals[0], tickvals, ticktext
326
+
327
+
328
+ def _generate_tickvals(maxy, miny):
329
+ range_size = maxy - miny
330
+
331
+ # Determine the order of magnitude of the range
332
+ order = int(math.log10(range_size))
333
+
334
+ # Start with an initial step size
335
+ step_size = 10**order
336
+
337
+ # Calculate the number of ticks based on the step size
338
+ num_ticks = math.ceil(range_size / step_size)
339
+
340
+ # Adjust step size to ensure the number of ticks is between 6 and 10
341
+ while num_ticks < 6 or num_ticks > 10:
342
+ if num_ticks < 6: # Too few ticks -> decrease step size
343
+ step_size /= 2
344
+ elif num_ticks > 10: # Too many ticks -> increase step size
345
+ step_size *= 2
346
+ num_ticks = math.ceil(range_size / step_size)
347
+
348
+ # Adjust the start and end of the range to align with the step size
349
+ start = math.floor(miny / step_size) * step_size
350
+ end = math.ceil(maxy / step_size) * step_size
351
+
352
+ # Generate tick values
353
+ tickvals = list(range(int(start), int(end) + 1, int(step_size)))
354
+ return tickvals
@@ -0,0 +1,29 @@
1
+ """Helper functions for use to plot charts"""
2
+
3
+
4
+ from constants import (
5
+ CHART_LABEL_FONT_SIZE,
6
+ )
7
+
8
+
9
+ def get_legend_configuration(itemclick=True, itemdoubleclick=True):
10
+ """
11
+ Returns the legend configuration for charts with customizable interaction settings.
12
+ Args:
13
+ itemclick (bool): Determines whether clicking on a legend item toggles its visibility.
14
+ Set to True by default, allowing click interactions.
15
+ itemdoubleclick (bool): Determines the behavior when double-clicking on a legend item.
16
+ Set to True by default, allowing double-click interactions.
17
+ Returns:
18
+ dict: A dictionary containing the configuration settings for the legend.
19
+ """
20
+ return {
21
+ "x": 0,
22
+ "y": -0.22,
23
+ "yanchor": "top",
24
+ "traceorder": "normal",
25
+ "orientation": "v",
26
+ "font": {"size": CHART_LABEL_FONT_SIZE},
27
+ "itemclick": "toggle" if itemclick else False,
28
+ "itemdoubleclick": "toggle" if itemdoubleclick else False,
29
+ }
@@ -0,0 +1,24 @@
1
+ """Function to update the background colour and margin for plots"""
2
+
3
+ import plotly.graph_objects as go
4
+ from gov_uk_dashboards import colours
5
+
6
+
7
+ def update_layout_bgcolor_margin(fig: go.Figure, colour: str):
8
+ """update background colour and margin for plot"""
9
+ fig.update_layout(
10
+ plot_bgcolor=colour,
11
+ paper_bgcolor=colour,
12
+ yaxis_zerolinecolor=colour,
13
+ margin={"l": 0, "r": 0, "b": 0, "t": 0},
14
+ )
15
+ fig.update_xaxes(
16
+ gridcolor=colour,
17
+ zerolinecolor=colour,
18
+ ticks="outside",
19
+ tickcolor=colours.GovUKColours.MID_GREY.value,
20
+ )
21
+ fig.update_yaxes(
22
+ gridcolor=colours.GovUKColours.MID_GREY.value,
23
+ zerolinecolor=colours.GovUKColours.MID_GREY.value,
24
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gov_uk_dashboards
3
- Version: 13.4.2
3
+ Version: 13.5.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
@@ -60,6 +60,7 @@ gov_uk_dashboards/components/plotly/details.py
60
60
  gov_uk_dashboards/components/plotly/download_button.py
61
61
  gov_uk_dashboards/components/plotly/filter_panel.py
62
62
  gov_uk_dashboards/components/plotly/footer.py
63
+ gov_uk_dashboards/components/plotly/generate_dash_graph_from_figure.py
63
64
  gov_uk_dashboards/components/plotly/graph.py
64
65
  gov_uk_dashboards/components/plotly/header.py
65
66
  gov_uk_dashboards/components/plotly/heading.py
@@ -73,6 +74,7 @@ gov_uk_dashboards/components/plotly/paragraph.py
73
74
  gov_uk_dashboards/components/plotly/phase_banner.py
74
75
  gov_uk_dashboards/components/plotly/row_component.py
75
76
  gov_uk_dashboards/components/plotly/side_navbar.py
77
+ gov_uk_dashboards/components/plotly/stacked_barchart.py
76
78
  gov_uk_dashboards/components/plotly/table.py
77
79
  gov_uk_dashboards/components/plotly/tooltip.py
78
80
  gov_uk_dashboards/components/plotly/tooltip_title.py
@@ -94,6 +96,8 @@ gov_uk_dashboards/lib/__init__.py
94
96
  gov_uk_dashboards/lib/enable_basic_auth.py
95
97
  gov_uk_dashboards/lib/http_headers.py
96
98
  gov_uk_dashboards/lib/logging.py
99
+ gov_uk_dashboards/lib/plotting_helper_functions.py
100
+ gov_uk_dashboards/lib/update_layout_bgcolor_margin.py
97
101
  gov_uk_dashboards/lib/dap/__init__.py
98
102
  gov_uk_dashboards/lib/dap/dap_deployment.py
99
103
  gov_uk_dashboards/lib/dap/get_dataframe_from_cds.py
@@ -10,7 +10,7 @@ setup(
10
10
  author="Department for Levelling Up, Housing and Communities",
11
11
  description="Provides access to functionality common to creating a data dashboard.",
12
12
  name="gov_uk_dashboards",
13
- version="13.4.2",
13
+ version="13.5.0",
14
14
  long_description=long_description,
15
15
  long_description_content_type="text/markdown",
16
16
  packages=find_packages(),