1 /**
2  * @file lv_chart.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_chart_private.h"
10 #include "../../misc/lv_area_private.h"
11 #include "../../draw/lv_draw_private.h"
12 #include "../../core/lv_obj_private.h"
13 #include "../../core/lv_obj_class_private.h"
14 #if LV_USE_CHART != 0
15 
16 #include "../../misc/lv_assert.h"
17 
18 /*********************
19  *      DEFINES
20  *********************/
21 #define MY_CLASS (&lv_chart_class)
22 
23 #define LV_CHART_HDIV_DEF 3
24 #define LV_CHART_VDIV_DEF 5
25 #define LV_CHART_POINT_CNT_DEF 10
26 #define LV_CHART_LABEL_MAX_TEXT_LENGTH 16
27 
28 /**********************
29  *      TYPEDEFS
30  **********************/
31 
32 /**********************
33  *  STATIC PROTOTYPES
34  **********************/
35 static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
36 static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
37 static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e);
38 
39 static void draw_div_lines(lv_obj_t * obj, lv_layer_t * layer);
40 static void draw_series_line(lv_obj_t * obj, lv_layer_t * layer);
41 static void draw_series_bar(lv_obj_t * obj, lv_layer_t * layer);
42 static void draw_series_scatter(lv_obj_t * obj, lv_layer_t * layer);
43 static void draw_cursors(lv_obj_t * obj, lv_layer_t * layer);
44 static uint32_t get_index_from_x(lv_obj_t * obj, int32_t x);
45 static void invalidate_point(lv_obj_t * obj, uint32_t i);
46 static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, int32_t ** a);
47 
48 /**********************
49  *  STATIC VARIABLES
50  **********************/
51 
52 const lv_obj_class_t lv_chart_class = {
53     .constructor_cb = lv_chart_constructor,
54     .destructor_cb = lv_chart_destructor,
55     .event_cb = lv_chart_event,
56     .width_def = LV_PCT(100),
57     .height_def = LV_DPI_DEF * 2,
58     .instance_size = sizeof(lv_chart_t),
59     .base_class = &lv_obj_class,
60     .name = "chart",
61 };
62 
63 /**********************
64  *      MACROS
65  **********************/
66 
67 /**********************
68  *   GLOBAL FUNCTIONS
69  **********************/
70 
lv_chart_create(lv_obj_t * parent)71 lv_obj_t * lv_chart_create(lv_obj_t * parent)
72 {
73     LV_LOG_INFO("begin");
74     lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
75     lv_obj_class_init_obj(obj);
76     return obj;
77 }
78 
lv_chart_set_type(lv_obj_t * obj,lv_chart_type_t type)79 void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type)
80 {
81     LV_ASSERT_OBJ(obj, MY_CLASS);
82 
83     lv_chart_t * chart  = (lv_chart_t *)obj;
84     if(chart->type == type) return;
85 
86     if(chart->type == LV_CHART_TYPE_SCATTER) {
87         lv_chart_series_t * ser;
88         LV_LL_READ_BACK(&chart->series_ll, ser) {
89             lv_free(ser->x_points);
90             ser->x_points = NULL;
91         }
92     }
93 
94     if(type == LV_CHART_TYPE_SCATTER) {
95         lv_chart_series_t * ser;
96         LV_LL_READ_BACK(&chart->series_ll, ser) {
97             ser->x_points = lv_malloc(sizeof(int32_t) * chart->point_cnt);
98             LV_ASSERT_MALLOC(ser->x_points);
99             if(ser->x_points == NULL) return;
100         }
101     }
102 
103     chart->type = type;
104 
105     lv_chart_refresh(obj);
106 }
107 
lv_chart_set_point_count(lv_obj_t * obj,uint32_t cnt)108 void lv_chart_set_point_count(lv_obj_t * obj, uint32_t cnt)
109 {
110     LV_ASSERT_OBJ(obj, MY_CLASS);
111 
112     lv_chart_t * chart  = (lv_chart_t *)obj;
113     if(chart->point_cnt == cnt) return;
114 
115     lv_chart_series_t * ser;
116 
117     if(cnt < 1) cnt = 1;
118 
119     LV_LL_READ_BACK(&chart->series_ll, ser) {
120         if(chart->type == LV_CHART_TYPE_SCATTER) {
121             if(!ser->x_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->x_points);
122         }
123         if(!ser->y_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->y_points);
124         ser->start_point = 0;
125     }
126 
127     chart->point_cnt = cnt;
128 
129     lv_chart_refresh(obj);
130 }
131 
lv_chart_set_range(lv_obj_t * obj,lv_chart_axis_t axis,int32_t min,int32_t max)132 void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, int32_t min, int32_t max)
133 {
134     LV_ASSERT_OBJ(obj, MY_CLASS);
135 
136     max = max == min ? max + 1 : max;
137 
138     lv_chart_t * chart  = (lv_chart_t *)obj;
139     switch(axis) {
140         case LV_CHART_AXIS_PRIMARY_Y:
141             chart->ymin[0] = min;
142             chart->ymax[0] = max;
143             break;
144         case LV_CHART_AXIS_SECONDARY_Y:
145             chart->ymin[1] = min;
146             chart->ymax[1] = max;
147             break;
148         case LV_CHART_AXIS_PRIMARY_X:
149             chart->xmin[0] = min;
150             chart->xmax[0] = max;
151             break;
152         case LV_CHART_AXIS_SECONDARY_X:
153             chart->xmin[1] = min;
154             chart->xmax[1] = max;
155             break;
156         default:
157             LV_LOG_WARN("Invalid axis: %d", axis);
158             return;
159     }
160 
161     lv_chart_refresh(obj);
162 }
163 
lv_chart_set_update_mode(lv_obj_t * obj,lv_chart_update_mode_t update_mode)164 void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode)
165 {
166     LV_ASSERT_OBJ(obj, MY_CLASS);
167 
168     lv_chart_t * chart  = (lv_chart_t *)obj;
169     if(chart->update_mode == update_mode) return;
170 
171     chart->update_mode = update_mode;
172     lv_obj_invalidate(obj);
173 }
174 
lv_chart_set_div_line_count(lv_obj_t * obj,uint8_t hdiv,uint8_t vdiv)175 void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv)
176 {
177     LV_ASSERT_OBJ(obj, MY_CLASS);
178 
179     lv_chart_t * chart  = (lv_chart_t *)obj;
180     if(chart->hdiv_cnt == hdiv && chart->vdiv_cnt == vdiv) return;
181 
182     chart->hdiv_cnt = hdiv;
183     chart->vdiv_cnt = vdiv;
184 
185     lv_obj_invalidate(obj);
186 }
187 
lv_chart_get_type(const lv_obj_t * obj)188 lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj)
189 {
190     LV_ASSERT_OBJ(obj, MY_CLASS);
191 
192     lv_chart_t * chart  = (lv_chart_t *)obj;
193     return chart->type;
194 }
195 
lv_chart_get_point_count(const lv_obj_t * obj)196 uint32_t lv_chart_get_point_count(const lv_obj_t * obj)
197 {
198     LV_ASSERT_OBJ(obj, MY_CLASS);
199 
200     lv_chart_t * chart  = (lv_chart_t *)obj;
201     return chart->point_cnt;
202 }
203 
lv_chart_get_x_start_point(const lv_obj_t * obj,lv_chart_series_t * ser)204 uint32_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser)
205 {
206     LV_ASSERT_NULL(ser);
207     LV_UNUSED(obj);
208 
209     return ser->start_point;
210 }
211 
lv_chart_get_point_pos_by_id(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t id,lv_point_t * p_out)212 void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, lv_point_t * p_out)
213 {
214     LV_ASSERT_NULL(obj);
215     LV_ASSERT_NULL(ser);
216     LV_ASSERT_OBJ(obj, MY_CLASS);
217 
218     lv_chart_t * chart  = (lv_chart_t *)obj;
219     if(id >= chart->point_cnt) {
220         LV_LOG_WARN("Invalid index: %"LV_PRIu32, id);
221         p_out->x = 0;
222         p_out->y = 0;
223         return;
224     }
225 
226     int32_t w = lv_obj_get_content_width(obj);
227     int32_t h = lv_obj_get_content_height(obj);
228 
229     if(chart->type == LV_CHART_TYPE_LINE) {
230         if(chart->point_cnt > 1) {
231             p_out->x = (w * id) / (chart->point_cnt - 1);
232         }
233         else {
234             p_out->x = 0;
235         }
236     }
237     else if(chart->type == LV_CHART_TYPE_SCATTER) {
238         p_out->x = lv_map(ser->x_points[id], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
239     }
240     else if(chart->type == LV_CHART_TYPE_BAR) {
241         uint32_t ser_cnt = lv_ll_get_len(&chart->series_ll);
242         int32_t ser_gap = lv_obj_get_style_pad_column(obj, LV_PART_ITEMS);
243 
244         /*Gap between the columns on adjacent X ticks*/
245         int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
246 
247         int32_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;
248 
249         if(chart->point_cnt > 1) {
250             p_out->x = (int32_t)((int32_t)(w - block_w) * id) / (chart->point_cnt - 1);
251         }
252         else {
253             p_out->x = 0;
254         }
255 
256         lv_chart_series_t * ser_i = NULL;
257         uint32_t ser_idx = 0;
258         LV_LL_READ(&chart->series_ll, ser_i) {
259             if(ser_i == ser) break;
260             ser_idx++;
261         }
262 
263         p_out->x = (int32_t)((int32_t)(w + block_gap) * id) / chart->point_cnt;
264         p_out->x += block_w * ser_idx / ser_cnt;
265 
266         int32_t col_w = (block_w - (ser_gap * (ser_cnt - 1))) / ser_cnt;
267         p_out->x += col_w / 2;
268     }
269     else {
270         p_out->x = 0;
271     }
272 
273     int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
274     p_out->x += lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
275     p_out->x -= lv_obj_get_scroll_left(obj);
276 
277     uint32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
278     id = ((int32_t)start_point + id) % chart->point_cnt;
279     int32_t temp_y = 0;
280     temp_y = (int32_t)((int32_t)ser->y_points[id] - chart->ymin[ser->y_axis_sec]) * h;
281     temp_y = temp_y / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
282     p_out->y = h - temp_y;
283     p_out->y += lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
284     p_out->y -= lv_obj_get_scroll_top(obj);
285 }
286 
lv_chart_refresh(lv_obj_t * obj)287 void lv_chart_refresh(lv_obj_t * obj)
288 {
289     LV_ASSERT_OBJ(obj, MY_CLASS);
290 
291     lv_obj_invalidate(obj);
292 }
293 
294 /*======================
295  * Series
296  *=====================*/
297 
lv_chart_add_series(lv_obj_t * obj,lv_color_t color,lv_chart_axis_t axis)298 lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis)
299 {
300     LV_LOG_INFO("begin");
301 
302     LV_ASSERT_OBJ(obj, MY_CLASS);
303 
304     lv_chart_t * chart    = (lv_chart_t *)obj;
305 
306     /* Allocate space for a new series and add it to the chart series linked list */
307     lv_chart_series_t * ser = lv_ll_ins_tail(&chart->series_ll);
308     LV_ASSERT_MALLOC(ser);
309     if(ser == NULL) return NULL;
310     lv_memzero(ser, sizeof(lv_chart_series_t));
311 
312     /* Allocate memory for point_cnt points, handle failure below */
313     ser->y_points = lv_malloc(sizeof(int32_t) * chart->point_cnt);
314     LV_ASSERT_MALLOC(ser->y_points);
315 
316     if(chart->type == LV_CHART_TYPE_SCATTER) {
317         ser->x_points = lv_malloc(sizeof(int32_t) * chart->point_cnt);
318         LV_ASSERT_MALLOC(ser->x_points);
319         if(NULL == ser->x_points) {
320             lv_free(ser->y_points);
321             lv_ll_remove(&chart->series_ll, ser);
322             lv_free(ser);
323             return NULL;
324         }
325     }
326     else {
327         ser->x_points = NULL;
328     }
329 
330     if(ser->y_points == NULL) {
331         if(ser->x_points) {
332             lv_free(ser->x_points);
333             ser->x_points = NULL;
334         }
335 
336         lv_ll_remove(&chart->series_ll, ser);
337         lv_free(ser);
338         return NULL;
339     }
340 
341     /* Set series properties on successful allocation */
342     ser->color = color;
343     ser->start_point = 0;
344     ser->y_ext_buf_assigned = false;
345     ser->hidden = 0;
346     ser->x_axis_sec = axis & LV_CHART_AXIS_SECONDARY_X ? 1 : 0;
347     ser->y_axis_sec = axis & LV_CHART_AXIS_SECONDARY_Y ? 1 : 0;
348 
349     uint32_t i;
350     const int32_t def = LV_CHART_POINT_NONE;
351     int32_t * p_tmp = ser->y_points;
352     for(i = 0; i < chart->point_cnt; i++) {
353         *p_tmp = def;
354         p_tmp++;
355     }
356 
357     return ser;
358 }
359 
lv_chart_remove_series(lv_obj_t * obj,lv_chart_series_t * series)360 void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series)
361 {
362     LV_ASSERT_OBJ(obj, MY_CLASS);
363     LV_ASSERT_NULL(series);
364 
365     lv_chart_t * chart    = (lv_chart_t *)obj;
366     if(!series->y_ext_buf_assigned && series->y_points) lv_free(series->y_points);
367     if(!series->x_ext_buf_assigned && series->x_points) lv_free(series->x_points);
368 
369     lv_ll_remove(&chart->series_ll, series);
370     lv_free(series);
371 
372     return;
373 }
374 
lv_chart_hide_series(lv_obj_t * chart,lv_chart_series_t * series,bool hide)375 void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide)
376 {
377     LV_ASSERT_OBJ(chart, MY_CLASS);
378     LV_ASSERT_NULL(series);
379 
380     series->hidden = hide ? 1 : 0;
381     lv_chart_refresh(chart);
382 }
383 
lv_chart_set_series_color(lv_obj_t * chart,lv_chart_series_t * series,lv_color_t color)384 void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color)
385 {
386     LV_ASSERT_OBJ(chart, MY_CLASS);
387     LV_ASSERT_NULL(series);
388 
389     series->color = color;
390     lv_chart_refresh(chart);
391 }
392 
lv_chart_get_series_color(lv_obj_t * chart,const lv_chart_series_t * series)393 lv_color_t lv_chart_get_series_color(lv_obj_t * chart, const lv_chart_series_t * series)
394 {
395     LV_ASSERT_OBJ(chart, MY_CLASS);
396     LV_ASSERT_NULL(series);
397     LV_UNUSED(chart);
398 
399     return series->color;
400 }
401 
lv_chart_set_x_start_point(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t id)402 void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id)
403 {
404     LV_ASSERT_OBJ(obj, MY_CLASS);
405     LV_ASSERT_NULL(ser);
406 
407     lv_chart_t * chart  = (lv_chart_t *)obj;
408     if(id >= chart->point_cnt) return;
409     ser->start_point = id;
410 }
411 
lv_chart_get_series_next(const lv_obj_t * obj,const lv_chart_series_t * ser)412 lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * obj, const lv_chart_series_t * ser)
413 {
414     LV_ASSERT_OBJ(obj, MY_CLASS);
415 
416     lv_chart_t * chart  = (lv_chart_t *)obj;
417     if(ser == NULL) return lv_ll_get_head(&chart->series_ll);
418     else return lv_ll_get_next(&chart->series_ll, ser);
419 }
420 
421 /*=====================
422  * Cursor
423  *====================*/
424 
lv_chart_add_cursor(lv_obj_t * obj,lv_color_t color,lv_dir_t dir)425 lv_chart_cursor_t  * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir)
426 {
427     LV_ASSERT_OBJ(obj, MY_CLASS);
428 
429     lv_chart_t * chart  = (lv_chart_t *)obj;
430     lv_chart_cursor_t * cursor = lv_ll_ins_head(&chart->cursor_ll);
431     LV_ASSERT_MALLOC(cursor);
432     if(cursor == NULL) return NULL;
433 
434     lv_point_set(&cursor->pos, LV_CHART_POINT_NONE, LV_CHART_POINT_NONE);
435     cursor->point_id = LV_CHART_POINT_NONE;
436     cursor->pos_set = 0;
437     cursor->color = color;
438     cursor->dir = dir;
439 
440     return cursor;
441 }
442 
lv_chart_set_cursor_pos(lv_obj_t * chart,lv_chart_cursor_t * cursor,lv_point_t * pos)443 void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos)
444 {
445     LV_ASSERT_NULL(cursor);
446     LV_UNUSED(chart);
447 
448     cursor->pos = *pos;
449     cursor->pos_set = 1;
450     lv_chart_refresh(chart);
451 }
452 
lv_chart_set_cursor_point(lv_obj_t * chart,lv_chart_cursor_t * cursor,lv_chart_series_t * ser,uint32_t point_id)453 void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser, uint32_t point_id)
454 {
455     LV_ASSERT_NULL(cursor);
456     LV_UNUSED(chart);
457 
458     cursor->point_id = point_id;
459     cursor->pos_set = 0;
460     if(ser == NULL) ser = lv_chart_get_series_next(chart, NULL);
461     cursor->ser = ser;
462     lv_chart_refresh(chart);
463 }
464 
lv_chart_get_cursor_point(lv_obj_t * chart,lv_chart_cursor_t * cursor)465 lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor)
466 {
467     LV_ASSERT_NULL(cursor);
468     LV_UNUSED(chart);
469 
470     return cursor->pos;
471 }
472 
473 /*=====================
474  * Set/Get value(s)
475  *====================*/
476 
lv_chart_set_all_values(lv_obj_t * obj,lv_chart_series_t * ser,int32_t value)477 void lv_chart_set_all_values(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value)
478 {
479     LV_ASSERT_OBJ(obj, MY_CLASS);
480     LV_ASSERT_NULL(ser);
481 
482     lv_chart_t * chart  = (lv_chart_t *)obj;
483     uint32_t i;
484     for(i = 0; i < chart->point_cnt; i++) {
485         ser->y_points[i] = value;
486     }
487     ser->start_point = 0;
488     lv_chart_refresh(obj);
489 }
490 
lv_chart_set_next_value(lv_obj_t * obj,lv_chart_series_t * ser,int32_t value)491 void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value)
492 {
493     LV_ASSERT_OBJ(obj, MY_CLASS);
494     LV_ASSERT_NULL(ser);
495 
496     lv_chart_t * chart  = (lv_chart_t *)obj;
497     ser->y_points[ser->start_point] = value;
498     invalidate_point(obj, ser->start_point);
499     ser->start_point = (ser->start_point + 1) % chart->point_cnt;
500     invalidate_point(obj, ser->start_point);
501 }
502 
lv_chart_set_next_value2(lv_obj_t * obj,lv_chart_series_t * ser,int32_t x_value,int32_t y_value)503 void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, int32_t x_value, int32_t y_value)
504 {
505     LV_ASSERT_OBJ(obj, MY_CLASS);
506     LV_ASSERT_NULL(ser);
507 
508     lv_chart_t * chart  = (lv_chart_t *)obj;
509 
510     if(chart->type != LV_CHART_TYPE_SCATTER) {
511         LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER");
512         return;
513     }
514 
515     ser->x_points[ser->start_point] = x_value;
516     ser->y_points[ser->start_point] = y_value;
517     ser->start_point = (ser->start_point + 1) % chart->point_cnt;
518     invalidate_point(obj, ser->start_point);
519 }
520 
lv_chart_set_value_by_id(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t id,int32_t value)521 void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t value)
522 {
523     LV_ASSERT_OBJ(obj, MY_CLASS);
524     LV_ASSERT_NULL(ser);
525     lv_chart_t * chart  = (lv_chart_t *)obj;
526 
527     if(id >= chart->point_cnt) return;
528     ser->y_points[id] = value;
529     invalidate_point(obj, id);
530 }
531 
lv_chart_set_value_by_id2(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t id,int32_t x_value,int32_t y_value)532 void lv_chart_set_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t x_value,
533                                int32_t y_value)
534 {
535     LV_ASSERT_OBJ(obj, MY_CLASS);
536     LV_ASSERT_NULL(ser);
537     lv_chart_t * chart  = (lv_chart_t *)obj;
538 
539     if(chart->type != LV_CHART_TYPE_SCATTER) {
540         LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER");
541         return;
542     }
543 
544     if(id >= chart->point_cnt) return;
545     ser->x_points[id] = x_value;
546     ser->y_points[id] = y_value;
547     invalidate_point(obj, id);
548 }
549 
lv_chart_set_ext_y_array(lv_obj_t * obj,lv_chart_series_t * ser,int32_t array[])550 void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[])
551 {
552     LV_ASSERT_OBJ(obj, MY_CLASS);
553     LV_ASSERT_NULL(ser);
554 
555     if(!ser->y_ext_buf_assigned && ser->y_points) lv_free(ser->y_points);
556     ser->y_ext_buf_assigned = true;
557     ser->y_points = array;
558     lv_obj_invalidate(obj);
559 }
560 
lv_chart_set_ext_x_array(lv_obj_t * obj,lv_chart_series_t * ser,int32_t array[])561 void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[])
562 {
563     LV_ASSERT_OBJ(obj, MY_CLASS);
564     LV_ASSERT_NULL(ser);
565 
566     if(!ser->x_ext_buf_assigned && ser->x_points) lv_free(ser->x_points);
567     ser->x_ext_buf_assigned = true;
568     ser->x_points = array;
569     lv_obj_invalidate(obj);
570 }
571 
lv_chart_get_y_array(const lv_obj_t * obj,lv_chart_series_t * ser)572 int32_t * lv_chart_get_y_array(const lv_obj_t * obj, lv_chart_series_t * ser)
573 {
574     LV_UNUSED(obj);
575     LV_ASSERT_OBJ(obj, MY_CLASS);
576     LV_ASSERT_NULL(ser);
577     return ser->y_points;
578 }
579 
lv_chart_get_x_array(const lv_obj_t * obj,lv_chart_series_t * ser)580 int32_t * lv_chart_get_x_array(const lv_obj_t * obj, lv_chart_series_t * ser)
581 {
582     LV_UNUSED(obj);
583     LV_ASSERT_OBJ(obj, MY_CLASS);
584     LV_ASSERT_NULL(ser);
585     return ser->x_points;
586 }
587 
lv_chart_get_pressed_point(const lv_obj_t * obj)588 uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj)
589 {
590     lv_chart_t * chart = (lv_chart_t *)obj;
591     return chart->pressed_point_id;
592 }
593 
lv_chart_get_first_point_center_offset(lv_obj_t * obj)594 int32_t lv_chart_get_first_point_center_offset(lv_obj_t * obj)
595 {
596     lv_chart_t * chart = (lv_chart_t *)obj;
597 
598     int32_t x_ofs = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
599     if(chart->type == LV_CHART_TYPE_BAR) {
600         lv_obj_update_layout(obj);
601         /*Gap between the columns on ~adjacent X*/
602         int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
603         int32_t w = lv_obj_get_content_width(obj);
604         int32_t block_w = (w + block_gap) / (chart->point_cnt);
605 
606         x_ofs += (block_w - block_gap) / 2;
607     }
608 
609     return x_ofs;
610 }
611 
612 /**********************
613  *   STATIC FUNCTIONS
614  **********************/
615 
lv_chart_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)616 static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
617 {
618     LV_UNUSED(class_p);
619     LV_TRACE_OBJ_CREATE("begin");
620 
621     lv_chart_t * chart = (lv_chart_t *)obj;
622 
623     lv_ll_init(&chart->series_ll, sizeof(lv_chart_series_t));
624     lv_ll_init(&chart->cursor_ll, sizeof(lv_chart_cursor_t));
625 
626     chart->ymin[0] = 0;
627     chart->xmin[0] = 0;
628     chart->ymin[1] = 0;
629     chart->xmin[1] = 0;
630     chart->ymax[0] = 100;
631     chart->xmax[0] = 100;
632     chart->ymax[1] = 100;
633     chart->xmax[1] = 100;
634 
635     chart->hdiv_cnt    = LV_CHART_HDIV_DEF;
636     chart->vdiv_cnt    = LV_CHART_VDIV_DEF;
637     chart->point_cnt   = LV_CHART_POINT_CNT_DEF;
638     chart->pressed_point_id  = LV_CHART_POINT_NONE;
639     chart->type        = LV_CHART_TYPE_LINE;
640     chart->update_mode = LV_CHART_UPDATE_MODE_SHIFT;
641 
642     LV_TRACE_OBJ_CREATE("finished");
643 }
644 
lv_chart_destructor(const lv_obj_class_t * class_p,lv_obj_t * obj)645 static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
646 {
647     LV_UNUSED(class_p);
648     LV_TRACE_OBJ_CREATE("begin");
649 
650     lv_chart_t * chart = (lv_chart_t *)obj;
651     lv_chart_series_t * ser;
652     while(chart->series_ll.head) {
653         ser = lv_ll_get_head(&chart->series_ll);
654         if(!ser) continue;
655 
656         if(!ser->y_ext_buf_assigned) lv_free(ser->y_points);
657         if(!ser->x_ext_buf_assigned) lv_free(ser->x_points);
658 
659         lv_ll_remove(&chart->series_ll, ser);
660         lv_free(ser);
661     }
662     lv_ll_clear(&chart->series_ll);
663 
664     lv_chart_cursor_t * cur;
665     while(chart->cursor_ll.head) {
666         cur = lv_ll_get_head(&chart->cursor_ll);
667         lv_ll_remove(&chart->cursor_ll, cur);
668         lv_free(cur);
669     }
670     lv_ll_clear(&chart->cursor_ll);
671 
672     LV_TRACE_OBJ_CREATE("finished");
673 }
674 
lv_chart_event(const lv_obj_class_t * class_p,lv_event_t * e)675 static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e)
676 {
677     LV_UNUSED(class_p);
678 
679     /*Call the ancestor's event handler*/
680     lv_result_t res;
681 
682     res = lv_obj_event_base(MY_CLASS, e);
683     if(res != LV_RESULT_OK) return;
684 
685     lv_event_code_t code = lv_event_get_code(e);
686     lv_obj_t * obj = lv_event_get_current_target(e);
687 
688     lv_chart_t * chart  = (lv_chart_t *)obj;
689     if(code == LV_EVENT_PRESSED) {
690         lv_indev_t * indev = lv_indev_active();
691         lv_point_t p;
692         lv_indev_get_point(indev, &p);
693 
694         p.x -= obj->coords.x1;
695         uint32_t id = get_index_from_x(obj, p.x + lv_obj_get_scroll_left(obj));
696         if(id != (uint32_t)chart->pressed_point_id) {
697             invalidate_point(obj, id);
698             invalidate_point(obj, chart->pressed_point_id);
699             chart->pressed_point_id = id;
700             lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL);
701         }
702     }
703     else if(code == LV_EVENT_RELEASED) {
704         invalidate_point(obj, chart->pressed_point_id);
705         chart->pressed_point_id = LV_CHART_POINT_NONE;
706     }
707     else if(code == LV_EVENT_DRAW_MAIN) {
708         lv_layer_t * layer = lv_event_get_layer(e);
709         draw_div_lines(obj, layer);
710 
711         if(lv_ll_is_empty(&chart->series_ll) == false) {
712             if(chart->type == LV_CHART_TYPE_LINE) draw_series_line(obj, layer);
713             else if(chart->type == LV_CHART_TYPE_BAR) draw_series_bar(obj, layer);
714             else if(chart->type == LV_CHART_TYPE_SCATTER) draw_series_scatter(obj, layer);
715         }
716 
717         draw_cursors(obj, layer);
718     }
719 }
720 
draw_div_lines(lv_obj_t * obj,lv_layer_t * layer)721 static void draw_div_lines(lv_obj_t * obj, lv_layer_t * layer)
722 {
723     lv_chart_t * chart  = (lv_chart_t *)obj;
724 
725     lv_area_t series_clip_area;
726     bool mask_ret = lv_area_intersect(&series_clip_area, &obj->coords, &layer->_clip_area);
727     if(mask_ret == false) return;
728 
729     const lv_area_t clip_area_ori = layer->_clip_area;
730     layer->_clip_area = series_clip_area;
731 
732     int16_t i;
733     int16_t i_start;
734     int16_t i_end;
735     int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
736     int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
737     int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
738     int32_t w = lv_obj_get_content_width(obj);
739     int32_t h = lv_obj_get_content_height(obj);
740 
741     lv_draw_line_dsc_t line_dsc;
742     lv_draw_line_dsc_init(&line_dsc);
743     line_dsc.base.layer = layer;
744     lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc);
745 
746     lv_opa_t border_opa = lv_obj_get_style_border_opa(obj, LV_PART_MAIN);
747     int32_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
748     lv_border_side_t border_side = lv_obj_get_style_border_side(obj, LV_PART_MAIN);
749 
750     int32_t scroll_left = lv_obj_get_scroll_left(obj);
751     int32_t scroll_top = lv_obj_get_scroll_top(obj);
752     if(chart->hdiv_cnt != 0) {
753         int32_t y_ofs = obj->coords.y1 + pad_top - scroll_top;
754         line_dsc.p1.x = obj->coords.x1;
755         line_dsc.p2.x = obj->coords.x2;
756 
757         i_start = 0;
758         i_end = chart->hdiv_cnt;
759         if(border_opa > LV_OPA_MIN && border_w > 0) {
760             if((border_side & LV_BORDER_SIDE_TOP) && (lv_obj_get_style_pad_top(obj, LV_PART_MAIN) == 0)) i_start++;
761             if((border_side & LV_BORDER_SIDE_BOTTOM) && (lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN) == 0)) i_end--;
762         }
763 
764         for(i = i_start; i < i_end; i++) {
765             line_dsc.p1.y = (int32_t)((int32_t)h * i) / (chart->hdiv_cnt - 1);
766             line_dsc.p1.y += y_ofs;
767             line_dsc.p2.y = line_dsc.p1.y;
768             line_dsc.base.id1 = i;
769 
770             lv_draw_line(layer, &line_dsc);
771         }
772     }
773 
774     if(chart->vdiv_cnt != 0) {
775         int32_t x_ofs = obj->coords.x1 + pad_left - scroll_left;
776         line_dsc.p1.y = obj->coords.y1;
777         line_dsc.p2.y = obj->coords.y2;
778         i_start = 0;
779         i_end = chart->vdiv_cnt;
780         if(border_opa > LV_OPA_MIN && border_w > 0) {
781             if((border_side & LV_BORDER_SIDE_LEFT) && (lv_obj_get_style_pad_left(obj, LV_PART_MAIN) == 0)) i_start++;
782             if((border_side & LV_BORDER_SIDE_RIGHT) && (lv_obj_get_style_pad_right(obj, LV_PART_MAIN) == 0)) i_end--;
783         }
784 
785         for(i = i_start; i < i_end; i++) {
786             line_dsc.p1.x = (int32_t)((int32_t)w * i) / (chart->vdiv_cnt - 1);
787             line_dsc.p1.x += x_ofs;
788             line_dsc.p2.x = line_dsc.p1.x;
789             line_dsc.base.id1 = i;
790 
791             lv_draw_line(layer, &line_dsc);
792         }
793     }
794 
795     layer->_clip_area = clip_area_ori;
796 }
797 
draw_series_line(lv_obj_t * obj,lv_layer_t * layer)798 static void draw_series_line(lv_obj_t * obj, lv_layer_t * layer)
799 {
800     lv_area_t clip_area;
801     if(lv_area_intersect(&clip_area, &obj->coords, &layer->_clip_area) == false) return;
802 
803     const lv_area_t clip_area_ori = layer->_clip_area;
804     layer->_clip_area = clip_area;
805 
806     lv_chart_t * chart  = (lv_chart_t *)obj;
807     if(chart->point_cnt < 2) return;
808 
809     uint32_t i;
810     int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
811     int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
812     int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
813     int32_t w     = lv_obj_get_content_width(obj);
814     int32_t h     = lv_obj_get_content_height(obj);
815     int32_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj);
816     int32_t y_ofs = obj->coords.y1 + pad_top - lv_obj_get_scroll_top(obj);
817     lv_chart_series_t * ser;
818 
819     lv_area_t series_clip_area;
820     bool mask_ret = lv_area_intersect(&series_clip_area, &obj->coords, &layer->_clip_area);
821     if(mask_ret == false) return;
822 
823     lv_draw_line_dsc_t line_dsc;
824     lv_draw_line_dsc_init(&line_dsc);
825     line_dsc.base.layer = layer;
826     lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc);
827 
828     lv_draw_rect_dsc_t point_dsc_default;
829     lv_draw_rect_dsc_init(&point_dsc_default);
830     point_dsc_default.base.layer = layer;
831     lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default);
832 
833     int32_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2;
834     int32_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2;
835 
836     /*Do not bother with line ending is the point will over it*/
837     if(LV_MIN(point_w, point_h) > line_dsc.width / 2) line_dsc.raw_end = 1;
838     if(line_dsc.width == 1) line_dsc.raw_end = 1;
839 
840     /*If there are at least as many points as pixels then draw only vertical lines*/
841     bool crowded_mode = (int32_t)chart->point_cnt >= w;
842 
843     line_dsc.base.id1 = lv_ll_get_len(&chart->series_ll) - 1;
844     point_dsc_default.base.id1 = line_dsc.base.id1;
845     /*Go through all data lines*/
846     LV_LL_READ_BACK(&chart->series_ll, ser) {
847         if(ser->hidden) {
848             line_dsc.base.id1--;
849             point_dsc_default.base.id1--;
850             continue;
851         }
852         line_dsc.color = ser->color;
853         point_dsc_default.bg_color = ser->color;
854         line_dsc.base.id2 = 0;
855         point_dsc_default.base.id2 = 0;
856 
857         int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
858 
859         line_dsc.p1.x = x_ofs;
860         line_dsc.p2.x = x_ofs;
861 
862         int32_t p_act = start_point;
863         int32_t p_prev = start_point;
864         int32_t y_tmp = (int32_t)((int32_t)ser->y_points[p_prev] - chart->ymin[ser->y_axis_sec]) * h;
865         y_tmp  = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
866         line_dsc.p2.y   = h - y_tmp + y_ofs;
867 
868         lv_value_precise_t y_min = line_dsc.p2.y;
869         lv_value_precise_t y_max = line_dsc.p2.y;
870 
871         for(i = 0; i < chart->point_cnt; i++) {
872             line_dsc.p1.x = line_dsc.p2.x;
873             line_dsc.p1.y = line_dsc.p2.y;
874 
875             if(line_dsc.p1.x > clip_area_ori.x2 + point_w + 1) break;
876             line_dsc.p2.x = (lv_value_precise_t)((w * i) / (chart->point_cnt - 1)) + x_ofs;
877 
878             p_act = (start_point + i) % chart->point_cnt;
879 
880             y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
881             y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
882             line_dsc.p2.y  = h - y_tmp + y_ofs;
883 
884             if(line_dsc.p2.x < clip_area_ori.x1 - point_w - 1) {
885                 p_prev = p_act;
886                 continue;
887             }
888 
889             /*Don't draw the first point. A second point is also required to draw the line*/
890             if(i != 0) {
891                 if(crowded_mode) {
892                     if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
893                         /*Draw only one vertical line between the min and max y-values on the same x-value*/
894                         y_max = LV_MAX(y_max, line_dsc.p2.y);
895                         y_min = LV_MIN(y_min, line_dsc.p2.y);
896                         if(line_dsc.p1.x != line_dsc.p2.x) {
897                             lv_value_precise_t y_cur = line_dsc.p2.y;
898                             line_dsc.p2.x--;         /*It's already on the next x value*/
899                             line_dsc.p1.x = line_dsc.p2.x;
900                             line_dsc.p1.y = y_min;
901                             line_dsc.p2.y = y_max;
902                             if(line_dsc.p1.y == line_dsc.p2.y) line_dsc.p2.y++;    /*If they are the same no line will be drawn*/
903                             lv_draw_line(layer, &line_dsc);
904                             line_dsc.p2.x++;         /*Compensate the previous x--*/
905                             y_min = y_cur;  /*Start the line of the next x from the current last y*/
906                             y_max = y_cur;
907                         }
908                     }
909                 }
910                 else {
911                     lv_area_t point_area;
912                     point_area.x1 = (int32_t)line_dsc.p1.x - point_w;
913                     point_area.x2 = (int32_t)line_dsc.p1.x + point_w;
914                     point_area.y1 = (int32_t)line_dsc.p1.y - point_h;
915                     point_area.y2 = (int32_t)line_dsc.p1.y + point_h;
916 
917                     if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
918                         line_dsc.base.id2 = i;
919                         lv_draw_line(layer, &line_dsc);
920                     }
921 
922                     if(point_w && point_h && ser->y_points[p_prev] != LV_CHART_POINT_NONE) {
923                         point_dsc_default.base.id2 = i - 1;
924                         lv_draw_rect(layer, &point_dsc_default, &point_area);
925                     }
926                 }
927 
928             }
929             p_prev = p_act;
930         }
931 
932         /*Draw the last point*/
933         if(!crowded_mode && i == chart->point_cnt) {
934 
935             if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
936                 lv_area_t point_area;
937                 point_area.x1 = (int32_t)line_dsc.p2.x - point_w;
938                 point_area.x2 = (int32_t)line_dsc.p2.x + point_w;
939                 point_area.y1 = (int32_t)line_dsc.p2.y - point_h;
940                 point_area.y2 = (int32_t)line_dsc.p2.y + point_h;
941                 point_dsc_default.base.id2 = i - 1;
942                 lv_draw_rect(layer, &point_dsc_default, &point_area);
943             }
944         }
945 
946         point_dsc_default.base.id1--;
947         line_dsc.base.id1--;
948     }
949 
950     layer->_clip_area = clip_area_ori;
951 }
952 
draw_series_scatter(lv_obj_t * obj,lv_layer_t * layer)953 static void draw_series_scatter(lv_obj_t * obj, lv_layer_t * layer)
954 {
955 
956     lv_area_t clip_area;
957     if(lv_area_intersect(&clip_area, &obj->coords, &layer->_clip_area) == false) return;
958 
959     const lv_area_t clip_area_ori = layer->_clip_area;
960     layer->_clip_area = clip_area;
961 
962     lv_chart_t * chart  = (lv_chart_t *)obj;
963 
964     uint32_t i;
965     int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
966     int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
967     int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
968     int32_t w     = lv_obj_get_content_width(obj);
969     int32_t h     = lv_obj_get_content_height(obj);
970     int32_t x_ofs = obj->coords.x1 + pad_left + border_width - lv_obj_get_scroll_left(obj);
971     int32_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj);
972     lv_chart_series_t * ser;
973 
974     lv_draw_line_dsc_t line_dsc;
975     lv_draw_line_dsc_init(&line_dsc);
976     line_dsc.base.layer = layer;
977     lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc);
978 
979     lv_draw_rect_dsc_t point_dsc_default;
980     lv_draw_rect_dsc_init(&point_dsc_default);
981     point_dsc_default.base.layer = layer;
982     lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default);
983 
984     int32_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2;
985     int32_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2;
986 
987     /*Do not bother with line ending is the point will over it*/
988     if(LV_MIN(point_w, point_h) > line_dsc.width / 2) line_dsc.raw_end = 1;
989     if(line_dsc.width == 1) line_dsc.raw_end = 1;
990 
991     /*Go through all data lines*/
992     LV_LL_READ_BACK(&chart->series_ll, ser) {
993         if(ser->hidden) continue;
994         line_dsc.color = ser->color;
995         point_dsc_default.bg_color = ser->color;
996 
997         int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
998 
999         line_dsc.p1.x = x_ofs;
1000         line_dsc.p2.x = x_ofs;
1001 
1002         int32_t p_act = start_point;
1003         int32_t p_prev = start_point;
1004         if(ser->y_points[p_act] != LV_CHART_POINT_CNT_DEF) {
1005             line_dsc.p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
1006             line_dsc.p2.x += x_ofs;
1007 
1008             line_dsc.p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
1009             line_dsc.p2.y = h - line_dsc.p2.y;
1010             line_dsc.p2.y += y_ofs;
1011         }
1012         else {
1013             line_dsc.p2.x = (lv_value_precise_t)LV_COORD_MIN;
1014             line_dsc.p2.y = (lv_value_precise_t)LV_COORD_MIN;
1015         }
1016 
1017         for(i = 0; i < chart->point_cnt; i++) {
1018             line_dsc.p1.x = line_dsc.p2.x;
1019             line_dsc.p1.y = line_dsc.p2.y;
1020 
1021             p_act = (start_point + i) % chart->point_cnt;
1022             if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1023                 line_dsc.p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
1024                 line_dsc.p2.y = h - line_dsc.p2.y;
1025                 line_dsc.p2.y += y_ofs;
1026 
1027                 line_dsc.p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
1028                 line_dsc.p2.x += x_ofs;
1029             }
1030             else {
1031                 p_prev = p_act;
1032                 continue;
1033             }
1034 
1035             /*Don't draw the first point. A second point is also required to draw the line*/
1036             if(i != 0) {
1037                 lv_area_t point_area;
1038                 point_area.x1 = (int32_t)line_dsc.p1.x - point_w;
1039                 point_area.x2 = (int32_t)line_dsc.p1.x + point_w;
1040                 point_area.y1 = (int32_t)line_dsc.p1.y - point_h;
1041                 point_area.y2 = (int32_t)line_dsc.p1.y + point_h;
1042 
1043                 if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1044                     line_dsc.base.id2 = i;
1045                     lv_draw_line(layer, &line_dsc);
1046                     if(point_w && point_h) {
1047                         point_dsc_default.base.id2 = i;
1048                         lv_draw_rect(layer, &point_dsc_default, &point_area);
1049                     }
1050                 }
1051 
1052                 p_prev = p_act;
1053             }
1054 
1055             /*Draw the last point*/
1056             if(i == chart->point_cnt) {
1057 
1058                 if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1059                     lv_area_t point_area;
1060                     point_area.x1 = (int32_t)line_dsc.p2.x - point_w;
1061                     point_area.x2 = (int32_t)line_dsc.p2.x + point_w;
1062                     point_area.y1 = (int32_t)line_dsc.p2.y - point_h;
1063                     point_area.y2 = (int32_t)line_dsc.p2.y + point_h;
1064 
1065                     point_dsc_default.base.id2 = i;
1066                     lv_draw_rect(layer, &point_dsc_default, &point_area);
1067                 }
1068             }
1069         }
1070         line_dsc.base.id1++;
1071         point_dsc_default.base.id1++;
1072         layer->_clip_area = clip_area_ori;
1073     }
1074 }
1075 
draw_series_bar(lv_obj_t * obj,lv_layer_t * layer)1076 static void draw_series_bar(lv_obj_t * obj, lv_layer_t * layer)
1077 {
1078     lv_area_t clip_area;
1079     if(lv_area_intersect(&clip_area, &obj->coords, &layer->_clip_area) == false) return;
1080 
1081     const lv_area_t clip_area_ori = layer->_clip_area;
1082     layer->_clip_area = clip_area;
1083 
1084     lv_chart_t * chart  = (lv_chart_t *)obj;
1085 
1086     uint32_t i;
1087     lv_area_t col_a;
1088     int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1089     int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
1090     int32_t w = lv_obj_get_content_width(obj);
1091     int32_t h  = lv_obj_get_content_height(obj);
1092     int32_t y_tmp;
1093     lv_chart_series_t * ser;
1094     uint32_t ser_cnt = lv_ll_get_len(&chart->series_ll);
1095     if(ser_cnt == 0) {
1096         return;
1097     }
1098     int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);  /*Gap between the column on ~adjacent X*/
1099     int32_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;
1100     int32_t ser_gap = lv_obj_get_style_pad_column(obj, LV_PART_ITEMS); /*Gap between the columns on the ~same X*/
1101     int32_t col_w = (block_w - (ser_cnt - 1) * ser_gap) / ser_cnt;
1102     if(col_w < 1) col_w  = 1;
1103 
1104     int32_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1105     int32_t x_ofs = pad_left - lv_obj_get_scroll_left(obj) + border_w;
1106     int32_t y_ofs = pad_top - lv_obj_get_scroll_top(obj) + border_w;
1107 
1108     lv_draw_rect_dsc_t col_dsc;
1109     lv_draw_rect_dsc_init(&col_dsc);
1110     col_dsc.base.layer = layer;
1111     lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc);
1112     col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE;
1113     col_dsc.bg_opa = LV_OPA_COVER;
1114 
1115     /*Make the cols longer with `radius` to clip the rounding from the bottom*/
1116     col_a.y2 = obj->coords.y2 + col_dsc.radius;
1117 
1118     /*Go through all points*/
1119     for(i = 0; i < chart->point_cnt; i++) {
1120         int32_t x_act;
1121         if(chart->point_cnt <= 1) {
1122             x_act = obj->coords.x1 + x_ofs;
1123         }
1124         else {
1125             x_act = (int32_t)((int32_t)(w - block_w) * i) / (chart->point_cnt - 1) + obj->coords.x1 + x_ofs;
1126         }
1127 
1128         col_dsc.base.id2 = i;
1129         col_dsc.base.id1 = 0;
1130 
1131         /*Draw the current point of all data line*/
1132         LV_LL_READ(&chart->series_ll, ser) {
1133             if(ser->hidden) continue;
1134 
1135             int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
1136 
1137             col_a.x1 = x_act;
1138             col_a.x2 = col_a.x1 + col_w - 1;
1139             x_act += col_w + ser_gap;
1140 
1141             if(col_a.x2 < clip_area.x1) {
1142                 col_dsc.base.id1++;
1143                 continue;
1144             }
1145             if(col_a.x1 > clip_area.x2) break;
1146 
1147             col_dsc.bg_color = ser->color;
1148 
1149             int32_t p_act = (start_point + i) % chart->point_cnt;
1150             y_tmp            = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
1151             y_tmp            = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
1152             col_a.y1         = h - y_tmp + obj->coords.y1 + y_ofs;
1153 
1154             if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1155                 lv_draw_rect(layer, &col_dsc, &col_a);
1156             }
1157             col_dsc.base.id1++;
1158         }
1159     }
1160     layer->_clip_area = clip_area_ori;
1161 }
1162 
draw_cursors(lv_obj_t * obj,lv_layer_t * layer)1163 static void draw_cursors(lv_obj_t * obj, lv_layer_t * layer)
1164 {
1165     LV_ASSERT_OBJ(obj, MY_CLASS);
1166 
1167     lv_chart_t * chart  = (lv_chart_t *)obj;
1168     if(lv_ll_is_empty(&chart->cursor_ll)) return;
1169 
1170     lv_area_t clip_area;
1171     if(!lv_area_intersect(&clip_area, &layer->_clip_area, &obj->coords)) return;
1172 
1173     const lv_area_t clip_area_ori = layer->_clip_area;
1174     layer->_clip_area = clip_area;
1175 
1176     lv_chart_cursor_t * cursor;
1177 
1178     lv_draw_line_dsc_t line_dsc_ori;
1179     lv_draw_line_dsc_init(&line_dsc_ori);
1180     line_dsc_ori.base.layer = layer;
1181     lv_obj_init_draw_line_dsc(obj, LV_PART_CURSOR, &line_dsc_ori);
1182 
1183     lv_draw_rect_dsc_t point_dsc_ori;
1184     lv_draw_rect_dsc_init(&point_dsc_ori);
1185     point_dsc_ori.base.layer = layer;
1186     lv_obj_init_draw_rect_dsc(obj, LV_PART_CURSOR, &point_dsc_ori);
1187 
1188     lv_draw_line_dsc_t line_dsc;
1189     lv_draw_rect_dsc_t point_dsc_tmp;
1190 
1191     int32_t point_w = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
1192     int32_t point_h = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
1193 
1194     /*Go through all cursor lines*/
1195     LV_LL_READ_BACK(&chart->cursor_ll, cursor) {
1196         lv_memcpy(&line_dsc, &line_dsc_ori, sizeof(lv_draw_line_dsc_t));
1197         lv_memcpy(&point_dsc_tmp, &point_dsc_ori, sizeof(lv_draw_rect_dsc_t));
1198         line_dsc.color = cursor->color;
1199         point_dsc_tmp.bg_color = cursor->color;
1200 
1201         int32_t cx;
1202         int32_t cy;
1203         if(cursor->pos_set) {
1204             cx = cursor->pos.x;
1205             cy = cursor->pos.y;
1206         }
1207         else {
1208             if(cursor->point_id == LV_CHART_POINT_NONE) continue;
1209             lv_point_t p;
1210             lv_chart_get_point_pos_by_id(obj, cursor->ser, cursor->point_id, &p);
1211             cx = p.x;
1212             cy = p.y;
1213         }
1214 
1215         cx += obj->coords.x1;
1216         cy += obj->coords.y1;
1217 
1218         lv_area_t point_area;
1219         bool draw_point = point_w && point_h;
1220         point_area.x1 = cx - point_w;
1221         point_area.x2 = cx + point_w;
1222         point_area.y1 = cy - point_h;
1223         point_area.y2 = cy + point_h;
1224 
1225         if(cursor->dir & LV_DIR_HOR) {
1226             line_dsc.p1.x = cursor->dir & LV_DIR_LEFT ? obj->coords.x1 : cx;
1227             line_dsc.p1.y = cy;
1228             line_dsc.p2.x = cursor->dir & LV_DIR_RIGHT ? obj->coords.x2 : cx;
1229             line_dsc.p2.y = line_dsc.p1.y;
1230 
1231             line_dsc.base.id2 = 0;
1232             point_dsc_tmp.base.id2 = 0;
1233 
1234             lv_draw_line(layer, &line_dsc);
1235 
1236             if(draw_point) {
1237                 lv_draw_rect(layer, &point_dsc_tmp, &point_area);
1238             }
1239         }
1240 
1241         if(cursor->dir & LV_DIR_VER) {
1242             line_dsc.p1.x = cx;
1243             line_dsc.p1.y = cursor->dir & LV_DIR_TOP ? obj->coords.y1 : cy;
1244             line_dsc.p2.x = line_dsc.p1.x;
1245             line_dsc.p2.y = cursor->dir & LV_DIR_BOTTOM ? obj->coords.y2 : cy;
1246 
1247             line_dsc.base.id2 = 1;
1248             point_dsc_tmp.base.id2 = 1;
1249 
1250             lv_draw_line(layer, &line_dsc);
1251 
1252             if(draw_point) {
1253                 lv_draw_rect(layer, &point_dsc_tmp, &point_area);
1254             }
1255         }
1256         line_dsc_ori.base.id1++;
1257         point_dsc_ori.base.id1++;
1258     }
1259 
1260     layer->_clip_area = clip_area_ori;
1261 }
1262 
1263 /**
1264  * Get the nearest index to an X coordinate
1265  * @param chart pointer to a chart object
1266  * @param coord the coordination of the point relative to the series area.
1267  * @return the found index
1268  */
get_index_from_x(lv_obj_t * obj,int32_t x)1269 static uint32_t get_index_from_x(lv_obj_t * obj, int32_t x)
1270 {
1271     lv_chart_t * chart  = (lv_chart_t *)obj;
1272     int32_t w = lv_obj_get_content_width(obj);
1273     int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1274     x -= pad_left;
1275 
1276     if(x < 0) return 0;
1277     if(x > w) return chart->point_cnt - 1;
1278     if(chart->type == LV_CHART_TYPE_LINE) return (x * (chart->point_cnt - 1) + w / 2) / w;
1279     if(chart->type == LV_CHART_TYPE_BAR) return (x * chart->point_cnt) / w;
1280 
1281     return 0;
1282 }
1283 
invalidate_point(lv_obj_t * obj,uint32_t i)1284 static void invalidate_point(lv_obj_t * obj, uint32_t i)
1285 {
1286     lv_chart_t * chart  = (lv_chart_t *)obj;
1287     if(i >= chart->point_cnt) return;
1288 
1289     int32_t w  = lv_obj_get_content_width(obj);
1290     int32_t scroll_left = lv_obj_get_scroll_left(obj);
1291 
1292     /*In shift mode the whole chart changes so the whole object*/
1293     if(chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT) {
1294         lv_obj_invalidate(obj);
1295         return;
1296     }
1297 
1298     if(chart->type == LV_CHART_TYPE_LINE) {
1299         int32_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1300         int32_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1301         int32_t x_ofs = obj->coords.x1 + pleft + bwidth - scroll_left;
1302         int32_t line_width = lv_obj_get_style_line_width(obj, LV_PART_ITEMS);
1303         int32_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR);
1304 
1305         lv_area_t coords;
1306         lv_area_copy(&coords, &obj->coords);
1307         coords.y1 -= line_width + point_w;
1308         coords.y2 += line_width + point_w;
1309 
1310         if(i < chart->point_cnt - 1) {
1311             coords.x1 = ((w * i) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
1312             coords.x2 = ((w * (i + 1)) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
1313             lv_obj_invalidate_area(obj, &coords);
1314         }
1315 
1316         if(i > 0) {
1317             coords.x1 = ((w * (i - 1)) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
1318             coords.x2 = ((w * i) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
1319             lv_obj_invalidate_area(obj, &coords);
1320         }
1321     }
1322     else if(chart->type == LV_CHART_TYPE_BAR) {
1323         lv_area_t col_a;
1324         /*Gap between the column on ~adjacent X*/
1325         int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
1326 
1327         int32_t block_w = (w + block_gap) / chart->point_cnt;
1328 
1329         int32_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1330         int32_t x_act;
1331         x_act = (int32_t)((int32_t)(block_w) * i) ;
1332         x_act += obj->coords.x1 + bwidth + lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1333 
1334         lv_obj_get_coords(obj, &col_a);
1335         col_a.x1 = x_act - scroll_left;
1336         col_a.x2 = col_a.x1 + block_w;
1337         col_a.x1 -= block_gap;
1338 
1339         lv_obj_invalidate_area(obj, &col_a);
1340     }
1341     else {
1342         lv_obj_invalidate(obj);
1343     }
1344 }
1345 
new_points_alloc(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t cnt,int32_t ** a)1346 static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, int32_t ** a)
1347 {
1348     if((*a) == NULL) return;
1349 
1350     lv_chart_t * chart = (lv_chart_t *) obj;
1351     uint32_t point_cnt_old = chart->point_cnt;
1352     uint32_t i;
1353 
1354     if(ser->start_point != 0) {
1355         int32_t * new_points = lv_malloc(sizeof(int32_t) * cnt);
1356         LV_ASSERT_MALLOC(new_points);
1357         if(new_points == NULL) return;
1358 
1359         if(cnt >= point_cnt_old) {
1360             for(i = 0; i < point_cnt_old; i++) {
1361                 new_points[i] =
1362                     (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
1363             }
1364             for(i = point_cnt_old; i < cnt; i++) {
1365                 new_points[i] = LV_CHART_POINT_NONE; /*Fill up the rest with default value*/
1366             }
1367         }
1368         else {
1369             for(i = 0; i < cnt; i++) {
1370                 new_points[i] =
1371                     (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
1372             }
1373         }
1374 
1375         /*Switch over pointer from old to new*/
1376         lv_free((*a));
1377         (*a) = new_points;
1378     }
1379     else {
1380         (*a) = lv_realloc((*a), sizeof(int32_t) * cnt);
1381         LV_ASSERT_MALLOC((*a));
1382         if((*a) == NULL) return;
1383         /*Initialize the new points*/
1384         if(cnt > point_cnt_old) {
1385             for(i = point_cnt_old - 1; i < cnt; i++) {
1386                 (*a)[i] = LV_CHART_POINT_NONE;
1387             }
1388         }
1389     }
1390 }
1391 
1392 #endif
1393