From 5ade6be9a505fb4f34fd6ddcc2e88207acaac9c5 Mon Sep 17 00:00:00 2001 From: Andy Sellick Date: Fri, 18 Oct 2024 16:25:55 +0100 Subject: [PATCH 1/6] Add minimal option to chart component --- .../components/_chart.scss | 14 +- .../components/_chart.html.erb | 146 ++++++++++-------- .../components/docs/chart.yml | 54 ++++++- spec/components/chart_spec.rb | 26 +++- 4 files changed, 171 insertions(+), 69 deletions(-) diff --git a/app/assets/stylesheets/govuk_publishing_components/components/_chart.scss b/app/assets/stylesheets/govuk_publishing_components/components/_chart.scss index 16d6c4aa73..d846b613fd 100644 --- a/app/assets/stylesheets/govuk_publishing_components/components/_chart.scss +++ b/app/assets/stylesheets/govuk_publishing_components/components/_chart.scss @@ -1,6 +1,8 @@ @import "govuk_publishing_components/individual_component_support"; .gem-c-chart { + position: relative; + // slight hack to hide the table automatically added by the charts JS // not needed as we already output the table manually in the component svg + div:has(table) { @@ -39,6 +41,14 @@ } } -.gem-c-chart__accessibility-message { - @include govuk-visually-hidden; +.gem-c-chart__minimal-link { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + &:focus { + background: transparent; // overrides govuk-link background, which obscures the graph + } } diff --git a/app/views/govuk_publishing_components/components/_chart.html.erb b/app/views/govuk_publishing_components/components/_chart.html.erb index 158789798a..05ecf38284 100644 --- a/app/views/govuk_publishing_components/components/_chart.html.erb +++ b/app/views/govuk_publishing_components/components/_chart.html.erb @@ -12,7 +12,9 @@ rows ||= [] keys ||= [] chart_overview ||= nil - hide_legend ||= false + minimal ||= false + minimal_link ||= nil + hide_legend ||= minimal link ||= false chart_id = "chart-id-#{SecureRandom.hex(4)}" @@ -22,6 +24,7 @@ component_helper = GovukPublishingComponents::Presenters::ComponentWrapperHelper.new(local_assigns) component_helper.add_class("gem-c-chart") component_helper.add_class(shared_helper.get_margin_bottom) + component_helper.add_class("gem-c-chart--minimal") if minimal require "chartkick" Chartkick.options[:html] = '
' @@ -30,13 +33,22 @@ font_19 = { color: '#000', fontName: 'GDS Transport', fontSize: '19', italic: false } legend = 'none' legend = { position: 'top', textStyle: font_16 } unless hide_legend + pointSize = 10 + pointSize = 0 if minimal + enableInteractivity = true + enableInteractivity = false if minimal + textPosition = nil + textPosition = 'none' if minimal + valid_minimal = true + valid_minimal = false if minimal && !minimal_link # minimal version must include a link chart_library_options = { chartArea: { width: '80%', height: '60%' }, crosshair: { orientation: 'vertical', trigger: 'both', color: '#ccc' }, curveType: 'none', + enableInteractivity: enableInteractivity, legend: legend, - pointSize: 10, + pointSize: pointSize, height: 400, tooltip: { isHtml: true }, hAxis: { @@ -44,12 +56,14 @@ format: 'd MMM Y', # https://developers.google.com/chart/interactive/docs/reference#dateformatter title: h_axis_title, titleTextStyle: font_19, + textPosition: textPosition, }, vAxis: { format: '#,###,###', textStyle: font_16, title: v_axis_title, titleTextStyle: font_19, + textPosition: textPosition, }, } @@ -63,10 +77,10 @@ end end %> -<% if rows.length > 0 && keys.length > 0 %> +<% if rows.length > 0 && keys.length > 0 && valid_minimal %> <%= javascript_include_tag "https://www.gstatic.com/charts/loader.js" %> <%= tag.div(**component_helper.all_attributes) do %> - <% if chart_heading %> + <% if chart_heading && !minimal %> <%= render "govuk_publishing_components/components/heading", { text: chart_heading, heading_level: chart_heading_level, @@ -75,77 +89,85 @@ <% end %>
-
- <%= t("components.chart.accessibility_html", table_id: table_id) %> - <%= content_tag :span, chart_overview, class: "gem-c-chart__overview" if chart_overview %> +
+ <%= content_tag :div, chart_overview, class: "gem-c-chart__a11y-note-1" if chart_overview %> + <%= content_tag :div, t("components.chart.accessibility_html", table_id: table_id), class: "gem-c-chart__a11y-note-2" unless minimal %>
+ <%= line_chart(chart_format_data, library: chart_library_options) %>
-
- <%= render("govuk_publishing_components/components/details", - title: t("components.chart.table_dropdown") - ) do %> -
- - <% if table_direction == "horizontal" %> - - - - <% keys.each do |key| %> - - <% end %> - - - - <% rows.each do |row| %> + <% unless minimal %> +
+ <%= render("govuk_publishing_components/components/details", + title: t("components.chart.table_dropdown") + ) do %> +
+
- <%= key %> -
+ <% if table_direction == "horizontal" %> + - - <% row[:values].each do |value| %> - + + <% keys.each do |key| %> + <% end %> - <% end %> - - <% else %> - - - + + <% rows.each do |row| %> - + + + <% row[:values].each do |value| %> + + <% end %> + <% end %> - - - - <% keys.each_with_index do |key, index| %> - - + + <% else %> + + + <% rows.each do |row| %> - + <% end %> - <% end %> - - <% end %> -
<%= row[:label] %> - <%= number_with_delimiter value %> - + <%= key %> +
- <%= row[:label] %> -
<%= row[:label] %> + <%= number_with_delimiter value %> +
- <%= key %> -
- <%= number_with_delimiter row[:values][index] %> - + <%= row[:label] %> +
-
- <% end %> -
+ + + <% keys.each_with_index do |key, index| %> + + + <%= key %> + + <% rows.each do |row| %> + + <%= number_with_delimiter row[:values][index] %> + + <% end %> + + <% end %> + + <% end %> + +
+ <% end %> + - <% if link %> -

- <%= link_to "Download chart data", link, class: "govuk-link" %> -

+ <% if link %> +

+ <%= link_to "Download chart data", link, class: "govuk-link" %> +

+ <% end %> + <% end %> + <% if minimal %> + <%= link_to(minimal_link, class: "govuk-link gem-c-chart__minimal-link") do %> + <%= chart_heading %> + <% end %> <% end %> <% end %> <% end %> diff --git a/app/views/govuk_publishing_components/components/docs/chart.yml b/app/views/govuk_publishing_components/components/docs/chart.yml index 60d16bf081..67176ec3aa 100644 --- a/app/views/govuk_publishing_components/components/docs/chart.yml +++ b/app/views/govuk_publishing_components/components/docs/chart.yml @@ -6,7 +6,7 @@ body: | The chart relies upon chartkick and renders using JavaScript, so the table is provided as a fallback for a lack of JavaScript, an accessible view on the data for screenreaders, and a simple view of the raw data for all users. - The `overview` option can be used to provide an explanation for screen reader users of what the graph shows. + The `chart_overview` option can be used to provide an explanation for screen reader users of what the graph shows. accessibility_criteria: | Charts must: @@ -20,7 +20,7 @@ examples: chart_heading: Page views chart h_axis_title: Day v_axis_title: Views - overview: This chart shows page views for January 2015. + chart_overview: This chart shows page views for January 2015. hide_legend: true keys: - "2015-01-01" @@ -52,6 +52,7 @@ examples: chart_heading: Page views chart h_axis_title: Day v_axis_title: Views + chart_overview: This chart shows page views for January in different years. keys: - 1st - 2nd @@ -107,6 +108,7 @@ examples: table_direction: vertical h_axis_title: Day v_axis_title: Views + chart_overview: This chart shows page views for January in different years. keys: - 1st - 2nd @@ -141,6 +143,7 @@ examples: chart_heading_level: 4 h_axis_title: Day v_axis_title: Views + chart_overview: This chart shows page views for January in different years. keys: - 1st - 2nd @@ -175,6 +178,7 @@ examples: h_axis_title: Day v_axis_title: Views link: 'https://www.gov.uk' + chart_overview: This chart shows page views for January in different years. keys: - 1st - 2nd @@ -209,6 +213,52 @@ examples: h_axis_title: Day v_axis_title: Views margin_bottom: 9 + chart_overview: This chart shows page views for January in different years. + keys: + - 1st + - 2nd + - 3rd + - 4th + - 5th + - 6th + - 7th + rows: + - label: January 2015 + values: + - 5 + - 119 + - 74 + - 117 + - 33 + - 89 + - 79 + - label: January 2016 + values: + - 3 + - 8 + - 37 + - 82 + - 118 + - 85 + - 80 + minimal_version: + description: | + The minimal version presents a simplified version of the chart and should only be used where the user is then directed to a more detailed view of the data. Specifically, minimal mode: + + - turns the chart into a link that should point to a page with a full version of the chart with all data + - hides the chart heading, but uses the text as the text for the link + - removes the legend and X and Y axis values + - removes the data table and link to the data (if supplied) beneath the chart + - removes the part of the visually hidden accessibility message that links to the table + - removes the ability to interact with the chart (no hover popups or clicks) + - sets the size of the points on the chart to zero + data: + chart_heading: Page views + h_axis_title: Day + v_axis_title: Views + minimal: true + minimal_link: "https://www.gov.uk" + chart_overview: This is a graph of views per day keys: - 1st - 2nd diff --git a/spec/components/chart_spec.rb b/spec/components/chart_spec.rb index 48427ef55e..03ba7bfd13 100644 --- a/spec/components/chart_spec.rb +++ b/spec/components/chart_spec.rb @@ -85,10 +85,11 @@ def component_name end it "can include an overview" do - text = "This chart shows a gradual decline in the numbers of hedgehogs using social media since 2008." - data[:chart_overview] = text + overview = "This chart shows a gradual decline in the numbers of hedgehogs using social media since 2008." + data[:chart_overview] = overview render_component(data) - assert_select ".gem-c-chart__overview", text: + assert_select ".gem-c-chart__a11y-note-1", text: overview + assert_select ".gem-c-chart__a11y-note-2", text: "This chart is a visual representation of the data available in the table." end it "can include a download link" do @@ -104,4 +105,23 @@ def component_name assert_select '.gem-c-chart.govuk-\!-margin-bottom-0' end + + it "renders a minimal version" do + data[:minimal] = true + data[:link] = "https://should.not.be.shown" + data[:minimal_link] = "https://www.gov.uk" + data[:chart_overview] = "This is a chart showing a rise in sea levels in the last ten years" + render_component(data) + + assert_select ".gem-c-chart.gem-c-chart--minimal" + assert_select '.gem-c-chart .govuk-link[href="https://should.not.be.shown"]', false + assert_select '.gem-c-chart .gem-c-chart__minimal-link[href="https://www.gov.uk"]' + assert_select ".gem-c-chart__a11y-note-1", text: "This is a chart showing a rise in sea levels in the last ten years" + assert_select ".gem-c-chart__a11y-note-2", false + end + + it "does not render a minimal version if a link is not supplied" do + data[:minimal] = true + assert_empty render_component(data) + end end From 9f5ed5bd20f6b72fb9507ba46562b537d0610f83 Mon Sep 17 00:00:00 2001 From: Andy Sellick Date: Fri, 18 Oct 2024 16:42:36 +0100 Subject: [PATCH 2/6] Add option for chart height - currently hard coded as the chart isn't responsive - quite difficult to write a test for this as it's applied as an inline style --- .../components/_chart.html.erb | 3 +- .../components/docs/chart.yml | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/app/views/govuk_publishing_components/components/_chart.html.erb b/app/views/govuk_publishing_components/components/_chart.html.erb index 05ecf38284..09101aa3d4 100644 --- a/app/views/govuk_publishing_components/components/_chart.html.erb +++ b/app/views/govuk_publishing_components/components/_chart.html.erb @@ -16,6 +16,7 @@ minimal_link ||= nil hide_legend ||= minimal link ||= false + height ||= 400 chart_id = "chart-id-#{SecureRandom.hex(4)}" table_id = "table-id-#{SecureRandom.hex(4)}" @@ -49,7 +50,7 @@ enableInteractivity: enableInteractivity, legend: legend, pointSize: pointSize, - height: 400, + height: height, tooltip: { isHtml: true }, hAxis: { textStyle: font_16, diff --git a/app/views/govuk_publishing_components/components/docs/chart.yml b/app/views/govuk_publishing_components/components/docs/chart.yml index 67176ec3aa..f34b4d601b 100644 --- a/app/views/govuk_publishing_components/components/docs/chart.yml +++ b/app/views/govuk_publishing_components/components/docs/chart.yml @@ -286,3 +286,43 @@ examples: - 118 - 85 - 80 + with_a_different_height: + description: | + Sets a height in pixels for the chart, defaulting to `400`. This is useful where the chart might appear in a narrow column and the default height would be too tall. + + The component is not currently responsive. + data: + chart_heading: Page views + h_axis_title: Day + v_axis_title: Views + minimal: true + minimal_link: "https://www.gov.uk" + chart_overview: This is a graph of views per day + height: 200 + keys: + - 1st + - 2nd + - 3rd + - 4th + - 5th + - 6th + - 7th + rows: + - label: January 2015 + values: + - 5 + - 119 + - 74 + - 117 + - 33 + - 89 + - 79 + - label: January 2016 + values: + - 3 + - 8 + - 37 + - 82 + - 118 + - 85 + - 80 From f8cc2ed8dcaca177e59cd2e09e8660f329d98846 Mon Sep 17 00:00:00 2001 From: Andy Sellick Date: Mon, 21 Oct 2024 11:56:16 +0100 Subject: [PATCH 3/6] Make Google charts script load only once - use an Openstruct to persist a value between multiple instances of the component to make the external script for Google charts only be called once - perhaps unsurprisingly this doesn't make any difference to the page weight in Chrome, presumably because Chrome is smart enough to realise it's already loaded that script if there's more than one chart - however it feels tidier to limit it this way, particularly for browsers that may not be so clever --- .../components/_chart.html.erb | 4 +++- spec/components/chart_spec.rb | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/views/govuk_publishing_components/components/_chart.html.erb b/app/views/govuk_publishing_components/components/_chart.html.erb index 09101aa3d4..81112c6e28 100644 --- a/app/views/govuk_publishing_components/components/_chart.html.erb +++ b/app/views/govuk_publishing_components/components/_chart.html.erb @@ -20,6 +20,8 @@ chart_id = "chart-id-#{SecureRandom.hex(4)}" table_id = "table-id-#{SecureRandom.hex(4)}" + @external_script ||= OpenStruct.new(loaded: 0) + @external_script[:loaded] += 1 shared_helper = GovukPublishingComponents::Presenters::SharedHelper.new(local_assigns) component_helper = GovukPublishingComponents::Presenters::ComponentWrapperHelper.new(local_assigns) @@ -79,7 +81,7 @@ end %> <% if rows.length > 0 && keys.length > 0 && valid_minimal %> - <%= javascript_include_tag "https://www.gstatic.com/charts/loader.js" %> + <%= javascript_include_tag "https://www.gstatic.com/charts/loader.js" if @external_script[:loaded] == 1 %> <%= tag.div(**component_helper.all_attributes) do %> <% if chart_heading && !minimal %> <%= render "govuk_publishing_components/components/heading", { diff --git a/spec/components/chart_spec.rb b/spec/components/chart_spec.rb index 03ba7bfd13..34d14937c8 100644 --- a/spec/components/chart_spec.rb +++ b/spec/components/chart_spec.rb @@ -124,4 +124,12 @@ def component_name data[:minimal] = true assert_empty render_component(data) end + + it "only calls an external script once" do + render_component(data) + data[:classes] = "" # need to 'reset' this otherwise it carries from the first component and breaks shared_helper + render_component(data) + + assert_select 'script[src="https://www.gstatic.com/charts/loader.js"]', count: 1 + end end From 357afbd4c7f4e1f22326cc36481ed02f0816ea89 Mon Sep 17 00:00:00 2001 From: Andy Sellick Date: Tue, 22 Oct 2024 11:21:18 +0100 Subject: [PATCH 4/6] Use Rails validation to check minimal configuration --- .../govuk_publishing_components/components/_chart.html.erb | 7 ++++--- spec/components/chart_spec.rb | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/views/govuk_publishing_components/components/_chart.html.erb b/app/views/govuk_publishing_components/components/_chart.html.erb index 81112c6e28..d58adbe3e1 100644 --- a/app/views/govuk_publishing_components/components/_chart.html.erb +++ b/app/views/govuk_publishing_components/components/_chart.html.erb @@ -42,8 +42,9 @@ enableInteractivity = false if minimal textPosition = nil textPosition = 'none' if minimal - valid_minimal = true - valid_minimal = false if minimal && !minimal_link # minimal version must include a link + if minimal && !minimal_link + raise ArgumentError, "Minimal version must include a link" + end chart_library_options = { chartArea: { width: '80%', height: '60%' }, @@ -80,7 +81,7 @@ end end %> -<% if rows.length > 0 && keys.length > 0 && valid_minimal %> +<% if rows.length > 0 && keys.length > 0 %> <%= javascript_include_tag "https://www.gstatic.com/charts/loader.js" if @external_script[:loaded] == 1 %> <%= tag.div(**component_helper.all_attributes) do %> <% if chart_heading && !minimal %> diff --git a/spec/components/chart_spec.rb b/spec/components/chart_spec.rb index 34d14937c8..3ca62ebc13 100644 --- a/spec/components/chart_spec.rb +++ b/spec/components/chart_spec.rb @@ -122,7 +122,9 @@ def component_name it "does not render a minimal version if a link is not supplied" do data[:minimal] = true - assert_empty render_component(data) + expect { + render_component(data) + }.to raise_error("Minimal version must include a link") end it "only calls an external script once" do From cc9ccf53dcd085b2ec6c2739ba40084eb5e8b265 Mon Sep 17 00:00:00 2001 From: Andy Sellick Date: Tue, 22 Oct 2024 11:51:30 +0100 Subject: [PATCH 5/6] Improve link accessibility - improve focus state around link to have a complete border and be more visible - use aria hidden on the graph when in minimal mode --- .../components/_chart.scss | 13 +++++++++++++ .../components/_chart.html.erb | 15 +++++++++------ spec/components/chart_spec.rb | 4 ++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/govuk_publishing_components/components/_chart.scss b/app/assets/stylesheets/govuk_publishing_components/components/_chart.scss index d846b613fd..29ebcda790 100644 --- a/app/assets/stylesheets/govuk_publishing_components/components/_chart.scss +++ b/app/assets/stylesheets/govuk_publishing_components/components/_chart.scss @@ -50,5 +50,18 @@ &:focus { background: transparent; // overrides govuk-link background, which obscures the graph + + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-top: solid 1px $govuk-focus-colour; + border-left: solid 3px $govuk-focus-colour; + border-right: solid 3px $govuk-focus-colour; + box-sizing: border-box; + } } } diff --git a/app/views/govuk_publishing_components/components/_chart.html.erb b/app/views/govuk_publishing_components/components/_chart.html.erb index d58adbe3e1..468f7eff09 100644 --- a/app/views/govuk_publishing_components/components/_chart.html.erb +++ b/app/views/govuk_publishing_components/components/_chart.html.erb @@ -92,14 +92,17 @@ } %> <% end %> -
-
- <%= content_tag :div, chart_overview, class: "gem-c-chart__a11y-note-1" if chart_overview %> - <%= content_tag :div, t("components.chart.accessibility_html", table_id: table_id), class: "gem-c-chart__a11y-note-2" unless minimal %> -
+ <% aria_attributes = { hidden: true } if minimal %> + <%= content_tag :div, id: chart_id, class: "gem-c-chart__chart", aria: aria_attributes do %> + <% unless minimal %> +
+ <%= content_tag :div, chart_overview, class: "gem-c-chart__a11y-note-1" if chart_overview %> + <%= content_tag :div, t("components.chart.accessibility_html", table_id: table_id), class: "gem-c-chart__a11y-note-2" %> +
+ <% end %> <%= line_chart(chart_format_data, library: chart_library_options) %> -
+ <% end %> <% unless minimal %>
diff --git a/spec/components/chart_spec.rb b/spec/components/chart_spec.rb index 3ca62ebc13..edca2521b2 100644 --- a/spec/components/chart_spec.rb +++ b/spec/components/chart_spec.rb @@ -114,10 +114,10 @@ def component_name render_component(data) assert_select ".gem-c-chart.gem-c-chart--minimal" + assert_select ".gem-c-chart__chart[aria-hidden='true']" assert_select '.gem-c-chart .govuk-link[href="https://should.not.be.shown"]', false assert_select '.gem-c-chart .gem-c-chart__minimal-link[href="https://www.gov.uk"]' - assert_select ".gem-c-chart__a11y-note-1", text: "This is a chart showing a rise in sea levels in the last ten years" - assert_select ".gem-c-chart__a11y-note-2", false + assert_select ".gem-c-chart__chart .govuk-visually-hidden", false end it "does not render a minimal version if a link is not supplied" do From e4417df2732e0e0479db0e7d2e2982a1fc587c33 Mon Sep 17 00:00:00 2001 From: Andy Sellick Date: Tue, 22 Oct 2024 13:28:54 +0100 Subject: [PATCH 6/6] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a20333975..8d43a4c7ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ## Unreleased +* Add new chart component options ([PR #4318](https://github.com/alphagov/govuk_publishing_components/pull/4318)) * Add shared helper and component wrapper helper to govspeak component ([PR #4325](https://github.com/alphagov/govuk_publishing_components/pull/4325)) ## 44.4.2