[yew-devel] [PATCH yew-comp 17/20] rrd: precalculate the grid line and label positions

Dominik Csapak d.csapak at proxmox.com
Fri May 30 14:21:59 CEST 2025


Introduce a new `RrdGrid` struct, that calculates the correct positions
of the grid lines and label positions with the help of a `GraphSpace`
struct.

Since the calculation is now split off from the creation of the
component, the code becomes much clearer, and the code in the main graph
module becomes much smaller.

It saves only the base data, not the actual component themselves, they
are generated via helper methods on the fly.

This should speed up all operations on the graph that don't need to
update the grid lines, like moving the mouse cursor over the data.

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 src/rrd/graph.rs |  98 +++++--------------------------
 src/rrd/grid.rs  | 147 +++++++++++++++++++++++++++++++++++++++++++++++
 src/rrd/mod.rs   |   2 +
 3 files changed, 164 insertions(+), 83 deletions(-)
 create mode 100644 src/rrd/grid.rs

diff --git a/src/rrd/graph.rs b/src/rrd/graph.rs
index a02e086..7845dc3 100644
--- a/src/rrd/graph.rs
+++ b/src/rrd/graph.rs
@@ -128,13 +128,14 @@ pub struct PwtRRDGraph {
     y_label_ref: NodeRef,
     serie0_visible: bool,
     serie1_visible: bool,
+    grid: RrdGrid,
 }
 
-use pwt::widget::canvas::{Canvas, Circle, Group, Path, Rect, SvgLength, Text};
+use pwt::widget::canvas::{Canvas, Circle, Group, Path, Rect};
 
 use super::graph_space::{CoordinateRange, GraphSpace};
+use super::grid::RrdGrid;
 use super::series::{compute_fill_path, compute_outline_path};
