diff --git a/src/mavlink/reflection.rs b/src/mavlink/reflection.rs
index c7665364fda6ffb74bf642523e3ace510c722c64..e3ed70e723913efad04c48e64e1bf43777ec1ad0 100644
--- a/src/mavlink/reflection.rs
+++ b/src/mavlink/reflection.rs
@@ -7,9 +7,10 @@
 use std::collections::HashMap;
 
 use mavlink_bindgen::parser::{MavProfile, MavType};
+use serde::ser::SerializeStruct;
 use skyward_mavlink::mavlink::Message;
 
-use crate::error::ErrInstrument;
+use crate::{MAVLINK_PROFILE, error::ErrInstrument};
 
 use super::MAVLINK_PROFILE_SERIALIZED;
 
@@ -267,6 +268,32 @@ impl PartialEq for IndexedField {
     }
 }
 
+impl serde::Serialize for IndexedField {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        let mut state = serializer.serialize_struct("IndexedField", 3)?;
+        state.serialize_field("id", &self.id)?;
+        state.serialize_field("msg_id", &self.msg.id)?;
+        state.end()
+    }
+}
+
+impl<'de> serde::Deserialize<'de> for IndexedField {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        #[derive(serde::Deserialize)]
+        struct IndexedFieldDe {
+            id: usize,
+            msg_id: u32,
+        }
+
+        let de = IndexedFieldDe::deserialize(deserializer)?;
+        let field = de
+            .id
+            .to_mav_field(de.msg_id, &MAVLINK_PROFILE)
+            .map_err(|u| serde::de::Error::custom(format!("Invalid field: {}", u)))?;
+        Ok(field)
+    }
+}
+
 pub trait MessageLike {
     fn to_mav_message(
         &self,
diff --git a/src/ui/panes/plot.rs b/src/ui/panes/plot.rs
index 7159a19c2a21f92c5c8e7943e9b100c07796b5eb..c778a69e2cdf6dfbb951e77757efd22e98c688d3 100644
--- a/src/ui/panes/plot.rs
+++ b/src/ui/panes/plot.rs
@@ -19,6 +19,7 @@ use source_window::sources_window;
 use std::{
     hash::{DefaultHasher, Hash, Hasher},
     iter::zip,
+    time::{Duration, Instant},
 };
 
 #[derive(Clone, Default, Debug, Serialize, Deserialize)]
@@ -26,7 +27,7 @@ pub struct Plot2DPane {
     settings: PlotSettings,
     // UI settings
     #[serde(skip)]
-    line_data: Vec<Vec<PlotPoint>>,
+    line_data: Vec<TimeAwarePlotPoints>,
     #[serde(skip)]
     state_valid: bool,
     #[serde(skip)]
@@ -149,7 +150,9 @@ impl PaneBehavior for Plot2DPane {
                 response.set_drag_started();
             }
 
-            for ((field, settings), points) in zip(self.settings.plot_lines(), &self.line_data) {
+            for ((field, settings), TimeAwarePlotPoints { points, .. }) in
+                zip(&self.settings.y_fields, &self.line_data)
+            {
                 plot_ui.line(
                     Line::new(&points[..])
                         .color(settings.color)
@@ -188,9 +191,17 @@ impl PaneBehavior for Plot2DPane {
         }
 
         let PlotSettings {
-            x_field, y_fields, ..
+            x_field,
+            y_fields,
+            points_lifespan,
+            ..
         } = &self.settings;
 
+        // purge old data
+        for line in &mut self.line_data {
+            line.purge_old(*points_lifespan);
+        }
+
         for msg in messages {
             let x: f64 = x_field.extract_as_f64(&msg.message).log_unwrap();
             let ys: Vec<f64> = y_fields
@@ -199,11 +210,11 @@ impl PaneBehavior for Plot2DPane {
                 .collect();
 
             if self.line_data.len() < ys.len() {
-                self.line_data.resize(ys.len(), Vec::new());
+                self.line_data.resize(ys.len(), TimeAwarePlotPoints::new());
             }
 
-            for (line, y) in zip(&mut self.line_data, ys) {
-                line.push(PlotPoint::new(x, y));
+            for (points, y) in zip(&mut self.line_data, ys) {
+                points.push(msg.time, PlotPoint::new(x, y));
             }
         }
 
@@ -230,51 +241,21 @@ fn show_menu(ui: &mut egui::Ui, settings_visible: &mut bool, settings: &mut Plot
     ui.checkbox(&mut settings.axes_visible, "Show Axes");
 }
 
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 struct PlotSettings {
-    plot_message_id: u32,
-    x_field: IndexedField,
-    y_fields: Vec<(IndexedField, LineSettings)>,
-    axes_visible: bool,
+    /// The message id to plot
+    pub(super) plot_message_id: u32,
+    /// The field to plot on the x-axis
+    pub(super) x_field: IndexedField,
+    /// The fields to plot, with their respective line settings
+    pub(super) y_fields: Vec<(IndexedField, LineSettings)>,
+    /// Whether to show the axes of the plot
+    pub(super) axes_visible: bool,
+    /// Points will be shown for this duration before being removed
+    pub(super) points_lifespan: Duration,
 }
 
 impl PlotSettings {
-    fn plot_lines(&self) -> &[(IndexedField, LineSettings)] {
-        &self.y_fields
-    }
-
-    fn fields_empty(&self) -> bool {
-        self.y_fields.is_empty()
-    }
-
-    fn get_msg_id(&self) -> u32 {
-        self.plot_message_id
-    }
-
-    fn get_x_field(&self) -> &IndexedField {
-        &self.x_field
-    }
-
-    fn get_mut_x_field(&mut self) -> &mut IndexedField {
-        &mut self.x_field
-    }
-
-    fn get_mut_y_fields(&mut self) -> &mut [(IndexedField, LineSettings)] {
-        &mut self.y_fields[..]
-    }
-
-    fn set_x_field(&mut self, field: IndexedField) {
-        self.x_field = field;
-    }
-
-    fn fields_len(&self) -> usize {
-        self.y_fields.len()
-    }
-
-    fn contains_field(&self, field: &IndexedField) -> bool {
-        self.y_fields.iter().any(|(f, _)| f == field)
-    }
-
     fn add_field(&mut self, field: IndexedField) {
         let line_settings = LineSettings::default();
         self.y_fields.push((field, line_settings));
@@ -310,6 +291,7 @@ impl Default for PlotSettings {
             x_field,
             y_fields,
             axes_visible: true,
+            points_lifespan: Duration::from_secs(600),
         }
     }
 }
@@ -336,69 +318,33 @@ impl Hash for LineSettings {
     }
 }
 
-mod plot_serde {
-    use serde::{Deserialize, Serialize};
-
-    use super::*;
-
-    #[derive(Serialize, Deserialize)]
-    struct FieldSettings {
-        field: usize,
-        settings: LineSettings,
-    }
+#[derive(Clone, Debug)]
+struct TimeAwarePlotPoints {
+    times: Vec<Instant>,
+    points: Vec<PlotPoint>,
+}
 
-    #[derive(Serialize, Deserialize)]
-    struct PlotSettingsData {
-        msg_id: u32,
-        x_field: usize,
-        y_fields: Vec<FieldSettings>,
-        axes_visible: bool,
+impl TimeAwarePlotPoints {
+    fn new() -> Self {
+        Self {
+            times: Vec::new(),
+            points: Vec::new(),
+        }
     }
 
-    impl Serialize for PlotSettings {
-        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
-            let data = PlotSettingsData {
-                msg_id: self.plot_message_id,
-                x_field: self.x_field.id(),
-                y_fields: self
-                    .y_fields
-                    .iter()
-                    .map(|(field, settings)| FieldSettings {
-                        field: field.id(),
-                        settings: settings.clone(),
-                    })
-                    .collect(),
-                axes_visible: self.axes_visible,
-            };
-            data.serialize(serializer)
-        }
+    fn push(&mut self, time: Instant, point: PlotPoint) {
+        self.times.push(time);
+        self.points.push(point);
     }
 
-    impl<'de> Deserialize<'de> for PlotSettings {
-        fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
-            let data = PlotSettingsData::deserialize(deserializer)?;
-            let x_field = data
-                .x_field
-                .to_mav_field(data.msg_id, &MAVLINK_PROFILE)
-                .log_unwrap();
-            let y_fields = data
-                .y_fields
-                .into_iter()
-                .map(|FieldSettings { field, settings }| {
-                    (
-                        field
-                            .to_mav_field(data.msg_id, &MAVLINK_PROFILE)
-                            .log_unwrap(),
-                        settings,
-                    )
-                })
-                .collect();
-            Ok(Self {
-                plot_message_id: data.msg_id,
-                x_field,
-                y_fields,
-                axes_visible: data.axes_visible,
-            })
+    fn purge_old(&mut self, lifespan: Duration) {
+        while let Some(time) = self.times.first().copied() {
+            if time.elapsed() > lifespan {
+                self.times.remove(0);
+                self.points.remove(0);
+            } else {
+                break;
+            }
         }
     }
 }
diff --git a/src/ui/panes/plot/source_window.rs b/src/ui/panes/plot/source_window.rs
index 61ca3c7d22001a2fb32eb7e1db2e36fd2e6b815f..a62d1f9e41bcb3154c7bd4197327fc62c37a7306 100644
--- a/src/ui/panes/plot/source_window.rs
+++ b/src/ui/panes/plot/source_window.rs
@@ -1,13 +1,30 @@
-use crate::MAVLINK_PROFILE;
+use std::time::Duration;
 
-use crate::error::ErrInstrument;
+use crate::{MAVLINK_PROFILE, error::ErrInstrument};
 
 use super::{LineSettings, PlotSettings};
 
 #[profiling::function]
 pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) {
-    let data_settings_digest = plot_settings.data_digest();
+    // select how many points are shown on the plot
+    let mut points_lifespan_sec = plot_settings.points_lifespan.as_secs();
+    ui.horizontal(|ui| {
+        let res1 = ui.add(egui::Label::new("Points Lifespan: "));
+        let res2 = ui.add(
+            egui::DragValue::new(&mut points_lifespan_sec)
+                .range(5..=1800)
+                .speed(1)
+                .suffix(" seconds"),
+        );
+        res1.union(res2)
+    })
+    .inner
+    .on_hover_text("How long the data is shown on the plot");
+    plot_settings.points_lifespan = Duration::from_secs(points_lifespan_sec);
+
+    ui.add_sized([250., 10.], egui::Separator::default());
 
+    let data_settings_digest = plot_settings.data_digest();
     // extract the msg name from the id to show it in the combo box
     let msg_name = MAVLINK_PROFILE
         .get_msg(plot_settings.plot_message_id)
@@ -30,10 +47,10 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) {
 
     // check fields and assign a default field_x and field_y once the msg is changed
     let fields = MAVLINK_PROFILE
-        .get_plottable_fields(plot_settings.get_msg_id())
+        .get_plottable_fields(plot_settings.plot_message_id)
         .log_expect("Invalid message id");
     // get the first field that is in the list of fields or the previous if valid
-    let x_field = plot_settings.get_x_field();
+    let x_field = &plot_settings.x_field;
     let new_field_x = fields
         .iter()
         .any(|f| f == x_field)
@@ -46,10 +63,10 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) {
         return;
     };
     // update the field_x
-    plot_settings.set_x_field(new_field_x);
-    let x_field = plot_settings.get_mut_x_field();
+    plot_settings.x_field = new_field_x;
 
     // if fields are valid, show the combo boxes for the x_axis
+    let x_field = &mut plot_settings.x_field;
     egui::ComboBox::from_label("X Axis")
         .selected_text(&x_field.field().name)
         .show_ui(ui, |ui| {
@@ -59,19 +76,17 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) {
         });
 
     // populate the plot_lines with the first field if it is empty and there are more than 1 fields
-    if plot_settings.fields_empty() && fields.len() > 1 {
+    if plot_settings.y_fields.is_empty() && fields.len() > 1 {
         plot_settings.add_field(fields[1].to_owned());
     }
 
     // check how many fields are left and how many are selected
-    let plot_lines_len = plot_settings.fields_len();
+    let plot_lines_len = plot_settings.y_fields.len();
     egui::Grid::new(ui.auto_id_with("y_axis"))
         .num_columns(3)
         .spacing([10.0, 2.5])
         .show(ui, |ui| {
-            for (i, (field, line_settings)) in
-                plot_settings.get_mut_y_fields().iter_mut().enumerate()
-            {
+            for (i, (field, line_settings)) in plot_settings.y_fields[..].iter_mut().enumerate() {
                 let LineSettings { width, color } = line_settings;
                 let widget_label = if plot_lines_len > 1 {
                     format!("Y Axis {}", i + 1)
@@ -104,9 +119,10 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) {
             .on_hover_text("Add another Y axis")
             .clicked()
     {
+        // get the first field that is not in the plot_lines
         let next_field = fields
             .iter()
-            .find(|f| !plot_settings.contains_field(f))
+            .find(|field| !plot_settings.y_fields.iter().any(|(f, _)| f == *field))
             .log_unwrap();
         plot_settings.add_field(next_field.to_owned());
     }