1use super::rect::Rect;
10use super::renderers::GPU_RENDER_THRESHOLD;
11use super::renderers::GpuChartLineRenderer;
12use super::types::{
13 AxisId, AxisOrientation, AxisPosition, Chart, ChartType, DataPoint, FillRegionKind,
14};
15use crate::{GeometryRenderer, PathBuilder, ScissorRect, Stroke, Style};
16use astrelis_core::profiling::profile_scope;
17use astrelis_render::{Color, Viewport, wgpu};
18use glam::Vec2;
19
20pub struct ChartRenderer<'a> {
22 geometry: &'a mut GeometryRenderer,
23 gpu_line_renderer: Option<&'a mut GpuChartLineRenderer>,
25}
26
27impl<'a> ChartRenderer<'a> {
28 pub fn new(geometry: &'a mut GeometryRenderer) -> Self {
30 Self {
31 geometry,
32 gpu_line_renderer: None,
33 }
34 }
35
36 pub fn with_gpu_line_renderer(
42 geometry: &'a mut GeometryRenderer,
43 gpu_line_renderer: &'a mut GpuChartLineRenderer,
44 ) -> Self {
45 Self {
46 geometry,
47 gpu_line_renderer: Some(gpu_line_renderer),
48 }
49 }
50
51 pub fn draw(&mut self, chart: &Chart, bounds: Rect) {
58 self.draw_internal(chart, bounds, false);
59 }
60
61 pub fn draw_with_gpu_lines(&mut self, chart: &Chart, bounds: Rect) -> Rect {
69 let plot_area = bounds.inset(chart.padding);
70 self.draw_internal(chart, bounds, true);
71 plot_area
72 }
73
74 fn draw_internal(&mut self, chart: &Chart, bounds: Rect, skip_gpu_lines: bool) {
76 profile_scope!("chart_draw");
77
78 let use_gpu_for_lines = skip_gpu_lines && chart.chart_type == ChartType::Line;
81
82 self.geometry
84 .draw_rect(bounds.position(), bounds.size(), chart.background_color);
85
86 let plot_area = bounds.inset(chart.padding);
88
89 {
91 profile_scope!("draw_grid");
92 self.draw_grid(chart, &plot_area);
93 }
94
95 {
97 profile_scope!("draw_axes");
98 self.draw_all_axes(chart, &plot_area);
99 }
100
101 self.geometry.set_scissor(ScissorRect::from_f32(
103 plot_area.x,
104 plot_area.y,
105 plot_area.width,
106 plot_area.height,
107 ));
108
109 {
111 profile_scope!("draw_fill_regions");
112 self.draw_fill_regions(chart, &plot_area);
113 }
114
115 {
117 profile_scope!("draw_line_annotations");
118 self.draw_line_annotations(chart, &plot_area);
119 }
120
121 {
123 profile_scope!("draw_series");
124 match chart.chart_type {
125 ChartType::Line => {
126 if use_gpu_for_lines {
127 self.draw_line_series_tessellated_only(chart, &plot_area);
129 } else {
130 self.draw_line_series(chart, &plot_area);
131 }
132 }
133 ChartType::Scatter => self.draw_scatter_series(chart, &plot_area),
134 ChartType::Bar => self.draw_bar_series(chart, &plot_area),
135 ChartType::Area => self.draw_area_series(chart, &plot_area),
136 }
137 }
138
139 if chart.show_crosshair {
141 profile_scope!("draw_crosshair");
142 self.draw_crosshair(chart, &plot_area);
143 }
144
145 self.geometry.reset_scissor();
147 }
148
149 fn draw_line_series_tessellated_only(&mut self, chart: &Chart, plot_area: &Rect) {
151 profile_scope!("draw_line_series_small");
152 for series in &chart.series {
153 if series.data.len() > GPU_RENDER_THRESHOLD {
155 continue;
156 }
157
158 if series.data.len() < 2 {
159 continue;
160 }
161
162 self.draw_single_line_series_tessellated(chart, series, plot_area);
163 }
164 }
165
166 fn draw_single_line_series_tessellated(
168 &mut self,
169 chart: &Chart,
170 series: &super::types::Series,
171 plot_area: &Rect,
172 ) {
173 let (x_min, x_max) = chart.axis_range(series.x_axis);
175 let x_range = x_max - x_min;
176 let buffer = x_range * 0.1;
177 let visible_x_min = x_min - buffer;
178 let visible_x_max = x_max + buffer;
179
180 let (start_idx, end_idx) =
182 Self::find_visible_range(&series.data, visible_x_min, visible_x_max);
183
184 if end_idx <= start_idx + 1 {
185 return;
186 }
187
188 let mut builder = PathBuilder::new();
190 let first_point = self.data_to_pixel_with_axes(
191 chart,
192 plot_area,
193 series.data[start_idx].x,
194 series.data[start_idx].y,
195 series.x_axis,
196 series.y_axis,
197 );
198 builder.move_to(first_point);
199
200 for point in &series.data[start_idx + 1..end_idx] {
201 let pixel = self.data_to_pixel_with_axes(
202 chart,
203 plot_area,
204 point.x,
205 point.y,
206 series.x_axis,
207 series.y_axis,
208 );
209 builder.line_to(pixel);
210 }
211
212 let path = builder.build();
213 let stroke = Stroke::solid(series.style.color, series.style.line_width);
214 self.geometry.draw_path_stroke(&path, &stroke);
215
216 if let Some(point_style) = &series.style.point_style {
218 for point in &series.data[start_idx..end_idx] {
219 let pixel = self.data_to_pixel_with_axes(
220 chart,
221 plot_area,
222 point.x,
223 point.y,
224 series.x_axis,
225 series.y_axis,
226 );
227 self.geometry
228 .draw_circle(pixel, point_style.size * 0.5, point_style.color);
229 }
230 }
231 }
232
233 fn data_to_pixel_with_axes(
235 &self,
236 chart: &Chart,
237 plot_area: &Rect,
238 x: f64,
239 y: f64,
240 x_axis_id: AxisId,
241 y_axis_id: AxisId,
242 ) -> Vec2 {
243 let (x_min, x_max) = chart.axis_range(x_axis_id);
244 let (y_min, y_max) = chart.axis_range(y_axis_id);
245
246 let px = plot_area.x + ((x - x_min) / (x_max - x_min)) as f32 * plot_area.width;
247 let py = plot_area.y + plot_area.height
249 - ((y - y_min) / (y_max - y_min)) as f32 * plot_area.height;
250
251 Vec2::new(px, py)
252 }
253
254 pub fn pixel_to_data(&self, chart: &Chart, plot_area: &Rect, pixel: Vec2) -> DataPoint {
256 let (x_min, x_max) = chart.x_range();
257 let (y_min, y_max) = chart.y_range();
258
259 let x = x_min + ((pixel.x - plot_area.x) / plot_area.width) as f64 * (x_max - x_min);
260 let y = y_max - ((pixel.y - plot_area.y) / plot_area.height) as f64 * (y_max - y_min);
261
262 DataPoint::new(x, y)
263 }
264
265 fn draw_fill_regions(&mut self, chart: &Chart, plot_area: &Rect) {
266 for region in &chart.fill_regions {
267 match ®ion.kind {
268 FillRegionKind::HorizontalBand { y_min, y_max } => {
269 let (x_range_min, x_range_max) = chart.axis_range(region.x_axis);
270 let top_left = self.data_to_pixel_with_axes(
271 chart,
272 plot_area,
273 x_range_min,
274 *y_max,
275 region.x_axis,
276 region.y_axis,
277 );
278 let bottom_right = self.data_to_pixel_with_axes(
279 chart,
280 plot_area,
281 x_range_max,
282 *y_min,
283 region.x_axis,
284 region.y_axis,
285 );
286
287 self.geometry.draw_rect(
288 Vec2::new(top_left.x, top_left.y),
289 Vec2::new(bottom_right.x - top_left.x, bottom_right.y - top_left.y),
290 region.color,
291 );
292 }
293 FillRegionKind::VerticalBand { x_min, x_max } => {
294 let (y_range_min, y_range_max) = chart.axis_range(region.y_axis);
295 let top_left = self.data_to_pixel_with_axes(
296 chart,
297 plot_area,
298 *x_min,
299 y_range_max,
300 region.x_axis,
301 region.y_axis,
302 );
303 let bottom_right = self.data_to_pixel_with_axes(
304 chart,
305 plot_area,
306 *x_max,
307 y_range_min,
308 region.x_axis,
309 region.y_axis,
310 );
311
312 self.geometry.draw_rect(
313 Vec2::new(top_left.x, top_left.y),
314 Vec2::new(bottom_right.x - top_left.x, bottom_right.y - top_left.y),
315 region.color,
316 );
317 }
318 FillRegionKind::BelowSeries {
319 series_index,
320 y_baseline,
321 } => {
322 if let Some(series) = chart.series.get(*series_index) {
323 if series.data.len() < 2 {
324 continue;
325 }
326
327 let mut builder = PathBuilder::new();
328
329 let base_start = self.data_to_pixel_with_axes(
331 chart,
332 plot_area,
333 series.data[0].x,
334 *y_baseline,
335 series.x_axis,
336 series.y_axis,
337 );
338 builder.move_to(base_start);
339
340 let first = self.data_to_pixel_with_axes(
342 chart,
343 plot_area,
344 series.data[0].x,
345 series.data[0].y,
346 series.x_axis,
347 series.y_axis,
348 );
349 builder.line_to(first);
350
351 for point in &series.data[1..] {
353 let p = self.data_to_pixel_with_axes(
354 chart,
355 plot_area,
356 point.x,
357 point.y,
358 series.x_axis,
359 series.y_axis,
360 );
361 builder.line_to(p);
362 }
363
364 let base_end = self.data_to_pixel_with_axes(
366 chart,
367 plot_area,
368 series.data.last().unwrap().x,
369 *y_baseline,
370 series.x_axis,
371 series.y_axis,
372 );
373 builder.line_to(base_end);
374 builder.close();
375
376 let path = builder.build();
377 let style = Style::fill_color(region.color);
378 self.geometry.draw_path(&path, &style);
379 }
380 }
381 FillRegionKind::BetweenSeries {
382 series_index_1,
383 series_index_2,
384 } => {
385 let series1 = chart.series.get(*series_index_1);
386 let series2 = chart.series.get(*series_index_2);
387
388 if let (Some(s1), Some(s2)) = (series1, series2) {
389 if s1.data.is_empty() || s2.data.is_empty() {
390 continue;
391 }
392
393 let mut builder = PathBuilder::new();
394
395 let first = self.data_to_pixel_with_axes(
397 chart,
398 plot_area,
399 s1.data[0].x,
400 s1.data[0].y,
401 s1.x_axis,
402 s1.y_axis,
403 );
404 builder.move_to(first);
405
406 for point in &s1.data[1..] {
407 let p = self.data_to_pixel_with_axes(
408 chart, plot_area, point.x, point.y, s1.x_axis, s1.y_axis,
409 );
410 builder.line_to(p);
411 }
412
413 for point in s2.data.iter().rev() {
415 let p = self.data_to_pixel_with_axes(
416 chart, plot_area, point.x, point.y, s2.x_axis, s2.y_axis,
417 );
418 builder.line_to(p);
419 }
420
421 builder.close();
422
423 let path = builder.build();
424 let style = Style::fill_color(region.color);
425 self.geometry.draw_path(&path, &style);
426 }
427 }
428 FillRegionKind::Rectangle {
429 x_min,
430 y_min,
431 x_max,
432 y_max,
433 } => {
434 let top_left = self.data_to_pixel_with_axes(
435 chart,
436 plot_area,
437 *x_min,
438 *y_max,
439 region.x_axis,
440 region.y_axis,
441 );
442 let bottom_right = self.data_to_pixel_with_axes(
443 chart,
444 plot_area,
445 *x_max,
446 *y_min,
447 region.x_axis,
448 region.y_axis,
449 );
450
451 self.geometry.draw_rect(
452 Vec2::new(top_left.x, top_left.y),
453 Vec2::new(bottom_right.x - top_left.x, bottom_right.y - top_left.y),
454 region.color,
455 );
456 }
457 FillRegionKind::Polygon { points } => {
458 if points.len() < 3 {
459 continue;
460 }
461
462 let mut builder = PathBuilder::new();
463 let first = self.data_to_pixel_with_axes(
464 chart,
465 plot_area,
466 points[0].x,
467 points[0].y,
468 region.x_axis,
469 region.y_axis,
470 );
471 builder.move_to(first);
472
473 for point in &points[1..] {
474 let p = self.data_to_pixel_with_axes(
475 chart,
476 plot_area,
477 point.x,
478 point.y,
479 region.x_axis,
480 region.y_axis,
481 );
482 builder.line_to(p);
483 }
484 builder.close();
485
486 let path = builder.build();
487 let style = Style::fill_color(region.color);
488 self.geometry.draw_path(&path, &style);
489 }
490 }
491 }
492 }
493
494 fn draw_line_annotations(&mut self, chart: &Chart, plot_area: &Rect) {
495 for annotation in &chart.line_annotations {
496 let start = self.data_to_pixel_with_axes(
497 chart,
498 plot_area,
499 annotation.start.x,
500 annotation.start.y,
501 annotation.x_axis,
502 annotation.y_axis,
503 );
504 let end = self.data_to_pixel_with_axes(
505 chart,
506 plot_area,
507 annotation.end.x,
508 annotation.end.y,
509 annotation.x_axis,
510 annotation.y_axis,
511 );
512
513 self.geometry
514 .draw_line(start, end, annotation.width, annotation.color);
515 }
516 }
517
518 fn draw_crosshair(&mut self, chart: &Chart, plot_area: &Rect) {
519 if let Some((series_idx, point_idx)) = chart.interactive.hovered_point
520 && let Some(series) = chart.series.get(series_idx)
521 && let Some(point) = series.data.get(point_idx)
522 {
523 let pixel = self.data_to_pixel_with_axes(
524 chart,
525 plot_area,
526 point.x,
527 point.y,
528 series.x_axis,
529 series.y_axis,
530 );
531
532 let crosshair_color = Color::rgba(1.0, 1.0, 1.0, 0.5);
533
534 self.geometry.draw_line(
536 Vec2::new(pixel.x, plot_area.y),
537 Vec2::new(pixel.x, plot_area.bottom()),
538 1.0,
539 crosshair_color,
540 );
541
542 self.geometry.draw_line(
544 Vec2::new(plot_area.x, pixel.y),
545 Vec2::new(plot_area.right(), pixel.y),
546 1.0,
547 crosshair_color,
548 );
549
550 self.geometry.draw_circle(pixel, 6.0, series.style.color);
552 }
553 }
554
555 fn draw_grid(&mut self, chart: &Chart, plot_area: &Rect) {
556 for axis in &chart.axes {
558 if !axis.grid_lines || !axis.visible {
559 continue;
560 }
561
562 let style = &axis.style;
563 let tick_count = axis.tick_count;
564
565 match axis.orientation {
566 AxisOrientation::Horizontal => {
567 for i in 0..=tick_count {
569 let t = i as f32 / tick_count as f32;
570 let x = plot_area.x + t * plot_area.width;
571 self.geometry.draw_line(
572 Vec2::new(x, plot_area.y),
573 Vec2::new(x, plot_area.bottom()),
574 style.grid_width,
575 style.grid_color,
576 );
577 }
578 }
579 AxisOrientation::Vertical => {
580 for i in 0..=tick_count {
582 let t = i as f32 / tick_count as f32;
583 let y = plot_area.y + t * plot_area.height;
584 self.geometry.draw_line(
585 Vec2::new(plot_area.x, y),
586 Vec2::new(plot_area.right(), y),
587 style.grid_width,
588 style.grid_color,
589 );
590 }
591 }
592 }
593 }
594 }
595
596 fn draw_all_axes(&mut self, chart: &Chart, plot_area: &Rect) {
597 for axis in &chart.axes {
598 if !axis.visible {
599 continue;
600 }
601
602 let style = &axis.style;
603
604 match (axis.orientation, axis.position) {
605 (AxisOrientation::Horizontal, AxisPosition::Bottom) => {
606 self.geometry.draw_line(
608 Vec2::new(plot_area.x, plot_area.bottom()),
609 Vec2::new(plot_area.right(), plot_area.bottom()),
610 style.line_width,
611 style.line_color,
612 );
613
614 for i in 0..=axis.tick_count {
616 let t = i as f32 / axis.tick_count as f32;
617 let x = plot_area.x + t * plot_area.width;
618 let y = plot_area.bottom();
619 self.geometry.draw_line(
620 Vec2::new(x, y),
621 Vec2::new(x, y + style.tick_length),
622 style.line_width,
623 style.tick_color,
624 );
625 }
626 }
627 (AxisOrientation::Horizontal, AxisPosition::Top) => {
628 self.geometry.draw_line(
630 Vec2::new(plot_area.x, plot_area.y),
631 Vec2::new(plot_area.right(), plot_area.y),
632 style.line_width,
633 style.line_color,
634 );
635
636 for i in 0..=axis.tick_count {
638 let t = i as f32 / axis.tick_count as f32;
639 let x = plot_area.x + t * plot_area.width;
640 let y = plot_area.y;
641 self.geometry.draw_line(
642 Vec2::new(x, y - style.tick_length),
643 Vec2::new(x, y),
644 style.line_width,
645 style.tick_color,
646 );
647 }
648 }
649 (AxisOrientation::Vertical, AxisPosition::Left) => {
650 self.geometry.draw_line(
652 Vec2::new(plot_area.x, plot_area.y),
653 Vec2::new(plot_area.x, plot_area.bottom()),
654 style.line_width,
655 style.line_color,
656 );
657
658 for i in 0..=axis.tick_count {
660 let t = i as f32 / axis.tick_count as f32;
661 let x = plot_area.x;
662 let y = plot_area.y + t * plot_area.height;
663 self.geometry.draw_line(
664 Vec2::new(x - style.tick_length, y),
665 Vec2::new(x, y),
666 style.line_width,
667 style.tick_color,
668 );
669 }
670 }
671 (AxisOrientation::Vertical, AxisPosition::Right) => {
672 self.geometry.draw_line(
674 Vec2::new(plot_area.right(), plot_area.y),
675 Vec2::new(plot_area.right(), plot_area.bottom()),
676 style.line_width,
677 style.line_color,
678 );
679
680 for i in 0..=axis.tick_count {
682 let t = i as f32 / axis.tick_count as f32;
683 let x = plot_area.right();
684 let y = plot_area.y + t * plot_area.height;
685 self.geometry.draw_line(
686 Vec2::new(x, y),
687 Vec2::new(x + style.tick_length, y),
688 style.line_width,
689 style.tick_color,
690 );
691 }
692 }
693 _ => {}
694 }
695 }
696 }
697
698 fn draw_line_series(&mut self, chart: &Chart, plot_area: &Rect) {
699 profile_scope!("draw_line_series");
700 for series in &chart.series {
701 if series.data.len() < 2 {
702 continue;
703 }
704
705 let (x_min, x_max) = chart.axis_range(series.x_axis);
707 let x_range = x_max - x_min;
708 let buffer = x_range * 0.1; let visible_x_min = x_min - buffer;
710 let visible_x_max = x_max + buffer;
711
712 let (start_idx, end_idx) =
714 Self::find_visible_range(&series.data, visible_x_min, visible_x_max);
715
716 tracing::trace!(
717 "Series '{}': rendering {} of {} points (indices {}..{})",
718 series.name,
719 end_idx - start_idx,
720 series.data.len(),
721 start_idx,
722 end_idx
723 );
724
725 if end_idx <= start_idx + 1 {
727 continue;
728 }
729
730 let mut builder = PathBuilder::new();
732 let first_point = self.data_to_pixel_with_axes(
733 chart,
734 plot_area,
735 series.data[start_idx].x,
736 series.data[start_idx].y,
737 series.x_axis,
738 series.y_axis,
739 );
740 builder.move_to(first_point);
741
742 for point in &series.data[start_idx + 1..end_idx] {
743 let pixel = self.data_to_pixel_with_axes(
744 chart,
745 plot_area,
746 point.x,
747 point.y,
748 series.x_axis,
749 series.y_axis,
750 );
751 builder.line_to(pixel);
752 }
753
754 let path = builder.build();
755
756 let stroke = Stroke::solid(series.style.color, series.style.line_width);
758 self.geometry.draw_path_stroke(&path, &stroke);
759
760 if let Some(point_style) = &series.style.point_style {
762 for point in &series.data[start_idx..end_idx] {
763 let pixel = self.data_to_pixel_with_axes(
764 chart,
765 plot_area,
766 point.x,
767 point.y,
768 series.x_axis,
769 series.y_axis,
770 );
771 self.geometry
772 .draw_circle(pixel, point_style.size * 0.5, point_style.color);
773 }
774 }
775 }
776 }
777
778 fn find_visible_range(data: &[DataPoint], x_min: f64, x_max: f64) -> (usize, usize) {
782 if data.is_empty() {
783 return (0, 0);
784 }
785
786 let start = data
788 .binary_search_by(|p| p.x.partial_cmp(&x_min).unwrap_or(std::cmp::Ordering::Equal))
789 .unwrap_or_else(|i| i);
790
791 let end = data
793 .binary_search_by(|p| {
794 if p.x <= x_max {
795 std::cmp::Ordering::Less
796 } else {
797 std::cmp::Ordering::Greater
798 }
799 })
800 .unwrap_or_else(|i| i);
801
802 let start = start.saturating_sub(1);
804 let end = (end + 1).min(data.len());
805
806 (start, end)
807 }
808
809 fn draw_scatter_series(&mut self, chart: &Chart, plot_area: &Rect) {
810 let default_point_style = super::style::PointStyle::default();
811 for series in &chart.series {
812 let point_style = series
813 .style
814 .point_style
815 .as_ref()
816 .unwrap_or(&default_point_style);
817
818 let (x_min, x_max) = chart.axis_range(series.x_axis);
820 let x_range = x_max - x_min;
821 let buffer = x_range * 0.1;
822 let (start_idx, end_idx) =
823 Self::find_visible_range(&series.data, x_min - buffer, x_max + buffer);
824
825 for point in &series.data[start_idx..end_idx] {
826 let pixel = self.data_to_pixel_with_axes(
827 chart,
828 plot_area,
829 point.x,
830 point.y,
831 series.x_axis,
832 series.y_axis,
833 );
834 self.geometry
835 .draw_circle(pixel, point_style.size * 0.5, series.style.color);
836 }
837 }
838 }
839
840 fn draw_bar_series(&mut self, chart: &Chart, plot_area: &Rect) {
841 let bar_width = chart.bar_config.bar_width;
842 let gap = chart.bar_config.gap;
843
844 let series_count = chart.series.len() as f32;
845 let total_width = bar_width * series_count + gap * (series_count - 1.0);
846
847 for (series_idx, series) in chart.series.iter().enumerate() {
848 let (y_min, _) = chart.axis_range(series.y_axis);
849 let offset = series_idx as f32 * (bar_width + gap) - total_width * 0.5;
850
851 let (x_min, x_max) = chart.axis_range(series.x_axis);
853 let x_range = x_max - x_min;
854 let buffer = x_range * 0.1;
855 let (start_idx, end_idx) =
856 Self::find_visible_range(&series.data, x_min - buffer, x_max + buffer);
857
858 for point in &series.data[start_idx..end_idx] {
859 let center_pixel = self.data_to_pixel_with_axes(
860 chart,
861 plot_area,
862 point.x,
863 point.y,
864 series.x_axis,
865 series.y_axis,
866 );
867 let base_pixel = self.data_to_pixel_with_axes(
868 chart,
869 plot_area,
870 point.x,
871 y_min,
872 series.x_axis,
873 series.y_axis,
874 );
875
876 let bar_x = center_pixel.x + offset;
877 let bar_height = (base_pixel.y - center_pixel.y).abs();
878 let bar_y = center_pixel.y.min(base_pixel.y);
879
880 self.geometry.draw_rect(
881 Vec2::new(bar_x, bar_y),
882 Vec2::new(bar_width, bar_height),
883 series.style.color,
884 );
885 }
886 }
887 }
888
889 fn draw_area_series(&mut self, chart: &Chart, plot_area: &Rect) {
890 for series in &chart.series {
891 if series.data.len() < 2 {
892 continue;
893 }
894
895 let (y_min, _) = chart.axis_range(series.y_axis);
896
897 let (x_min, x_max) = chart.axis_range(series.x_axis);
899 let x_range = x_max - x_min;
900 let buffer = x_range * 0.1; let visible_x_min = x_min - buffer;
902 let visible_x_max = x_max + buffer;
903
904 let (start_idx, end_idx) =
906 Self::find_visible_range(&series.data, visible_x_min, visible_x_max);
907
908 if end_idx <= start_idx + 1 {
910 continue;
911 }
912
913 let visible_data = &series.data[start_idx..end_idx];
914
915 let mut builder = PathBuilder::new();
917
918 let first_x = visible_data[0].x;
920 let base_start = self.data_to_pixel_with_axes(
921 chart,
922 plot_area,
923 first_x,
924 y_min,
925 series.x_axis,
926 series.y_axis,
927 );
928 builder.move_to(base_start);
929
930 let first_point = self.data_to_pixel_with_axes(
932 chart,
933 plot_area,
934 first_x,
935 visible_data[0].y,
936 series.x_axis,
937 series.y_axis,
938 );
939 builder.line_to(first_point);
940
941 for point in &visible_data[1..] {
943 let pixel = self.data_to_pixel_with_axes(
944 chart,
945 plot_area,
946 point.x,
947 point.y,
948 series.x_axis,
949 series.y_axis,
950 );
951 builder.line_to(pixel);
952 }
953
954 let last_x = visible_data.last().unwrap().x;
956 let base_end = self.data_to_pixel_with_axes(
957 chart,
958 plot_area,
959 last_x,
960 y_min,
961 series.x_axis,
962 series.y_axis,
963 );
964 builder.line_to(base_end);
965 builder.close();
966
967 let path = builder.build();
968
969 let fill_color = if let Some(fill) = &series.style.fill {
971 Color::rgba(fill.color.r, fill.color.g, fill.color.b, fill.opacity)
972 } else {
973 Color::rgba(
974 series.style.color.r,
975 series.style.color.g,
976 series.style.color.b,
977 0.3,
978 )
979 };
980
981 let style = Style::fill_color(fill_color);
982 self.geometry.draw_path(&path, &style);
983
984 let mut builder = PathBuilder::new();
986 let first_point = self.data_to_pixel_with_axes(
987 chart,
988 plot_area,
989 visible_data[0].x,
990 visible_data[0].y,
991 series.x_axis,
992 series.y_axis,
993 );
994 builder.move_to(first_point);
995
996 for point in &visible_data[1..] {
997 let pixel = self.data_to_pixel_with_axes(
998 chart,
999 plot_area,
1000 point.x,
1001 point.y,
1002 series.x_axis,
1003 series.y_axis,
1004 );
1005 builder.line_to(pixel);
1006 }
1007
1008 let path = builder.build();
1009 let stroke = Stroke::solid(series.style.color, series.style.line_width);
1010 self.geometry.draw_path_stroke(&path, &stroke);
1011 }
1012 }
1013
1014 pub fn render(&mut self, pass: &mut wgpu::RenderPass, viewport: Viewport) {
1016 self.geometry.render(pass, viewport);
1017 }
1018
1019 pub fn render_with_gpu_lines(
1031 &mut self,
1032 pass: &mut wgpu::RenderPass,
1033 viewport: Viewport,
1034 chart: &Chart,
1035 plot_area: &Rect,
1036 ) {
1037 profile_scope!("chart_render_gpu_lines");
1038
1039 self.geometry.render(pass, viewport);
1041
1042 if let Some(gpu_renderer) = &self.gpu_line_renderer {
1044 profile_scope!("render_gpu_line_series");
1045 gpu_renderer.render(pass, viewport, plot_area, chart);
1046 }
1047 }
1048}
1049
1050#[derive(Debug, Clone)]
1052pub struct HitTestResult {
1053 pub series_index: usize,
1055 pub point_index: usize,
1057 pub distance: f32,
1059 pub data_point: DataPoint,
1061 pub pixel_position: Vec2,
1063}
1064
1065impl ChartRenderer<'_> {
1066 pub fn hit_test(
1068 &self,
1069 chart: &Chart,
1070 plot_area: &Rect,
1071 pixel: Vec2,
1072 max_distance: f32,
1073 ) -> Option<HitTestResult> {
1074 if !plot_area.contains(pixel) {
1075 return None;
1076 }
1077
1078 let mut best: Option<HitTestResult> = None;
1079
1080 for (series_idx, series) in chart.series.iter().enumerate() {
1081 for (point_idx, point) in series.data.iter().enumerate() {
1082 let point_pixel = self.data_to_pixel_with_axes(
1083 chart,
1084 plot_area,
1085 point.x,
1086 point.y,
1087 series.x_axis,
1088 series.y_axis,
1089 );
1090
1091 let dist = pixel.distance(point_pixel);
1092
1093 if dist <= max_distance && best.as_ref().is_none_or(|b| dist < b.distance) {
1094 best = Some(HitTestResult {
1095 series_index: series_idx,
1096 point_index: point_idx,
1097 distance: dist,
1098 data_point: *point,
1099 pixel_position: point_pixel,
1100 });
1101 }
1102 }
1103 }
1104
1105 best
1106 }
1107}