iced_plot 0.5.0

A GPU-accelerated plotting widget for Iced.
Documentation
//! Demonstrates toggling plot interaction controls at runtime.
use iced::{
    Element, Length, keyboard, mouse,
    widget::{checkbox, column, container, row, text},
};
use iced_plot::{
    ClickAction, Color, DragAction, KeyAction, LineStyle, MarkerStyle, PlotUiMessage, PlotWidget,
    PlotWidgetBuilder, ScrollAction, Series,
};

fn main() -> iced::Result {
    iced::application(App::new, App::update, App::view)
        .font(include_bytes!("fonts/FiraCodeNerdFont-Regular.ttf"))
        .default_font(iced::Font::with_name("FiraCode Nerd Font"))
        .run()
}

#[derive(Debug, Clone)]
enum Message {
    Plot(PlotUiMessage),
    ToggleScrollToPan(bool),
    ToggleDragToPan(bool),
    ToggleArrowsToPan(bool),
    ToggleBoxZoom(bool),
    ToggleCtrlScrollZoom(bool),
    ToggleDoubleClickAutoscale(bool),
    ToggleKeyAutoscale(bool),
    ToggleClickToPick(bool),
    ToggleClearPickOnEscape(bool),
    ToggleHighlightOnHover(bool),
    ToggleShowControlsHelp(bool),
}

struct App {
    widget: PlotWidget,
}

impl App {
    fn new() -> Self {
        let line = Series::line_only(
            (0..240)
                .map(|i| {
                    let x = i as f64 * 0.05;
                    [x, (x * 0.8).sin()]
                })
                .collect(),
            LineStyle::solid(),
        )
        .with_label("sin(x)")
        .with_color(Color::from_rgb(0.3, 0.7, 1.0));

        let markers = Series::markers_only(
            (0..80)
                .map(|i| {
                    let x = i as f64 * 0.15;
                    [x, (x * 0.6).cos() * 0.8]
                })
                .collect(),
            MarkerStyle::circle(5.0),
        )
        .with_label("0.8 * cos(x)")
        .with_color(Color::from_rgb(1.0, 0.5, 0.35));

        let widget = PlotWidgetBuilder::new()
            .with_x_label("x")
            .with_y_label("y")
            .with_crosshairs(true)
            .with_cursor_overlay(true)
            .with_pick_highlight_provider(|ctx, point| {
                point.resize_marker(1.8);
                point.color = Color::from_rgb(1.0, 0.2, 0.2);
                Some(format!(
                    "{}\nindex: {}\nx: {:.2}\ny: {:.2}\n(selected)",
                    ctx.series_label, ctx.point_index, point.x, point.y
                ))
            })
            .add_series(line)
            .add_series(markers)
            .build()
            .unwrap();
        Self { widget }
    }

    fn set_drag(&mut self, button: mouse::Button, action: DragAction, enabled: bool) {
        if enabled {
            self.widget.get_controls_mut().bind_drag(button, action);
        } else {
            self.widget.get_controls_mut().unbind_drag(button);
        }
    }

    fn drag_enabled(&self, button: mouse::Button, action: DragAction) -> bool {
        self.widget.get_controls().drag_is_bound(button, action)
    }

    fn set_scroll(&mut self, modifiers: keyboard::Modifiers, action: ScrollAction, enabled: bool) {
        if enabled {
            self.widget
                .get_controls_mut()
                .bind_scroll(modifiers, action);
        } else {
            self.widget.get_controls_mut().unbind_scroll(modifiers);
        }
    }

    fn scroll_enabled(&self, modifiers: keyboard::Modifiers, action: ScrollAction) -> bool {
        self.widget
            .get_controls()
            .scroll_is_bound(modifiers, action)
    }

    fn set_click(&mut self, button: mouse::Button, action: ClickAction, enabled: bool) {
        if enabled {
            self.widget.get_controls_mut().bind_click(button, action);
        } else {
            self.widget.get_controls_mut().unbind_click(button);
        }
    }

    fn click_enabled(&self, button: mouse::Button, action: ClickAction) -> bool {
        self.widget.get_controls().click_is_bound(button, action)
    }

    fn set_double_click(&mut self, button: mouse::Button, action: ClickAction, enabled: bool) {
        if enabled {
            self.widget
                .get_controls_mut()
                .bind_double_click(button, action);
        } else {
            self.widget.get_controls_mut().unbind_double_click(button);
        }
    }

    fn double_click_enabled(&self, button: mouse::Button, action: ClickAction) -> bool {
        self.widget
            .get_controls()
            .double_click_is_bound(button, action)
    }

    fn set_key(&mut self, key: keyboard::Key, action: KeyAction, enabled: bool) {
        if enabled {
            self.widget.get_controls_mut().bind_key(key, action);
        } else {
            self.widget.get_controls_mut().unbind_key(&key);
        }
    }

