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

add csv download link to speed, prediction accuracy, service, ridership #935

Merged
merged 7 commits into from
Jan 15, 2024
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
56 changes: 15 additions & 41 deletions common/components/buttons/DownloadButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,26 @@ import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFileArrowDown } from '@fortawesome/free-solid-svg-icons';
import classNames from 'classnames';
import type { DataPoint } from '../../types/dataPoints';
import type { AggregateDataPoint, Location } from '../../types/charts';
import type { Location } from '../../types/charts';
import { lineColorTextHover } from '../../styles/general';
import { useDelimitatedRoute } from '../../utils/router';

const directionAbbrs = {
northbound: 'NB',
southbound: 'SB',
eastbound: 'EB',
westbound: 'WB',
inbound: 'IB',
outbound: 'OB',
};

function filename(
datasetName: string,
location: Location,
bothStops: boolean,
startDate: string,
endDate?: string
) {
// CharlesMGH-SB_dwells_20210315.csv
// CentralSquareCambridge-MelneaCassWashington_traveltimesByHour-weekday_20200101-20201231.csv
// BostonUniversityWest-EB_headways_20161226-20170328.csv
const fromStop = location.from.replace(/[^A-z]/g, '');
const toStop = location.to.replace(/[^A-z]/g, '');
const dir = directionAbbrs[location.direction];
const where = `${fromStop}-${bothStops ? toStop : dir}`;

const what = datasetName;

const date1 = startDate.replaceAll('-', '');
const date2 = endDate ? `-${endDate.replaceAll('-', '')}` : '';
const when = `${date1}${date2}`;

return `${where}_${what}_${when}.csv`;
}
import { getCsvFilename } from '../../utils/csv';

