Skip to content

Commit

Permalink
[Agency Dashboard] New dataviz controls design + metric insights plac…
Browse files Browse the repository at this point in the history
…ements (#181)

* new dataviz controls design + metric insights in left panel and bottom of chart

* put back column for metric insights on mobile

* fix lint
  • Loading branch information
terryttsai authored Nov 28, 2022
1 parent 4c02edd commit 3e61f79
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 58 deletions.
5 changes: 3 additions & 2 deletions agency-dashboard/src/DashboardView.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,13 @@ export const RightPanelBackButtonContainer = styled(BackButtonContainer)`
export const MetricTitle = styled.div`
${typography.sizeCSS.headline}
margin-top: 36px;
margin-bottom: 16px;
margin-bottom: 40px;
hyphens: auto;
overflow-wrap: break-word;
`;

export const RightPanelMetricTitle = styled(MetricTitle)`
margin-bottom: 40px;
margin-bottom: 24px;
@media only screen and (min-width: ${COMMON_DESKTOP_WIDTH}px) {
display: none;
Expand All @@ -113,6 +113,7 @@ export const RightPanelMetricTitle = styled(MetricTitle)`

export const MetricOverviewContent = styled.div`
${typography.sizeCSS.medium}
margin-top: 36px;
@media only screen and (max-width: ${TABLET_WIDTH - 1}px) {
${typography.sizeCSS.normal}
Expand Down
37 changes: 20 additions & 17 deletions agency-dashboard/src/DashboardView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ReactComponent as InfoIcon } from "@justice-counts/common/assets/info-i
import { ReactComponent as LeftArrowIcon } from "@justice-counts/common/assets/left-arrow-icon.svg";
import { ReactComponent as ShareIcon } from "@justice-counts/common/assets/share-icon.svg";
import { DatapointsView } from "@justice-counts/common/components/DataViz/DatapointsView";
import { MetricInsights } from "@justice-counts/common/components/DataViz/MetricInsights";
import { COMMON_DESKTOP_WIDTH } from "@justice-counts/common/components/GlobalStyles";
import { observer } from "mobx-react-lite";
import React, { useEffect, useState } from "react";
Expand Down Expand Up @@ -84,8 +85,9 @@ const MetricOverviewActionShareButton = () => (
);

const DashboardView = () => {
const [shouldResizeChartHeight, setShouldResizeChartHeight] =
useState<boolean>(getScreenWidth() >= COMMON_DESKTOP_WIDTH);
const [isDesktopWidth, setIsDesktopWidth] = useState<boolean>(
getScreenWidth() >= COMMON_DESKTOP_WIDTH
);
const navigate = useNavigate();
const params = useParams();
const agencyId = Number(params.id);
Expand Down Expand Up @@ -114,13 +116,10 @@ const DashboardView = () => {
useEffect(() => {
const resizeListener = () => {
// change width from the state object
if (shouldResizeChartHeight && getScreenWidth() < COMMON_DESKTOP_WIDTH) {
setShouldResizeChartHeight(false);
} else if (
!shouldResizeChartHeight &&
getScreenWidth() >= COMMON_DESKTOP_WIDTH
) {
setShouldResizeChartHeight(true);
if (isDesktopWidth && getScreenWidth() < COMMON_DESKTOP_WIDTH) {
setIsDesktopWidth(false);
} else if (!isDesktopWidth && getScreenWidth() >= COMMON_DESKTOP_WIDTH) {
setIsDesktopWidth(true);
}
};
// set resize listener
Expand All @@ -131,7 +130,7 @@ const DashboardView = () => {
// remove resize listener
window.removeEventListener("resize", resizeListener);
};
}, [shouldResizeChartHeight]);
}, [isDesktopWidth]);

if (
!metricKey ||
Expand All @@ -149,14 +148,19 @@ const DashboardView = () => {
datapointsStore.dimensionNamesByMetricAndDisaggregation
);

const metricName =
datapointsStore.metricKeyToDisplayName[metricKey] || metricKey;

const datapoints =
datapointsStore.datapointsByMetric[metricKey]?.aggregate || [];

return (
<Container key={metricKey}>
<HeaderBar />
<LeftPanel>
<BackButton onClick={() => navigate(`/agency/${agencyId}`)} />
<MetricTitle>
{datapointsStore.metricKeyToDisplayName[metricKey] || metricKey}
</MetricTitle>
<MetricTitle>{metricName}</MetricTitle>
<MetricInsights datapoints={datapoints} />
<MetricOverviewContent>
Measures the number of individuals with at least one parole violation
during the reporting period.
Expand All @@ -169,9 +173,7 @@ const DashboardView = () => {
</LeftPanel>
<RightPanel>
<RightPanelBackButton onClick={() => navigate(`/agency/${agencyId}`)} />
<RightPanelMetricTitle>
{datapointsStore.metricKeyToDisplayName[metricKey] || metricKey}
</RightPanelMetricTitle>
<RightPanelMetricTitle>{metricName}</RightPanelMetricTitle>
<DatapointsView
datapointsGroupedByAggregateAndDisaggregations={
datapointsStore.datapointsByMetric[metricKey]
Expand All @@ -183,7 +185,8 @@ const DashboardView = () => {
onMetricsSelect={(metric) =>
navigate(`/agency/${agencyId}/dashboard?metric=${metric}`)
}
resizeHeight={shouldResizeChartHeight}
showBottomMetricInsights={!isDesktopWidth}
resizeHeight={isDesktopWidth}
/>
<RightPanelMetricOverviewContent>
Measures the number of individuals with at least one parole violation
Expand Down
42 changes: 23 additions & 19 deletions common/components/DataViz/DatapointsView.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,7 @@ export const DatapointsViewControlsContainer = styled.div`
display: flex;
flex-direction: row;
flex-grow: 1;
border-top: 1px solid ${palette.highlight.grey9};
border-bottom: 1px solid ${palette.highlight.grey9};
border: 1px solid ${palette.highlight.grey9};
`;

const DatapointsViewDropdown = styled(Dropdown)`
Expand Down Expand Up @@ -137,14 +136,19 @@ const DatapointsViewDropdownToggleContainer = styled(DropdownToggle)`
text-align: start;
&:hover {
color: ${palette.solid.blue};
color: ${palette.solid.darkgrey};
}
&[aria-expanded="true"] {
color: ${palette.solid.darkgrey};
}
`;
const DatapointsViewDropdownToggleSelection = styled.div`
height: 64px;
padding: 32px 32px 0px 8px;
height: 40px;
padding-top: 7px;
padding-left: 22px;
width: 100%;
${typography.sizeCSS.large}
${typography.sizeCSS.normal}
color: ${palette.solid.darkgrey};
line-height: 1.6rem;
text-overflow: ellipsis;
Expand All @@ -158,17 +162,16 @@ const DatapointsViewDropdownToggleSelection = styled.div`
}
`;
const DatapointsViewDropdownToggleTitle = styled.div`
padding: 8px 8px 0;
position: absolute;
${typography.sizeCSS.small}
pointer-events: none;
top: 0;
top: -20px;
left: 0;
`;

const DatapointsViewDropdownToggleArrow = styled.div`
bottom: 0;
right: 0;
top: 2px;
left: -7px;
position: absolute;
font-size: 6px;
transform: rotate(45deg);
Expand Down Expand Up @@ -230,16 +233,13 @@ export const DatapointsViewControlsDropdown: React.FC<
</DatapointsViewDropdown>
);

export const MetricInsightsRow = styled.div<{ selfWidth: number }>`
export const MetricInsightsContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-end;
${({
selfWidth,
}) => `@media only screen and (max-width: calc(1148px + ${selfWidth}px)) {
display: none;
}`}
@media only screen and (max-width: ${TABLET_WIDTH - 1}px) {
flex-direction: column;
}
`;

const MetricInsightContainer = styled.div`
Expand Down Expand Up @@ -285,11 +285,16 @@ export const MetricInsight: React.FC<MetricInsightProps> = ({
</MetricInsightContainer>
);

export const BottomMetricInsightsContainer = styled.div`
margin: 24px 0;
`;

export const DatapointsViewControlsRow = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 24px;
margin-top: 32px;
@media only screen and (max-width: ${TABLET_WIDTH - 1}px) {
display: none;
Expand Down Expand Up @@ -323,10 +328,9 @@ export const MobileFiltersButton = styled.div`
`;

export const SelectMetricsButtonContainer = styled.div`
height: 40px;
padding-left: 16px;
padding-right: 16px;
padding-top: 8px;
padding-bottom: 8px;
display: flex;
align-items: center;
border-radius: 2px;
Expand Down
33 changes: 13 additions & 20 deletions common/components/DataViz/DatapointsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ import {
ReportFrequency,
} from "@justice-counts/common/types";
import { DropdownMenu, DropdownToggle } from "@recidiviz/design-system";
import React, { useEffect, useRef } from "react";
import React, { useEffect } from "react";

import { DatapointsTitle } from "./DatapointsTitle";
import {
BottomMetricInsightsContainer,
DatapointsViewContainer,
DatapointsViewControlsContainer,
DatapointsViewControlsDropdown,
Expand All @@ -39,8 +40,6 @@ import {
ExtendedDropdown,
ExtendedDropdownMenuItem,
MetricHeaderWrapper,
MetricInsight,
MetricInsightsRow,
MobileFiltersButton,
MobileFiltersRow,
MobileSelectMetricsButton,
Expand All @@ -49,6 +48,7 @@ import {
SelectMetricsButtonContainer,
SelectMetricsButtonText,
} from "./DatapointsView.styles";
import { MetricInsights } from "./MetricInsights";
import {
filterByTimeRange,
filterNullDatapoints,
Expand Down Expand Up @@ -98,6 +98,7 @@ export const DatapointsView: React.FC<{
metricFrequency?: ReportFrequency;
metricNames?: string[];
onMetricsSelect?: (metric: string) => void;
showBottomMetricInsights?: boolean;
resizeHeight?: boolean;
}> = ({
datapointsGroupedByAggregateAndDisaggregations,
Expand All @@ -106,6 +107,7 @@ export const DatapointsView: React.FC<{
metricFrequency,
metricNames,
onMetricsSelect,
showBottomMetricInsights = false,
resizeHeight = false,
}) => {
const [selectedTimeRange, setSelectedTimeRange] =
Expand All @@ -114,8 +116,6 @@ export const DatapointsView: React.FC<{
React.useState<string>(noDisaggregationOption);
const [datapointsViewSetting, setDatapointsViewSetting] =
React.useState<DatapointsViewSetting>("Count");
const [insightsWidth, setInsightsWidth] = React.useState(0);
const insightsRef = useRef<HTMLDivElement | null>(null);
const [mobileSelectMetricsVisible, setMobileSelectMetricsVisible] =
React.useState<boolean>(false);

Expand Down Expand Up @@ -161,9 +161,6 @@ export const DatapointsView: React.FC<{
}
}, [selectedDisaggregation]);

useEffect(() => {
if (insightsRef.current) setInsightsWidth(insightsRef.current.offsetWidth);
}, [metricName]);
/** Prevent body from scrolling when modal is open */
useEffect(() => {
if (mobileSelectMetricsVisible) {
Expand Down Expand Up @@ -255,16 +252,6 @@ export const DatapointsView: React.FC<{

const chartViewInsightsInfo = `Year-to-Year: ${percentChange},\nAvg. Total Value: ${avgValue},\nMost Recent: ${mostRecentValue}`;

const renderMetricInsightsRow = () => {
return (
<MetricInsightsRow ref={insightsRef} selfWidth={insightsWidth}>
<MetricInsight title="Year-to-Year" value={percentChange} />
<MetricInsight title="Avg. Total Value" value={avgValue} />
<MetricInsight title="Most Recent" value={mostRecentValue} />
</MetricInsightsRow>
);
};

return (
<DatapointsViewContainer>
<DatapointsViewHeaderWrapper>
Expand All @@ -275,10 +262,11 @@ export const DatapointsView: React.FC<{
metricFrequency={metricFrequency}
insights={chartViewInsightsInfo}
/>
{data.length > 0 && renderMetricInsightsRow()}
{data.length > 0 && (
<MetricInsights datapoints={dataSelectedInTimeRange} />
)}
</MetricHeaderWrapper>
)}
{/* {renderDataVizControls()} */}
</DatapointsViewHeaderWrapper>
<DatapointsViewControlsRow>
{metricNames && onMetricsSelect && (
Expand All @@ -294,6 +282,11 @@ export const DatapointsView: React.FC<{
</MobileFiltersRow>
{renderChartForMetric()}
{renderLegend()}
{showBottomMetricInsights && (
<BottomMetricInsightsContainer>
<MetricInsights datapoints={dataSelectedInTimeRange} />
</BottomMetricInsightsContainer>
)}
<MobileSelectMetricsButtonContainer>
<MobileSelectMetricsButton
onClick={() => setMobileSelectMetricsVisible(true)}
Expand Down
45 changes: 45 additions & 0 deletions common/components/DataViz/MetricInsights.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Recidiviz - a data platform for criminal justice reform
// Copyright (C) 2022 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 React from "react";

import { Datapoint } from "../../types";
import {
MetricInsight,
MetricInsightsContainer,
} from "./DatapointsView.styles";
import {
getAverageTotalValue,
getLatestDateFormatted,
getPercentChangeOverTime,
} from "./utils";

export const MetricInsights: React.FC<{
datapoints: Datapoint[];
}> = ({ datapoints }) => {
const isAnnual = datapoints[0]?.frequency === "ANNUAL";
const percentChange = getPercentChangeOverTime(datapoints);
const avgValue = getAverageTotalValue(datapoints, isAnnual);
const mostRecentValue = getLatestDateFormatted(datapoints, isAnnual);
return (
<MetricInsightsContainer>
<MetricInsight title="Year-to-Year" value={percentChange} />
<MetricInsight title="Avg. Total Value" value={avgValue} />
<MetricInsight title="Most Recent" value={mostRecentValue} />
</MetricInsightsContainer>
);
};

0 comments on commit 3e61f79

Please sign in to comment.