1use aksel::ScreenRect;
82use derive_more::{Display, Error};
83use iced_core::{
84 Clipboard, Color, Element, Event, Font, Layout, Length, Padding, Pixels, Point, Rectangle,
85 Shell, Size, Widget, keyboard,
86 layout::{self, Limits, Node},
87 mouse,
88 renderer::{Quad, Style},
89 widget::{Tree, tree},
90};
91use std::fmt::Debug;
92use std::hash::Hash;
93use std::ops::Deref;
94
95pub use aksel::{Float, Transform, scale, scale::Scale, transform, transform::PlotPoint};
97
98mod action;
99mod event;
100mod layer;
101mod measure;
102mod memory;
103mod render;
104mod state;
105
106pub mod axis;
107pub mod interaction;
108pub mod plot;
109pub mod radii;
110pub mod shape;
111pub mod stroke;
112pub mod style;
113
114pub use axis::Axis;
115pub use event::*;
116pub use interaction::Interaction;
117pub use layer::{Cached, LayerId};
118pub use measure::Measure;
119pub use plot::{Plot, PlotData};
120pub use radii::{Radii, Radius};
121pub use render::{Quality, Renderer};
122pub use shape::Shape;
123pub use state::State;
124pub use stroke::Stroke;
125pub use style::Catalog;
126
127use crate::interaction::{Area, InteractionQuery};
128use crate::memory::{CacheSignature, HoverIdentity};
129use crate::render::{LineArrows, LineExtensions, Primitive};
130use crate::stroke::ResolvedStroke;
131use action::Action;
132use axis::{MarkerContext, MarkerPosition, MarkerRequest, Orientation, Position};
133use layer::Layer;
134use memory::Memory;
135
136const DEFAULT_DRAG_DEADBAND: f32 = 5.0;
138const INTERACTION_HIT_TOLERANCE: Pixels = Pixels(1.0);
139
140#[derive(Debug, Clone, Error, Display)]
142pub enum Error<AxisId> {
143 #[display("Duplicate axis id's received for a layer: {id:?}")]
145 DuplicateAxis {
146 id: AxisId,
148 },
149 #[display(
151 "Conflicting axis orientations: {horizontal:?}({horizontal_orientation:?}) | {vertical:?}(vertical_orientation:?)"
152 )]
153 AxisConflict {
154 horizontal: AxisId,
156 horizontal_orientation: Orientation,
158 vertical: AxisId,
160 vertical_orientation: Orientation,
162 },
163 #[display("Unknown axis id: '{id:?}'")]
165 UnknownAxis {
166 id: AxisId,
168 },
169}
170
171type ErrorHandler<AxisId, Message> = event::Handler<Message, (Error<AxisId>,)>;
173type MoveHandler<Message> = event::Handler<Message, (MoveEvent<Point>,)>;
174type EnterHandler<Message> = event::Handler<Message, (EnterEvent,)>;
175type ExitHandler<Message> = event::Handler<Message, (ExitEvent,)>;
176type DragHandler<Message> = event::Handler<Message, (DragEvent<Delta>,)>;
177type ScrollHandler<Message> = event::Handler<Message, (ScrollEvent<Point>,)>;
178type PressHandler<Message> = event::Handler<Message, (PressEvent<Point>,)>;
179type ReleaseHandler<Message> = event::Handler<Message, (ReleaseEvent<Point>,)>;
180
181type AxisMoveHandler<AxisId, Message> = event::Handler<Message, (AxisId, MoveEvent<f32>)>;
183type AxisEnterHandler<AxisId, Message> = event::Handler<Message, (AxisId, EnterEvent)>;
184type AxisExitHandler<AxisId, Message> = event::Handler<Message, (AxisId, ExitEvent)>;
185type AxisDragHandler<AxisId, Message> = event::Handler<Message, (AxisId, DragEvent<f32>)>;
186type AxisScrollHandler<AxisId, Message> = event::Handler<Message, (AxisId, ScrollEvent<f32>)>;
187type AxisPressHandler<AxisId, Message> = event::Handler<Message, (AxisId, PressEvent<f32>)>;
188type AxisReleaseHandler<AxisId, Message> = event::Handler<Message, (AxisId, ReleaseEvent<f32>)>;
189
190pub struct Chart<
218 'a,
219 AxisId,
220 Domain,
221 Message,
222 Tag = (),
223 Theme = iced_core::Theme,
224 Renderer = iced_renderer::Renderer,
225> where
226 AxisId: Hash + Eq + Clone + Debug,
227 Domain: Float,
228 Theme: Catalog,
229 Renderer: crate::Renderer,
230 Message: Clone,
231 Tag: Hash + Eq + Clone,
232{
233 state: &'a State<AxisId, Domain, Theme>,
234 layers: Vec<Layer<'a, AxisId, Domain, Message, Tag, Renderer, Theme>>,
235 width: Length,
236 height: Length,
237 class: <Theme as Catalog>::Class<'a>,
238 errors: Vec<Error<AxisId>>,
239 drag_deadband: f32,
240 padding: Padding,
241 quality: Quality,
242 markers: Vec<MarkerRequest<'a, AxisId, Domain, Theme>>,
243
244 axis_font: Option<Font>,
246
247 on_error: Option<ErrorHandler<AxisId, Message>>,
249
250 default_cursor: mouse::Interaction,
252
253 on_press: Option<PressHandler<Message>>,
255 on_release: Option<ReleaseHandler<Message>>,
256 on_drag: Option<DragHandler<Message>>,
257 on_enter: Option<EnterHandler<Message>>,
258 on_exit: Option<ExitHandler<Message>>,
259 on_move: Option<MoveHandler<Message>>,
260 on_scroll: Option<ScrollHandler<Message>>,
261
262 on_axis_press: Option<AxisPressHandler<AxisId, Message>>,
264 on_axis_release: Option<AxisReleaseHandler<AxisId, Message>>,
265 on_axis_drag: Option<AxisDragHandler<AxisId, Message>>,
266 on_axis_move: Option<AxisMoveHandler<AxisId, Message>>,
267 on_axis_enter: Option<AxisEnterHandler<AxisId, Message>>,
268 on_axis_exit: Option<AxisExitHandler<AxisId, Message>>,
269 on_axis_scroll: Option<AxisScrollHandler<AxisId, Message>>,
270
271 debug: bool,
272}
273
274impl<'a, AxisId, Domain, Message: std::clone::Clone, Tag, Theme, Renderer>
275 Chart<'a, AxisId, Domain, Message, Tag, Theme, Renderer>
276where
277 Domain: Float,
278 AxisId: Hash + Eq + Clone + Debug,
279 Tag: Hash + Eq + Clone + Debug,
280 Theme: Catalog,
281 Renderer: crate::Renderer,
282{
283 pub fn new(state: &'a State<AxisId, Domain, Theme>) -> Self {
289 Self {
290 state,
291 layers: vec![],
292 width: Length::Fill,
293 height: Length::Fill,
294 class: <Theme as Catalog>::default(),
295 errors: vec![],
296 drag_deadband: DEFAULT_DRAG_DEADBAND,
297 padding: Padding::new(0.),
298 quality: Quality::Medium,
299 markers: Vec::with_capacity(state.axes().len()),
300
301 axis_font: None,
303 on_error: None,
304
305 default_cursor: mouse::Interaction::Idle,
306
307 on_drag: None,
308 on_enter: None,
309 on_exit: None,
310 on_move: None,
311 on_scroll: None,
312 on_press: None,
313 on_release: None,
314
315 on_axis_press: None,
316 on_axis_release: None,
317 on_axis_drag: None,
318 on_axis_move: None,
319 on_axis_enter: None,
320 on_axis_exit: None,
321 on_axis_scroll: None,
322
323 debug: false,
324 }
325 }
326
327 pub fn style(mut self, style: <Theme as Catalog>::Class<'a>) -> Self {
329 self.class = style;
330 self
331 }
332
333 pub const fn debug(mut self, debug: bool) -> Self {
338 self.debug = debug;
339 self
340 }
341
342 pub const fn quality(mut self, quality: Quality) -> Self {
349 self.quality = quality;
350 self
351 }
352
353 pub const fn axes_font(mut self, font: Font) -> Self {
355 self.axis_font = Some(font);
356 self
357 }
358
359 pub fn plot_data<T: plot::PlotData<Domain, Message, Tag, Renderer, Theme>>(
366 mut self,
367 items: &'a T,
368 x_axis_id: AxisId,
369 y_axis_id: AxisId,
370 ) -> Self {
371 let layer = Layer::new(items, x_axis_id, y_axis_id);
372 if verify_layer(&layer, self.state, &mut self.errors) {
373 self.layers.push(layer);
374 }
375
376 self
377 }
378
379 pub const fn width(mut self, width: Length) -> Self {
381 self.width = width;
382 self
383 }
384
385 pub const fn height(mut self, height: Length) -> Self {
387 self.height = height;
388 self
389 }
390
391 pub const fn padding(mut self, padding: Padding) -> Self {
395 self.padding = padding;
396 self
397 }
398
399 pub const fn drag_deadband(mut self, distance: f32) -> Self {
403 self.drag_deadband = distance;
404 self
405 }
406
407 pub const fn default_cursor(mut self, cursor: mouse::Interaction) -> Self {
411 self.default_cursor = cursor;
412 self
413 }
414
415 pub fn marker_maybe<F>(
417 mut self,
418 axis_id: &'a AxisId,
419 position: Option<MarkerPosition<Domain>>,
420 renderer: F,
421 ) -> Self
422 where
423 F: for<'ctx> Fn(MarkerContext<'ctx, Domain, Theme>) -> Option<axis::Marker> + 'static,
424 {
425 if let Some(position) = position {
426 self.markers.push(MarkerRequest {
427 axis_id,
428 position,
429 renderer: Box::new(renderer),
430 });
431 }
432 self
433 }
434
435 pub fn marker<F>(
437 mut self,
438 axis_id: &'a AxisId,
439 position: MarkerPosition<Domain>,
440 renderer: F,
441 ) -> Self
442 where
443 F: for<'ctx> Fn(MarkerContext<'ctx, Domain, Theme>) -> Option<axis::Marker> + 'static,
444 {
445 self.markers.push(MarkerRequest {
446 axis_id,
447 position,
448 renderer: Box::new(renderer),
449 });
450 self
451 }
452
453 event::impl_handlers!(
454 error: (Error<AxisId>,);
459
460 press: (PressEvent<Point>,);
462
463 release: (ReleaseEvent<Point>,);
465
466 drag: (DragEvent<Delta>,);
468
469 move: (MoveEvent<Point>,);
471
472 enter: (EnterEvent,);
474
475 exit: (ExitEvent,);
477
478 scroll: (ScrollEvent<Point>,);
480
481
482 axis_press: (AxisId, PressEvent<f32>);
484
485 axis_release: (AxisId, ReleaseEvent<f32>);
487
488 axis_drag: (AxisId, DragEvent<f32>);
490
491 axis_move: (AxisId, MoveEvent<f32>);
493
494 axis_enter: (AxisId, EnterEvent);
496
497 axis_exit: (AxisId, ExitEvent);
499
500 axis_scroll: (AxisId, ScrollEvent<f32>);
502 );
503
504 fn handle_mouse_press(
507 &self,
508 memory: &mut Memory<AxisId, Message, Tag, Renderer>,
509 layout: Layout,
510 shell: &mut Shell<'_, Message>,
511 click: mouse::Click,
512 button: mouse::Button,
513 ) {
514 if Action::Idle != memory.action {
516 return;
517 }
518
519 let plot_bounds = self.get_plot_layout(layout).bounds();
520 let mouse_pos = click.position();
521
522 if plot_bounds.contains(mouse_pos) {
524 shell.capture_event();
525
526 let interactions = memory.interaction_cache.borrow();
527
528 let query = InteractionQuery::Point {
530 position: mouse_pos,
531 tolerance: INTERACTION_HIT_TOLERANCE,
532 };
533
534 let target_id = interactions
536 .query_prioritized(&query, |interaction| interaction.on_press.is_some())
537 .map(|(id, interaction)| {
539 let handler = interaction.on_press.as_ref().unwrap();
542
543 let normalized = Point::new(
544 (mouse_pos.x - plot_bounds.x) / plot_bounds.width,
545 1.0 - ((mouse_pos.y - plot_bounds.y) / plot_bounds.height),
546 );
547
548 let event = PressEvent::new(
549 normalized,
550 button,
551 click.kind(),
552 memory.keyboard_modifiers,
553 );
554
555 if let Some(message) = handler.run((id.clone(), event)) {
556 shell.publish(message);
557 }
558
559 id
560 });
561
562 let handled = target_id.is_some();
563
564 memory.action = Action::DraggingPlot {
565 interaction_id: target_id,
566 origin: mouse_pos,
567 last_position: mouse_pos,
568 total_delta: 0.0,
569 button,
570 click_kind: click.kind(),
571 };
572
573 if handled {
575 return;
576 }
577
578 if let Some(handler) = &self.on_press {
579 let normalized = Point::new(
580 (mouse_pos.x - plot_bounds.x) / plot_bounds.width,
581 1.0 - ((mouse_pos.y - plot_bounds.y) / plot_bounds.height),
582 );
583 let event =
584 PressEvent::new(normalized, button, click.kind(), memory.keyboard_modifiers);
585 if let Some(message) = handler.run((event,)) {
586 shell.publish(message);
587 }
588 }
589
590 return;
591 }
592
593 for (i, (id, axis)) in self.state.axes().iter().enumerate() {
595 let axis_bounds = layout.children().nth(i).unwrap().bounds();
596
597 if !axis_bounds.contains(mouse_pos) {
598 continue;
599 }
600
601 let origin = match axis.orientation() {
602 Orientation::Horizontal => mouse_pos.x,
603 Orientation::Vertical => mouse_pos.y,
604 };
605
606 shell.capture_event();
607
608 memory.action = Action::DraggingAxis {
609 id: id.clone(),
610 origin,
611 last_position: origin,
612 total_delta: 0.0,
613 button,
614 click_kind: click.kind(),
615 };
616
617 if let Some(handler) = self.on_axis_press.as_ref()
619 && let Some(message) = handler.run((
620 id.clone(),
621 PressEvent::new(
622 axis.screen_to_normalized(origin, &axis_bounds),
623 button,
624 click.kind(),
625 memory.keyboard_modifiers,
626 ),
627 ))
628 {
629 shell.publish(message);
630 }
631
632 return;
634 }
635 }
636
637 fn handle_mouse_release(
640 &self,
641 memory: &mut Memory<AxisId, Message, Tag, Renderer>,
642 layout: Layout,
643 shell: &mut Shell<'_, Message>,
644 previous_click_kind: Option<mouse::click::Kind>,
645 button: mouse::Button,
646 ) {
647 let Memory { action, .. } = memory;
648
649 let was_dragging = action
651 .total_drag_delta()
652 .is_some_and(|delta| delta > self.drag_deadband);
653
654 match action {
655 Action::Idle => (), Action::DraggingPlot {
657 origin,
658 interaction_id,
659 ..
660 } => {
661 let plot_bounds = self.get_plot_layout(layout).bounds();
662 let interactions = memory.interaction_cache.borrow();
663
664 let normalized = Point::new(
665 (origin.x - plot_bounds.x) / plot_bounds.width,
666 1.0 - ((origin.y - plot_bounds.y) / plot_bounds.height),
667 );
668 let event = ReleaseEvent::new(
669 normalized,
670 button,
671 previous_click_kind,
672 memory.keyboard_modifiers,
673 was_dragging,
674 );
675
676 if let Some(existing_id) = interaction_id
678 && let Some(interaction) = interactions.get(existing_id)
679 && let Some(handler) = interaction.on_release.as_ref()
680 {
681 if let Some(message) = handler.run((existing_id.clone(), event)) {
684 shell.publish(message)
685 }
686 return;
687 }
688
689 let query = InteractionQuery::Point {
691 position: *origin,
692 tolerance: INTERACTION_HIT_TOLERANCE,
693 };
694 let target_id = interactions
695 .query_prioritized(&query, |interaction| interaction.on_release.is_some())
696 .map(|(id, interaction)| {
697 let handler = interaction.on_release.as_ref().unwrap();
700
701 if let Some(message) = handler.run((id.clone(), event)) {
702 shell.publish(message);
703 }
704
705 id
706 });
707
708 if target_id.is_some() {
709 return;
711 }
712
713 if let Some(handler) = &self.on_release
714 && let Some(message) = handler.run((event,))
715 {
716 shell.publish(message);
717 }
718 }
719 Action::DraggingAxis { id, origin, .. } => {
720 if let Some((i, id, axis)) = self.state.axes().get_full(id) {
721 let axis_bounds = layout.children().nth(i).unwrap().bounds();
722 let normalized = axis.screen_to_normalized(*origin, &axis_bounds);
723 if let Some(handler) = &self.on_axis_release
724 && let Some(message) = handler.run((
725 id.clone(),
726 ReleaseEvent::new(
727 normalized,
728 button,
729 previous_click_kind,
730 memory.keyboard_modifiers,
731 was_dragging,
732 ),
733 ))
734 {
735 shell.publish(message);
736 }
737 }
738 }
739 }
740 }
741
742 fn handle_mouse_moved(
747 &self,
748 memory: &mut Memory<AxisId, Message, Tag, Renderer>,
749 layout: Layout,
750 shell: &mut Shell<'_, Message>,
751 mouse_pos: Point,
752 ) -> Option<HoverIdentity<AxisId, Tag>> {
753 let Memory { action, .. } = memory;
754 let plot_bounds = self.get_plot_layout(layout).bounds();
755
756 if plot_bounds.contains(mouse_pos) {
758 match action {
759 Action::DraggingAxis { .. } => (), Action::Idle => {
761 if let Some(handler) = &self.on_move
762 && let Some(message) = handler.run((MoveEvent::new(
763 Point::new(
764 (mouse_pos.x - plot_bounds.x) / plot_bounds.width,
765 1.0 - ((mouse_pos.y - plot_bounds.y) / plot_bounds.height),
766 ),
767 memory.keyboard_modifiers,
768 ),))
769 {
770 shell.publish(message);
771 }
772
773 let interactions = memory.interaction_cache.borrow();
775 let query = InteractionQuery::Point {
776 position: mouse_pos,
777 tolerance: INTERACTION_HIT_TOLERANCE,
778 };
779
780 let prioritized_id = interactions
782 .query_prioritized(&query, |interaction| interaction.on_enter.is_some())
783 .map(|(id, _)| HoverIdentity::Interaction(id));
784
785 let identity = prioritized_id.unwrap_or(HoverIdentity::Plot);
786
787 if memory.last_hovered_identity != identity {
788 let last = std::mem::replace(&mut memory.last_hovered_identity, identity);
789 return Some(last);
790 }
791
792 return None;
793 }
794 Action::DraggingPlot {
795 last_position,
796 total_delta,
797 interaction_id,
798 button,
799 click_kind,
800 ..
801 } => {
802 let delta_x = mouse_pos.x - last_position.x;
803 let delta_y = mouse_pos.y - last_position.y;
804 *total_delta += delta_x.hypot(delta_y);
805 *last_position = mouse_pos;
806
807 if *total_delta < self.drag_deadband {
808 return None;
809 };
810
811 if let Some(id) = interaction_id {
813 shell.capture_event();
814
815 let interactions = memory.interaction_cache.borrow();
816 let handler = interactions.get(id)?.on_drag.as_ref()?;
817
818 let normalized_delta = Delta {
821 x: delta_x / plot_bounds.width,
822 y: -delta_y / plot_bounds.height,
823 };
824
825 let event = DragEvent::new(
826 normalized_delta,
827 *button,
828 *click_kind,
829 memory.keyboard_modifiers,
830 );
831
832 if let Some(message) = handler.run((id.clone(), event)) {
833 shell.publish(message);
834 return None;
836 };
837 }
838
839 if let Some(handler) = &self.on_drag {
840 shell.capture_event();
841
842 let normalized_delta = Delta {
845 x: -delta_x / plot_bounds.width,
846 y: delta_y / plot_bounds.height,
847 };
848
849 let event = DragEvent::new(
850 normalized_delta,
851 *button,
852 *click_kind,
853 memory.keyboard_modifiers,
854 );
855
856 if let Some(message) = handler.run((event,)) {
857 shell.publish(message);
858 }
859 }
860
861 return None;
862 }
863 }
864 }
865
866 if let Action::DraggingAxis {
868 id: dragging_id,
869 last_position,
870 total_delta,
871 button,
872 click_kind,
873 ..
874 } = action
875 {
876 shell.capture_event();
877 if let Some((i, id, axis)) = self.state.axes().get_full(dragging_id) {
878 let axis_bounds = layout.children().nth(i).unwrap().bounds();
879 let screen_value = match axis.orientation() {
880 axis::Orientation::Horizontal => mouse_pos.x,
881 axis::Orientation::Vertical => mouse_pos.y,
882 };
883
884 let delta = screen_value - *last_position;
885 *total_delta += delta.abs();
886 *last_position = screen_value;
887
888 if *total_delta > self.drag_deadband
889 && let Some(handler) = &self.on_axis_drag
890 {
891 let normalized_delta = axis.translate_drag_delta(delta, &axis_bounds);
892 let event = DragEvent::new(
893 normalized_delta,
894 *button,
895 *click_kind,
896 memory.keyboard_modifiers,
897 );
898 if let Some(message) = handler.run((id.clone(), event)) {
899 shell.publish(message);
900 }
901 }
902 }
903 }
904 else if matches!(action, Action::Idle) {
906 for (i, (id, axis)) in self.state.axes().iter().enumerate() {
907 let axis_bounds = layout.children().nth(i).unwrap().bounds();
908
909 if !axis_bounds.contains(mouse_pos) {
910 memory.last_hovered_identity = HoverIdentity::Axis(id.clone());
911 continue;
912 }
913
914 if let Some(handler) = &self.on_axis_move {
915 let screen_value = match axis.orientation() {
916 axis::Orientation::Horizontal => mouse_pos.x,
917 axis::Orientation::Vertical => mouse_pos.y,
918 };
919 let normalized = axis.screen_to_normalized(screen_value, &axis_bounds);
920 if let Some(message) = handler.run((
921 id.clone(),
922 MoveEvent::new(normalized, memory.keyboard_modifiers),
923 )) {
924 shell.publish(message);
925 }
926 }
927
928 break;
929 }
930 }
931
932 None
933 }
934
935 #[inline(always)]
936 fn get_plot_layout<'b>(&self, layout: Layout<'b>) -> Layout<'b> {
937 layout.children().last().unwrap()
939 }
940
941 fn draw_spine_corners(
943 &self,
944 layout: Layout<'_>,
945 style: &style::Style,
946 plot: Rectangle,
947 renderer: &mut Renderer,
948 ) {
949 let mut left: Option<(f32, Color)> = None;
951 let mut right: Option<(f32, Color)> = None;
952 let mut top: Option<(f32, Color)> = None;
953 let mut bottom: Option<(f32, Color)> = None;
954
955 let mut max_left_edge = f32::MIN;
957 let mut min_right_edge = f32::MAX;
958 let mut max_top_edge = f32::MIN;
959 let mut min_bottom_edge = f32::MAX;
960
961 for (i, (_, axis)) in self.state.axes().iter().enumerate() {
963 if !axis.is_visible() {
964 continue;
965 }
966
967 if style.axis.spine.width.0 <= 0.0 {
968 continue;
969 }
970
971 let style = axis.create_style(style).spine;
972 let bounds = layout.children().nth(i).unwrap().bounds();
973 let data = (style.width.0, style.color);
974
975 match axis.position() {
976 Position::Left => {
977 let edge = bounds.x + bounds.width;
978 if edge >= max_left_edge {
979 max_left_edge = edge;
980 left = Some(data);
981 }
982 }
983 Position::Right => {
984 let edge = bounds.x;
985 if edge <= min_right_edge {
986 min_right_edge = edge;
987 right = Some(data);
988 }
989 }
990 Position::Top => {
991 let edge = bounds.y + bounds.height;
992 if edge >= max_top_edge {
993 max_top_edge = edge;
994 top = Some(data);
995 }
996 }
997 Position::Bottom => {
998 let edge = bounds.y;
999 if edge <= min_bottom_edge {
1000 min_bottom_edge = edge;
1001 bottom = Some(data);
1002 }
1003 }
1004 }
1005 }
1006
1007 if let (Some((lw, lc)), Some((bw, _))) = (left, bottom) {
1011 let top_left = Point::new(plot.x - lw, plot.y + plot.height);
1012 let size = Size::new(lw, bw);
1013 renderer.fill_quad(
1014 Quad {
1015 bounds: Rectangle::new(top_left, size),
1016 snap: true,
1017 ..Default::default()
1018 },
1019 lc,
1020 );
1021 }
1022
1023 if let (Some((lw, lc)), Some((tw, _))) = (left, top) {
1025 let top_left = Point::new(plot.x - lw, plot.y - tw);
1026 let size = Size::new(lw, tw);
1027 renderer.fill_quad(
1028 Quad {
1029 bounds: Rectangle::new(top_left, size),
1030 snap: true,
1031 ..Default::default()
1032 },
1033 lc,
1034 );
1035 }
1036
1037 if let (Some((rw, rc)), Some((bw, _))) = (right, bottom) {
1039 let top_left = Point::new(plot.x + plot.width, plot.y + plot.height);
1040 let size = Size::new(rw, bw);
1041 renderer.fill_quad(
1042 Quad {
1043 bounds: Rectangle::new(top_left, size),
1044 snap: true,
1045 ..Default::default()
1046 },
1047 rc,
1048 );
1049 }
1050
1051 if let (Some((rw, rc)), Some((tw, _))) = (right, top) {
1053 let top_left = Point::new(plot.x + plot.width, plot.y - tw);
1054 let size = Size::new(rw, tw);
1055 renderer.fill_quad(
1056 Quad {
1057 bounds: Rectangle::new(top_left, size),
1058 snap: true,
1059 ..Default::default()
1060 },
1061 rc,
1062 );
1063 }
1064 }
1065}
1066
1067impl<AxisId, Domain, Message, Theme, Tag, Renderer> Widget<Message, Theme, Renderer>
1068 for Chart<'_, AxisId, Domain, Message, Tag, Theme, Renderer>
1069where
1070 AxisId: Hash + Eq + Debug + Clone + 'static,
1071 Domain: Float + 'static,
1072 Tag: Hash + Eq + Clone + Debug + 'static,
1073 Renderer: crate::Renderer + iced_core::text::Renderer<Font = iced_core::Font> + 'static,
1074 Theme: Catalog,
1075 Message: Clone + 'static,
1076{
1077 fn tag(&self) -> tree::Tag {
1078 tree::Tag::of::<Memory<AxisId, Message, Tag, Renderer>>()
1079 }
1080
1081 fn state(&self) -> tree::State {
1082 tree::State::new(Memory::<AxisId, Message, Tag, Renderer>::new())
1083 }
1084
1085 fn children(&self) -> Vec<Tree> {
1086 let mut children: Vec<Tree> = self.state.axes().iter().map(|_| Tree::empty()).collect();
1088 children.push(Tree::empty()); children
1090 }
1091
1092 fn diff(&self, _tree: &mut Tree) {}
1093
1094 fn size(&self) -> Size<Length> {
1095 Size::new(self.width, self.height)
1096 }
1097
1098 fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
1099 let memory: &mut Memory<AxisId, Message, Tag, Renderer> = tree.state.downcast_mut();
1100 memory.make_sure_cache_is_initialized(renderer, self.quality);
1101
1102 let bounds = limits.resolve(self.width, self.height, Size::ZERO);
1103
1104 let axis_count = self.state.axes().len();
1105
1106 let mut top_total = self.padding.top;
1108 let mut bottom_total = self.padding.bottom;
1109 let mut left_total = self.padding.left;
1110 let mut right_total = self.padding.right;
1111
1112 for (_, axis) in self.state.axes() {
1113 let thickness = axis.thickness().0;
1114 match axis.position() {
1115 Position::Top => top_total += thickness,
1116 Position::Bottom => bottom_total += thickness,
1117 Position::Left => left_total += thickness,
1118 Position::Right => right_total += thickness,
1119 }
1120 }
1121
1122 let chart_height = (bounds.height - top_total - bottom_total).max(0.0);
1124 let chart_width = (bounds.width - left_total - right_total).max(0.0);
1125
1126 let chart_origin = Point::new(left_total, top_total);
1127 let chart_size = Size::new(chart_width, chart_height);
1128
1129 let mut children_nodes = Vec::with_capacity(axis_count + 1);
1131
1132 let mut top_y = self.padding.top;
1133 let mut bot_y = top_total + chart_height;
1134 let mut left_x = self.padding.left;
1135 let mut right_x = left_total + chart_width;
1136
1137 for (_, axis) in self.state.axes() {
1138 let thickness = axis.thickness().0;
1139 let node = match axis.position() {
1140 Position::Top => {
1141 let n = layout_horizontal_axis(chart_width, axis, left_total, top_y, thickness);
1142 top_y += thickness;
1143 n
1144 }
1145 Position::Bottom => {
1146 let n = layout_horizontal_axis(chart_width, axis, left_total, bot_y, thickness);
1147 bot_y += thickness;
1148 n
1149 }
1150 Position::Left => {
1151 let n = layout_vertical_axis(chart_height, axis, left_x, top_total, thickness);
1152 left_x += thickness;
1153 n
1154 }
1155 Position::Right => {
1156 let n = layout_vertical_axis(chart_height, axis, right_x, top_total, thickness);
1157 right_x += thickness;
1158 n
1159 }
1160 };
1161 children_nodes.push(node);
1162 }
1163
1164 let chart_node = Node::new(chart_size).move_to(chart_origin);
1166 children_nodes.push(chart_node);
1167
1168 Node::with_children(bounds, children_nodes)
1169 }
1170
1171 fn update(
1172 &mut self,
1173 tree: &mut Tree,
1174 event: &Event,
1175 layout: layout::Layout<'_>,
1176 cursor: mouse::Cursor,
1177 _renderer: &Renderer,
1178 _clipboard: &mut dyn Clipboard,
1179 shell: &mut Shell<'_, Message>,
1180 _viewport: &Rectangle,
1181 ) {
1182 if !self.errors.is_empty()
1183 && let Some(handler) = &self.on_error
1184 {
1185 for error in self.errors.drain(..) {
1186 if let Some(message) = handler.run((error,)) {
1187 shell.publish(message);
1188 }
1189 }
1190 return;
1191 }
1192
1193 let signature = CacheSignature::new(self.state, &layout, &self.layers);
1194 let memory: &mut Memory<AxisId, Message, Tag, Renderer> = tree.state.downcast_mut();
1195 memory.update(signature);
1196 memory.update_partitions(self.get_plot_layout(layout).bounds());
1197
1198 let bounds = layout.bounds();
1200 let Some(mouse_pos) = cursor.position_over(bounds) else {
1201 return;
1202 };
1203
1204 match event {
1206 Event::Mouse(mouse::Event::ButtonPressed(button)) => {
1207 let new_click = memory.update_click(mouse_pos, *button);
1208 self.handle_mouse_press(memory, layout, shell, new_click, *button);
1209 }
1210 Event::Mouse(mouse::Event::ButtonReleased(button)) => {
1211 let previous_click_kind = memory.previous_click.take().map(|c| c.kind());
1212 self.handle_mouse_release(memory, layout, shell, previous_click_kind, *button);
1213 memory.action = Action::Idle;
1214 }
1215 Event::Mouse(mouse::Event::CursorMoved { position }) => {
1216 let changed = self.handle_mouse_moved(memory, layout, shell, *position);
1217
1218 let Some(last) = changed else {
1219 shell.request_redraw();
1220 return;
1221 };
1222
1223 let exit_message = match last {
1225 HoverIdentity::Plot => self.on_exit.as_ref().and_then(|handler| {
1226 handler.run((ExitEvent::new(memory.keyboard_modifiers),))
1227 }),
1228 HoverIdentity::Axis(id) => self.on_axis_exit.as_ref().and_then(|handler| {
1229 handler.run((id.clone(), ExitEvent::new(memory.keyboard_modifiers)))
1230 }),
1231 HoverIdentity::Interaction(id) => memory
1232 .interaction_cache
1233 .borrow()
1234 .get(&id)
1235 .and_then(|interaction| {
1236 interaction.on_exit.as_ref().map(|handler| {
1237 handler.run((id.clone(), ExitEvent::new(memory.keyboard_modifiers)))
1238 })
1239 })
1240 .flatten(),
1241 HoverIdentity::OutsideBounds => None, };
1243
1244 let enter_message = match &memory.last_hovered_identity {
1246 HoverIdentity::Plot => self.on_enter.as_ref().and_then(|handler| {
1247 handler.run((EnterEvent::new(memory.keyboard_modifiers),))
1248 }),
1249 HoverIdentity::Axis(id) => self.on_axis_enter.as_ref().and_then(|handler| {
1250 handler.run((id.clone(), EnterEvent::new(memory.keyboard_modifiers)))
1251 }),
1252 HoverIdentity::Interaction(id) => memory
1253 .interaction_cache
1254 .borrow()
1255 .get(id)
1256 .and_then(|interaction| {
1257 interaction.on_enter.as_ref().map(|handler| {
1258 handler
1259 .run((id.clone(), EnterEvent::new(memory.keyboard_modifiers)))
1260 })
1261 })
1262 .flatten(),
1263 HoverIdentity::OutsideBounds => None, };
1265
1266 if let Some(message) = exit_message {
1267 shell.publish(message);
1268 }
1269
1270 if let Some(message) = enter_message {
1271 shell.publish(message);
1272 }
1273 }
1274 Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
1275 if let Some(cursor_pos) = cursor.position() {
1276 let plot_bounds = self.get_plot_layout(layout).bounds();
1277
1278 if cursor.position_over(plot_bounds).is_some() {
1280 if let Some(handler) = &self.on_scroll {
1281 shell.capture_event();
1282
1283 let normalized = Point::new(
1285 (cursor_pos.x - plot_bounds.x) / plot_bounds.width,
1286 1.0 - ((cursor_pos.y - plot_bounds.y) / plot_bounds.height),
1287 );
1288
1289 let event =
1290 ScrollEvent::new(normalized, *delta, memory.keyboard_modifiers);
1291
1292 if let Some(message) = handler.run((event,)) {
1293 shell.publish(message);
1294 }
1295 }
1296 } else {
1297 for (i, (id, axis)) in self.state.axes().iter().enumerate() {
1299 let axis_bounds = layout.children().nth(i).unwrap().bounds();
1300
1301 if cursor.position_over(axis_bounds).is_some() {
1302 if let Some(handler) = &self.on_axis_scroll {
1303 let screen_value = match axis.orientation() {
1304 Orientation::Horizontal => cursor_pos.x,
1305 Orientation::Vertical => cursor_pos.y,
1306 };
1307
1308 let normalized =
1309 axis.screen_to_normalized(screen_value, &axis_bounds);
1310
1311 shell.capture_event();
1312
1313 let event = ScrollEvent::new(
1314 normalized,
1315 *delta,
1316 memory.keyboard_modifiers,
1317 );
1318
1319 if let Some(message) = handler.run((id.clone(), event)) {
1320 shell.publish(message);
1321 }
1322 }
1323 break;
1324 }
1325 }
1326 }
1327 }
1328 }
1329 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
1330 memory.update_modifiers(*modifiers)
1331 }
1332 _ => {}
1334 }
1335
1336 shell.request_redraw();
1337 }
1338
1339 fn draw(
1340 &self,
1341 tree: &Tree,
1342 renderer: &mut Renderer,
1343 theme: &Theme,
1344 _style: &Style,
1345 layout: Layout<'_>,
1346 cursor: mouse::Cursor,
1347 _viewport: &Rectangle,
1348 ) {
1349 let style = theme.style(&self.class);
1350 let bounds = layout.bounds();
1351 let plot_bounds = self.get_plot_layout(layout).bounds();
1352 let screen_rect = ScreenRect {
1353 x: plot_bounds.x,
1354 y: plot_bounds.y,
1355 width: plot_bounds.width,
1356 height: plot_bounds.height,
1357 };
1358
1359 let memory = tree
1361 .state
1362 .downcast_ref::<Memory<AxisId, Message, Tag, Renderer>>();
1363
1364 let Some(mut cache) = memory.get_cache_mut() else {
1366 return;
1367 };
1368
1369 for (i, (_, axis)) in self.state.axes().iter().enumerate() {
1371 let axis_layout = layout.children().nth(i).unwrap();
1373
1374 axis.draw::<Renderer>(
1376 renderer,
1377 theme,
1378 &style,
1379 axis_layout,
1380 &plot_bounds,
1381 &mut cache,
1382 &bounds,
1383 );
1384 }
1385
1386 self.draw_spine_corners(layout, &style, plot_bounds, renderer);
1388
1389 if cache.needs_redraw() {
1391 let mut interactions = memory.interaction_cache.borrow_mut();
1392 interactions.clear();
1393
1394 for layer in &self.layers {
1395 let x_axis = self.state.axis(&layer.horizontal_axis_id);
1397 let y_axis = self.state.axis(&layer.vertical_axis_id);
1398 let transform = Transform::new(&screen_rect, x_axis.deref(), y_axis.deref());
1399
1400 let mut plot: Plot<Domain, Message, Tag, Renderer> =
1401 Plot::new(renderer, &mut cache, &transform, &mut interactions);
1402
1403 layer.items.draw(&mut plot, theme);
1405 }
1406
1407 if self.debug
1409 && let Some(debug_cache_cell) = &memory.debug_cache
1410 {
1411 let mut debug_cache = debug_cache_cell.borrow_mut();
1412
1413 let mut new_debug_cache = match renderer.preferred_backend() {
1415 crate::render::Backend::Mesh => crate::render::RenderCache::new_mesh(),
1416 crate::render::Backend::Path => crate::render::RenderCache::new_path(),
1417 };
1418 new_debug_cache.set_quality(self.quality);
1419
1420 for (_, interaction) in interactions.iter() {
1421 if let Some(primitive) = build_debug_primitive(&interaction.area) {
1422 new_debug_cache.add_primitive(primitive);
1423 }
1424 }
1425
1426 *debug_cache = new_debug_cache;
1428 }
1429 }
1430
1431 for marker_request in &self.markers {
1433 let Some((idx, _id, axis)) = self.state.axes().get_full(marker_request.axis_id) else {
1434 continue;
1435 };
1436
1437 let axis_bounds = layout.child(idx).bounds();
1438
1439 let Some((marker, normalized_position)) = marker_request.create_marker(
1440 axis,
1441 &axis_bounds,
1442 &plot_bounds,
1443 cursor,
1444 &style.axis,
1445 theme,
1446 ) else {
1447 continue;
1448 };
1449
1450 axis.draw_marker_overlay(
1451 renderer,
1452 normalized_position,
1453 marker,
1454 axis_bounds,
1455 &bounds,
1456 style.axis.text_offset,
1457 );
1458 }
1459
1460 cache.draw(renderer, &plot_bounds);
1465
1466 if self.debug
1468 && let Some(mut debug_cache) = memory.get_debug_cache_mut()
1469 {
1470 debug_cache.draw(renderer, &plot_bounds);
1471 }
1472
1473 if self.debug {
1475 for (_, interaction) in memory.interaction_cache.borrow().iter() {
1476 if let Some(clipped_bounds) = interaction.bounding_box.intersection(&plot_bounds) {
1478 renderer.fill_quad(
1479 Quad {
1480 bounds: clipped_bounds,
1481 border: iced_core::Border::default()
1482 .color(Color::from_rgba(1.0, 0.0, 0.0, 0.8))
1483 .width(1.0),
1484 ..Default::default()
1485 },
1486 Color::from_rgba(1.0, 0.0, 0.0, 0.1),
1487 );
1488 }
1489 }
1490 }
1491 }
1492 fn mouse_interaction(
1493 &self,
1494 tree: &Tree,
1495 layout: Layout<'_>,
1496 cursor: mouse::Cursor,
1497 _viewport: &Rectangle,
1498 _renderer: &Renderer,
1499 ) -> mouse::Interaction {
1500 let memory: &Memory<AxisId, Message, Tag, Renderer> = tree.state.downcast_ref();
1501 let interactions = memory.interaction_cache.borrow();
1502
1503 let (target_id, click_kind, button_held, drag_delta) = match &memory.action {
1505 Action::DraggingPlot {
1506 interaction_id,
1507 click_kind,
1508 button,
1509 total_delta,
1510 ..
1511 } => (
1512 interaction_id.clone(),
1513 Some(*click_kind),
1514 Some(*button),
1515 Some(*total_delta),
1516 ),
1517 Action::Idle => {
1518 let id = if let HoverIdentity::Interaction(id) = &memory.last_hovered_identity {
1519 Some(id.clone())
1520 } else {
1521 None
1522 };
1523 (id, None, None, None)
1524 }
1525 _ => (None, None, None, None),
1526 };
1527
1528 if let Some(id) = target_id
1530 && let Some(interaction) = interactions.get(&id)
1531 && let Some(handler) = interaction.cursor_handler.as_ref()
1532 {
1533 let is_dragging = drag_delta.is_some_and(|delta| delta >= self.drag_deadband);
1535 let is_pressed = drag_delta.is_some_and(|_| !is_dragging);
1536 let is_hovered = !is_pressed
1537 && !is_dragging
1538 && matches!(memory.last_hovered_identity, HoverIdentity::Interaction(ref i) if i == &id);
1539
1540 let status = interaction::InteractionStatus {
1541 is_hovered,
1542 is_pressed,
1543 is_dragging,
1544 button_held,
1545 click_kind,
1546 modifiers: memory.keyboard_modifiers,
1547 };
1548
1549 if let Some(preferred_cursor) = handler.run((status,)) {
1551 return preferred_cursor;
1552 }
1553 }
1554
1555 if cursor.position_over(layout.bounds()).is_some() {
1559 self.default_cursor
1560 } else {
1561 mouse::Interaction::Idle
1562 }
1563 }
1564}
1565
1566impl<'a, AxisId, Domain, Message, Tag, Theme, Renderer>
1569 From<Chart<'a, AxisId, Domain, Message, Tag, Theme, Renderer>>
1570 for Element<'a, Message, Theme, Renderer>
1571where
1572 AxisId: Hash + Eq + Debug + Clone + 'static,
1573 Domain: Float + 'static,
1574 Message: Clone + 'a + 'static,
1575 Theme: Catalog + 'a,
1576 Renderer: crate::Renderer + iced_core::text::Renderer<Font = iced_core::Font> + 'static,
1577 Tag: Hash + Eq + Clone + Debug + 'static,
1578{
1579 fn from(plot: Chart<'a, AxisId, Domain, Message, Tag, Theme, Renderer>) -> Self {
1580 Element::new(plot)
1581 }
1582}
1583
1584#[inline(always)]
1585fn layout_horizontal_axis<Domain: Float, Theme>(
1586 chart_width: f32,
1587 axis: &Axis<Domain, Theme>,
1588 x: f32,
1589 y: f32,
1590 height: f32,
1591) -> Node {
1592 let limits = Limits::new(
1593 Size::new(chart_width, height),
1594 Size::new(chart_width, height),
1595 );
1596 axis.layout(&limits).move_to(Point::new(x, y))
1597}
1598
1599#[inline(always)]
1600fn layout_vertical_axis<Domain: Float, Theme>(
1601 chart_height: f32,
1602 axis: &Axis<Domain, Theme>,
1603 x: f32,
1604 y: f32,
1605 width: f32,
1606) -> Node {
1607 let limits = Limits::new(
1608 Size::new(width, chart_height),
1609 Size::new(width, chart_height),
1610 );
1611 axis.layout(&limits).move_to(Point::new(x, y))
1612}
1613
1614#[inline(always)]
1615fn verify_layer<
1616 'a,
1617 AxisId: Hash + Eq + Clone,
1618 Domain: Float,
1619 Message,
1620 Tag: Hash + Eq + Clone,
1621 Renderer,
1622 Theme,
1623>(
1624 layer: &Layer<'a, AxisId, Domain, Message, Tag, Renderer, Theme>,
1625 state: &'a State<AxisId, Domain, Theme>,
1626 errors: &mut Vec<Error<AxisId>>,
1627) -> bool {
1628 let x_id = &layer.horizontal_axis_id;
1629 let y_id = &layer.vertical_axis_id;
1630
1631 if x_id == y_id {
1632 errors.push(Error::DuplicateAxis { id: x_id.clone() });
1633 return false;
1634 }
1635
1636 let Some(x) = state.axis_opt(x_id) else {
1637 errors.push(Error::UnknownAxis { id: x_id.clone() });
1638 return false;
1639 };
1640 let Some(y) = state.axis_opt(y_id) else {
1641 errors.push(Error::UnknownAxis { id: y_id.clone() });
1642 return false;
1643 };
1644
1645 let horizontal_orientation = x.orientation();
1646 let vertical_orientation = y.orientation();
1647 if horizontal_orientation == vertical_orientation {
1648 errors.push(Error::AxisConflict {
1649 horizontal: x_id.clone(),
1650 horizontal_orientation,
1651 vertical: y_id.clone(),
1652 vertical_orientation,
1653 });
1654 return false;
1655 }
1656
1657 true
1658}
1659
1660pub(crate) fn build_debug_primitive(area: &Area) -> Option<Primitive> {
1662 let debug_stroke = ResolvedStroke {
1664 thickness: 1.0,
1665 fill: Color::from_rgba(1.0, 0.0, 0.0, 0.8),
1666 style: crate::stroke::StrokeStyle::Solid,
1667 };
1668
1669 match area {
1670 Area::Rectangle { top_left, size } => Some(Primitive::Rectangle {
1671 xy1: Point::new(top_left.x, top_left.y),
1672 xy2: Point::new(top_left.x + size.width, top_left.y + size.height),
1673 fill: None,
1674 stroke: Some(debug_stroke),
1675 }),
1676 Area::LineSegment {
1677 p1,
1678 p2,
1679 stroke_width,
1680 } => Some(Primitive::Line {
1681 start: *p1,
1682 end: *p2,
1683 stroke: ResolvedStroke {
1684 thickness: stroke_width.0,
1685 ..debug_stroke
1686 },
1687 clip_bounds: Rectangle::INFINITE, extensions: LineExtensions {
1689 start: false,
1690 end: false,
1691 },
1692 arrows: LineArrows {
1693 start: false,
1694 end: false,
1695 size: 0.0,
1696 },
1697 }),
1698 Area::Ellipse { center, radii } => Some(Primitive::Ellipse {
1699 center: *center,
1700 radii: *radii,
1701 fill: None,
1702 stroke: Some(debug_stroke),
1703 }),
1704 Area::Triangle { p1, p2, p3 } => Some(Primitive::Triangle {
1705 points: [*p1, *p2, *p3],
1706 fill: None,
1707 stroke: Some(debug_stroke),
1708 }),
1709 Area::Polygon { points } => Some(Primitive::Area {
1710 points: points.clone(),
1711 fill: None,
1712 stroke: Some(debug_stroke),
1713 }),
1714 Area::Polyline {
1715 points,
1716 stroke_width,
1717 } => Some(Primitive::PolyLine {
1718 points: points.clone(),
1719 stroke: ResolvedStroke {
1720 thickness: stroke_width.0,
1721 ..debug_stroke
1722 },
1723 clip_bounds: Rectangle::INFINITE,
1724 extensions: LineExtensions {
1725 start: false,
1726 end: false,
1727 },
1728 arrows: LineArrows {
1729 start: false,
1730 end: false,
1731 size: 0.0,
1732 },
1733 }),
1734 Area::RegularPolygon {
1735 center,
1736 radius,
1737 vertices,
1738 rotation,
1739 } => Some(Primitive::Polygon {
1740 center: *center,
1741 radius: *radius,
1742 vertices: *vertices,
1743 rotation: *rotation,
1744 fill: None,
1745 stroke: Some(debug_stroke),
1746 }),
1747 Area::Arc {
1748 center,
1749 radius_outer,
1750 radius_inner,
1751 start_angle,
1752 end_angle,
1753 } => Some(Primitive::Arc {
1754 center: *center,
1755 radius_inner: Some(*radius_inner),
1756 radius_outer: *radius_outer,
1757 start_angle: *start_angle,
1758 end_angle: *end_angle,
1759 fill: None,
1760 stroke: Some(debug_stroke),
1761 }),
1762 Area::Custom(_) => None, }
1764}