diff --git a/src/components/npm-stats/BaselineSection.tsx b/src/components/npm-stats/BaselineSection.tsx index 14b8bb34a..499557566 100644 --- a/src/components/npm-stats/BaselineSection.tsx +++ b/src/components/npm-stats/BaselineSection.tsx @@ -9,7 +9,7 @@ import { import { twMerge } from 'tailwind-merge' import { Tooltip } from '~/components/Tooltip' import { PackageSearch } from './PackageSearch' -import type { PackageGroup } from './shared' +import { getBaselineDisplayName, type PackageGroup } from './shared' import type { BaselinePreset } from '~/routes/stats/npm/-comparisons' export const BASELINE_LINE_COLOR = '#3b82f6' @@ -39,9 +39,7 @@ export function BaselineSection({ }: BaselineSectionProps) { const baselineGroups = packageGroups.filter((pg) => pg.baseline) const [showSearch, setShowSearch] = React.useState(false) - const names = baselineGroups - .map((pg) => pg.packages[0]?.name) - .filter((n): n is string => !!n) + const baselineDisplayName = getBaselineDisplayName(baselineGroups) const hasBaselines = baselineGroups.length > 0 const normalizeActive = hasBaselines && normalizeBaseline @@ -100,7 +98,7 @@ export function BaselineSection({ - {names.join(', ')} + {baselineDisplayName} diff --git a/src/components/npm-stats/NPMStatsChart.tsx b/src/components/npm-stats/NPMStatsChart.tsx index 3045d76d4..c11b037ac 100644 --- a/src/components/npm-stats/NPMStatsChart.tsx +++ b/src/components/npm-stats/NPMStatsChart.tsx @@ -12,10 +12,10 @@ import type { TimeRange, TransformMode, } from './shared' -import { getPackageColor } from './shared' +import { getBaselineDisplayName, getPackageColor } from './shared' import { BASELINE_LINE_COLOR } from './BaselineSection' -const BASELINE_LINE_NAME = '__baseline__' +const BASELINE_LINE_SERIES = '__baseline__' // Plot figure component function PlotFigure({ options }: { options: Parameters[0] }) { @@ -169,6 +169,9 @@ export function NPMStatsChart({ const baselineGroups = binnedPackageData.filter( (_, index) => packages[index]?.baseline, ) + const baselineLineName = getBaselineDisplayName( + packages.filter((packageGroup) => packageGroup.baseline), + ) const isMultiBaseline = baselineGroups.length > 1 const baselineDivisorByDate = baselineGroups.length @@ -243,13 +246,19 @@ export function NPMStatsChart({ return !isHidden }) - const plotData = filteredPackageData.flatMap((d) => d.downloads) + const plotData = filteredPackageData.flatMap((d) => + d.downloads.map((download) => ({ + ...download, + seriesName: download.name, + })), + ) if (showBaseline && baselineDivisorByDate) { const baselinePoints = [...baselineDivisorByDate.entries()] .sort((a, b) => a[0] - b[0]) .map(([time, divisor]) => ({ - name: BASELINE_LINE_NAME, + name: baselineLineName, + seriesName: BASELINE_LINE_SERIES, date: d3.utcDay(new Date(time)), // When normalized, baseline / baseline = 1 at every point. // When raw, multi-baseline shows the growth-index multiplier (~1.0+); @@ -271,6 +280,14 @@ export function NPMStatsChart({ fx: facetX, fy: facetY, } as const + const lineOptions: Plot.LineYOptions = { + ...baseOptions, + z: 'seriesName', + } + const getStrokeColor = (d: { name?: string; seriesName?: string }) => { + if (d.seriesName === BASELINE_LINE_SERIES) return BASELINE_LINE_COLOR + return d.name ? getPackageColor(d.name, packages) : 'currentColor' + } const partialBinEnd = binUnit.floor(now) const partialBinStart = binUnit.offset(partialBinEnd, -1) @@ -305,8 +322,8 @@ export function NPMStatsChart({ ? Plot.lineY( plotData.filter((d) => d.date >= partialBinStart), { - ...baseOptions, - stroke: 'name', + ...lineOptions, + stroke: getStrokeColor, strokeWidth: 1.5, strokeDasharray: '2 4', strokeOpacity: 0.8, @@ -317,8 +334,8 @@ export function NPMStatsChart({ Plot.lineY( plotData.filter((d) => d.date < partialBinEnd), { - ...baseOptions, - stroke: 'name', + ...lineOptions, + stroke: getStrokeColor, strokeWidth: 2, curve: 'monotone-x', }, @@ -363,7 +380,7 @@ export function NPMStatsChart({ range: [...new Set(plotData.map((d) => d.name))] .filter((pkg): pkg is string => pkg !== undefined) .map((pkg) => - pkg === BASELINE_LINE_NAME + pkg === baselineLineName ? BASELINE_LINE_COLOR : getPackageColor(pkg, packages), ), diff --git a/src/components/npm-stats/shared.ts b/src/components/npm-stats/shared.ts index 47746832e..14cac368a 100644 --- a/src/components/npm-stats/shared.ts +++ b/src/components/npm-stats/shared.ts @@ -107,6 +107,20 @@ export function getPackageColor( return defaultColors[packageIndex % defaultColors.length] } +export function getBaselineDisplayName(packageGroups: PackageGroup[]): string { + const baselineNames = packageGroups + .filter((packageGroup) => packageGroup.baseline) + .flatMap((packageGroup) => { + const label = packageGroup.baselineLabel?.trim() + if (label) return [label] + + const packageNames = packageGroup.packages.map((pkg) => pkg.name) + return packageNames.length ? [packageNames.join(', ')] : [] + }) + + return [...new Set(baselineNames)].join(', ') || 'Baseline' +} + export const formatNumber = (num: number) => { if (num >= 1_000_000) { return `${(num / 1_000_000).toFixed(1)}M` diff --git a/src/routes/$libraryId/$version.docs.npm-stats.tsx b/src/routes/$libraryId/$version.docs.npm-stats.tsx index 49963aa28..95c6053eb 100644 --- a/src/routes/$libraryId/$version.docs.npm-stats.tsx +++ b/src/routes/$libraryId/$version.docs.npm-stats.tsx @@ -294,6 +294,7 @@ function RouteComponent() { packages: [{ name: p.name, hidden: true }], color: p.color, baseline: true, + baselineLabel: preset.title, })), ], } diff --git a/src/routes/stats/npm/-comparisons.ts b/src/routes/stats/npm/-comparisons.ts index cf874d534..dd3a3862e 100644 --- a/src/routes/stats/npm/-comparisons.ts +++ b/src/routes/stats/npm/-comparisons.ts @@ -9,6 +9,7 @@ export const packageGroupSchema = v.object({ ), color: v.optional(v.nullable(v.string())), baseline: v.optional(v.boolean()), + baselineLabel: v.optional(v.string()), }) export const packageComparisonSchema = v.object({ diff --git a/src/routes/stats/npm/index.tsx b/src/routes/stats/npm/index.tsx index 97f569427..826c37251 100644 --- a/src/routes/stats/npm/index.tsx +++ b/src/routes/stats/npm/index.tsx @@ -216,6 +216,7 @@ type _NpmStatsSearch = { name?: string color?: string baseline?: boolean + baselineLabel?: string packages: Array<{ name?: string; hidden?: boolean }> }> range?: TimeRange @@ -308,6 +309,7 @@ function RouteComponent() { packages: [{ name: p.name, hidden: true }], color: p.color, baseline: true, + baselineLabel: preset.title, })), ], }