diff --git a/frontend/routes/package/(_islands)/DownloadChart.tsx b/frontend/routes/package/(_islands)/DownloadChart.tsx index d926d055..4d72de97 100644 --- a/frontend/routes/package/(_islands)/DownloadChart.tsx +++ b/frontend/routes/package/(_islands)/DownloadChart.tsx @@ -14,99 +14,97 @@ interface Props { export type AggregationPeriod = "daily" | "weekly" | "monthly"; -export function DownloadChart(props: Props) { - const chartDivRef = useRef(null); - const chartRef = useRef(null); - const graphRendered = useSignal(false); - - const getChartOptions = ( - isDarkMode: boolean, - aggregationPeriod: AggregationPeriod = "weekly", - ) => ({ - chart: { - type: "area", - stacked: true, - animations: { - enabled: false, - }, - height: "100%", - width: "100%", - zoom: { - allowMouseWheelZoom: false, - }, - background: "transparent", - foreColor: isDarkMode ? "#a8b2bd" : "#515d6c", // jsr-gray-300 for dark mode, jsr-gray-600 for light +const getChartOptions = ( + isDarkMode: boolean, +): ApexCharts.ApexOptions => ({ + chart: { + type: "area", + stacked: true, + animations: { + enabled: false, }, - legend: { - horizontalAlign: "center", - position: "top", - showForSingleSeries: true, - labels: { - colors: isDarkMode ? "#a8b2bd" : "#515d6c", // jsr-gray-300 for dark mode, jsr-gray-600 for light - }, + height: "100%", + width: "100%", + zoom: { + allowMouseWheelZoom: false, }, + background: "transparent", + foreColor: isDarkMode ? "#a8b2bd" : "#515d6c", // jsr-gray-300 for dark mode, jsr-gray-600 for light + }, + legend: { + horizontalAlign: "right", + offsetY: -1, + position: "top", + showForSingleSeries: true, + labels: { + colors: isDarkMode ? "#a8b2bd" : "#515d6c", // jsr-gray-300 for dark mode, jsr-gray-600 for light + }, + }, + tooltip: { + theme: isDarkMode ? "dark" : "light", + }, + dataLabels: { + enabled: false, + }, + stroke: { + curve: "straight", + width: 1.7, + }, + xaxis: { + type: "datetime", tooltip: { - items: { - padding: 0, + enabled: false, + }, + labels: { + style: { + colors: isDarkMode ? "#ced3da" : "#515d6c", // jsr-gray-200 for dark mode, jsr-gray-600 for light }, - theme: isDarkMode ? "dark" : "light", }, - dataLabels: { - enabled: false, + axisBorder: { + color: isDarkMode ? "#47515c" : "#ced3da", // jsr-gray-700 for dark mode, jsr-gray-200 for light }, - stroke: { - curve: "straight", - width: 1.7, + axisTicks: { + color: isDarkMode ? "#47515c" : "#ced3da", // jsr-gray-700 for dark mode, jsr-gray-200 for light }, - series: getSeries(props.downloads, aggregationPeriod), - xaxis: { - type: "datetime", - tooltip: { - enabled: false, - }, - labels: { - style: { - colors: isDarkMode ? "#ced3da" : "#515d6c", // jsr-gray-200 for dark mode, jsr-gray-600 for light - }, - }, - axisBorder: { - color: isDarkMode ? "#47515c" : "#ced3da", // jsr-gray-700 for dark mode, jsr-gray-200 for light - }, - axisTicks: { - color: isDarkMode ? "#47515c" : "#ced3da", // jsr-gray-700 for dark mode, jsr-gray-200 for light + }, + yaxis: { + labels: { + style: { + colors: isDarkMode ? "#a8b2bd" : "#515d6c", // jsr-gray-300 for dark mode, jsr-gray-600 for light }, }, - yaxis: { - labels: { - style: { - colors: isDarkMode ? "#a8b2bd" : "#515d6c", // jsr-gray-300 for dark mode, jsr-gray-600 for light + }, + grid: { + borderColor: isDarkMode ? "#47515c" : "#e5e8eb", // jsr-gray-700 for dark mode, jsr-gray-100 for light + strokeDashArray: 3, + }, + responsive: [ + { + breakpoint: 768, + options: { + legend: { + offsetY: -30, }, }, }, - grid: { - borderColor: isDarkMode ? "#47515c" : "#e5e8eb", // jsr-gray-700 for dark mode, jsr-gray-100 for light - strokeDashArray: 3, - }, - responsive: [ - { - breakpoint: 768, - options: { - legend: { - horizontalAlign: "left", - }, - }, - }, - ], - }); + ], +}); + +export function DownloadChart(props: Props) { + const chartDivRef = useRef(null); + const chartRef = useRef(null); + const graphRendered = useSignal(false); useEffect(() => { (async () => { const { default: ApexCharts } = await import("apexcharts"); const isDarkMode = document.documentElement.classList.contains("dark"); + const initialOptions = getChartOptions(isDarkMode); + initialOptions.series = getSeries(props.downloads, "weekly"); chartRef.current = new ApexCharts( chartDivRef.current!, - getChartOptions(isDarkMode), + initialOptions, ); chartRef.current.render(); @@ -138,40 +136,64 @@ export function DownloadChart(props: Props) { return (
{graphRendered.value && ( -
- - { + chartRef.current?.updateSeries( + getSeries( + props.downloads, + e.currentTarget.value as AggregationPeriod, + ), + ); + }} + className="input-container input select w-20" + > + + + + +
+
+ + + // Update chart with new options including the new stacked display + chartRef.current?.updateOptions( + { chart: { stacked: newDisplay } }, + ); + }} + className="input-container input select w-24" + > + + + +
)} -
+ +
@@ -198,19 +220,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; @@ -228,8 +248,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(), ); } @@ -253,17 +273,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); diff --git a/frontend/routes/package/versions.tsx b/frontend/routes/package/versions.tsx index 6dc28743..578b2921 100644 --- a/frontend/routes/package/versions.tsx +++ b/frontend/routes/package/versions.tsx @@ -101,7 +101,7 @@ export default define.page(function Versions({ latestVersion={data.package.latestVersion} /> -
+