@@ -18,6 +18,7 @@ import type {
1818 YearlyDataPoint ,
1919} from ' ~/types/chart'
2020import { DATE_INPUT_MAX } from ' ~/utils/input'
21+ import { applyDownloadFilter } from ' ~/utils/chart-filters'
2122
2223const props = withDefaults (
2324 defineProps <{
@@ -50,6 +51,7 @@ const props = withDefaults(
5051
5152const { locale } = useI18n ()
5253const { accentColors, selectedAccentColor } = useAccentColor ()
54+ const { settings } = useSettings ()
5355const colorMode = useColorMode ()
5456const resolvedMode = shallowRef <' light' | ' dark' >(' light' )
5557const rootEl = shallowRef <HTMLElement | null >(null )
@@ -926,15 +928,27 @@ watch(
926928
927929const effectiveDataSingle = computed <EvolutionData >(() => {
928930 const state = activeMetricState .value
931+ let data: EvolutionData
929932 if (
930933 selectedMetric .value === DEFAULT_METRIC_ID &&
931934 displayedGranularity .value === DEFAULT_GRANULARITY &&
932935 props .weeklyDownloads ?.length
933936 ) {
934- if (isWeeklyDataset (state .evolution ) && state .evolution .length ) return state .evolution
935- return props .weeklyDownloads
937+ data =
938+ isWeeklyDataset (state .evolution ) && state .evolution .length
939+ ? state .evolution
940+ : props .weeklyDownloads
941+ } else {
942+ data = state .evolution
943+ }
944+
945+ if (isDownloadsMetric .value && data .length ) {
946+ return applyDownloadFilter (
947+ data as Array <{ value: number }>,
948+ settings .value .chartFilter ,
949+ ) as EvolutionData
936950 }
937- return state . evolution
951+ return data
938952})
939953
940954/**
@@ -968,7 +982,13 @@ const chartData = computed<{
968982 const pointsByPackage = new Map <string , Array <{ timestamp: number ; value: number }>>()
969983
970984 for (const pkg of names ) {
971- const data = state .evolutionsByPackage [pkg ] ?? []
985+ let data = state .evolutionsByPackage [pkg ] ?? []
986+ if (isDownloadsMetric .value && data .length ) {
987+ data = applyDownloadFilter (
988+ data as Array <{ value: number }>,
989+ settings .value .chartFilter ,
990+ ) as EvolutionData
991+ }
972992 const points = extractSeriesPoints (granularity , data )
973993 pointsByPackage .set (pkg , points )
974994 for (const p of points ) timestampSet .add (p .timestamp )
@@ -1583,6 +1603,9 @@ const chartConfig = computed<VueUiXyConfig>(() => {
15831603 }
15841604})
15851605
1606+ const isDownloadsMetric = computed (() => selectedMetric .value === ' downloads' )
1607+ const showFilterControls = shallowRef (false )
1608+
15861609// Trigger data loading when the metric is switched
15871610watch (selectedMetric , value => {
15881611 if (! isMounted .value ) return
@@ -1671,6 +1694,66 @@ watch(selectedMetric, value => {
16711694 </button >
16721695 </div >
16731696
1697+ <!-- Download filter controls -->
1698+ <div v-if =" isDownloadsMetric" class =" flex flex-col gap-2" >
1699+ <button
1700+ type =" button"
1701+ class =" self-start flex items-center gap-1 text-2xs font-mono text-fg-subtle hover:text-fg transition-colors"
1702+ @click =" showFilterControls = !showFilterControls"
1703+ >
1704+ <span
1705+ class =" w-3.5 h-3.5 transition-transform"
1706+ :class =" showFilterControls ? 'i-lucide:chevron-down' : 'i-lucide:chevron-right'"
1707+ aria-hidden =" true"
1708+ />
1709+ Filters
1710+ </button >
1711+ <div v-if =" showFilterControls" class =" grid grid-cols-1 sm:grid-cols-3 gap-3" >
1712+ <label class =" flex flex-col gap-1" >
1713+ <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1714+ Hampel window
1715+ <span class =" text-fg-muted" >({{ settings.chartFilter.hampelWindow }})</span >
1716+ </span >
1717+ <input
1718+ v-model.number =" settings.chartFilter.hampelWindow"
1719+ type =" range"
1720+ min =" 0"
1721+ max =" 13"
1722+ step =" 1"
1723+ class =" accent-[var(--accent-color,var(--fg-subtle))]"
1724+ />
1725+ </label >
1726+ <label class =" flex flex-col gap-1" >
1727+ <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1728+ Outlier threshold
1729+ <span class =" text-fg-muted" >({{ settings.chartFilter.hampelThreshold }})</span >
1730+ </span >
1731+ <input
1732+ v-model.number =" settings.chartFilter.hampelThreshold"
1733+ type =" range"
1734+ min =" 0"
1735+ max =" 10"
1736+ step =" 0.5"
1737+ class =" accent-[var(--accent-color,var(--fg-subtle))]"
1738+ />
1739+ </label >
1740+ <label class =" flex flex-col gap-1" >
1741+ <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1742+ Smoothing (tau)
1743+ <span class =" text-fg-muted" >({{ settings.chartFilter.smoothingTau }})</span >
1744+ </span >
1745+ <input
1746+ v-model.number =" settings.chartFilter.smoothingTau"
1747+ type =" range"
1748+ min =" 0"
1749+ max =" 26"
1750+ step =" 1"
1751+ class =" accent-[var(--accent-color,var(--fg-subtle))]"
1752+ />
1753+ </label >
1754+ </div >
1755+ </div >
1756+
16741757 <p v-if =" skippedPackagesWithoutGitHub.length > 0" class =" text-2xs font-mono text-fg-subtle" >
16751758 {{ $t('package.trends.contributors_skip', { count: skippedPackagesWithoutGitHub.length }) }}
16761759 {{ skippedPackagesWithoutGitHub.join(', ') }}
0 commit comments