From da0182eef1afdbca60116ed354b94f7985443085 Mon Sep 17 00:00:00 2001 From: Aristotel Fani Date: Tue, 12 Dec 2023 18:10:50 -0500 Subject: [PATCH 1/2] Update other pie charts across the site to use the new chart library (#249) * Convert overview charts to new library * Refactor chart modal into new pie chart * Update chart titles for overview page * Update use of force pie chart * Fix no data layout --- .../Components/Charts/Overview/Overview.js | 230 +++++++++++------- .../Charts/TrafficStops/TrafficStops.js | 98 ++++---- .../Charts/UseOfForce/UseOfForce.js | 88 +++++-- frontend/src/Components/NewCharts/PieChart.js | 69 +++++- frontend/src/util/getDownloadableTitle.js | 3 + frontend/src/util/setChartColors.js | 9 + 6 files changed, 323 insertions(+), 174 deletions(-) create mode 100644 frontend/src/util/getDownloadableTitle.js diff --git a/frontend/src/Components/Charts/Overview/Overview.js b/frontend/src/Components/Charts/Overview/Overview.js index a844ddd3..986977cc 100644 --- a/frontend/src/Components/Charts/Overview/Overview.js +++ b/frontend/src/Components/Charts/Overview/Overview.js @@ -1,20 +1,17 @@ import React, { useState, useEffect } from 'react'; import * as S from '../ChartSections/ChartsCommon.styled'; import OverviewStyled from './Overview.styled'; -import { useTheme } from 'styled-components'; // Router import { useHistory, useRouteMatch } from 'react-router-dom'; // Constants -import toTitleCase from '../../../util/toTitleCase'; import { calculatePercentage, - reduceFullDataset, YEARS_DEFAULT, - STATIC_LEGEND_KEYS, RACES, calculateYearTotal, + reduceYearsToTotal, } from '../chartUtils'; import * as slugs from '../../../Routes/slugs'; @@ -31,14 +28,14 @@ import useDataset, { // Children import ChartHeader from '../ChartSections/ChartHeader'; -import Legend from '../ChartSections/Legend/Legend'; import useOfficerId from '../../../Hooks/useOfficerId'; -import Pie from '../ChartPrimitives/Pie'; import DataSubsetPicker from '../ChartSections/DataSubsetPicker/DataSubsetPicker'; +import PieChart from '../../NewCharts/PieChart'; +import getDownloadableTitle from '../../../util/getDownloadableTitle'; +import { pieChartConfig, pieChartLabels } from '../../../util/setChartColors'; function Overview(props) { const { agencyId } = props; - const theme = useTheme(); const history = useHistory(); const match = useRouteMatch(); const officerId = useOfficerId(); @@ -49,10 +46,20 @@ function Overview(props) { const [year, setYear] = useState(YEARS_DEFAULT); - const [censusPieData, setCensusPieData] = useState([]); - const [trafficStopsData, setTrafficStopsData] = useState([]); - const [searchesData, setSearchesData] = useState([]); - const [useOfForceData, setUseOfForceData] = useState([]); + const initChartData = { + labels: pieChartLabels, + datasets: [ + { + data: [], + ...pieChartConfig, + }, + ], + }; + + const [censusPieData, setCensusPieData] = useState(initChartData); + const [trafficStopsData, setTrafficStopsData] = useState(initChartData); + const [searchesData, setSearchesData] = useState(initChartData); + const [useOfForceData, setUseOfForceData] = useState(initChartData); const renderMetaTags = useMetaTags(); @@ -71,96 +78,84 @@ function Overview(props) { useEffect(() => { if (chartState.data[AGENCY_DETAILS].census_profile) { const data = chartState.data[AGENCY_DETAILS].census_profile; - if (Object.keys(data).length === 0) { - setCensusPieData([]); - return; + const chartData = RACES.map((race) => calculatePercentage(data[race], data['total'])); + + setCensusPieData({ + labels: pieChartLabels, + datasets: [ + { + data: chartData, + ...pieChartConfig, + }, + ], + }); + } + }, [chartState.data[AGENCY_DETAILS]]); + + function buildPieData(data, setFunc, dropdownYear = null) { + let chartData = [0, 0, 0, 0, 0]; + + if (!dropdownYear || dropdownYear === 'All') { + const totals = {}; + RACES.forEach((race) => { + totals[race] = reduceYearsToTotal(data, race)[race]; + }); + const total = calculateYearTotal(totals, RACES); + chartData = RACES.map((race) => calculatePercentage(totals[race], total)); + } else { + const yearData = data.find((d) => d.year === dropdownYear); + if (yearData) { + const total = RACES.map((race) => yearData[race]).reduce((a, b) => a + b, 0); + chartData = RACES.map((race) => calculatePercentage(yearData[race], total)); } - setCensusPieData( - RACES.map((race) => ({ - x: toTitleCase(race), - y: calculatePercentage(data[race], data.total), - color: theme.colors.ethnicGroup[race], - fontColor: theme.colors.fontColorsByEthnicGroup[race], - })) - ); } - }, [chartState.data[AGENCY_DETAILS], year]); + + setFunc({ + labels: pieChartLabels, + datasets: [ + { + data: chartData, + ...pieChartConfig, + }, + ], + }); + } + + const pieChartTitle = (chartTitle, download = false) => { + let subject = chartState.data[AGENCY_DETAILS].name; + if (subjectObserving() === 'officer') { + subject = `Officer ${officerId}`; + } + let title = `${chartTitle} for ${subject}`; + if (chartTitle !== 'Census Demographics') { + title = `${title} ${ + year === YEARS_DEFAULT ? `since ${chartState.yearRange.reverse()[0]}` : `during ${year}` + }`; + } + if (download) { + title = getDownloadableTitle(title); + } + return title; + }; // TRAFFIC STOPS useEffect(() => { - const data = chartState.data[STOPS]; - if (data) { - if (!year || year === 'All') { - setTrafficStopsData(reduceFullDataset(data, RACES, theme)); - } else { - let yearData = data.filter((d) => d.year === year); - let total = 0; - if (yearData.length > 0) { - // eslint-disable-next-line prefer-destructuring - yearData = yearData[0]; - total = calculateYearTotal(yearData); - } - setTrafficStopsData( - RACES.map((race) => ({ - x: toTitleCase(race), - y: calculatePercentage(yearData[race], total), - color: theme.colors.ethnicGroup[race], - fontColor: theme.colors.fontColorsByEthnicGroup[race], - })) - ); - } + if (chartState.data[STOPS]) { + buildPieData(chartState.data[STOPS], setTrafficStopsData, year); } }, [chartState.data[STOPS], year]); // SEARCHES useEffect(() => { - const data = chartState.data[SEARCHES]; - if (data) { - if (!year || year === 'All') { - setSearchesData(reduceFullDataset(data, RACES, theme)); - } else { - let yearData = data.filter((d) => d.year === year); - let total = 0; - if (yearData.length > 0) { - // eslint-disable-next-line prefer-destructuring - yearData = yearData[0]; - total = calculateYearTotal(yearData); - } - setSearchesData( - RACES.map((race) => ({ - x: toTitleCase(race), - y: calculatePercentage(yearData[race], total), - color: theme.colors.ethnicGroup[race], - fontColor: theme.colors.fontColorsByEthnicGroup[race], - })) - ); - } + if (chartState.data[SEARCHES]) { + buildPieData(chartState.data[SEARCHES], setSearchesData, year); } }, [chartState.data[SEARCHES], year]); // USE OF FORCE useEffect(() => { - const data = chartState.data[USE_OF_FORCE]; - if (data) { - if (!year || year === 'All') { - setUseOfForceData(reduceFullDataset(data, RACES, theme)); - } else { - let yearData = data.filter((d) => d.year === year); - let total = 0; - if (yearData.length > 0) { - // eslint-disable-next-line prefer-destructuring - yearData = yearData[0]; - total = calculateYearTotal(yearData); - } - setUseOfForceData( - RACES.map((race) => ({ - x: toTitleCase(race), - y: calculatePercentage(yearData[race], total), - color: theme.colors.ethnicGroup[race], - fontColor: theme.colors.fontColorsByEthnicGroup[race], - })) - ); - } + if (chartState.data[USE_OF_FORCE]) { + buildPieData(chartState.data[USE_OF_FORCE], setUseOfForceData, year); } }, [chartState.data[USE_OF_FORCE], year]); @@ -213,8 +208,21 @@ function Overview(props) { Census Demographics - - + NOTE: @@ -226,8 +234,19 @@ function Overview(props) { Traffic Stops - - + Shows the race/ethnic composition of drivers stopped by this {subjectObserving()}. @@ -241,8 +260,19 @@ function Overview(props) { Searches - - + Shows the race/ethnic composition of drivers searched by this {subjectObserving()}. @@ -252,8 +282,20 @@ function Overview(props) { Use of Force - - + Shows the race/ethnic composition of drivers whom {useOfForcePieChartCopy()} reported diff --git a/frontend/src/Components/Charts/TrafficStops/TrafficStops.js b/frontend/src/Components/Charts/TrafficStops/TrafficStops.js index ce4b7417..54aadf87 100644 --- a/frontend/src/Components/Charts/TrafficStops/TrafficStops.js +++ b/frontend/src/Components/Charts/TrafficStops/TrafficStops.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect } from 'react'; import TrafficStopsStyled, { GroupedStopsContainer, LineWrapper, @@ -46,10 +46,10 @@ import axios from '../../../Services/Axios'; import NewModal from '../../NewCharts/NewModal'; import displayDefinition from '../../../util/displayDefinition'; import PieChart from '../../NewCharts/PieChart'; -import ChartModal from '../../NewCharts/ChartModal'; -import Button from '../../Elements/Button'; import Switch from 'react-switch'; import Checkbox from '../../Elements/Inputs/Checkbox'; +import getDownloadableTitle from '../../../util/getDownloadableTitle'; +import { pieChartConfig, pieChartLabels, pieColors } from '../../../util/setChartColors'; function TrafficStops(props) { const { agencyId } = props; @@ -91,16 +91,7 @@ function TrafficStops(props) { STATIC_LEGEND_KEYS.map((k) => ({ ...k })) ); - const pieColors = ['#02bcbb', '#8879fc', '#9c0f2e', '#ffe066', '#0c3a66', '#9e7b9b']; - const [byPercentageLineData, setByPercentageLineData] = useState([]); - const pieChartLabels = ['White', 'Black', 'Hispanic', 'Asian', 'Native American', 'Other']; - const pieChartConfig = { - backgroundColor: pieColors, - borderColor: pieColors, - hoverBackgroundColor: pieColors, - borderWidth: 1, - }; const [byPercentagePieData, setByPercentagePieData] = useState({ labels: pieChartLabels, datasets: [ @@ -196,9 +187,6 @@ function TrafficStops(props) { const [yearForGroupedPieCharts, setYearForGroupedPieCharts] = useState('All'); const [checked, setChecked] = useState(false); - const [showZoomedPieChart, setShowZoomedPieChart] = useState(false); - const zoomedPieCharRef = useRef(null); - const [trafficStopsByCountRange, setTrafficStopsByCountRange] = useState(null); const [trafficStopsByCountPurpose, setTrafficStopsByCountPurpose] = useState(0); const [trafficStopsByCountMinMaxYears, setTrafficStopsByCountMinMaxYears] = useState([ @@ -553,10 +541,26 @@ function TrafficStops(props) { subject = `Officer ${officerId}`; } let title = `Traffic Stops By Percentage for ${subject} ${ - year === YEARS_DEFAULT ? `since ${stopsChartState.yearRange[0]}` : `during ${year}` + year === YEARS_DEFAULT ? `since ${stopsChartState.yearRange.reverse()[0]}` : `during ${year}` + }`; + if (download) { + title = getDownloadableTitle(title); + } + return title; + }; + + const getStopPurposeAndRaceCountPieTitle = (stopPurpose, download = false) => { + let subject = stopsChartState.data[AGENCY_DETAILS].name; + if (subjectObserving() === 'officer') { + subject = `Officer ${officerId}`; + } + let title = `Traffic Stops By ${stopPurpose} and Race Count for ${subject} ${ + yearForGroupedPieCharts === YEARS_DEFAULT + ? `since ${stopsGroupedByPurposeData.labels[0]}` + : `during ${yearForGroupedPieCharts}` }`; if (download) { - title = `${title.split(' ').join('_').toLowerCase()}.png`; + title = getDownloadableTitle(title); } return title; }; @@ -596,37 +600,20 @@ function TrafficStops(props) { showNonHispanic /> - setShowZoomedPieChart(false)} - chartToPrintRef={zoomedPieCharRef} - fileName={pieChartTitle(true)} - > - - - - - - @@ -636,13 +623,6 @@ function TrafficStops(props) { onChange={handleYearSelect} options={[YEARS_DEFAULT].concat(stopsChartState.yearRange)} /> -
- -
@@ -811,6 +791,14 @@ function TrafficStops(props) { title="Safety Violation" maintainAspectRatio={false} displayLegend={false} + modalConfig={{ + tableHeader: 'Traffic Stops By Stop Purpose and Race Count', + tableSubheader: + 'Shows the number of traffics stops broken down by purpose and race / ethnicity', + agencyName: stopsChartState.data[AGENCY_DETAILS].name, + chartTitle: getStopPurposeAndRaceCountPieTitle('Safety Violation'), + fileName: getStopPurposeAndRaceCountPieTitle('Safety Violation', true), + }} /> @@ -821,6 +809,13 @@ function TrafficStops(props) { title="Regulatory/Equipment" maintainAspectRatio={false} displayLegend={false} + modalConfig={{ + tableHeader: 'Traffic Stops By Stop Purpose and Race Count', + tableSubheader: `Shows the number of traffics stops broken down by purpose and race / ethnicity`, + agencyName: stopsChartState.data[AGENCY_DETAILS].name, + chartTitle: getStopPurposeAndRaceCountPieTitle('Regulatory/Equipment'), + fileName: getStopPurposeAndRaceCountPieTitle('Regulatory/Equipment', true), + }} /> @@ -831,6 +826,13 @@ function TrafficStops(props) { title="Other" maintainAspectRatio={false} displayLegend={false} + modalConfig={{ + tableHeader: 'Traffic Stops By Stop Purpose and Race Count', + tableSubheader: `Shows the number of traffics stops broken down by purpose and race / ethnicity`, + agencyName: stopsChartState.data[AGENCY_DETAILS].name, + chartTitle: getStopPurposeAndRaceCountPieTitle('Other'), + fileName: getStopPurposeAndRaceCountPieTitle('Other', true), + }} /> diff --git a/frontend/src/Components/Charts/UseOfForce/UseOfForce.js b/frontend/src/Components/Charts/UseOfForce/UseOfForce.js index 222127b9..a71a7028 100644 --- a/frontend/src/Components/Charts/UseOfForce/UseOfForce.js +++ b/frontend/src/Components/Charts/UseOfForce/UseOfForce.js @@ -8,13 +8,13 @@ import { YEARS_DEFAULT, STATIC_LEGEND_KEYS, RACES, - reduceFullDataset, calculatePercentage, calculateYearTotal, + reduceYearsToTotal, } from '../chartUtils'; // State -import useDataset, { USE_OF_FORCE } from '../../../Hooks/useDataset'; +import useDataset, { AGENCY_DETAILS, USE_OF_FORCE } from '../../../Hooks/useDataset'; // Hooks import useMetaTags from '../../../Hooks/useMetaTags'; @@ -25,10 +25,11 @@ import { P } from '../../../styles/StyledComponents/Typography'; import ChartHeader from '../ChartSections/ChartHeader'; import Legend from '../ChartSections/Legend/Legend'; import DataSubsetPicker from '../ChartSections/DataSubsetPicker/DataSubsetPicker'; -import toTitleCase from '../../../util/toTitleCase'; import useOfficerId from '../../../Hooks/useOfficerId'; import GroupedBar from '../ChartPrimitives/GroupedBar'; -import Pie from '../ChartPrimitives/Pie'; +import PieChart from '../../NewCharts/PieChart'; +import getDownloadableTitle from '../../../util/getDownloadableTitle'; +import { pieChartConfig, pieChartLabels } from '../../../util/setChartColors'; function UseOfForce(props) { const { agencyId, showCompare } = props; @@ -46,7 +47,15 @@ function UseOfForce(props) { ); const [useOfForceData, setUseOfForceData] = useState([]); - const [useOfForcePieData, setUseOfForcePieData] = useState([]); + const [useOfForcePieData, setUseOfForcePieData] = useState({ + labels: pieChartLabels, + datasets: [ + { + data: [], + ...pieChartConfig, + }, + ], + }); const renderMetaTags = useMetaTags(); const [renderTableModal, { openModal }] = useTableModal(); @@ -87,25 +96,34 @@ function UseOfForce(props) { // Pie chart data useEffect(() => { - const data = chartState.data[USE_OF_FORCE]; - if (data) { - if (!year || year === YEARS_DEFAULT) { - setUseOfForcePieData(reduceFullDataset(data, RACES, theme)); + if (chartState.data[USE_OF_FORCE]) { + const data = chartState.data[USE_OF_FORCE]; + let chartData = [0, 0, 0, 0, 0]; + + if (!year || year === 'All') { + const totals = {}; + RACES.forEach((race) => { + totals[race] = reduceYearsToTotal(data, race)[race]; + }); + const total = calculateYearTotal(totals, RACES); + chartData = RACES.map((race) => calculatePercentage(totals[race], total)); } else { const yearData = data.find((d) => d.year === year); - const total = calculateYearTotal(yearData); - setUseOfForcePieData( - RACES.map((race) => { - const rData = { - x: toTitleCase(race), - color: theme.colors.ethnicGroup[race], - fontColor: theme.colors.fontColorsByEthnicGroup[race], - }; - rData.y = yearData ? calculatePercentage(yearData[race], total) : 0; - return rData; - }) - ); + if (yearData) { + const total = RACES.map((race) => yearData[race]).reduce((a, b) => a + b, 0); + chartData = RACES.map((race) => calculatePercentage(yearData[race], total)); + } } + + setUseOfForcePieData({ + labels: pieChartLabels, + datasets: [ + { + data: chartData, + ...pieChartConfig, + }, + ], + }); } }, [chartState.data[USE_OF_FORCE], year]); @@ -135,6 +153,20 @@ function UseOfForce(props) { return t % 2 === 0 ? t : null; }; + const pieChartTitle = (download = false) => { + let subject = chartState.data[AGENCY_DETAILS].name; + if (subjectObserving() === 'officer') { + subject = `Officer ${officerId}`; + } + let title = `Use of Force for ${subject} ${ + year === YEARS_DEFAULT ? `since ${chartState.yearRange.reverse()[0]}` : `during ${year}` + }`; + if (download) { + title = getDownloadableTitle(title); + } + return title; + }; + return ( {renderMetaTags()} @@ -170,7 +202,19 @@ function UseOfForce(props) { - + parseInt(v, 10) === 0); + // Setup modal options + const [isChartOpen, setIsChartOpen] = useState(false); + const zoomedPieCharRef = useRef(null); + + const createModalOptions = (opts) => { + const modalOptions = JSON.parse(JSON.stringify(opts)); + modalOptions.plugins.legend = { + display: true, + position: 'right', + }; + modalOptions.plugins.tooltip.enabled = false; + modalOptions.plugins.title = { + display: true, + text: modalConfig.chartTitle, + }; + return modalOptions; + }; + + const pieChartModalPlugins = [whiteBackground, alwaysShowTooltip]; + const pieChartModalOptions = createModalOptions(options); + if (!data.datasets.length) { return ; } return ( <> - {noData &&
No Data Found
} - +
+ {noData &&
No Data Found
} + +
+ setIsChartOpen(false)} + chartToPrintRef={zoomedPieCharRef} + {...modalConfig} + > + + ); } diff --git a/frontend/src/util/getDownloadableTitle.js b/frontend/src/util/getDownloadableTitle.js new file mode 100644 index 00000000..e83d49c0 --- /dev/null +++ b/frontend/src/util/getDownloadableTitle.js @@ -0,0 +1,3 @@ +export default function getDownloadableTitle(title) { + return `${title.split(' ').join('_').toLowerCase()}.png`; +} diff --git a/frontend/src/util/setChartColors.js b/frontend/src/util/setChartColors.js index 6aec14b7..1cc3f887 100644 --- a/frontend/src/util/setChartColors.js +++ b/frontend/src/util/setChartColors.js @@ -1 +1,10 @@ +export const pieColors = ['#02bcbb', '#8879fc', '#9c0f2e', '#ffe066', '#0c3a66', '#9e7b9b']; +export const pieChartLabels = ['White', 'Black', 'Hispanic', 'Asian', 'Native American', 'Other']; +export const pieChartConfig = { + backgroundColor: pieColors, + borderColor: pieColors, + hoverBackgroundColor: pieColors, + borderWidth: 1, +}; + export default ({ id, data }) => data[`${id}Color`]; From b4b118213ca52dd32a600e19f2c4ecc96f489fe6 Mon Sep 17 00:00:00 2001 From: Aristotel Fani Date: Wed, 13 Dec 2023 16:05:59 -0500 Subject: [PATCH 2/2] Update pie chart modal descriptions (#250) --- .../Components/Charts/Overview/Overview.js | 21 +++++++++++---- .../Charts/TrafficStops/TrafficStops.js | 26 ++++++++++++++----- .../Charts/UseOfForce/UseOfForce.js | 10 ++++--- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/frontend/src/Components/Charts/Overview/Overview.js b/frontend/src/Components/Charts/Overview/Overview.js index 986977cc..399eb5c8 100644 --- a/frontend/src/Components/Charts/Overview/Overview.js +++ b/frontend/src/Components/Charts/Overview/Overview.js @@ -73,6 +73,14 @@ function Overview(props) { return ''; }; + const getYearPhrase = () => (year && year !== 'All' ? ` in ${year}` : ''); + + const getChartModalSubHeading = (title) => + `${title} by this ${subjectObserving()}${getYearPhrase()}.`; + + const getOverviewSubheader = () => + `Shows the race/ethnic composition of drivers ${subjectObserving()}${getYearPhrase()} reported using force against.`; + /* Build Data */ // CENSUS useEffect(() => { @@ -129,7 +137,7 @@ function Overview(props) { let title = `${chartTitle} for ${subject}`; if (chartTitle !== 'Census Demographics') { title = `${title} ${ - year === YEARS_DEFAULT ? `since ${chartState.yearRange.reverse()[0]}` : `during ${year}` + year === YEARS_DEFAULT ? `since ${chartState.yearRange.reverse()[0]}` : `in ${year}` }`; } if (download) { @@ -241,7 +249,9 @@ function Overview(props) { showWhiteBackground={false} modalConfig={{ tableHeader: 'Traffic Stops', - tableSubheader: `Shows the race/ethnic composition of drivers stopped by this ${subjectObserving()}.`, + tableSubheader: getChartModalSubHeading( + 'Shows the race/ethnic composition of drivers stopped' + ), agencyName: chartState.data[AGENCY_DETAILS].name, chartTitle: pieChartTitle('Traffic Stops'), fileName: pieChartTitle('Traffic Stops', true), @@ -267,7 +277,9 @@ function Overview(props) { showWhiteBackground={false} modalConfig={{ tableHeader: 'Searches', - tableSubheader: `Shows the race/ethnic composition of drivers searched by this ${subjectObserving()}.`, + tableSubheader: getChartModalSubHeading( + 'Shows the race/ethnic composition of drivers searched' + ), agencyName: chartState.data[AGENCY_DETAILS].name, chartTitle: pieChartTitle('Searches'), fileName: pieChartTitle('Searches', true), @@ -289,8 +301,7 @@ function Overview(props) { showWhiteBackground={false} modalConfig={{ tableHeader: 'Use of Force', - tableSubheader: `Shows the race/ethnic composition of drivers whom ${useOfForcePieChartCopy()} reported - using force against.`, + tableSubheader: getOverviewSubheader(), agencyName: chartState.data[AGENCY_DETAILS].name, chartTitle: pieChartTitle('Use of Force'), fileName: pieChartTitle('Use of Force', true), diff --git a/frontend/src/Components/Charts/TrafficStops/TrafficStops.js b/frontend/src/Components/Charts/TrafficStops/TrafficStops.js index 54aadf87..cbfb18de 100644 --- a/frontend/src/Components/Charts/TrafficStops/TrafficStops.js +++ b/frontend/src/Components/Charts/TrafficStops/TrafficStops.js @@ -541,7 +541,7 @@ function TrafficStops(props) { subject = `Officer ${officerId}`; } let title = `Traffic Stops By Percentage for ${subject} ${ - year === YEARS_DEFAULT ? `since ${stopsChartState.yearRange.reverse()[0]}` : `during ${year}` + year === YEARS_DEFAULT ? `since ${stopsChartState.yearRange.reverse()[0]}` : `in ${year}` }`; if (download) { title = getDownloadableTitle(title); @@ -549,6 +549,11 @@ function TrafficStops(props) { return title; }; + const getChartModalSubHeading = (title) => { + const yearSelected = year && year !== 'All' ? ` in ${year}` : ''; + return `${title} by this ${subjectObserving()}${yearSelected}.`; + }; + const getStopPurposeAndRaceCountPieTitle = (stopPurpose, download = false) => { let subject = stopsChartState.data[AGENCY_DETAILS].name; if (subjectObserving() === 'officer') { @@ -557,7 +562,7 @@ function TrafficStops(props) { let title = `Traffic Stops By ${stopPurpose} and Race Count for ${subject} ${ yearForGroupedPieCharts === YEARS_DEFAULT ? `since ${stopsGroupedByPurposeData.labels[0]}` - : `during ${yearForGroupedPieCharts}` + : `in ${yearForGroupedPieCharts}` }`; if (download) { title = getDownloadableTitle(title); @@ -609,7 +614,9 @@ function TrafficStops(props) { maintainAspectRatio modalConfig={{ tableHeader: 'Traffic Stops By Percentage', - tableSubheader: `Shows the race/ethnic composition of drivers stopped by this ${subjectObserving()} over time.`, + tableSubheader: getChartModalSubHeading( + 'Shows the race/ethnic composition of drivers stopped' + ), agencyName: stopsChartState.data[AGENCY_DETAILS].name, chartTitle: pieChartTitle(), fileName: pieChartTitle(true), @@ -793,8 +800,9 @@ function TrafficStops(props) { displayLegend={false} modalConfig={{ tableHeader: 'Traffic Stops By Stop Purpose and Race Count', - tableSubheader: - 'Shows the number of traffics stops broken down by purpose and race / ethnicity', + tableSubheader: getChartModalSubHeading( + 'Shows the number of traffics stops broken down by purpose and race / ethnicity' + ), agencyName: stopsChartState.data[AGENCY_DETAILS].name, chartTitle: getStopPurposeAndRaceCountPieTitle('Safety Violation'), fileName: getStopPurposeAndRaceCountPieTitle('Safety Violation', true), @@ -811,7 +819,9 @@ function TrafficStops(props) { displayLegend={false} modalConfig={{ tableHeader: 'Traffic Stops By Stop Purpose and Race Count', - tableSubheader: `Shows the number of traffics stops broken down by purpose and race / ethnicity`, + tableSubheader: getChartModalSubHeading( + 'Shows the number of traffics stops broken down by purpose and race / ethnicity' + ), agencyName: stopsChartState.data[AGENCY_DETAILS].name, chartTitle: getStopPurposeAndRaceCountPieTitle('Regulatory/Equipment'), fileName: getStopPurposeAndRaceCountPieTitle('Regulatory/Equipment', true), @@ -828,7 +838,9 @@ function TrafficStops(props) { displayLegend={false} modalConfig={{ tableHeader: 'Traffic Stops By Stop Purpose and Race Count', - tableSubheader: `Shows the number of traffics stops broken down by purpose and race / ethnicity`, + tableSubheader: getChartModalSubHeading( + 'Shows the number of traffics stops broken down by purpose and race / ethnicity' + ), agencyName: stopsChartState.data[AGENCY_DETAILS].name, chartTitle: getStopPurposeAndRaceCountPieTitle('Other'), fileName: getStopPurposeAndRaceCountPieTitle('Other', true), diff --git a/frontend/src/Components/Charts/UseOfForce/UseOfForce.js b/frontend/src/Components/Charts/UseOfForce/UseOfForce.js index a71a7028..49d4fce4 100644 --- a/frontend/src/Components/Charts/UseOfForce/UseOfForce.js +++ b/frontend/src/Components/Charts/UseOfForce/UseOfForce.js @@ -159,7 +159,7 @@ function UseOfForce(props) { subject = `Officer ${officerId}`; } let title = `Use of Force for ${subject} ${ - year === YEARS_DEFAULT ? `since ${chartState.yearRange.reverse()[0]}` : `during ${year}` + year === YEARS_DEFAULT ? `since ${chartState.yearRange.reverse()[0]}` : `in ${year}` }`; if (download) { title = getDownloadableTitle(title); @@ -167,6 +167,11 @@ function UseOfForce(props) { return title; }; + const getChartModalSubHeading = () => { + const yearSelected = year && year !== 'All' ? ` in ${year}` : ''; + return `Shows the race/ethnic composition of drivers ${subjectObserving()}${yearSelected} reported using force against.`; + }; + return ( {renderMetaTags()} @@ -208,8 +213,7 @@ function UseOfForce(props) { maintainAspectRatio modalConfig={{ tableHeader: 'Use of Force', - tableSubheader: `Shows the race/ethnic composition of drivers ${subjectObserving()} reported using force - against.`, + tableSubheader: getChartModalSubHeading(), agencyName: chartState.data[AGENCY_DETAILS].name, chartTitle: pieChartTitle(), fileName: pieChartTitle(true),