gov-uk-dashboards 13.5.2__py3-none-any.whl → 15.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 +17 -0
  10. gov_uk_dashboards/components/plotly/__init__.py +0 -92
  11. gov_uk_dashboards/components/plotly/choropleth_map.py +1 -1
  12. gov_uk_dashboards/components/plotly/enums.py +34 -0
  13. gov_uk_dashboards/components/plotly/stacked_barchart.py +12 -36
  14. gov_uk_dashboards/components/plotly/time_series_chart.py +477 -0
  15. gov_uk_dashboards/constants.py +5 -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.2.dist-info → gov_uk_dashboards-15.0.0.dist-info}/METADATA +1 -1
  19. {gov_uk_dashboards-13.5.2.dist-info → gov_uk_dashboards-15.0.0.dist-info}/RECORD +53 -47
  20. {gov_uk_dashboards-13.5.2.dist-info → gov_uk_dashboards-15.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.2.dist-info → gov_uk_dashboards-15.0.0.dist-info}/licenses/LICENSE +0 -0
  53. {gov_uk_dashboards-13.5.2.dist-info → gov_uk_dashboards-15.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,477 @@
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
+ self._format_x_axis(fig)
176
+
177
+ # if self.average_increment_for_average_trace is not None:
178
+ # trace_name = LINEAR_TRAJECTORY
179
+ # dates = [
180
+ # datetime(2024, 7, 9) + relativedelta(months=1 * i)
181
+ # for i in range(len(tick_values))
182
+ # ]
183
+ # values = [
184
+ # self.average_increment_for_average_trace * i for i in range(len(dates))
185
+ # ]
186
+ # fig.add_trace(
187
+ # go.Scatter(
188
+ # x=dates,
189
+ # y=values,
190
+ # mode="lines",
191
+ # name=trace_name,
192
+ # line={"dash": "dash", "color": "darkgray", "width": 2},
193
+ # hoverinfo="skip",
194
+ # )
195
+ # )
196
+
197
+ if self.verticle_line_x_value_and_name is not None:
198
+
199
+ fig.add_vline(
200
+ x=self.verticle_line_x_value_and_name[0],
201
+ line_width=2,
202
+ line_dash="dash",
203
+ line_color="#b3b3b3",
204
+ )
205
+ fig.add_annotation(
206
+ x=self.verticle_line_x_value_and_name,
207
+ yref="paper",
208
+ y=0.9,
209
+ text=self.verticle_line_x_value_and_name[1],
210
+ showarrow=False,
211
+ font={"color": "#414042", "size": 16},
212
+ xanchor="left",
213
+ yanchor="bottom",
214
+ )
215
+
216
+ y_range = [0, self._get_y_axis_range_max()]
217
+
218
+ fig.update_yaxes(rangemode="tozero", showgrid=True, range=y_range)
219
+ update_layout_bgcolor_margin(fig, "#FFFFFF")
220
+
221
+ if self.x_axis_title is not None:
222
+ fig.add_annotation(
223
+ xref="x domain",
224
+ yref="y domain",
225
+ x=1,
226
+ y=-0.25,
227
+ text=self.x_axis_title,
228
+ showarrow=False,
229
+ font={"size": CHART_LABEL_FONT_SIZE},
230
+ )
231
+
232
+ fig.update_layout(
233
+ legend=get_legend_configuration(),
234
+ font={"size": CHART_LABEL_FONT_SIZE},
235
+ yaxis_tickformat=",",
236
+ )
237
+ return fig
238
+
239
+ # pylint: disable=duplicate-code
240
+ def _format_x_axis(self, fig):
241
+ tick_text, tick_values, range_x = self._get_x_axis_content()
242
+
243
+ fig.update_xaxes(
244
+ tickvals=tick_values,
245
+ ticktext=tick_text,
246
+ tickmode="array",
247
+ range=range_x,
248
+ )
249
+
250
+ def create_time_series_trace(
251
+ self,
252
+ df: pl.DataFrame,
253
+ trace_name: str,
254
+ line_style: dict[str, str],
255
+ fill: str,
256
+ hover_label: dict[str, str],
257
+ marker: dict[str, str],
258
+ ):
259
+ """Creates a trace for the plot.
260
+
261
+ Args:
262
+ df (pl.DataFrame): Dataframe to use to create trace. Must contain "Date valid" column,
263
+ y_value column and columns defined in self.hover_data[CUSTOM_DATA].
264
+ trace_name (str): Name of trace.
265
+ line_style (dict[str, str]): Properties for line_style parameter.
266
+ fill (str): Properties for fill parameter.
267
+ hover_label (dict[str,str]): Properties for hoverlabel parameter.
268
+ marker (dict[str,str]): Properties for marker parameter.
269
+ """
270
+
271
+ return go.Scatter(
272
+ x=df[self.x_axis_column],
273
+ y=df[self.y_axis_column],
274
+ line=line_style,
275
+ name=self._get_trace_name(trace_name),
276
+ hovertemplate=self._get_hover_template(df, trace_name),
277
+ customdata=self._get_custom_data(df, trace_name),
278
+ marker=marker,
279
+ fill=fill,
280
+ hoverlabel=hover_label,
281
+ fillcolor=(
282
+ get_rgba_from_hex_colour_and_alpha(
283
+ AFAccessibleColours.TURQUOISE.value, alpha=0.2
284
+ )
285
+ if fill
286
+ else None
287
+ ),
288
+ showlegend=(
289
+ trace_name in self.legend_dict if self.legend_dict is not None else True
290
+ ),
291
+ )
292
+
293
+ def _get_hover_template(self, df, trace_name):
294
+ return [
295
+ (
296
+ ""
297
+ if i == 0
298
+ and self.trace_names_to_prevent_hover_of_first_point is not None
299
+ and trace_name in self.trace_names_to_prevent_hover_of_first_point
300
+ else self._get_custom_hover_template(i, df, trace_name)
301
+ )
302
+ for i in range(df.shape[0]) # the number of rows in df
303
+ ]
304
+
305
+ def _get_custom_hover_template(self, i, df, trace_name):
306
+ hover_text_headers = self.hover_data[trace_name][HOVER_TEXT_HEADERS]
307
+ if (
308
+ self.hover_data_for_traces_with_different_hover_for_last_point is not None
309
+ and trace_name
310
+ in self.hover_data_for_traces_with_different_hover_for_last_point
311
+ and i == df.shape[0] - 1
312
+ ):
313
+ hover_text_headers = (
314
+ self.hover_data_for_traces_with_different_hover_for_last_point[
315
+ trace_name
316
+ ][HOVER_TEXT_HEADERS]
317
+ )
318
+ # pylint: disable=duplicate-code
319
+ return (
320
+ f"{trace_name}<br>"
321
+ f"{hover_text_headers[0]}"
322
+ ": %{customdata[0]}<br>"
323
+ f"{hover_text_headers[1]}"
324
+ ": %{customdata[1]}<extra></extra>"
325
+ )
326
+
327
+ def _get_custom_data(self, df, trace_name):
328
+ # For last points of trace_name in [], we want different custom data.
329
+ customdata = df[self.hover_data[trace_name][CUSTOM_DATA]]
330
+ if (
331
+ self.hover_data_for_traces_with_different_hover_for_last_point is not None
332
+ and trace_name
333
+ in self.hover_data_for_traces_with_different_hover_for_last_point
334
+ ):
335
+
336
+ customdata = [
337
+ (
338
+ [df[col][i] for col in self.hover_data[trace_name][CUSTOM_DATA]]
339
+ if i < df.shape[0] - 1
340
+ else [
341
+ df[col][i]
342
+ for col in self.hover_data_for_traces_with_different_hover_for_last_point[
343
+ trace_name
344
+ ][
345
+ CUSTOM_DATA
346
+ ]
347
+ ]
348
+ ) # Use different columns for the last point
349
+ for i in range(df.shape[0])
350
+ ]
351
+ return customdata
352
+
353
+ def _get_trace_name(self, trace_name):
354
+ if self.legend_dict is not None and trace_name in self.legend_dict:
355
+ return self.legend_dict[trace_name]
356
+ return trace_name
357
+
358
+ def _get_x_axis_content(self):
359
+ """Generates tick text and values for the x-axis based on the unique years calculated from
360
+ the DATE_VALID column in the dataframe.
361
+ Returns:
362
+ tuple: A tuple containing tick_text, tick_values and range_x.
363
+ """
364
+ if self.xaxis_tick_text_format == XAxisFormat.YEAR.value:
365
+ df_with_year_column = self.filtered_df.with_columns(
366
+ pl.col(self.x_axis_column)
367
+ .str.strptime(pl.Date, "%Y-%m-%d")
368
+ .dt.year()
369
+ .alias(YEAR)
370
+ )
371
+
372
+ year_list = df_with_year_column[YEAR].unique().to_list()
373
+
374
+ tick_text = [min(year_list) - 1] + year_list + [max(year_list) + 1]
375
+
376
+ tick_values = [date(year, 1, 1) for year in tick_text]
377
+
378
+ range_x = [
379
+ tick_values[0] + relativedelta(months=6),
380
+ tick_values[-1] + relativedelta(months=6),
381
+ ]
382
+
383
+ elif self.xaxis_tick_text_format == XAxisFormat.MONTH_YEAR.value:
384
+ df = self.filtered_df.with_columns(
385
+ pl.col(self.x_axis_column)
386
+ .str.strptime(pl.Date, "%Y-%m-%d")
387
+ .alias(self.x_axis_column)
388
+ ).sort(self.x_axis_column)
389
+
390
+ start_datetime = datetime(2024, 7, 1).date()
391
+ latest_datetime = df[self.x_axis_column].max()
392
+ tick_text = []
393
+ current = start_datetime
394
+ while current <= latest_datetime:
395
+ tick_text.append(current.strftime("%b %Y"))
396
+ current += relativedelta(months=1)
397
+
398
+ tick_text_length = len(tick_text)
399
+ total_tick_points = int((tick_text_length / 5) * 7)
400
+ additional_tick_points = total_tick_points - tick_text_length
401
+
402
+ last_current_tick_text = datetime.strptime(tick_text[-1], "%b %Y")
403
+
404
+ for x in range(additional_tick_points):
405
+ tick_text = tick_text + [
406
+ (last_current_tick_text + relativedelta(months=x + 1)).strftime(
407
+ "%b %Y"
408
+ )
409
+ ]
410
+
411
+ tick_values = [
412
+ datetime.strptime(month_year, "%b %Y").replace(day=1)
413
+ for month_year in tick_text
414
+ ]
415
+
416
+ range_x = [
417
+ tick_values[0],
418
+ tick_values[-1] + relativedelta(months=1),
419
+ ]
420
+ tick_text = replace_jun_jul_month_abbreviations(tick_text)
421
+ elif self.xaxis_tick_text_format == XAxisFormat.FINANCIAL_QUARTER.value:
422
+ tick_values = [1, 2, 3, 4]
423
+ tick_text = [
424
+ convert_financial_quarter_to_financial_quarter_text(quarter)
425
+ for quarter in tick_values
426
+ ]
427
+
428
+ range_x = [0.5, 4.5]
429
+ else:
430
+ raise ValueError(
431
+ f"Invalid xaxis_tick_text_format: {self.xaxis_tick_text_format}"
432
+ )
433
+ return tick_text, tick_values, range_x
434
+
435
+ def _get_y_axis_range_max(self):
436
+ """Get the y axis range maximum value to ensure there is an axis label greater than the
437
+ maximum y value."""
438
+ largest_number_of_weeks = self.filtered_df[self.y_axis_column].max()
439
+
440
+ y_axis_max = largest_number_of_weeks + (0.3 * largest_number_of_weeks)
441
+ return y_axis_max
442
+
443
+ def _get_df_list_for_time_series(self) -> list[pl.DataFrame]:
444
+ if self.trace_name_column is not None:
445
+ df_list = [
446
+ self.filtered_df.filter(pl.col(self.trace_name_column) == trace_name)
447
+ for trace_name in self.trace_name_list
448
+ ]
449
+ else:
450
+ df_list = [self.filtered_df]
451
+ return df_list
452
+
453
+ def _get_colour_list(self):
454
+ """Returns a list of colours."""
455
+ number_of_traces = len(self.trace_name_list)
456
+ if number_of_traces == 2:
457
+ colour_list = [
458
+ AFAccessibleColours.DARK_BLUE.value,
459
+ AFAccessibleColours.ORANGE.value,
460
+ ] # if 2 lines should use dark blue & orange as have highest contrast ratio
461
+ else:
462
+ colour_list = AFAccessibleColours.CATEGORICAL.value.copy()
463
+ colour_shift_dict = (
464
+ {"default": 0}
465
+ if self.number_of_traces_colour_shift_dict is None
466
+ else self.number_of_traces_colour_shift_dict
467
+ )
468
+
469
+ colour_shift_value = colour_shift_dict.get(
470
+ number_of_traces, colour_shift_dict["default"]
471
+ )
472
+ if isinstance(colour_shift_value, list):
473
+ return colour_shift_value # list of colours
474
+ while colour_shift_value > 0:
475
+ colour_list.append(colour_list.pop(0))
476
+ colour_shift_value -= 1
477
+ return colour_list
@@ -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