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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
// SPDX-License-Identifier: MIT OR Apache-2.0
use core::{ops::Deref, ptr::null_mut};
use oxivgl_sys::*;
use super::{
WidgetError,
obj::{AsLvHandle, Obj},
};
/// Type-safe wrapper for `lv_chart_type_t`.
#[repr(u32)]
#[derive(Clone, Copy, Debug)]
pub enum ChartType {
/// Don't draw the series.
None = lv_chart_type_t_LV_CHART_TYPE_NONE,
/// Connect points with lines.
Line = lv_chart_type_t_LV_CHART_TYPE_LINE,
/// Connect points with smooth curves.
Curve = lv_chart_type_t_LV_CHART_TYPE_CURVE,
/// Draw columns.
Bar = lv_chart_type_t_LV_CHART_TYPE_BAR,
/// Stacked bars.
Stacked = lv_chart_type_t_LV_CHART_TYPE_STACKED,
/// Draw points and lines in 2D (x,y coordinates).
Scatter = lv_chart_type_t_LV_CHART_TYPE_SCATTER,
}
/// Chart data update mode.
#[repr(u32)]
#[derive(Clone, Copy, Debug)]
pub enum ChartUpdateMode {
/// Shift old data left, add new at end.
Shift = lv_chart_update_mode_t_LV_CHART_UPDATE_MODE_SHIFT,
/// Circular — overwrite oldest points.
Circular = lv_chart_update_mode_t_LV_CHART_UPDATE_MODE_CIRCULAR,
}
/// Type-safe wrapper for `lv_chart_axis_t`.
#[repr(u32)]
#[derive(Clone, Copy, Debug)]
pub enum ChartAxis {
/// Primary Y axis.
PrimaryY = 0x00,
/// Secondary Y axis.
SecondaryY = 0x01,
/// Primary X axis.
PrimaryX = 0x02,
/// Secondary X axis.
SecondaryX = 0x04,
}
/// Opaque handle to a chart data series.
///
/// Returned by [`Chart::add_series`]. The series is owned by LVGL and freed
/// when the parent chart is deleted.
#[derive(Debug)]
pub struct ChartSeries {
ptr: *mut lv_chart_series_t,
}
/// Opaque handle to a chart cursor.
///
/// Returned by [`Chart::add_cursor`]. The cursor is owned by LVGL and freed
/// when the parent chart is deleted.
#[derive(Debug)]
pub struct ChartCursor {
ptr: *mut lv_chart_cursor_t,
}
/// Sentinel value indicating "no point" (e.g., gap in data).
/// Equivalent to LVGL's `LV_CHART_POINT_NONE` (0x7FFFFFFF).
pub const CHART_POINT_NONE: u32 = 0x7FFF_FFFF;
/// LVGL chart widget — line, bar, or scatter plots.
#[derive(Debug)]
pub struct Chart<'p> {
obj: Obj<'p>,
}
impl<'p> AsLvHandle for Chart<'p> {
fn lv_handle(&self) -> *mut lv_obj_t {
self.obj.lv_handle()
}
}
impl<'p> Deref for Chart<'p> {
type Target = Obj<'p>;
fn deref(&self) -> &Obj<'p> {
&self.obj
}
}
impl<'p> Chart<'p> {
/// Create a new chart widget.
pub fn new(parent: &impl AsLvHandle) -> Result<Self, WidgetError> {
let parent_ptr = parent.lv_handle();
assert_ne!(parent_ptr, null_mut(), "Parent widget cannot be null");
// SAFETY: parent_ptr non-null (asserted above); lv_init() called via
// LvglDriver.
let handle = unsafe { lv_chart_create(parent_ptr) };
if handle.is_null() { Err(WidgetError::LvglNullPointer) } else { Ok(Chart { obj: Obj::from_raw(handle) }) }
}
/// Set the chart type (line, bar, scatter, or none).
pub fn set_type(&self, t: ChartType) -> &Self {
// SAFETY: lv_handle() is non-null (checked in new()).
unsafe { lv_chart_set_type(self.lv_handle(), t as lv_chart_type_t) };
self
}
/// Set the number of data points per series.
pub fn set_point_count(&self, count: u32) -> &Self {
// SAFETY: lv_handle() is non-null (checked in new()).
unsafe { lv_chart_set_point_count(self.lv_handle(), count) };
self
}
/// Set the value range for a given axis.
pub fn set_axis_range(&self, axis: ChartAxis, min: i32, max: i32) -> &Self {
// SAFETY: lv_handle() is non-null (checked in new()).
unsafe { lv_chart_set_axis_range(self.lv_handle(), axis as lv_chart_axis_t, min, max) };
self
}
/// Add a data series bound to the given axis. Returns a handle for
/// subsequent value operations.
pub fn add_series(&self, color: lv_color_t, axis: ChartAxis) -> ChartSeries {
// SAFETY: lv_handle() is non-null (checked in new()).
let ptr = unsafe { lv_chart_add_series(self.lv_handle(), color, axis as lv_chart_axis_t) };
assert!(!ptr.is_null(), "lv_chart_add_series returned NULL");
ChartSeries { ptr }
}
/// Set a specific point's x/y values by index (scatter plots).
pub fn set_series_value_by_id2(&self, series: &ChartSeries, id: u32, x: i32, y: i32) -> &Self {
// SAFETY: lv_handle() and series.ptr are non-null (created by LVGL).
unsafe { lv_chart_set_series_value_by_id2(self.lv_handle(), series.ptr, id, x, y) };
self
}
/// Append the next value to a series (shift mode).
pub fn set_next_value(&self, series: &ChartSeries, value: i32) -> &Self {
// SAFETY: lv_handle() and series.ptr are non-null (created by LVGL).
unsafe { lv_chart_set_next_value(self.lv_handle(), series.ptr, value) };
self
}
/// Append the next x/y value pair to a series (scatter, shift mode).
pub fn set_next_value2(&self, series: &ChartSeries, x: i32, y: i32) -> &Self {
// SAFETY: lv_handle() and series.ptr are non-null (created by LVGL).
unsafe { lv_chart_set_next_value2(self.lv_handle(), series.ptr, x, y) };
self
}
/// Set a specific point's Y value by index.
pub fn set_series_value_by_id(&self, series: &ChartSeries, id: u32, value: i32) -> &Self {
// SAFETY: lv_handle() and series.ptr are non-null (created by LVGL).
unsafe { lv_chart_set_series_value_by_id(self.lv_handle(), series.ptr, id, value) };
self
}
/// Get a mutable pointer to the Y data array for a series.
///
/// # Safety
/// The returned pointer is valid for `get_point_count()` elements. The
/// caller must not write beyond the array bounds or use the pointer after
/// the chart or series is freed.
pub unsafe fn get_series_y_array(&self, series: &ChartSeries) -> *mut i32 {
// SAFETY: lv_handle() and series.ptr are non-null (created by LVGL).
unsafe { lv_chart_get_series_y_array(self.lv_handle(), series.ptr) }
}
/// Get the pixel offset of the first data point from the chart edge.
pub fn get_first_point_center_offset(&self) -> i32 {
// SAFETY: lv_handle() is non-null (checked in new()).
unsafe { lv_chart_get_first_point_center_offset(self.lv_handle()) }
}
/// Get the current number of data points per series.
pub fn get_point_count(&self) -> u32 {
// SAFETY: lv_handle() is non-null (checked in new()).
unsafe { lv_chart_get_point_count(self.lv_handle()) }
}
/// Set the number of horizontal and vertical division lines.
pub fn set_div_line_count(&self, hdiv: u32, vdiv: u32) -> &Self {
// SAFETY: lv_handle() is non-null (checked in new()).
unsafe { lv_chart_set_div_line_count(self.lv_handle(), hdiv, vdiv) };
self
}
/// Set the update mode (shift or circular).
pub fn set_update_mode(&self, mode: ChartUpdateMode) -> &Self {
// SAFETY: lv_handle() is non-null (checked in new()).
unsafe { lv_chart_set_update_mode(self.lv_handle(), mode as lv_chart_update_mode_t) };
self
}
/// Get the start-point index for a series (circular mode).
pub fn get_x_start_point(&self, series: &ChartSeries) -> u32 {
// SAFETY: lv_handle() and series.ptr are non-null.
unsafe { lv_chart_get_x_start_point(self.lv_handle(), series.ptr) }
}
/// Add a cursor crosshair to the chart.
pub fn add_cursor(&self, color: lv_color_t, dir: u32) -> ChartCursor {
// SAFETY: lv_handle() is non-null.
let ptr = unsafe { lv_chart_add_cursor(self.lv_handle(), color, dir) };
assert!(!ptr.is_null(), "lv_chart_add_cursor returned NULL");
ChartCursor { ptr }
}
/// Move a cursor to the given point index.
///
/// Pass `series` as `None` to use the first series.
pub fn set_cursor_point(&self, cursor: &ChartCursor, series: Option<&ChartSeries>, point_id: u32) -> &Self {
let ser_ptr = match series {
Some(s) => s.ptr,
None => core::ptr::null_mut(),
};
// SAFETY: lv_handle() and cursor.ptr are non-null.
unsafe { lv_chart_set_cursor_point(self.lv_handle(), cursor.ptr, ser_ptr, point_id) };
self
}
/// Get the index of the last pressed data point.
///
/// Returns `None` if no point is pressed (`LV_CHART_POINT_NONE`).
pub fn get_pressed_point(&self) -> Option<u32> {
// SAFETY: lv_handle() is non-null.
let id = unsafe { lv_chart_get_pressed_point(self.lv_handle()) };
if id == CHART_POINT_NONE { None } else { Some(id) }
}
/// Get the next series after `prev`, or the first series if `prev` is `None`.
pub fn get_series_next(&self, prev: Option<&ChartSeries>) -> Option<ChartSeries> {
let prev_ptr = match prev {
Some(s) => s.ptr,
None => core::ptr::null_mut(),
};
// SAFETY: lv_handle() is non-null.
let ptr = unsafe { lv_chart_get_series_next(self.lv_handle(), prev_ptr) };
if ptr.is_null() { None } else { Some(ChartSeries { ptr }) }
}
/// Get the current chart type.
pub fn get_type(&self) -> ChartType {
// SAFETY: lv_handle() is non-null (checked in new()).
let raw = unsafe { lv_chart_get_type(self.lv_handle()) };
match raw {
x if x == lv_chart_type_t_LV_CHART_TYPE_LINE => ChartType::Line,
x if x == lv_chart_type_t_LV_CHART_TYPE_CURVE => ChartType::Curve,
x if x == lv_chart_type_t_LV_CHART_TYPE_BAR => ChartType::Bar,
x if x == lv_chart_type_t_LV_CHART_TYPE_STACKED => ChartType::Stacked,
x if x == lv_chart_type_t_LV_CHART_TYPE_SCATTER => ChartType::Scatter,
_ => ChartType::None,
}
}
/// Get the current data update mode.
pub fn get_update_mode(&self) -> ChartUpdateMode {
// SAFETY: lv_handle() is non-null (checked in new()).
let raw = unsafe { lv_chart_get_update_mode(self.lv_handle()) };
if raw == lv_chart_update_mode_t_LV_CHART_UPDATE_MODE_CIRCULAR {
ChartUpdateMode::Circular
} else {
ChartUpdateMode::Shift
}
}
/// Get the number of horizontal division lines.
pub fn get_hor_div_line_count(&self) -> u32 {
// SAFETY: lv_handle() is non-null (checked in new()).
unsafe { lv_chart_get_hor_div_line_count(self.lv_handle()) }
}
/// Get the number of vertical division lines.
pub fn get_ver_div_line_count(&self) -> u32 {
// SAFETY: lv_handle() is non-null (checked in new()).
unsafe { lv_chart_get_ver_div_line_count(self.lv_handle()) }
}
/// Refresh the chart — call after externally modifying series data.
pub fn refresh(&self) -> &Self {
// SAFETY: lv_handle() is non-null (checked in new()).
unsafe { lv_chart_refresh(self.lv_handle()) };
self
}
}