Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 114 additions & 109 deletions apps/mobile/src/components/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ export function BarChart({

const { disableScroll, enableScroll } = useScreenScrollControl()

const today = new Date()
today.setHours(0, 0, 0, 0)

// Find oldest date from data or events
const oldestDate = React.useMemo(() => {
const candidatesForOldestDate: Date[] = []
Expand Down Expand Up @@ -109,138 +106,146 @@ export function BarChart({
}, [data, events])

// Generate bars data (including empty bars)
const bars: Bar[] = []
if (timeRange === 'Year') {
// For year view, group all data by month+year and sum values

// Find the current month
const currentMonth = new Date(today.getFullYear(), today.getMonth(), 1)
currentMonth.setHours(0, 0, 0, 0)

// Calculate total months from first month to current month
const totalMonths = (currentMonth.getFullYear() - oldestDate.getFullYear()) * 12 +
(currentMonth.getMonth() - oldestDate.getMonth()) + 1

// Determine the start month - backfill if needed to ensure at least 12 bars
// Always end at current month, backfill empty months before first month if needed
let startMonth: Date
if (totalMonths < 12) {
// Backfill empty months before the first month to reach 12 bars total
const monthsToBackfill = 12 - totalMonths
startMonth = new Date(oldestDate)
startMonth.setMonth(startMonth.getMonth() - monthsToBackfill)
startMonth.setDate(1)
startMonth.setHours(0, 0, 0, 0)
} else {
startMonth = new Date(oldestDate)
}
// Memoize bars to prevent unnecessary re-renders and scroll resets
const bars = React.useMemo(() => {
const today = new Date()
today.setHours(0, 0, 0, 0)

const bars: Bar[] = []
if (timeRange === 'Year') {
// For year view, group all data by month+year and sum values

// Find the current month
const currentMonth = new Date(today.getFullYear(), today.getMonth(), 1)
currentMonth.setHours(0, 0, 0, 0)

// Calculate total months from first month to current month
const totalMonths = (currentMonth.getFullYear() - oldestDate.getFullYear()) * 12 +
(currentMonth.getMonth() - oldestDate.getMonth()) + 1

// Determine the start month - backfill if needed to ensure at least 12 bars
// Always end at current month, backfill empty months before first month if needed
let startMonth: Date
if (totalMonths < 12) {
// Backfill empty months before the first month to reach 12 bars total
const monthsToBackfill = 12 - totalMonths
startMonth = new Date(oldestDate)
startMonth.setMonth(startMonth.getMonth() - monthsToBackfill)
startMonth.setDate(1)
startMonth.setHours(0, 0, 0, 0)
} else {
startMonth = new Date(oldestDate)
}

// Loop through all months from start month to current month (always at least 12)
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
const currentMonthDate = new Date(startMonth)
// Loop through all months from start month to current month (always at least 12)
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
const currentMonthDate = new Date(startMonth)

while (currentMonthDate <= currentMonth) {
const monthYear = currentMonthDate.getFullYear()
const monthIndex = currentMonthDate.getMonth()
while (currentMonthDate <= currentMonth) {
const monthYear = currentMonthDate.getFullYear()
const monthIndex = currentMonthDate.getMonth()

let monthValue = 0
// Find all data points in this month+year and sum their values
const monthData = data.filter(d => {
const dDate = new Date(d.date)
dDate.setHours(0, 0, 0, 0)
return dDate.getFullYear() === monthYear && dDate.getMonth() === monthIndex
})
let monthValue = 0
// Find all data points in this month+year and sum their values
const monthData = data.filter(d => {
const dDate = new Date(d.date)
dDate.setHours(0, 0, 0, 0)
return dDate.getFullYear() === monthYear && dDate.getMonth() === monthIndex
})

monthValue = monthData.reduce((sum, d) => sum + d.value, 0)
monthValue = monthData.reduce((sum, d) => sum + d.value, 0)

// Collect log entries for this month
const monthLogs: Array<BarChartLogEntry> = []
monthData.forEach(d => {
if (d.logs) {
monthLogs.push(...d.logs)
}
})
// Collect log entries for this month
const monthLogs: Array<BarChartLogEntry> = []
monthData.forEach(d => {
if (d.logs) {
monthLogs.push(...d.logs)
}
})

bars.push({
label: monthNames[monthIndex],
value: monthValue,
date: new Date(currentMonthDate),
logs: monthLogs.length > 0 ? monthLogs : undefined,
})
bars.push({
label: monthNames[monthIndex],
value: monthValue,
date: new Date(currentMonthDate),
logs: monthLogs.length > 0 ? monthLogs : undefined,
})

// Move to next month
currentMonthDate.setMonth(currentMonthDate.getMonth() + 1)
}
} else {
// For week and month views, iterate day by day
const msInDay = 24 * 60 * 60 * 1000
// Move to next month
currentMonthDate.setMonth(currentMonthDate.getMonth() + 1)
}
} else {
// For week and month views, iterate day by day
const msInDay = 24 * 60 * 60 * 1000

const normalizedOldestDate = new Date(oldestDate)
normalizedOldestDate.setHours(0, 0, 0, 0)
const normalizedOldestDate = new Date(oldestDate)
normalizedOldestDate.setHours(0, 0, 0, 0)

const normalizedNewestDate = new Date(newestDate)
normalizedNewestDate.setHours(0, 0, 0, 0)
const normalizedNewestDate = new Date(newestDate)
normalizedNewestDate.setHours(0, 0, 0, 0)

// Like Year view, always show up through "today" (unless there is future data/events).
const endDate = new Date(Math.max(today.getTime(), normalizedNewestDate.getTime()))
endDate.setHours(0, 0, 0, 0)
// Like Year view, always show up through "today" (unless there is future data/events).
const endDate = new Date(Math.max(today.getTime(), normalizedNewestDate.getTime()))
endDate.setHours(0, 0, 0, 0)

const minBarsForRange = timeRange === 'Week' ? 7 : 30
const minBarsForRange = timeRange === 'Week' ? 7 : 30

// Clamp start so we don't get negative ranges (e.g. future-only data).
const naturalStartDate = new Date(Math.min(normalizedOldestDate.getTime(), endDate.getTime()))
naturalStartDate.setHours(0, 0, 0, 0)
// Clamp start so we don't get negative ranges (e.g. future-only data).
const naturalStartDate = new Date(Math.min(normalizedOldestDate.getTime(), endDate.getTime()))
naturalStartDate.setHours(0, 0, 0, 0)

const totalDays = Math.floor((endDate.getTime() - naturalStartDate.getTime()) / msInDay) + 1
const totalDays = Math.floor((endDate.getTime() - naturalStartDate.getTime()) / msInDay) + 1

// Backfill empty days before the first day to ensure a minimum bar count.
const startDate = new Date(naturalStartDate)
if (totalDays < minBarsForRange) {
const daysToBackfill = minBarsForRange - totalDays
startDate.setDate(startDate.getDate() - daysToBackfill)
}
// Backfill empty days before the first day to ensure a minimum bar count.
const startDate = new Date(naturalStartDate)
if (totalDays < minBarsForRange) {
const daysToBackfill = minBarsForRange - totalDays
startDate.setDate(startDate.getDate() - daysToBackfill)
}

const currentDate = new Date(startDate)
while (currentDate <= endDate) {
const existingData = data.find(d => {
const dDate = new Date(d.date)
dDate.setHours(0, 0, 0, 0)
const currentDate = new Date(startDate)
while (currentDate <= endDate) {
const existingData = data.find(d => {
const dDate = new Date(d.date)
dDate.setHours(0, 0, 0, 0)

return dDate.getTime() === currentDate.getTime()
})
return dDate.getTime() === currentDate.getTime()
})

let label = ''
if (timeRange === 'Week') {
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
label = `${dayNames[currentDate.getDay()]} ${currentDate.getDate()}`
} else {
// Month
if (currentDate.getDate() % 5 !== 0) {
label = ''
let label = ''
if (timeRange === 'Week') {
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
label = `${dayNames[currentDate.getDay()]} ${currentDate.getDate()}`
} else {
label = `${currentDate.getMonth() + 1}/${currentDate.getDate()}`
// Month
if (currentDate.getDate() % 5 !== 0) {
label = ''
} else {
label = `${currentDate.getMonth() + 1}/${currentDate.getDate()}`
}
}
}

bars.push({
label,
value: existingData ? existingData.value : 0,
date: new Date(currentDate),
logs: existingData?.logs,
})
bars.push({
label,
value: existingData ? existingData.value : 0,
date: new Date(currentDate),
logs: existingData?.logs,
})

currentDate.setDate(currentDate.getDate() + 1)
}
}

currentDate.setDate(currentDate.getDate() + 1)
// Limit to latest X bars to prevent performance issues
if (bars.length > MAX_BARS) {
bars.splice(0, bars.length - MAX_BARS)
}
}

// Limit to latest X bars to prevent performance issues
if (bars.length > MAX_BARS) {
bars.splice(0, bars.length - MAX_BARS)
}
return bars
}, [data, events, timeRange, oldestDate, newestDate])

const numBarsInView = timeRange === 'Year' ? 12 : timeRange === 'Week' ? 7 : 30
const availableWidth = CHART_WIDTH - (numBarsInView * BAR_SPACING)
const barWidth = Math.max(availableWidth / numBarsInView, 4) - (showYAxisLabels ? 2 : 0)
const barWidth = React.useMemo(() => Math.max(availableWidth / numBarsInView, 4) - (showYAxisLabels ? 2 : 0), [numBarsInView, showYAxisLabels])
const maxValue = Math.max(...bars.map(d => d.value), 1)
const chartHeight = height - (showXAxisLabels ? LABEL_HEIGHT : 0) - (events.length > 0 ? INFO_ICON_HEIGHT : 0)

Expand Down