Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Agency Dashboard] New dataviz controls design + metric insights placements #181

Merged
merged 3 commits into from
Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
);
};