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

feat: Accessible graphs #405

Merged
merged 8 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
73 changes: 64 additions & 9 deletions backend/src/v1/services/report-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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,
});
});
});
Expand Down Expand Up @@ -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,
});
});
});
Expand All @@ -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.',
);
}
});
});
});
Expand Down
43 changes: 42 additions & 1 deletion backend/src/v1/services/report-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,37 @@ const reportServicePrivate = {
return result;
},

/*
This method converts the raw data that could be used to draw a bar chart
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
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) =>
`${Math.min(Math.round(d.value), 100)}% 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
Expand Down Expand Up @@ -956,6 +987,10 @@ const reportService = {
'bonus pay',
false,
),
percentBonusPay: reportServicePrivate.getPercentSummary(
chartData.percentReceivingBonusPay,
'receive bonus pay',
),
meanOvertimeHoursGap: reportServicePrivate.getHoursGapTextSummary(
referenceGenderCode,
tableData.meanOvertimeHoursGap,
Expand All @@ -968,6 +1003,10 @@ const reportService = {
'median',
'overtime hours',
),
percentOvertimePay: reportServicePrivate.getPercentSummary(
chartData.percentReceivingOvertimePay,
'receive overtime pay',
),
hourlyPayQuartiles:
reportServicePrivate.getHourlyPayQuartilesTextSummary(
referenceGenderCode,
Expand Down Expand Up @@ -1172,7 +1211,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,
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ <h4 class="mb-0">
<div class="mb-2 text-normal">
Difference as compared to reference group (<%= referenceGenderCategory %>)
</div>
<table id="mean-overtime-hours-table" class="table" role="presentation">
<table id="mean-overtime-hours-table" class="table" role="table">
<% for(var i=0; i < tableData.meanOvertimeHoursGap.length; i++) { %>
<tr>
<td class="text-normal">
Expand Down Expand Up @@ -212,8 +212,7 @@ <h4 class="mb-0">
<div class="mb-2 text-normal">
Difference as compared to reference group (<%= referenceGenderCategory %>)
</div>
<table id="median-overtime-hours-table" class="table" role="table"
aria-describedby="Median difference in overtime hours">
<table id="median-overtime-hours-table" class="table" role="table">
<tr style="display: none" aria-hidden="true">
<th scope="col">Gender Category</th>
<th scope="col">Difference (hours)</th>
Expand Down
21 changes: 17 additions & 4 deletions doc-gen-service/src/templates/report.script.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function percentFilledHorizBarChart(data, options = {}) {
numberFormat: '1.0f',
maxX: 100,
unfilledColor: '#eeeeee',
ariaLabel: null,
};
options = { ...defaultOptions, ...options };

Expand Down Expand Up @@ -60,7 +61,10 @@ function percentFilledHorizBarChart(data, options = {}) {
.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');

if (options.ariaLabel) svg.attr('aria-label', options.ariaLabel);

const color = (i) => colors[i];

Expand Down Expand Up @@ -216,13 +220,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);

Expand Down Expand Up @@ -324,7 +333,9 @@ function horizontalBarChart(data, numberFormat = '$0.2f') {
.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];

Expand Down Expand Up @@ -411,7 +422,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')
Expand Down
10 changes: 8 additions & 2 deletions doc-gen-service/src/v1/services/doc-gen-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down