    fn key_enabled(&self, key: &keyboard::Key, action: KeyAction) -> bool {
        self.widget.get_controls().key_is_bound(key, action)
    }

    fn update(&mut self, message: Message) {
        match message {
            Message::Plot(msg) => self.widget.update(msg),
            Message::ToggleScrollToPan(enabled) => {
                self.set_scroll(keyboard::Modifiers::NONE, ScrollAction::Pan, enabled);
            }
            Message::ToggleDragToPan(enabled) => {
                self.set_drag(mouse::Button::Left, DragAction::Pan, enabled);
            }
            Message::ToggleArrowsToPan(enabled) => {
                if enabled {
                    self.widget.get_controls_mut().bind_arrow_pan(0.1);
                } else {
                    self.widget.get_controls_mut().unbind_arrow_pan();
                }
            }
            Message::ToggleBoxZoom(enabled) => {
                self.set_drag(mouse::Button::Right, DragAction::BoxZoom, enabled);
            }
            Message::ToggleCtrlScrollZoom(enabled) => {
                self.set_scroll(keyboard::Modifiers::CTRL, ScrollAction::Zoom, enabled);
            }
            Message::ToggleDoubleClickAutoscale(enabled) => {
                self.set_double_click(mouse::Button::Left, ClickAction::Autoscale, enabled);
            }
            Message::ToggleKeyAutoscale(enabled) => {
                self.set_key(
                    keyboard::Key::Character("f".into()),
                    KeyAction::Autoscale,
                    enabled,
                );
            }
            Message::ToggleClickToPick(enabled) => {
                self.set_click(mouse::Button::Left, ClickAction::Pick, enabled);
            }
            Message::ToggleClearPickOnEscape(enabled) => {
                self.set_key(
                    keyboard::Key::Named(keyboard::key::Named::Escape),
                    KeyAction::ClearPick,
                    enabled,
                );
            }
            Message::ToggleHighlightOnHover(enabled) => {
                self.widget.set_highlight_on_hover(enabled);
            }
            Message::ToggleShowControlsHelp(enabled) => {
                self.widget.set_controls_help(enabled);
            }
        }
    }

    fn view(&self) -> Element<'_, Message> {
        let controls_panel = container(
            column![
                text("Plot controls").size(18),
                row![
                    text("Pan:"),
                    checkbox(self.scroll_enabled(keyboard::Modifiers::NONE, ScrollAction::Pan))
                        .label("scroll")
                        .on_toggle(Message::ToggleScrollToPan),
                    checkbox(self.drag_enabled(mouse::Button::Left, DragAction::Pan))
                        .label("drag")
                        .on_toggle(Message::ToggleDragToPan),
                    checkbox(self.widget.get_controls().arrows_to_pan_enabled())
                        .label("arrows")
                        .on_toggle(Message::ToggleArrowsToPan),
                ]
                .spacing(16),
                row![
                    text("Zoom:"),
                    checkbox(self.drag_enabled(mouse::Button::Right, DragAction::BoxZoom))
                        .label("box zoom with right-drag")
                        .on_toggle(Message::ToggleBoxZoom),
                    checkbox(self.scroll_enabled(keyboard::Modifiers::CTRL, ScrollAction::Zoom))
                        .label("Ctrl+scroll")
                        .on_toggle(Message::ToggleCtrlScrollZoom),
                    checkbox(
                        self.double_click_enabled(mouse::Button::Left, ClickAction::Autoscale)
                    )
                    .label("double-click autoscale")
                    .on_toggle(Message::ToggleDoubleClickAutoscale),
                    checkbox(
                        self.key_enabled(
                            &keyboard::Key::Character("f".into()),
                            KeyAction::Autoscale,
                        )
                    )
                    .label("key \"f\" to autoscale")
                    .on_toggle(Message::ToggleKeyAutoscale),
                ]
                .spacing(16),
                row![
                    text("Pick:"),
                    checkbox(self.click_enabled(mouse::Button::Left, ClickAction::Pick))
                        .label("click to pick")
                        .on_toggle(Message::ToggleClickToPick),
                    checkbox(self.key_enabled(
                        &keyboard::Key::Named(keyboard::key::Named::Escape),
                        KeyAction::ClearPick,
                    ))
                    .label("Esc clears")
                    .on_toggle(Message::ToggleClearPickOnEscape),
                ]
                .spacing(16),
                row![
                    checkbox(self.widget.highlight_on_hover())
                        .label("Highlight on hover")
                        .on_toggle(Message::ToggleHighlightOnHover),
                    checkbox(self.widget.controls_help_enabled())
                        .label("Show controls help")
                        .on_toggle(Message::ToggleShowControlsHelp),
                ]
                .spacing(16),
            ]
            .spacing(10),
        )
        .padding(12)
        .width(Length::Fill);

        column![controls_panel, self.widget.view().map(Message::Plot)]
            .spacing(8)
            .into()
    }
}