Skip to content

Improve the utility of the Traffic History graph #5

Description

@qaid

Background

TrafficGraphView (Sources/Spook/Views/TrafficGraphView.swift) renders a simple line+fill graph of total download/upload traffic. GraphCanvas draws the graph using SwiftUI Path geometry. There are no axis labels, no interactive elements, and the data does not refresh while the panel stays open.

Current Limitations

  1. No Y-axis labels: The graph normalises to maxValue but never displays it. Users see a line but can't read the scale.
  2. No X-axis time labels: The time range (1h / 24h / 7d) is selected but no timestamps or time markers appear on the graph.
  3. Stale data: The graph loads once on .task and again when the range picker changes. It does not auto-refresh during an open session — live network activity accumulates in the database but is not reflected.
  4. Confusing legend: The legend shows "Total: ↓X ↑Y" using lastSample.cumulativeIn/Out, but GraphCanvas plots per-sample bytesIn/Out. The word "Total" and the cumulative/per-period distinction is unclear.
  5. No hover interaction: There is no way to inspect the value at a specific point in the graph.

What to Fix

Required

1. Y-axis peak label

Inside GraphCanvas (or overlaid on TrafficGraphView), show the maxValue formatted with ByteFormatter.format() at the top-left of the graph canvas. Optionally, show a midpoint label (maxValue / 2) at the horizontal grid line midpoint.

2. X-axis time labels

Below GraphCanvas, add a small HStack with the start and end timestamps of the visible range:

  • For .hour: "1h ago" on the left, "now" on the right.
  • For .day: "24h ago" on the left, "now" on the right.
  • For .week: "7d ago" on the left, "now" on the right.

Use .font(.system(size: 9)) and .foregroundColor(.secondary) to keep them subtle.

3. Clarify the legend

Replace "Total:" with a label that reflects the selected range:

Text("\(ByteFormatter.format(lastSample.cumulativeIn))\(ByteFormatter.format(lastSample.cumulativeOut))")

Or label it "Last \(selectedRange.rawValue):" to make the period explicit.

4. Auto-refresh while the panel is open

Add a repeating timer to reload graph data. Use SwiftUI's onReceive with a Timer.publish:

.onReceive(Timer.publish(every: 60, on: .main, in: .common).autoconnect()) { _ in
    Task { await loadSamples() }
}

This should be added to the TrafficGraphView.body.

Optional

5. Hover tooltip

Use onContinuousHover (macOS 13+) on GraphCanvas to detect cursor position. Map the X coordinate back to the nearest TrafficSample and display a small overlay showing the timestamp and byte value at that point.

Acceptance Criteria

  • The maximum Y-axis value is shown as a formatted byte label on the graph.
  • Time context (start and end of the selected range) is visible on or below the graph.
  • Graph data refreshes automatically while the panel is open (at most every 60 seconds).
  • The legend label accurately communicates the time period it covers.
  • All byte values use ByteFormatter.format().

Relevant Files

  • Sources/Spook/Views/TrafficGraphView.swiftTrafficGraphView, GraphCanvas, LegendItem
  • Sources/Spook/Services/HistoryStore.swiftgetHourlySamples(hours:)
  • Sources/Spook/Utilities/Formatters.swiftByteFormatter

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions