# How Sniffnet Renders and Updates Its Real-Time Traffic Chart: A Deep Dive

> Discover how Sniffnet renders its real-time traffic chart using plotters-iced2. Learn how the TrafficChart struct updates automatically with new network data. Explore the deep dive into Sniffnet's charting mechanism.

- Repository: [Giuliano Bellini/sniffnet](https://github.com/GyulyVGC/sniffnet)
- Tags: deep-dive
- Published: 2026-04-28

---

**Sniffnet renders its real-time traffic chart using the `plotters-iced2` crate, where the `TrafficChart` struct stores spline-based time series data and implements the `Chart` trait to draw inbound/outbound area series that update automatically when the `Sniffer` receives new `InfoTraffic` messages.**

Sniffnet, the open-source network monitoring application written in Rust, visualizes live packet captures through a smoothly scrolling real-time traffic chart. This component displays incoming and outgoing traffic volumes as colored area charts that refresh continuously during active captures. Understanding the underlying implementation reveals how the project decouples data aggregation from visual rendering while maintaining a responsive 30-second sliding window view.

## Data Collection and State Management

The chart’s state lives in [`src/chart/types/traffic_chart.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/chart/types/traffic_chart.rs), where the `TrafficChart` struct maintains four independent time series plus scaling metadata. The struct definition (lines 28-61) appears as follows:

```rust
pub struct TrafficChart {
    pub ticks: u32,                     // current time-slot index
    pub out_bytes: ChartSeries,
    pub in_bytes: ChartSeries,
    pub out_packets: ChartSeries,
    pub in_packets: ChartSeries,
    pub min_bytes: f32,
    pub max_bytes: f32,
    pub min_packets: f32,
    pub max_packets: f32,
    pub language: Language,
    pub data_repr: DataRepr,            // Bytes / Packets / Bits
    pub style: StyleType,
    pub thumbnail: bool,
    pub is_live_capture: bool,
    pub no_more_packets: bool,
    pub first_packet_timestamp: Timestamp,
}

```

When the capture engine processes a new batch of packets, the `Sniffer::refresh_data` method (located in [`src/gui/sniffer.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/sniffer.rs) at lines 309-311) forwards an `InfoTraffic` message to update the chart:

```rust
self.traffic_chart.update_charts_data(&msg, no_more_packets);

```

The `update_charts_data` method (lines 85-126 in [`traffic_chart.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/traffic_chart.rs)) extracts totals from `InfoTraffic.tot_data_info` for the current interval. It converts these values to signed floats, storing **outbound values as negative numbers** so the area appears on the left side of the Y-axis centerline:

```rust
let out_bytes_entry = -(info_traffic_msg.tot_data_info.outgoing_data(DataRepr::Bytes) as f32);
let in_bytes_entry  =  info_traffic_msg.tot_data_info.incoming_data(DataRepr::Bytes) as f32;
self.out_bytes.update_series((tot_seconds, out_bytes_entry), self.is_live_capture, no_more_packets);
self.in_bytes .update_series((tot_seconds, in_bytes_entry ), self.is_live_capture, no_more_packets);
self.min_bytes = self.out_bytes.get_min();
self.max_bytes = self.in_bytes.get_max();

```

When reading from offline pcap files, `Sniffer::offline_gap` (lines 220-224 in [`sniffer.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/sniffer.rs)) detects temporal gaps and calls `push_offline_gap_to_splines` to insert zero-valued points, ensuring the chart displays flat lines during capture pauses rather than interpolating across the gap.

## Rendering with Plotters

`TrafficChart` implements the `Chart<Message>` trait from `plotters-iced2`, with the core rendering logic residing in the `build_chart` method (lines 152-207 in [`traffic_chart.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/traffic_chart.rs)). This method constructs a Cartesian coordinate system and draws the visualization in distinct stages:

1. **Configure drawing area**: `set_margins_and_label_areas` removes margins in thumbnail mode to maximize drawing space.
2. **Compute axis ranges**: `x_axis_range()` returns the last 30 seconds (or a single-point placeholder), while `y_axis_range()` uses the pre-calculated min/max values from the series (multiplying by 8 when displaying bits).
3. **Draw mesh**: Configures axis styles, grid lines, and label formatters for both axes.
4. **Render area series**: Calls `area_series(direction)` to build `AreaSeries` objects from the spline data, applying the current style’s color and opacity (`alpha_chart_badge`).
5. **Draw zero line**: A horizontal `LineSeries` at *y = 0* masks the negative outbound values during live captures.
6. **Configure legend**: Positions the legend in the upper-right corner using localized labels from `incoming_translation` and `outgoing_translation`.

The resulting chart displays two filled areas—typically green for incoming and orange for outgoing traffic—that smoothly interpolate between data points using the internal spline representation.

## GUI Integration and Update Cycle

The chart integrates into the Iced-based GUI through the `view` method, which returns an `Element` containing a `ChartWidget`:

```rust
pub fn view(&self) -> Element<'_, Message, StyleType> {
    let x_labels = if self.is_live_capture || self.thumbnail { None } else { /* timestamps */ };
    Column::new()
        .push(ChartWidget::new(self))
        .push(x_labels)
        .into()
}

```

This view is embedded in two distinct locations:
- **Overview page**: `container_chart` in [`src/gui/pages/overview_page.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/pages/overview_page.rs) (lines 92-106) displays the full-size chart with titles and controls.
- **Thumbnail page**: [`src/gui/pages/thumbnail_page.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/pages/thumbnail_page.rs) (lines 53-59) embeds a compact version where `thumbnail = true` triggers reduced margins and hidden X-axis labels.

The update cycle follows a reactive pattern: when the backend thread sends a `BackendTrafficMessage::TickRun`, the `Sniffer::tick_run` method triggers `refresh_data`, which mutates the `TrafficChart` state. Because Iced’s architecture automatically redraws widgets when their underlying data changes, the chart updates fluidly without explicit frame scheduling, creating the illusion of a continuously scrolling real-time graph.

## Summary

- **State storage**: `TrafficChart` maintains four `ChartSeries` instances (incoming/outgoing bytes/packets) with min/max bounds for dynamic Y-axis scaling in [`src/chart/types/traffic_chart.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/chart/types/traffic_chart.rs).
- **Data flow**: Raw packets transform into `InfoTraffic` messages, which `Sniffer::refresh_data` (in [`src/gui/sniffer.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/sniffer.rs)) passes to `update_charts_data` to populate the series.
- **Rendering engine**: The `Chart` trait implementation in `TrafficChart` leverages `plotters-iced2` to build Cartesian coordinates, draw smoothed area series, and configure legends via `build_chart`.
- **UI integration**: The `view` method produces a `ChartWidget` embedded in both the main overview and thumbnail pages, with layout adjustments controlled by the `thumbnail` boolean flag.
- **Real-time updates**: Iced’s reactive framework automatically re-renders the widget whenever the underlying data changes, driven by tick messages from the capture backend.

## Frequently Asked Questions

### What charting library does Sniffnet use for its real-time visualization?

Sniffnet uses **plotters-iced2**, an Iced-compatible backend for the Plotters Rust visualization library. The `TrafficChart` struct implements the `Chart<Message>` trait from this crate, utilizing its `ChartBuilder` API to construct Cartesian coordinates and draw area series with anti-aliased splines.

### How does Sniffnet distinguish between incoming and outgoing traffic on the same chart?

The application stores **outgoing traffic values as negative floats** while keeping incoming values positive. During rendering in `build_chart`, a horizontal zero-line hides the negative portion's edge, causing the outbound area to extend leftward from the centerline and the inbound area to extend rightward, creating a symmetrical visualization around the Y-axis origin.

### Why does the chart show flat lines instead of drops when reading offline pcap files?

When parsing pcap files, `Sniffer::offline_gap` detects temporal gaps between packets and calls `push_offline_gap_to_splines` to insert zero-valued data points into the `ChartSeries`. This ensures the spline interpolation creates a flat line at zero during inactive periods rather than drawing connecting lines across the time gap, accurately representing the absence of traffic.

### How does Sniffnet handle different data representations (Bytes, Packets, Bits)?

The `TrafficChart` stores separate `ChartSeries` for both bytes and packets simultaneously. When a user switches the display mode via the UI dropdown (triggering `Sniffer::data_repr_selection`), the chart simply changes which series it queries during the `view` and `build_chart` phases. For **Bits** mode, the Y-axis range calculation multiplies the byte values by 8 before determining scale limits.