1 /**
2 * @file lv_chart.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_chart.h"
10 #if LV_USE_CHART != 0
11
12 #include "../lv_misc/lv_debug.h"
13 #include "../lv_core/lv_refr.h"
14 #include "../lv_draw/lv_draw.h"
15 #include "../lv_misc/lv_math.h"
16 #include "../lv_themes/lv_theme.h"
17
18 /*********************
19 * DEFINES
20 *********************/
21 #define LV_OBJX_NAME "lv_chart"
22
23 #define LV_CHART_YMIN_DEF 0
24 #define LV_CHART_YMAX_DEF 100
25 #define LV_CHART_HDIV_DEF 3
26 #define LV_CHART_VDIV_DEF 5
27 #define LV_CHART_PNUM_DEF 10
28 #define LV_CHART_AXIS_MAJOR_TICK_LEN_COE 1 / 15
29 #define LV_CHART_AXIS_MINOR_TICK_LEN_COE 2 / 3
30 #define LV_CHART_LABEL_ITERATOR_FORWARD 1
31 #define LV_CHART_LABEL_ITERATOR_REVERSE 0
32
33 /**********************
34 * TYPEDEFS
35 **********************/
36
37 typedef struct {
38 const char * list_start;
39 const char * current_pos;
40 uint8_t items_left;
41 uint8_t is_reverse_iter;
42 } lv_chart_label_iterator_t;
43
44 /**********************
45 * STATIC PROTOTYPES
46 **********************/
47 static lv_design_res_t lv_chart_design(lv_obj_t * chart, const lv_area_t * clip_area, lv_design_mode_t mode);
48 static lv_res_t lv_chart_signal(lv_obj_t * chart, lv_signal_t sign, void * param);
49 static lv_style_list_t * lv_chart_get_style(lv_obj_t * chart, uint8_t part);
50
51 static void draw_series_bg(lv_obj_t * chart, const lv_area_t * series_area, const lv_area_t * mask);
52 static void draw_series_line(lv_obj_t * chart, const lv_area_t * series_area, const lv_area_t * clip_area);
53 static void draw_series_column(lv_obj_t * chart, const lv_area_t * series_area, const lv_area_t * clip_area);
54 static void draw_axes(lv_obj_t * chart, const lv_area_t * series_area, const lv_area_t * mask);
55 static void invalidate_lines(lv_obj_t * chart, uint16_t i);
56 static void invalidate_columns(lv_obj_t * chart, uint16_t i);
57 static void get_series_area(lv_obj_t * chart, lv_area_t * series_area);
58 static void get_next_axis_label(lv_chart_label_iterator_t * iterator, char * buf);
59 static inline bool is_tick_with_label(uint8_t tick_num, lv_chart_axis_cfg_t * axis);
60 static lv_chart_label_iterator_t create_axis_label_iter(const char * list, uint8_t iterator_dir);
61
62 /**********************
63 * STATIC VARIABLES
64 **********************/
65 static lv_design_cb_t ancestor_design;
66 static lv_signal_cb_t ancestor_signal;
67
68 /**********************
69 * MACROS
70 **********************/
71
72 /**********************
73 * GLOBAL FUNCTIONS
74 **********************/
75
76 /**
77 * Create a chart background objects
78 * @param par pointer to an object, it will be the parent of the new chart background
79 * @param copy pointer to a chart background object, if not NULL then the new object will be copied
80 * from it
81 * @return pointer to the created chart background
82 */
lv_chart_create(lv_obj_t * par,const lv_obj_t * copy)83 lv_obj_t * lv_chart_create(lv_obj_t * par, const lv_obj_t * copy)
84 {
85 LV_LOG_TRACE("chart create started");
86
87 /*Create the ancestor basic object*/
88 lv_obj_t * chart = lv_obj_create(par, copy);
89 LV_ASSERT_MEM(chart);
90 if(chart == NULL) return NULL;
91
92 /*Allocate the object type specific extended data*/
93 lv_chart_ext_t * ext = lv_obj_allocate_ext_attr(chart, sizeof(lv_chart_ext_t));
94 LV_ASSERT_MEM(ext);
95 if(ext == NULL) {
96 lv_obj_del(chart);
97 return NULL;
98 }
99
100 _lv_ll_init(&ext->series_ll, sizeof(lv_chart_series_t));
101
102 uint8_t i;
103 for(i = 0; i < _LV_CHART_AXIS_LAST; i++) {
104 ext->ymin[i] = LV_CHART_YMIN_DEF;
105 ext->ymax[i] = LV_CHART_YMAX_DEF;
106 }
107
108 ext->hdiv_cnt = LV_CHART_HDIV_DEF;
109 ext->vdiv_cnt = LV_CHART_VDIV_DEF;
110 ext->point_cnt = LV_CHART_PNUM_DEF;
111 ext->type = LV_CHART_TYPE_LINE;
112 ext->update_mode = LV_CHART_UPDATE_MODE_SHIFT;
113 _lv_memset_00(&ext->x_axis, sizeof(ext->x_axis));
114 _lv_memset_00(&ext->y_axis, sizeof(ext->y_axis));
115 _lv_memset_00(&ext->secondary_y_axis, sizeof(ext->secondary_y_axis));
116 ext->x_axis.major_tick_len = LV_CHART_TICK_LENGTH_AUTO;
117 ext->x_axis.minor_tick_len = LV_CHART_TICK_LENGTH_AUTO;
118 ext->y_axis.major_tick_len = LV_CHART_TICK_LENGTH_AUTO;
119 ext->y_axis.minor_tick_len = LV_CHART_TICK_LENGTH_AUTO;
120 ext->secondary_y_axis.major_tick_len = LV_CHART_TICK_LENGTH_AUTO;
121 ext->secondary_y_axis.minor_tick_len = LV_CHART_TICK_LENGTH_AUTO;
122
123 lv_style_list_init(&ext->style_series_bg);
124 lv_style_list_init(&ext->style_series);
125
126 if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(chart);
127 if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(chart);
128
129 lv_obj_set_signal_cb(chart, lv_chart_signal);
130 lv_obj_set_design_cb(chart, lv_chart_design);
131
132 /*Init the new chart background object*/
133 if(copy == NULL) {
134 lv_obj_set_size(chart, LV_DPI * 3, LV_DPI * 2);
135
136 lv_theme_apply(chart, LV_THEME_CHART);
137 }
138 else {
139 lv_chart_ext_t * ext_copy = lv_obj_get_ext_attr(copy);
140
141 lv_style_list_copy(&ext->style_series, &ext_copy->style_series);
142 lv_style_list_copy(&ext->style_series_bg, &ext_copy->style_series_bg);
143
144 ext->type = ext_copy->type;
145 ext->hdiv_cnt = ext_copy->hdiv_cnt;
146 ext->vdiv_cnt = ext_copy->vdiv_cnt;
147 ext->point_cnt = ext_copy->point_cnt;
148 _lv_memcpy_small(ext->ymin, ext_copy->ymin, sizeof(ext->ymin));
149 _lv_memcpy_small(ext->ymax, ext_copy->ymax, sizeof(ext->ymax));
150 _lv_memcpy(&ext->x_axis, &ext_copy->x_axis, sizeof(lv_chart_axis_cfg_t));
151 _lv_memcpy(&ext->y_axis, &ext_copy->y_axis, sizeof(lv_chart_axis_cfg_t));
152 _lv_memcpy(&ext->secondary_y_axis, &ext_copy->secondary_y_axis, sizeof(lv_chart_axis_cfg_t));
153
154 /*Refresh the style with new signal function*/
155 lv_obj_refresh_style(chart, LV_OBJ_PART_ALL, LV_STYLE_PROP_ALL);
156 }
157
158 LV_LOG_INFO("chart created");
159
160 return chart;
161 }
162
163 /*======================
164 * Add/remove functions
165 *=====================*/
166
167 /**
168 * Allocate and add a data series to the chart
169 * @param chart pointer to a chart object
170 * @param color color of the data series
171 * @return pointer to the allocated data series
172 */
lv_chart_add_series(lv_obj_t * chart,lv_color_t color)173 lv_chart_series_t * lv_chart_add_series(lv_obj_t * chart, lv_color_t color)
174 {
175 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
176
177 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
178 lv_chart_series_t * ser = _lv_ll_ins_head(&ext->series_ll);
179 LV_ASSERT_MEM(ser);
180 if(ser == NULL) return NULL;
181
182 lv_coord_t def = LV_CHART_POINT_DEF;
183
184 ser->color = color;
185 ser->points = lv_mem_alloc(sizeof(lv_coord_t) * ext->point_cnt);
186 LV_ASSERT_MEM(ser->points);
187 if(ser->points == NULL) {
188 _lv_ll_remove(&ext->series_ll, ser);
189 lv_mem_free(ser);
190 return NULL;
191 }
192
193 ser->start_point = 0;
194 ser->ext_buf_assigned = false;
195 ser->y_axis = LV_CHART_AXIS_PRIMARY_Y;
196
197 uint16_t i;
198 lv_coord_t * p_tmp = ser->points;
199 for(i = 0; i < ext->point_cnt; i++) {
200 *p_tmp = def;
201 p_tmp++;
202 }
203
204 return ser;
205 }
206
207 /**
208 * Clear the point of a series
209 * @param chart pointer to a chart object
210 * @param series pointer to the chart's series to clear
211 */
lv_chart_clear_series(lv_obj_t * chart,lv_chart_series_t * series)212 void lv_chart_clear_series(lv_obj_t * chart, lv_chart_series_t * series)
213 {
214 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
215 LV_ASSERT_NULL(series);
216
217 if(chart == NULL || series == NULL) return;
218 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
219 if(ext == NULL) return;
220
221 uint32_t i;
222 for(i = 0; i < ext->point_cnt; i++) {
223 series->points[i] = LV_CHART_POINT_DEF;
224 }
225
226 series->start_point = 0;
227 }
228
229 /*=====================
230 * Setter functions
231 *====================*/
232
233 /**
234 * Set the number of horizontal and vertical division lines
235 * @param chart pointer to a graph background object
236 * @param hdiv number of horizontal division lines
237 * @param vdiv number of vertical division lines
238 */
lv_chart_set_div_line_count(lv_obj_t * chart,uint8_t hdiv,uint8_t vdiv)239 void lv_chart_set_div_line_count(lv_obj_t * chart, uint8_t hdiv, uint8_t vdiv)
240 {
241 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
242
243 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
244 if(ext->hdiv_cnt == hdiv && ext->vdiv_cnt == vdiv) return;
245
246 ext->hdiv_cnt = hdiv;
247 ext->vdiv_cnt = vdiv;
248
249 lv_obj_invalidate(chart);
250 }
251
252 /**
253 * Set the minimal and maximal y values on an axis
254 * @param chart pointer to a graph background object
255 * @param axis `LV_CHART_AXIS_PRIMARY_Y` or `LV_CHART_AXIS_SECONDARY_Y`
256 * @param ymin y minimum value
257 * @param ymax y maximum value
258 */
lv_chart_set_y_range(lv_obj_t * chart,lv_chart_axis_t axis,lv_coord_t ymin,lv_coord_t ymax)259 void lv_chart_set_y_range(lv_obj_t * chart, lv_chart_axis_t axis, lv_coord_t ymin, lv_coord_t ymax)
260 {
261 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
262
263 if(axis >= _LV_CHART_AXIS_LAST) {
264 LV_LOG_WARN("Invalid axis: %d", axis);
265 return;
266 }
267
268 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
269 if(ext->ymin[axis] == ymin && ext->ymax[axis] == ymax) return;
270
271 ext->ymin[axis] = ymin;
272 ext->ymax[axis] = ymax;
273
274 lv_chart_refresh(chart);
275 }
276
277 /**
278 * Set a new type for a chart
279 * @param chart pointer to a chart object
280 * @param type new type of the chart (from 'lv_chart_type_t' enum)
281 */
lv_chart_set_type(lv_obj_t * chart,lv_chart_type_t type)282 void lv_chart_set_type(lv_obj_t * chart, lv_chart_type_t type)
283 {
284 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
285
286 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
287 if(ext->type == type) return;
288
289 ext->type = type;
290
291 lv_chart_refresh(chart);
292 }
293
294 /**
295 * Set the number of points on a data line on a chart
296 * @param chart pointer r to chart object
297 * @param point_cnt new number of points on the data lines
298 */
lv_chart_set_point_count(lv_obj_t * chart,uint16_t point_cnt)299 void lv_chart_set_point_count(lv_obj_t * chart, uint16_t point_cnt)
300 {
301 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
302
303 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
304 if(ext->point_cnt == point_cnt) return;
305
306 lv_chart_series_t * ser;
307 uint16_t point_cnt_old = ext->point_cnt;
308 uint16_t i;
309 lv_coord_t def = LV_CHART_POINT_DEF;
310
311 if(point_cnt < 1) point_cnt = 1;
312
313 _LV_LL_READ_BACK(ext->series_ll, ser) {
314 if(!ser->ext_buf_assigned) {
315 if(ser->start_point != 0) {
316 lv_coord_t * new_points = lv_mem_alloc(sizeof(lv_coord_t) * point_cnt);
317 LV_ASSERT_MEM(new_points);
318 if(new_points == NULL) return;
319
320 if(point_cnt >= point_cnt_old) {
321 for(i = 0; i < point_cnt_old; i++) {
322 new_points[i] =
323 ser->points[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
324 }
325 for(i = point_cnt_old; i < point_cnt; i++) {
326 new_points[i] = def; /*Fill up the rest with default value*/
327 }
328 }
329 else {
330 for(i = 0; i < point_cnt; i++) {
331 new_points[i] =
332 ser->points[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
333 }
334 }
335
336 /*Switch over pointer from old to new*/
337 lv_mem_free(ser->points);
338 ser->points = new_points;
339 }
340 else {
341 ser->points = lv_mem_realloc(ser->points, sizeof(lv_coord_t) * point_cnt);
342 LV_ASSERT_MEM(ser->points);
343 if(ser->points == NULL) return;
344 /*Initialize the new points*/
345 if(point_cnt > point_cnt_old) {
346 for(i = point_cnt_old - 1; i < point_cnt; i++) {
347 ser->points[i] = def;
348 }
349 }
350 }
351 }
352 ser->start_point = 0;
353 }
354
355 ext->point_cnt = point_cnt;
356
357 lv_chart_refresh(chart);
358 }
359
360 /**
361 * Initialize all data points with a value
362 * @param chart pointer to chart object
363 * @param ser pointer to a data series on 'chart'
364 * @param y the new value for all points
365 */
lv_chart_init_points(lv_obj_t * chart,lv_chart_series_t * ser,lv_coord_t y)366 void lv_chart_init_points(lv_obj_t * chart, lv_chart_series_t * ser, lv_coord_t y)
367 {
368 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
369 LV_ASSERT_NULL(ser);
370
371 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
372 uint16_t i;
373 for(i = 0; i < ext->point_cnt; i++) {
374 ser->points[i] = y;
375 }
376 ser->start_point = 0;
377 lv_chart_refresh(chart);
378 }
379
380 /**
381 * Set the value of points from an array
382 * @param chart pointer to chart object
383 * @param ser pointer to a data series on 'chart'
384 * @param y_array array of 'lv_coord_t' points (with 'points count' elements )
385 */
lv_chart_set_points(lv_obj_t * chart,lv_chart_series_t * ser,lv_coord_t y_array[])386 void lv_chart_set_points(lv_obj_t * chart, lv_chart_series_t * ser, lv_coord_t y_array[])
387 {
388 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
389 LV_ASSERT_NULL(ser);
390
391 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
392 memcpy(ser->points, y_array, ext->point_cnt * (sizeof(lv_coord_t)));
393 ser->start_point = 0;
394 lv_chart_refresh(chart);
395 }
396
397 /**
398 * Shift all data left and set the rightmost data on a data line
399 * @param chart pointer to chart object
400 * @param ser pointer to a data series on 'chart'
401 * @param y the new value of the rightmost data
402 */
lv_chart_set_next(lv_obj_t * chart,lv_chart_series_t * ser,lv_coord_t y)403 void lv_chart_set_next(lv_obj_t * chart, lv_chart_series_t * ser, lv_coord_t y)
404 {
405 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
406 LV_ASSERT_NULL(ser);
407
408 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
409 if(ext->update_mode == LV_CHART_UPDATE_MODE_SHIFT) {
410 ser->points[ser->start_point] =
411 y; /*This was the place of the former left most value, after shifting it is the rightmost*/
412 ser->start_point = (ser->start_point + 1) % ext->point_cnt;
413 lv_chart_refresh(chart);
414 }
415 else if(ext->update_mode == LV_CHART_UPDATE_MODE_CIRCULAR) {
416 ser->points[ser->start_point] = y;
417
418 if(ext->type & LV_CHART_TYPE_LINE) invalidate_lines(chart, ser->start_point);
419 if(ext->type & LV_CHART_TYPE_COLUMN) invalidate_columns(chart, ser->start_point);
420
421 ser->start_point = (ser->start_point + 1) % ext->point_cnt; /*update the x for next incoming y*/
422 }
423 }
424
425 /**
426 * Set update mode of the chart object.
427 * @param chart pointer to a chart object
428 * @param update mode
429 */
lv_chart_set_update_mode(lv_obj_t * chart,lv_chart_update_mode_t update_mode)430 void lv_chart_set_update_mode(lv_obj_t * chart, lv_chart_update_mode_t update_mode)
431 {
432 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
433
434 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
435 if(ext->update_mode == update_mode) return;
436
437 ext->update_mode = update_mode;
438 lv_obj_invalidate(chart);
439 }
440
441 /**
442 * Set the length of the tick marks on the x axis
443 * @param chart pointer to the chart
444 * @param major_tick_len the length of the major tick or `LV_CHART_TICK_LENGTH_AUTO` to set automatically
445 * (where labels are added)
446 * @param minor_tick_len the length of the minor tick, `LV_CHART_TICK_LENGTH_AUTO` to set automatically
447 * (where no labels are added)
448 */
lv_chart_set_x_tick_length(lv_obj_t * chart,uint8_t major_tick_len,uint8_t minor_tick_len)449 void lv_chart_set_x_tick_length(lv_obj_t * chart, uint8_t major_tick_len, uint8_t minor_tick_len)
450 {
451 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
452
453 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
454 ext->x_axis.major_tick_len = major_tick_len;
455 ext->x_axis.minor_tick_len = minor_tick_len;
456 }
457
458 /**
459 * Set the length of the tick marks on the y axis
460 * @param chart pointer to the chart
461 * @param major_tick_len the length of the major tick or `LV_CHART_TICK_LENGTH_AUTO` to set automatically
462 * (where labels are added)
463 * @param minor_tick_len the length of the minor tick, `LV_CHART_TICK_LENGTH_AUTO` to set automatically
464 * (where no labels are added)
465 */
lv_chart_set_y_tick_length(lv_obj_t * chart,uint8_t major_tick_len,uint8_t minor_tick_len)466 void lv_chart_set_y_tick_length(lv_obj_t * chart, uint8_t major_tick_len, uint8_t minor_tick_len)
467 {
468 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
469
470 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
471 ext->y_axis.major_tick_len = major_tick_len;
472 ext->y_axis.minor_tick_len = minor_tick_len;
473 }
474
475 /**
476 * Set the length of the tick marks on the secondary y axis
477 * @param chart pointer to the chart
478 * @param major_tick_len the length of the major tick or `LV_CHART_TICK_LENGTH_AUTO` to set automatically
479 * (where labels are added)
480 * @param minor_tick_len the length of the minor tick, `LV_CHART_TICK_LENGTH_AUTO` to set automatically
481 * (where no labels are added)
482 */
lv_chart_set_secondary_y_tick_length(lv_obj_t * chart,uint8_t major_tick_len,uint8_t minor_tick_len)483 void lv_chart_set_secondary_y_tick_length(lv_obj_t * chart, uint8_t major_tick_len, uint8_t minor_tick_len)
484 {
485 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
486
487 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
488 ext->secondary_y_axis.major_tick_len = major_tick_len;
489 ext->secondary_y_axis.minor_tick_len = minor_tick_len;
490 }
491
492 /**
493 * Set the x-axis tick count and labels of a chart
494 * @param chart pointer to a chart object
495 * @param list_of_values list of string values, terminated with \n, except the last
496 * @param num_tick_marks if list_of_values is NULL: total number of ticks per axis
497 * else number of ticks between two value labels
498 * @param options extra options
499 */
lv_chart_set_x_tick_texts(lv_obj_t * chart,const char * list_of_values,uint8_t num_tick_marks,lv_chart_axis_options_t options)500 void lv_chart_set_x_tick_texts(lv_obj_t * chart, const char * list_of_values, uint8_t num_tick_marks,
501 lv_chart_axis_options_t options)
502 {
503 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
504 LV_ASSERT_NULL(list_of_values);
505
506 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
507 ext->x_axis.num_tick_marks = num_tick_marks;
508 ext->x_axis.list_of_values = list_of_values;
509 ext->x_axis.options = options;
510 }
511
512 /**
513 * Set the y-axis tick count and labels of a chart
514 * @param chart pointer to a chart object
515 * @param list_of_values list of string values, terminated with \n, except the last
516 * @param num_tick_marks if list_of_values is NULL: total number of ticks per axis
517 * else number of ticks between two value labels
518 * @param options extra options
519 */
lv_chart_set_y_tick_texts(lv_obj_t * chart,const char * list_of_values,uint8_t num_tick_marks,lv_chart_axis_options_t options)520 void lv_chart_set_y_tick_texts(lv_obj_t * chart, const char * list_of_values, uint8_t num_tick_marks,
521 lv_chart_axis_options_t options)
522 {
523 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
524 LV_ASSERT_NULL(list_of_values);
525
526 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
527 ext->y_axis.num_tick_marks = num_tick_marks;
528 ext->y_axis.list_of_values = list_of_values;
529 ext->y_axis.options = options;
530 }
531
532 /**
533 * Set the secondary y-axis tick count and labels of a chart
534 * @param chart pointer to a chart object
535 * @param list_of_values list of string values, terminated with \n, except the last
536 * @param num_tick_marks if list_of_values is NULL: total number of ticks per axis
537 * else number of ticks between two value labels
538 * @param options extra options
539 */
lv_chart_set_secondary_y_tick_texts(lv_obj_t * chart,const char * list_of_values,uint8_t num_tick_marks,lv_chart_axis_options_t options)540 void lv_chart_set_secondary_y_tick_texts(lv_obj_t * chart, const char * list_of_values, uint8_t num_tick_marks,
541 lv_chart_axis_options_t options)
542 {
543 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
544 LV_ASSERT_NULL(list_of_values);
545
546 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
547 ext->secondary_y_axis.num_tick_marks = num_tick_marks;
548 ext->secondary_y_axis.list_of_values = list_of_values;
549 ext->secondary_y_axis.options = options;
550 }
551
552 /**
553 * Set the index of the x-axis start point in the data array
554 * @param chart pointer to a chart object
555 * @param ser pointer to a data series on 'chart'
556 * @param id the index of the x point in the data array
557 */
lv_chart_set_x_start_point(lv_obj_t * chart,lv_chart_series_t * ser,uint16_t id)558 void lv_chart_set_x_start_point(lv_obj_t * chart, lv_chart_series_t * ser, uint16_t id)
559 {
560 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
561 LV_ASSERT_NULL(ser);
562
563 if(chart == NULL || ser == NULL) return;
564 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
565 if(ext == NULL) return;
566 if(id >= ext->point_cnt) return;
567 ser->start_point = id;
568 }
569
570 /**
571 * Set an external array of data points to use for the chart
572 * NOTE: It is the users responsibility to make sure the point_cnt matches the external array size.
573 * @param chart pointer to a chart object
574 * @param ser pointer to a data series on 'chart'
575 * @param array external array of points for chart
576 * @param point_cnt number of external points in the array
577 */
lv_chart_set_ext_array(lv_obj_t * chart,lv_chart_series_t * ser,lv_coord_t array[],uint16_t point_cnt)578 void lv_chart_set_ext_array(lv_obj_t * chart, lv_chart_series_t * ser, lv_coord_t array[], uint16_t point_cnt)
579 {
580 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
581 LV_ASSERT_NULL(ser);
582
583 if(chart == NULL || ser == NULL) return;
584 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
585 if(!ser->ext_buf_assigned && ser->points) lv_mem_free(ser->points);
586 ser->ext_buf_assigned = true;
587 ser->points = array;
588 ext->point_cnt = point_cnt;
589 }
590
591 /**
592 * Set an individual point y value in the chart series directly based on index
593 * @param chart pointer to a chart object
594 * @param ser pointer to a data series on 'chart'
595 * @param value value to assign to array point
596 * @param id the index of the x point in the array
597 */
lv_chart_set_point_id(lv_obj_t * chart,lv_chart_series_t * ser,lv_coord_t value,uint16_t id)598 void lv_chart_set_point_id(lv_obj_t * chart, lv_chart_series_t * ser, lv_coord_t value, uint16_t id)
599 {
600 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
601 LV_ASSERT_NULL(ser);
602
603 if(chart == NULL || ser == NULL) return;
604 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
605 if(ext == NULL) return;
606 if(id >= ext->point_cnt) return;
607 ser->points[id] = value;
608 }
609
610 /**
611 * Set the Y axis of a series
612 * @param chart pointer to a chart object
613 * @param ser pointer to series
614 * @param axis `LV_CHART_AXIS_PRIMARY_Y` or `LV_CHART_AXIS_SECONDARY_Y`
615 */
lv_chart_set_series_axis(lv_obj_t * chart,lv_chart_series_t * ser,lv_chart_axis_t axis)616 void lv_chart_set_series_axis(lv_obj_t * chart, lv_chart_series_t * ser, lv_chart_axis_t axis)
617 {
618 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
619 LV_ASSERT_NULL(ser);
620
621 if(axis >= _LV_CHART_AXIS_LAST) {
622 LV_LOG_WARN("Invalid axis: %d", axis);
623 return;
624 }
625
626 if(ser->y_axis == axis) return;
627
628 ser->y_axis = axis;
629 lv_chart_refresh(chart);
630 }
631
632 /*=====================
633 * Getter functions
634 *====================*/
635
636 /**
637 * Get the type of a chart
638 * @param chart pointer to chart object
639 * @return type of the chart (from 'lv_chart_t' enum)
640 */
lv_chart_get_type(const lv_obj_t * chart)641 lv_chart_type_t lv_chart_get_type(const lv_obj_t * chart)
642 {
643 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
644
645 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
646 return ext->type;
647 }
648
649 /**
650 * Get the data point number per data line on chart
651 * @param chart pointer to chart object
652 * @return point number on each data line
653 */
lv_chart_get_point_count(const lv_obj_t * chart)654 uint16_t lv_chart_get_point_count(const lv_obj_t * chart)
655 {
656 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
657
658 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
659 return ext->point_cnt;
660 }
661
662 /**
663 * Get the current index of the x-axis start point in the data array
664 * @param ser pointer to a data series on 'chart'
665 * @return the index of the current x start point in the data array
666 */
lv_chart_get_x_start_point(lv_chart_series_t * ser)667 uint16_t lv_chart_get_x_start_point(lv_chart_series_t * ser)
668 {
669 LV_ASSERT_NULL(ser);
670
671 return(ser->start_point);
672 }
673
674 /**
675 * Get an individual point y value in the chart series directly based on index
676 * @param chart pointer to a chart object
677 * @param ser pointer to a data series on 'chart'
678 * @param id the index of the x point in the array
679 * @return value of array point at index id
680 */
lv_chart_get_point_id(lv_obj_t * chart,lv_chart_series_t * ser,uint16_t id)681 lv_coord_t lv_chart_get_point_id(lv_obj_t * chart, lv_chart_series_t * ser, uint16_t id)
682 {
683 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
684 LV_ASSERT_NULL(ser);
685
686 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
687 if(id >= ext->point_cnt) id = 0;
688 return(ser->points[id]);
689
690 }
691
692 /**
693 * Get the Y axis of a series
694 * @param chart pointer to a chart object
695 * @param ser pointer to series
696 * @return `LV_CHART_AXIS_PRIMARY_Y` or `LV_CHART_AXIS_SECONDARY_Y`
697 */
lv_chart_get_series_axis(lv_obj_t * chart,lv_chart_series_t * ser)698 lv_chart_axis_t lv_chart_get_series_axis(lv_obj_t * chart, lv_chart_series_t * ser)
699 {
700 LV_ASSERT_NULL(ser);
701 LV_UNUSED(chart);
702
703 return ser->y_axis;
704 }
705 /*=====================
706 * Other functions
707 *====================*/
708
709 /**
710 * Refresh a chart if its data line has changed
711 * @param chart pointer to chart object
712 */
lv_chart_refresh(lv_obj_t * chart)713 void lv_chart_refresh(lv_obj_t * chart)
714 {
715 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
716
717 lv_obj_invalidate(chart);
718 }
719
720 /**********************
721 * STATIC FUNCTIONS
722 **********************/
723
724 /**
725 * Handle the drawing related tasks of the chart backgrounds
726 * @param chart pointer to an object
727 * @param clip_area the object will be drawn only in this area
728 * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
729 * (return 'true' if yes)
730 * LV_DESIGN_DRAW: draw the object (always return 'true')
731 * LV_DESIGN_DRAW_POST: drawing after every children are drawn
732 * @param return an element of `lv_design_res_t`
733 */
lv_chart_design(lv_obj_t * chart,const lv_area_t * clip_area,lv_design_mode_t mode)734 static lv_design_res_t lv_chart_design(lv_obj_t * chart, const lv_area_t * clip_area, lv_design_mode_t mode)
735 {
736 if(mode == LV_DESIGN_COVER_CHK) {
737 return ancestor_design(chart, clip_area, mode);
738 }
739 else if(mode == LV_DESIGN_DRAW_MAIN) {
740 /*Draw the background*/
741 lv_draw_rect_dsc_t bg_dsc;
742 lv_draw_rect_dsc_init(&bg_dsc);
743 lv_obj_init_draw_rect_dsc(chart, LV_CHART_PART_BG, &bg_dsc);
744 lv_draw_rect(&chart->coords, clip_area, &bg_dsc);
745
746 lv_area_t series_area;
747 get_series_area(chart, &series_area);
748
749 draw_series_bg(chart, &series_area, clip_area);
750 draw_axes(chart, &series_area, clip_area);
751
752
753 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
754 if(ext->type & LV_CHART_TYPE_LINE) draw_series_line(chart, &series_area, clip_area);
755 if(ext->type & LV_CHART_TYPE_COLUMN) draw_series_column(chart, &series_area, clip_area);
756
757 }
758 return LV_DESIGN_RES_OK;
759 }
760
761 /**
762 * Signal function of the chart background
763 * @param chart pointer to a chart background object
764 * @param sign a signal type from lv_signal_t enum
765 * @param param pointer to a signal specific variable
766 */
lv_chart_signal(lv_obj_t * chart,lv_signal_t sign,void * param)767 static lv_res_t lv_chart_signal(lv_obj_t * chart, lv_signal_t sign, void * param)
768 {
769 /* Include the ancient signal function */
770 lv_res_t res;
771 if(sign == LV_SIGNAL_GET_STYLE) {
772 lv_get_style_info_t * info = param;
773 info->result = lv_chart_get_style(chart, info->part);
774 if(info->result != NULL) return LV_RES_OK;
775 else return ancestor_signal(chart, sign, param);
776 }
777
778 res = ancestor_signal(chart, sign, param);
779 if(res != LV_RES_OK) return res;
780 if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
781
782 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
783
784 if(sign == LV_SIGNAL_CLEANUP) {
785 lv_chart_series_t * ser;
786 while(ext->series_ll.head != NULL) {
787 ser = _lv_ll_get_head(&ext->series_ll);
788
789 if(!ser->ext_buf_assigned) lv_mem_free(ser->points);
790
791 _lv_ll_remove(&ext->series_ll, ser);
792 lv_mem_free(ser);
793 }
794 _lv_ll_clear(&ext->series_ll);
795
796 lv_obj_clean_style_list(chart, LV_CHART_PART_SERIES);
797 lv_obj_clean_style_list(chart, LV_CHART_PART_SERIES_BG);
798 }
799
800 return res;
801 }
802
803
804 /**
805 * Get the style descriptor of a part of the object
806 * @param chart pointer the object
807 * @param part the part of the chart. (LV_CHART_PART_...)
808 * @return pointer to the style descriptor of the specified part
809 */
lv_chart_get_style(lv_obj_t * chart,uint8_t part)810 static lv_style_list_t * lv_chart_get_style(lv_obj_t * chart, uint8_t part)
811 {
812 LV_ASSERT_OBJ(chart, LV_OBJX_NAME);
813
814 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
815 lv_style_list_t * style_dsc_p;
816
817 switch(part) {
818 case LV_CHART_PART_BG:
819 style_dsc_p = &chart->style_list;
820 break;
821 case LV_CHART_PART_SERIES_BG:
822 style_dsc_p = &ext->style_series_bg;
823 break;
824 case LV_CHART_PART_SERIES:
825 style_dsc_p = &ext->style_series;
826 break;
827 default:
828 style_dsc_p = NULL;
829 }
830
831 return style_dsc_p;
832 }
833
834 /**
835 * Draw the division lines on chart background
836 * @param chart pointer to chart object
837 * @param clip_area mask, inherited from the design function
838 */
draw_series_bg(lv_obj_t * chart,const lv_area_t * series_area,const lv_area_t * clip_area)839 static void draw_series_bg(lv_obj_t * chart, const lv_area_t * series_area, const lv_area_t * clip_area)
840 {
841 /*Draw the background of the series*/
842 lv_draw_rect_dsc_t bg_dsc;
843 lv_draw_rect_dsc_init(&bg_dsc);
844 lv_obj_init_draw_rect_dsc(chart, LV_CHART_PART_SERIES_BG, &bg_dsc);
845 lv_draw_rect(series_area, clip_area, &bg_dsc);
846
847 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
848
849 uint8_t div_i;
850 uint8_t div_i_end;
851 uint8_t div_i_start;
852 lv_point_t p1;
853 lv_point_t p2;
854 lv_coord_t w = lv_area_get_width(series_area);
855 lv_coord_t h = lv_area_get_height(series_area);
856 lv_coord_t x_ofs = series_area->x1;
857 lv_coord_t y_ofs = series_area->y1;
858
859 lv_draw_line_dsc_t line_dsc;
860 lv_draw_line_dsc_init(&line_dsc);
861 lv_obj_init_draw_line_dsc(chart, LV_CHART_PART_SERIES_BG, &line_dsc);
862
863 if(ext->hdiv_cnt != 0) {
864 /*Draw side lines if no border*/
865 if(bg_dsc.border_width != 0) {
866 div_i_start = 1;
867 div_i_end = ext->hdiv_cnt;
868 }
869 else {
870 div_i_start = 0;
871 div_i_end = ext->hdiv_cnt + 1;
872 }
873
874 p1.x = 0 + x_ofs;
875 p2.x = w - 1 + x_ofs;
876 for(div_i = div_i_start; div_i <= div_i_end; div_i++) {
877 p1.y = (int32_t)((int32_t)(h - line_dsc.width) * div_i) / (ext->hdiv_cnt + 1);
878 p1.y += y_ofs;
879 p2.y = p1.y;
880 lv_draw_line(&p1, &p2, clip_area, &line_dsc);
881 }
882 }
883
884 if(ext->vdiv_cnt != 0) {
885 /*Draw side lines if no border*/
886 if(bg_dsc.border_width != 0) {
887 div_i_start = 1;
888 div_i_end = ext->vdiv_cnt;
889 }
890 else {
891 div_i_start = 0;
892 div_i_end = ext->vdiv_cnt + 1;
893 }
894
895 p1.y = 0 + y_ofs;
896 p2.y = h + y_ofs - 1;
897 for(div_i = div_i_start; div_i <= div_i_end; div_i++) {
898 p1.x = (int32_t)((int32_t)(w - line_dsc.width) * div_i) / (ext->vdiv_cnt + 1);
899 p1.x += x_ofs;
900 p2.x = p1.x;
901 lv_draw_line(&p1, &p2, clip_area, &line_dsc);
902 }
903 }
904 }
905
906 /**
907 * Draw the data lines as lines on a chart
908 * @param obj pointer to chart object
909 */
draw_series_line(lv_obj_t * chart,const lv_area_t * series_area,const lv_area_t * clip_area)910 static void draw_series_line(lv_obj_t * chart, const lv_area_t * series_area, const lv_area_t * clip_area)
911 {
912 lv_area_t com_area;
913 if(_lv_area_intersect(&com_area, series_area, clip_area) == false) return;
914
915 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
916
917 uint16_t i;
918 lv_point_t p1;
919 lv_point_t p2;
920 lv_coord_t w = lv_area_get_width(series_area);
921 lv_coord_t h = lv_area_get_height(series_area);
922 lv_coord_t x_ofs = series_area->x1;
923 lv_coord_t y_ofs = series_area->y1;
924 lv_chart_series_t * ser;
925
926 lv_area_t series_mask;
927 bool mask_ret = _lv_area_intersect(&series_mask, series_area, clip_area);
928 if(mask_ret == false) return;
929
930 lv_draw_line_dsc_t line_dsc;
931 lv_draw_line_dsc_init(&line_dsc);
932 lv_obj_init_draw_line_dsc(chart, LV_CHART_PART_SERIES, &line_dsc);
933
934 lv_draw_mask_fade_param_t mask_fade_p;
935 int16_t mask_fade_id = LV_MASK_ID_INV;
936 lv_draw_rect_dsc_t area_dsc;
937 bool has_area = lv_obj_get_style_bg_opa(chart, LV_CHART_PART_SERIES) > LV_OPA_MIN ? true : false;
938 bool has_fade = false;
939 if(has_area) {
940 lv_draw_rect_dsc_init(&area_dsc);
941 lv_obj_init_draw_rect_dsc(chart, LV_CHART_PART_SERIES, &area_dsc);
942 area_dsc.border_width = 0;
943
944 has_fade = area_dsc.bg_grad_dir == LV_GRAD_DIR_VER ? true : false;
945 if(has_fade) {
946 lv_draw_mask_fade_init(&mask_fade_p, series_area, area_dsc.bg_main_color_stop, series_area->y1,
947 area_dsc.bg_grad_color_stop, series_area->y2);
948 }
949 }
950
951 lv_draw_rect_dsc_t point_dsc;
952 lv_draw_rect_dsc_init(&point_dsc);
953 point_dsc.bg_opa = line_dsc.opa;
954 point_dsc.radius = LV_RADIUS_CIRCLE;
955
956
957 lv_coord_t point_radius = lv_obj_get_style_size(chart, LV_CHART_PART_SERIES);
958
959 /*Do not bother with line ending is the point will over it*/
960 if(point_radius > line_dsc.width / 2) line_dsc.raw_end = 1;
961
962 /*Go through all data lines*/
963 _LV_LL_READ_BACK(ext->series_ll, ser) {
964 line_dsc.color = ser->color;
965 point_dsc.bg_color = ser->color;
966 area_dsc.bg_color = ser->color;
967 area_dsc.bg_grad_color = ser->color;
968
969 lv_coord_t start_point = ext->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
970
971 p1.x = 0 + x_ofs;
972 p2.x = 0 + x_ofs;
973
974 lv_coord_t p_act = start_point;
975 lv_coord_t p_prev = start_point;
976 int32_t y_tmp = (int32_t)((int32_t)ser->points[p_prev] - ext->ymin[ser->y_axis]) * h;
977 y_tmp = y_tmp / (ext->ymax[ser->y_axis] - ext->ymin[ser->y_axis]);
978 p2.y = h - y_tmp + y_ofs;
979
980 for(i = 0; i < ext->point_cnt; i++) {
981 p1.x = p2.x;
982 p1.y = p2.y;
983
984 p2.x = ((w * i) / (ext->point_cnt - 1)) + x_ofs;
985
986 p_act = (start_point + i) % ext->point_cnt;
987
988 y_tmp = (int32_t)((int32_t)ser->points[p_act] - ext->ymin[ser->y_axis]) * h;
989 y_tmp = y_tmp / (ext->ymax[ser->y_axis] - ext->ymin[ser->y_axis]);
990 p2.y = h - y_tmp + y_ofs;
991
992 /*Don't draw the first point. A second point is also required to draw the line*/
993 if(i != 0 && ser->points[p_prev] != LV_CHART_POINT_DEF && ser->points[p_act] != LV_CHART_POINT_DEF) {
994 lv_draw_line(&p1, &p2, &series_mask, &line_dsc);
995
996 lv_coord_t y_top = LV_MATH_MIN(p1.y, p2.y);
997 if(has_area && y_top <= clip_area->y2) {
998 int16_t mask_line_id;
999 lv_draw_mask_line_param_t mask_line_p;
1000 lv_draw_mask_line_points_init(&mask_line_p, p1.x, p1.y, p2.x, p2.y, LV_DRAW_MASK_LINE_SIDE_BOTTOM);
1001 mask_line_id = lv_draw_mask_add(&mask_line_p, NULL);
1002
1003 lv_area_t a;
1004 a.x1 = p1.x;
1005 a.x2 = p2.x - 1;
1006 a.y1 = y_top;
1007 a.y2 = series_area->y2;
1008
1009 if(has_fade) mask_fade_id = lv_draw_mask_add(&mask_fade_p, NULL);
1010
1011 lv_draw_rect(&a, &series_mask, &area_dsc);
1012
1013 lv_draw_mask_remove_id(mask_line_id);
1014 lv_draw_mask_remove_id(mask_fade_id);
1015 }
1016 }
1017
1018 if(point_radius) {
1019 lv_area_t point_area;
1020
1021 point_area.x1 = p1.x;
1022 point_area.x2 = point_area.x1 + point_radius;
1023 point_area.x1 -= point_radius;
1024
1025 point_area.y1 = p1.y;
1026 point_area.y2 = point_area.y1 + point_radius;
1027 point_area.y1 -= point_radius;
1028
1029 if(ser->points[p_act] != LV_CHART_POINT_DEF) {
1030 /*Don't limit to `series_mask` to get full circles on the ends*/
1031 lv_draw_rect(&point_area, clip_area, &point_dsc);
1032 }
1033 }
1034
1035 p_prev = p_act;
1036 }
1037
1038 /*Draw the last point*/
1039 if(point_radius) {
1040 lv_area_t point_area;
1041
1042 point_area.x1 = p2.x;
1043 point_area.x2 = point_area.x1 + point_radius;
1044 point_area.x1 -= point_radius;
1045
1046 point_area.y1 = p2.y;
1047 point_area.y2 = point_area.y1 + point_radius;
1048 point_area.y1 -= point_radius;
1049
1050 if(ser->points[p_act] != LV_CHART_POINT_DEF) {
1051 /*Don't limit to `series_mask` to get full circles on the ends*/
1052 lv_draw_rect(&point_area, clip_area, &point_dsc);
1053 }
1054 }
1055 }
1056 }
1057
1058 /**
1059 * Draw the data lines as columns on a chart
1060 * @param chart pointer to chart object
1061 * @param mask mask, inherited from the design function
1062 */
draw_series_column(lv_obj_t * chart,const lv_area_t * series_area,const lv_area_t * clip_area)1063 static void draw_series_column(lv_obj_t * chart, const lv_area_t * series_area, const lv_area_t * clip_area)
1064 {
1065 lv_area_t com_area;
1066 if(_lv_area_intersect(&com_area, series_area, clip_area) == false) return;
1067
1068 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
1069
1070 uint16_t i;
1071 lv_area_t col_a;
1072 lv_coord_t w = lv_area_get_width(series_area);
1073 lv_coord_t h = lv_area_get_height(series_area);
1074 int32_t y_tmp;
1075 lv_chart_series_t * ser;
1076 lv_coord_t col_w = w / ((_lv_ll_get_len(&ext->series_ll) + 1) * ext->point_cnt); /* Suppose + 1 series as separator*/
1077 lv_coord_t x_ofs = col_w / 2; /*Shift with a half col.*/
1078 lv_style_int_t col_space = lv_obj_get_style_pad_inner(chart, LV_CHART_PART_SERIES);
1079
1080 lv_draw_rect_dsc_t col_dsc;
1081 lv_draw_rect_dsc_init(&col_dsc);
1082 lv_obj_init_draw_rect_dsc(chart, LV_CHART_PART_SERIES, &col_dsc);
1083 col_dsc.bg_grad_dir = LV_GRAD_DIR_NONE;
1084 col_dsc.bg_opa = LV_OPA_COVER;
1085
1086 /*Make the cols longer with `radius` to clip the rounding from the bottom*/
1087 col_a.y2 = series_area->y2 + col_dsc.radius;
1088
1089 lv_area_t series_mask;
1090 bool mask_ret = _lv_area_intersect(&series_mask, series_area, clip_area);
1091 if(mask_ret == false) return;
1092
1093 /*Go through all points*/
1094 for(i = 0; i < ext->point_cnt; i++) {
1095 lv_coord_t x_act = (int32_t)((int32_t)w * i) / ext->point_cnt;
1096 x_act += series_area->x1 + x_ofs;
1097
1098 /*Draw the current point of all data line*/
1099 _LV_LL_READ_BACK(ext->series_ll, ser) {
1100 lv_coord_t start_point = ext->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
1101
1102 col_a.x1 = x_act;
1103 col_a.x2 = col_a.x1 + col_w - col_space;
1104 x_act += col_w;
1105
1106 if(col_a.x2 < series_mask.x1) continue;
1107 if(col_a.x1 > series_mask.x2) break;
1108
1109 col_dsc.bg_color = ser->color;
1110
1111 lv_coord_t p_act = (start_point + i) % ext->point_cnt;
1112 y_tmp = (int32_t)((int32_t)ser->points[p_act] - ext->ymin[ser->y_axis]) * h;
1113 y_tmp = y_tmp / (ext->ymax[ser->y_axis] - ext->ymin[ser->y_axis]);
1114 col_a.y1 = h - y_tmp + series_area->y1;
1115
1116 if(ser->points[p_act] != LV_CHART_POINT_DEF) {
1117 lv_draw_rect(&col_a, &series_mask, &col_dsc);
1118 }
1119 }
1120 }
1121 }
1122
1123 /**
1124 * Create iterator for newline-separated list
1125 * @param list pointer to newline-separated labels list
1126 * @param iterator_dir LV_CHART_ITERATOR_FORWARD or LV_CHART_LABEL_ITERATOR_REVERSE
1127 * @return lv_chart_label_iterator_t
1128 */
create_axis_label_iter(const char * list,uint8_t iterator_dir)1129 static lv_chart_label_iterator_t create_axis_label_iter(const char * list, uint8_t iterator_dir)
1130 {
1131 lv_chart_label_iterator_t iterator = {0};
1132 uint8_t j;
1133
1134 iterator.list_start = list;
1135
1136 /* count number of list items */
1137 for(j = 0; list[j] != '\0'; j++) {
1138 if(list[j] == '\n')
1139 iterator.items_left++;
1140 }
1141
1142 if(iterator_dir == LV_CHART_LABEL_ITERATOR_FORWARD) {
1143 iterator.is_reverse_iter = 0;
1144 iterator.current_pos = list;
1145 }
1146 else {
1147 iterator.is_reverse_iter = 1;
1148 // -1 to skip '\0' at the end of the string
1149 iterator.current_pos = list + j - 1;
1150 }
1151 iterator.items_left++;
1152 return iterator;
1153 }
1154
1155 /**
1156 * Get next label from iterator created by lv_chart_create_label_iter()
1157 * @param iterator iterator to get label from
1158 * @param[out] buf buffer to point next label to
1159 */
get_next_axis_label(lv_chart_label_iterator_t * iterator,char * buf)1160 static void get_next_axis_label(lv_chart_label_iterator_t * iterator, char * buf)
1161 {
1162 uint32_t label_len = 0;
1163 if(iterator->is_reverse_iter) {
1164 const char * label_start;
1165 /* count the length of the current label*/
1166 while((*iterator->current_pos != '\n') &&
1167 (iterator->current_pos != iterator->list_start)) {
1168 iterator->current_pos--;
1169 label_len++;
1170 }
1171
1172 label_start = iterator->current_pos;
1173
1174 if(*iterator->current_pos == '\n') {
1175 /* do not copy \n symbol, +1 to skip it*/
1176 label_start++;
1177 /* skip newline*/
1178 iterator->current_pos--;
1179 }
1180 else {
1181 /* it is last label in list (first one from the beginning )*/
1182 label_len++;
1183 }
1184
1185 /* do not allow output buffer overflow */
1186 if(label_len > LV_CHART_AXIS_TICK_LABEL_MAX_LEN) {
1187 label_len = LV_CHART_AXIS_TICK_LABEL_MAX_LEN;
1188 }
1189
1190 strncpy(buf, label_start, label_len);
1191 }
1192 else {
1193 /* search for tick string */
1194 while(iterator->current_pos[label_len] != '\n' &&
1195 iterator->current_pos[label_len] != '\0') {
1196 /* do not overflow the buffer, but move to the end of the current label */
1197 if(label_len < LV_CHART_AXIS_TICK_LABEL_MAX_LEN) {
1198 buf[label_len] = iterator->current_pos[label_len];
1199 label_len++;
1200 }
1201 else {
1202 label_len++;
1203 }
1204 }
1205
1206 iterator->current_pos += label_len;
1207
1208 /* do not allow output buffer overflow */
1209 if(label_len > LV_CHART_AXIS_TICK_LABEL_MAX_LEN) {
1210 label_len = LV_CHART_AXIS_TICK_LABEL_MAX_LEN;
1211 }
1212
1213 if(*iterator->current_pos == '\n') iterator->current_pos++;
1214 }
1215
1216 /* terminate the string */
1217 buf[label_len] = '\0';
1218 }
1219
1220 /**
1221 * Check whether there should be a label next to tick with given
1222 * number
1223 * @param tick_num number of the tick to check
1224 * @param axis pointer to struct containing info on the axis
1225 * @return true if label should be located next to current tick
1226 */
is_tick_with_label(uint8_t tick_num,lv_chart_axis_cfg_t * axis)1227 static inline bool is_tick_with_label(uint8_t tick_num, lv_chart_axis_cfg_t * axis)
1228 {
1229 return ((tick_num == 0) || ((tick_num % axis->num_tick_marks) == 0));
1230 }
1231
draw_y_ticks(lv_obj_t * chart,const lv_area_t * series_area,const lv_area_t * mask,uint8_t which_axis)1232 static void draw_y_ticks(lv_obj_t * chart, const lv_area_t * series_area, const lv_area_t * mask, uint8_t which_axis)
1233 {
1234 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
1235 lv_chart_axis_cfg_t * y_axis = (which_axis == LV_CHART_AXIS_PRIMARY_Y) ?
1236 &ext->y_axis : &ext->secondary_y_axis;
1237
1238 if(y_axis->list_of_values == NULL && y_axis->num_tick_marks == 0) return;
1239
1240 uint8_t i;
1241 uint8_t num_of_labels;
1242 uint8_t num_scale_ticks;
1243 int8_t major_tick_len, minor_tick_len;
1244 uint8_t iter_dir;
1245
1246 lv_point_t p1;
1247 lv_point_t p2;
1248 lv_coord_t x_ofs;
1249 lv_chart_label_iterator_t iter;
1250 lv_coord_t y_ofs = series_area->y1;
1251 lv_coord_t h = lv_area_get_height(series_area);
1252 lv_coord_t w = lv_area_get_width(series_area);
1253 char buf[LV_CHART_AXIS_TICK_LABEL_MAX_LEN + 1]; /* up to N symbols per label + null terminator */
1254
1255 /* chose correct side of the chart */
1256 if(which_axis == LV_CHART_AXIS_PRIMARY_Y)
1257 x_ofs = series_area->x1;
1258 else
1259 x_ofs = series_area->x2;
1260
1261 /* calculate the size of tick marks */
1262 if(y_axis->major_tick_len == LV_CHART_TICK_LENGTH_AUTO)
1263 major_tick_len = (int32_t)w * LV_CHART_AXIS_MAJOR_TICK_LEN_COE;
1264 else
1265 major_tick_len = y_axis->major_tick_len;
1266
1267 if(y_axis->minor_tick_len == LV_CHART_TICK_LENGTH_AUTO)
1268 minor_tick_len = major_tick_len * LV_CHART_AXIS_MINOR_TICK_LEN_COE;
1269 else
1270 minor_tick_len = y_axis->minor_tick_len;
1271
1272 /* tick lines on secondary y axis are drawn in other direction*/
1273 if(which_axis == LV_CHART_AXIS_SECONDARY_Y) {
1274 major_tick_len *= -1;
1275 minor_tick_len *= -1;
1276 }
1277
1278 iter_dir = (y_axis->options & LV_CHART_AXIS_INVERSE_LABELS_ORDER) ? LV_CHART_LABEL_ITERATOR_REVERSE :
1279 LV_CHART_LABEL_ITERATOR_FORWARD;
1280 iter = create_axis_label_iter(y_axis->list_of_values, iter_dir);
1281
1282 /*determine the number of options */
1283 num_of_labels = iter.items_left;
1284
1285 /* we can't have string labels without ticks step, set to 1 if not specified */
1286 if(y_axis->num_tick_marks == 0) y_axis->num_tick_marks = 1;
1287
1288 /* calculate total number of ticks */
1289 if(num_of_labels < 2)
1290 num_scale_ticks = y_axis->num_tick_marks;
1291 else
1292 num_scale_ticks = (y_axis->num_tick_marks * (num_of_labels - 1));
1293
1294 lv_style_int_t label_dist = which_axis == LV_CHART_AXIS_PRIMARY_Y ?
1295 lv_obj_get_style_pad_left(chart, LV_CHART_PART_SERIES_BG) : lv_obj_get_style_pad_right(chart, LV_CHART_PART_SERIES_BG);
1296
1297 lv_draw_line_dsc_t line_dsc;
1298 lv_draw_line_dsc_init(&line_dsc);
1299 lv_obj_init_draw_line_dsc(chart, LV_CHART_PART_BG, &line_dsc);
1300
1301 lv_draw_label_dsc_t label_dsc;
1302 lv_draw_label_dsc_init(&label_dsc);
1303 lv_obj_init_draw_label_dsc(chart, LV_CHART_PART_BG, &label_dsc);
1304
1305 for(i = 0; i < (num_scale_ticks + 1); i++) { /* one extra loop - it may not exist in the list, empty label */
1306
1307 /* draw a line at moving y position */
1308 p2.y = p1.y =
1309 y_ofs + (int32_t)((int32_t)(h - line_dsc.width) * i) / num_scale_ticks;
1310
1311 if(p2.y - label_dsc.font->line_height > mask->y2) return;
1312 if(p2.y + label_dsc.font->line_height < mask->y1) {
1313 if(is_tick_with_label(i, y_axis)) {
1314 get_next_axis_label(&iter, buf);
1315 }
1316 continue;
1317 }
1318
1319 /* first point of the tick */
1320 p1.x = x_ofs;
1321
1322 /* move extra pixel out of chart boundary */
1323 if(which_axis == LV_CHART_AXIS_PRIMARY_Y)
1324 p1.x--;
1325 else
1326 p1.x++;
1327
1328 /* second point of the tick */
1329 if((num_of_labels != 0) && (i == 0 || i % y_axis->num_tick_marks == 0))
1330 p2.x = p1.x - major_tick_len; /* major tick */
1331 else
1332 p2.x = p1.x - minor_tick_len; /* minor tick */
1333
1334 if(y_axis->options & LV_CHART_AXIS_INVERSE_LABELS_ORDER) {
1335 /*if label order is inversed last tick have number 0*/
1336 if(i != 0)
1337 lv_draw_line(&p1, &p2, mask, &line_dsc);
1338 else if((y_axis->options & LV_CHART_AXIS_DRAW_LAST_TICK) != 0)
1339 lv_draw_line(&p1, &p2, mask, &line_dsc);
1340 }
1341 else {
1342 if(i != num_scale_ticks)
1343 lv_draw_line(&p1, &p2, mask, &line_dsc);
1344 else if((y_axis->options & LV_CHART_AXIS_DRAW_LAST_TICK) != 0)
1345 lv_draw_line(&p1, &p2, mask, &line_dsc);
1346 }
1347
1348 /* draw values if available */
1349 if(num_of_labels != 0) {
1350 /* add text only to major tick */
1351 if(is_tick_with_label(i, y_axis)) {
1352
1353 get_next_axis_label(&iter, buf);
1354
1355 /* reserve appropriate area */
1356 lv_point_t size;
1357 _lv_txt_get_size(&size, buf, label_dsc.font, label_dsc.letter_space, label_dsc.line_space,
1358 LV_COORD_MAX, LV_TXT_FLAG_CENTER);
1359
1360 /* set the area at some distance of the major tick len left of the tick */
1361 lv_area_t a = {.y1 = p2.y - size.y / 2, .y2 = p2.y + size.y / 2};
1362
1363 if(which_axis == LV_CHART_AXIS_PRIMARY_Y) {
1364 a.x1 = p2.x - size.x - label_dist;
1365 a.x2 = p2.x - label_dist;
1366 }
1367 else {
1368 a.x1 = p2.x + label_dist;
1369 a.x2 = p2.x + size.x + label_dist;
1370 }
1371
1372 lv_draw_label(&a, mask, &label_dsc, buf, NULL);
1373 }
1374 }
1375 }
1376 }
1377
draw_x_ticks(lv_obj_t * chart,const lv_area_t * series_area,const lv_area_t * mask)1378 static void draw_x_ticks(lv_obj_t * chart, const lv_area_t * series_area, const lv_area_t * mask)
1379 {
1380 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
1381
1382 if(ext->x_axis.list_of_values == NULL && ext->x_axis.num_tick_marks == 0) return;
1383
1384 uint8_t i;
1385 uint8_t num_of_labels;
1386 uint8_t num_scale_ticks;
1387 uint8_t major_tick_len, minor_tick_len;
1388 lv_chart_label_iterator_t iter;
1389 lv_point_t p1;
1390 lv_point_t p2;
1391 lv_coord_t x_ofs = series_area->x1;
1392 lv_coord_t y_ofs = series_area->y1;
1393 lv_coord_t h = lv_area_get_height(series_area);
1394 lv_coord_t w = lv_area_get_width(series_area);
1395 lv_style_int_t label_dist = lv_obj_get_style_pad_bottom(chart, LV_CHART_PART_SERIES_BG);
1396
1397 lv_draw_label_dsc_t label_dsc;
1398 lv_draw_label_dsc_init(&label_dsc);
1399 lv_obj_init_draw_label_dsc(chart, LV_CHART_PART_BG, &label_dsc);
1400
1401
1402 /* calculate the size of tick marks */
1403 if(ext->x_axis.major_tick_len == LV_CHART_TICK_LENGTH_AUTO)
1404 major_tick_len = (int32_t)w * LV_CHART_AXIS_MAJOR_TICK_LEN_COE;
1405 else
1406 major_tick_len = ext->x_axis.major_tick_len;
1407
1408 if(ext->x_axis.minor_tick_len == LV_CHART_TICK_LENGTH_AUTO)
1409 minor_tick_len = major_tick_len * LV_CHART_AXIS_MINOR_TICK_LEN_COE;
1410 else
1411 minor_tick_len = ext->x_axis.minor_tick_len;
1412
1413 if(h + y_ofs > mask->y2) return;
1414 if(h + y_ofs + label_dist + label_dsc.font->line_height + major_tick_len < mask->y1) return;
1415
1416 lv_draw_line_dsc_t line_dsc;
1417 lv_draw_line_dsc_init(&line_dsc);
1418 lv_obj_init_draw_line_dsc(chart, LV_CHART_PART_BG, &line_dsc);
1419
1420 /* The columns don't start at the most right position
1421 * so change the width and offset accordingly. */
1422 if(ext->type == LV_CHART_TYPE_COLUMN) {
1423 uint32_t ser_num = _lv_ll_get_len(&ext->series_ll);
1424 lv_coord_t col_w = w / ((ser_num + 1) * ext->point_cnt); /* Suppose + 1 series as separator*/
1425 x_ofs += col_w / 2 + (col_w * (ser_num) / 2);
1426 w -= col_w * ser_num + col_w;
1427 }
1428
1429 char buf[LV_CHART_AXIS_TICK_LABEL_MAX_LEN + 1]; /* up to N symbols per label + null terminator */
1430
1431 /*determine the number of options */
1432 iter = create_axis_label_iter(ext->x_axis.list_of_values, LV_CHART_LABEL_ITERATOR_FORWARD);
1433 num_of_labels = iter.items_left;
1434
1435 /* we can't have string labels without ticks step, set to 1 if not specified */
1436 if(ext->x_axis.num_tick_marks == 0) ext->x_axis.num_tick_marks = 1;
1437
1438 /* calculate total number of marks */
1439 if(num_of_labels < 2)
1440 num_scale_ticks = ext->x_axis.num_tick_marks;
1441 else
1442 num_scale_ticks = (ext->x_axis.num_tick_marks * (num_of_labels - 1));
1443
1444 for(i = 0; i < (num_scale_ticks + 1); i++) { /* one extra loop - it may not exist in the list, empty label */
1445 /* first point of the tick */
1446 p1.y = h + y_ofs;
1447
1448 /* second point of the tick */
1449 if((num_of_labels != 0) && (i == 0 || i % ext->x_axis.num_tick_marks == 0))
1450 p2.y = p1.y + major_tick_len; /* major tick */
1451 else
1452 p2.y = p1.y + minor_tick_len; /* minor tick */
1453
1454 /* draw a line at moving x position */
1455 p2.x = p1.x = x_ofs + (int32_t)((int32_t)(w - line_dsc.width) * i) / num_scale_ticks;
1456
1457 if(i != num_scale_ticks)
1458 lv_draw_line(&p1, &p2, mask, &line_dsc);
1459 else if((ext->x_axis.options & LV_CHART_AXIS_DRAW_LAST_TICK) != 0)
1460 lv_draw_line(&p1, &p2, mask, &line_dsc);
1461
1462 /* draw values if available */
1463 if(num_of_labels != 0) {
1464 /* add text only to major tick */
1465 if(is_tick_with_label(i, &(ext->x_axis))) {
1466 get_next_axis_label(&iter, buf);
1467
1468 /* reserve appropriate area */
1469 lv_point_t size;
1470 _lv_txt_get_size(&size, buf, label_dsc.font, label_dsc.letter_space, label_dsc.line_space,
1471 LV_COORD_MAX, LV_TXT_FLAG_CENTER);
1472
1473 /* set the area at some distance of the major tick len under of the tick */
1474 lv_area_t a = {(p2.x - size.x / 2), (p2.y + label_dist), (p2.x + size.x / 2),
1475 (p2.y + size.y + label_dist)
1476 };
1477 lv_draw_label(&a, mask, &label_dsc, buf, NULL);
1478 }
1479 }
1480 }
1481 }
1482
draw_axes(lv_obj_t * chart,const lv_area_t * series_area,const lv_area_t * mask)1483 static void draw_axes(lv_obj_t * chart, const lv_area_t * series_area, const lv_area_t * mask)
1484 {
1485 draw_y_ticks(chart, series_area, mask, LV_CHART_AXIS_PRIMARY_Y);
1486 draw_y_ticks(chart, series_area, mask, LV_CHART_AXIS_SECONDARY_Y);
1487 draw_x_ticks(chart, series_area, mask);
1488 }
1489
1490 /**
1491 * invalid area of the new line data lines on a chart
1492 * @param obj pointer to chart object
1493 */
invalidate_lines(lv_obj_t * chart,uint16_t i)1494 static void invalidate_lines(lv_obj_t * chart, uint16_t i)
1495 {
1496 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
1497 if(i >= ext->point_cnt) return;
1498
1499 lv_area_t series_area;
1500 get_series_area(chart, &series_area);
1501
1502 lv_coord_t w = lv_area_get_width(&series_area);
1503 lv_coord_t x_ofs = series_area.x1;
1504
1505 lv_style_int_t line_width = lv_obj_get_style_line_width(chart, LV_CHART_PART_SERIES);
1506 lv_style_int_t point_radius = lv_obj_get_style_size(chart, LV_CHART_PART_SERIES);
1507
1508 lv_area_t coords;
1509 lv_area_copy(&coords, &series_area);
1510 coords.y1 -= line_width + point_radius;
1511 coords.y2 += line_width + point_radius;
1512
1513 if(i < ext->point_cnt - 1) {
1514 coords.x1 = ((w * i) / (ext->point_cnt - 1)) + x_ofs - line_width - point_radius;
1515 coords.x2 = ((w * (i + 1)) / (ext->point_cnt - 1)) + x_ofs + line_width + point_radius;
1516 lv_obj_invalidate_area(chart, &coords);
1517 }
1518
1519 if(i > 0) {
1520 coords.x1 = ((w * (i - 1)) / (ext->point_cnt - 1)) + x_ofs - line_width - point_radius;
1521 coords.x2 = ((w * i) / (ext->point_cnt - 1)) + x_ofs + line_width + point_radius;
1522 lv_obj_invalidate_area(chart, &coords);
1523 }
1524 }
1525
1526
1527 /**
1528 * invalid area of the new column data lines on a chart
1529 * @param chart pointer to chart object
1530 * @param mask mask, inherited from the design function
1531 */
invalidate_columns(lv_obj_t * chart,uint16_t i)1532 static void invalidate_columns(lv_obj_t * chart, uint16_t i)
1533 {
1534 lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
1535
1536 lv_area_t series_area;
1537 get_series_area(chart, &series_area);
1538
1539 lv_area_t col_a;
1540 lv_coord_t w = lv_area_get_width(&series_area);
1541 lv_coord_t col_w = w / ((_lv_ll_get_len(&ext->series_ll) + 1) * ext->point_cnt); /* Suppose + 1 series as separator*/
1542 lv_coord_t x_ofs = col_w / 2; /*Shift with a half col.*/
1543
1544 lv_coord_t x_act;
1545 x_act = (int32_t)((int32_t)w * i) / ext->point_cnt;
1546 x_act += series_area.x1 + x_ofs;
1547
1548 lv_obj_get_coords(chart, &col_a);
1549 col_a.x1 = x_act;
1550 col_a.x2 = col_a.x1 + col_w;
1551
1552 _lv_inv_area(lv_obj_get_disp(chart), &col_a);
1553 }
1554
get_series_area(lv_obj_t * chart,lv_area_t * series_area)1555 static void get_series_area(lv_obj_t * chart, lv_area_t * series_area)
1556 {
1557 lv_area_copy(series_area, &chart->coords);
1558 series_area->x1 += lv_obj_get_style_pad_left(chart, LV_CHART_PART_BG);
1559 series_area->x2 -= lv_obj_get_style_pad_right(chart, LV_CHART_PART_BG);
1560 series_area->y1 += lv_obj_get_style_pad_top(chart, LV_CHART_PART_BG);
1561 series_area->y2 -= lv_obj_get_style_pad_bottom(chart, LV_CHART_PART_BG);
1562 }
1563
1564 #endif
1565