diff --git a/opossum/examples/ghost_focus.rs b/opossum/examples/ghost_focus.rs
index 9313760d997343396214ccb630fc234fb023b989..338a11020e49ed32f7f5a684c42a4fb7ee36dae3 100644
--- a/opossum/examples/ghost_focus.rs
+++ b/opossum/examples/ghost_focus.rs
@@ -4,7 +4,10 @@ use opossum::{
     degree,
     error::OpmResult,
     joule, millimeter,
-    nodes::{round_collimated_ray_source, Lens, NodeGroup, SpotDiagram, Wedge},
+    nodes::{
+        collimated_line_ray_source, round_collimated_ray_source, Lens, NodeGroup, SpotDiagram,
+        Wedge,
+    },
     optic_node::{Alignable, OpticNode},
     optic_ports::PortType,
     refractive_index::RefrIndexConst,
@@ -14,11 +17,10 @@ use std::path::Path;
 
 fn main() -> OpmResult<()> {
     let mut scenery = NodeGroup::default();
-    let i_src = scenery.add_node(round_collimated_ray_source(
-        millimeter!(50.0),
-        joule!(1.0),
-        5,
-    )?)?;
+    let i_src = scenery.add_node(
+        // collimated_line_ray_source(millimeter!(50.), joule!(1.), 3)?
+        round_collimated_ray_source(millimeter!(50.0), joule!(1.0), 1)?,
+    )?;
     let i_sd = scenery.add_node(SpotDiagram::default())?;
 
     let mut lens = Lens::default();
@@ -42,15 +44,16 @@ fn main() -> OpmResult<()> {
         degree!(10.0),
         &RefrIndexConst::new(1.5)?,
     )?
-    .with_tilt(degree!(0.0, 5.0, 0.0))?;
+    .with_tilt(degree!(0.0, 0.0, 0.0))?;
     wedge.set_coating(&PortType::Input, "front", &CoatingType::Fresnel)?;
     wedge.set_coating(&PortType::Input, "front", &CoatingType::Fresnel)?;
     let i_w = scenery.add_node(wedge)?;
 
     let i_sd2 = scenery.add_node(SpotDiagram::default())?;
     scenery.connect_nodes(i_src, "out1", i_sd, "in1", millimeter!(20.0))?;
-    scenery.connect_nodes(i_sd, "out1", i_l, "front", millimeter!(80.0))?;
-    scenery.connect_nodes(i_l, "rear", i_w, "front", millimeter!(70.0))?;
+    scenery.connect_nodes(i_sd, "out1", i_w, "front", millimeter!(80.0))?;
+    // scenery.connect_nodes(i_sd, "out1", i_l, "front", millimeter!(80.0))?;
+    // scenery.connect_nodes(i_l, "rear", i_w, "front", millimeter!(70.0))?;
     scenery.connect_nodes(i_w, "rear", i_sd2, "in1", millimeter!(70.0))?;
 
     let mut doc = OpmDocument::new(scenery);
diff --git a/opossum/src/analyzers/ghostfocus.rs b/opossum/src/analyzers/ghostfocus.rs
index d4297f324a2f024e75d1f9f77abfb097e1bfe016..825013033b0a6e8569b8a4914b318df95c85c7c3 100644
--- a/opossum/src/analyzers/ghostfocus.rs
+++ b/opossum/src/analyzers/ghostfocus.rs
@@ -1,14 +1,19 @@
 //! Analyzer performing a ghost focus analysis using ray tracing
 use chrono::Local;
 use log::{info, warn};
+use nalgebra::{MatrixXx2, MatrixXx3, Vector3};
+use plotters::style::RGBAColor;
 use serde::{Deserialize, Serialize};
+use uom::si::{f64::Length, length::millimeter};
 
 use crate::{
-    error::OpmResult,
+    error::{OpmResult, OpossumError},
     get_version,
     light_result::{LightRays, LightResult},
+    millimeter,
     nodes::NodeGroup,
     optic_node::OpticNode,
+    plottable::{PlotArgs, PlotData, PlotParameters, PlotSeries, PlotType, Plottable},
     properties::{Properties, Proptype},
     rays::Rays,
     reporting::analysis_report::{AnalysisReport, NodeReport},
@@ -71,17 +76,38 @@ impl Analyzer for GhostFocusAnalyzer {
             "Performing ghost focus analysis of scenery{scenery_name} up to {} ray bounces.",
             self.config.max_bounces
         );
-        AnalysisGhostFocus::analyze(scenery, LightRays::default(), &self.config)?;
+        for bounce in 0..=self.config.max_bounces {
+            let mut ray_collection = Vec::<Rays>::new();
+            if bounce % 2 == 0 {
+                scenery.set_inverted(false)?;
+                info!("Analyzing pass {bounce} (forward) ...");
+            } else {
+                scenery.set_inverted(true)?;
+                info!("Analyzing pass {bounce} (backward) ...");
+            }
+            AnalysisGhostFocus::analyze(
+                scenery,
+                LightRays::default(),
+                self.config(),
+                &mut ray_collection,
+            )?;
+            scenery.clear_edges();
+            for rays in &ray_collection {
+                scenery.add_to_accumulated_rays(rays, bounce);
+            }
+        }
+
         Ok(())
     }
     fn report(&self, scenery: &NodeGroup) -> OpmResult<AnalysisReport> {
         let mut analysis_report = AnalysisReport::new(get_version(), Local::now());
         analysis_report.add_scenery(scenery);
         let mut props = Properties::default();
-        let all_rays = scenery.accumulated_rays();
-        if let Ok(proptype) = <Rays as TryInto<Proptype>>::try_into(all_rays.clone()) {
-            props.create("propagation", "ray propagation", None, proptype)?;
-        }
+        let ghost_focus_history = GhostFocusHistory::from(scenery.accumulated_rays().clone());
+
+        let proptype = Proptype::from(ghost_focus_history);
+        props.create("propagation", "ray propagation", None, proptype)?;
+
         let node_report =
             NodeReport::new("ray propagation", "Global ray propagation", "global", props);
         analysis_report.add_node_report(node_report);
@@ -115,6 +141,7 @@ pub trait AnalysisGhostFocus: OpticNode + AnalysisRayTrace {
         &mut self,
         _incoming_data: LightRays,
         _config: &GhostFocusConfig,
+        _ray_collection: &mut Vec<Rays>,
     ) -> OpmResult<LightRays> {
         warn!(
             "{}: No ghost focus analysis function defined.",
@@ -123,3 +150,167 @@ pub trait AnalysisGhostFocus: OpticNode + AnalysisRayTrace {
         Ok(LightRays::default())
     }
 }
+
+/// struct that holds the history of the ray positions that is needed for report generation
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct GhostFocusHistory {
+    /// vector of ray positions for each raybundle at a specifc spectral position
+    pub rays_pos_history: Vec<Vec<MatrixXx3<Length>>>,
+    /// view direction if the rayposition thistory is plotted
+    pub plot_view_direction: Option<Vector3<f64>>,
+}
+impl GhostFocusHistory {
+    /// Projects the positions o fthie [`GhostFocusHistory`] onto a 2D plane
+    /// # Attributes
+    /// `plane_normal_vec`: normal vector of the plane to project onto
+    ///
+    /// # Errors
+    /// This function errors if the length of the plane normal vector is zero
+    /// # Returns
+    /// This function returns a set of 2d vectors in the defined plane projected to a view that is perpendicular to this plane.
+    pub fn project_to_plane(
+        &self,
+        plane_normal_vec: Vector3<f64>,
+    ) -> OpmResult<Vec<Vec<MatrixXx2<Length>>>> {
+        let vec_norm = plane_normal_vec.norm();
+
+        if vec_norm < f64::EPSILON {
+            return Err(OpossumError::Other(
+                "The plane normal vector must have a non-zero length!".into(),
+            ));
+        }
+
+        let normed_normal_vec = plane_normal_vec / vec_norm;
+
+        //define an axis on the plane.
+        //Do this by projection of one of the main coordinate axes onto that plane
+        //Beforehand check, if these axes are not parallel to the normal vec
+        let (co_ax_1, co_ax_2) = if plane_normal_vec.cross(&Vector3::x()).norm() < f64::EPSILON {
+            //parallel to the x-axis
+            (Vector3::z(), Vector3::y())
+        } else if plane_normal_vec.cross(&Vector3::y()).norm() < f64::EPSILON {
+            (Vector3::z(), Vector3::x())
+        } else if plane_normal_vec.cross(&Vector3::z()).norm() < f64::EPSILON {
+            (Vector3::x(), Vector3::y())
+        } else {
+            //arbitrarily project x-axis onto that plane
+            let x_vec = Vector3::x();
+            let mut proj_x = x_vec - x_vec.dot(&normed_normal_vec) * plane_normal_vec;
+            proj_x /= proj_x.norm();
+
+            //second axis defined by cross product of x-axis projection and plane normal, which yields another vector that is perpendicular to both others
+            (proj_x, proj_x.cross(&normed_normal_vec))
+        };
+
+        let mut projected_history =
+            Vec::<Vec<MatrixXx2<Length>>>::with_capacity(self.rays_pos_history.len());
+        for ray_bundle in &self.rays_pos_history {
+            let mut rays_pos_projection = Vec::<MatrixXx2<Length>>::with_capacity(ray_bundle.len());
+            for ray_pos in ray_bundle {
+                let mut projected_ray_pos = MatrixXx2::<Length>::zeros(ray_pos.column(0).len());
+                for (row, pos) in ray_pos.row_iter().enumerate() {
+                    // let pos_t = Vector3::from_vec(pos.transpose().iter().map(|p| p.get::<millimeter>()).collect::<Vec<f64>>());
+                    let pos_t = Vector3::from_vec(
+                        pos.iter()
+                            .map(uom::si::f64::Length::get::<millimeter>)
+                            .collect::<Vec<f64>>(),
+                    );
+                    let proj_pos = pos_t - pos_t.dot(&normed_normal_vec) * plane_normal_vec;
+
+                    projected_ray_pos[(row, 0)] = millimeter!(proj_pos.dot(&co_ax_1));
+                    projected_ray_pos[(row, 1)] = millimeter!(proj_pos.dot(&co_ax_2));
+                }
+                rays_pos_projection.push(projected_ray_pos);
+            }
+            projected_history.push(rays_pos_projection);
+        }
+
+        Ok(projected_history)
+    }
+}
+
+impl From<Vec<Rays>> for GhostFocusHistory {
+    fn from(value: Vec<Rays>) -> Self {
+        let mut ghost_focus_history = Vec::<Vec<MatrixXx3<Length>>>::with_capacity(value.len());
+        for rays in &value {
+            let mut rays_history = Vec::<MatrixXx3<Length>>::with_capacity(rays.nr_of_rays(false));
+            for ray in rays {
+                rays_history.push(ray.position_history());
+            }
+            ghost_focus_history.push(rays_history);
+        }
+        Self {
+            rays_pos_history: ghost_focus_history,
+            plot_view_direction: None,
+        }
+    }
+}
+
+impl Plottable for GhostFocusHistory {
+    fn add_plot_specific_params(&self, plt_params: &mut PlotParameters) -> OpmResult<()> {
+        plt_params
+            .set(&PlotArgs::XLabel("distance in mm (z axis)".into()))?
+            .set(&PlotArgs::YLabel("distance in mm (y axis)".into()))?
+            .set(&PlotArgs::PlotSize((1200, 1200)))?
+            .set(&PlotArgs::AxisEqual(true))?
+            .set(&PlotArgs::PlotAutoSize(true))?
+            .set(&PlotArgs::Legend(false))?;
+        Ok(())
+    }
+
+    fn get_plot_type(&self, plt_params: &PlotParameters) -> PlotType {
+        PlotType::MultiLine2D(plt_params.clone())
+    }
+
+    fn get_plot_series(
+        &self,
+        _plt_type: &mut PlotType,
+        _legend: bool,
+    ) -> OpmResult<Option<Vec<PlotSeries>>> {
+        if self.rays_pos_history.is_empty() {
+            Ok(None)
+        } else {
+            let num_series = self.rays_pos_history.len();
+            let mut plt_series = Vec::<PlotSeries>::with_capacity(num_series);
+
+            let Some(plot_view_direction) = self.plot_view_direction else {
+                return Err(OpossumError::Other("cannot get plot series for raypropagationvisualizer, plot_view_direction not defined".into()));
+            };
+
+            let projected_positions = self.project_to_plane(plot_view_direction)?;
+            for (i, bounce_positions) in projected_positions.iter().enumerate() {
+                let mut proj_pos_mm =
+                    Vec::<MatrixXx2<f64>>::with_capacity(projected_positions.len());
+                for ray_pos in bounce_positions {
+                    proj_pos_mm.push(MatrixXx2::from_vec(
+                        ray_pos
+                            .iter()
+                            .map(uom::si::f64::Length::get::<millimeter>)
+                            .collect::<Vec<f64>>(),
+                    ));
+                }
+
+                let gradient = colorous::TURBO;
+
+                let c = if projected_positions.len() > 10 {
+                    gradient.eval_rational(i, projected_positions.len())
+                } else {
+                    colorous::CATEGORY10[i]
+                };
+
+                let plt_data = PlotData::MultiDim2 {
+                    vec_of_xy_data: proj_pos_mm,
+                };
+
+                let series_label = format!("Bounce: {i}");
+                plt_series.push(PlotSeries::new(
+                    &plt_data,
+                    RGBAColor(c.r, c.g, c.b, 1.),
+                    Some(series_label),
+                ));
+            }
+
+            Ok(Some(plt_series))
+        }
+    }
+}
diff --git a/opossum/src/nodes/cylindric_lens/analysis_ghostfocus.rs b/opossum/src/nodes/cylindric_lens/analysis_ghostfocus.rs
index ae41f8af80501dedd76ca2a93ea043298a1b956a..72a1bdf9d301775d64955058fac50b811996a17c 100644
--- a/opossum/src/nodes/cylindric_lens/analysis_ghostfocus.rs
+++ b/opossum/src/nodes/cylindric_lens/analysis_ghostfocus.rs
@@ -4,6 +4,7 @@ use crate::{
     light_result::LightRays,
     optic_node::OpticNode,
     properties::Proptype,
+    rays::Rays,
 };
 
 use super::CylindricLens;
@@ -13,6 +14,7 @@ impl AnalysisGhostFocus for CylindricLens {
         &mut self,
         incoming_data: LightRays,
         _config: &GhostFocusConfig,
+        _ray_collection: &mut Vec<Rays>,
     ) -> OpmResult<LightRays> {
         let (in_port, out_port) = if self.inverted() {
             ("rear", "front")
diff --git a/opossum/src/nodes/fluence_detector.rs b/opossum/src/nodes/fluence_detector.rs
index 05b1ae13694d751e75a6d2539330370729ba02f2..9de6d6e8cdecd59fa2d3475a125c1019690efae9 100644
--- a/opossum/src/nodes/fluence_detector.rs
+++ b/opossum/src/nodes/fluence_detector.rs
@@ -173,6 +173,7 @@ impl AnalysisGhostFocus for FluenceDetector {
         &mut self,
         incoming_data: LightRays,
         _config: &GhostFocusConfig,
+        _ray_collection: &mut Vec<Rays>,
     ) -> OpmResult<LightRays> {
         let (in_port, out_port) = if self.inverted() {
             ("out1", "in1")
diff --git a/opossum/src/nodes/lens/analysis_ghostfocus.rs b/opossum/src/nodes/lens/analysis_ghostfocus.rs
index 54b3365932c1ead4c8a535d5b722806602dd33aa..e42d1c3b1278941e7daa30478f014604f8b7d0cf 100644
--- a/opossum/src/nodes/lens/analysis_ghostfocus.rs
+++ b/opossum/src/nodes/lens/analysis_ghostfocus.rs
@@ -5,6 +5,7 @@ use crate::{
     light_result::LightRays,
     optic_node::OpticNode,
     properties::Proptype,
+    rays::Rays,
 };
 
 impl AnalysisGhostFocus for Lens {
@@ -12,6 +13,7 @@ impl AnalysisGhostFocus for Lens {
         &mut self,
         incoming_data: LightRays,
         _config: &GhostFocusConfig,
+        _ray_collection: &mut Vec<Rays>,
     ) -> OpmResult<LightRays> {
         let (in_port, out_port) = if self.inverted() {
             ("rear", "front")
diff --git a/opossum/src/nodes/node_group/analysis_ghostfocus.rs b/opossum/src/nodes/node_group/analysis_ghostfocus.rs
index 2afa048d8582caaab02a46007c5af348757b5675..920064b66ccd1e09f3b907dfb3040843ee1cc8d6 100644
--- a/opossum/src/nodes/node_group/analysis_ghostfocus.rs
+++ b/opossum/src/nodes/node_group/analysis_ghostfocus.rs
@@ -5,14 +5,13 @@ use crate::{
     light_result::{light_rays_to_light_result, light_result_to_light_rays, LightRays},
     lightdata::LightData,
     optic_node::OpticNode,
-    optic_ports::PortType,
     rays::Rays,
 };
-use log::{info, warn};
+use log::warn;
 
 fn filter_ray_limits(light_rays: &mut LightRays, config: &GhostFocusConfig) {
     for lr in light_rays {
-        lr.1.filter_by_nr_of_bounces(config.max_bounces() + 1);
+        lr.1.filter_by_nr_of_bounces(config.max_bounces());
     }
 }
 impl AnalysisGhostFocus for NodeGroup {
@@ -20,81 +19,67 @@ impl AnalysisGhostFocus for NodeGroup {
         &mut self,
         incoming_data: LightRays,
         config: &GhostFocusConfig,
+        ray_collection: &mut Vec<Rays>,
     ) -> OpmResult<LightRays> {
-        let mut all_propagting_rays = Rays::default();
         let mut current_bouncing_rays = incoming_data;
-        let mut group_inversion = self.inverted();
-        for pass in 0..=config.max_bounces() {
-            let direction = if group_inversion {
-                "backward"
+
+        if self.inverted() {
+            self.graph.invert_graph()?;
+        }
+
+        let g_clone = self.clone();
+        if !self.graph.is_single_tree() {
+            warn!("group contains unconnected sub-trees. Analysis might not be complete.");
+        }
+        let sorted = self.graph.topologically_sorted()?;
+        for idx in sorted {
+            let node = g_clone.graph.node_by_idx(idx)?.optical_ref;
+            if self.graph.is_stale_node(idx) {
+                warn!(
+                    "graph contains stale (completely unconnected) node {}. Skipping.",
+                    node.borrow()
+                );
             } else {
-                "forward"
-            };
-            info!("Analyzing pass {pass} ({direction}) ...");
-            if group_inversion {
-                self.graph.invert_graph()?;
-            }
-            let current_bounce_data = current_bouncing_rays.clone();
-            let g_clone = self.clone();
-            if !self.graph.is_single_tree() {
-                warn!("group contains unconnected sub-trees. Analysis might not be complete.");
-            }
-            let sorted = self.graph.topologically_sorted()?;
-            for idx in sorted {
-                let node = g_clone.graph.node_by_idx(idx)?.optical_ref;
-                if self.graph.is_stale_node(idx) {
-                    warn!(
-                        "graph contains stale (completely unconnected) node {}. Skipping.",
-                        node.borrow()
-                    );
-                } else {
-                    let incoming_edges = self.graph.get_incoming(
-                        idx,
-                        &light_rays_to_light_result(current_bounce_data.clone()),
-                    );
-                    let node_name = format!("{}", node.borrow());
+                let incoming_edges = self.graph.get_incoming(
+                    idx,
+                    &light_rays_to_light_result(current_bouncing_rays.clone()),
+                );
+                let node_name = format!("{}", node.borrow());
 
-                    let mut outgoing_edges = AnalysisGhostFocus::analyze(
-                        &mut *node.borrow_mut(),
-                        light_result_to_light_rays(incoming_edges)?,
-                        config,
-                    )
-                    .map_err(|e| {
-                        OpossumError::Analysis(format!("analysis of node {node_name} failed: {e}"))
-                    })?;
-                    filter_ray_limits(&mut outgoing_edges, config);
-                    for rays in &outgoing_edges {
-                        all_propagting_rays.merge(rays.1);
-                    }
-                    current_bouncing_rays.clone_from(&outgoing_edges);
-                    let outgoing_edges = light_rays_to_light_result(outgoing_edges);
+                let mut outgoing_edges = AnalysisGhostFocus::analyze(
+                    &mut *node.borrow_mut(),
+                    light_result_to_light_rays(incoming_edges)?,
+                    config,
+                    ray_collection,
+                )
+                .map_err(|e| {
+                    OpossumError::Analysis(format!("analysis of node {node_name} failed: {e}"))
+                })?;
+                filter_ray_limits(&mut outgoing_edges, config);
+                current_bouncing_rays.clone_from(&outgoing_edges);
+                let outgoing_edges = light_rays_to_light_result(outgoing_edges);
 
-                    // If node is sink node, rewrite port names according to output mapping
-                    if self.graph.is_output_node(idx) {
-                        let portmap = if self.graph.is_inverted() {
-                            self.graph.port_map(&PortType::Input).clone()
-                        } else {
-                            self.graph.port_map(&PortType::Output).clone()
-                        };
-                        let assigned_ports = portmap.assigned_ports_for_node(idx);
-                        for port in assigned_ports {
-                            if let Some(LightData::Geometric(_rays)) = outgoing_edges.get(&port.1) {
-                                // light_result.insert(port.0, rays.clone());
-                            }
-                        }
-                    }
-                    for outgoing_edge in outgoing_edges {
+                for outgoing_edge in outgoing_edges {
+                    println!(
+                        "{}",
+                        self.graph.node_by_idx(idx)?.optical_ref.borrow().name()
+                    );
+                    let no_sink =
                         self.graph
-                            .set_outgoing_edge_data(idx, &outgoing_edge.0, outgoing_edge.1);
+                            .set_outgoing_edge_data(idx, &outgoing_edge.0, &outgoing_edge.1);
+
+                    if !no_sink {
+                        if let LightData::Geometric(rays) = outgoing_edge.1 {
+                            ray_collection.push(rays);
+                        }
                     }
                 }
             }
-            if group_inversion {
-                self.graph.invert_graph()?;
-            } // revert initial inversion (if necessary)
-            group_inversion = !group_inversion;
         }
-        self.accumulated_rays = all_propagting_rays;
+        if self.inverted() {
+            self.graph.invert_graph()?;
+            self.set_inverted(false)?;
+        } // revert initial inversion (if necessary)
         Ok(current_bouncing_rays)
     }
 }
diff --git a/opossum/src/nodes/node_group/analysis_raytrace.rs b/opossum/src/nodes/node_group/analysis_raytrace.rs
index e01f241b374ffe36af6f3592fd1d326a007f72cb..6703a969a8464276a43c72d8262f100360b5b821 100644
--- a/opossum/src/nodes/node_group/analysis_raytrace.rs
+++ b/opossum/src/nodes/node_group/analysis_raytrace.rs
@@ -74,7 +74,7 @@ impl AnalysisRayTrace for NodeGroup {
                 }
                 for outgoing_edge in outgoing_edges {
                     self.graph
-                        .set_outgoing_edge_data(idx, &outgoing_edge.0, outgoing_edge.1);
+                        .set_outgoing_edge_data(idx, &outgoing_edge.0, &outgoing_edge.1);
                 }
             }
         }
@@ -179,7 +179,7 @@ impl AnalysisRayTrace for NodeGroup {
                 }
 
                 self.graph
-                    .set_outgoing_edge_data(idx, &outgoing_edge.0, outgoing_edge.1);
+                    .set_outgoing_edge_data(idx, &outgoing_edge.0, &outgoing_edge.1);
             }
         }
         self.reset_data();
diff --git a/opossum/src/nodes/node_group/mod.rs b/opossum/src/nodes/node_group/mod.rs
index ba34acf3c9b551f0c078818956930ea92fbe2d3e..766f5c5e2139d37afd17c331a8dbfa3bc021b6ea 100644
--- a/opossum/src/nodes/node_group/mod.rs
+++ b/opossum/src/nodes/node_group/mod.rs
@@ -70,7 +70,7 @@ pub struct NodeGroup {
     node_attr: NodeAttr,
     input_port_distances: BTreeMap<String, Length>,
     #[serde(skip)]
-    accumulated_rays: Rays,
+    accumulated_rays: Vec<Rays>,
 }
 impl Default for NodeGroup {
     fn default() -> Self {
@@ -90,7 +90,7 @@ impl Default for NodeGroup {
             graph: OpticGraph::default(),
             input_port_distances: BTreeMap::default(),
             node_attr,
-            accumulated_rays: Rays::default(),
+            accumulated_rays: Vec::<Rays>::new(),
         }
     }
 }
@@ -382,11 +382,28 @@ impl NodeGroup {
     /// Returns a reference to the accumulated rays of this [`NodeGroup`].
     ///
     /// This function returns a bundle of all rays that propagated in a group after a ghost focus analysis.
-    /// This function is in particular helpful for generating a global rya propagation plot.
+    /// This function is in particular helpful for generating a global ray propagation plot.
     #[must_use]
-    pub const fn accumulated_rays(&self) -> &Rays {
+    pub const fn accumulated_rays(&self) -> &Vec<Rays> {
         &self.accumulated_rays
     }
+
+    /// add a ray bundle to the set of accumulated rays of this node group
+    /// # Arguments
+    /// - rays: pointer to ray bundle that should be included
+    /// - bounce: bouncle level of these rays
+    pub fn add_to_accumulated_rays(&mut self, rays: &Rays, bounce: usize) {
+        if self.accumulated_rays.len() <= bounce {
+            self.accumulated_rays.push(rays.clone());
+        } else {
+            self.accumulated_rays[bounce].merge(rays);
+        }
+    }
+
+    ///clears the edges of a graph. Necessary for ghost focus analysis
+    pub fn clear_edges(&mut self) {
+        self.graph.clear_edges();
+    }
 }
 
 impl OpticNode for NodeGroup {
@@ -477,7 +494,7 @@ impl OpticNode for NodeGroup {
         for node in nodes {
             node.optical_ref.borrow_mut().reset_data();
         }
-        self.accumulated_rays = Rays::default();
+        self.accumulated_rays = Vec::<Rays>::new();
     }
 }
 
diff --git a/opossum/src/nodes/node_group/optic_graph.rs b/opossum/src/nodes/node_group/optic_graph.rs
index 341dd67887e7c96bcd407f331a03cf9ad9620b06..332974d4f7eff895c6f4e4dcfa0e1b52d4104b44 100644
--- a/opossum/src/nodes/node_group/optic_graph.rs
+++ b/opossum/src/nodes/node_group/optic_graph.rs
@@ -12,6 +12,7 @@ use crate::{
     optic_senery_rsc::SceneryResources,
     port_map::PortMap,
     properties::Proptype,
+    rays::Rays,
 };
 use log::warn;
 use nalgebra::Vector3;
@@ -296,6 +297,34 @@ impl OpticGraph {
             self.incoming_edges(idx)
         }
     }
+
+    ///Clear the edges of an optic graph. Useful for back- and forth-propagation in ghost focus analysis
+    pub fn clear_edges(&mut self) {
+        let node_indices = self.g.node_indices();
+        for idx in node_indices {
+            let mut ids = Vec::<EdgeIndex>::new();
+            for edge in self.edges_directed(idx, Direction::Incoming) {
+                ids.push(edge.id());
+            }
+            for id in ids {
+                let light = self.g.edge_weight_mut(id);
+                if let Some(light) = light {
+                    light.set_data(Some(LightData::Geometric(Rays::default())));
+                }
+            }
+
+            let mut ids = Vec::<EdgeIndex>::new();
+            for edge in self.edges_directed(idx, Direction::Outgoing) {
+                ids.push(edge.id());
+            }
+            for id in ids {
+                let light = self.g.edge_weight_mut(id);
+                if let Some(light) = light {
+                    light.set_data(Some(LightData::Geometric(Rays::default())));
+                }
+            }
+        }
+    }
     /// .
     #[must_use]
     pub fn is_stale_node(&self, idx: NodeIndex) -> bool {
@@ -410,7 +439,7 @@ impl OpticGraph {
                     }
                 }
                 for outgoing_edge in outgoing_edges {
-                    self.set_outgoing_edge_data(idx, &outgoing_edge.0, outgoing_edge.1);
+                    self.set_outgoing_edge_data(idx, &outgoing_edge.0, &outgoing_edge.1);
                 }
             }
         }
@@ -480,7 +509,8 @@ impl OpticGraph {
             .collect::<LightResult>()
     }
     /// Sets the outgoing edge data of this [`OpticGraph`].
-    pub fn set_outgoing_edge_data(&mut self, idx: NodeIndex, port: &str, data: LightData) {
+    /// Returns true if data has been passed on, false otherwise
+    pub fn set_outgoing_edge_data(&mut self, idx: NodeIndex, port: &str, data: &LightData) -> bool {
         let edges = self.g.edges_directed(idx, Direction::Outgoing);
         let edge_ref = edges
             .into_iter()
@@ -490,9 +520,14 @@ impl OpticGraph {
             let edge_idx = edge_ref.id();
             let light = self.g.edge_weight_mut(edge_idx);
             if let Some(light) = light {
-                light.set_data(Some(data));
+                light.set_data(Some(data.clone()));
             }
-        } // else outgoing edge not connected -> data dropped
+            true
+        }
+        // else outgoing edge not connected -> data dropped
+        else {
+            false
+        }
     }
     fn edges_directed(&self, idx: NodeIndex, dir: Direction) -> Edges<'_, LightFlow, Directed> {
         self.g.edges_directed(idx, dir)
diff --git a/opossum/src/nodes/ray_propagation_visualizer.rs b/opossum/src/nodes/ray_propagation_visualizer.rs
index 14d77815ab9b0e9e67dc1f0a7bb3fd986adb48f2..2aaa6296aab4d12e28bcfacee341bdb1b4b38814 100644
--- a/opossum/src/nodes/ray_propagation_visualizer.rs
+++ b/opossum/src/nodes/ray_propagation_visualizer.rs
@@ -170,6 +170,7 @@ impl AnalysisGhostFocus for RayPropagationVisualizer {
         &mut self,
         incoming_data: LightRays,
         _config: &GhostFocusConfig,
+        _ray_collection: &mut Vec<Rays>,
     ) -> OpmResult<LightRays> {
         let (in_port, out_port) = if self.inverted() {
             ("out1", "in1")
diff --git a/opossum/src/nodes/source.rs b/opossum/src/nodes/source.rs
index d0a0d135c0ad33e63a069ab9da8ac949b44a5f51..e1576f7addd774d2b5bfc20f59782e94219010a2 100644
--- a/opossum/src/nodes/source.rs
+++ b/opossum/src/nodes/source.rs
@@ -6,14 +6,12 @@ use super::node_attr::NodeAttr;
 use crate::{
     analyzers::{
         energy::AnalysisEnergy, ghostfocus::AnalysisGhostFocus, raytrace::AnalysisRayTrace,
-        Analyzable, RayTraceConfig,
+        Analyzable, GhostFocusConfig, RayTraceConfig,
     },
     dottable::Dottable,
     error::{OpmResult, OpossumError},
     joule,
-    light_result::{
-        light_rays_to_light_result, light_result_to_light_rays, LightRays, LightResult,
-    },
+    light_result::{light_result_to_light_rays, LightRays, LightResult},
     lightdata::LightData,
     millimeter,
     optic_node::{Alignable, OpticNode},
@@ -269,7 +267,8 @@ impl AnalysisGhostFocus for Source {
     fn analyze(
         &mut self,
         incoming_data: LightRays,
-        _config: &crate::analyzers::GhostFocusConfig,
+        _config: &GhostFocusConfig,
+        _ray_collection: &mut Vec<Rays>,
     ) -> OpmResult<LightRays> {
         let mut rays = if self.inverted() {
             let Some(bouncing_rays) = incoming_data.get("out1") else {
@@ -303,11 +302,12 @@ impl AnalysisGhostFocus for Source {
                 "no location for surface defined. Aborting".into(),
             ));
         }
-        let outgoing = AnalysisRayTrace::analyze(
-            self,
-            light_rays_to_light_result(incoming_data),
-            &RayTraceConfig::default(),
-        )?;
+        // let outgoing = AnalysisRayTrace::analyze(
+        //     self,
+        //     light_rays_to_light_result(incoming_data),
+        //     &RayTraceConfig::default(),
+        // )?;
+        let outgoing = LightResult::from([("out1".into(), LightData::Geometric(rays))]);
         light_result_to_light_rays(outgoing)
     }
 }
diff --git a/opossum/src/nodes/spot_diagram.rs b/opossum/src/nodes/spot_diagram.rs
index 0679382ec77ece3e2bed15aa2e4512bc59c224cf..8cd4470d92cc4a8fd7242bd32e32e7662f56b577 100644
--- a/opossum/src/nodes/spot_diagram.rs
+++ b/opossum/src/nodes/spot_diagram.rs
@@ -183,6 +183,7 @@ impl OpticNode for SpotDiagram {
     }
     fn reset_data(&mut self) {
         self.light_data = None;
+        self.surface.reset_hit_map();
     }
 }
 
@@ -197,6 +198,7 @@ impl AnalysisGhostFocus for SpotDiagram {
         &mut self,
         incoming_data: LightRays,
         _config: &GhostFocusConfig,
+        _ray_collection: &mut Vec<Rays>,
     ) -> OpmResult<LightRays> {
         let (in_port, out_port) = if self.inverted() {
             ("out1", "in1")
@@ -224,7 +226,7 @@ impl AnalysisGhostFocus for SpotDiagram {
         self.light_data = Some(ray_cache);
 
         let mut out_light_rays = LightRays::default();
-        out_light_rays.insert(out_port.to_string(), rays.clone());
+        out_light_rays.insert(out_port.to_string(), rays);
         Ok(out_light_rays)
     }
 }
diff --git a/opossum/src/nodes/wedge/analysis_ghostfocus.rs b/opossum/src/nodes/wedge/analysis_ghostfocus.rs
index 36f2fbf9a442b21736151a552247dd9ba439c2f4..a106408a8f7cbec8b575f27c3c0c29b61effe916 100644
--- a/opossum/src/nodes/wedge/analysis_ghostfocus.rs
+++ b/opossum/src/nodes/wedge/analysis_ghostfocus.rs
@@ -5,6 +5,7 @@ use crate::{
     light_result::LightRays,
     optic_node::OpticNode,
     properties::Proptype,
+    rays::Rays,
 };
 
 impl AnalysisGhostFocus for Wedge {
@@ -12,6 +13,7 @@ impl AnalysisGhostFocus for Wedge {
         &mut self,
         incoming_data: LightRays,
         _config: &GhostFocusConfig,
+        _ray_collection: &mut Vec<Rays>,
     ) -> OpmResult<LightRays> {
         let (in_port, out_port) = if self.inverted() {
             ("rear", "front")
diff --git a/opossum/src/plottable.rs b/opossum/src/plottable.rs
index b19bf2189eb8814d7769e61375defd547791f194..c06e94fa96e328b0ab472f3a17da2afea4ac392e 100644
--- a/opossum/src/plottable.rs
+++ b/opossum/src/plottable.rs
@@ -1415,7 +1415,8 @@ pub trait Plottable {
 
         if let Some(plt_series) = &mut plt_series_opt {
             if plt_series.len() == 1 {
-                plt_series[0].color = RGBAColor(255, 0, 0, 0.8);
+                let c = colorous::CATEGORY10[0];
+                plt_series[0].color = RGBAColor(c.r, c.g, c.b, 1.);
             }
         }
         plt_series_opt.map_or(Ok(None), |plt_series| plt_type.plot(&plt_series))
diff --git a/opossum/src/port_map.rs b/opossum/src/port_map.rs
index 18b8d8954744ef2ea418dac35d459eb2f060f947..65fb7a1a501c48982009f05fcbd6889e77f80e4c 100644
--- a/opossum/src/port_map.rs
+++ b/opossum/src/port_map.rs
@@ -14,6 +14,7 @@ impl PortMap {
     pub fn port_names(&self) -> Vec<String> {
         self.0.iter().map(|p| p.0.clone()).collect_vec()
     }
+
     /// Get the internal node port info for the given external port name.
     #[must_use]
     pub fn get(&self, port_name: &str) -> Option<&(NodeIndex, String)> {
diff --git a/opossum/src/properties/mod.rs b/opossum/src/properties/mod.rs
index b1d6541f3f28bd301b43d55a3370c95a1f44f8f5..605c5b9f1ebb490be2ad4fd6b25889469353f492 100644
--- a/opossum/src/properties/mod.rs
+++ b/opossum/src/properties/mod.rs
@@ -152,7 +152,11 @@ impl Properties {
             } else {
                 uuid
             };
-            if let Ok(html_prop_value) = prop.1.prop().to_html(node_name, node_uuid) {
+            if let Ok(html_prop_value) = prop
+                .1
+                .prop()
+                .to_html((prop.0.to_owned() + "_" + node_name).as_str(), node_uuid)
+            {
                 let html_prop = HtmlProperty {
                     name: prop.0.to_owned(),
                     description: prop.1.description().into(),
@@ -176,7 +180,8 @@ impl Properties {
     /// returns an error.
     pub fn export_data(&self, report_path: &Path, id: &str) -> OpmResult<()> {
         for prop in &self.props {
-            prop.1.export_data(report_path, id)?;
+            prop.1
+                .export_data(report_path, (prop.0.to_owned() + "_" + id).as_str())?;
         }
         Ok(())
     }
diff --git a/opossum/src/properties/property.rs b/opossum/src/properties/property.rs
index dcd004856903486ef46f4ba4eb8d89635671ff0f..d59129149643a6162d22965d1455b1f41580cb9d 100644
--- a/opossum/src/properties/property.rs
+++ b/opossum/src/properties/property.rs
@@ -191,6 +191,12 @@ impl Property {
                 ray_hist_clone.plot_view_direction = Some(vector![1.0, 0.0, 0.0]);
                 ray_hist_clone.to_plot(&file_path, crate::plottable::PltBackEnd::SVG)?;
             }
+            Proptype::GhostFocusHistory(ghost_hist) => {
+                let file_path = report_path.join(Path::new(&format!("ghost_propagation_{id}.svg")));
+                let mut ghost_hist = ghost_hist.clone();
+                ghost_hist.plot_view_direction = Some(vector![1.0, 0.0, 0.0]);
+                ghost_hist.to_plot(&file_path, crate::plottable::PltBackEnd::SVG)?;
+            }
             Proptype::WaveFrontData(_wf_data) => {
                 todo!()
             }
diff --git a/opossum/src/properties/proptype.rs b/opossum/src/properties/proptype.rs
index 1a1f67dfc969cf4c5ca98afbc521b35a99241aaf..896d345758c370d41e781f25762f372b9ad541d2 100644
--- a/opossum/src/properties/proptype.rs
+++ b/opossum/src/properties/proptype.rs
@@ -1,4 +1,5 @@
 use crate::{
+    analyzers::ghostfocus::GhostFocusHistory,
     aperture::Aperture,
     error::{OpmResult, OpossumError},
     lightdata::LightData,
@@ -83,6 +84,9 @@ pub enum Proptype {
     WaveFrontData(WaveFrontData),
     /// This property stores the ray position history of all [`Rays`](crate::rays::Rays) during propagation through the optic scenery
     RayPositionHistory(RayPositionHistories),
+    /// This property stores the ray position history of all [`Rays`](crate::rays::Rays), separated by their bounce level,
+    /// during propagation through the optic scenery
+    GhostFocusHistory(GhostFocusHistory),
     /// A (nested set) of Properties
     NodeReport(NodeReport),
     /// linear density in `1/length_unit`
@@ -179,6 +183,11 @@ impl Proptype {
                 "image",
                 &format!("data/ray_propagation_{property_name}_{uuid}.svg"),
             ),
+            Self::GhostFocusHistory(_) => tt.render(
+                "image",
+                &format!("data/ghost_propagation_{property_name}_{uuid}.svg"),
+            ),
+
             _ => Ok("unknown property type".into()),
         };
         string_value.map_err(|e| OpossumError::Other(e.to_string()))
@@ -230,6 +239,12 @@ impl From<Angle> for Proptype {
     }
 }
 
+impl From<GhostFocusHistory> for Proptype {
+    fn from(value: GhostFocusHistory) -> Self {
+        Self::GhostFocusHistory(value)
+    }
+}
+
 fn format_value_with_prefix(value: f64) -> String {
     if value.is_nan() {
         return String::from("     nan ");
diff --git a/opossum/src/rays.rs b/opossum/src/rays.rs
index 25dff290af692c3e4175804f4718aae334ca9794..7b76e74abb1a81c5e3420f374a59d01ba1fc3c00 100644
--- a/opossum/src/rays.rs
+++ b/opossum/src/rays.rs
@@ -1035,12 +1035,12 @@ impl Rays {
             ray.set_invalid();
         }
     }
-    /// Invalide all rays that have a number of bounces (reflections) higher or equal than the given upper limit.
+    /// Invalide all rays that have a number of bounces (reflections) higher than the given upper limit.
     pub fn filter_by_nr_of_bounces(&mut self, max_bounces: usize) {
         for ray in self
             .rays
             .iter_mut()
-            .filter(|r| r.number_of_bounces() >= max_bounces)
+            .filter(|r| r.number_of_bounces() > max_bounces)
         {
             ray.set_invalid();
         }
diff --git a/opossum/src/surface/hit_map.rs b/opossum/src/surface/hit_map.rs
index 6aa5af55849a4ed55208f9247bbde087b8c359d3..f663647222ca783c981fc6aefc90cee64c56f4aa 100644
--- a/opossum/src/surface/hit_map.rs
+++ b/opossum/src/surface/hit_map.rs
@@ -1,6 +1,5 @@
 //! Data structure for storing intersection points (and energies) of [`Rays`](crate::rays::Rays) hitting an
 //! [`OpticalSurface`](crate::surface::OpticalSurface).
-use colorous::Color;
 use nalgebra::{DVector, MatrixXx2, Point2, Point3};
 use plotters::style::RGBAColor;
 use serde::{Deserialize, Serialize};
@@ -108,9 +107,7 @@ impl Plottable for HitMap {
             };
 
             let gradient = colorous::TURBO;
-            let c = if self.hit_map.len() == 1 {
-                Color { r: 255, g: 0, b: 0 }
-            } else if self.hit_map.len() > 10 {
+            let c = if self.hit_map.len() > 10 {
                 gradient.eval_rational(i, self.hit_map.len())
             } else {
                 colorous::CATEGORY10[i]
@@ -128,8 +125,16 @@ impl Plottable for HitMap {
         x_min *= f64::powi(10., -x_exponent);
         y_min *= f64::powi(10., -y_exponent);
 
-        let x_limits = AxLims::create_useful_axlims(x_min * 1.1, x_max * 1.1);
-        let y_limits = AxLims::create_useful_axlims(y_min * 1.1, y_max * 1.1);
+        let x_diff = x_max - x_min;
+        let y_diff = y_max - y_min;
+        let x_limits = AxLims::create_useful_axlims(
+            0.1f64.mul_add(-x_diff, x_min),
+            0.1f64.mul_add(x_diff, x_max),
+        );
+        let y_limits = AxLims::create_useful_axlims(
+            0.1f64.mul_add(-y_diff, y_min),
+            0.1f64.mul_add(-y_diff, y_min),
+        );
 
         plt_type.set_plot_param(&PlotArgs::XLim(x_limits))?;
         plt_type.set_plot_param(&PlotArgs::YLim(y_limits))?;