gov-uk-dashboards 13.5.1__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.
Files changed (53) hide show
  1. gov_uk_dashboards/components/__init__.py +1 -1
  2. gov_uk_dashboards/components/dash/__init__.py +92 -0
  3. gov_uk_dashboards/components/{create_download_chart_button.py → dash/create_download_chart_button.py} +1 -1
  4. gov_uk_dashboards/components/{create_download_data_button.py → dash/create_download_data_button.py} +1 -1
  5. gov_uk_dashboards/components/{plotly → dash}/filter_panel.py +1 -1
  6. gov_uk_dashboards/components/{plotly → dash}/table.py +2 -2
  7. gov_uk_dashboards/components/helpers/__init__.py +0 -0
  8. gov_uk_dashboards/components/{display_chart_or_table_with_header.py → helpers/display_chart_or_table_with_header.py} +4 -4
  9. gov_uk_dashboards/{lib → components/helpers}/plotting_helper_functions.py +18 -1
  10. gov_uk_dashboards/components/plotly/__init__.py +0 -92
  11. gov_uk_dashboards/components/plotly/choropleth_map.py +2 -2
  12. gov_uk_dashboards/components/plotly/enums.py +34 -0
  13. gov_uk_dashboards/components/plotly/stacked_barchart.py +13 -37
  14. gov_uk_dashboards/components/plotly/time_series_chart.py +478 -0
  15. gov_uk_dashboards/constants.py +17 -0
  16. gov_uk_dashboards/lib/datetime_functions/__init__.py +0 -0
  17. gov_uk_dashboards/lib/datetime_functions/datetime_functions.py +345 -0
  18. {gov_uk_dashboards-13.5.1.dist-info → gov_uk_dashboards-14.0.0.dist-info}/METADATA +1 -1
  19. {gov_uk_dashboards-13.5.1.dist-info → gov_uk_dashboards-14.0.0.dist-info}/RECORD +53 -46
  20. {gov_uk_dashboards-13.5.1.dist-info → gov_uk_dashboards-14.0.0.dist-info}/WHEEL +1 -1
  21. /gov_uk_dashboards/components/{plotly → dash}/apply_and_reset_filters_buttons.py +0 -0
  22. /gov_uk_dashboards/components/{plotly → dash}/banners.py +0 -0
  23. /gov_uk_dashboards/components/{plotly → dash}/card.py +0 -0
  24. /gov_uk_dashboards/components/{plotly → dash}/card_full_width.py +0 -0
  25. /gov_uk_dashboards/components/{plotly → dash}/collapsible_panel.py +0 -0
  26. /gov_uk_dashboards/components/{plotly → dash}/comparison_la_filter_button.py +0 -0
  27. /gov_uk_dashboards/components/{plotly → dash}/context_banner.py +0 -0
  28. /gov_uk_dashboards/components/{plotly → dash}/dashboard_container.py +0 -0
  29. /gov_uk_dashboards/components/{plotly → dash}/details.py +0 -0
  30. /gov_uk_dashboards/components/{plotly → dash}/download_button.py +0 -0
  31. /gov_uk_dashboards/components/{plotly → dash}/footer.py +0 -0
  32. /gov_uk_dashboards/components/{plotly → dash}/graph.py +0 -0
  33. /gov_uk_dashboards/components/{plotly → dash}/header.py +0 -0
  34. /gov_uk_dashboards/components/{plotly → dash}/heading.py +0 -0
  35. /gov_uk_dashboards/components/{plotly → dash}/html_list.py +0 -0
  36. /gov_uk_dashboards/components/{plotly → dash}/key_value_pair.py +0 -0
  37. /gov_uk_dashboards/components/{plotly → dash}/main_content.py +0 -0
  38. /gov_uk_dashboards/components/{plotly → dash}/navbar.py +0 -0
  39. /gov_uk_dashboards/components/{plotly → dash}/no_data_message.py +0 -0
  40. /gov_uk_dashboards/components/{plotly → dash}/notification_banner.py +0 -0
  41. /gov_uk_dashboards/components/{plotly → dash}/paragraph.py +0 -0
  42. /gov_uk_dashboards/components/{plotly → dash}/phase_banner.py +0 -0
  43. /gov_uk_dashboards/components/{plotly → dash}/row_component.py +0 -0
  44. /gov_uk_dashboards/components/{plotly → dash}/side_navbar.py +0 -0
  45. /gov_uk_dashboards/components/{plotly → dash}/tooltip.py +0 -0
  46. /gov_uk_dashboards/components/{plotly → dash}/tooltip_title.py +0 -0
  47. /gov_uk_dashboards/components/{plotly → dash}/visualisation_commentary.py +0 -0
  48. /gov_uk_dashboards/components/{plotly → dash}/visualisation_title.py +0 -0
  49. /gov_uk_dashboards/components/{plotly → dash}/warning_text.py +0 -0
  50. /gov_uk_dashboards/components/{plotly → helpers}/generate_dash_graph_from_figure.py +0 -0
  51. /gov_uk_dashboards/{lib → components/helpers}/update_layout_bgcolor_margin.py +0 -0
  52. {gov_uk_dashboards-13.5.1.dist-info → gov_uk_dashboards-14.0.0.dist-info}/licenses/LICENSE +0 -0
  53. {gov_uk_dashboards-13.5.1.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
@@ -0,0 +1,17 @@
1
+ """Constants that are common across the dashboard."""
2
+
3
+ CUSTOM_DATA = "custom_data"
4
+ HOVER_TEXT_HEADERS = "hover_text_headers"
5
+ DATE_VALID = "Date valid"
6
+ CHART_LABEL_FONT_SIZE = 19
7
+ FINANCIAL_YEAR_ENDING = "Financial year ending"
8
+ VALUE = "Value"
9
+ MAIN_TITLE = "main_title"
10
+ SUBTITLE = "subtitle"
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"
17
+ DEFAULT_COLOURSCALE = "tealgrn"
File without changes