diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/AnalyticsChartControls.vue b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/AnalyticsChartControls.vue
index c1804f9ca0..e3f8afe751 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/AnalyticsChartControls.vue
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/AnalyticsChartControls.vue
@@ -39,7 +39,7 @@
ref="controlsMenuPanel"
role="dialog"
:aria-label="formatMessage(analyticsChartMessages.controlsDialogAria)"
- class="mt-1 flex w-[228px] max-w-[calc(100vw_-_2rem)] flex-col overflow-hidden rounded-[14px] border border-solid border-surface-4 bg-surface-3 text-sm shadow-2xl"
+ class="mt-1 flex w-[238px] max-w-[calc(100vw_-_2rem)] flex-col overflow-hidden rounded-[14px] border border-solid border-surface-4 bg-surface-3 text-sm shadow-2xl"
>
{{ activeControlCountLabel }}
@@ -68,7 +68,7 @@
>
- {{ formatMessage(analyticsChartMessages.previousPeriod) }}
+ {{ formatMessage(comparisonMessage) }}
+ props.isSameDayLastWeekComparison
+ ? analyticsChartMessages.sameDayLastWeek
+ : analyticsChartMessages.previousPeriod,
+)
const ratioModeModel = computed({
get: () => props.ratioMode,
diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/index.vue b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/index.vue
index 7ccec367f3..ca1bf24b5f 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/index.vue
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/index.vue
@@ -40,6 +40,7 @@
v-model:show-previous-period="showPreviousPeriod"
:can-use-ratio-mode="canUseRatioMode"
:can-show-previous-period="canShowPreviousPeriod"
+ :is-same-day-last-week-comparison="isSameDayLastWeekComparison"
:has-chart-events="hasChartEvents"
:has-project-events="hasProjectEvents"
:small-toggles="smallToggles"
@@ -89,6 +90,7 @@ const props = defineProps<{
showTopGraphDatasetsButton: boolean
canUseRatioMode: boolean
canShowPreviousPeriod: boolean
+ isSameDayLastWeekComparison: boolean
hasChartEvents: boolean
hasProjectEvents: boolean
smallToggles: boolean
diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/use-analytics-chart-legend.ts b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/use-analytics-chart-legend.ts
index 7d649de131..1026038824 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/use-analytics-chart-legend.ts
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-header/use-analytics-chart-legend.ts
@@ -28,6 +28,7 @@ export function useAnalyticsChartLegend({
allChartDatasets,
previousChartDatasets,
shouldShowPreviousPeriod,
+ isSameDayLastWeekComparison,
isRatioMode,
hiddenGraphDatasetIds,
selectedBreakdowns,
@@ -40,6 +41,7 @@ export function useAnalyticsChartLegend({
allChartDatasets: ComputedRef
previousChartDatasets: ComputedRef
shouldShowPreviousPeriod: ComputedRef
+ isSameDayLastWeekComparison: ComputedRef
isRatioMode: Ref
hiddenGraphDatasetIds: Ref
selectedBreakdowns: Ref
@@ -51,6 +53,11 @@ export function useAnalyticsChartLegend({
const { formatMessage } = useVIntl()
const hoveredLegendEntryId = ref(null)
const hiddenDatasetIds = computed(() => new Set(hiddenGraphDatasetIds.value))
+ const previousPeriodSuffixMessage = computed(() =>
+ isSameDayLastWeekComparison.value
+ ? analyticsChartMessages.sameDayLastWeekSuffix
+ : analyticsChartMessages.previousPeriodSuffix,
+ )
const previousChartDatasetByOriginalId = computed(() => {
const datasets = new Map()
for (const dataset of previousChartDatasets.value) {
@@ -117,7 +124,7 @@ export function useAnalyticsChartLegend({
const previousDataset = previousChartDatasetByOriginalId.value.get(entry.id)
const previousEntry: AnalyticsChartLegendEntry = {
id: getPreviousPeriodDatasetId(entry.id),
- name: formatMessage(analyticsChartMessages.previousPeriodSuffix, { name: entry.name }),
+ name: formatMessage(previousPeriodSuffixMessage.value, { name: entry.name }),
projectName: entry.projectName,
tooltip: entry.tooltip,
color: entry.color,
@@ -151,7 +158,7 @@ export function useAnalyticsChartLegend({
)
datasets.set(getPreviousPeriodDatasetId(dataset.projectId), {
projectId: getPreviousPeriodDatasetId(dataset.projectId),
- label: formatMessage(analyticsChartMessages.previousPeriodSuffix, {
+ label: formatMessage(previousPeriodSuffixMessage.value, {
name: dataset.label,
}),
projectName: dataset.projectName,
diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/AnalyticsChartTooltip.vue b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/AnalyticsChartTooltip.vue
index 109eab32e8..f9fa7758a0 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/AnalyticsChartTooltip.vue
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/AnalyticsChartTooltip.vue
@@ -21,7 +21,7 @@
{{ previousRangeLabel }}
- {{ formatMessage(analyticsChartMessages.previousPeriodShort) }}
+ {{ formatMessage(previousPeriodMessage) }}
@@ -169,6 +169,7 @@ const props = defineProps<{
containerHeight: number
pinned: boolean
ratioMode: boolean
+ isSameDayLastWeekComparison: boolean
capitalizeLabels: boolean
shiftKeyPressed: boolean
}>()
@@ -180,6 +181,11 @@ const emit = defineEmits<{
}>()
const { formatMessage } = useVIntl()
+const previousPeriodMessage = computed(() =>
+ props.isSameDayLastWeekComparison
+ ? analyticsChartMessages.sameDayLastWeekShort
+ : analyticsChartMessages.previousPeriodShort,
+)
function onEntryClick(event: MouseEvent, entry: AnalyticsChartTooltipEntry) {
if (entry.toggleDisabled && !event.shiftKey) return
diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/index.vue b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/index.vue
index 91fe11d48f..ecce24cfde 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/index.vue
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/index.vue
@@ -69,6 +69,7 @@
:container-height="containerSize.height"
:pinned="isHoverPinned"
:ratio-mode="isRatioMode"
+ :is-same-day-last-week-comparison="isSameDayLastWeekComparison"
:capitalize-labels="shouldCapitalizeDatasetLabels"
:shift-key-pressed="isShiftKeyPressed"
@entry-click="(datasetId, shiftKey) => emit('entry-click', datasetId, shiftKey)"
@@ -120,6 +121,7 @@ const props = defineProps<{
fetchRequest: Labrinth.Analytics.v3.FetchRequest | null
sliceCount: number
shouldShowPreviousPeriod: boolean
+ isSameDayLastWeekComparison: boolean
allChartDatasets: ChartDataset[]
currentLegendEntries: AnalyticsChartLegendEntry[]
legendEntries: AnalyticsChartLegendEntry[]
@@ -165,6 +167,7 @@ const {
allChartDatasets: computed(() => props.allChartDatasets),
chartRangeBounds: computed(() => props.chartRangeBounds),
shouldShowPreviousPeriod: computed(() => props.shouldShowPreviousPeriod),
+ isSameDayLastWeekComparison: computed(() => props.isSameDayLastWeekComparison),
onRangeSelected: (start, end, groupBy) => emit('range-select', start, end, groupBy),
})
diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/use-analytics-chart-interactions.ts b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/use-analytics-chart-interactions.ts
index 25c7099a43..8a51f2164c 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/use-analytics-chart-interactions.ts
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-plot/use-analytics-chart-interactions.ts
@@ -27,6 +27,7 @@ export function useAnalyticsChartInteractions({
allChartDatasets,
chartRangeBounds,
shouldShowPreviousPeriod,
+ isSameDayLastWeekComparison,
onRangeSelected,
}: {
isDataLoading: ComputedRef
@@ -36,6 +37,7 @@ export function useAnalyticsChartInteractions({
allChartDatasets: ComputedRef
chartRangeBounds: ComputedRef
shouldShowPreviousPeriod: ComputedRef
+ isSameDayLastWeekComparison: ComputedRef
onRangeSelected: (start: Date, end: Date, groupBy: AnalyticsGroupByPreset) => void
}) {
const chartContainer = ref(null)
@@ -226,7 +228,9 @@ export function useAnalyticsChartInteractions({
const rangeBounds = chartRangeBounds.value
if (!bucketRange || !rangeBounds) return null
- const periodMs = rangeBounds.end.getTime() - rangeBounds.start.getTime()
+ const periodMs = isSameDayLastWeekComparison.value
+ ? 7 * 24 * 60 * 60 * 1000
+ : rangeBounds.end.getTime() - rangeBounds.start.getTime()
if (!Number.isFinite(periodMs) || periodMs <= 0) return null
return {
diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/index.vue b/apps/frontend/src/components/analytics-dashboard/analytics-chart/index.vue
index 2011888a65..c28cf9fb01 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/index.vue
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/index.vue
@@ -24,6 +24,7 @@
:show-top-graph-datasets-button="showTopGraphDatasetsButton"
:can-use-ratio-mode="canUseRatioMode"
:can-show-previous-period="canShowPreviousPeriodToggle"
+ :is-same-day-last-week-comparison="isSameDayLastWeekComparison"
:has-chart-events="hasChartEvents"
:has-project-events="hasProjectEvents"
:small-toggles="!isMobileLayout"
@@ -66,6 +67,7 @@
:fetch-request="fetchRequest"
:slice-count="sliceCount"
:should-show-previous-period="shouldShowPreviousPeriod"
+ :is-same-day-last-week-comparison="isSameDayLastWeekComparison"
:all-chart-datasets="allChartDatasets"
:current-legend-entries="currentLegendEntries"
:legend-entries="legendEntries"
@@ -130,6 +132,7 @@ const {
isGraphDatasetSelectionActive,
selectedProjectIds: currentSelectedProjectIds,
selectedTimeframeMode,
+ selectedTimeframe,
selectedCustomTimeframeStartDate,
selectedCustomTimeframeEndDate,
selectedGroupBy: selectedDashboardGroupBy,
@@ -140,6 +143,9 @@ const {
} = dashboardContext
const isDataLoading = computed(() => isLoading.value)
+const isSameDayLastWeekComparison = computed(
+ () => selectedTimeframeMode.value === 'preset' && selectedTimeframe.value === 'yesterday',
+)
const defaultShowProjectEvents = computed(() =>
getDefaultAnalyticsGraphProjectEventsVisibility(currentSelectedProjectIds.value),
)
@@ -194,6 +200,7 @@ const {
allChartDatasets,
previousChartDatasets,
shouldShowPreviousPeriod,
+ isSameDayLastWeekComparison,
isRatioMode,
hiddenGraphDatasetIds,
selectedBreakdowns,
diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts b/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts
index 49a0b7ea27..f6dfef4c5a 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts
@@ -291,6 +291,14 @@ export const analyticsStatCardMessages = defineMessages({
id: 'analytics.stat.previous-period-comparison-short',
defaultMessage: 'vs prev.',
},
+ sameDayLastWeekComparison: {
+ id: 'analytics.stat.same-day-last-week-comparison',
+ defaultMessage: 'vs. same day last week',
+ },
+ sameDayLastWeekComparisonShort: {
+ id: 'analytics.stat.same-day-last-week-comparison-short',
+ defaultMessage: 'vs. last wk.',
+ },
})
export const analyticsGroupByMessages = defineMessages({
@@ -619,6 +627,10 @@ export const analyticsChartMessages = defineMessages({
id: 'analytics.chart.controls.previous-period',
defaultMessage: 'Previous period',
},
+ sameDayLastWeek: {
+ id: 'analytics.chart.controls.same-day-last-week',
+ defaultMessage: 'Same day last week',
+ },
ratio: {
id: 'analytics.chart.controls.ratio',
defaultMessage: 'Ratio',
@@ -664,6 +676,14 @@ export const analyticsChartMessages = defineMessages({
id: 'analytics.chart.tooltip.previous-period-short',
defaultMessage: '(prev.)',
},
+ sameDayLastWeekSuffix: {
+ id: 'analytics.chart.legend.same-day-last-week-suffix',
+ defaultMessage: '{name} (Last wk.)',
+ },
+ sameDayLastWeekShort: {
+ id: 'analytics.chart.tooltip.same-day-last-week-short',
+ defaultMessage: '(last wk.)',
+ },
tooltipPinned: {
id: 'analytics.chart.tooltip.pinned',
defaultMessage: 'Chart tooltip pinned',
diff --git a/apps/frontend/src/components/analytics-dashboard/stat-cards/StatCard.vue b/apps/frontend/src/components/analytics-dashboard/stat-cards/StatCard.vue
index 071e2eb584..7376550ac3 100644
--- a/apps/frontend/src/components/analytics-dashboard/stat-cards/StatCard.vue
+++ b/apps/frontend/src/components/analytics-dashboard/stat-cards/StatCard.vue
@@ -54,7 +54,7 @@
-
+
- {{ formatMessage(analyticsStatCardMessages.previousPeriodComparison) }}
+ {{ formatMessage(comparisonMessage) }}
- {{ formatMessage(analyticsStatCardMessages.previousPeriodComparisonShort) }}
+ {{ formatMessage(comparisonMessageShort) }}
@@ -117,6 +117,7 @@ const props = defineProps<{
statLabel: string
statTooltip?: string
vsPrevPeriodPercent: string | null
+ isSameDayLastWeekComparison?: boolean
icon: string
active?: boolean
disabled?: boolean
@@ -127,6 +128,16 @@ const emit = defineEmits<{
}>()
const { formatMessage } = useVIntl()
+const comparisonMessage = computed(() =>
+ props.isSameDayLastWeekComparison
+ ? analyticsStatCardMessages.sameDayLastWeekComparison
+ : analyticsStatCardMessages.previousPeriodComparison,
+)
+const comparisonMessageShort = computed(() =>
+ props.isSameDayLastWeekComparison
+ ? analyticsStatCardMessages.sameDayLastWeekComparisonShort
+ : analyticsStatCardMessages.previousPeriodComparisonShort,
+)
const statCardIconMap: Record
= {
clock: ClockIcon,
timer: TimerIcon,
diff --git a/apps/frontend/src/components/analytics-dashboard/stat-cards/StatCards.vue b/apps/frontend/src/components/analytics-dashboard/stat-cards/StatCards.vue
index 601dfaf89e..df27b477d4 100644
--- a/apps/frontend/src/components/analytics-dashboard/stat-cards/StatCards.vue
+++ b/apps/frontend/src/components/analytics-dashboard/stat-cards/StatCards.vue
@@ -28,6 +28,7 @@
:stat-label="card.statLabel"
:stat-tooltip="card.statTooltip"
:vs-prev-period-percent="card.vsPrevPeriodPercent"
+ :is-same-day-last-week-comparison="isSameDayLastWeekComparison"
:icon="card.icon"
:active="activeStat === card.key"
:disabled="card.disabled"
@@ -61,6 +62,8 @@ const {
percentChanges,
hasPreviousPeriodComparison,
selectedBreakdowns,
+ selectedTimeframeMode,
+ selectedTimeframe,
isAnalyticsDashboardStatRelevant,
} = injectAnalyticsDashboardContext()
const formatNumber = useFormatNumber()
@@ -70,6 +73,9 @@ const showMonetizationBanner = computed(
() => selectedBreakdowns.value.includes('monetization') && !monetizationBannerDismissed.value,
)
const MAX_PREVIOUS_PERIOD_PERCENT_DISPLAY = 1000
+const isSameDayLastWeekComparison = computed(
+ () => selectedTimeframeMode.value === 'preset' && selectedTimeframe.value === 'yesterday',
+)
const compactNumberFormatter = computed(
() =>
diff --git a/apps/frontend/src/providers/analytics/analytics-data-utils.ts b/apps/frontend/src/providers/analytics/analytics-data-utils.ts
index d415eb8a57..2deebf6035 100644
--- a/apps/frontend/src/providers/analytics/analytics-data-utils.ts
+++ b/apps/frontend/src/providers/analytics/analytics-data-utils.ts
@@ -37,6 +37,7 @@ function isProjectAnalyticsPoint(
export function buildComparisonFetchRequest(
fetchRequest: Labrinth.Analytics.v3.FetchRequest | null,
minStartTime = ANALYTICS_START_TIME,
+ comparisonOffsetMs?: number,
): AnalyticsProjectFetchRequest | null {
if (!isAnalyticsFetchRequestReady(fetchRequest)) {
return null
@@ -50,7 +51,12 @@ export function buildComparisonFetchRequest(
return null
}
- const previousStart = new Date(startTimestamp - duration)
+ const offset = comparisonOffsetMs ?? duration
+ if (!Number.isFinite(offset) || offset < duration) {
+ return null
+ }
+
+ const previousStart = new Date(startTimestamp - offset)
if (previousStart.getTime() < minStartTime) {
return null
@@ -64,7 +70,9 @@ export function buildComparisonFetchRequest(
resolution:
'slices' in fetchRequest.time_range.resolution
? {
- slices: fetchRequest.time_range.resolution.slices * 2,
+ slices: Math.ceil(
+ fetchRequest.time_range.resolution.slices * ((offset + duration) / duration),
+ ),
}
: fetchRequest.time_range.resolution,
},
@@ -99,10 +107,11 @@ export function splitAnalyticsTimeSlices(
timeSlices: Labrinth.Analytics.v3.TimeSlice[],
fetchRequest: Labrinth.Analytics.v3.FetchRequest | null,
minStartTime = ANALYTICS_START_TIME,
+ comparisonOffsetMs?: number,
): AnalyticsTimeSliceSplit {
if (
!isAnalyticsFetchRequestReady(fetchRequest) ||
- !buildComparisonFetchRequest(fetchRequest, minStartTime)
+ !buildComparisonFetchRequest(fetchRequest, minStartTime, comparisonOffsetMs)
) {
return {
currentTimeSlices: timeSlices,
@@ -112,11 +121,20 @@ export function splitAnalyticsTimeSlices(
const currentSliceCount = getAnalyticsTimeSliceCount(fetchRequest.time_range, timeSlices.length)
const currentStartIndex = Math.max(0, timeSlices.length - currentSliceCount)
- const previousStartIndex = Math.max(0, currentStartIndex - currentSliceCount)
+ const startTimestamp = new Date(fetchRequest.time_range.start).getTime()
+ const endTimestamp = new Date(fetchRequest.time_range.end).getTime()
+ const duration = endTimestamp - startTimestamp
+ const offsetSliceCount = Math.round(
+ currentSliceCount * ((comparisonOffsetMs ?? duration) / duration),
+ )
+ const previousStartIndex = Math.max(0, currentStartIndex - offsetSliceCount)
return {
currentTimeSlices: timeSlices.slice(currentStartIndex),
- previousTimeSlices: timeSlices.slice(previousStartIndex, currentStartIndex),
+ previousTimeSlices: timeSlices.slice(
+ previousStartIndex,
+ previousStartIndex + currentSliceCount,
+ ),
}
}
diff --git a/apps/frontend/src/providers/analytics/analytics.ts b/apps/frontend/src/providers/analytics/analytics.ts
index a623a3913e..e02d929871 100644
--- a/apps/frontend/src/providers/analytics/analytics.ts
+++ b/apps/frontend/src/providers/analytics/analytics.ts
@@ -1031,8 +1031,17 @@ export function createAnalyticsDashboardContext(
return ANALYTICS_START_TIME
})
+ const comparisonOffsetMs = computed(() =>
+ selectedTimeframeMode.value === 'preset' && selectedTimeframe.value === 'yesterday'
+ ? 7 * 24 * 60 * 60 * 1000 // 7 days
+ : undefined,
+ )
const comparisonFetchRequest = computed(() =>
- buildComparisonFetchRequest(fetchRequest.value, analyticsComparisonStartTime.value),
+ buildComparisonFetchRequest(
+ fetchRequest.value,
+ analyticsComparisonStartTime.value,
+ comparisonOffsetMs.value,
+ ),
)
const analyticsTimeSlicesFetchRequest = computed(
() => comparisonFetchRequest.value ?? fetchRequest.value,
@@ -1126,8 +1135,11 @@ export function createAnalyticsDashboardContext(
const dailyFetchRequest = buildDailyAnalyticsFetchRequest(fetchRequest.value)
return (
- buildComparisonFetchRequest(dailyFetchRequest, analyticsComparisonStartTime.value) ??
- dailyFetchRequest
+ buildComparisonFetchRequest(
+ dailyFetchRequest,
+ analyticsComparisonStartTime.value,
+ comparisonOffsetMs.value,
+ ) ?? dailyFetchRequest
)
})
@@ -1583,6 +1595,7 @@ export function createAnalyticsDashboardContext(
nextAnalyticsData.metrics,
fetchRequest.value,
analyticsComparisonStartTime.value,
+ comparisonOffsetMs.value,
)
timeSlices.value = splitTimeSlices.currentTimeSlices
previousTimeSlices.value = splitTimeSlices.previousTimeSlices