Skip to main content

egui_charts/model/
markers.rs

1//! Marker API for custom chart annotations
2//!
3//! Markers are visual indicators that can be placed at specific data points on the chart.
4//! Common use cases: buy/sell signals, events, annotations, alerts, etc.
5
6use crate::tokens::DESIGN_TOKENS;
7use chrono::{DateTime, Utc};
8use egui::Color32;
9use serde::{Deserialize, Serialize};
10
11/// Marker shape
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
13pub enum MarkerShape {
14    /// Circle marker - filled circle
15    #[default]
16    Circle,
17    /// Square marker - filled square
18    Square,
19    /// Arrow up marker - points upward
20    ArrowUp,
21    /// Arrow down marker - points downward
22    ArrowDown,
23    /// Triangle up marker - points upward (hollow)
24    TriangleUp,
25    /// Triangle down marker - points downward (hollow)
26    TriangleDown,
27    /// Diamond marker - filled diamond shape
28    Diamond,
29    /// Star marker
30    Star,
31    /// Cross marker (X shape)
32    Cross,
33    /// Flag marker - for marking important events
34    Flag,
35}
36
37/// Marker position relative to the data point
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
39pub enum MarkerPos {
40    /// Above the high price
41    #[default]
42    AboveBar,
43    /// Below the low price
44    BelowBar,
45    /// At the data point (on the candle/bar)
46    InBar,
47    /// At the top of the chart pane
48    Top,
49    /// At the bottom of the chart pane
50    Bottom,
51    /// At the left edge of the chart
52    Left,
53    /// At the right edge of the chart
54    Right,
55    /// Absolute pixel position (up arrow)
56    AbsoluteUp,
57    /// Absolute pixel position (down arrow)
58    AbsoluteDown,
59    /// Absolute pixel position (centered)
60    Absolute,
61    /// Just above [`Marker::price`], the shape's lower edge on the price.
62    AtPriceTop,
63    /// Just below [`Marker::price`], the shape's upper edge on the price.
64    AtPriceBottom,
65    /// Centred on [`Marker::price`].
66    AtPriceMiddle,
67}
68
69/// Where a series' markers are painted among the other content of the price pane.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
71pub enum MarkerZOrder {
72    /// With their series: over it, under the series added after it and under the studies.
73    #[default]
74    Normal,
75    /// Over every series and study, under the price lines and drawings.
76    AboveSeries,
77    /// Over the series, studies, price lines and drawings.
78    Top,
79}
80
81/// How the markers of one series are fitted and layered; see `Chart::set_marker_options`.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
83pub struct MarkerOptions {
84    /// Whether visible price-relative markers widen the autoscaled range to fit their shapes
85    /// and text at the configured size, when the pane has enough height. Pane-edge positions
86    /// and manual ranges are unchanged. Default true.
87    pub auto_scale: bool,
88    /// Default [`MarkerZOrder::Normal`].
89    pub z_order: MarkerZOrder,
90}
91
92impl Default for MarkerOptions {
93    fn default() -> Self {
94        Self {
95            auto_scale: true,
96            z_order: MarkerZOrder::default(),
97        }
98    }
99}
100
101/// Marker for chart annotations
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct Marker {
104    /// Time of the marker (matches a data point)
105    pub time: DateTime<Utc>,
106    /// Pos relative to the bar
107    pub position: MarkerPos,
108    /// Shape of the marker
109    pub shape: MarkerShape,
110    /// Color of the marker; `PLACEHOLDER`, the default of [`Marker::new`], uses the theme's text
111    /// colour.
112    #[serde(default)]
113    pub color: Color32,
114    /// Optional text label
115    pub text: Option<String>,
116    /// Optional tooltip (shown on hover)
117    pub tooltip: Option<String>,
118    /// Size multiplier (1.0 = normal size)
119    pub size: f32,
120    /// Unique ID for the marker (for removal/editing)
121    pub id: Option<String>,
122    /// Price of the `AtPrice` positions, in the series' units; without one they use the close.
123    pub price: Option<f64>,
124}
125
126impl Marker {
127    /// Create a new marker at the given time
128    pub fn new(time: DateTime<Utc>) -> Self {
129        Self {
130            time,
131            position: MarkerPos::default(),
132            shape: MarkerShape::default(),
133            color: Color32::PLACEHOLDER,
134            text: None,
135            tooltip: None,
136            size: 1.0,
137            id: None,
138            price: None,
139        }
140    }
141
142    /// Set the position
143    #[must_use]
144    pub fn with_pos(mut self, position: MarkerPos) -> Self {
145        self.position = position;
146        self
147    }
148
149    /// Set the shape
150    #[must_use]
151    pub fn with_shape(mut self, shape: MarkerShape) -> Self {
152        self.shape = shape;
153        self
154    }
155
156    /// Set the color
157    #[must_use]
158    pub fn with_color(mut self, color: Color32) -> Self {
159        self.color = color;
160        self
161    }
162
163    /// Set the text label
164    #[must_use]
165    pub fn with_text(mut self, text: impl Into<String>) -> Self {
166        self.text = Some(text.into());
167        self
168    }
169
170    /// Set the tooltip
171    #[must_use]
172    pub fn with_tooltip(mut self, tooltip: impl Into<String>) -> Self {
173        self.tooltip = Some(tooltip.into());
174        self
175    }
176
177    /// Set the size
178    #[must_use]
179    pub fn with_size(mut self, size: f32) -> Self {
180        self.size = size;
181        self
182    }
183
184    /// Set the ID
185    #[must_use]
186    pub fn with_id(mut self, id: impl Into<String>) -> Self {
187        self.id = Some(id.into());
188        self
189    }
190
191    /// Anchors the marker to `price` at `position`, one of the `AtPrice` positions.
192    #[must_use]
193    pub fn at_price(mut self, price: f64, position: MarkerPos) -> Self {
194        self.price = Some(price);
195        self.position = position;
196        self
197    }
198
199    /// Quick constructor for a buy signal marker (green arrow up)
200    pub fn buy_signal(time: DateTime<Utc>) -> Self {
201        Self::new(time)
202            .with_shape(MarkerShape::ArrowUp)
203            .with_pos(MarkerPos::BelowBar)
204            .with_color(DESIGN_TOKENS.semantic.extended.bullish)
205            .with_text("BUY")
206    }
207
208    /// Quick constructor for a sell signal marker (red arrow down)
209    pub fn sell_signal(time: DateTime<Utc>) -> Self {
210        Self::new(time)
211            .with_shape(MarkerShape::ArrowDown)
212            .with_pos(MarkerPos::AboveBar)
213            .with_color(DESIGN_TOKENS.semantic.extended.bearish)
214            .with_text("SELL")
215    }
216
217    /// Quick constructor for an event marker (blue flag)
218    pub fn event(time: DateTime<Utc>, text: impl Into<String>) -> Self {
219        Self::new(time)
220            .with_shape(MarkerShape::Flag)
221            .with_pos(MarkerPos::AboveBar)
222            .with_color(DESIGN_TOKENS.semantic.extended.info)
223            .with_text(text)
224    }
225}
226
227/// Colours and lifetime of the marks that show where the close of a series rose or fell
228/// between live updates; see `Chart::set_change_markers`.
229#[derive(Debug, Clone, Copy, PartialEq)]
230pub struct ChangeMarkers {
231    /// Colour of a rise.
232    pub up_color: Color32,
233    /// Colour of a fall.
234    pub down_color: Color32,
235    /// How long a mark fades linearly from its configured colour to transparent after a
236    /// changed close. Another change to the same bar restarts the fade. Zero hides the mark.
237    /// Default 5 seconds.
238    pub duration: std::time::Duration,
239}
240
241impl Default for ChangeMarkers {
242    fn default() -> Self {
243        Self {
244            up_color: DESIGN_TOKENS.semantic.extended.bullish,
245            down_color: DESIGN_TOKENS.semantic.extended.bearish,
246            duration: std::time::Duration::from_secs(5),
247        }
248    }
249}
250
251/// Line style of drawings, series, grid and price lines.
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
253pub enum LineStyle {
254    /// A continuous line.
255    #[default]
256    Solid,
257    /// Short dashes.
258    Dashed,
259    /// Closely spaced dots.
260    Dotted,
261    /// Dashes twice as long as [`Dashed`](Self::Dashed), with twice the gap.
262    LargeDashed,
263    /// Dots with twice the spacing of [`Dotted`](Self::Dotted).
264    SparseDotted,
265}
266
267impl LineStyle {
268    /// Get the dash pattern for rendering
269    /// Returns (dash_len, gap_len) or None for solid
270    #[inline]
271    pub fn dash_pattern(&self) -> Option<(f32, f32)> {
272        match self {
273            LineStyle::Solid => None,
274            LineStyle::Dashed => Some((8.0, 4.0)),
275            LineStyle::Dotted => Some((2.0, 3.0)),
276            LineStyle::LargeDashed => Some((16.0, 8.0)),
277            LineStyle::SparseDotted => Some((2.0, 8.0)),
278        }
279    }
280
281    /// The dash pattern of one-pixel lines such as price, grid and crosshair lines, or `None`
282    /// for solid. Their dots are one pixel long.
283    #[cfg(feature = "widget")]
284    #[inline]
285    pub(crate) fn hairline_pattern(&self) -> Option<(f32, f32)> {
286        match self {
287            LineStyle::Solid => None,
288            LineStyle::Dashed => Some((4.0, 4.0)),
289            LineStyle::Dotted => Some((1.0, 3.0)),
290            LineStyle::LargeDashed => Some((8.0, 8.0)),
291            LineStyle::SparseDotted => Some((1.0, 7.0)),
292        }
293    }
294
295    /// All available line styles
296    pub fn all() -> &'static [LineStyle] {
297        &[
298            LineStyle::Solid,
299            LineStyle::Dashed,
300            LineStyle::Dotted,
301            LineStyle::LargeDashed,
302            LineStyle::SparseDotted,
303        ]
304    }
305
306    /// The style's name in settings menus.
307    pub fn name(&self) -> &'static str {
308        match self {
309            LineStyle::Solid => "Solid",
310            LineStyle::Dashed => "Dashed",
311            LineStyle::Dotted => "Dotted",
312            LineStyle::LargeDashed => "Large dashed",
313            LineStyle::SparseDotted => "Sparse dotted",
314        }
315    }
316}
317
318/// A horizontal line at a price on a series' scale, with an optional title and a price axis label.
319#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
320pub struct PriceLine {
321    /// Price in the series' units.
322    pub price: f64,
323    /// Colour of the line, title and axis tag.
324    pub color: Color32,
325    /// Width in points. Default 1.0.
326    pub width: f32,
327    /// Default [`LineStyle::Dashed`].
328    pub style: LineStyle,
329    /// Drawn at the left end of the line, above it.
330    pub title: Option<String>,
331    /// Price label on the axis, on a tag in the line's colour. Default true.
332    pub axis_label: bool,
333    /// Fill of the axis tag; `None` uses [`color`](Self::color).
334    pub axis_label_color: Option<Color32>,
335    /// Text of the axis tag; `None` picks white or black, whichever reads on the fill.
336    pub axis_label_text_color: Option<Color32>,
337    /// Colour of the title; `None` uses [`color`](Self::color).
338    pub title_color: Option<Color32>,
339}
340
341impl PriceLine {
342    /// Creates a dashed price line with an axis label.
343    pub fn new(price: f64, color: Color32) -> Self {
344        Self {
345            price,
346            color,
347            width: 1.0,
348            style: LineStyle::Dashed,
349            title: None,
350            axis_label: true,
351            axis_label_color: None,
352            axis_label_text_color: None,
353            title_color: None,
354        }
355    }
356
357    /// Sets the width in points.
358    #[must_use]
359    pub fn with_width(mut self, width: f32) -> Self {
360        self.width = width;
361        self
362    }
363
364    /// Sets the line style.
365    #[must_use]
366    pub fn with_style(mut self, style: LineStyle) -> Self {
367        self.style = style;
368        self
369    }
370
371    /// Sets the title drawn just above the line's left end.
372    #[must_use]
373    pub fn with_title(mut self, title: impl Into<String>) -> Self {
374        self.title = Some(title.into());
375        self
376    }
377
378    /// Sets whether to draw the price axis label.
379    #[must_use]
380    pub fn with_axis_label(mut self, show: bool) -> Self {
381        self.axis_label = show;
382        self
383    }
384
385    /// Sets the fill and the text colour of the axis tag.
386    #[must_use]
387    pub fn with_axis_label_colors(mut self, fill: Color32, text: Color32) -> Self {
388        self.axis_label_color = Some(fill);
389        self.axis_label_text_color = Some(text);
390        self
391    }
392
393    /// Sets the colour of the title.
394    #[must_use]
395    pub fn with_title_color(mut self, color: Color32) -> Self {
396        self.title_color = Some(color);
397        self
398    }
399}