-use super::units::GraphKeyData;
 use super::Series;
 
 fn format_date_time(t: i64) -> String {
@@ -187,6 +188,7 @@ impl PwtRRDGraph {
         let (time_data, data1, data2) = self.get_view_data(ctx);
         self.graph_space
             .update(time_data, &[data1, data2], props.include_zero, props.binary);
+        self.grid = RrdGrid::new(&self.graph_space);
     }
 
     fn get_view_data<'a>(&self, ctx: &'a Context<Self>) -> (&'a [i64], &'a [f64], &'a [f64]) {
@@ -222,88 +224,14 @@ impl PwtRRDGraph {
 
         let (data0, data1, data2) = self.get_view_data(ctx);
 
-        let GraphKeyData {
-            data_min,
-            data_max,
-            data_interval,
-            time_max,
-            time_interval,
-            start_time,
-            ..
-        } = self.graph_space.graph_data;
-
-        let mut grid_path = String::new();
-
-        let mut value_labels: Vec<Html> = Vec::new();
-        let mut time_labels: Vec<Html> = Vec::new();
-
-        if !data0.is_empty() {
-            let (x0, x1) = self.graph_space.get_x_range(CoordinateRange::OutsideBorder);
-
-            let mut v = data_min;
-            while v <= data_max {
-                let y = self.graph_space.compute_y(v);
-                grid_path.push_str(&format!("M {:.1} {:.1} L {:.1} {:.1}", x0, y, x1, y));
-
-                let label = render_value(props, v);
-                value_labels.push(
-                    Text::new(label)
-                        .class("pwt-rrd-label-text")
-                        .position(x0 as f32, y as f32)
-                        .dy(SvgLength::Px(4.0))
-                        .dx(SvgLength::Px(-4.0))
-                        .attribute("text-anchor", "end")
-                        .into(),
-                );
-
-                v += data_interval;
-            }
-
-            let mut t = start_time;
-            let (ymin, ymax) = self.graph_space.get_y_range(CoordinateRange::OutsideBorder);
-
-            let mut last_date = String::new();
-
-            while t <= time_max {
-                let x = self.graph_space.compute_x(t);
-                grid_path.push_str(&format!("M {:.1} {:.1} L {:.1} {:.1}", x, ymin, x, ymax));
-
-                let (time, date) = format_time(t);
-
-                time_labels.push(
-                    Text::new(time)
-                        .class("pwt-rrd-label-text")
-                        .position(x as f32, ymin as f32)
-                        .dy(SvgLength::Px(10.0))
-                        .attribute("text-anchor", "middle")
-                        .into(),
-                );
-
-                if date != last_date {
-                    time_labels.push(
-                        Text::new(date.clone())
-                            .class("pwt-rrd-label-text")
-                            .position(x as f32, ymin as f32)
-                            .dy(SvgLength::Px(10.0 + 16.0))
-                            .attribute("text-anchor", "middle")
-                            .into(),
-                    );
-
-                    last_date = date;
-                }
-
-                t += time_interval;
-            }
-        }
         let mut children: Vec<Html> = Vec::new();
 
-        children.push(
-            Path::new()
-                .key("grid")
-                .class("pwt-rrd-grid")
-                .d(grid_path)
-                .into(),
-        );
+        // draw grid and labels
+        let (value_labels, time_labels) = self
+            .grid
+            .to_label_list(|x| render_value(props, x), format_time);
+
+        children.push(self.grid.to_path().key("grid").into());
         children.push(Group::new().key("time-labels").children(time_labels).into());
 
         children.push(
@@ -497,11 +425,14 @@ impl Component for PwtRRDGraph {
     fn create(ctx: &Context<Self>) -> Self {
         ctx.link().send_message(Msg::Reload);
 
+        let graph_space = GraphSpace::default();
+        let grid = RrdGrid::new(&graph_space);
+
         let mut this = Self {
             node_ref: NodeRef::default(),
             size_observer: None,
             canvas_ref: NodeRef::default(),
-            graph_space: GraphSpace::default(),
+            graph_space,
             selection: None,
             view_range: None,
             captured_pointer_id: None,
@@ -511,6 +442,7 @@ impl Component for PwtRRDGraph {
             y_label_ref: NodeRef::default(),
             serie0_visible: true,
             serie1_visible: true,
+            grid,
         };
 
         this.update_grid_content(ctx);
diff --git a/src/rrd/grid.rs b/src/rrd/grid.rs
new file mode 100644
index 0000000..dbad2c5
--- /dev/null
+++ b/src/rrd/grid.rs
@@ -0,0 +1,147 @@
+use pwt::{
+    props::WidgetBuilder,
+    widget::canvas::{Path, SvgLength, Text},
+};
+use yew::Html;
+
+use super::graph_space::{CoordinateRange, GraphSpace};
+
+/// Holds the coordinates and path for the rrd grid and labels, so we don't have to recalculate
+/// them too often
+pub(crate) struct RrdGrid {
+    x_points: XPoints,
+    y_points: YPoints,
+    grid_path: String,
+
+    x0: f64,
+    y0: f64,
+}
+
+pub type XPoints = Vec<(f64, i64)>;
+pub type YPoints = Vec<(f64, f64)>;
+
+impl RrdGrid {
+    /// Calculates the correct points for the grid and labels with help from [`GraphSpace`]
+    pub fn new(graph_space: &GraphSpace) -> Self {
+        let (x_points, y_points) = Self::calculate_grid_points(graph_space);
+        let grid_path = Self::create_svg_path(&x_points, &y_points, graph_space);
+
+        let (x0, _) = graph_space.get_x_range(CoordinateRange::OutsideBorder);
+        let (y0, _) = graph_space.get_y_range(CoordinateRange::OutsideBorder);
+
+        Self {
+            x_points,
+            y_points,
+            grid_path,
+            x0,
+            y0,
+        }
+    }
+
+    /// returns a [`Path`] object for the RRD grid
+    pub fn to_path(&self) -> Path {
+        Path::new().class("pwt-rrd-grid").d(self.grid_path.clone())
+    }
+
+    /// Returns a list of labels (using a [`Text`] component) for the values (y axis) and points in time (x axis)
+    pub fn to_label_list<R, T>(&self, render_value: R, format_time: T) -> (Vec<Html>, Vec<Html>)
+    where
+        R: Fn(f64) -> String,
+        T: Fn(i64) -> (String, String),
+    {
+        let mut value_labels: Vec<Html> = Vec::new();
+        let mut time_labels: Vec<Html> = Vec::new();
+
+        for (y, v) in &self.y_points {
+            let label = render_value(*v);
+            value_labels.push(
+                Text::new(label)
+                    .class("pwt-rrd-label-text")
+                    .position(self.x0 as f32, *y as f32)
+                    .dy(SvgLength::Px(4.0))
+                    .dx(SvgLength::Px(-4.0))
+                    .attribute("text-anchor", "end")
+                    .into(),
+            );
+        }
+
+        let mut last_date = String::new();
+        for (x, t) in &self.x_points {
+            let (time, date) = format_time(*t);
+
+            time_labels.push(
+                Text::new(time)
+                    .class("pwt-rrd-label-text")
+                    .position(*x as f32, self.y0 as f32)
+                    .dy(SvgLength::Px(10.0))
+                    .attribute("text-anchor", "middle")
+                    .into(),
+            );
+
+            if date != last_date {
+                time_labels.push(
+                    Text::new(date.clone())
+                        .class("pwt-rrd-label-text")
+                        .position(*x as f32, self.y0 as f32)
+                        .dy(SvgLength::Px(10.0 + 16.0))
+                        .attribute("text-anchor", "middle")
+                        .into(),
+                );
+
+                last_date = date;
+            }
+        }
+
+        (value_labels, time_labels)
+    }
+
+    // maps the coordinates of the grid points from data space to svg space
+    fn calculate_grid_points(graph_space: &GraphSpace) -> (XPoints, YPoints) {
+        let mut x_points = Vec::new();
+        let mut y_points = Vec::new();
+
+        let parameters = &graph_space.graph_data;
+
+        let mut v = parameters.data_min;
+        if parameters.data_range > 0.0 {
+            while v <= parameters.data_max {
+                let y = graph_space.compute_y(v);
+                y_points.push((y, v));
+                v += parameters.data_interval;
+            }
+        }
+
+        let mut t = parameters.start_time;
+
+        if parameters.time_range > 0 {
+            while t <= parameters.time_max {
+                let x = graph_space.compute_x(t);
+                x_points.push((x, t));
+                t += parameters.time_interval;
+            }
+        }
+        (x_points, y_points)
+    }
+
+    // creates the svg path for the grid lines
+    fn create_svg_path(
+        x_points: &[(f64, i64)],
+        y_points: &[(f64, f64)],
+        graph_space: &GraphSpace,
+    ) -> String {
+        let mut grid_path = String::new();
+
+        let (x0, x1) = graph_space.get_x_range(CoordinateRange::OutsideBorder);
+        let (y0, y1) = graph_space.get_y_range(CoordinateRange::OutsideBorder);
+
+        for (y, _) in y_points {
+            grid_path.push_str(&format!("M {:.1} {:.1} L {:.1} {:.1}", x0, y, x1, y));
+        }
+
+        for (x, _) in x_points {
+            grid_path.push_str(&format!("M {:.1} {:.1} L {:.1} {:.1}", x, y0, x, y1));
+        }
+
+        grid_path
+    }
+}
diff --git a/src/rrd/mod.rs b/src/rrd/mod.rs
index b55ac3f..5bcc9be 100644
--- a/src/rrd/mod.rs
+++ b/src/rrd/mod.rs
@@ -3,6 +3,8 @@ pub use graph::*;
 
 pub(crate) mod graph_space;
 
+pub(crate) mod grid;
+
 pub(crate) mod series;
 pub use series::Series;
 
-- 
2.39.5





More information about the yew-devel mailing list