Skip to content
111 changes: 62 additions & 49 deletions frontend/routes/package/(_islands)/DownloadChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export function DownloadChart(props: Props) {

const getChartOptions = (
isDarkMode: boolean,
aggregationPeriod: AggregationPeriod = "weekly",
) => ({
chart: {
type: "area",
Expand Down Expand Up @@ -57,7 +56,7 @@ export function DownloadChart(props: Props) {
curve: "straight",
width: 1.7,
},
series: getSeries(props.downloads, aggregationPeriod),
series: getSeries(props.downloads, "weekly"),
xaxis: {
type: "datetime",
tooltip: {
Expand Down Expand Up @@ -137,37 +136,52 @@ export function DownloadChart(props: Props) {
return (
<div class="relative">
{graphRendered && (
<div className="absolute flex items-center gap-2 pt-1 text-sm pl-5 z-20">
<label htmlFor="aggregationPeriod" className="text-secondary">
Aggregation Period:
</label>
<select
id="aggregationPeriod"
onChange={(e) => {
const isDarkMode = document.documentElement.classList.contains(
"dark",
);
const newAggregationPeriod = e.currentTarget
.value as AggregationPeriod;
<div className="absolute flex md:flex-col md:-top-4 gap-2 pt-1 text-sm pl-5 z-20">
<div className="flex items-center gap-2">
<label htmlFor="aggregationPeriod" className="text-secondary">
Aggregation Period:
</label>
<select
id="aggregationPeriod"
onChange={(e) => {
chartRef.current?.updateSeries(
getSeries(
props.downloads,
e.currentTarget.value as AggregationPeriod,
),
);
}}
className="input-container input select w-20"
>
<option value="daily">Daily</option>
<option value="weekly" selected>
Weekly
</option>
<option value="monthly">Monthly</option>
</select>
</div>
<div className="flex items-center gap-2">
<label htmlFor="displayAs" className="text-secondary">
Display As
</label>
<select
id="displayAs"
onChange={(e) => {
const newDisplay = e.currentTarget.value === "stacked";

// Update chart with new options including the new aggregation period
chartRef.current?.updateOptions(
getChartOptions(isDarkMode, newAggregationPeriod),
);

chartRef.current?.updateSeries(
getSeries(
props.downloads,
e.currentTarget.value as AggregationPeriod,
),
);
}}
className="input-container input select w-20"
>
<option value="daily">Daily</option>
<option value="weekly" selected>Weekly</option>
<option value="monthly">Monthly</option>
</select>
// Update chart with new options including the new stacked display
chartRef.current?.updateOptions(
{ chart: { stacked: newDisplay } },
);
}}
className="input-container input select w-24"
>
<option value="stacked" selected>Stacked</option>
<option value="unstacked">
Unstacked
</option>
</select>
</div>
</div>
)}
<div className="h-[300px] md:pt-0 pt-10 text-secondary">
Expand Down Expand Up @@ -197,19 +211,17 @@ function adjustTimePeriod(
switch (aggregation) {
case "weekly":
// start of week (Sunday) in UTC
out = new Date(Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate() - date.getUTCDay(),
));
out = new Date(
Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate() - date.getUTCDay(),
),
);
break;
case "monthly":
// first day of month in UTC
out = new Date(Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
1,
));
out = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
break;
default: // daily
out = date;
Expand All @@ -227,8 +239,8 @@ export function collectX(
xValues.add(adjustTimePeriod(point.timeBucket, aggregationPeriod));
});

return Array.from(xValues).sort((a, b) =>
new Date(a).getTime() - new Date(b).getTime()
return Array.from(xValues).sort(
(a, b) => new Date(a).getTime() - new Date(b).getTime(),
);
}

Expand All @@ -252,17 +264,18 @@ export function normalize(
}
});

return Object.entries(normalized).map((
[key, value],
) => [new Date(key).getTime(), value]);
return Object.entries(normalized).map(([key, value]) => [
new Date(key).getTime(),
value,
]);
}

function getSeries(
recentVersions: PackageDownloadsRecentVersion[],
aggregationPeriod: AggregationPeriod,
) {
const dataPointsWithDownloads = recentVersions.filter((dataPoints) =>
dataPoints.downloads.length > 0
const dataPointsWithDownloads = recentVersions.filter(
(dataPoints) => dataPoints.downloads.length > 0,
);

const dataPointsToDisplay = dataPointsWithDownloads.slice(0, 5);
Expand Down