dataviz\figure\display/
hovergroupbarchart.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
use ab_glyph::FontRef;
use image::ImageBuffer;
use imageproc::drawing::{draw_line_segment_mut, draw_text_mut, text_size};

use crate::figure::{canvas::pixelcanvas::PixelCanvas, figuretypes::groupbarchart::GroupBarChart};

use super::hover::Hover;

impl Hover for GroupBarChart {
    fn handle_hover(&self, mouse_x: u32, mouse_y: u32, canvas: &PixelCanvas) -> Option<Vec<u32>> {
        let scale_x = (canvas.width - 2 * canvas.margin) as f64 / self.datasets.len() as f64;

        let mut img =
            ImageBuffer::from_raw(canvas.width, canvas.height, canvas.buffer.clone()).unwrap();

        let mut closest_bar_group: Option<(f64, Vec<f64>)> = None;
        let mut min_distance = f64::MAX;

        // Find the closest bar group
        for (group_index, _) in self.datasets.iter().enumerate() {
            let group_center_x = canvas.margin as f64 + (group_index as f64 + 0.5) * scale_x;
            let distance = (mouse_x as f64 - group_center_x).abs();

            if distance < min_distance {
                min_distance = distance;

                // Collect values for this group
                let values = self
                    .datasets
                    .iter()
                    .filter_map(|dataset| dataset.data.get(group_index).map(|&(_, value)| value))
                    .collect::<Vec<f64>>();

                closest_bar_group = Some((group_center_x, values));
            }
        }

        if let Some((group_center_x, group_values)) = closest_bar_group {
            // Draw a red line from the center of the bar group to the cursor
            draw_line_segment_mut(
                &mut img,
                (
                    group_center_x as f32,
                    (canvas.height - canvas.margin) as f32,
                ),
                (mouse_x as f32, mouse_y as f32),
                image::Rgb([255, 0, 0]), // Red line
            );

            // Format the tooltip text
            let mut tooltip_text = String::from("X Value: ");

            // Calculate tooltip dimensions
            let font = self.get_font(self.config.font_label.as_bytes());
            let scale = ab_glyph::PxScale { x: 12.0, y: 12.0 };
            let text_size = text_size(scale, &font, &tooltip_text).0 as i32;

            let rect_x = mouse_x as i32 + 10;
            let rect_y = mouse_y as i32 - 30;
            let rect_width = text_size + 20;
            let rect_height = 25 * (group_values.len() as i32 + 1);

            // Ensure tooltip stays within bounds
            let rect_x = rect_x.max(0).min((canvas.width as i32 - rect_width) as i32);
            let mut rect_y = rect_y
                .max(0)
                .min((canvas.height as i32 - rect_height) as i32);

            // Draw tooltip background
            for y in rect_y..(rect_y + rect_height) {
                for x in rect_x..(rect_x + rect_width) {
                    if x >= 0 && y >= 0 && (x as u32) < canvas.width && (y as u32) < canvas.height {
                        img.put_pixel(x as u32, y as u32, image::Rgb([255, 255, 255]));
                    }
                }
            }

            // Draw tooltip border
            for x in rect_x..(rect_x + rect_width) {
                img.put_pixel(x as u32, rect_y as u32, image::Rgb([0, 0, 0])); // Top edge
                img.put_pixel(
                    x as u32,
                    (rect_y + rect_height - 1) as u32,
                    image::Rgb([0, 0, 0]),
                ); // Bottom edge
            }
            for y in rect_y..(rect_y + rect_height) {
                img.put_pixel(rect_x as u32, y as u32, image::Rgb([0, 0, 0])); // Left edge
                img.put_pixel(
                    (rect_x + rect_width - 1) as u32,
                    y as u32,
                    image::Rgb([0, 0, 0]),
                ); // Right edge
            }

            for (dataset_index, value) in group_values.iter().enumerate() {
                if dataset_index == 0 {
                    tooltip_text.push_str(&format!("{:.2}", value));
                } else {
                    let dataset_label = &self.datasets[dataset_index].label;
                    tooltip_text = format!("{} : {:.2}", dataset_label, value);
                }

                // Draw the tooltip text
                draw_text_mut(
                    &mut img,
                    image::Rgb([0, 0, 0]), // Text color
                    rect_x + 10,
                    rect_y + 5,
                    scale,
                    &font,
                    &tooltip_text,
                );

                rect_y += 20;
            }

            Some(
                img.pixels()
                    .map(|pixel: &image::Rgb<u8>| {
                        let [r, g, b] = pixel.0;
                        (r as u32) << 16 | (g as u32) << 8 | b as u32
                    })
                    .collect(),
            )
        } else {
            None
        }
    }

    fn find_closest_point(
        &self,
        mouse_x: u32,
        mouse_y: u32,
        canvas: &PixelCanvas,
    ) -> Option<((f64, f64), f64)> {
        let scale_x = (canvas.width - 2 * canvas.margin) as f64 / self.datasets.len() as f64;
        let scale_y = (canvas.height - 2 * canvas.margin) as f64 / 10.0; // Assume a fixed y-range for now

        let mut closest_bar = None;
        let mut min_distance = f64::MAX;

        for dataset in &self.datasets {
            for &(x, y) in &dataset.data {
                let px = ((x) * scale_x + canvas.margin as f64) as f64;
                let py = (canvas.height as f64 - canvas.margin as f64 - y * scale_y) as f64;

                let distance =
                    ((mouse_x as f64 - px).powi(2) + (mouse_y as f64 - py).powi(2)).sqrt();
                if distance < min_distance {
                    min_distance = distance;
                    closest_bar = Some(((x, y), y));
                }
            }
        }

        closest_bar
    }

    fn to_canvas_coordinates(&self, x: f64, y: f64, canvas: &PixelCanvas) -> (u32, u32) {
        let scale_x = (canvas.width - 2 * canvas.margin) as f64 / self.datasets.len() as f64;
        let scale_y = (canvas.height - 2 * canvas.margin) as f64 / 10.0; // Assume a fixed y-range for now

        let px = ((x) * scale_x + canvas.margin as f64) as u32;
        let py = (canvas.height as f64 - canvas.margin as f64 - y * scale_y) as u32;

        (px, py)
    }

    fn get_font<'a>(&self, font_data: &'a [u8]) -> FontRef<'a> {
        FontRef::try_from_slice(&font_data).unwrap()
    }
}