Skip to main content

dear_implot/plots/
text.rs

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