Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Charts spike WIP #95

Merged
merged 6 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 11 additions & 36 deletions cms/datavis/models/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ class Chart(Visualisation):

show_legend = models.BooleanField(verbose_name=_("show legend?"), default=True) # type: ignore[var-annotated]
show_value_labels = models.BooleanField( # type: ignore[var-annotated]
verbose_name=_("show value labels?"), default=False
verbose_name=_("show value labels?"),
default=False,
help_text=_("For cluster charts with more than 2 series, the data labels will be hidden."),
)
theme = models.CharField( # type: ignore[var-annotated]
verbose_name=_("theme"),
Expand Down Expand Up @@ -95,8 +97,8 @@ def media(self) -> Media:

def get_context(self, request: Optional["HttpRequest"] = None, **kwargs: Any) -> dict[str, Any]:
config = self.get_component_config(self.primary_data_source.headers, self.primary_data_source.rows)
# theme = self.theme
return super().get_context(request, config=config, **kwargs)
annotations_values = self.get_annotations_values()
return super().get_context(request, config=config, annotations_values=annotations_values, **kwargs)

general_panels: ClassVar[Sequence["Panel"]] = [
FieldPanel("name"),
Expand Down Expand Up @@ -200,13 +202,6 @@ def get_component_config(self, headers: Sequence[str], rows: Sequence[list[str |
},
"xAxis": self.get_x_axis_config(headers, rows),
"yAxis": self.get_y_axis_config(headers, rows),
"navigation": {
"enabled": False,
},
"annotations": self.get_annotations_config(),
"credits": {
"enabled": False,
},
"series": self.get_series_data(headers, rows),
}

Expand Down Expand Up @@ -254,38 +249,18 @@ def get_y_axis_config(
config["max"] = self.y_max
return config

def get_annotations_config(self) -> list[dict[str, Any]]:
config: list[dict[str, Any]] = []
annotation_group: dict[str, Any] = {
"draggable": "",
"labelOptions": {
"backgroundColor": "rgba(255,255,255,0.5)",
"verticalAlign": "top",
},
}
# TODO: It's likely we'll want to support a few different style
# options for annotations, in which case, we'd split annotations
# into multiple groups, each with a separate 'labelOptions' value
# to control the styling.
group_labels: list[dict[str, Any]] = []
def get_annotations_values(self) -> list[dict[str, Any]]:
annotation_values: list[dict[str, Any]] = []
for item in self.annotations.raw_data: # pylint: disable=no-member
value = item["value"]
group_labels.append(
annotation_values.append(
{
"text": value["label"],
"point": {
"x": numberfy(value["x_position"]),
"y": numberfy(value["y_position"]),
"xAxis": 0,
"yAxis": 0,
},
"xValue": numberfy(value["x_position"]),
"yValue": numberfy(value["y_position"]),
}
)

if group_labels:
annotation_group["labels"] = group_labels
config.append(annotation_group)
return config
return annotation_values

def get_series_data(self, headers: Sequence[str], rows: Sequence[list[str | int | float]]) -> list[dict[str, Any]]:
series = []
Expand Down
37 changes: 37 additions & 0 deletions cms/jinja2/templates/datavis/_macro.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% macro onsChart(params) %}
<div data-highcharts-base-chart data-highcharts-type="{{ params.chartType }}" data-highcharts-theme="{{ params.theme }}" data-highcharts-title="{{ params.title }}" {% if params.use_stacked_layout %}data-highcharts-use-stacked-layout{% endif %}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: What's the motivation behind this change? Is it to have one macro for any charts at all? Or just one macro for highcharts for now? I'm more asking about the naming, I guess, because it's named agnostically, but contains highcharts related data attributes throughout.

Another possibility is that the role of the highcharts template is to convert our bespoke internal data structure (annotations here, data there, metadata and titles from the page…) into a Highcharts-specific format, but that too is being done in the macro file.

What am I missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the design system component is built, everything in the _macro.njk will live in the design system repo, and our html template will call that. The naming is just how the design system names all their macro files, with context coming from the directory structure, e.g. https://github.com/ONSdigital/design-system/tree/main/src/components/accordion

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, I could have explained that better by identifying the subject. I was asking about the onsChart macro name, not the _macro.njk file name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see - I think it will be down to the DS team to decide on the final name for that - but you're right it might be onsHighchart or similar.

<figure id="{{ params.domId }}">
{# Heading levels will need to be configurable depending on page surroundings #}
<h3>{{ params.title }}</h3>
<h4>{{ params.subtitle }}</h4>
<div data-highcharts-chart data-highcharts-id="{{ params.uuid }}"></div>
{% if params.caption %}
<figcaption>{{ params.caption }}</figcaption>
{% endif %}
</figure>

<!-- Set scripts to pass the config and annotations values as json to the javascript -->
{% set config_scriptid = ["config--", params.uuid] | join %}
{{ params.config|json_script(config_scriptid) }}

{% if params.annotations_values %}
{% set annotations_values_scriptid = ["annotations-values--", params.uuid] | join %}
{{ params.annotations_values|json_script(annotations_values_scriptid) }}
{% endif %}

{% if params.download_title and (params.download_image or params.download_csv or params.download_excel) %}
{% if params.download_title %}<h4>{{ params.download_title }}</h4>{% endif %}
<ol>
{% if params.download_excel %}
<li><a href="{{ params.download_excel }}">Excel spreadsheet (XLSX format{% if params.download_excel_size %}, {{ params.download_excel_size }}{% endif %})</a></li>
{% endif %}
{% if params.download_csv %}
<li><a href="{{ params.download_csv }}">Simple text file (CSV format{% if params.download_csv_size %}, {{ params.download_csv_size }}{% endif %})</a></li>
{% endif %}
{% if params.download_image %}
<li><a href="{{ params.download_image }}">Image (PNG format{% if params.download_image_size %}, {{ params.download_image_size }}{% endif %})</a></li>
{% endif %}
</ol>
{% endif %}
</div>
{% endmacro %}
43 changes: 28 additions & 15 deletions cms/jinja2/templates/datavis/base_highcharts_chart.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
<div data-highcharts-base-chart data-highcharts-type="{{ object.highcharts_chart_type }}" data-highcharts-theme="{{ object.theme }}" data-highcharts-title="{{ object.title }}">
{% if not media_already_loaded and object.media %}
{{ object.media }}
{% endif %}
<figure class="highcharts-figure" id="chart-figure--{{ object.uuid }}">
<h2>{{ object.title }}</h2>
<h3>{{ object.subtitle }}</h3>
<div data-highcharts-chart data-highcharts-id="{{ object.uuid }}"></div>
{% if object.caption %}
<figcaption>{{ object.caption|richtext }}</figcaption>
{% endif %}
</figure>
{% set scriptid = ["config--", object.uuid] | join %}
{{ config|json_script(scriptid) }}
</div>
{% from "templates/datavis/_macro.njk" import onsChart %}

{% if not media_already_loaded and object.media %}
{{ object.media }}
{% endif %}

{# fmt:off #}
{{
onsChart({
"chartType": object.highcharts_chart_type,
"theme": object.theme,
"title": object.title,
"subtitle": object.subtitle,
"uuid": object.uuid,
"caption": object.caption|richtext,
"use_stacked_layout": object.use_stacked_layout,
"config": config,
"annotations_values": annotations_values,
"download_title": "Figure 1 data",
"download_image": "xyz",
"download_image_size": "18KB",
"download_csv": "xyz",
"download_csv_size": "25KB",
"download_excel": "xyz",
"download_excel_size": "25KB",
})
}}
{# fmt:on #}
37 changes: 37 additions & 0 deletions cms/static_src/javascript/components/bar-chart-plot-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import ChartConstants from './chart-constants';

class BarChartPlotOptions {
static plotOptions() {
return this.plotOptions;
}

constructor() {
const constants = ChartConstants.constants();
this.plotOptions = {
bar: {
// Set the width of the bars to be 30px
// The spacing is worked out in the highcharts-base-chart.js file
pointWidth: 30, // Fixed bar height
pointPadding: 0,
groupPadding: 0,
borderWidth: 0,
borderRadius: 0,
// Set the data labels to be enabled and positioned outside the bars
// We can add custom formatting on each chart to move the labels inside the bars if the bar is wide enough
dataLabels: {
enabled: true,
inside: false,
style: {
textOutline: 'none',
// there is no semibold font weight available in the design system fonts, so we use 400 instead
fontWeight: '400',
color: constants.labelColor,
fontSize: constants.mobileFontSize,
},
},
},
};
}
}

export default BarChartPlotOptions;
30 changes: 30 additions & 0 deletions cms/static_src/javascript/components/chart-constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class ChartConstants {
static constants() {
const constants = {
primaryTheme: [
'#206095',
'#27a0cc',
'#003c57',
'#118c7b',
'#a8bd3a',
'#871a5b',
'#f66068',
'#746cb1',
'#22d0b6',
],
// Alternate theme colours from https://service-manual.ons.gov.uk/data-visualisation/colours/using-colours-in-charts
alternateTheme: ['#206095', '#27A0CC', '#871A5B', '#A8BD3A', '#F66068'],
labelColor: '#414042',
axisLabelColor: '#707071',
gridLineColor: '#d9d9d9',
zeroLineColor: '#b3b3b3',
// Responsive font sizes
mobileFontSize: '0.875rem',
desktopFontSize: '1rem',
};

return constants;
}
}

export default ChartConstants;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class ColumnChartPlotOptions {
static plotOptions() {
return this.plotOptions;
}

constructor() {
this.plotOptions = {
column: {
groupPadding: 0.2,
borderWidth: 0,
borderRadius: 0,
},
};
}
}

export default ColumnChartPlotOptions;
Loading