From bf111f0f4df8f7b901ff73e0214625522786b445 Mon Sep 17 00:00:00 2001 From: jer3k <99355997+jer3k@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:58:56 -0700 Subject: [PATCH 1/5] accessible graph --- backend/src/v1/services/report-service.ts | 9 +++++--- .../report-template-emp-data-summary.html | 5 ++--- .../src/templates/report.script.js | 21 ++++++++++++++++--- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/backend/src/v1/services/report-service.ts b/backend/src/v1/services/report-service.ts index 5dadb6e58..47fd9b2a6 100644 --- a/backend/src/v1/services/report-service.ts +++ b/backend/src/v1/services/report-service.ts @@ -161,7 +161,8 @@ const reportServicePrivate = { This method converts the raw data that could be used to draw a bar chart of gender pay gaps into a text summary of the data. The text summary will be of a form similar to: - "In this company, women’s average hourly wages + "This graph describes that in this + company, women’s average hourly wages are 9% less than men while non-binary people’s average hourly wages are 7% less than men. For every dollar a man earns on average, women earn 91 cents on @@ -220,7 +221,7 @@ const reportServicePrivate = { }); const result = ` - In this organization ${typeASummaries.join(' and ')}. + This graph describes that in this organization ${typeASummaries.join(' and ')}. For every dollar ${refChartDataRecord.genderChartInfo.extendedLabel.toLowerCase()} earn in ${statisticName} ${measureName}, ${typeBSummaries.join(' and ')} in ${statisticName} ${measureName}.`; return result; @@ -1172,7 +1173,9 @@ const reportService = { }, data: { report_status: enumReportStatus.Published, - create_date: existing_published_report?.create_date || report_to_publish.create_date + create_date: + existing_published_report?.create_date || + report_to_publish.create_date, }, }); }); diff --git a/doc-gen-service/src/templates/report-template-emp-data-summary.html b/doc-gen-service/src/templates/report-template-emp-data-summary.html index 21e4bc8cd..98db04959 100644 --- a/doc-gen-service/src/templates/report-template-emp-data-summary.html +++ b/doc-gen-service/src/templates/report-template-emp-data-summary.html @@ -178,7 +178,7 @@

