Skip to content
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
19 changes: 13 additions & 6 deletions components/Package/Charts/VersionDownloadsChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { useRouter } from 'next/router';
import { useState } from 'react';
import { View } from 'react-native';

import { Label } from '~/common/styleguide';
import ChartTooltip from '~/components/Package/Charts/ChartTooltip';
import HoveredBarOutline from '~/components/Package/Charts/HoveredBarOutline';
import { Label, useLayout } from '~/common/styleguide';
import { type NpmPerVersionDownloads, type PackageVersionsData } from '~/types';
import { replaceQueryParam } from '~/util/queryParams';
import { NUMBER_FORMATTER, pluralize } from '~/util/strings';
import tw from '~/util/tailwind';

import ChartTooltip from './ChartTooltip';
import HoveredBarOutline from './HoveredBarOutline';
import {
type VersionsAggregatedChartMode,
type VersionsChartData,
Expand All @@ -28,6 +28,7 @@ import {
DEFAULT_CHART_MODE,
getChartLeftMargin,
getLargestSeriesLength,
getLatestVersionDownloadsPercentage,
getPrimaryChartLabel,
LABEL_TO_BAR_GAP,
mapVersionDistTags,
Expand All @@ -46,6 +47,7 @@ const OTHER_BAR_GRADIENT_ID = 'version-downloads-chart-other';

export default function VersionDownloadsChart({ npmDownloads, registryData }: Props) {
const isDark = tw.prefixMatch('dark');
const { isSmallScreen } = useLayout();

const router = useRouter();
const routeMode = parseChartMode(router.query[CHART_MODE_QUERY_PARAM]);
Expand All @@ -59,6 +61,7 @@ export default function VersionDownloadsChart({ npmDownloads, registryData }: Pr
const series = chartSeriesByMode[mode];
const seriesByLabel = keyBy(series, item => item.label);
const leftMargin = getChartLeftMargin(series);
const downloadsPercentage = getLatestVersionDownloadsPercentage(baseSeries, registryData, mode);

if (!series.length) {
return (
Expand Down Expand Up @@ -88,13 +91,17 @@ export default function VersionDownloadsChart({ npmDownloads, registryData }: Pr
return (
// @ts-expect-error Ref type miss-match
<View style={tw`w-full gap-3`} ref={parentRef}>
<VersionDownloadsChartModes mode={mode} onModeChange={handleModeChange} />
<VersionDownloadsChartModes
mode={mode}
onModeChange={handleModeChange}
downloadsPercentage={downloadsPercentage}
/>
<XYChart
width={width}
height={height}
xScale={{ type: 'linear', domain: xDomain }}
yScale={{ type: 'band', paddingInner: 0.23, paddingOuter: 0.15 }}
margin={{ top: 2, right: 12, bottom: 20, left: leftMargin }}>
margin={{ top: 2, right: 8, bottom: 20, left: leftMargin }}>
<LinearGradient
id={BAR_GRADIENT_ID}
from={isDark ? 'var(--primary-darker)' : 'var(--primary-darker)'}
Expand Down Expand Up @@ -134,7 +141,7 @@ export default function VersionDownloadsChart({ npmDownloads, registryData }: Pr
/>
<Axis
orientation="bottom"
numTicks={5}
numTicks={isSmallScreen ? 3 : 5}
hideAxisLine
hideTicks
tickFormat={value => NUMBER_FORMATTER.format(value)}
Expand Down
54 changes: 35 additions & 19 deletions components/Package/Charts/VersionDownloadsChartModes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { type VersionsChartMode } from './types';
type Props = {
mode: VersionsChartMode;
onModeChange: (mode: VersionsChartMode) => void;
downloadsPercentage: number;
};

const CHART_MODES: { key: VersionsChartMode; label: string; shortLabel: string }[] = [
Expand All @@ -17,27 +18,42 @@ const CHART_MODES: { key: VersionsChartMode; label: string; shortLabel: string }
{ key: 'major', label: 'By major', shortLabel: 'Major' },
];

export default function VersionDownloadsChartModes({ mode, onModeChange }: Props) {
export default function VersionDownloadsChartModes({
mode,
onModeChange,
downloadsPercentage,
}: Props) {
const { isSmallScreen } = useLayout();
return (
<View style={tw`flex-row gap-2 self-start`}>
{CHART_MODES.map(({ key, label, shortLabel }) => (
<Button
key={key}
onPress={() => onModeChange(key)}
style={[
tw`border bg-default px-3 py-1.5`,
isSmallScreen ? tw`min-w-[62px]` : tw`min-w-[92px]`,
key === mode
? tw`border-[#bde0f7] bg-primary-hover dark:border-[#203b4d]`
: tw`border-default`,
]}>
<Label
style={key === mode ? tw`text-primary-darker dark:text-primary` : tw`text-secondary`}>
{isSmallScreen ? shortLabel : label}
</Label>
</Button>
))}
<View style={[tw`w-full flex-row items-center gap-2`, isSmallScreen && tw`flex-col`]}>
<View style={[tw`flex-1 flex-row flex-wrap gap-2`, isSmallScreen && tw`w-full gap-[3.3%]`]}>
{CHART_MODES.map(({ key, label, shortLabel }) => (
<Button
key={key}
onPress={() => onModeChange(key)}
containerStyle={isSmallScreen ? tw`min-w-[30%] flex-1` : tw`min-w-[92px]`}
style={[
tw`border bg-default px-3 py-1.5`,
key === mode
? tw`border-[#bde0f7] bg-primary-hover dark:border-[#203b4d]`
: tw`border-default`,
]}>
<Label
style={key === mode ? tw`text-primary-darker dark:text-primary` : tw`text-secondary`}>
{isSmallScreen ? shortLabel : label}
</Label>
</Button>
))}
</View>
<Label style={tw`shrink-0 text-right font-light text-secondary`}>
<span style={tw`font-normal text-black dark:text-white`}>
{downloadsPercentage.toLocaleString('en-US', {
maximumFractionDigits: 1,
})}
%
</span>{' '}
on latest {mode}
</Label>
</View>
);
}
42 changes: 41 additions & 1 deletion components/Package/Charts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const DEFAULT_CHART_MODE: VersionsChartMode = 'version';
export const LABEL_TO_BAR_GAP = 6;

const VERSIONS_LIMIT = 12;
const MIN_YAXIS_LABEL_WIDTH = 72;
const MIN_YAXIS_LABEL_WIDTH = 60;
const OTHER_VERSION_LABEL = 'Other';
const TEXT_MEASURE_CANVAS_ID = 'measure-canvas';
const Y_AXIS_LABEL_WIDTH_CACHE = new Map<string, number>();
Expand Down Expand Up @@ -60,6 +60,46 @@ export function buildChartSeriesByMode(baseSeries: VersionsChartData[]): Version
};
}

export function getLatestVersionDownloadsPercentage(
baseSeries: VersionsChartData[],
registryData: PackageVersionsData,
mode: VersionsChartMode
) {
const totalDownloads = sumBy(baseSeries, item => item.downloads);

if (!totalDownloads) {
return 0;
}

const latestVersion = registryData['dist-tags'].latest;

if (!latestVersion) {
return 0;
}

if (mode === 'version') {
return (
((baseSeries.find(item => item.label === latestVersion)?.downloads ?? 0) / totalDownloads) *
100
);
}

const latestAggregateLabel = getAggregatedSemverLabel(latestVersion, mode);

if (!latestAggregateLabel) {
return (
((baseSeries.find(item => item.label === latestVersion)?.downloads ?? 0) / totalDownloads) *
100
);
}

const latestAggregateDownloads = sumBy(baseSeries, item =>
getAggregatedSemverLabel(item.label, mode) === latestAggregateLabel ? item.downloads : 0
);

return (latestAggregateDownloads / totalDownloads) * 100;
}

export function createVersionChartEntry(
version: string,
downloads = 0,
Expand Down