Skip to content

Commit

Permalink
UI polish for recidivism chart (#255)
Browse files Browse the repository at this point in the history
  • Loading branch information
macfarlandian committed Nov 17, 2020
1 parent 35b171e commit dd9ed09
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 12 deletions.
7 changes: 6 additions & 1 deletion public-dashboard-client/src/page-prison/PagePrison.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,12 @@ export default function PagePrison() {
// doing this inside the render loop rather than in an effect
// to prevent an intermediate state from flashing on the chart;
// the current value check avoids an infinite render loop
if (!singleCohortSelected && recidivismDimension !== DIMENSION_KEYS.total) {
if (
!singleCohortSelected &&
// we don't need to reset the dimension if no cohorts are selected
selectedCohorts.length > 1 &&
recidivismDimension !== DIMENSION_KEYS.total
) {
setRecidivismDimension(DIMENSION_KEYS.total);
}

Expand Down
43 changes: 40 additions & 3 deletions public-dashboard-client/src/tooltip/Tooltip.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2020 Recidiviz, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import PropTypes from "prop-types";
import React from "react";
import styled from "styled-components";
Expand All @@ -23,6 +40,17 @@ const TooltipWrapper = styled.div`
}
`;

const LabelColorSwatch = styled.div`
background-color: ${(props) => props.color};
/* border: 1px solid ${(props) => props.theme.colors.bodyLight}; */
border-radius: 0.5em;
display: inline-block;
height: 0.8em;
margin-right: 0.5em;
vertical-align: baseline;
width: 0.8em;
`;

const TooltipTitle = styled.div`
color: ${(props) => props.theme.colors.infoPanelTitle};
Expand Down Expand Up @@ -53,7 +81,10 @@ const TooltipRecord = styled.div`
const TooltipLabel = styled.div`
.InfoPanel & {
font-size: 16px;
opacity: 0.6;
.TooltipLabel__text {
opacity: 0.6;
}
}
`;
const TooltipValue = styled.div`
Expand Down Expand Up @@ -91,9 +122,14 @@ export const Tooltip = ({ title, records }) => {
<TooltipWrapper>
<TooltipTitle>{title}</TooltipTitle>
<TooltipRecordList>
{records.map(({ label, value, pct }, i) => (
{records.map(({ color, label, value, pct }, i) => (
<TooltipRecord key={label || i}>
{label && <TooltipLabel>{label}</TooltipLabel>}
{label && (
<TooltipLabel>
{color && <LabelColorSwatch color={color} />}
<span className="TooltipLabel__text">{label}</span>
</TooltipLabel>
)}
<TooltipValue>
{typeof value === "number" ? formatAsNumber(value) : value}
</TooltipValue>
Expand All @@ -110,6 +146,7 @@ export const Tooltip = ({ title, records }) => {
Tooltip.propTypes = {
records: PropTypes.arrayOf(
PropTypes.shape({
color: PropTypes.string,
label: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,20 @@ export default function RecidivismRatesChart({ data, highlightedCohort }) {
lines={data}
margin={MARGIN}
otherChartProps={{
xExtent: [1, 10],
xExtent: [0, 10],
}}
size={[width, 475]}
tooltipControllerProps={{
getTooltipProps: (d) => {
const currentPeriod = d.followupYears;
return {
title: `${currentPeriod} year${
currentPeriod > 1 ? "s" : ""
currentPeriod === 1 ? "" : "s"
} since release`,
records: d.points
.filter((p) => p.data.followupYears === currentPeriod)
.map((p) => ({
color: p.parentLine.color,
label: p.parentLine.label,
pct: p.data.recidivismRate,
value: `${p.data.recidivated_releases} of ${p.data.releases}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,36 @@ import {
} from "../utils";
import RecidivismRatesChart from "./RecidivismRatesChart";

/**
* adds an initial record to each series for year zero with rate zero.
* Helps to make the initial point in the line chart more visible,
* especially for lines that would otherwise only have one point.
*/
function prependZero(records) {
const zeroRecord = {
...records[0],
followupYears: 0,
recidivated_releases: "0",
recidivismRate: 0,
};
return [zeroRecord, ...records];
}

/**
* When multiple cohorts are selected, or a single cohort and the `total` dimension,
* will return one data series per cohort.
* Otherwise (i.e., a single cohort and some dimensional breakdown is selected),
* will return one data series per demographic subgroup.
*/
function prepareChartData({ data, dimension, selectedCohorts }) {
function prepareChartData({
data,
dimension,
highlightedCohort,
selectedCohorts,
}) {
const showDemographics =
selectedCohorts &&
selectedCohorts.length === 1 &&
selectedCohorts.length <= 1 &&
dimension !== DIMENSION_KEYS.total;

const filteredData = data.filter(recordIsTotalByDimension(dimension));
Expand All @@ -54,11 +74,11 @@ function prepareChartData({ data, dimension, selectedCohorts }) {
),
(d) => d[DIMENSION_DATA_KEYS[dimension]]
),
([key, value]) => {
([key, records]) => {
return {
key,
label: DIMENSION_MAPPINGS.get(dimension).get(key),
coordinates: value,
coordinates: prependZero(records),
};
}
)
Expand All @@ -68,11 +88,11 @@ function prepareChartData({ data, dimension, selectedCohorts }) {
return (
Array.from(
group(filteredData, (d) => d.releaseCohort),
([key, value]) => {
([key, records]) => {
return {
key,
label: key,
coordinates: value,
coordinates: prependZero(records),
};
}
)
Expand All @@ -83,6 +103,10 @@ function prepareChartData({ data, dimension, selectedCohorts }) {
if (!selectedCohorts) {
return true;
}
// highlighted cohort is included even if it's not selected
if (highlightedCohort && highlightedCohort.label === record.label) {
return true;
}
return selectedCohorts.some(({ id }) => id === record.label);
})
);
Expand All @@ -94,6 +118,7 @@ export default function VizRecidivismRates({
const chartData = prepareChartData({
data: recidivismRates,
dimension,
highlightedCohort,
selectedCohorts,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@ test("renders one line per cohort", () => {
expect(getMainByLabelText("7 lines in a line chart")).toBeVisible();
});

test("renders the highlighted cohort even if it's not selected", () => {
const dimension = DIMENSION_KEYS.total;

render(
<VizRecidivismRates
data={{
dimension,
highlightedCohort: allSelectedCohorts[8],
recidivismRates: recidivismRatesFixture,
selectedCohorts: allSelectedCohorts.slice(0, 5),
}}
/>
);
expect(getMainByLabelText("6 lines in a line chart")).toBeVisible();
});

test("renders one line per demographic subgroup", () => {
const dimension = DIMENSION_KEYS.race;
render(
Expand Down

0 comments on commit dd9ed09

Please sign in to comment.