From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: <yew-devel-bounces@lists.proxmox.com> Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 32DD31FF15C for <inbox@lore.proxmox.com>; Fri, 30 May 2025 14:22:20 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 2DFBA2E74B; Fri, 30 May 2025 14:22:36 +0200 (CEST) From: Dominik Csapak <d.csapak@proxmox.com> To: yew-devel@lists.proxmox.com Date: Fri, 30 May 2025 14:21:59 +0200 Message-Id: <20250530122202.2779300-18-d.csapak@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250530122202.2779300-1-d.csapak@proxmox.com> References: <20250530122202.2779300-1-d.csapak@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.022 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [yew-devel] [PATCH yew-comp 17/20] rrd: precalculate the grid line and label positions X-BeenThere: yew-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Yew framework devel list at Proxmox <yew-devel.lists.proxmox.com> List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/yew-devel>, <mailto:yew-devel-request@lists.proxmox.com?subject=unsubscribe> List-Archive: <http://lists.proxmox.com/pipermail/yew-devel/> List-Post: <mailto:yew-devel@lists.proxmox.com> List-Help: <mailto:yew-devel-request@lists.proxmox.com?subject=help> List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/yew-devel>, <mailto:yew-devel-request@lists.proxmox.com?subject=subscribe> Reply-To: Yew framework devel list at Proxmox <yew-devel@lists.proxmox.com> Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: yew-devel-bounces@lists.proxmox.com Sender: "yew-devel" <yew-devel-bounces@lists.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