@@ -1345,12 +1345,54 @@ function drawSvgPrintLegend(svg: Record<string, any>) {
13451345 return seriesNames .join (' \n ' )
13461346}
13471347
1348+ const showCorrectionControls = shallowRef (false )
1349+ const isResizing = shallowRef (false )
1350+
1351+ const chartHeight = computed (() => {
1352+ if (isMobile .value ) {
1353+ return 950
1354+ }
1355+ return showCorrectionControls .value && props .inModal ? 494 : 600
1356+ })
1357+
1358+ const timeoutId = shallowRef <ReturnType <typeof setTimeout > | null >(null )
1359+
1360+ function pauseChartTransitions() {
1361+ if (timeoutId .value ) {
1362+ clearTimeout (timeoutId .value )
1363+ }
1364+
1365+ isResizing .value = true
1366+
1367+ timeoutId .value = setTimeout (() => {
1368+ isResizing .value = false
1369+ timeoutId .value = null
1370+ }, 200 )
1371+ }
1372+
1373+ onBeforeUnmount (() => {
1374+ if (timeoutId .value ) {
1375+ clearTimeout (timeoutId .value )
1376+ }
1377+ })
1378+
1379+ watch (
1380+ chartHeight ,
1381+ (newH , oldH ) => {
1382+ if (newH !== oldH ) {
1383+ // Avoids triggering chart line transitions when the chart is resized
1384+ pauseChartTransitions ()
1385+ }
1386+ },
1387+ { immediate: true },
1388+ )
1389+
13481390// VueUiXy chart component configuration
13491391const chartConfig = computed <VueUiXyConfig >(() => {
13501392 return {
13511393 theme: isDarkMode .value ? ' dark' : (' ' as VueDataUiTheme ),
13521394 chart: {
1353- height: isMobile .value ? 950 : 600 ,
1395+ height: chartHeight .value ,
13541396 backgroundColor: colors .value .bg ,
13551397 padding: { bottom: displayedGranularity .value === ' yearly' ? 84 : 64 , right: 128 }, // padding right is set to leave space of last datapoint label(s)
13561398 userOptions: {
@@ -1554,7 +1596,6 @@ const chartConfig = computed<VueUiXyConfig>(() => {
15541596})
15551597
15561598const isDownloadsMetric = computed (() => selectedMetric .value === ' downloads' )
1557- const showCorrectionControls = shallowRef (false )
15581599
15591600const packageAnomalies = computed (() => getAnomaliesForPackages (effectivePackageNames .value ))
15601601const hasAnomalies = computed (() => packageAnomalies .value .length > 0 )
@@ -1674,116 +1715,139 @@ watch(selectedMetric, value => {
16741715 />
16751716 {{ $t('package.trends.data_correction') }}
16761717 </button >
1677- <div v-if =" showCorrectionControls" class =" grid grid-cols-2 sm:flex items-end gap-3" >
1678- <label class =" flex flex-col gap-1 flex-1" >
1679- <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1680- {{ $t('package.trends.average_window') }}
1681- <span class =" text-fg-muted" >({{ settings.chartFilter.averageWindow }})</span >
1682- </span >
1683- <input
1684- v-model.number =" settings.chartFilter.averageWindow"
1685- type =" range"
1686- min =" 0"
1687- max =" 20"
1688- step =" 1"
1689- class =" accent-[var(--accent-color,var(--fg-subtle))]"
1690- />
1691- </label >
1692- <label class =" flex flex-col gap-1 flex-1" >
1693- <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1694- {{ $t('package.trends.smoothing') }}
1695- <span class =" text-fg-muted" >({{ settings.chartFilter.smoothingTau }})</span >
1696- </span >
1697- <input
1698- v-model.number =" settings.chartFilter.smoothingTau"
1699- type =" range"
1700- min =" 0"
1701- max =" 20"
1702- step =" 1"
1703- class =" accent-[var(--accent-color,var(--fg-subtle))]"
1704- />
1705- </label >
1706- <label class =" flex flex-col gap-1 flex-1" >
1707- <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1708- {{ $t('package.trends.prediction') }}
1709- <span class =" text-fg-muted" >({{ settings.chartFilter.predictionPoints }})</span >
1710- </span >
1711- <input
1712- v-model.number =" settings.chartFilter.predictionPoints"
1713- type =" range"
1714- min =" 0"
1715- max =" 30"
1716- step =" 1"
1717- class =" accent-[var(--accent-color,var(--fg-subtle))]"
1718- />
1719- </label >
1720- <div class =" flex flex-col gap-1 shrink-0" >
1721- <span
1722- class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase flex items-center justify-between"
1723- >
1724- {{ $t('package.trends.known_anomalies') }}
1725- <TooltipApp interactive :to =" inModal ? '#chart-modal' : undefined" >
1726- <button
1727- type =" button"
1728- class =" i-lucide:info w-3.5 h-3.5 text-fg-muted cursor-help"
1729- :aria-label =" $t('package.trends.known_anomalies')"
1718+
1719+ <div
1720+ class =" overflow-hidden transition-[opacity] duration-200 ease-out"
1721+ :class ="
1722+ showCorrectionControls
1723+ ? 'max-h-[220px] opacity-100'
1724+ : 'max-h-0 opacity-0 pointer-events-none'
1725+ "
1726+ >
1727+ <div class =" pt-1 min-h-[160px] sm:min-h-[76px]" >
1728+ <div class =" grid grid-cols-2 sm:flex items-end gap-3" >
1729+ <label class =" flex flex-col gap-1 flex-1" >
1730+ <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1731+ {{ $t('package.trends.average_window') }}
1732+ <span class =" text-fg-muted" >({{ settings.chartFilter.averageWindow }})</span >
1733+ </span >
1734+ <input
1735+ v-model.number =" settings.chartFilter.averageWindow"
1736+ type =" range"
1737+ min =" 0"
1738+ max =" 20"
1739+ step =" 1"
1740+ class =" accent-[var(--accent-color,var(--fg-subtle))]"
17301741 />
1731- <template #content >
1732- <div class =" flex flex-col gap-3" >
1733- <p class =" text-xs text-fg-muted" >
1734- {{ $t('package.trends.known_anomalies_description') }}
1735- </p >
1736- <div v-if =" hasAnomalies" >
1737- <p class =" text-xs text-fg-subtle font-medium" >
1738- {{ $t('package.trends.known_anomalies_ranges') }}
1739- </p >
1740- <ul class =" text-xs text-fg-subtle list-disc list-inside" >
1741- <li v-for =" a in packageAnomalies" :key =" `${a.packageName}-${a.start}`" >
1742+ </label >
1743+
1744+ <label class =" flex flex-col gap-1 flex-1" >
1745+ <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1746+ {{ $t('package.trends.smoothing') }}
1747+ <span class =" text-fg-muted" >({{ settings.chartFilter.smoothingTau }})</span >
1748+ </span >
1749+ <input
1750+ v-model.number =" settings.chartFilter.smoothingTau"
1751+ type =" range"
1752+ min =" 0"
1753+ max =" 20"
1754+ step =" 1"
1755+ class =" accent-[var(--accent-color,var(--fg-subtle))]"
1756+ />
1757+ </label >
1758+
1759+ <label class =" flex flex-col gap-1 flex-1" >
1760+ <span class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase" >
1761+ {{ $t('package.trends.prediction') }}
1762+ <span class =" text-fg-muted" >({{ settings.chartFilter.predictionPoints }})</span >
1763+ </span >
1764+ <input
1765+ v-model.number =" settings.chartFilter.predictionPoints"
1766+ type =" range"
1767+ min =" 0"
1768+ max =" 30"
1769+ step =" 1"
1770+ class =" accent-[var(--accent-color,var(--fg-subtle))]"
1771+ />
1772+ </label >
1773+
1774+ <div class =" flex flex-col gap-1 shrink-0" >
1775+ <span
1776+ class =" text-2xs font-mono text-fg-subtle tracking-wide uppercase flex items-center justify-between"
1777+ >
1778+ {{ $t('package.trends.known_anomalies') }}
1779+ <TooltipApp interactive :to =" inModal ? '#chart-modal' : undefined" >
1780+ <button
1781+ type =" button"
1782+ class =" i-lucide:info w-3.5 h-3.5 text-fg-muted cursor-help"
1783+ :aria-label =" $t('package.trends.known_anomalies')"
1784+ />
1785+ <template #content >
1786+ <div class =" flex flex-col gap-3" >
1787+ <p class =" text-xs text-fg-muted" >
1788+ {{ $t('package.trends.known_anomalies_description') }}
1789+ </p >
1790+
1791+ <div v-if =" hasAnomalies" >
1792+ <p class =" text-xs text-fg-subtle font-medium" >
1793+ {{ $t('package.trends.known_anomalies_ranges') }}
1794+ </p >
1795+ <ul class =" text-xs text-fg-subtle list-disc list-inside" >
1796+ <li v-for =" a in packageAnomalies" :key =" `${a.packageName}-${a.start}`" >
1797+ {{
1798+ isMultiPackageMode
1799+ ? $t('package.trends.known_anomalies_range_named', {
1800+ packageName: a.packageName,
1801+ start: formatAnomalyDate(a.start),
1802+ end: formatAnomalyDate(a.end),
1803+ })
1804+ : $t('package.trends.known_anomalies_range', {
1805+ start: formatAnomalyDate(a.start),
1806+ end: formatAnomalyDate(a.end),
1807+ })
1808+ }}
1809+ </li >
1810+ </ul >
1811+ </div >
1812+
1813+ <p v-else class =" text-xs text-fg-muted" >
17421814 {{
1743- isMultiPackageMode
1744- ? $t('package.trends.known_anomalies_range_named', {
1745- packageName: a.packageName,
1746- start: formatAnomalyDate(a.start),
1747- end: formatAnomalyDate(a.end),
1748- })
1749- : $t('package.trends.known_anomalies_range', {
1750- start: formatAnomalyDate(a.start),
1751- end: formatAnomalyDate(a.end),
1752- })
1815+ $t('package.trends.known_anomalies_none', effectivePackageNames.length)
17531816 }}
1754- </li >
1755- </ul >
1756- </div >
1757- <p v-else class =" text-xs text-fg-muted" >
1758- {{ $t('package.trends.known_anomalies_none', effectivePackageNames.length) }}
1759- </p >
1760- <div class =" flex justify-end" >
1761- <LinkBase
1762- to =" https://github.com/npmx-dev/npmx.dev/edit/main/app/utils/download-anomalies.data.ts"
1763- class =" text-xs text-accent"
1764- >
1765- {{ $t('package.trends.known_anomalies_contribute') }}
1766- </LinkBase >
1767- </div >
1768- </div >
1769- </template >
1770- </TooltipApp >
1771- </span >
1772- <label
1773- class =" flex items-center gap-1.5 text-2xs font-mono text-fg-subtle cursor-pointer h-4"
1774- :class =" { 'opacity-50 pointer-events-none': !hasAnomalies }"
1775- >
1776- <input
1777- :checked =" settings.chartFilter.anomaliesFixed && hasAnomalies"
1778- @change ="
1779- settings.chartFilter.anomaliesFixed = ($event.target as HTMLInputElement).checked
1780- "
1781- type =" checkbox"
1782- :disabled =" !hasAnomalies"
1783- class =" accent-[var(--accent-color,var(--fg-subtle))]"
1784- />
1785- {{ $t('package.trends.apply_correction') }}
1786- </label >
1817+ </p >
1818+
1819+ <div class =" flex justify-end" >
1820+ <LinkBase
1821+ to =" https://github.com/npmx-dev/npmx.dev/edit/main/app/utils/download-anomalies.data.ts"
1822+ class =" text-xs text-accent"
1823+ >
1824+ {{ $t('package.trends.known_anomalies_contribute') }}
1825+ </LinkBase >
1826+ </div >
1827+ </div >
1828+ </template >
1829+ </TooltipApp >
1830+ </span >
1831+
1832+ <label
1833+ class =" flex items-center gap-1.5 text-2xs font-mono text-fg-subtle cursor-pointer h-4"
1834+ :class =" { 'opacity-50 pointer-events-none': !hasAnomalies }"
1835+ >
1836+ <input
1837+ :checked =" settings.chartFilter.anomaliesFixed && hasAnomalies"
1838+ @change ="
1839+ settings.chartFilter.anomaliesFixed = (
1840+ $event.target as HTMLInputElement
1841+ ).checked
1842+ "
1843+ type =" checkbox"
1844+ :disabled =" !hasAnomalies"
1845+ class =" accent-[var(--accent-color,var(--fg-subtle))]"
1846+ />
1847+ {{ $t('package.trends.apply_correction') }}
1848+ </label >
1849+ </div >
1850+ </div >
17871851 </div >
17881852 </div >
17891853 </div >
@@ -1802,14 +1866,23 @@ watch(selectedMetric, value => {
18021866 <div
18031867 role =" region"
18041868 aria-labelledby =" trends-chart-title"
1805- :class =" isMobile === false && width > 0 ? 'min-h-[567px]' : 'min-h-[260px]'"
1869+ :class ="
1870+ isMobile === false && width > 0
1871+ ? showCorrectionControls
1872+ ? 'h-[491px]'
1873+ : 'h-[567px]'
1874+ : 'min-h-[260px]'
1875+ "
18061876 >
18071877 <ClientOnly v-if =" chartData.dataset" >
18081878 <div :data-pending =" pending" :data-minimap-visible =" maxDatapoints > 6" >
18091879 <VueUiXy
18101880 :dataset =" normalisedDataset"
18111881 :config =" chartConfig"
1812- class =" [direction:ltr]"
1882+ :class =" {
1883+ '[direction:ltr]': true,
1884+ 'no-transition': isResizing,
1885+ }"
18131886 @zoomStart =" setIsZoom"
18141887 @zoomEnd =" setIsZoom"
18151888 @zoomReset =" isZoomed = false"
@@ -2105,4 +2178,9 @@ watch(selectedMetric, value => {
21052178[data-minimap-visible = ' false' ] .vue-data-ui-watermark {
21062179 top : calc (100% - 2rem ) !important ;
21072180}
2181+
2182+ .no-transition line ,
2183+ .no-transition circle {
2184+ transition : none !important ;
2185+ }
21082186 </style >
0 commit comments