Skip to main content

dear_implot/plots/
text.rs

1//! Text plot implementation
2
3use super::{PlotData, PlotError, plot_spec_from, with_plot_str};
4use crate::{ItemFlags, TextFlags, sys};
5
6/// Builder for text plots with extensive customization options
7///
8/// Text plots allow placing text labels at specific coordinates in the plot area.
9pub struct TextPlot<'a> {
10    text: &'a str,
11    x: f64,
12    y: f64,
13    pix_offset_x: f64,
14    pix_offset_y: f64,
15    flags: TextFlags,
16    item_flags: ItemFlags,
17}
18
19impl<'a> TextPlot<'a> {
20    /// Create a new text plot with the given text and position
21    pub fn new(text: &'a str, x: f64, y: f64) -> Self {
22        Self {
23            text,
24            x,
25            y,
26            pix_offset_x: 0.0,
27            pix_offset_y: 0.0,
28            flags: TextFlags::NONE,
29            item_flags: ItemFlags::NONE,
30        }
31    }
32
33    /// Set pixel offset for fine positioning
34    pub fn with_pixel_offset(mut self, offset_x: f64, offset_y: f64) -> Self {
35        self.pix_offset_x = offset_x;
36        self.pix_offset_y = offset_y;
37        self
38    }
39
40    /// Set text flags for customization
41    pub fn with_flags(mut self, flags: TextFlags) -> Self {
42        self.flags = flags;
43        self
44    }
45
46    /// Set common item flags for this plot item (applies to all plot types)
47    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
48        self.item_flags = flags;
49        self
50    }
51
52    /// Make text vertical instead of horizontal
53    pub fn vertical(mut self) -> Self {
54        self.flags |= TextFlags::VERTICAL;
55        self
56    }
57
58    /// Validate the plot data
59    pub fn validate(&self) -> Result<(), PlotError> {
60        if self.text.is_empty() {
61            return Err(PlotError::InvalidData("Text cannot be empty".to_string()));
62        }
63        if self.text.contains('\0') {
64            return Err(PlotError::StringConversion(
65                "text contained null byte".to_string(),
66            ));
67        }
68        Ok(())
69    }
70
71    /// Plot the text
72    pub fn plot(self) {
73        let pix_offset = sys::ImVec2_c {
74            x: self.pix_offset_x as f32,
75            y: self.pix_offset_y as f32,
76        };
77        let _ = with_plot_str(self.text, |text_ptr| unsafe {
78            let spec = plot_spec_from(
79                self.flags.bits() | self.item_flags.bits(),
80                0,
81                crate::IMPLOT_AUTO,
82            );
83            sys::ImPlot_PlotText(text_ptr, self.x, self.y, pix_offset, spec);
84        });
85    }
86}
87
88impl<'a> PlotData for TextPlot<'a> {
89    fn label(&self) -> &str {
90        self.text
91    }
92
93    fn data_len(&self) -> usize {
94        1 // Text plot has one data point
95    }
96}
97
98/// Multiple text labels plot
99pub struct MultiTextPlot<'a> {
100    texts: Vec<&'a str>,
101    positions: Vec<(f64, f64)>,
102    pixel_offsets: Vec<(f64, f64)>,
103    flags: TextFlags,
104    item_flags: ItemFlags,
105}
106
107impl<'a> MultiTextPlot<'a> {
108    /// Create a new multi-text plot
109    pub fn new(texts: Vec<&'a str>, positions: Vec<(f64, f64)>) -> Self {
110        let pixel_offsets = vec![(0.0, 0.0); texts.len()];
111        Self {
112            texts,
113            positions,
114            pixel_offsets,
115            flags: TextFlags::NONE,
116            item_flags: ItemFlags::NONE,
117        }
118    }
119
120    /// Set pixel offsets for all texts
121    pub fn with_pixel_offsets(mut self, offsets: Vec<(f64, f64)>) -> Self {
122        self.pixel_offsets = offsets;
123        self
124    }
125
126    /// Set text flags for all texts
127    pub fn with_flags(mut self, flags: TextFlags) -> Self {
128        self.flags = flags;
129        self
130    }
131
132    /// Set common item flags for all text items (applies to all plot types)
133    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
134        self.item_flags = flags;
135        self
136    }
137
138    /// Make all texts vertical
139    pub fn vertical(mut self) -> Self {
140        self.flags |= TextFlags::VERTICAL;
141        self
142    }
143
144    /// Validate the plot data
145    pub fn validate(&self) -> Result<(), PlotError> {
146        if self.texts.len() != self.positions.len() {
147            return Err(PlotError::InvalidData(format!(
148                "Text count ({}) must match position count ({})",
149                self.texts.len(),
150                self.positions.len()
151            )));
152        }
153
154        if self.pixel_offsets.len() != self.texts.len() {
155            return Err(PlotError::InvalidData(format!(
156                "Pixel offset count ({}) must match text count ({})",
157                self.pixel_offsets.len(),
158                self.texts.len()
159            )));
160        }
161
162        if self.texts.is_empty() {
163            return Err(PlotError::EmptyData);
164        }
165
166        for (i, text) in self.texts.iter().enumerate() {
167            if text.is_empty() {
168                return Err(PlotError::InvalidData(format!(
169                    "Text at index {} cannot be empty",
170                    i
171                )));
172            }
173        }
174
175        Ok(())
176    }
177
178    /// Plot all texts
179    pub fn plot(self) {
180        for (i, &text) in self.texts.iter().enumerate() {
181            let position = self.positions[i];
182            let offset = self.pixel_offsets[i];
183
184            let text_plot = TextPlot::new(text, position.0, position.1)
185                .with_pixel_offset(offset.0, offset.1)
186                .with_flags(self.flags)
187                .with_item_flags(self.item_flags);
188
189            text_plot.plot();
190        }
191    }
192}
193
194impl<'a> PlotData for MultiTextPlot<'a> {
195    fn label(&self) -> &str {
196        "MultiText"
197    }
198
199    fn data_len(&self) -> usize {
200        self.texts.len()
201    }
202}
203
204/// Formatted text plot with dynamic content
205pub struct FormattedTextPlot {
206    text: String,
207    x: f64,
208    y: f64,
209    pix_offset_x: f64,
210    pix_offset_y: f64,
211    flags: TextFlags,
212    item_flags: ItemFlags,
213}
214
215impl FormattedTextPlot {
216    /// Create a new formatted text plot
217    pub fn new(text: String, x: f64, y: f64) -> Self {
218        Self {
219            text,
220            x,
221            y,
222            pix_offset_x: 0.0,
223            pix_offset_y: 0.0,
224            flags: TextFlags::NONE,
225            item_flags: ItemFlags::NONE,
226        }
227    }
228
229    /// Create a formatted text plot from format arguments
230    pub fn from_format(x: f64, y: f64, args: std::fmt::Arguments) -> Self {
231        Self::new(format!("{}", args), x, y)
232    }
233
234    /// Set pixel offset for fine positioning
235    pub fn with_pixel_offset(mut self, offset_x: f64, offset_y: f64) -> Self {
236        self.pix_offset_x = offset_x;
237        self.pix_offset_y = offset_y;
238        self
239    }
240
241    /// Set text flags for customization
242    pub fn with_flags(mut self, flags: TextFlags) -> Self {
243        self.flags = flags;
244        self
245    }
246
247    /// Set common item flags for this plot item (applies to all plot types)
248    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
249        self.item_flags = flags;
250        self
251    }
252
253    /// Make text vertical
254    pub fn vertical(mut self) -> Self {
255        self.flags |= TextFlags::VERTICAL;
256        self
257    }
258
259    /// Validate the plot data
260    pub fn validate(&self) -> Result<(), PlotError> {
261        if self.text.is_empty() {
262            return Err(PlotError::InvalidData("Text cannot be empty".to_string()));
263        }
264        if self.text.contains('\0') {
265            return Err(PlotError::StringConversion(
266                "text contained null byte".to_string(),
267            ));
268        }
269        Ok(())
270    }
271
272    /// Plot the formatted text
273    pub fn plot(self) {
274        let pix_offset = sys::ImVec2_c {
275            x: self.pix_offset_x as f32,
276            y: self.pix_offset_y as f32,
277        };
278        let _ = with_plot_str(&self.text, |text_ptr| unsafe {
279            let spec = plot_spec_from(
280                self.flags.bits() | self.item_flags.bits(),
281                0,
282                crate::IMPLOT_AUTO,
283            );
284            sys::ImPlot_PlotText(text_ptr, self.x, self.y, pix_offset, spec);
285        });
286    }
287}
288
289impl PlotData for FormattedTextPlot {
290    fn label(&self) -> &str {
291        &self.text
292    }
293
294    fn data_len(&self) -> usize {
295        1
296    }
297}
298
299/// Convenience macro for creating formatted text plots
300#[macro_export]
301macro_rules! plot_text {
302    ($x:expr, $y:expr, $($arg:tt)*) => {
303        $crate::plots::text::FormattedTextPlot::from_format($x, $y, format_args!($($arg)*))
304    };
305}
306
307/// Text annotation with automatic positioning
308pub struct TextAnnotation<'a> {
309    text: &'a str,
310    x: f64,
311    y: f64,
312    auto_offset: bool,
313    flags: TextFlags,
314    item_flags: ItemFlags,
315}
316
317impl<'a> TextAnnotation<'a> {
318    /// Create a new text annotation
319    pub fn new(text: &'a str, x: f64, y: f64) -> Self {
320        Self {
321            text,
322            x,
323            y,
324            auto_offset: true,
325            flags: TextFlags::NONE,
326            item_flags: ItemFlags::NONE,
327        }
328    }
329
330    /// Disable automatic offset calculation
331    pub fn no_auto_offset(mut self) -> Self {
332        self.auto_offset = false;
333        self
334    }
335
336    /// Set text flags
337    pub fn with_flags(mut self, flags: TextFlags) -> Self {
338        self.flags = flags;
339        self
340    }
341
342    /// Set common item flags for the annotation text
343    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
344        self.item_flags = flags;
345        self
346    }
347
348    /// Make text vertical
349    pub fn vertical(mut self) -> Self {
350        self.flags |= TextFlags::VERTICAL;
351        self
352    }
353
354    /// Plot the annotation
355    pub fn plot(self) {
356        let offset = if self.auto_offset {
357            // Simple auto-offset logic - could be enhanced
358            (5.0, -5.0)
359        } else {
360            (0.0, 0.0)
361        };
362
363        let text_plot = TextPlot::new(self.text, self.x, self.y)
364            .with_pixel_offset(offset.0, offset.1)
365            .with_flags(self.flags)
366            .with_item_flags(self.item_flags);
367
368        text_plot.plot();
369    }
370}