iced_plot 0.5.0

A GPU-accelerated plotting widget for Iced.
Documentation
struct CameraUniform {
    view_proj: mat4x4<f32>,
    pixel_to_clip: vec4<f32>, // (2/width, 2/height, _, _) - for screen-space sizing
    pixel_to_world: vec4<f32>, // (world_per_pixel_x, world_per_pixel_y, _, _) - for world-space sizing and patterns
};
@group(0) @binding(0)
var<uniform> camera: CameraUniform;

struct VsIn {
    @location(0) segment_start: vec2<f32>,
    @location(1) segment_end: vec2<f32>,
    @location(2) color: vec4<f32>,
    @location(3) line_style: u32, // 0=solid, 1=dotted, 2=dashed
    @location(4) distance_start: f32, // cumulative distance at the segment start
    @location(5) segment_length_world: f32,
    @location(6) style_param: f32, // spacing for dotted, length for dashed
    @location(7) width: f32,
    @location(8) width_mode: u32,
    @location(9) along: f32, // 0=start edge, 1=end edge
    @location(10) side: f32, // -1 or +1
};

struct VsOut {
    @builtin(position) clip: vec4<f32>,
    @location(0) color: vec4<f32>,
    @interpolate(flat) @location(1) line_style: u32,
    @location(2) distance_start: f32,
    @location(3) segment_length_world: f32,
    @location(4) style_param: f32,
    @location(5) local_x_px: f32,
    @location(6) local_y_px: f32,
    @location(7) half_width_px: f32,
    @location(8) segment_length_px: f32,
};

const LINE_AA_RADIUS_PX: f32 = 1.0;
const PIXEL_TO_WORLD_MIN: f32 = 1e-12;

fn perp(v: vec2<f32>) -> vec2<f32> {
    return vec2<f32>(-v.y, v.x);
}

@vertex
fn vs_main(in: VsIn) -> VsOut {
    var out: VsOut;

    let pixel_to_world = vec2<f32>(
        max(camera.pixel_to_world.x, PIXEL_TO_WORLD_MIN),
        max(camera.pixel_to_world.y, PIXEL_TO_WORLD_MIN),
    );
    let start_px = in.segment_start / pixel_to_world;
    let end_px = in.segment_end / pixel_to_world;
    let delta_px = end_px - start_px;
    let segment_length_px = max(length(delta_px), 1e-6);
    let tangent_px = delta_px / segment_length_px;
    let normal_px = perp(tangent_px);

    var half_width_px: f32;
    if in.width_mode == 0u {
        half_width_px = max(in.width, 0.5) * 0.5;
    } else {
        let world_per_px_normal = max(
            length(normal_px * pixel_to_world),
            PIXEL_TO_WORLD_MIN,
        );
        half_width_px = max(in.width / world_per_px_normal, 0.5) * 0.5;
    }
    let outer_half_width_px = half_width_px + LINE_AA_RADIUS_PX;

    let local_x_px = select(
        -outer_half_width_px,
        segment_length_px + outer_half_width_px,
        in.along > 0.5,
    );
    let local_y_px = in.side * outer_half_width_px;

    let offset_px = tangent_px * local_x_px + normal_px * local_y_px;
    let start_clip = camera.view_proj * vec4<f32>(in.segment_start, 0.0, 1.0);
    let offset_ndc = vec2<f32>(
        offset_px.x * camera.pixel_to_clip.x,
        offset_px.y * camera.pixel_to_clip.y,
    );

    out.clip = vec4<f32>(
        start_clip.xy + offset_ndc * start_clip.w,
        start_clip.z,
        start_clip.w,
    );
    out.color = in.color;
    out.line_style = in.line_style;
    out.distance_start = in.distance_start;
    out.segment_length_world = in.segment_length_world;
    out.style_param = in.style_param;
    out.local_x_px = local_x_px;
    out.local_y_px = local_y_px;
    out.half_width_px = half_width_px;
    out.segment_length_px = segment_length_px;
    return out;
}

@fragment
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
    let clamped_x_px = clamp(in.local_x_px, 0.0, in.segment_length_px);
    let cap_dx_px = in.local_x_px - clamped_x_px;
    let distance_to_stroke_px = length(vec2<f32>(cap_dx_px, in.local_y_px));
    let edge_alpha = clamp(in.half_width_px + 0.5 - distance_to_stroke_px, 0.0, 1.0);

    let pixel_to_world = max(camera.pixel_to_world.x, PIXEL_TO_WORLD_MIN);
    let distance_along_line = in.distance_start
        + clamped_x_px * (in.segment_length_world / max(in.segment_length_px, 1e-6));

    var alpha = 1.0;

    if in.line_style == 1u {
        let spacing_world = in.style_param * pixel_to_world;
        let pattern_length = spacing_world * 2.0;
        let t = fract(distance_along_line / pattern_length);
        if t > 0.5 {
            alpha = 0.0;
        }
    } else if in.line_style == 2u {
        let dash_length_world = in.style_param * pixel_to_world;
        let gap_length_world = dash_length_world * 0.5;
        let pattern_length = dash_length_world + gap_length_world;
        let t = fract(distance_along_line / pattern_length);
        if t > (dash_length_world / pattern_length) {
            alpha = 0.0;
        }
    }

    alpha *= edge_alpha;

    if alpha < 1e-3 {
        discard;
    }

    return vec4<f32>(in.color.rgb, in.color.a * alpha);
}