Lines
0 %
Functions
Branches
100 %
//! Canvas renderer for the browser. Uses `plotters-canvas::CanvasBackend`
//! and the shared `draw_on` dispatch in `crate::draw`, so a chart
//! rendered into a `<canvas>` looks identical to its SVG twin.
//!
//! Only compiles under feature `canvas`, which is enabled by the
//! `nomisync-frontend` WASM crate.
use plotters::prelude::*;
use plotters_canvas::CanvasBackend;
use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;
use crate::draw::draw_on;
use crate::spec::ChartSpec;
/// Render `spec` into `canvas` via `plotters-canvas`. Sizes the
/// backing canvas to `device_pixel_ratio` so the chart stays crisp on
/// `HiDPI` screens; the caller is expected to set the CSS width/height
/// to the logical size before calling.
///
/// # Errors
/// Returns `Err(JsValue)` when the canvas element cannot be wrapped
/// (missing 2D context or the shared drawing dispatch fails).
pub fn render_canvas(spec: &ChartSpec, canvas: &HtmlCanvasElement) -> Result<(), JsValue> {
apply_dpr(canvas)?;
let backend = CanvasBackend::with_canvas_object(canvas.clone())
.ok_or_else(|| JsValue::from_str("plotters-canvas: failed to get 2D context"))?;
let root = backend.into_drawing_area();
draw_on(&root, spec).map_err(|e| JsValue::from_str(&format!("draw failed: {e}")))?;
root.present()
.map_err(|e| JsValue::from_str(&format!("present failed: {e}")))?;
Ok(())
}
/// Multiply the backing store by the window's device pixel ratio so
/// strokes and text stay crisp. CSS width/height (the logical size)
/// must be set by the caller — this function only touches the
/// `width`/`height` attributes on the canvas element.
fn apply_dpr(canvas: &HtmlCanvasElement) -> Result<(), JsValue> {
let dpr = web_sys::window()
.ok_or_else(|| JsValue::from_str("no window"))?
.device_pixel_ratio();
if dpr <= 0.0 || !dpr.is_finite() {
return Ok(());
let logical_w = f64::from(canvas.client_width().max(1));
let logical_h = f64::from(canvas.client_height().max(1));
let backing_w = (logical_w * dpr).round().max(1.0) as u32;
let backing_h = (logical_h * dpr).round().max(1.0) as u32;
// Only resize the backing store when the canvas is attached and has
// non-zero CSS dimensions. Otherwise leave the author's width/height
// attributes untouched.
if logical_w > 0.0 && logical_h > 0.0 {
canvas.set_width(backing_w);
canvas.set_height(backing_h);