gov-uk-dashboards 13.5.2__py3-none-any.whl → 14.0.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/components/__init__.py +1 -1
- gov_uk_dashboards/components/dash/__init__.py +92 -0
- gov_uk_dashboards/components/{create_download_chart_button.py → dash/create_download_chart_button.py} +1 -1
- gov_uk_dashboards/components/{create_download_data_button.py → dash/create_download_data_button.py} +1 -1
- gov_uk_dashboards/components/{plotly → dash}/filter_panel.py +1 -1
- gov_uk_dashboards/components/{plotly → dash}/table.py +2 -2
- gov_uk_dashboards/components/helpers/__init__.py +0 -0
- gov_uk_dashboards/components/{display_chart_or_table_with_header.py → helpers/display_chart_or_table_with_header.py} +4 -4
- gov_uk_dashboards/{lib → components/helpers}/plotting_helper_functions.py +17 -0
- gov_uk_dashboards/components/plotly/__init__.py +0 -92
- gov_uk_dashboards/components/plotly/choropleth_map.py +1 -1
- gov_uk_dashboards/components/plotly/enums.py +34 -0
- gov_uk_dashboards/components/plotly/stacked_barchart.py +12 -36
- gov_uk_dashboards/components/plotly/time_series_chart.py +478 -0
- gov_uk_dashboards/constants.py +5 -0
- gov_uk_dashboards/lib/datetime_functions/__init__.py +0 -0
- gov_uk_dashboards/lib/datetime_functions/datetime_functions.py +345 -0
- {gov_uk_dashboards-13.5.2.dist-info → gov_uk_dashboards-14.0.0.dist-info}/METADATA +1 -1
- {gov_uk_dashboards-13.5.2.dist-info → gov_uk_dashboards-14.0.0.dist-info}/RECORD +53 -47
- {gov_uk_dashboards-13.5.2.dist-info → gov_uk_dashboards-14.0.0.dist-info}/WHEEL +1 -1
- /gov_uk_dashboards/components/{plotly → dash}/apply_and_reset_filters_buttons.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/banners.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/card.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/card_full_width.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/collapsible_panel.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/comparison_la_filter_button.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/context_banner.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/dashboard_container.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/details.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/download_button.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/footer.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/graph.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/header.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/heading.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/html_list.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/key_value_pair.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/main_content.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/navbar.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/no_data_message.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/notification_banner.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/paragraph.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/phase_banner.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/row_component.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/side_navbar.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/tooltip.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/tooltip_title.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/visualisation_commentary.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/visualisation_title.py +0 -0
- /gov_uk_dashboards/components/{plotly → dash}/warning_text.py +0 -0
- /gov_uk_dashboards/components/{plotly → helpers}/generate_dash_graph_from_figure.py +0 -0
- /gov_uk_dashboards/{lib → components/helpers}/update_layout_bgcolor_margin.py +0 -0
- {gov_uk_dashboards-13.5.2.dist-info → gov_uk_dashboards-14.0.0.dist-info}/licenses/LICENSE +0 -0
- {gov_uk_dashboards-13.5.2.dist-info → gov_uk_dashboards-14.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,478 @@
|
|
1
|
+
"""get_time_series_chart function"""
|
2
|
+
|
3
|
+
from datetime import datetime, date
|
4
|
+
from typing import Optional
|
5
|
+
from dateutil.relativedelta import relativedelta
|
6
|
+
from dash import html
|
7
|
+
import polars as pl
|
8
|
+
|
9
|
+
import plotly.graph_objects as go
|
10
|
+
|
11
|
+
from gov_uk_dashboards.constants import (
|
12
|
+
CHART_LABEL_FONT_SIZE,
|
13
|
+
CUSTOM_DATA,
|
14
|
+
DATE_VALID,
|
15
|
+
FILL_TO_PREVIOUS_TRACE,
|
16
|
+
HOVER_TEXT_HEADERS,
|
17
|
+
MAIN_TITLE,
|
18
|
+
REMOVE_INITIAL_MARKER,
|
19
|
+
SUBTITLE,
|
20
|
+
YEAR,
|
21
|
+
)
|
22
|
+
from gov_uk_dashboards.colours import AFAccessibleColours
|
23
|
+
from gov_uk_dashboards.components.helpers.display_chart_or_table_with_header import (
|
24
|
+
display_chart_or_table_with_header,
|
25
|
+
)
|
26
|
+
from gov_uk_dashboards.components.helpers.generate_dash_graph_from_figure import (
|
27
|
+
generate_dash_graph_from_figure,
|
28
|
+
)
|
29
|
+
from gov_uk_dashboards.components.helpers.plotting_helper_functions import (
|
30
|
+
get_legend_configuration,
|
31
|
+
get_rgba_from_hex_colour_and_alpha,
|
32
|
+
)
|
33
|
+
from gov_uk_dashboards.components.helpers.update_layout_bgcolor_margin import (
|
34
|
+
update_layout_bgcolor_margin,
|
35
|
+
)
|
36
|
+
from gov_uk_dashboards.components.plotly.enums import (
|
37
|
+
HoverDataByTrace,
|
38
|
+
TitleDataStructure,
|
39
|
+
XAxisFormat,
|
40
|
+
)
|
41
|
+
from gov_uk_dashboards.lib.datetime_functions.datetime_functions import (
|
42
|
+
convert_financial_quarter_to_financial_quarter_text,
|
43
|
+
replace_jun_jul_month_abbreviations,
|
44
|
+
)
|
45
|
+
|
46
|
+
|
47
|
+
class TimeSeriesChart:
|
48
|
+
"""Class for use in generating time series charts."""
|
49
|
+
|
50
|
+
# pylint: disable=too-many-instance-attributes
|
51
|
+
# pylint: disable=too-many-arguments
|
52
|
+
# pylint: disable=too-many-locals
|
53
|
+
# pylint: disable=too-many-positional-arguments
|
54
|
+
|
55
|
+
def __init__(
|
56
|
+
self,
|
57
|
+
title_data: TitleDataStructure,
|
58
|
+
y_column: str,
|
59
|
+
hover_data: HoverDataByTrace,
|
60
|
+
filtered_df: pl.DataFrame,
|
61
|
+
trace_name_list: list[str],
|
62
|
+
hover_data_for_traces_with_different_hover_for_last_point: Optional[
|
63
|
+
HoverDataByTrace
|
64
|
+
] = None,
|
65
|
+
legend_dict: dict[str, str] = None,
|
66
|
+
trace_name_column: Optional[str] = None,
|
67
|
+
xaxis_tick_text_format: XAxisFormat = XAxisFormat.YEAR.value,
|
68
|
+
verticle_line_x_value_and_name: tuple = None,
|
69
|
+
last_2_traces_filled=False,
|
70
|
+
trace_names_to_prevent_hover_of_first_point_list=None,
|
71
|
+
x_axis_column=DATE_VALID,
|
72
|
+
x_axis_title: Optional[str] = None,
|
73
|
+
download_chart_button_id: Optional[str] = None,
|
74
|
+
download_data_button_id: Optional[str] = None,
|
75
|
+
number_of_traces_colour_shift_dict: Optional[dict] = None,
|
76
|
+
):
|
77
|
+
self.title_data = title_data
|
78
|
+
self.y_axis_column = y_column
|
79
|
+
self.hover_data = hover_data
|
80
|
+
self.hover_data_for_traces_with_different_hover_for_last_point = (
|
81
|
+
hover_data_for_traces_with_different_hover_for_last_point
|
82
|
+
)
|
83
|
+
self.filtered_df = filtered_df
|
84
|
+
self.trace_name_list = trace_name_list
|
85
|
+
self.legend_dict = legend_dict
|
86
|
+
self.trace_name_column = trace_name_column
|
87
|
+
self.xaxis_tick_text_format = xaxis_tick_text_format
|
88
|
+
self.verticle_line_x_value_and_name = verticle_line_x_value_and_name
|
89
|
+
self.last_2_traces_filled = last_2_traces_filled
|
90
|
+
self.trace_names_to_prevent_hover_of_first_point = (
|
91
|
+
trace_names_to_prevent_hover_of_first_point_list
|
92
|
+
)
|
93
|
+
self.x_axis_column = x_axis_column
|
94
|
+
self.x_axis_title = x_axis_title
|
95
|
+
self.download_chart_button_id = download_chart_button_id
|
96
|
+
self.download_data_button_id = download_data_button_id
|
97
|
+
self.markers = ["square", "diamond", "circle", "triangle-up"]
|
98
|
+
self.number_of_traces_colour_shift_dict = number_of_traces_colour_shift_dict
|
99
|
+
self.colour_list = self._get_colour_list()
|
100
|
+
self.fig = self.create_time_series_chart()
|
101
|
+
|
102
|
+
def get_time_series_chart(self) -> html.Div:
|
103
|
+
"""Creates and returns time series chart for display on application.
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
html.Div: Styled div containing title, subtile and chart.
|
107
|
+
"""
|
108
|
+
# pylint: disable=duplicate-code
|
109
|
+
graph_name = self.title_data[MAIN_TITLE].replace(" ", "-")
|
110
|
+
return display_chart_or_table_with_header(
|
111
|
+
generate_dash_graph_from_figure(
|
112
|
+
self.fig,
|
113
|
+
graph_name,
|
114
|
+
class_name="default-cursor-graph non-interactive-legend-cursor",
|
115
|
+
),
|
116
|
+
self.title_data[MAIN_TITLE],
|
117
|
+
self.title_data[SUBTITLE],
|
118
|
+
self.download_chart_button_id,
|
119
|
+
self.download_data_button_id,
|
120
|
+
)
|
121
|
+
|
122
|
+
def create_time_series_chart(
|
123
|
+
self,
|
124
|
+
):
|
125
|
+
"""generates a time series chart"""
|
126
|
+
# pylint: disable=too-many-locals
|
127
|
+
# pylint: disable=duplicate-code
|
128
|
+
fig = go.Figure()
|
129
|
+
for i, (df, trace_name, colour, marker) in enumerate(
|
130
|
+
zip(
|
131
|
+
self._get_df_list_for_time_series(),
|
132
|
+
self.trace_name_list,
|
133
|
+
self.colour_list,
|
134
|
+
self.markers,
|
135
|
+
)
|
136
|
+
):
|
137
|
+
is_last = is_second_last = False
|
138
|
+
if self.last_2_traces_filled is True:
|
139
|
+
number_of_traces = len(self._get_df_list_for_time_series())
|
140
|
+
is_last = i == number_of_traces - 1
|
141
|
+
is_second_last = i == number_of_traces - 2
|
142
|
+
if REMOVE_INITIAL_MARKER in df.columns and True in df.get_column(
|
143
|
+
REMOVE_INITIAL_MARKER
|
144
|
+
):
|
145
|
+
marker_sizes = [0] + [12] * (len(df.with_row_count()) - 1)
|
146
|
+
else:
|
147
|
+
marker_sizes = [12] * (len(df.with_row_count()))
|
148
|
+
fig.add_trace(
|
149
|
+
self.create_time_series_trace(
|
150
|
+
df.sort(self.x_axis_column),
|
151
|
+
trace_name,
|
152
|
+
(
|
153
|
+
{"width": 0}
|
154
|
+
if is_last or is_second_last
|
155
|
+
else {"dash": "solid", "color": colour}
|
156
|
+
),
|
157
|
+
fill=(
|
158
|
+
"tonexty"
|
159
|
+
if FILL_TO_PREVIOUS_TRACE in df.columns
|
160
|
+
and True in df.get_column(FILL_TO_PREVIOUS_TRACE)
|
161
|
+
else None
|
162
|
+
),
|
163
|
+
hover_label=(
|
164
|
+
{"bgcolor": AFAccessibleColours.TURQUOISE.value}
|
165
|
+
if is_last or is_second_last
|
166
|
+
else None
|
167
|
+
),
|
168
|
+
marker=(
|
169
|
+
{"opacity": 0}
|
170
|
+
if is_last or is_second_last
|
171
|
+
else {"symbol": marker, "size": marker_sizes, "opacity": 1}
|
172
|
+
),
|
173
|
+
)
|
174
|
+
)
|
175
|
+
|
176
|
+
# if self.average_increment_for_average_trace is not None:
|
177
|
+
# trace_name = LINEAR_TRAJECTORY
|
178
|
+
# dates = [
|
179
|
+
# datetime(2024, 7, 9) + relativedelta(months=1 * i)
|
180
|
+
# for i in range(len(tick_values))
|
181
|
+
# ]
|
182
|
+
# values = [
|
183
|
+
# self.average_increment_for_average_trace * i for i in range(len(dates))
|
184
|
+
# ]
|
185
|
+
# fig.add_trace(
|
186
|
+
# go.Scatter(
|
187
|
+
# x=dates,
|
188
|
+
# y=values,
|
189
|
+
# mode="lines",
|
190
|
+
# name=trace_name,
|
191
|
+
# line={"dash": "dash", "color": "darkgray", "width": 2},
|
192
|
+
# hoverinfo="skip",
|
193
|
+
# )
|
194
|
+
# )
|
195
|
+
|
196
|
+
if self.verticle_line_x_value_and_name is not None:
|
197
|
+
|
198
|
+
fig.add_vline(
|
199
|
+
x=self.verticle_line_x_value_and_name[0],
|
200
|
+
line_width=2,
|
201
|
+
line_dash="dash",
|
202
|
+
line_color="#b3b3b3",
|
203
|
+
)
|
204
|
+
fig.add_annotation(
|
205
|
+
x=self.verticle_line_x_value_and_name,
|
206
|
+
yref="paper",
|
207
|
+
y=0.9,
|
208
|
+
text=self.verticle_line_x_value_and_name[1],
|
209
|
+
showarrow=False,
|
210
|
+
font={"color": "#414042", "size": 16},
|
211
|
+
xanchor="left",
|
212
|
+
yanchor="bottom",
|
213
|
+
)
|
214
|
+
|
215
|
+
y_range = [0, self._get_y_axis_range_max()]
|
216
|
+
|
217
|
+
fig.update_yaxes(rangemode="tozero", showgrid=True, range=y_range)
|
218
|
+
update_layout_bgcolor_margin(fig, "#FFFFFF")
|
219
|
+
|
220
|
+
if self.x_axis_title is not None:
|
221
|
+
fig.add_annotation(
|
222
|
+
xref="x domain",
|
223
|
+
yref="y domain",
|
224
|
+
x=1,
|
225
|
+
y=-0.25,
|
226
|
+
text=self.x_axis_title,
|
227
|
+
showarrow=False,
|
228
|
+
font={"size": CHART_LABEL_FONT_SIZE},
|
229
|
+
)
|
230
|
+
|
231
|
+
fig.update_layout(
|
232
|
+
legend=get_legend_configuration(),
|
233
|
+
font={"size": CHART_LABEL_FONT_SIZE},
|
234
|
+
yaxis_tickformat=",",
|
235
|
+
)
|
236
|
+
return fig
|
237
|
+
|
238
|
+
# pylint: disable=duplicate-code
|
239
|
+
def _format_x_axis(self, fig):
|
240
|
+
tick_text, tick_values, range_x = self._get_x_axis_content()
|
241
|
+
|
242
|
+
fig.update_xaxes(
|
243
|
+
tickvals=tick_values,
|
244
|
+
ticktext=tick_text,
|
245
|
+
tickmode="array",
|
246
|
+
range=range_x,
|
247
|
+
)
|
248
|
+
|
249
|
+
return tick_values
|
250
|
+
|
251
|
+
def create_time_series_trace(
|
252
|
+
self,
|
253
|
+
df: pl.DataFrame,
|
254
|
+
trace_name: str,
|
255
|
+
line_style: dict[str, str],
|
256
|
+
fill: str,
|
257
|
+
hover_label: dict[str, str],
|
258
|
+
marker: dict[str, str],
|
259
|
+
):
|
260
|
+
"""Creates a trace for the plot.
|
261
|
+
|
262
|
+
Args:
|
263
|
+
df (pl.DataFrame): Dataframe to use to create trace. Must contain "Date valid" column,
|
264
|
+
y_value column and columns defined in self.hover_data[CUSTOM_DATA].
|
265
|
+
trace_name (str): Name of trace.
|
266
|
+
line_style (dict[str, str]): Properties for line_style parameter.
|
267
|
+
fill (str): Properties for fill parameter.
|
268
|
+
hover_label (dict[str,str]): Properties for hoverlabel parameter.
|
269
|
+
marker (dict[str,str]): Properties for marker parameter.
|
270
|
+
"""
|
271
|
+
|
272
|
+
return go.Scatter(
|
273
|
+
x=df[self.x_axis_column],
|
274
|
+
y=df[self.y_axis_column],
|
275
|
+
line=line_style,
|
276
|
+
name=self._get_trace_name(trace_name),
|
277
|
+
hovertemplate=self._get_hover_template(df, trace_name),
|
278
|
+
customdata=self._get_custom_data(df, trace_name),
|
279
|
+
marker=marker,
|
280
|
+
fill=fill,
|
281
|
+
hoverlabel=hover_label,
|
282
|
+
fillcolor=(
|
283
|
+
get_rgba_from_hex_colour_and_alpha(
|
284
|
+
AFAccessibleColours.TURQUOISE.value, alpha=0.2
|
285
|
+
)
|
286
|
+
if fill
|
287
|
+
else None
|
288
|
+
),
|
289
|
+
showlegend=(
|
290
|
+
trace_name in self.legend_dict if self.legend_dict is not None else True
|
291
|
+
),
|
292
|
+
)
|
293
|
+
|
294
|
+
def _get_hover_template(self, df, trace_name):
|
295
|
+
return [
|
296
|
+
(
|
297
|
+
""
|
298
|
+
if i == 0
|
299
|
+
and self.trace_names_to_prevent_hover_of_first_point is not None
|
300
|
+
and trace_name in self.trace_names_to_prevent_hover_of_first_point
|
301
|
+
else self._get_custom_hover_template(i, df, trace_name)
|
302
|
+
)
|
303
|
+
for i in range(df.shape[0]) # the number of rows in df
|
304
|
+
]
|
305
|
+
|
306
|
+
def _get_custom_hover_template(self, i, df, trace_name):
|
307
|
+
hover_text_headers = self.hover_data[trace_name][HOVER_TEXT_HEADERS]
|
308
|
+
if (
|
309
|
+
self.hover_data_for_traces_with_different_hover_for_last_point is not None
|
310
|
+
and trace_name
|
311
|
+
in self.hover_data_for_traces_with_different_hover_for_last_point
|
312
|
+
and i == df.shape[0] - 1
|
313
|
+
):
|
314
|
+
hover_text_headers = (
|
315
|
+
self.hover_data_for_traces_with_different_hover_for_last_point[
|
316
|
+
trace_name
|
317
|
+
][HOVER_TEXT_HEADERS]
|
318
|
+
)
|
319
|
+
# pylint: disable=duplicate-code
|
320
|
+
return (
|
321
|
+
f"{trace_name}<br>"
|
322
|
+
f"{hover_text_headers[0]}"
|
323
|
+
": %{customdata[0]}<br>"
|
324
|
+
f"{hover_text_headers[1]}"
|
325
|
+
": %{customdata[1]}<extra></extra>"
|
326
|
+
)
|
327
|
+
|
328
|
+
def _get_custom_data(self, df, trace_name):
|
329
|
+
# For last points of trace_name in [], we want different custom data.
|
330
|
+
customdata = df[self.hover_data[trace_name][CUSTOM_DATA]]
|
331
|
+
if (
|
332
|
+
self.hover_data_for_traces_with_different_hover_for_last_point is not None
|
333
|
+
and trace_name
|
334
|
+
in self.hover_data_for_traces_with_different_hover_for_last_point
|
335
|
+
):
|
336
|
+
|
337
|
+
customdata = [
|
338
|
+
(
|
339
|
+
[df[col][i] for col in self.hover_data[trace_name][CUSTOM_DATA]]
|
340
|
+
if i < df.shape[0] - 1
|
341
|
+
else [
|
342
|
+
df[col][i]
|
343
|
+
for col in self.hover_data_for_traces_with_different_hover_for_last_point[
|
344
|
+
trace_name
|
345
|
+
][
|
346
|
+
CUSTOM_DATA
|
347
|
+
]
|
348
|
+
]
|
349
|
+
) # Use different columns for the last point
|
350
|
+
for i in range(df.shape[0])
|
351
|
+
]
|
352
|
+
return customdata
|
353
|
+
|
354
|
+
def _get_trace_name(self, trace_name):
|
355
|
+
if self.legend_dict is not None and trace_name in self.legend_dict:
|
356
|
+
return self.legend_dict[trace_name]
|
357
|
+
return trace_name
|
358
|
+
|
359
|
+
def _get_x_axis_content(self):
|
360
|
+
"""Generates tick text and values for the x-axis based on the unique years calculated from
|
361
|
+
the DATE_VALID column in the dataframe.
|
362
|
+
Returns:
|
363
|
+
tuple: A tuple containing tick_text, tick_values and range_x.
|
364
|
+
"""
|
365
|
+
if self.xaxis_tick_text_format == XAxisFormat.YEAR.value:
|
366
|
+
df_with_year_column = self.filtered_df.with_columns(
|
367
|
+
pl.col(self.x_axis_column)
|
368
|
+
.str.strptime(pl.Date, "%Y-%m-%d")
|
369
|
+
.dt.year()
|
370
|
+
.alias(YEAR)
|
371
|
+
)
|
372
|
+
|
373
|
+
year_list = df_with_year_column[YEAR].unique().to_list()
|
374
|
+
|
375
|
+
tick_text = [min(year_list) - 1] + year_list + [max(year_list) + 1]
|
376
|
+
|
377
|
+
tick_values = [date(year, 1, 1) for year in tick_text]
|
378
|
+
|
379
|
+
range_x = [
|
380
|
+
tick_values[0] + relativedelta(months=6),
|
381
|
+
tick_values[-1] + relativedelta(months=6),
|
382
|
+
]
|
383
|
+
|
384
|
+
elif self.xaxis_tick_text_format == XAxisFormat.MONTH_YEAR.value:
|
385
|
+
df = self.filtered_df.with_columns(
|
386
|
+
pl.col(self.x_axis_column)
|
387
|
+
.str.strptime(pl.Date, "%Y-%m-%d")
|
388
|
+
.alias(self.x_axis_column)
|
389
|
+
).sort(self.x_axis_column)
|
390
|
+
|
391
|
+
start_datetime = datetime(2024, 7, 1).date()
|
392
|
+
latest_datetime = df[self.x_axis_column].max()
|
393
|
+
tick_text = []
|
394
|
+
current = start_datetime
|
395
|
+
while current <= latest_datetime:
|
396
|
+
tick_text.append(current.strftime("%b %Y"))
|
397
|
+
current += relativedelta(months=1)
|
398
|
+
|
399
|
+
tick_text_length = len(tick_text)
|
400
|
+
total_tick_points = int((tick_text_length / 5) * 7)
|
401
|
+
additional_tick_points = total_tick_points - tick_text_length
|
402
|
+
|
403
|
+
last_current_tick_text = datetime.strptime(tick_text[-1], "%b %Y")
|
404
|
+
|
405
|
+
for x in range(additional_tick_points):
|
406
|
+
tick_text = tick_text + [
|
407
|
+
(last_current_tick_text + relativedelta(months=x + 1)).strftime(
|
408
|
+
"%b %Y"
|
409
|
+
)
|
410
|
+
]
|
411
|
+
|
412
|
+
tick_values = [
|
413
|
+
datetime.strptime(month_year, "%b %Y").replace(day=1)
|
414
|
+
for month_year in tick_text
|
415
|
+
]
|
416
|
+
|
417
|
+
range_x = [
|
418
|
+
tick_values[0],
|
419
|
+
tick_values[-1] + relativedelta(months=1),
|
420
|
+
]
|
421
|
+
tick_text = replace_jun_jul_month_abbreviations(tick_text)
|
422
|
+
elif self.xaxis_tick_text_format == XAxisFormat.FINANCIAL_QUARTER.value:
|
423
|
+
tick_values = [1, 2, 3, 4]
|
424
|
+
tick_text = [
|
425
|
+
convert_financial_quarter_to_financial_quarter_text(quarter)
|
426
|
+
for quarter in tick_values
|
427
|
+
]
|
428
|
+
|
429
|
+
range_x = [0.5, 4.5]
|
430
|
+
else:
|
431
|
+
raise ValueError(
|
432
|
+
f"Invalid xaxis_tick_text_format: {self.xaxis_tick_text_format}"
|
433
|
+
)
|
434
|
+
return tick_text, tick_values, range_x
|
435
|
+
|
436
|
+
def _get_y_axis_range_max(self):
|
437
|
+
"""Get the y axis range maximum value to ensure there is an axis label greater than the
|
438
|
+
maximum y value."""
|
439
|
+
largest_number_of_weeks = self.filtered_df[self.y_axis_column].max()
|
440
|
+
|
441
|
+
y_axis_max = largest_number_of_weeks + (0.3 * largest_number_of_weeks)
|
442
|
+
return y_axis_max
|
443
|
+
|
444
|
+
def _get_df_list_for_time_series(self) -> list[pl.DataFrame]:
|
445
|
+
if self.trace_name_column is not None:
|
446
|
+
df_list = [
|
447
|
+
self.filtered_df.filter(pl.col(self.trace_name_column) == trace_name)
|
448
|
+
for trace_name in self.trace_name_list
|
449
|
+
]
|
450
|
+
else:
|
451
|
+
df_list = [self.filtered_df]
|
452
|
+
return df_list
|
453
|
+
|
454
|
+
def _get_colour_list(self):
|
455
|
+
"""Returns a list of colours."""
|
456
|
+
number_of_traces = len(self.trace_name_list)
|
457
|
+
if number_of_traces == 2:
|
458
|
+
colour_list = [
|
459
|
+
AFAccessibleColours.DARK_BLUE.value,
|
460
|
+
AFAccessibleColours.ORANGE.value,
|
461
|
+
] # if 2 lines should use dark blue & orange as have highest contrast ratio
|
462
|
+
else:
|
463
|
+
colour_list = AFAccessibleColours.CATEGORICAL.value.copy()
|
464
|
+
colour_shift_dict = (
|
465
|
+
{"default": 0}
|
466
|
+
if self.number_of_traces_colour_shift_dict is None
|
467
|
+
else self.number_of_traces_colour_shift_dict
|
468
|
+
)
|
469
|
+
|
470
|
+
colour_shift_value = colour_shift_dict.get(
|
471
|
+
number_of_traces, colour_shift_dict["default"]
|
472
|
+
)
|
473
|
+
if isinstance(colour_shift_value, list):
|
474
|
+
return colour_shift_value # list of colours
|
475
|
+
while colour_shift_value > 0:
|
476
|
+
colour_list.append(colour_list.pop(0))
|
477
|
+
colour_shift_value -= 1
|
478
|
+
return colour_list
|
gov_uk_dashboards/constants.py
CHANGED
@@ -9,4 +9,9 @@ VALUE = "Value"
|
|
9
9
|
MAIN_TITLE = "main_title"
|
10
10
|
SUBTITLE = "subtitle"
|
11
11
|
MEASURE = "measure"
|
12
|
+
FILL_TO_PREVIOUS_TRACE = "Fill to previous trace"
|
13
|
+
REMOVE_INITIAL_MARKER = "Remove initial marker"
|
14
|
+
YEAR = "Year"
|
15
|
+
|
16
|
+
UNIT_SIZE = "Unit size"
|
12
17
|
DEFAULT_COLOURSCALE = "tealgrn"
|
File without changes
|