Difference as compared to reference group (<%= referenceGenderCategory %>)
- + <% for(var i=0; i < tableData.meanOvertimeHoursGap.length; i++) { %>
@@ -212,8 +212,7 @@

Difference as compared to reference group (<%= referenceGenderCategory %>)
- +
diff --git a/doc-gen-service/src/templates/report.script.js b/doc-gen-service/src/templates/report.script.js index d7b8d737e..e258859ab 100644 --- a/doc-gen-service/src/templates/report.script.js +++ b/doc-gen-service/src/templates/report.script.js @@ -54,13 +54,20 @@ function percentFilledHorizBarChart(data, options = {}) { // Create a value format. const format = (d) => `${x.tickFormat(1, options.numberFormat)(d)}%`; + // Create accessibility text for the visually impaired + const ariaLabel = data + .map((d) => `${format(d.value)} of ${d.genderChartInfo.label}.`) + .join(' '); + // Create the SVG container. const svg = d3 .create('svg') .attr('width', width) .attr('height', height) .attr('viewBox', [0, 0, width, height]) - .attr('style', `max-width: 100%; height: auto; font: ${valueFont};`); + .attr('style', `max-width: 100%; height: auto; font: ${valueFont};`) + .attr('role', 'img') + .attr('aria-label', ariaLabel); const color = (i) => colors[i]; @@ -216,13 +223,18 @@ function horizontalStackedBarChart(data, numberFormat = '1.0f') { // A function to format the value in the tooltip. const formatValue = (x) => (isNaN(x) ? 'N/A' : x.toLocaleString('en')); + // Create accessibility text for the visually impaired + const ariaLabel = data.map((d) => label(d.genderChartInfo.code)).join(' '); + // Create the SVG container. const svg = d3 .create('svg') .attr('width', width) .attr('height', height) .attr('viewBox', [0, 0, width, height]) - .attr('style', 'max-width: 100%; height: auto;'); + .attr('style', 'max-width: 100%; height: auto;') + .attr('role', 'img') + .attr('aria-label', ariaLabel); const barGroup = svg.append('g').append('g').selectAll().data(stacks); @@ -321,6 +333,7 @@ function horizontalBarChart(data, numberFormat = '$0.2f') { // Create the SVG container. const svg = d3 .create('svg') + .attr('role', 'img') .attr('width', width) .attr('height', height) .attr('viewBox', [0, 0, width, height]) @@ -411,7 +424,9 @@ function createLegend(data, options = {}) { .attr('width', options.width) .attr('height', height) .attr('viewBox', [0, 0, options.width, height]) - .attr('style', 'max-width: 100%; height: auto; height: intrinsic;'); + .attr('style', 'max-width: 100%; height: auto; height: intrinsic;') + .attr('role', 'img') + .attr('aria-label', 'Legend'); svg .append('g') From 76a34cea8155144a423ff14b7c6aed47fafcac23 Mon Sep 17 00:00:00 2001 From: jer3k <99355997+jer3k@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:47:40 -0700 Subject: [PATCH 2/5] add ariaLabel to percent graphs --- backend/src/v1/services/report-service.ts | 39 +++++++++++++++++++ .../src/templates/report.script.js | 9 +++-- .../src/v1/services/doc-gen-service.ts | 10 ++++- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/backend/src/v1/services/report-service.ts b/backend/src/v1/services/report-service.ts index 47fd9b2a6..85cd1d8d2 100644 --- a/backend/src/v1/services/report-service.ts +++ b/backend/src/v1/services/report-service.ts @@ -227,6 +227,37 @@ const reportServicePrivate = { return result; }, + /* + This method converts the raw data that could be used to draw a bar chart + of gender pay gaps into a text summary of the data. The text summary + will be of a form similar to: + "This graph describes that in this organization + 4 percent of Men receive overtime pay, 1 percent + of women receive overtime pay, 0% of non-binary + people receive overtime pay, and 0% of prefer + not to say slash unknown receive overtime pay." + @param chartDataRecords: an array of chart data records that are to be summarized + @param measureName: the name of the pay category being summarized + (e.g. "receive overtime pay" or "receive bonus pay") + */ + getPercentSummary(chartDataRecords: ChartDataRecord[], measureName: string) { + if (chartDataRecords.length == 0) { + return null; + } + + const summaries = chartDataRecords.map( + (d) => + `${d.value}% of ${d.genderChartInfo.extendedLabel.toLowerCase()} ${measureName}`, + ); + + let result = ''; + if (chartDataRecords.length == 1) + result = `This graph describes that in this organization ${summaries[0]}.`; + else + result = `This graph describes that in this organization ${summaries.slice(0, -1).join(', ')}, and ${summaries[summaries.length - 1]}.`; + return result; + }, + /* This method converts the raw data that could be used to draw a bar chart of gaps in hours worked between gender groups into a text summary of the @@ -957,6 +988,10 @@ const reportService = { 'bonus pay', false, ), + percentBonusPay: reportServicePrivate.getPercentSummary( + tableData.percentReceivingBonusPay, + 'receive bonus pay', + ), meanOvertimeHoursGap: reportServicePrivate.getHoursGapTextSummary( referenceGenderCode, tableData.meanOvertimeHoursGap, @@ -969,6 +1004,10 @@ const reportService = { 'median', 'overtime hours', ), + percentOvertimePay: reportServicePrivate.getPercentSummary( + tableData.percentReceivingOvertimePay, + 'receive overtime pay', + ), hourlyPayQuartiles: reportServicePrivate.getHourlyPayQuartilesTextSummary( referenceGenderCode, diff --git a/doc-gen-service/src/templates/report.script.js b/doc-gen-service/src/templates/report.script.js index e258859ab..a15cffe18 100644 --- a/doc-gen-service/src/templates/report.script.js +++ b/doc-gen-service/src/templates/report.script.js @@ -20,6 +20,7 @@ function percentFilledHorizBarChart(data, options = {}) { numberFormat: '1.0f', maxX: 100, unfilledColor: '#eeeeee', + ariaLabel: null, }; options = { ...defaultOptions, ...options }; @@ -55,9 +56,9 @@ function percentFilledHorizBarChart(data, options = {}) { const format = (d) => `${x.tickFormat(1, options.numberFormat)(d)}%`; // Create accessibility text for the visually impaired - const ariaLabel = data - .map((d) => `${format(d.value)} of ${d.genderChartInfo.label}.`) - .join(' '); + // const ariaLabel = data + // .map((d) => `${format(d.value)} of ${d.genderChartInfo.label}.`) + // .join(' '); // Create the SVG container. const svg = d3 @@ -67,7 +68,7 @@ function percentFilledHorizBarChart(data, options = {}) { .attr('viewBox', [0, 0, width, height]) .attr('style', `max-width: 100%; height: auto; font: ${valueFont};`) .attr('role', 'img') - .attr('aria-label', ariaLabel); + .attr('aria-label', options.ariaLabel); const color = (i) => colors[i]; diff --git a/doc-gen-service/src/v1/services/doc-gen-service.ts b/doc-gen-service/src/v1/services/doc-gen-service.ts index f09eda6c0..9b36e2149 100644 --- a/doc-gen-service/src/v1/services/doc-gen-service.ts +++ b/doc-gen-service/src/v1/services/doc-gen-service.ts @@ -842,7 +842,10 @@ async function generateReport( .getElementById('percent-receiving-overtime-pay-chart') ?.appendChild( // @ts-ignore - percentFilledHorizBarChart(chartData.percentReceivingOvertimePay), + percentFilledHorizBarChart(chartData.percentReceivingOvertimePay, { + // @ts-ignore + ariaLabel: reportData.chartSummaryText.percentOvertimePay, + }), ); document.getElementById('mean-bonus-pay-gap-chart')?.appendChild( // @ts-ignore @@ -856,7 +859,10 @@ async function generateReport( .getElementById('percent-receiving-bonus-pay-chart') ?.appendChild( // @ts-ignore - percentFilledHorizBarChart(chartData.percentReceivingBonusPay), + percentFilledHorizBarChart(chartData.percentReceivingBonusPay, { + // @ts-ignore + ariaLabel: reportData.chartSummaryText.percentBonusPay, + }), ); document.getElementById('hourly-pay-quartile-4-chart')?.appendChild( // @ts-ignore From 60b752fc067e323e8b49fc0a122477566bb6083f Mon Sep 17 00:00:00 2001 From: jer3k <99355997+jer3k@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:17:25 -0700 Subject: [PATCH 3/5] fix summaries --- backend/src/v1/services/report-service.ts | 10 +++++----- doc-gen-service/src/templates/report.script.js | 10 +++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/backend/src/v1/services/report-service.ts b/backend/src/v1/services/report-service.ts index 85cd1d8d2..d50d3ba2e 100644 --- a/backend/src/v1/services/report-service.ts +++ b/backend/src/v1/services/report-service.ts @@ -229,7 +229,7 @@ const reportServicePrivate = { /* This method converts the raw data that could be used to draw a bar chart - of gender pay gaps into a text summary of the data. The text summary + of percentages into a text summary of the data. The text summary will be of a form similar to: "This graph describes that in this organization 4 percent of Men receive overtime pay, 1 percent @@ -241,13 +241,13 @@ const reportServicePrivate = { (e.g. "receive overtime pay" or "receive bonus pay") */ getPercentSummary(chartDataRecords: ChartDataRecord[], measureName: string) { - if (chartDataRecords.length == 0) { + if (chartDataRecords?.length == 0) { return null; } const summaries = chartDataRecords.map( (d) => - `${d.value}% of ${d.genderChartInfo.extendedLabel.toLowerCase()} ${measureName}`, + `${Math.min(Math.round(d.value), 100)}% of ${d.genderChartInfo.extendedLabel.toLowerCase()} ${measureName}`, ); let result = ''; @@ -989,7 +989,7 @@ const reportService = { false, ), percentBonusPay: reportServicePrivate.getPercentSummary( - tableData.percentReceivingBonusPay, + chartData.percentReceivingBonusPay, 'receive bonus pay', ), meanOvertimeHoursGap: reportServicePrivate.getHoursGapTextSummary( @@ -1005,7 +1005,7 @@ const reportService = { 'overtime hours', ), percentOvertimePay: reportServicePrivate.getPercentSummary( - tableData.percentReceivingOvertimePay, + chartData.percentReceivingOvertimePay, 'receive overtime pay', ), hourlyPayQuartiles: diff --git a/doc-gen-service/src/templates/report.script.js b/doc-gen-service/src/templates/report.script.js index a15cffe18..adf302ac3 100644 --- a/doc-gen-service/src/templates/report.script.js +++ b/doc-gen-service/src/templates/report.script.js @@ -55,11 +55,6 @@ function percentFilledHorizBarChart(data, options = {}) { // Create a value format. const format = (d) => `${x.tickFormat(1, options.numberFormat)(d)}%`; - // Create accessibility text for the visually impaired - // const ariaLabel = data - // .map((d) => `${format(d.value)} of ${d.genderChartInfo.label}.`) - // .join(' '); - // Create the SVG container. const svg = d3 .create('svg') @@ -67,8 +62,9 @@ function percentFilledHorizBarChart(data, options = {}) { .attr('height', height) .attr('viewBox', [0, 0, width, height]) .attr('style', `max-width: 100%; height: auto; font: ${valueFont};`) - .attr('role', 'img') - .attr('aria-label', options.ariaLabel); + .attr('role', 'img'); + + if (options.ariaLabel) svg.attr('aria-label', options.ariaLabel); const color = (i) => colors[i]; From b4bd93396a348d774622005c68a6ce27a019e839 Mon Sep 17 00:00:00 2001 From: jer3k <99355997+jer3k@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:22:43 -0700 Subject: [PATCH 4/5] Fix misunderstanding of where this phrase occures. --- backend/src/v1/services/report-service.ts | 5 ++--- doc-gen-service/src/templates/report.script.js | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/v1/services/report-service.ts b/backend/src/v1/services/report-service.ts index d50d3ba2e..c15085669 100644 --- a/backend/src/v1/services/report-service.ts +++ b/backend/src/v1/services/report-service.ts @@ -161,8 +161,7 @@ const reportServicePrivate = { This method converts the raw data that could be used to draw a bar chart of gender pay gaps into a text summary of the data. The text summary will be of a form similar to: - "This graph describes that in this - company, women’s average hourly wages + "In this company, women’s average hourly wages are 9% less than men while non-binary people’s average hourly wages are 7% less than men. For every dollar a man earns on average, women earn 91 cents on @@ -221,7 +220,7 @@ const reportServicePrivate = { }); const result = ` - This graph describes that in this organization ${typeASummaries.join(' and ')}. + In this organization ${typeASummaries.join(' and ')}. For every dollar ${refChartDataRecord.genderChartInfo.extendedLabel.toLowerCase()} earn in ${statisticName} ${measureName}, ${typeBSummaries.join(' and ')} in ${statisticName} ${measureName}.`; return result; diff --git a/doc-gen-service/src/templates/report.script.js b/doc-gen-service/src/templates/report.script.js index adf302ac3..7abe9998d 100644 --- a/doc-gen-service/src/templates/report.script.js +++ b/doc-gen-service/src/templates/report.script.js @@ -330,11 +330,12 @@ function horizontalBarChart(data, numberFormat = '$0.2f') { // Create the SVG container. const svg = d3 .create('svg') - .attr('role', 'img') .attr('width', width) .attr('height', height) .attr('viewBox', [0, 0, width, height]) - .attr('style', `max-width: 100%; height: auto; font: ${valueFont};`); + .attr('style', `max-width: 100%; height: auto; font: ${valueFont};`) + .attr('role', 'img') + .attr('aria-label', 'This graph describes that'); const color = (i) => colors[i]; From d4f2599ffb8e7890abcc7bb6c28fbe0915e572cd Mon Sep 17 00:00:00 2001 From: jer3k <99355997+jer3k@users.noreply.github.com> Date: Thu, 18 Apr 2024 09:40:46 -0700 Subject: [PATCH 5/5] test case --- .../src/v1/services/report-service.spec.ts | 73 ++++++++++++++++--- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/backend/src/v1/services/report-service.spec.ts b/backend/src/v1/services/report-service.spec.ts index 9a11ccaf4..c21d35481 100644 --- a/backend/src/v1/services/report-service.spec.ts +++ b/backend/src/v1/services/report-service.spec.ts @@ -509,6 +509,61 @@ describe('getWageGapTextSummary', () => { }); }); +describe('getPercentSummary', () => { + describe('when a valid chartDataRecords array is provided', () => { + it('returns an object containing info about how the gender category should be depicted on charts', () => { + const mockChartData = [ + { + genderChartInfo: { + code: 'M', + label: 'Men', + extendedLabel: 'Men', + color: '#1c3664', + }, + value: 3.9049394221808016, + }, + { + genderChartInfo: { + code: 'F', + label: 'Women', + extendedLabel: 'Women', + color: '#1b75bb', + }, + value: 1.1846344485749691, + }, + { + genderChartInfo: { + code: 'X', + label: 'Non-binary', + extendedLabel: 'Non-binary people', + color: '#00a54f', + }, + value: 0.11838989739542227, + }, + { + genderChartInfo: { + code: 'U', + label: 'Prefer not to say / Unknown', + extendedLabel: 'Prefer not to say / Unknown', + color: '#444444', + }, + value: 0.41911984831853105, + }, + ]; + + const text: string = reportServicePrivate.getPercentSummary( + mockChartData, + 'receive overtime pay', + ); + + expect(text).not.toBeNull(); + expect(text).toContain( + 'This graph describes that in this organization 4% of men receive overtime pay, 1% of women receive overtime pay, 0% of non-binary people receive overtime pay, and 0% of prefer not to say / unknown receive overtime pay.', + ); + }); + }); +}); + describe('getHoursGapTextSummary', () => { describe('where no gender categories are suppressed', () => { it('returns summary text describing the OT hours data', () => { @@ -747,7 +802,7 @@ describe('publishReport', () => { // Expect only one column to be updated (the report status_column) expect(updateStatement.data).toStrictEqual({ report_status: enumReportStatus.Published, - create_date: mockDraftReportInApi.create_date + create_date: mockDraftReportInApi.create_date, }); }); }); @@ -785,7 +840,7 @@ describe('publishReport', () => { // Expect only one column to be updated (the report status_column) expect(updateStatement.data).toStrictEqual({ report_status: enumReportStatus.Published, - create_date: mockPublishedReportInDb.create_date + create_date: mockPublishedReportInDb.create_date, }); }); }); @@ -799,13 +854,13 @@ describe('publishReport', () => { .spyOn(reportServicePrivate, 'movePublishedReportToHistory') .mockReturnValueOnce(null); - try { - await reportService.publishReport(mockDraftReportInApi); - } catch (error) { - expect(error.message).toBe( - 'A report for this time period already exists and cannot be updated.', - ); - } + try { + await reportService.publishReport(mockDraftReportInApi); + } catch (error) { + expect(error.message).toBe( + 'A report for this time period already exists and cannot be updated.', + ); + } }); }); });