interface DownloadButtonProps {
datasetName: string;
data: (DataPoint | AggregateDataPoint)[];
location: Location;
bothStops: boolean;
data: Record<string, any>[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi: unknown should work here instead of any and then the linter won't complain.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah in general we should prefer unknown as it throws more warnings whereas any basically disables checks.
Probably should add some linting for prefer unknown

startDate: string;
includeBothStopsForLocation?: boolean;
location?: Location;
endDate?: string;
}

export const DownloadButton: React.FC<DownloadButtonProps> = ({
datasetName,
data,
location,
bothStops,
includeBothStopsForLocation,
startDate,
location,
endDate,
}) => {
const { line } = useDelimitatedRoute();
Expand All @@ -65,7 +32,14 @@ export const DownloadButton: React.FC<DownloadButtonProps> = ({
className={'csv-link'}
data={data}
title={'Download data as CSV'}
filename={filename(datasetName, location, bothStops, startDate, endDate)}
filename={getCsvFilename({
datasetName,
includeBothStopsForLocation,
startDate,
line,
location,
endDate,
})}
>
<FontAwesomeIcon
icon={faFileArrowDown}
Expand Down
4 changes: 2 additions & 2 deletions common/components/charts/AggregateLineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const AggregateLineChart: React.FC<AggregateLineProps> = ({
data,
location,
pointField,
bothStops = false,
includeBothStopsForLocation = false,
fname,
timeUnit,
timeFormat,
Expand Down Expand Up @@ -179,7 +179,7 @@ export const AggregateLineChart: React.FC<AggregateLineProps> = ({
data={data}
datasetName={fname}
location={location}
bothStops={bothStops}
includeBothStopsForLocation={includeBothStopsForLocation}
startDate={startDate}
/>
)}
Expand Down
4 changes: 2 additions & 2 deletions common/components/charts/SingleDayLineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const SingleDayLineChart: React.FC<SingleDayLineProps> = ({
pointField,
benchmarkField,
fname,
bothStops = false,
includeBothStopsForLocation = false,
location,
units,
showLegend = true,
Expand Down Expand Up @@ -229,7 +229,7 @@ export const SingleDayLineChart: React.FC<SingleDayLineProps> = ({
data={data}
datasetName={fname}
location={location}
bothStops={bothStops}
includeBothStopsForLocation={includeBothStopsForLocation}
startDate={date}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import type { ChartData } from 'chart.js';

import { enUS } from 'date-fns/locale';
import { useBreakpoint } from '../../../hooks/useBreakpoint';
import { ChartBorder } from '../ChartBorder';
import { ChartDiv } from '../ChartDiv';
import { CHART_COLORS, COLORS } from '../../../constants/colors';

Expand Down Expand Up @@ -282,9 +281,5 @@ export const TimeSeriesChart = <Data extends Dataset[]>(props: Props<Data>) => {
);
}, [isMobile, chartJsData, chartJsOptions, chartJsPlugins]);

return (
<ChartBorder>
<ChartDiv isMobile={isMobile}>{chart}</ChartDiv>
</ChartBorder>
);
return <ChartDiv isMobile={isMobile}>{chart}</ChartDiv>;
};
4 changes: 2 additions & 2 deletions common/types/charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export interface LineProps {
chartId: string;
location: Location;
pointField: PointField; // X value
bothStops?: boolean;
includeBothStopsForLocation?: boolean;
fname: DataName;
showLegend?: boolean;
}
Expand Down Expand Up @@ -111,7 +111,7 @@ export interface HeadwayHistogramProps {
date: string | undefined;
location: Location;
isLoading: boolean;
bothStops?: boolean;
includeBothStopsForLocation?: boolean;
fname: DataName;
showLegend?: boolean;
metricField: MetricField;
Expand Down
2 changes: 2 additions & 0 deletions common/types/dataPoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export interface DeliveredTripMetrics {
miles_covered: number;
total_time: number;
count: number;
miles_per_hour?: string;
}

export type LineSegmentData = {
Expand Down Expand Up @@ -136,6 +137,7 @@ export interface TimePrediction {
weekly: string;
num_accurate_predictions: number;
num_predictions: number;
accuracy_percentage?: string;
}

export type PredictionBin = '0-3 min' | '3-6 min' | '6-12 min' | '12-30 min';
Expand Down
61 changes: 61 additions & 0 deletions common/utils/csv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { flatten } from 'lodash';
import type { Location } from '../types/charts';
import type { DeliveredTripMetrics, TimePredictionWeek } from '../types/dataPoints';

const directionAbbrs = {
northbound: 'NB',
southbound: 'SB',
eastbound: 'EB',
westbound: 'WB',
inbound: 'IB',
outbound: 'OB',
};

type GetCsvFilenameOptions = {
datasetName: string;
startDate: string;
endDate?: string;
line?: string;
location?: Location;
includeBothStopsForLocation?: boolean | undefined;
};

export function getCsvFilename(options: GetCsvFilenameOptions) {
const { datasetName, startDate, endDate, line, location, includeBothStopsForLocation } = options;
// CharlesMGH-SB_dwells_20210315.csv
// CentralSquareCambridge-MelneaCassWashington_traveltimesByHour-weekday_20200101-20201231.csv
// BostonUniversityWest-EB_headways_20161226-20170328.csv
const fromStop = location?.from.replace(/[^A-z]/g, '');
const toStop = location?.to.replace(/[^A-z]/g, '');
const dir = location && directionAbbrs[location.direction];

//Location does not exist on all widgets - in that case, 'where' will just be the name of the line
const where = location ? `${fromStop}-${includeBothStopsForLocation ? toStop : dir}` : line;
const what = datasetName;
const date1 = startDate.replaceAll('-', '');
const date2 = endDate ? `-${endDate.replaceAll('-', '')}` : '';
const when = `${date1}${date2}`;

return `${where}_${what}_${when}.csv`;
}

export const addAccuracyPercentageToData = (data: TimePredictionWeek[]) => {
const predictionsList = flatten(data.map(({ prediction }) => prediction));

const newData = predictionsList.map((item) => {
const accuracyPercentage = (item?.num_accurate_predictions / item?.num_predictions) * 100;
return { ...item, accuracy_percentage: accuracyPercentage.toFixed(1) };
});

return newData;
};

export const addMPHToSpeedData = (data: DeliveredTripMetrics[]) => {
const newData = data.map((item) => {
const hours = item.total_time / 3600;
const mph = item.miles_covered / hours;
return { ...item, miles_per_hour: mph.toFixed(1) };
});

return newData;
};
2 changes: 1 addition & 1 deletion modules/dwells/charts/DwellsAggregateChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const DwellsAggregateChart: React.FC<DwellsAggregateChartProps> = ({
endDate={endDate}
fillColor={CHART_COLORS.FILL}
location={getLocationDetails(fromStation, toStation)}
bothStops={false}
includeBothStopsForLocation={false}
fname="dwells"
yUnit="Minutes"
/>
Expand Down
2 changes: 1 addition & 1 deletion modules/headways/charts/HeadwaysAggregateChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const HeadwaysAggregateChart: React.FC<HeadwaysAggregateChartProps> = ({
endDate={endDate}
fillColor={CHART_COLORS.FILL}
location={getLocationDetails(fromStation, toStation)}
bothStops={false}
includeBothStopsForLocation={false}
fname="headways"
yUnit="Minutes"
/>
Expand Down
15 changes: 15 additions & 0 deletions modules/predictions/charts/PredictionsGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { ChartDiv } from '../../../common/components/charts/ChartDiv';
import { PEAK_SPEED } from '../../../common/constants/baselines';
import { getRemainingBlockAnnotation } from '../../service/utils/graphUtils';
import { DATE_FORMAT, TODAY } from '../../../common/constants/dates';
import { DownloadButton } from '../../../common/components/buttons/DownloadButton';
import { addAccuracyPercentageToData } from '../../../common/utils/csv';

interface PredictionsGraphProps {
data: TimePredictionWeek[];
Expand Down Expand Up @@ -61,6 +63,8 @@ export const PredictionsGraph: React.FC<PredictionsGraphProps> = ({
}, 0),
}));

const dataWithPercentage = addAccuracyPercentageToData(data);

return (
<ChartBorder>
<ChartDiv isMobile={isMobile}>
Expand Down Expand Up @@ -207,6 +211,17 @@ export const PredictionsGraph: React.FC<PredictionsGraphProps> = ({
]}
/>
</ChartDiv>
<div className="flex flex-row items-end justify-end gap-4">
{startDate && (
<DownloadButton
data={dataWithPercentage}
datasetName="ridership predictions"
includeBothStopsForLocation={false}
startDate={startDate}
endDate={endDate}
/>
)}
</div>
</ChartBorder>
);
};
12 changes: 12 additions & 0 deletions modules/ridership/RidershipGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useBreakpoint } from '../../common/hooks/useBreakpoint';
import { watermarkLayout } from '../../common/constants/charts';
import { ChartBorder } from '../../common/components/charts/ChartBorder';
import { ChartDiv } from '../../common/components/charts/ChartDiv';
import { DownloadButton } from '../../common/components/buttons/DownloadButton';

interface RidershipGraphProps {
data: RidershipCount[];
Expand Down Expand Up @@ -198,6 +199,17 @@ export const RidershipGraph: React.FC<RidershipGraphProps> = ({
]}
/>
</ChartDiv>
<div className="flex flex-row items-end justify-end gap-4">
{startDate && (
<DownloadButton
data={data}
datasetName="ridership"
includeBothStopsForLocation={false}
startDate={startDate}
endDate={endDate}
/>
)}
</div>
</ChartBorder>
);
}, [
Expand Down
38 changes: 27 additions & 11 deletions modules/service/ServiceGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { useDelimitatedRoute } from '../../common/utils/router';
import { PEAK_SCHEDULED_SERVICE } from '../../common/constants/baselines';
import type { DeliveredTripMetrics, ScheduledService } from '../../common/types/dataPoints';
import type { ParamsType } from '../speed/constants/speeds';
import { getShuttlingBlockAnnotations } from './utils/graphUtils';
import { ChartBorder } from '../../common/components/charts/ChartBorder';
import { DownloadButton } from '../../common/components/buttons/DownloadButton';
import { ScheduledAndDeliveredGraph } from './ScheduledAndDeliveredGraph';
import { getShuttlingBlockAnnotations } from './utils/graphUtils';

interface ServiceGraphProps {
config: ParamsType;
Expand Down Expand Up @@ -68,15 +70,29 @@ export const ServiceGraph: React.FC<ServiceGraphProps> = (props: ServiceGraphPro
}, [data, peak]);

return (
<ScheduledAndDeliveredGraph
valueAxisLabel="Round trips"
scheduled={scheduled}
delivered={delivered}
blocks={blocks}
benchmarks={benchmarks}
startDate={startDate}
endDate={endDate}
agg={config.agg}
/>
<ChartBorder>
<ScheduledAndDeliveredGraph
valueAxisLabel="Round trips"
scheduled={scheduled}
delivered={delivered}
blocks={blocks}
benchmarks={benchmarks}
startDate={startDate}
endDate={endDate}
agg={config.agg}
/>

<div className="flex flex-row items-end justify-end gap-4">
{startDate && (
<DownloadButton
data={data}
datasetName="service"
includeBothStopsForLocation={false}
startDate={startDate}
endDate={endDate}
/>
)}
</div>
</ChartBorder>
);
};
19 changes: 11 additions & 8 deletions modules/service/ServiceHoursGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useMemo } from 'react';

import type { FetchServiceHoursResponse } from '../../common/types/api';
import type { AggType } from '../speed/constants/speeds';
import { ChartBorder } from '../../common/components/charts/ChartBorder';
import { ScheduledAndDeliveredGraph } from './ScheduledAndDeliveredGraph';

interface ServiceHoursGraphProps {
Expand Down Expand Up @@ -32,13 +33,15 @@ export const ServiceHoursGraph: React.FC<ServiceHoursGraphProps> = (
}, [serviceHours]);

return (
<ScheduledAndDeliveredGraph
scheduled={scheduled}
delivered={delivered}
startDate={startDate}
endDate={endDate}
agg={agg}
valueAxisLabel="Service hours"
/>
<ChartBorder>
<ScheduledAndDeliveredGraph
scheduled={scheduled}
delivered={delivered}
startDate={startDate}
endDate={endDate}
agg={agg}
valueAxisLabel="Service hours"
/>
</ChartBorder>
);
};
Loading