diff --git a/public-dashboard-client/src/page-prison/PagePrison.js b/public-dashboard-client/src/page-prison/PagePrison.js index 2d096336..d8539554 100644 --- a/public-dashboard-client/src/page-prison/PagePrison.js +++ b/public-dashboard-client/src/page-prison/PagePrison.js @@ -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); } diff --git a/public-dashboard-client/src/tooltip/Tooltip.js b/public-dashboard-client/src/tooltip/Tooltip.js index f8556851..28d4c353 100644 --- a/public-dashboard-client/src/tooltip/Tooltip.js +++ b/public-dashboard-client/src/tooltip/Tooltip.js @@ -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 . +// ============================================================================= + import PropTypes from "prop-types"; import React from "react"; import styled from "styled-components"; @@ -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}; @@ -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` @@ -91,9 +122,14 @@ export const Tooltip = ({ title, records }) => { {title} - {records.map(({ label, value, pct }, i) => ( + {records.map(({ color, label, value, pct }, i) => ( - {label && {label}} + {label && ( + + {color && } + {label} + + )} {typeof value === "number" ? formatAsNumber(value) : value} @@ -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, diff --git a/public-dashboard-client/src/viz-recidivism-rates/RecidivismRatesChart.js b/public-dashboard-client/src/viz-recidivism-rates/RecidivismRatesChart.js index 06e8ccaa..33eb2d31 100644 --- a/public-dashboard-client/src/viz-recidivism-rates/RecidivismRatesChart.js +++ b/public-dashboard-client/src/viz-recidivism-rates/RecidivismRatesChart.js @@ -105,7 +105,7 @@ export default function RecidivismRatesChart({ data, highlightedCohort }) { lines={data} margin={MARGIN} otherChartProps={{ - xExtent: [1, 10], + xExtent: [0, 10], }} size={[width, 475]} tooltipControllerProps={{ @@ -113,11 +113,12 @@ export default function RecidivismRatesChart({ data, highlightedCohort }) { 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}`, diff --git a/public-dashboard-client/src/viz-recidivism-rates/VizRecidivismRates.js b/public-dashboard-client/src/viz-recidivism-rates/VizRecidivismRates.js index e79dbce2..ba4dd3d0 100644 --- a/public-dashboard-client/src/viz-recidivism-rates/VizRecidivismRates.js +++ b/public-dashboard-client/src/viz-recidivism-rates/VizRecidivismRates.js @@ -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)); @@ -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), }; } ) @@ -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), }; } ) @@ -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); }) ); @@ -94,6 +118,7 @@ export default function VizRecidivismRates({ const chartData = prepareChartData({ data: recidivismRates, dimension, + highlightedCohort, selectedCohorts, }); diff --git a/public-dashboard-client/src/viz-recidivism-rates/VizRecidivismRates.test.js b/public-dashboard-client/src/viz-recidivism-rates/VizRecidivismRates.test.js index 986f2d5c..ef65efaa 100644 --- a/public-dashboard-client/src/viz-recidivism-rates/VizRecidivismRates.test.js +++ b/public-dashboard-client/src/viz-recidivism-rates/VizRecidivismRates.test.js @@ -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( + + ); + expect(getMainByLabelText("6 lines in a line chart")).toBeVisible(); +}); + test("renders one line per demographic subgroup", () => { const dimension = DIMENSION_KEYS.race; render(