Skip to content

Commit

Permalink
[Agency Dashboard] DataVizStore implementation (#186)
Browse files Browse the repository at this point in the history
* WIP datavizstore implemention

* go back to sharing DatapointsView component

* export

* lint and simplify

* .

* reorg

* fix missing key issue

* fix tests

* move store methods out of datapointsview component

* pr

* more pr

* lint
  • Loading branch information
terryttsai authored Nov 30, 2022
1 parent b7c01c6 commit e7be62b
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 120 deletions.
27 changes: 23 additions & 4 deletions agency-dashboard/src/DashboardView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import { ReactComponent as LeftArrowIcon } from "@justice-counts/common/assets/l
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 { transformDataForMetricInsights } from "@justice-counts/common/components/DataViz/utils";
import { COMMON_DESKTOP_WIDTH } from "@justice-counts/common/components/GlobalStyles";
import { DataVizTimeRangesMap } from "@justice-counts/common/types";
import { observer } from "mobx-react-lite";
import React, { useEffect, useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
Expand Down Expand Up @@ -91,7 +93,16 @@ const DashboardView = () => {
const navigate = useNavigate();
const params = useParams();
const agencyId = Number(params.id);
const { datapointsStore } = useStore();
const { datapointsStore, dataVizStore } = useStore();

const {
timeRange,
disaggregationName,
countOrPercentageView,
setTimeRange,
setDisaggregationName,
setCountOrPercentageView,
} = dataVizStore;

const { search } = useLocation();
const query = new URLSearchParams(search);
Expand Down Expand Up @@ -151,16 +162,18 @@ const DashboardView = () => {
const metricName =
datapointsStore.metricKeyToDisplayName[metricKey] || metricKey;

const datapoints =
datapointsStore.datapointsByMetric[metricKey]?.aggregate || [];
const filteredAggregateData = transformDataForMetricInsights(
datapointsStore.datapointsByMetric[metricKey]?.aggregate || [],
DataVizTimeRangesMap[dataVizStore.timeRange]
);

return (
<Container key={metricKey}>
<HeaderBar />
<LeftPanel>
<BackButton onClick={() => navigate(`/agency/${agencyId}`)} />
<MetricTitle>{metricName}</MetricTitle>
<MetricInsights datapoints={datapoints} />
<MetricInsights datapoints={filteredAggregateData} />
<MetricOverviewContent>
Measures the number of individuals with at least one parole violation
during the reporting period.
Expand All @@ -181,6 +194,12 @@ const DashboardView = () => {
dimensionNamesByDisaggregation={
datapointsStore.dimensionNamesByMetricAndDisaggregation[metricKey]
}
timeRange={timeRange}
disaggregationName={disaggregationName}
countOrPercentageView={countOrPercentageView}
setTimeRange={setTimeRange}
setDisaggregationName={setDisaggregationName}
setCountOrPercentageView={setCountOrPercentageView}
metricNames={metricNames}
onMetricsSelect={(metric) =>
navigate(`/agency/${agencyId}/dashboard?metric=${metric}`)
Expand Down
5 changes: 5 additions & 0 deletions agency-dashboard/src/stores/RootStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

import DataVizStore from "@justice-counts/common/stores/DataVizStore";

import DatapointsStore from "./DatapointsStore";

class RootStore {
datapointsStore: DatapointsStore;

dataVizStore: DataVizStore;

constructor() {
this.datapointsStore = new DatapointsStore();
this.dataVizStore = new DataVizStore();
}
}

Expand Down
5 changes: 2 additions & 3 deletions common/components/DataViz/DatapointsTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ const reportFrequencyBadgeColors: BadgeColorMapping = {
export const DatapointsTitle: React.FC<{
metricName: string;
metricFrequency?: string;
insights?: string;
}> = ({ metricName, metricFrequency, insights }) => {
}> = ({ metricName, metricFrequency }) => {
const [titleWidth, setTitleWidth] = useState<number>(0);
const titleRef = useRef<HTMLDivElement | null>(null);

Expand All @@ -44,7 +43,7 @@ export const DatapointsTitle: React.FC<{

return (
<MetricTitleWrapper>
<MetricTitle ref={titleRef} titleWidth={titleWidth} title={insights}>
<MetricTitle ref={titleRef} titleWidth={titleWidth}>
{metricName}
{metricFrequency && (
<Badge
Expand Down
148 changes: 70 additions & 78 deletions common/components/DataViz/DatapointsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,7 @@

import { ReactComponent as GridIcon } from "@justice-counts/common/assets/grid-icon.svg";
import BarChart from "@justice-counts/common/components/DataViz/BarChart";
import Legend from "@justice-counts/common/components/DataViz/Legend";
import {
DatapointsGroupedByAggregateAndDisaggregations,
DatapointsViewSetting,
DataVizAggregateName,
DataVizTimeRangesMap,
DimensionNamesByDisaggregation,
ReportFrequency,
} from "@justice-counts/common/types";
import { DropdownMenu, DropdownToggle } from "@recidiviz/design-system";
import React, { useEffect } from "react";

import { DatapointsTitle } from "./DatapointsTitle";
import { DatapointsTitle } from "@justice-counts/common/components/DataViz/DatapointsTitle";
import {
BottomMetricInsightsContainer,
DatapointsViewContainer,
Expand All @@ -47,17 +35,26 @@ import {
MobileSelectMetricsModalContainer,
SelectMetricsButtonContainer,
SelectMetricsButtonText,
} from "./DatapointsView.styles";
import { MetricInsights } from "./MetricInsights";
} from "@justice-counts/common/components/DataViz/DatapointsView.styles";
import Legend from "@justice-counts/common/components/DataViz/Legend";
import { MetricInsights } from "@justice-counts/common/components/DataViz/MetricInsights";
import {
filterByTimeRange,
filterNullDatapoints,
getAverageTotalValue,
getLatestDateFormatted,
getPercentChangeOverTime,
sortDatapointDimensions,
transformData,
} from "./utils";
transformDataForBarChart,
transformDataForMetricInsights,
} from "@justice-counts/common/components/DataViz/utils";
import {
DatapointsGroupedByAggregateAndDisaggregations,
DataVizAggregateName,
DataVizCountOrPercentageView,
DataVizTimeRangeDisplayName,
DataVizTimeRangesMap,
DimensionNamesByDisaggregation,
NoDisaggregationOption,
ReportFrequency,
} from "@justice-counts/common/types";
import { DropdownMenu, DropdownToggle } from "@recidiviz/design-system";
import React, { useEffect } from "react";

const noDisaggregationOption = "None";

Expand Down Expand Up @@ -92,8 +89,14 @@ const SelectMetricButtonDropdown: React.FC<{
);

export const DatapointsView: React.FC<{
datapointsGroupedByAggregateAndDisaggregations: DatapointsGroupedByAggregateAndDisaggregations;
dimensionNamesByDisaggregation: DimensionNamesByDisaggregation;
datapointsGroupedByAggregateAndDisaggregations?: DatapointsGroupedByAggregateAndDisaggregations;
dimensionNamesByDisaggregation?: DimensionNamesByDisaggregation;
timeRange: DataVizTimeRangeDisplayName;
disaggregationName: string;
countOrPercentageView: DataVizCountOrPercentageView;
setTimeRange: (timeRange: DataVizTimeRangeDisplayName) => void;
setDisaggregationName: (disaggregation: string) => void;
setCountOrPercentageView: (viewSetting: DataVizCountOrPercentageView) => void;
metricName?: string;
metricFrequency?: ReportFrequency;
metricNames?: string[];
Expand All @@ -103,63 +106,64 @@ export const DatapointsView: React.FC<{
}> = ({
datapointsGroupedByAggregateAndDisaggregations,
dimensionNamesByDisaggregation,
timeRange,
disaggregationName,
countOrPercentageView,
setTimeRange,
setDisaggregationName,
setCountOrPercentageView,
metricName,
metricFrequency,
metricNames,
onMetricsSelect,
showBottomMetricInsights = false,
resizeHeight = false,
}) => {
const [selectedTimeRange, setSelectedTimeRange] =
React.useState<string>("All");
const [selectedDisaggregation, setSelectedDisaggregation] =
React.useState<string>(noDisaggregationOption);
const [datapointsViewSetting, setDatapointsViewSetting] =
React.useState<DatapointsViewSetting>("Count");
const [mobileSelectMetricsVisible, setMobileSelectMetricsVisible] =
React.useState<boolean>(false);

const data =
(selectedDisaggregation !== noDisaggregationOption &&
const selectedData =
(disaggregationName !== NoDisaggregationOption &&
Object.values(
datapointsGroupedByAggregateAndDisaggregations.disaggregations[
selectedDisaggregation
datapointsGroupedByAggregateAndDisaggregations?.disaggregations[
disaggregationName
] || {}
)) ||
datapointsGroupedByAggregateAndDisaggregations?.aggregate ||
[];
const isAnnual = data[0]?.frequency === "ANNUAL";
const disaggregations = Object.keys(dimensionNamesByDisaggregation);
const isAnnual = selectedData[0]?.frequency === "ANNUAL";
const disaggregations = Object.keys(dimensionNamesByDisaggregation || {});
const disaggregationOptions = [...disaggregations];
disaggregationOptions.unshift(noDisaggregationOption);
const dimensionNames =
selectedDisaggregation !== noDisaggregationOption
? (dimensionNamesByDisaggregation[selectedDisaggregation] || [])
disaggregationName !== noDisaggregationOption
? (dimensionNamesByDisaggregation?.[disaggregationName] || [])
.slice() // Must use slice() before sorting a MobX observableArray
.sort(sortDatapointDimensions)
: [DataVizAggregateName];

const selectedTimeRangeValue = DataVizTimeRangesMap[selectedTimeRange];
const selectedTimeRangeValue = DataVizTimeRangesMap[timeRange];

useEffect(() => {
if (isAnnual && selectedTimeRangeValue === 6) {
setSelectedTimeRange("All");
setTimeRange("All");
}
if (!disaggregationOptions.includes(selectedDisaggregation)) {
setSelectedDisaggregation(noDisaggregationOption);
setDatapointsViewSetting("Count");
if (!disaggregationOptions.includes(disaggregationName)) {
setDisaggregationName(noDisaggregationOption);
setCountOrPercentageView("Count");
}
if (selectedDisaggregation === noDisaggregationOption) {
setDatapointsViewSetting("Count");
if (disaggregationName === noDisaggregationOption) {
setCountOrPercentageView("Count");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [datapointsGroupedByAggregateAndDisaggregations]);

useEffect(() => {
if (selectedDisaggregation === noDisaggregationOption) {
setDatapointsViewSetting("Count");
if (disaggregationName === noDisaggregationOption) {
setCountOrPercentageView("Count");
}
}, [selectedDisaggregation]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disaggregationName]);

/** Prevent body from scrolling when modal is open */
useEffect(() => {
Expand All @@ -174,22 +178,22 @@ export const DatapointsView: React.FC<{
const renderChartForMetric = () => {
return (
<BarChart
data={transformData(
data,
data={transformDataForBarChart(
selectedData,
selectedTimeRangeValue,
datapointsViewSetting
countOrPercentageView
)}
dimensionNames={dimensionNames}
percentageView={
!!selectedDisaggregation && datapointsViewSetting === "Percentage"
!!disaggregationName && countOrPercentageView === "Percentage"
}
resizeHeight={resizeHeight}
/>
);
};

const renderLegend = () => {
if (selectedDisaggregation !== noDisaggregationOption) {
if (disaggregationName !== noDisaggregationOption) {
return <Legend names={dimensionNames} />;
}
return <Legend />;
Expand All @@ -200,7 +204,7 @@ export const DatapointsView: React.FC<{
<DatapointsViewControlsContainer>
<DatapointsViewControlsDropdown
title="Date Range"
selectedValue={selectedTimeRange}
selectedValue={timeRange}
options={
isAnnual
? Object.keys(DataVizTimeRangesMap).filter(
Expand All @@ -209,49 +213,38 @@ export const DatapointsView: React.FC<{
: Object.keys(DataVizTimeRangesMap)
}
onSelect={(key) => {
setSelectedTimeRange(key);
setTimeRange(key as DataVizTimeRangeDisplayName);
}}
/>
{disaggregationOptions.length > 1 && (
<DatapointsViewControlsDropdown
title="Disaggregation"
selectedValue={selectedDisaggregation}
selectedValue={disaggregationName}
options={disaggregationOptions}
onSelect={(key) => {
setSelectedDisaggregation(key);
setDisaggregationName(key);
}}
/>
)}
{selectedDisaggregation !== noDisaggregationOption && (
{disaggregationName !== noDisaggregationOption && (
<DatapointsViewControlsDropdown
title="View"
selectedValue={datapointsViewSetting}
selectedValue={countOrPercentageView}
options={["Count", "Percentage"]}
onSelect={(key) => {
setDatapointsViewSetting(key as DatapointsViewSetting);
setCountOrPercentageView(key as DataVizCountOrPercentageView);
}}
/>
)}
</DatapointsViewControlsContainer>
);
};

// insights data
const dataSelectedInTimeRange = filterNullDatapoints(
filterByTimeRange(
datapointsGroupedByAggregateAndDisaggregations?.aggregate || [],
selectedTimeRangeValue
)
);
const percentChange = getPercentChangeOverTime(dataSelectedInTimeRange);
const avgValue = getAverageTotalValue(dataSelectedInTimeRange, isAnnual);
const mostRecentValue = getLatestDateFormatted(
dataSelectedInTimeRange,
isAnnual
const filteredAggregateData = transformDataForMetricInsights(
datapointsGroupedByAggregateAndDisaggregations?.aggregate || [],
selectedTimeRangeValue
);

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

return (
<DatapointsViewContainer>
<DatapointsViewHeaderWrapper>
Expand All @@ -260,10 +253,9 @@ export const DatapointsView: React.FC<{
<DatapointsTitle
metricName={metricName}
metricFrequency={metricFrequency}
insights={chartViewInsightsInfo}
/>
{data.length > 0 && (
<MetricInsights datapoints={dataSelectedInTimeRange} />
{selectedData.length > 0 && (
<MetricInsights datapoints={filteredAggregateData} />
)}
</MetricHeaderWrapper>
)}
Expand All @@ -284,7 +276,7 @@ export const DatapointsView: React.FC<{
{renderLegend()}
{showBottomMetricInsights && (
<BottomMetricInsightsContainer>
<MetricInsights datapoints={dataSelectedInTimeRange} />
<MetricInsights datapoints={filteredAggregateData} />
</BottomMetricInsightsContainer>
)}
<MobileSelectMetricsButtonContainer>
Expand Down
Loading

0 comments on commit e7be62b

Please sign in to comment.