Skip to content

Commit

Permalink
Add arrests percentage stops by contraband type graph (#286)
Browse files Browse the repository at this point in the history
* Add arrests by percentage view + component

* add arrests percentage of searches graph

* add graph for stop counts with or without driver arrested

* Add graph for percentage of stops with arrests per stop purpose group

* Add percentage of stops with arrests per stop purpose

* Add percentage of searches for stop purpose groups graph

* add arrests percentage of searches per stop purpose graph

* add percentage of stops per contraband type

* Update counts of stops w/o arrests graph
  • Loading branch information
Afani97 authored Mar 22, 2024
1 parent 3766793 commit 90117c6
Show file tree
Hide file tree
Showing 19 changed files with 1,909 additions and 8 deletions.
8 changes: 7 additions & 1 deletion frontend/src/Components/AgencyData/AgencyData.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,13 @@ function AgencyData(props) {
</motion.div>
)}
</AnimatePresence>
{chartsOpen && <ChartRoutes agencyId={agencyId} showCompare={props.showCompare} />}
{chartsOpen && (
<ChartRoutes
agencyId={agencyId}
showCompare={props.showCompare}
agencyName={chartState.data[AGENCY_DETAILS].name}
/>
)}
</S.ContentWrapper>
</S.AgencyData>
);
Expand Down
136 changes: 136 additions & 0 deletions frontend/src/Components/Charts/Arrest/Arrests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, { useEffect, useState } from 'react';
import ArrestsStyled from './Arrests.styles';

// Util
import { YEARS_DEFAULT } from '../chartUtils';

// Hooks
import useMetaTags from '../../../Hooks/useMetaTags';
import useTableModal from '../../../Hooks/useTableModal';

// Children
import DataSubsetPicker from '../ChartSections/DataSubsetPicker/DataSubsetPicker';
import PercentageOfStops from './Charts/PercentageOfStops';
import useYearSet from '../../../Hooks/useYearSet';
import PercentageOfSearches from './Charts/PercentageOfSearches';
import CountOfStopsAndArrests from './Charts/CountOfStopsAndArrests';
import PercentageOfStopsForStopPurposeGroup from './Charts/PercentageOfStopsForPurposeGroup';
import PercentageOfStopsForStopPurpose from './Charts/PercentageOfStopsPerStopPurpose';
import PercentageOfSearchesForStopPurposeGroup from './Charts/PercentageOfSearchesForPurposeGroup';
import PercentageOfSearchesPerStopPurpose from './Charts/PercentageOfSearchesPerStopPurpose';
import PercentageOfStopsPerContrabandType from './Charts/PercentageOfStopsPerContrabandType';
import Switch from 'react-switch';
import { SwitchContainer } from '../TrafficStops/TrafficStops.styled';

function Arrests(props) {
const [year, setYear] = useState(YEARS_DEFAULT);
const [yearRange] = useYearSet();
const [togglePercentageOfStops, setTogglePercentageOfStops] = useState(true);
const [togglePercentageOfSearches, setTogglePercentageOfSearches] = useState(true);

const renderMetaTags = useMetaTags();
const [renderTableModal] = useTableModal();

useEffect(() => {
if (window.location.hash) {
document.querySelector(`${window.location.hash}`).scrollIntoView();
}
}, []);

const handleYearSelect = (y) => {
if (y === year) return;
setYear(y);
};

return (
<ArrestsStyled>
{renderMetaTags()}
{renderTableModal()}
<div style={{ display: 'flex', justifyContent: 'center' }}>
<DataSubsetPicker
label="Year"
value={year}
onChange={handleYearSelect}
options={[YEARS_DEFAULT].concat(yearRange)}
dropDown
/>
</div>
<PercentageOfStops {...props} year={year} />
<PercentageOfSearches {...props} year={year} />
<CountOfStopsAndArrests {...props} year={year} />

<SwitchContainer>
<span>
Switch to view {togglePercentageOfStops ? 'all stop purposes' : 'grouped stop purposes '}
</span>
<Switch
onChange={() => setTogglePercentageOfStops(!togglePercentageOfStops)}
checked={togglePercentageOfStops}
className="react-switch"
/>
</SwitchContainer>
{togglePercentageOfStops ? (
<PercentageOfStopsForStopPurposeGroup {...props} year={year} />
) : (
<PercentageOfStopsForStopPurpose {...props} year={year} />
)}

<SwitchContainer>
<span>
Switch to view{' '}
{togglePercentageOfSearches ? 'all stop purposes' : 'grouped stop purposes '}
</span>
<Switch
onChange={() => setTogglePercentageOfSearches(!togglePercentageOfSearches)}
checked={togglePercentageOfSearches}
className="react-switch"
/>
</SwitchContainer>

{togglePercentageOfSearches ? (
<PercentageOfSearchesForStopPurposeGroup {...props} year={year} />
) : (
<PercentageOfSearchesPerStopPurpose {...props} year={year} />
)}

<PercentageOfStopsPerContrabandType {...props} year={year} />
</ArrestsStyled>
);
}

export default Arrests;

export const ARRESTS_TABLE_COLUMNS = [
{
Header: 'Year',
accessor: 'year', // accessor is the "key" in the data
},
{
Header: 'White*',
accessor: 'white',
},
{
Header: 'Black*',
accessor: 'black',
},
{
Header: 'Native American*',
accessor: 'native_american',
},
{
Header: 'Asian*',
accessor: 'asian',
},
{
Header: 'Other*',
accessor: 'other',
},
{
Header: 'Hispanic',
accessor: 'hispanic',
},
{
Header: 'Total',
accessor: 'total',
},
];
32 changes: 32 additions & 0 deletions frontend/src/Components/Charts/Arrest/Arrests.styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import styled from 'styled-components';
import ChartPageBase from '../ChartSections/ChartPageBase';
import { smallerThanDesktop, smallerThanTabletLandscape } from '../../../styles/breakpoints';

