From: Dominik Csapak <d.csapak@proxmox.com>
To: yew-devel@lists.proxmox.com
Subject: [yew-devel] [PATCH yew-comp 17/20] rrd: precalculate the grid line and label positions
Date: Fri, 30 May 2025 14:21:59 +0200 [thread overview]
Message-ID: <20250530122202.2779300-18-d.csapak@proxmox.com> (raw)
In-Reply-To: <20250530122202.2779300-1-d.csapak@proxmox.com>
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@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
_______________________________________________
yew-devel mailing list
yew-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/yew-devel
next prev parent reply other threads:[~2025-05-30 12:22 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-30 12:21 [yew-devel] [PATCH yew-comp 00/20] refactor and improve rrd graph code Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 01/20] remove old rrd uplot code Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 02/20] rrd: refactor code for compute_min_max Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 03/20] rrd: move into own module Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 04/20] rrd: move unit calculation to " Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 05/20] rrd: units: add tests Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 06/20] rrd: units: simplify calculations for get_grid_unit_base Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 07/20] rrd: remove unnecessary `no_data` field Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 08/20] rrd: align tooltip directly to pointer position Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 09/20] rrd: use 'cross_pos' state instead of 'draw_cross' Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 10/20] rrd: give all elements in svg keys Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 11/20] rrd: simplify toggle Msg Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 12/20] rrd: remove wrongly annotated lifetime Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 13/20] rrd: refactor series related struct and functions into own module Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 14/20] rrd: clamp view range when time_data changes Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 15/20] rrd: refactor grid data computation Dominik Csapak
2025-05-30 12:21 ` [yew-devel] [PATCH yew-comp 16/20] rrd: introduce GraphSpace struct and use it to precalculate graph data Dominik Csapak
2025-05-30 12:21 ` Dominik Csapak [this message]
2025-05-30 12:22 ` [yew-devel] [PATCH yew-comp 18/20] rrd: calculate series svg data only when necessary Dominik Csapak
2025-05-30 12:22 ` [yew-devel] [PATCH yew-comp 19/20] rrd: refactor selection rectangle calculation Dominik Csapak
2025-05-30 12:22 ` [yew-devel] [PATCH yew-comp 20/20] rrd: refactor the cross position calculation Dominik Csapak
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250530122202.2779300-18-d.csapak@proxmox.com \
--to=d.csapak@proxmox.com \
--cc=yew-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal