Skip to main content

dear_implot/plots/
text.rs

1//! Text plot implementation
2
3use super::{
4    PlotData, PlotDataLayout, PlotError, PlotItemStyle, PlotItemStyled, plot_spec_with_style,
5    with_plot_str,
6};
7use crate::{ItemFlags, TextFlags, sys};
8
9/// Builder for text plots with extensive customization options
10///
11/// Text plots allow placing text labels at specific coordinates in the plot area.
12pub struct TextPlot<'a> {
13    text: &'a str,
14    x: f64,
15    y: f64,
16    style: PlotItemStyle,
17    pix_offset_x: f64,
18    pix_offset_y: f64,
19    flags: TextFlags,
20    item_flags: ItemFlags,
21}
22
23impl<'a> super::PlotItemStyled for TextPlot<'a> {
24    fn style_mut(&mut self) -> &mut PlotItemStyle {
25        &mut self.style
26    }
27}
28
29impl<'a> TextPlot<'a> {
30    /// Create a new text plot with the given text and position
31    pub fn new(text: &'a str, x: f64, y: f64) -> Self {
32        Self {
33            text,
34            x,
35            y,
36            style: PlotItemStyle::default(),
37            pix_offset_x: 0.0,
38            pix_offset_y: 0.0,
39            flags: TextFlags::NONE,
40            item_flags: ItemFlags::NONE,
41        }
42    }
43
44    /// Set pixel offset for fine positioning
45    pub fn with_pixel_offset(mut self, offset_x: f64, offset_y: f64) -> Self {
46        self.pix_offset_x = offset_x;
47        self.pix_offset_y = offset_y;
48        self
49    }
50
51    /// Set text flags for customization
52    pub fn with_flags(mut self, flags: TextFlags) -> Self {
53        self.flags = flags;
54        self
55    }
56
57    /// Set common item flags for this plot item (applies to all plot types)
58    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
59        self.item_flags = flags;
60        self
61    }
62
63    /// Make text vertical instead of horizontal
64    pub fn vertical(mut self) -> Self {
65        self.flags |= TextFlags::VERTICAL;
66        self
67    }
68
69    /// Validate the plot data
70    pub fn validate(&self) -> Result<(), PlotError> {
71        if self.text.is_empty() {
72            return Err(PlotError::InvalidData("Text cannot be empty".to_string()));
73        }
74        if self.text.contains('\0') {
75            return Err(PlotError::StringConversion(
76                "text contained null byte".to_string(),
77            ));
78        }
79        Ok(())
80    }
81
82    /// Plot the text
83    pub fn plot(self) {
84        let pix_offset = sys::ImVec2_c {
85            x: self.pix_offset_x as f32,
86            y: self.pix_offset_y as f32,
87        };
88        let _ = with_plot_str(self.text, |text_ptr| unsafe {
89            let spec = plot_spec_with_style(
90                self.style,
91                self.flags.bits() | self.item_flags.bits(),
92                PlotDataLayout::DEFAULT,
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                PlotDataLayout::DEFAULT,
311            );
312            sys::ImPlot_PlotText(text_ptr, self.x, self.y, pix_offset, spec);
313        });
314    }
315}
316
317impl PlotData for FormattedTextPlot {
318    fn label(&self) -> &str {
319        &self.text
320    }
321
322    fn data_len(&self) -> usize {
323        1
324    }
325}
326
327/// Convenience macro for creating formatted text plots
328#[macro_export]
329macro_rules! plot_text {
330    ($x:expr, $y:expr, $($arg:tt)*) => {
331        $crate::plots::text::FormattedTextPlot::from_format($x, $y, format_args!($($arg)*))
332    };
333}
334
335/// Text annotation with automatic positioning
336pub struct TextAnnotation<'a> {
337    text: &'a str,
338    x: f64,
339    y: f64,
340    style: PlotItemStyle,
341    auto_offset: bool,
342    flags: TextFlags,
343    item_flags: ItemFlags,
344}
345
346impl<'a> super::PlotItemStyled for TextAnnotation<'a> {
347    fn style_mut(&mut self) -> &mut PlotItemStyle {
348        &mut self.style
349    }
350}
351
352impl<'a> TextAnnotation<'a> {
353    /// Create a new text annotation
354    pub fn new(text: &'a str, x: f64, y: f64) -> Self {
355        Self {
356            text,
357            x,
358            y,
359            style: PlotItemStyle::default(),
360            auto_offset: true,
361            flags: TextFlags::NONE,
362            item_flags: ItemFlags::NONE,
363        }
364    }
365
366    /// Disable automatic offset calculation
367    pub fn no_auto_offset(mut self) -> Self {
368        self.auto_offset = false;
369        self
370    }
371
372    /// Set text flags
373    pub fn with_flags(mut self, flags: TextFlags) -> Self {
374        self.flags = flags;
375        self
376    }
377
378    /// Set common item flags for the annotation text
379    pub fn with_item_flags(mut self, flags: ItemFlags) -> Self {
380        self.item_flags = flags;
381        self
382    }
383
384    /// Make text vertical
385    pub fn vertical(mut self) -> Self {
386        self.flags |= TextFlags::VERTICAL;
387        self
388    }
389
390    /// Plot the annotation
391    pub fn plot(self) {
392        let offset = if self.auto_offset {
393            // Simple auto-offset logic - could be enhanced
394            (5.0, -5.0)
395        } else {
396            (0.0, 0.0)
397        };
398
399        let text_plot = TextPlot::new(self.text, self.x, self.y)
400            .with_style(self.style)
401            .with_pixel_offset(offset.0, offset.1)
402            .with_flags(self.flags)
403            .with_item_flags(self.item_flags);
404
405        text_plot.plot();
406    }
407}