export default styled(ChartPageBase)``;

export const ChartWrapper = styled.div`
width: 100%;
height: auto;
`;

export const HorizontalBarWrapper = styled.div`
display: flex;
flex-direction: row;
flex-wrap: no-wrap;
width: 100%;
margin: 0 auto;
justify-content: space-evenly;
@media (${smallerThanDesktop}) {
flex-wrap: wrap;
}
`;

export const BarContainer = styled.div`
width: 100%;
height: 500px;
@media (${smallerThanTabletLandscape}) {
width: 100%;
}
display: ${(props) => (props.visible ? 'block' : 'none')};
`;
150 changes: 150 additions & 0 deletions frontend/src/Components/Charts/Arrest/Charts/CountOfStopsAndArrests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { useEffect, useState } from 'react';
import * as S from '../../ChartSections/ChartsCommon.styled';

// Children
import { P } from '../../../../styles/StyledComponents/Typography';
import ChartHeader from '../../ChartSections/ChartHeader';
import HorizontalBarChart from '../../../NewCharts/HorizontalBarChart';
import axios from '../../../../Services/Axios';
import useOfficerId from '../../../../Hooks/useOfficerId';
import { ChartWrapper } from '../Arrests.styles';
import NewModal from '../../../NewCharts/NewModal';
import { ARRESTS_TABLE_COLUMNS } from '../Arrests';

function CountOfStopsAndArrests(props) {
const { agencyId, agencyName, showCompare, year } = props;

const officerId = useOfficerId();

const initArrestData = {
labels: [],
datasets: [],
isModalOpen: false,
tableData: [],
csvData: [],
loading: true,
};
const [arrestData, setArrestData] = useState(initArrestData);

useEffect(() => {
const params = [];
if (year && year !== 'All') {
params.push({ param: 'year', val: year });
}
if (officerId) {
params.push({ param: 'officer', val: officerId });
}

const urlParams = params.map((p) => `${p.param}=${p.val}`).join('&');
const url = `/api/agency/${agencyId}/arrests-stops-driver-arrested/?${urlParams}`;
axios
.get(url)
.then((res) => {
const tableData = [];
const resTableData = res.data.table_data.length
? JSON.parse(res.data.table_data)
: { data: [] };
resTableData.data.forEach((e) => {
const dataCounts = { ...e };
delete dataCounts.year;
// Need to assign explicitly otherwise the download data orders columns by alphabet.
tableData.unshift({
year: e.year,
white: e.white,
black: e.black,
native_american: e.native_american,
asian: e.asian,
other: e.other,
hispanic: e.hispanic,
total: Object.values(dataCounts).reduce((a, b) => a + b, 0),
});
});
const labels = ['Stops With Arrests', 'Stops Without Arrests'];
const colors = ['#96a0fa', '#5364f4'];

const datasets = res.data.arrest_counts.map((dataset, i) => ({
axis: 'y',
label: labels[i],
data: dataset.data,
fill: false,
backgroundColor: colors[i],
borderColor: colors[i],
hoverBackgroundColor: colors[i],
borderWidth: 1,
}));
const data = {
labels: ['White', 'Black', 'Hispanic', 'Asian', 'Native American', 'Other'],
datasets,
isModalOpen: false,
tableData,
csvData: tableData,
};

setArrestData(data);
})
.catch((err) => console.log(err));
}, [year]);

const formatTooltipValue = (ctx) => ctx.raw;

const subjectObserving = () => {
if (officerId) {
return 'by this officer';
}
if (agencyId === '-1') {
return 'for the entire state';
}
return 'by this department';
};

const getYearPhrase = () => (year && year !== 'All' ? ` in ${year}` : '');

const getBarChartModalSubHeading = (title) => `${title} ${subjectObserving()}${getYearPhrase()}`;

return (
<S.ChartSection>
<ChartHeader
chartTitle="Stop Counts With/Without Arrests"
handleViewData={() => setArrestData((state) => ({ ...state, isOpen: true }))}
/>
<S.ChartDescription>
<P>Percentage of stops that led to an arrest for a given race / ethnic group.</P>
<NewModal
tableHeader="Stop Counts With/Without Arrests"
tableSubheader="Shows what numbers of stops led to an arrest for a given race / ethnic group."
agencyName={agencyName}
tableData={arrestData.tableData}
csvData={arrestData.csvData}
columns={ARRESTS_TABLE_COLUMNS}
tableDownloadName="Arrests_By_Percentage"
isOpen={arrestData.isOpen}
closeModal={() => setArrestData((state) => ({ ...state, isOpen: false }))}
/>
</S.ChartDescription>
<S.ChartSubsection showCompare={showCompare}>
<ChartWrapper>
<HorizontalBarChart
title="Stop Counts With/Without Arrests"
data={arrestData}
pinMaxValue={false}
xStacked
yStacked
tickStyle={null}
stepSize={50000}
tooltipLabelCallback={formatTooltipValue}
modalConfig={{
tableHeader: 'Stop Counts With/Without Arrests',
tableSubheader: getBarChartModalSubHeading(
'Shows what number of stops led to an arrest for a given race / ethnic group'
),
agencyName,
chartTitle: getBarChartModalSubHeading('Stop Counts With/Without Arrests'),
}}
/>
</ChartWrapper>
</S.ChartSubsection>
</S.ChartSection>
);
}

export default CountOfStopsAndArrests;
Loading

0 comments on commit 90117c6

Please sign in to comment.