From 58b9a425401e53d4b2812903b619ecb1680f0c84 Mon Sep 17 00:00:00 2001 From: graphieros Date: Tue, 20 Oct 2026 20:09:54 +0200 Subject: [PATCH 1/6] fix: preserve download chart gaps when data is interpolated and highlight missing data --- app/components/Chart/SplitSparkline.vue | 16 ++--- app/components/Package/TrendsChart.vue | 43 +++++++++--- app/utils/chart-data-prediction.ts | 52 ++++++++++----- server/utils/embed-downloads-svg.ts | 31 ++++++++- shared/utils/download-chart-gaps.ts | 51 ++++++++++++++ shared/utils/trends-chart.ts | 16 +++-- .../app/utils/chart-data-pipeline.spec.ts | 57 ++++++++++++++++ .../server/utils/embed-downloads-svg.spec.ts | 30 ++++++++- .../shared/utils/download-chart-gaps.spec.ts | 42 ++++++++++++ test/unit/shared/utils/trends-chart.spec.ts | 66 +++++++++++++++---- 10 files changed, 344 insertions(+), 60 deletions(-) create mode 100644 shared/utils/download-chart-gaps.ts create mode 100644 test/unit/app/utils/chart-data-pipeline.spec.ts create mode 100644 test/unit/shared/utils/download-chart-gaps.spec.ts diff --git a/app/components/Chart/SplitSparkline.vue b/app/components/Chart/SplitSparkline.vue index 9cb6761efd..6f0f0af9da 100644 --- a/app/components/Chart/SplitSparkline.vue +++ b/app/components/Chart/SplitSparkline.vue @@ -18,7 +18,7 @@ const props = defineProps<{ dataset?: Array< VueUiXyDatasetItem & { color?: string - series: number[] + series: Array dashIndices?: number[] } > @@ -29,7 +29,6 @@ const props = defineProps<{ day: string } showLastDatapointEstimation: boolean - nullifyZeroValues?: boolean largePackageSeries?: boolean[] granularity: ChartTimeGranularity }>() @@ -124,14 +123,7 @@ const datasets = computed(() => { return { period, - value: - rawValue === 0 - ? props.nullifyZeroValues && hasNoDataTail - ? index === props.dates.length - 1 - ? 0 - : null - : 0 - : rawValue || (index === props.dates.length - 1 ? 0 : null), + value: rawValue ?? null, } }) }) @@ -234,9 +226,9 @@ const configs = computed(() => { strokeWidth: 1, dashIndices, dashArray: 3, - cutNullValues: false, + cutNullValues: true, nullDashes: { - show: true, + show: false, }, }, plot: { diff --git a/app/components/Package/TrendsChart.vue b/app/components/Package/TrendsChart.vue index ceda0bfceb..d65be663dd 100644 --- a/app/components/Package/TrendsChart.vue +++ b/app/components/Package/TrendsChart.vue @@ -23,13 +23,13 @@ import { buildTrendsChartData, isWeeklyDataset, getTrendsDatetimeFormatterOptions, - nullifyZeroValues, isMissingDownloadValue, isLargeDownloadSeries, MULTI_PACKAGE_MODE_TOP_PADDING, } from '#shared/utils/trends-chart' import { downloadFileLink } from '~/utils/download' import { useCopyChartPng } from '~/composables/useCopyChartPng' +import { getDownloadChartGaps } from '#shared/utils/download-chart-gaps' import { createLastDatapointLabelsSvg } from '#shared/utils/download-chart-last-label' import('vue-data-ui/style.css') @@ -72,6 +72,7 @@ const { copy, copied } = useClipboard() const colorMode = useColorMode() const resolvedMode = shallowRef<'light' | 'dark'>('light') +const gapPatternId = useId() const rootEl = shallowRef(null) const isZoomed = shallowRef(false) const zoomStartIndex = shallowRef(0) @@ -200,17 +201,11 @@ const normalisedDataset = computed(() => { data.forEach((item, index) => { const packageName = getPackageNameForSeriesIndex(index) - item.series = nullifyZeroValues({ - enabled: selectedMetric.value === DEFAULT_METRIC_ID && isLargePackage.value[packageName], - values: item.series, - keepLastZero: false, - }) as number[] - if (selectedMetric.value !== DEFAULT_METRIC_ID || !item.series.length) return const rawLastValue = chartData.value.dataset?.[index]?.series?.at(-1) - if (!isLargePackage.value[packageName] && isMissingDownloadValue(rawLastValue)) { + if (!isLargePackage.value[packageName] && rawLastValue === 0) { const series = item.series as Array series[series.length - 1] = 0 } @@ -1909,7 +1904,6 @@ const copyEmbedUrl = () => copyEmbed(embedUrl.value) :dates="chartData.dates" :datetimeFormatterOptions :showLastDatapointEstimation="shouldRenderEstimationOverlay && !isEndDateOnPeriodEnd" - :nullify-zero-values="selectedMetric === DEFAULT_METRIC_ID" :granularity="selectedGranularity" :large-package-series="largePackageSeries" /> @@ -1944,6 +1938,37 @@ const copyEmbedUrl = () => copyEmbed(embedUrl.value) - -