diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/AnalyticsChart.client.vue b/apps/frontend/src/components/analytics-dashboard/analytics-chart/AnalyticsChart.client.vue
index 712a8fe9b..2c3afa763 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/AnalyticsChart.client.vue
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/AnalyticsChart.client.vue
@@ -27,6 +27,7 @@ import {
LineController,
LineElement,
PointElement,
+ type ScriptableLineSegmentContext,
Tooltip,
} from 'chart.js'
@@ -446,6 +447,12 @@ function buildDatasets() {
pointHoverBackgroundColor: colors.borderColor,
pointHoverBorderWidth: 0,
pointHitRadius: 16,
+ segment: dataset.lastDataPointUnavailable
+ ? {
+ borderColor: (context: ScriptableLineSegmentContext) =>
+ context.p1DataIndex === dataset.data.length - 1 ? 'transparent' : undefined,
+ }
+ : undefined,
stack: props.stacked ? 'analytics' : undefined,
}
})
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 109eab32e..c36913638 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
@@ -59,7 +59,9 @@
{{ formatMessage(analyticsChartMessages.total) }}
- {{ formattedTotal }}
+
+ {{ formattedTotal }}
+
{{ entry.formattedValue }}
@@ -148,6 +154,7 @@ export type AnalyticsChartTooltipEntry = {
tooltip?: string
color: string
formattedValue: string
+ noData: boolean
hidden: boolean
toggleDisabled: boolean
isPreviousPeriod?: boolean
@@ -164,6 +171,7 @@ const props = defineProps<{
chartStart: Date | null
chartEnd: Date | null
formattedTotal: string
+ totalHasData: boolean
entries: AnalyticsChartTooltipEntry[]
containerWidth: number
containerHeight: number
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 91fe11d48..0d19d9aa7 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
@@ -64,6 +64,7 @@
:chart-start="chartRangeBounds?.start ?? null"
:chart-end="chartRangeBounds?.end ?? null"
:formatted-total="hoverFormattedTotal"
+ :total-has-data="hoverHasData"
:entries="hoverEntries"
:container-width="containerSize.width"
:container-height="containerSize.height"
@@ -89,6 +90,7 @@ import type {
AnalyticsGroupByPreset,
} from '~/providers/analytics/analytics'
+import { analyticsChartMessages } from '../../analytics-messages.ts'
import type {
AnalyticsChartLegendEntry,
AnalyticsChartRangeBounds,
@@ -188,7 +190,26 @@ const hoverTotalValue = computed(() => {
}, 0)
})
+function hasDatasetDataAtSlice(dataset: ChartDataset | undefined, sliceIndex: number) {
+ if (!dataset) return false
+ return !dataset.lastDataPointUnavailable || sliceIndex !== dataset.data.length - 1
+}
+
+const hoverHasData = computed(() => {
+ if (hoverState.sliceIndex === null) return false
+ const sliceIndex = hoverState.sliceIndex
+
+ return props.currentLegendEntries.some((legendEntry) => {
+ if (legendEntry.hidden) return false
+ const dataset = props.chartDatasetById.get(legendEntry.id)
+ return hasDatasetDataAtSlice(dataset, sliceIndex)
+ })
+})
+
const hoverFormattedTotal = computed(() => {
+ if (!hoverHasData.value) {
+ return formatMessage(analyticsChartMessages.noData)
+ }
if (props.isRatioMode) {
return hoverTotalValue.value > 0 ? '100%' : '0%'
}
@@ -203,6 +224,7 @@ const hoverEntries = computed(() => {
return props.legendEntries.map((legendEntry) => {
const dataset = props.chartDatasetById.get(legendEntry.id)
const value = dataset?.data[sliceIndex] ?? 0
+ const hasData = hasDatasetDataAtSlice(dataset, sliceIndex)
const ratioValue = legendEntry.hidden || totalValue === 0 ? 0 : (value / totalValue) * 100
return {
projectId: legendEntry.id,
@@ -210,9 +232,12 @@ const hoverEntries = computed(() => {
projectName: legendEntry.projectName,
tooltip: legendEntry.tooltip,
color: legendEntry.color,
- formattedValue: props.isRatioMode
- ? `${ratioValue.toFixed(1)}%`
- : formatMetricValue(value, props.activeStat, formatNumber, formatMessage),
+ formattedValue: hasData
+ ? props.isRatioMode
+ ? `${ratioValue.toFixed(1)}%`
+ : formatMetricValue(value, props.activeStat, formatNumber, formatMessage)
+ : formatMessage(analyticsChartMessages.noData),
+ noData: !hasData,
hidden: legendEntry.hidden,
toggleDisabled: !legendEntry.hidden && isLegendEntryToggleDisabled(legendEntry),
isPreviousPeriod: legendEntry.isPreviousPeriod,
diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-utils.ts b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-utils.ts
index 84144f344..e09e5b615 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-utils.ts
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-utils.ts
@@ -38,6 +38,7 @@ export type ChartDataset = {
projectName?: string
tooltip?: string
data: number[]
+ lastDataPointUnavailable?: boolean
borderColor: string
backgroundColor: string
borderDash?: number[]
diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/use-analytics-chart-datasets.ts b/apps/frontend/src/components/analytics-dashboard/analytics-chart/use-analytics-chart-datasets.ts
index 482c87900..2b0c92de8 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/use-analytics-chart-datasets.ts
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/use-analytics-chart-datasets.ts
@@ -32,6 +32,7 @@ import {
type ChartDataset,
getChartDatasetTotal,
getShortHourlyAxisTickLimit,
+ getSliceBucketRange,
getSliceCount,
shouldCapitalizeBreakdownLabel,
} from './analytics-chart-utils'
@@ -161,8 +162,8 @@ export function useAnalyticsChartDatasets(
)
: undefined
})
- const chartDatasetsByStat = computed>(() =>
- buildDatasetsByStat(
+ const chartDatasetsByStat = computed>(() => {
+ const datasets = buildDatasetsByStat(
context.displayedTimeSlices.value,
selectedProjects.value,
legendPalette.value,
@@ -175,8 +176,20 @@ export function useAnalyticsChartDatasets(
showProjectVersionNames.value ? context.getVersionProjectName : undefined,
formatMessage,
sliceCount.value,
- ),
- )
+ )
+ const nextFetchRequest = context.displayedFetchRequest.value
+ if (
+ nextFetchRequest &&
+ context.displayedSelectedGroupBy.value === 'day' &&
+ isLastBucketCurrentDay(nextFetchRequest.time_range, sliceCount.value)
+ ) {
+ datasets.revenue = datasets.revenue.map((dataset) => ({
+ ...dataset,
+ lastDataPointUnavailable: true,
+ }))
+ }
+ return datasets
+ })
const previousChartDatasetsByStat = computed>(() =>
buildDatasetsByStat(
context.displayedPreviousTimeSlices.value,
@@ -406,6 +419,16 @@ function buildDatasetsByStat(
return datasetsByStat
}
+function isLastBucketCurrentDay(timeRange: Labrinth.Analytics.v3.TimeRange, sliceCount: number) {
+ const lastBucket = getSliceBucketRange(timeRange, sliceCount, sliceCount - 1)
+ const todayStart = new Date()
+ todayStart.setHours(0, 0, 0, 0)
+ const tomorrowStart = new Date(todayStart)
+ tomorrowStart.setDate(tomorrowStart.getDate() + 1)
+
+ return lastBucket.start < tomorrowStart && lastBucket.end > todayStart
+}
+
function sortDatasetsByTotal(datasets: ChartDataset[]) {
return [...datasets]
.sort((a, b) => {
diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts b/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts
index f388bd3df..d669769ec 100644
--- a/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts
+++ b/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts
@@ -688,6 +688,10 @@ export const analyticsChartMessages = defineMessages({
id: 'analytics.chart.tooltip.hide-entry',
defaultMessage: 'Hide {name} in graph',
},
+ noData: {
+ id: 'analytics.chart.tooltip.no-data',
+ defaultMessage: 'No data',
+ },
durationDays: {
id: 'analytics.chart.tooltip.duration.days',
defaultMessage: '{count, plural, one {# day} other {# days}}',
diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json
index 1c7cdbd4e..aac072810 100644
--- a/apps/frontend/src/locales/en-US/index.json
+++ b/apps/frontend/src/locales/en-US/index.json
@@ -191,6 +191,9 @@
"analytics.chart.tooltip.hide-entry": {
"message": "Hide {name} in graph"
},
+ "analytics.chart.tooltip.no-data": {
+ "message": "No data"
+ },
"analytics.chart.tooltip.pinned": {
"message": "Chart tooltip pinned"
},