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 "../../../misc/lv_assert.h"
13 
14 /*********************
15  *      DEFINES
16  *********************/
17 #define MY_CLASS &lv_chart_class
18 
19 #define LV_CHART_HDIV_DEF 3
20 #define LV_CHART_VDIV_DEF 5
21 #define LV_CHART_POINT_CNT_DEF 10
22 #define LV_CHART_LABEL_MAX_TEXT_LENGTH 16
23 
24 /**********************
25  *      TYPEDEFS
26  **********************/
27 
28 /**********************
29  *  STATIC PROTOTYPES
30  **********************/
31 static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
32 static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
33 static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e);
34 
35 static void draw_div_lines(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
36 static void draw_series_line(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
37 static void draw_series_bar(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
38 static void draw_series_scatter(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
39 static void draw_cursors(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
40 static void draw_axes(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
41 static uint32_t get_index_from_x(lv_obj_t * obj, lv_coord_t x);
42 static void invalidate_point(lv_obj_t * obj, uint16_t i);
43 static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, lv_coord_t ** a);
44 lv_chart_tick_dsc_t * get_tick_gsc(lv_obj_t * obj, lv_chart_axis_t axis);
45 
46 /**********************
47  *  STATIC VARIABLES
48  **********************/
49 const lv_obj_class_t lv_chart_class = {
50     .constructor_cb = lv_chart_constructor,
51     .destructor_cb = lv_chart_destructor,
52     .event_cb = lv_chart_event,
53     .width_def = LV_PCT(100),
54     .height_def = LV_DPI_DEF * 2,
55     .instance_size = sizeof(lv_chart_t),
56     .base_class = &lv_obj_class
57 };
58 
59 /**********************
60  *      MACROS
61  **********************/
62 
63 /**********************
64  *   GLOBAL FUNCTIONS
65  **********************/
66 
lv_chart_create(lv_obj_t * parent)67 lv_obj_t * lv_chart_create(lv_obj_t * parent)
68 {
69     LV_LOG_INFO("begin");
70     lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
71     lv_obj_class_init_obj(obj);
72     return obj;
73 }
74 
lv_chart_set_type(lv_obj_t * obj,lv_chart_type_t type)75 void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type)
76 {
77     LV_ASSERT_OBJ(obj, MY_CLASS);
78 
79     lv_chart_t * chart  = (lv_chart_t *)obj;
80     if(chart->type == type) return;
81 
82     if(chart->type == LV_CHART_TYPE_SCATTER) {
83         lv_chart_series_t * ser;
84         _LV_LL_READ_BACK(&chart->series_ll, ser) {
85             lv_mem_free(ser->x_points);
86             ser->x_points = NULL;
87         }
88     }
89 
90     if(type == LV_CHART_TYPE_SCATTER) {
91         lv_chart_series_t * ser;
92         _LV_LL_READ_BACK(&chart->series_ll, ser) {
93             ser->x_points = lv_mem_alloc(sizeof(lv_point_t) * chart->point_cnt);
94             LV_ASSERT_MALLOC(ser->x_points);
95             if(ser->x_points == NULL) return;
96         }
97     }
98 
99     chart->type = type;
100 
101     lv_chart_refresh(obj);
102 }
103 
lv_chart_set_point_count(lv_obj_t * obj,uint16_t cnt)104 void lv_chart_set_point_count(lv_obj_t * obj, uint16_t cnt)
105 {
106     LV_ASSERT_OBJ(obj, MY_CLASS);
107 
108     lv_chart_t * chart  = (lv_chart_t *)obj;
109     if(chart->point_cnt == cnt) return;
110 
111     lv_chart_series_t * ser;
112 
113     if(cnt < 1) cnt = 1;
114 
115     _LV_LL_READ_BACK(&chart->series_ll, ser) {
116         if(chart->type == LV_CHART_TYPE_SCATTER) {
117             if(!ser->x_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->x_points);
118         }
119         if(!ser->y_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->y_points);
120         ser->start_point = 0;
121     }
122 
123     chart->point_cnt = cnt;
124 
125     lv_chart_refresh(obj);
126 }
127 
lv_chart_set_range(lv_obj_t * obj,lv_chart_axis_t axis,lv_coord_t min,lv_coord_t max)128 void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t min, lv_coord_t max)
129 {
130     LV_ASSERT_OBJ(obj, MY_CLASS);
131 
132     max = max == min ? max + 1 : max;
133 
134     lv_chart_t * chart  = (lv_chart_t *)obj;
135     switch(axis) {
136         case LV_CHART_AXIS_PRIMARY_Y:
137             chart->ymin[0] = min;
138             chart->ymax[0] = max;
139             break;
140         case LV_CHART_AXIS_SECONDARY_Y:
141             chart->ymin[1] = min;
142             chart->ymax[1] = max;
143             break;
144         case LV_CHART_AXIS_PRIMARY_X:
145             chart->xmin[0] = min;
146             chart->xmax[0] = max;
147             break;
148         case LV_CHART_AXIS_SECONDARY_X:
149             chart->xmin[1] = min;
150             chart->xmax[1] = max;
151             break;
152         default:
153             LV_LOG_WARN("Invalid axis: %d", axis);
154             return;
155     }
156 
157     lv_chart_refresh(obj);
158 }
159 
lv_chart_set_update_mode(lv_obj_t * obj,lv_chart_update_mode_t update_mode)160 void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode)
161 {
162     LV_ASSERT_OBJ(obj, MY_CLASS);
163 
164     lv_chart_t * chart  = (lv_chart_t *)obj;
165     if(chart->update_mode == update_mode) return;
166 
167     chart->update_mode = update_mode;
168     lv_obj_invalidate(obj);
169 }
170 
lv_chart_set_div_line_count(lv_obj_t * obj,uint8_t hdiv,uint8_t vdiv)171 void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv)
172 {
173     LV_ASSERT_OBJ(obj, MY_CLASS);
174 
175     lv_chart_t * chart  = (lv_chart_t *)obj;
176     if(chart->hdiv_cnt == hdiv && chart->vdiv_cnt == vdiv) return;
177 
178     chart->hdiv_cnt = hdiv;
179     chart->vdiv_cnt = vdiv;
180 
181     lv_obj_invalidate(obj);
182 }
183 
184 
lv_chart_set_zoom_x(lv_obj_t * obj,uint16_t zoom_x)185 void lv_chart_set_zoom_x(lv_obj_t * obj, uint16_t zoom_x)
186 {
187     LV_ASSERT_OBJ(obj, MY_CLASS);
188 
189     lv_chart_t * chart  = (lv_chart_t *)obj;
190     if(chart->zoom_x == zoom_x) return;
191 
192     chart->zoom_x = zoom_x;
193     lv_obj_refresh_self_size(obj);
194     /*Be the chart doesn't remain scrolled out*/
195     lv_obj_readjust_scroll(obj, LV_ANIM_OFF);
196     lv_obj_invalidate(obj);
197 }
198 
lv_chart_set_zoom_y(lv_obj_t * obj,uint16_t zoom_y)199 void lv_chart_set_zoom_y(lv_obj_t * obj, uint16_t zoom_y)
200 {
201     LV_ASSERT_OBJ(obj, MY_CLASS);
202 
203     lv_chart_t * chart  = (lv_chart_t *)obj;
204     if(chart->zoom_y == zoom_y) return;
205 
206     chart->zoom_y = zoom_y;
207     lv_obj_refresh_self_size(obj);
208     /*Be the chart doesn't remain scrolled out*/
209     lv_obj_readjust_scroll(obj, LV_ANIM_OFF);
210     lv_obj_invalidate(obj);
211 }
212 
lv_chart_get_zoom_x(const lv_obj_t * obj)213 uint16_t lv_chart_get_zoom_x(const lv_obj_t * obj)
214 {
215     LV_ASSERT_OBJ(obj, MY_CLASS);
216 
217     lv_chart_t * chart  = (lv_chart_t *)obj;
218     return chart->zoom_x;
219 }
220 
lv_chart_get_zoom_y(const lv_obj_t * obj)221 uint16_t lv_chart_get_zoom_y(const lv_obj_t * obj)
222 {
223     LV_ASSERT_OBJ(obj, MY_CLASS);
224 
225     lv_chart_t * chart  = (lv_chart_t *)obj;
226     return chart->zoom_y;
227 }
228 
lv_chart_set_axis_tick(lv_obj_t * obj,lv_chart_axis_t axis,lv_coord_t major_len,lv_coord_t minor_len,lv_coord_t major_cnt,lv_coord_t minor_cnt,bool label_en,lv_coord_t draw_size)229 void lv_chart_set_axis_tick(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t major_len, lv_coord_t minor_len,
230                             lv_coord_t major_cnt, lv_coord_t minor_cnt, bool label_en, lv_coord_t draw_size)
231 {
232     LV_ASSERT_OBJ(obj, MY_CLASS);
233 
234     lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis);
235     t->major_len = major_len;
236     t->minor_len = minor_len;
237     t->minor_cnt = minor_cnt;
238     t->major_cnt = major_cnt;
239     t->label_en = label_en;
240     t->draw_size = draw_size;
241 
242     lv_obj_refresh_ext_draw_size(obj);
243     lv_obj_invalidate(obj);
244 }
245 
lv_chart_get_type(const lv_obj_t * obj)246 lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj)
247 {
248     LV_ASSERT_OBJ(obj, MY_CLASS);
249 
250     lv_chart_t * chart  = (lv_chart_t *)obj;
251     return chart->type;
252 }
253 
lv_chart_get_point_count(const lv_obj_t * obj)254 uint16_t lv_chart_get_point_count(const lv_obj_t * obj)
255 {
256     LV_ASSERT_OBJ(obj, MY_CLASS);
257 
258     lv_chart_t * chart  = (lv_chart_t *)obj;
259     return chart->point_cnt;
260 }
261 
lv_chart_get_x_start_point(const lv_obj_t * obj,lv_chart_series_t * ser)262 uint16_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser)
263 {
264     LV_ASSERT_NULL(ser);
265     lv_chart_t * chart  = (lv_chart_t *)obj;
266 
267     return chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
268 }
269 
lv_chart_get_point_pos_by_id(lv_obj_t * obj,lv_chart_series_t * ser,uint16_t id,lv_point_t * p_out)270 void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_point_t * p_out)
271 {
272     LV_ASSERT_NULL(obj);
273     LV_ASSERT_NULL(ser);
274     LV_ASSERT_OBJ(obj, MY_CLASS);
275 
276     lv_chart_t * chart  = (lv_chart_t *)obj;
277     if(id >= chart->point_cnt) {
278         LV_LOG_WARN("Invalid index: %d", id);
279         p_out->x = 0;
280         p_out->y = 0;
281         return;
282     }
283 
284     lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
285     lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
286 
287     if(chart->type == LV_CHART_TYPE_LINE) {
288         p_out->x = (w * id) / (chart->point_cnt - 1);
289     }
290     else if(chart->type == LV_CHART_TYPE_SCATTER) {
291         p_out->x = lv_map(ser->x_points[id], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
292     }
293     else if(chart->type == LV_CHART_TYPE_BAR) {
294         uint32_t ser_cnt = _lv_ll_get_len(&chart->series_ll);
295         /*Gap between the column on the X tick*/
296         int32_t ser_gap = ((int32_t)lv_obj_get_style_pad_column(obj, LV_PART_ITEMS) * chart->zoom_x) >> 8;
297 
298         /*Gap between the columns on adjacent X ticks*/
299         int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, LV_PART_MAIN) * chart->zoom_x) >> 8;
300 
301         lv_coord_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;
302 
303         lv_chart_series_t * ser_i = NULL;
304         uint32_t ser_idx = 0;
305         _LV_LL_READ_BACK(&chart->series_ll, ser_i) {
306             if(ser_i == ser) break;
307             ser_idx++;
308         }
309 
310         p_out->x = (int32_t)((int32_t)(w + block_gap) * id) / chart->point_cnt;
311         p_out->x += block_w * ser_idx / ser_cnt;
312 
313         lv_coord_t col_w = (block_w - (ser_gap * (ser_cnt - 1))) / ser_cnt;
314         p_out->x += col_w / 2;
315     }
316 
317     lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
318     p_out->x += lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
319     p_out->x -= lv_obj_get_scroll_left(obj);
320 
321     uint32_t start_point = lv_chart_get_x_start_point(obj, ser);
322     id = ((int32_t)start_point + id) % chart->point_cnt;
323     int32_t temp_y = 0;
324     temp_y = (int32_t)((int32_t)ser->y_points[id] - chart->ymin[ser->y_axis_sec]) * h;
325     temp_y = temp_y / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
326     p_out->y = h - temp_y;
327     p_out->y += lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
328     p_out->y -= lv_obj_get_scroll_top(obj);
329 }
330 
lv_chart_refresh(lv_obj_t * obj)331 void lv_chart_refresh(lv_obj_t * obj)
332 {
333     LV_ASSERT_OBJ(obj, MY_CLASS);
334 
335     lv_obj_invalidate(obj);
336 }
337 
338 /*======================
339  * Series
340  *=====================*/
341 
lv_chart_add_series(lv_obj_t * obj,lv_color_t color,lv_chart_axis_t axis)342 lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis)
343 {
344     LV_LOG_INFO("begin");
345 
346     LV_ASSERT_OBJ(obj, MY_CLASS);
347 
348     lv_chart_t * chart    = (lv_chart_t *)obj;
349     lv_chart_series_t * ser = _lv_ll_ins_head(&chart->series_ll);
350     LV_ASSERT_MALLOC(ser);
351     if(ser == NULL) return NULL;
352 
353     lv_coord_t def = LV_CHART_POINT_NONE;
354 
355     ser->color  = color;
356     ser->y_points = lv_mem_alloc(sizeof(lv_coord_t) * chart->point_cnt);
357     LV_ASSERT_MALLOC(ser->y_points);
358 
359     if(chart->type == LV_CHART_TYPE_SCATTER) {
360         ser->x_points = lv_mem_alloc(sizeof(lv_coord_t) * chart->point_cnt);
361         LV_ASSERT_MALLOC(ser->x_points);
362     }
363     if(ser->y_points == NULL) {
364         _lv_ll_remove(&chart->series_ll, ser);
365         lv_mem_free(ser);
366         return NULL;
367     }
368 
369     ser->start_point = 0;
370     ser->y_ext_buf_assigned = false;
371     ser->hidden = 0;
372     ser->x_axis_sec = axis & LV_CHART_AXIS_SECONDARY_X ? 1 : 0;
373     ser->y_axis_sec = axis & LV_CHART_AXIS_SECONDARY_Y ? 1 : 0;
374 
375     uint16_t i;
376     lv_coord_t * p_tmp = ser->y_points;
377     for(i = 0; i < chart->point_cnt; i++) {
378         *p_tmp = def;
379         p_tmp++;
380     }
381 
382     return ser;
383 }
384 
lv_chart_remove_series(lv_obj_t * obj,lv_chart_series_t * series)385 void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series)
386 {
387     LV_ASSERT_OBJ(obj, MY_CLASS);
388     LV_ASSERT_NULL(series);
389 
390     lv_chart_t * chart    = (lv_chart_t *)obj;
391     if(!series->y_ext_buf_assigned && series->y_points) lv_mem_free(series->y_points);
392 
393     _lv_ll_remove(&chart->series_ll, series);
394     lv_mem_free(series);
395 
396     return;
397 }
398 
lv_chart_hide_series(lv_obj_t * chart,lv_chart_series_t * series,bool hide)399 void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide)
400 {
401     LV_ASSERT_OBJ(chart, MY_CLASS);
402     LV_ASSERT_NULL(series);
403 
404     series->hidden = hide ? 1 : 0;
405     lv_chart_refresh(chart);
406 }
407 
408 
lv_chart_set_series_color(lv_obj_t * chart,lv_chart_series_t * series,lv_color_t color)409 void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color)
410 {
411     LV_ASSERT_OBJ(chart, MY_CLASS);
412     LV_ASSERT_NULL(series);
413 
414     series->color = color;
415     lv_chart_refresh(chart);
416 }
417 
lv_chart_set_x_start_point(lv_obj_t * obj,lv_chart_series_t * ser,uint16_t id)418 void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id)
419 {
420     LV_ASSERT_OBJ(obj, MY_CLASS);
421     LV_ASSERT_NULL(ser);
422 
423     lv_chart_t * chart  = (lv_chart_t *)obj;
424     if(id >= chart->point_cnt) return;
425     ser->start_point = id;
426 }
427 
lv_chart_get_series_next(const lv_obj_t * obj,const lv_chart_series_t * ser)428 lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * obj, const lv_chart_series_t * ser)
429 {
430     LV_ASSERT_OBJ(obj, MY_CLASS);
431 
432     lv_chart_t * chart  = (lv_chart_t *)obj;
433     if(ser == NULL) return _lv_ll_get_head(&chart->series_ll);
434     else return _lv_ll_get_next(&chart->series_ll, ser);
435 }
436 
437 /*=====================
438  * Cursor
439  *====================*/
440 
441 /**
442  * Add a cursor with a given color
443  * @param chart     pointer to chart object
444  * @param color     color of the cursor
445  * @param dir       direction of the cursor. `LV_DIR_RIGHT/LEFT/TOP/DOWN/HOR/VER/ALL`. OR-ed values are possible
446  * @return          pointer to the created cursor
447  */
lv_chart_add_cursor(lv_obj_t * obj,lv_color_t color,lv_dir_t dir)448 lv_chart_cursor_t  * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir)
449 {
450     LV_ASSERT_OBJ(obj, MY_CLASS);
451 
452     lv_chart_t * chart  = (lv_chart_t *)obj;
453     lv_chart_cursor_t * cursor = _lv_ll_ins_head(&chart->cursor_ll);
454     LV_ASSERT_MALLOC(cursor);
455     if(cursor == NULL) return NULL;
456 
457     cursor->pos.x = LV_CHART_POINT_NONE;
458     cursor->pos.y = LV_CHART_POINT_NONE;
459     cursor->point_id = LV_CHART_POINT_NONE;
460     cursor->pos_set = 0;
461     cursor->color = color;
462     cursor->dir = dir;
463 
464     return cursor;
465 }
466 
467 /**
468  * Set the coordinate of the cursor with respect
469  * to the origin of series area of the chart.
470  * @param chart pointer to a chart object.
471  * @param cursor pointer to the cursor.
472  * @param pos the new coordinate of cursor relative to the series area
473  */
lv_chart_set_cursor_pos(lv_obj_t * chart,lv_chart_cursor_t * cursor,lv_point_t * pos)474 void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos)
475 {
476     LV_ASSERT_NULL(cursor);
477     LV_UNUSED(chart);
478 
479     cursor->pos.x = pos->x;
480     cursor->pos.y = pos->y;
481     cursor->pos_set = 1;
482     lv_chart_refresh(chart);
483 }
484 
485 
486 /**
487  * Set the coordinate of the cursor with respect
488  * to the origin of series area of the chart.
489  * @param chart pointer to a chart object.
490  * @param cursor pointer to the cursor.
491  * @param pos the new coordinate of cursor relative to the series area
492  */
lv_chart_set_cursor_point(lv_obj_t * chart,lv_chart_cursor_t * cursor,lv_chart_series_t * ser,uint16_t point_id)493 void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser, uint16_t point_id)
494 {
495     LV_ASSERT_NULL(cursor);
496     LV_UNUSED(chart);
497 
498     cursor->point_id = point_id;
499     cursor->pos_set = 0;
500     if(ser == NULL) ser = lv_chart_get_series_next(chart, NULL);
501     cursor->ser = ser;
502     lv_chart_refresh(chart);
503 }
504 /**
505  * Get the coordinate of the cursor with respect
506  * to the origin of series area of the chart.
507  * @param chart pointer to a chart object
508  * @param cursor pointer to cursor
509  * @return coordinate of the cursor as lv_point_t
510  */
lv_chart_get_cursor_point(lv_obj_t * chart,lv_chart_cursor_t * cursor)511 lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor)
512 {
513     LV_ASSERT_NULL(cursor);
514     LV_UNUSED(chart);
515 
516     return cursor->pos;
517 }
518 
519 /*=====================
520  * Set/Get value(s)
521  *====================*/
522 
523 
lv_chart_set_all_value(lv_obj_t * obj,lv_chart_series_t * ser,lv_coord_t value)524 void lv_chart_set_all_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value)
525 {
526     LV_ASSERT_OBJ(obj, MY_CLASS);
527     LV_ASSERT_NULL(ser);
528 
529     lv_chart_t * chart  = (lv_chart_t *)obj;
530     uint16_t i;
531     for(i = 0; i < chart->point_cnt; i++) {
532         ser->y_points[i] = value;
533     }
534     ser->start_point = 0;
535     lv_chart_refresh(obj);
536 }
537 
lv_chart_set_next_value(lv_obj_t * obj,lv_chart_series_t * ser,lv_coord_t value)538 void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value)
539 {
540     LV_ASSERT_OBJ(obj, MY_CLASS);
541     LV_ASSERT_NULL(ser);
542 
543     lv_chart_t * chart  = (lv_chart_t *)obj;
544     ser->y_points[ser->start_point] = value;
545     invalidate_point(obj, ser->start_point);
546     ser->start_point = (ser->start_point + 1) % chart->point_cnt;
547     invalidate_point(obj, ser->start_point);
548 }
549 
lv_chart_set_next_value2(lv_obj_t * obj,lv_chart_series_t * ser,lv_coord_t x_value,lv_coord_t y_value)550 void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t x_value, lv_coord_t y_value)
551 {
552     LV_ASSERT_OBJ(obj, MY_CLASS);
553     LV_ASSERT_NULL(ser);
554 
555     lv_chart_t * chart  = (lv_chart_t *)obj;
556 
557     if(chart->type != LV_CHART_TYPE_SCATTER) {
558         LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER");
559         return;
560     }
561 
562     ser->x_points[ser->start_point] = x_value;
563     ser->y_points[ser->start_point] = y_value;
564     ser->start_point = (ser->start_point + 1) % chart->point_cnt;
565     invalidate_point(obj, ser->start_point);
566 }
567 
lv_chart_set_value_by_id(lv_obj_t * obj,lv_chart_series_t * ser,uint16_t id,lv_coord_t value)568 void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t value)
569 {
570     LV_ASSERT_OBJ(obj, MY_CLASS);
571     LV_ASSERT_NULL(ser);
572     lv_chart_t * chart  = (lv_chart_t *)obj;
573 
574     if(id >= chart->point_cnt) return;
575     ser->y_points[id] = value;
576     invalidate_point(obj, id);
577 }
578 
lv_chart_set_value_by_id2(lv_obj_t * obj,lv_chart_series_t * ser,uint16_t id,lv_coord_t x_value,lv_coord_t y_value)579 void lv_chart_set_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t x_value,
580                                lv_coord_t y_value)
581 {
582     LV_ASSERT_OBJ(obj, MY_CLASS);
583     LV_ASSERT_NULL(ser);
584     lv_chart_t * chart  = (lv_chart_t *)obj;
585 
586     if(chart->type != LV_CHART_TYPE_SCATTER) {
587         LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER");
588         return;
589     }
590 
591     if(id >= chart->point_cnt) return;
592     ser->x_points[id] = x_value;
593     ser->y_points[id] = y_value;
594     invalidate_point(obj, id);
595 }
596 
lv_chart_set_ext_y_array(lv_obj_t * obj,lv_chart_series_t * ser,lv_coord_t array[])597 void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[])
598 {
599     LV_ASSERT_OBJ(obj, MY_CLASS);
600     LV_ASSERT_NULL(ser);
601 
602     if(!ser->y_ext_buf_assigned && ser->y_points) lv_mem_free(ser->y_points);
603     ser->y_ext_buf_assigned = true;
604     ser->y_points = array;
605     lv_obj_invalidate(obj);
606 }
607 
lv_chart_set_ext_x_array(lv_obj_t * obj,lv_chart_series_t * ser,lv_coord_t array[])608 void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[])
609 {
610     LV_ASSERT_OBJ(obj, MY_CLASS);
611     LV_ASSERT_NULL(ser);
612 
613     if(!ser->x_ext_buf_assigned && ser->x_points) lv_mem_free(ser->x_points);
614     ser->x_ext_buf_assigned = true;
615     ser->x_points = array;
616     lv_obj_invalidate(obj);
617 }
618 
lv_chart_get_y_array(const lv_obj_t * obj,lv_chart_series_t * ser)619 lv_coord_t * lv_chart_get_y_array(const lv_obj_t * obj, lv_chart_series_t * ser)
620 {
621     LV_UNUSED(obj);
622     LV_ASSERT_OBJ(obj, MY_CLASS);
623     LV_ASSERT_NULL(ser);
624     return ser->y_points;
625 }
626 
lv_chart_get_x_array(const lv_obj_t * obj,lv_chart_series_t * ser)627 lv_coord_t * lv_chart_get_x_array(const lv_obj_t * obj, lv_chart_series_t * ser)
628 {
629     LV_UNUSED(obj);
630     LV_ASSERT_OBJ(obj, MY_CLASS);
631     LV_ASSERT_NULL(ser);
632     return ser->x_points;
633 }
634 
lv_chart_get_pressed_point(const lv_obj_t * obj)635 uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj)
636 {
637     lv_chart_t * chart = (lv_chart_t *)obj;
638     return chart->pressed_point_id;
639 }
640 
641 /**********************
642  *   STATIC FUNCTIONS
643  **********************/
644 
lv_chart_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)645 static void lv_chart_constructor(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 
652     _lv_ll_init(&chart->series_ll, sizeof(lv_chart_series_t));
653     _lv_ll_init(&chart->cursor_ll, sizeof(lv_chart_cursor_t));
654 
655     chart->ymin[0] = 0;
656     chart->xmin[0] = 0;
657     chart->ymin[1] = 0;
658     chart->xmin[1] = 0;
659     chart->ymax[0] = 100;
660     chart->xmax[0] = 100;
661     chart->ymax[1] = 100;
662     chart->xmax[1] = 100;
663 
664     chart->hdiv_cnt    = LV_CHART_HDIV_DEF;
665     chart->vdiv_cnt    = LV_CHART_VDIV_DEF;
666     chart->point_cnt   = LV_CHART_POINT_CNT_DEF;
667     chart->pressed_point_id  = LV_CHART_POINT_NONE;
668     chart->type        = LV_CHART_TYPE_LINE;
669     chart->update_mode = LV_CHART_UPDATE_MODE_SHIFT;
670     chart->zoom_x      = LV_IMG_ZOOM_NONE;
671     chart->zoom_y      = LV_IMG_ZOOM_NONE;
672 
673     LV_TRACE_OBJ_CREATE("finished");
674 }
675 
lv_chart_destructor(const lv_obj_class_t * class_p,lv_obj_t * obj)676 static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
677 {
678     LV_UNUSED(class_p);
679     LV_TRACE_OBJ_CREATE("begin");
680 
681     lv_chart_t * chart = (lv_chart_t *)obj;
682     lv_chart_series_t * ser;
683     while(chart->series_ll.head) {
684         ser = _lv_ll_get_head(&chart->series_ll);
685 
686         if(!ser->y_ext_buf_assigned) lv_mem_free(ser->y_points);
687 
688         _lv_ll_remove(&chart->series_ll, ser);
689         lv_mem_free(ser);
690     }
691     _lv_ll_clear(&chart->series_ll);
692 
693     lv_chart_cursor_t * cur;
694     while(chart->cursor_ll.head) {
695         cur = _lv_ll_get_head(&chart->cursor_ll);
696         _lv_ll_remove(&chart->cursor_ll, cur);
697         lv_mem_free(cur);
698     }
699     _lv_ll_clear(&chart->cursor_ll);
700 
701     LV_TRACE_OBJ_CREATE("finished");
702 }
703 
lv_chart_event(const lv_obj_class_t * class_p,lv_event_t * e)704 static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e)
705 {
706     LV_UNUSED(class_p);
707 
708     /*Call the ancestor's event handler*/
709     lv_res_t res;
710 
711     res = lv_obj_event_base(MY_CLASS, e);
712     if(res != LV_RES_OK) return;
713 
714     lv_event_code_t code = lv_event_get_code(e);
715     lv_obj_t * obj = lv_event_get_target(e);
716 
717     lv_chart_t * chart  = (lv_chart_t *)obj;
718     if(code == LV_EVENT_PRESSED) {
719         lv_indev_t * indev = lv_indev_get_act();
720         lv_point_t p;
721         lv_indev_get_point(indev, &p);
722 
723         p.x -= obj->coords.x1;
724         uint32_t id = get_index_from_x(obj, p.x + lv_obj_get_scroll_left(obj));
725         if(id != (uint32_t)chart->pressed_point_id) {
726             invalidate_point(obj, id);
727             invalidate_point(obj, chart->pressed_point_id);
728             chart->pressed_point_id = id;
729             lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
730         }
731     }
732     else if(code == LV_EVENT_RELEASED) {
733         invalidate_point(obj, chart->pressed_point_id);
734         chart->pressed_point_id = LV_CHART_POINT_NONE;
735     }
736     else if(code == LV_EVENT_SIZE_CHANGED) {
737         lv_obj_refresh_self_size(obj);
738     }
739     else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
740         lv_event_set_ext_draw_size(e, LV_MAX4(chart->tick[0].draw_size, chart->tick[1].draw_size, chart->tick[2].draw_size,
741                                               chart->tick[3].draw_size));
742     }
743     else if(code == LV_EVENT_GET_SELF_SIZE) {
744         lv_point_t * p = lv_event_get_param(e);
745         p->x = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
746         p->y = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
747     }
748     else if(code == LV_EVENT_DRAW_MAIN) {
749         lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
750         draw_div_lines(obj, draw_ctx);
751         draw_axes(obj, draw_ctx);
752 
753         if(_lv_ll_is_empty(&chart->series_ll) == false) {
754             if(chart->type == LV_CHART_TYPE_LINE) draw_series_line(obj, draw_ctx);
755             else if(chart->type == LV_CHART_TYPE_BAR) draw_series_bar(obj, draw_ctx);
756             else if(chart->type == LV_CHART_TYPE_SCATTER) draw_series_scatter(obj, draw_ctx);
757         }
758 
759         draw_cursors(obj, draw_ctx);
760     }
761 }
762 
draw_div_lines(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)763 static void draw_div_lines(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
764 {
765     lv_chart_t * chart  = (lv_chart_t *)obj;
766 
767     lv_area_t series_clip_area;
768     bool mask_ret = _lv_area_intersect(&series_clip_area, &obj->coords, draw_ctx->clip_area);
769     if(mask_ret == false) return;
770 
771     const lv_area_t * clip_area_ori = draw_ctx->clip_area;
772     draw_ctx->clip_area = &series_clip_area;
773 
774     int16_t i;
775     int16_t i_start;
776     int16_t i_end;
777     lv_point_t p1;
778     lv_point_t p2;
779     lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
780     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
781     lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
782     lv_coord_t w     = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
783     lv_coord_t h     = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
784 
785     lv_draw_line_dsc_t line_dsc;
786     lv_draw_line_dsc_init(&line_dsc);
787     lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc);
788 
789     lv_obj_draw_part_dsc_t part_draw_dsc;
790     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
791     part_draw_dsc.part = LV_PART_MAIN;
792     part_draw_dsc.class_p = MY_CLASS;
793     part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_INIT;
794     part_draw_dsc.line_dsc = &line_dsc;
795     part_draw_dsc.id = 0xFFFFFFFF;
796     part_draw_dsc.p1 = NULL;
797     part_draw_dsc.p2 = NULL;
798     lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
799 
800     lv_opa_t border_opa = lv_obj_get_style_border_opa(obj, LV_PART_MAIN);
801     lv_coord_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
802     lv_border_side_t border_side = lv_obj_get_style_border_side(obj, LV_PART_MAIN);
803 
804     lv_coord_t scroll_left = lv_obj_get_scroll_left(obj);
805     lv_coord_t scroll_top = lv_obj_get_scroll_top(obj);
806     if(chart->hdiv_cnt != 0) {
807         lv_coord_t y_ofs = obj->coords.y1 + pad_top - scroll_top;
808         p1.x = obj->coords.x1;
809         p2.x = obj->coords.x2;
810 
811         i_start = 0;
812         i_end = chart->hdiv_cnt;
813         if(border_opa > LV_OPA_MIN && border_w > 0) {
814             if((border_side & LV_BORDER_SIDE_TOP) && (lv_obj_get_style_pad_top(obj, LV_PART_MAIN) == 0)) i_start++;
815             if((border_side & LV_BORDER_SIDE_BOTTOM) && (lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN) == 0)) i_end--;
816         }
817 
818         for(i = i_start; i < i_end; i++) {
819             p1.y = (int32_t)((int32_t)h * i) / (chart->hdiv_cnt - 1);
820             p1.y += y_ofs;
821             p2.y = p1.y;
822 
823             part_draw_dsc.class_p = MY_CLASS;
824             part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_HOR;
825             part_draw_dsc.p1 = &p1;
826             part_draw_dsc.p2 = &p2;
827             part_draw_dsc.id = i;
828 
829             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
830             lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
831             lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
832         }
833     }
834 
835     if(chart->vdiv_cnt != 0) {
836         lv_coord_t x_ofs = obj->coords.x1 + pad_left - scroll_left;
837         p1.y = obj->coords.y1;
838         p2.y = obj->coords.y2;
839         i_start = 0;
840         i_end = chart->vdiv_cnt;
841         if(border_opa > LV_OPA_MIN && border_w > 0) {
842             if((border_side & LV_BORDER_SIDE_LEFT) && (lv_obj_get_style_pad_left(obj, LV_PART_MAIN) == 0)) i_start++;
843             if((border_side & LV_BORDER_SIDE_RIGHT) && (lv_obj_get_style_pad_right(obj, LV_PART_MAIN) == 0)) i_end--;
844         }
845 
846         for(i = i_start; i < i_end; i++) {
847             p1.x = (int32_t)((int32_t)w * i) / (chart->vdiv_cnt - 1);
848             p1.x += x_ofs;
849             p2.x = p1.x;
850 
851             part_draw_dsc.class_p = MY_CLASS;
852             part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_VER;
853             part_draw_dsc.p1 = &p1;
854             part_draw_dsc.p2 = &p2;
855             part_draw_dsc.id = i;
856 
857             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
858             lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
859             lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
860         }
861     }
862 
863     part_draw_dsc.id = 0xFFFFFFFF;
864     part_draw_dsc.p1 = NULL;
865     part_draw_dsc.p2 = NULL;
866     lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
867 
868     draw_ctx->clip_area = clip_area_ori;
869 }
870 
draw_series_line(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)871 static void draw_series_line(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
872 {
873     lv_area_t clip_area;
874     if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return;
875 
876     const lv_area_t * clip_area_ori = draw_ctx->clip_area;
877     draw_ctx->clip_area = &clip_area;
878 
879     lv_chart_t * chart  = (lv_chart_t *)obj;
880     if(chart->point_cnt < 2) return;
881 
882     uint16_t i;
883     lv_point_t p1;
884     lv_point_t p2;
885     lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
886     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
887     lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
888     lv_coord_t w     = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
889     lv_coord_t h     = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
890     lv_coord_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj);
891     lv_coord_t y_ofs = obj->coords.y1 + pad_top - lv_obj_get_scroll_top(obj);
892     lv_chart_series_t * ser;
893 
894     lv_area_t series_clip_area;
895     bool mask_ret = _lv_area_intersect(&series_clip_area, &obj->coords, draw_ctx->clip_area);
896     if(mask_ret == false) return;
897 
898     lv_draw_line_dsc_t line_dsc_default;
899     lv_draw_line_dsc_init(&line_dsc_default);
900     lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc_default);
901 
902     lv_draw_rect_dsc_t point_dsc_default;
903     lv_draw_rect_dsc_init(&point_dsc_default);
904     lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default);
905 
906     lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2;
907     lv_coord_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2;
908 
909     /*Do not bother with line ending is the point will over it*/
910     if(LV_MIN(point_w, point_h) > line_dsc_default.width / 2) line_dsc_default.raw_end = 1;
911     if(line_dsc_default.width == 1) line_dsc_default.raw_end = 1;
912 
913     /*If there are at least as much points as pixels then draw only vertical lines*/
914     bool crowded_mode = chart->point_cnt >= w ? true : false;
915 
916     /*Go through all data lines*/
917     _LV_LL_READ_BACK(&chart->series_ll, ser) {
918         if(ser->hidden) continue;
919         line_dsc_default.color = ser->color;
920         point_dsc_default.bg_color = ser->color;
921 
922         lv_coord_t start_point = lv_chart_get_x_start_point(obj, ser);
923 
924         p1.x = x_ofs;
925         p2.x = x_ofs;
926 
927         lv_coord_t p_act = start_point;
928         lv_coord_t p_prev = start_point;
929         int32_t y_tmp = (int32_t)((int32_t)ser->y_points[p_prev] - chart->ymin[ser->y_axis_sec]) * h;
930         y_tmp  = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
931         p2.y   = h - y_tmp + y_ofs;
932 
933         lv_obj_draw_part_dsc_t part_draw_dsc;
934         lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
935         part_draw_dsc.class_p = MY_CLASS;
936         part_draw_dsc.type = LV_CHART_DRAW_PART_LINE_AND_POINT;
937         part_draw_dsc.part = LV_PART_ITEMS;
938         part_draw_dsc.line_dsc = &line_dsc_default;
939         part_draw_dsc.rect_dsc = &point_dsc_default;
940         part_draw_dsc.sub_part_ptr = ser;
941 
942         lv_coord_t y_min = p2.y;
943         lv_coord_t y_max = p2.y;
944 
945         for(i = 0; i < chart->point_cnt; i++) {
946             p1.x = p2.x;
947             p1.y = p2.y;
948 
949             if(p1.x > clip_area_ori->x2 + point_w + 1) break;
950             p2.x = ((w * i) / (chart->point_cnt - 1)) + x_ofs;
951 
952             p_act = (start_point + i) % chart->point_cnt;
953 
954             y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
955             y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
956             p2.y  = h - y_tmp + y_ofs;
957 
958             if(p2.x < clip_area_ori->x1 - point_w - 1) {
959                 p_prev = p_act;
960                 continue;
961             }
962 
963             /*Don't draw the first point. A second point is also required to draw the line*/
964             if(i != 0) {
965                 if(crowded_mode) {
966                     if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
967                         /*Draw only one vertical line between the min and max y-values on the same x-value*/
968                         y_max = LV_MAX(y_max, p2.y);
969                         y_min = LV_MIN(y_min, p2.y);
970                         if(p1.x != p2.x) {
971                             lv_coord_t y_cur = p2.y;
972                             p2.x--;         /*It's already on the next x value*/
973                             p1.x = p2.x;
974                             p1.y = y_min;
975                             p2.y = y_max;
976                             if(p1.y == p2.y) p2.y++;    /*If they are the same no line will be drawn*/
977                             lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2);
978                             p2.x++;         /*Compensate the previous x--*/
979                             y_min = y_cur;  /*Start the line of the next x from the current last y*/
980                             y_max = y_cur;
981                         }
982                     }
983                 }
984                 else {
985                     lv_area_t point_area;
986                     point_area.x1 = p1.x - point_w;
987                     point_area.x2 = p1.x + point_w;
988                     point_area.y1 = p1.y - point_h;
989                     point_area.y2 = p1.y + point_h;
990 
991                     part_draw_dsc.id = i - 1;
992                     part_draw_dsc.p1 = ser->y_points[p_prev] != LV_CHART_POINT_NONE ? &p1 : NULL;
993                     part_draw_dsc.p2 = ser->y_points[p_act] != LV_CHART_POINT_NONE ? &p2 : NULL;
994                     part_draw_dsc.draw_area = &point_area;
995                     part_draw_dsc.value = ser->y_points[p_prev];
996 
997                     lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
998 
999                     if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1000                         lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2);
1001                     }
1002 
1003                     if(point_w && point_h && ser->y_points[p_prev] != LV_CHART_POINT_NONE) {
1004                         lv_draw_rect(draw_ctx, &point_dsc_default, &point_area);
1005                     }
1006 
1007                     lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1008                 }
1009 
1010             }
1011             p_prev = p_act;
1012         }
1013 
1014         /*Draw the last point*/
1015         if(!crowded_mode && i == chart->point_cnt) {
1016 
1017             if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1018                 lv_area_t point_area;
1019                 point_area.x1 = p2.x - point_w;
1020                 point_area.x2 = p2.x + point_w;
1021                 point_area.y1 = p2.y - point_h;
1022                 point_area.y2 = p2.y + point_h;
1023 
1024                 part_draw_dsc.id = i - 1;
1025                 part_draw_dsc.p1 = NULL;
1026                 part_draw_dsc.p2 = NULL;
1027                 part_draw_dsc.draw_area = &point_area;
1028                 part_draw_dsc.value = ser->y_points[p_act];
1029 
1030                 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1031                 lv_draw_rect(draw_ctx, &point_dsc_default, &point_area);
1032                 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1033             }
1034         }
1035     }
1036 
1037     draw_ctx->clip_area = clip_area_ori;
1038 }
1039 
draw_series_scatter(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)1040 static void draw_series_scatter(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
1041 {
1042 
1043     lv_area_t clip_area;
1044     if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return;
1045 
1046     const lv_area_t * clip_area_ori = draw_ctx->clip_area;
1047     draw_ctx->clip_area = &clip_area;
1048 
1049     lv_chart_t * chart  = (lv_chart_t *)obj;
1050 
1051     uint16_t i;
1052     lv_point_t p1;
1053     lv_point_t p2;
1054     lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1055     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1056     lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
1057     lv_coord_t w     = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
1058     lv_coord_t h     = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
1059     lv_coord_t x_ofs = obj->coords.x1 + pad_left + border_width - lv_obj_get_scroll_left(obj);
1060     lv_coord_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj);
1061     lv_chart_series_t * ser;
1062 
1063     lv_draw_line_dsc_t line_dsc_default;
1064     lv_draw_line_dsc_init(&line_dsc_default);
1065     lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc_default);
1066 
1067     lv_draw_rect_dsc_t point_dsc_default;
1068     lv_draw_rect_dsc_init(&point_dsc_default);
1069     lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default);
1070 
1071     lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2;
1072     lv_coord_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2;
1073 
1074     /*Do not bother with line ending is the point will over it*/
1075     if(LV_MIN(point_w, point_h) > line_dsc_default.width / 2) line_dsc_default.raw_end = 1;
1076     if(line_dsc_default.width == 1) line_dsc_default.raw_end = 1;
1077 
1078     /*Go through all data lines*/
1079     _LV_LL_READ_BACK(&chart->series_ll, ser) {
1080         if(ser->hidden) continue;
1081         line_dsc_default.color = ser->color;
1082         point_dsc_default.bg_color = ser->color;
1083 
1084         lv_coord_t start_point = lv_chart_get_x_start_point(obj, ser);
1085 
1086         p1.x = x_ofs;
1087         p2.x = x_ofs;
1088 
1089         lv_coord_t p_act = start_point;
1090         lv_coord_t p_prev = start_point;
1091         if(ser->y_points[p_act] != LV_CHART_POINT_CNT_DEF) {
1092             p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
1093             p2.x += x_ofs;
1094 
1095             p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
1096             p2.y = h - p2.y;
1097             p2.y += y_ofs;
1098         }
1099         else {
1100             p2.x = LV_COORD_MIN;
1101             p2.y = LV_COORD_MIN;
1102         }
1103 
1104         lv_obj_draw_part_dsc_t part_draw_dsc;
1105         lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
1106         part_draw_dsc.part = LV_PART_ITEMS;
1107         part_draw_dsc.class_p = MY_CLASS;
1108         part_draw_dsc.type = LV_CHART_DRAW_PART_LINE_AND_POINT;
1109         part_draw_dsc.line_dsc = &line_dsc_default;
1110         part_draw_dsc.rect_dsc = &point_dsc_default;
1111         part_draw_dsc.sub_part_ptr = ser;
1112 
1113         for(i = 0; i < chart->point_cnt; i++) {
1114             p1.x = p2.x;
1115             p1.y = p2.y;
1116 
1117             p_act = (start_point + i) % chart->point_cnt;
1118             if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1119                 p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
1120                 p2.y = h - p2.y;
1121                 p2.y += y_ofs;
1122 
1123                 p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
1124                 p2.x += x_ofs;
1125             }
1126             else {
1127                 p_prev = p_act;
1128                 continue;
1129             }
1130 
1131             /*Don't draw the first point. A second point is also required to draw the line*/
1132             if(i != 0) {
1133                 lv_area_t point_area;
1134                 point_area.x1 = p1.x - point_w;
1135                 point_area.x2 = p1.x + point_w;
1136                 point_area.y1 = p1.y - point_h;
1137                 point_area.y2 = p1.y + point_h;
1138 
1139                 part_draw_dsc.id = i - 1;
1140                 part_draw_dsc.p1 = ser->y_points[p_prev] != LV_CHART_POINT_NONE ? &p1 : NULL;
1141                 part_draw_dsc.p2 = ser->y_points[p_act] != LV_CHART_POINT_NONE ? &p2 : NULL;
1142                 part_draw_dsc.draw_area = &point_area;
1143                 part_draw_dsc.value = ser->y_points[p_prev];
1144 
1145                 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1146 
1147                 if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1148                     lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2);
1149                     if(point_w && point_h) {
1150                         lv_draw_rect(draw_ctx, &point_dsc_default, &point_area);
1151                     }
1152                 }
1153 
1154                 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1155             }
1156             p_prev = p_act;
1157         }
1158 
1159         /*Draw the last point*/
1160         if(i == chart->point_cnt) {
1161 
1162             if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1163                 lv_area_t point_area;
1164                 point_area.x1 = p2.x - point_w;
1165                 point_area.x2 = p2.x + point_w;
1166                 point_area.y1 = p2.y - point_h;
1167                 point_area.y2 = p2.y + point_h;
1168 
1169                 part_draw_dsc.id = i - 1;
1170                 part_draw_dsc.p1 = NULL;
1171                 part_draw_dsc.p2 = NULL;
1172                 part_draw_dsc.draw_area = &point_area;
1173                 part_draw_dsc.value = ser->y_points[p_act];
1174                 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1175                 lv_draw_rect(draw_ctx, &point_dsc_default, &point_area);
1176                 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1177             }
1178         }
1179     }
1180     draw_ctx->clip_area = clip_area_ori;
1181 }
1182 
draw_series_bar(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)1183 static void draw_series_bar(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
1184 {
1185     lv_area_t clip_area;
1186     if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return;
1187 
1188     const lv_area_t * clip_area_ori = draw_ctx->clip_area;
1189     draw_ctx->clip_area = &clip_area;
1190 
1191     lv_chart_t * chart  = (lv_chart_t *)obj;
1192 
1193     uint16_t i;
1194     lv_area_t col_a;
1195     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1196     lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
1197     lv_coord_t w     = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
1198     lv_coord_t h     = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
1199     int32_t y_tmp;
1200     lv_chart_series_t * ser;
1201     uint32_t ser_cnt = _lv_ll_get_len(&chart->series_ll);
1202     int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
1203                                                               LV_PART_MAIN) * chart->zoom_x) >> 8;  /*Gap between the column on ~adjacent X*/
1204     lv_coord_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;
1205     int32_t ser_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
1206                                                             LV_PART_ITEMS) * chart->zoom_x) >> 8; /*Gap between the columns on the ~same X*/
1207     lv_coord_t col_w = (block_w - (ser_cnt - 1) * ser_gap) / ser_cnt;
1208     if(col_w < 1) col_w  = 1;
1209 
1210     lv_coord_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1211     lv_coord_t x_ofs = pad_left - lv_obj_get_scroll_left(obj) + border_w;
1212     lv_coord_t y_ofs = pad_top - lv_obj_get_scroll_top(obj) + border_w;
1213 
1214     lv_draw_rect_dsc_t col_dsc;
1215     lv_draw_rect_dsc_init(&col_dsc);
1216     lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc);
1217     col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE;
1218     col_dsc.bg_opa = LV_OPA_COVER;
1219 
1220     /*Make the cols longer with `radius` to clip the rounding from the bottom*/
1221     col_a.y2 = obj->coords.y2 + col_dsc.radius;
1222 
1223     lv_obj_draw_part_dsc_t part_draw_dsc;
1224     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
1225     part_draw_dsc.part = LV_PART_ITEMS;
1226     part_draw_dsc.class_p = MY_CLASS;
1227     part_draw_dsc.type = LV_CHART_DRAW_PART_BAR;
1228 
1229     /*Go through all points*/
1230     for(i = 0; i < chart->point_cnt; i++) {
1231         lv_coord_t x_act = (int32_t)((int32_t)(w - block_w) * i) / (chart->point_cnt - 1) + obj->coords.x1 + x_ofs;
1232 
1233         part_draw_dsc.id = i;
1234 
1235         /*Draw the current point of all data line*/
1236         _LV_LL_READ_BACK(&chart->series_ll, ser) {
1237             if(ser->hidden) continue;
1238 
1239             lv_coord_t start_point = lv_chart_get_x_start_point(obj, ser);
1240 
1241             col_a.x1 = x_act;
1242             col_a.x2 = col_a.x1 + col_w - 1;
1243             x_act += col_w + ser_gap;
1244 
1245             if(col_a.x2 < clip_area.x1) continue;
1246             if(col_a.x1 > clip_area.x2) break;
1247 
1248             col_dsc.bg_color = ser->color;
1249 
1250             lv_coord_t p_act = (start_point + i) % chart->point_cnt;
1251             y_tmp            = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
1252             y_tmp            = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
1253             col_a.y1         = h - y_tmp + obj->coords.y1 + y_ofs;
1254 
1255             if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1256                 part_draw_dsc.draw_area = &col_a;
1257                 part_draw_dsc.rect_dsc = &col_dsc;
1258                 part_draw_dsc.sub_part_ptr = ser;
1259                 part_draw_dsc.value = ser->y_points[p_act];
1260                 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1261                 lv_draw_rect(draw_ctx, &col_dsc, &col_a);
1262                 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1263             }
1264         }
1265     }
1266     draw_ctx->clip_area = clip_area_ori;
1267 }
1268 
draw_cursors(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)1269 static void draw_cursors(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
1270 {
1271     LV_ASSERT_OBJ(obj, MY_CLASS);
1272 
1273     lv_chart_t * chart  = (lv_chart_t *)obj;
1274     if(_lv_ll_is_empty(&chart->cursor_ll)) return;
1275 
1276     lv_area_t clip_area;
1277     if(!_lv_area_intersect(&clip_area, draw_ctx->clip_area, &obj->coords)) return;
1278 
1279     const lv_area_t * clip_area_ori = draw_ctx->clip_area;
1280     draw_ctx->clip_area = &clip_area;
1281 
1282     lv_point_t p1;
1283     lv_point_t p2;
1284     lv_chart_cursor_t * cursor;
1285 
1286     lv_draw_line_dsc_t line_dsc_ori;
1287     lv_draw_line_dsc_init(&line_dsc_ori);
1288     lv_obj_init_draw_line_dsc(obj, LV_PART_CURSOR, &line_dsc_ori);
1289 
1290     lv_draw_rect_dsc_t point_dsc_ori;
1291     lv_draw_rect_dsc_init(&point_dsc_ori);
1292     point_dsc_ori.bg_opa = line_dsc_ori.opa;
1293     point_dsc_ori.radius = LV_RADIUS_CIRCLE;
1294 
1295     lv_draw_line_dsc_t line_dsc_tmp;
1296     lv_draw_rect_dsc_t point_dsc_tmp;
1297 
1298     lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
1299     lv_coord_t point_h = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
1300 
1301     lv_obj_draw_part_dsc_t part_draw_dsc;
1302     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
1303     part_draw_dsc.line_dsc = &line_dsc_tmp;
1304     part_draw_dsc.rect_dsc = &point_dsc_tmp;
1305     part_draw_dsc.part = LV_PART_CURSOR;
1306     part_draw_dsc.class_p = MY_CLASS;
1307     part_draw_dsc.type = LV_CHART_DRAW_PART_CURSOR;
1308 
1309     /*Go through all cursor lines*/
1310     _LV_LL_READ_BACK(&chart->cursor_ll, cursor) {
1311         lv_memcpy(&line_dsc_tmp, &line_dsc_ori, sizeof(lv_draw_line_dsc_t));
1312         lv_memcpy(&point_dsc_tmp, &point_dsc_ori, sizeof(lv_draw_rect_dsc_t));
1313         line_dsc_tmp.color = cursor->color;
1314         point_dsc_tmp.bg_color = cursor->color;
1315 
1316         part_draw_dsc.p1 = &p1;
1317         part_draw_dsc.p2 = &p2;
1318 
1319         lv_coord_t cx;
1320         lv_coord_t cy;
1321         if(cursor->pos_set) {
1322             cx = cursor->pos.x;
1323             cy = cursor->pos.y;
1324         }
1325         else {
1326             if(cursor->point_id == LV_CHART_POINT_NONE) continue;
1327             lv_point_t p;
1328             lv_chart_get_point_pos_by_id(obj, cursor->ser, cursor->point_id, &p);
1329             cx = p.x;
1330             cy = p.y;
1331         }
1332 
1333         cx += obj->coords.x1;
1334         cy += obj->coords.y1;
1335 
1336         lv_area_t point_area;
1337         bool draw_point = point_w && point_h;
1338         if(draw_point) {
1339             point_area.x1 = cx - point_w;
1340             point_area.x2 = cx + point_w;
1341             point_area.y1 = cy - point_h;
1342             point_area.y2 = cy + point_h;
1343 
1344             part_draw_dsc.draw_area = &point_area;
1345         }
1346         else {
1347             part_draw_dsc.draw_area = NULL;
1348         }
1349 
1350         if(cursor->dir & LV_DIR_HOR) {
1351             p1.x = cursor->dir & LV_DIR_LEFT ? obj->coords.x1 : cx;
1352             p1.y = cy;
1353             p2.x = cursor->dir & LV_DIR_RIGHT ? obj->coords.x2 : cx;
1354             p2.y = p1.y;
1355 
1356             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1357             lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2);
1358 
1359             if(draw_point) {
1360                 lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area);
1361             }
1362 
1363             lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1364         }
1365 
1366         if(cursor->dir & LV_DIR_VER) {
1367             p1.x = cx;
1368             p1.y = cursor->dir & LV_DIR_TOP ? obj->coords.y1 : cy;
1369             p2.x = p1.x;
1370             p2.y = cursor->dir & LV_DIR_BOTTOM ? obj->coords.y2 : cy;
1371 
1372             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1373             lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2);
1374 
1375             if(draw_point) {
1376                 lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area);
1377             }
1378 
1379             lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1380         }
1381     }
1382 
1383     draw_ctx->clip_area = clip_area_ori;
1384 }
1385 
draw_y_ticks(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx,lv_chart_axis_t axis)1386 static void draw_y_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis)
1387 {
1388     lv_chart_t * chart  = (lv_chart_t *)obj;
1389 
1390     lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis);
1391 
1392     if(!t->label_en && !t->major_len && !t->minor_len) return;
1393     if(t->major_cnt <= 1) return;
1394     uint32_t total_tick_num = (t->major_cnt - 1) * (t->minor_cnt);
1395     if(total_tick_num == 0) return;
1396 
1397     uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_Y ? 0 : 1;
1398 
1399     uint32_t i;
1400 
1401     lv_point_t p1;
1402     lv_point_t p2;
1403 
1404     lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1405     lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
1406     lv_coord_t h     = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
1407     lv_coord_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj);
1408 
1409     lv_coord_t label_gap;
1410     lv_coord_t x_ofs;
1411     if(axis == LV_CHART_AXIS_PRIMARY_Y) {
1412         label_gap = lv_obj_get_style_pad_left(obj, LV_PART_TICKS);
1413         x_ofs = obj->coords.x1;
1414     }
1415     else {
1416         label_gap = lv_obj_get_style_pad_right(obj, LV_PART_TICKS);
1417         x_ofs = obj->coords.x2;
1418     }
1419 
1420     lv_coord_t major_len = t->major_len;
1421     lv_coord_t minor_len = t->minor_len;
1422     /*tick lines on secondary y axis are drawn in other direction*/
1423     if(axis == LV_CHART_AXIS_SECONDARY_Y) {
1424         major_len *= -1;
1425         minor_len *= -1;
1426     }
1427 
1428     lv_draw_line_dsc_t line_dsc;
1429     lv_draw_line_dsc_init(&line_dsc);
1430     lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc);
1431 
1432     lv_draw_label_dsc_t label_dsc;
1433     lv_draw_label_dsc_init(&label_dsc);
1434     lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc);
1435 
1436     lv_obj_draw_part_dsc_t part_draw_dsc;
1437     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
1438     part_draw_dsc.class_p = MY_CLASS;
1439     part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL;
1440     part_draw_dsc.id = axis;
1441     part_draw_dsc.part = LV_PART_TICKS;
1442     part_draw_dsc.line_dsc = &line_dsc;
1443     part_draw_dsc.label_dsc = &label_dsc;
1444 
1445     for(i = 0; i <= total_tick_num; i++) {
1446         /*draw a line at moving y position*/
1447         p2.y = p1.y = y_ofs + (int32_t)((int32_t)(h - line_dsc.width) * i) / total_tick_num;
1448 
1449         /*first point of the tick*/
1450         p1.x = x_ofs;
1451 
1452         /*move extra pixel out of chart boundary*/
1453         if(axis == LV_CHART_AXIS_PRIMARY_Y) p1.x--;
1454         else p1.x++;
1455 
1456         /*second point of the tick*/
1457         bool major = false;
1458         if(i % t->minor_cnt == 0) major = true;
1459 
1460         if(major) p2.x = p1.x - major_len; /*major tick*/
1461         else p2.x = p1.x - minor_len; /*minor tick*/
1462 
1463         part_draw_dsc.p1 = &p1;
1464         part_draw_dsc.p2 = &p2;
1465 
1466         int32_t tick_value = lv_map(total_tick_num - i, 0, total_tick_num, chart->ymin[sec_axis], chart->ymax[sec_axis]);
1467         part_draw_dsc.value = tick_value;
1468 
1469         /*add text only to major tick*/
1470         if(major && t->label_en)  {
1471             char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH];
1472             lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value);
1473             part_draw_dsc.label_dsc = &label_dsc;
1474             part_draw_dsc.text = buf;
1475             part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH;
1476             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1477 
1478             /*reserve appropriate area*/
1479             lv_point_t size;
1480             lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX,
1481                             LV_TEXT_FLAG_NONE);
1482 
1483             /*set the area at some distance of the major tick len left of the tick*/
1484             lv_area_t a;
1485             a.y1 = p2.y - size.y / 2;
1486             a.y2 = p2.y + size.y / 2;
1487 
1488             if(!sec_axis) {
1489                 a.x1 = p2.x - size.x - label_gap;
1490                 a.x2 = p2.x - label_gap;
1491             }
1492             else {
1493                 a.x1 = p2.x + label_gap;
1494                 a.x2 = p2.x + size.x + label_gap;
1495             }
1496 
1497             if(a.y2 >= obj->coords.y1 &&
1498                a.y1  <= obj->coords.y2) {
1499                 lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL);
1500             }
1501         }
1502         else {
1503             part_draw_dsc.label_dsc = NULL;
1504             part_draw_dsc.text = NULL;
1505             part_draw_dsc.text_length = 0;
1506             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1507         }
1508 
1509         if(p1.y + line_dsc.width / 2  >= obj->coords.y1 &&
1510            p2.y - line_dsc.width / 2  <= obj->coords.y2) {
1511             lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
1512         }
1513 
1514         lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1515     }
1516 }
1517 
draw_x_ticks(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx,lv_chart_axis_t axis)1518 static void draw_x_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis)
1519 {
1520     lv_chart_t * chart  = (lv_chart_t *)obj;
1521 
1522     lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis);
1523     if(t->major_cnt <= 1) return;
1524     if(!t->label_en && !t->major_len && !t->minor_len) return;
1525     uint32_t total_tick_num = (t->major_cnt - 1) * (t->minor_cnt);
1526     if(total_tick_num == 0) return;
1527 
1528     uint32_t i;
1529     lv_point_t p1;
1530     lv_point_t p2;
1531 
1532     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1533     lv_coord_t w     = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
1534 
1535     lv_draw_label_dsc_t label_dsc;
1536     lv_draw_label_dsc_init(&label_dsc);
1537     lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc);
1538 
1539     lv_coord_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj);
1540     lv_coord_t y_ofs;
1541     lv_coord_t label_gap;
1542     if(axis == LV_CHART_AXIS_PRIMARY_X) {
1543         label_gap = t->label_en ? lv_obj_get_style_pad_bottom(obj, LV_PART_TICKS) : 0;
1544         y_ofs = obj->coords.y2 + 1;
1545     }
1546     else {
1547         label_gap = t->label_en ? lv_obj_get_style_pad_top(obj, LV_PART_TICKS) : 0;
1548         y_ofs = obj->coords.y1 - 1;
1549     }
1550 
1551     if(axis == LV_CHART_AXIS_PRIMARY_X) {
1552         if(y_ofs > draw_ctx->clip_area->y2) return;
1553         if(y_ofs + label_gap + label_dsc.font->line_height + t->major_len < draw_ctx->clip_area->y1) return;
1554     }
1555 
1556     lv_draw_line_dsc_t line_dsc;
1557     lv_draw_line_dsc_init(&line_dsc);
1558     lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc);
1559     line_dsc.dash_gap = 0;
1560     line_dsc.dash_width = 0;
1561 
1562     lv_obj_draw_part_dsc_t part_draw_dsc;
1563     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
1564     part_draw_dsc.class_p = MY_CLASS;
1565     part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL;
1566     part_draw_dsc.id = LV_CHART_AXIS_PRIMARY_X;
1567     part_draw_dsc.part = LV_PART_TICKS;
1568     part_draw_dsc.label_dsc = &label_dsc;
1569     part_draw_dsc.line_dsc = &line_dsc;
1570 
1571     uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_X ? 0 : 1;
1572 
1573     /*The columns ticks should be aligned to the center of blocks*/
1574     if(chart->type == LV_CHART_TYPE_BAR) {
1575         int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
1576                                                                   LV_PART_MAIN) * chart->zoom_x) >> 8;  /*Gap between the columns on ~adjacent X*/
1577         lv_coord_t block_w = (w + block_gap) / (chart->point_cnt);
1578 
1579         x_ofs += (block_w - block_gap) / 2;
1580         w -= block_w - block_gap;
1581     }
1582 
1583     p1.y = y_ofs;
1584     for(i = 0; i <= total_tick_num; i++) { /*one extra loop - it may not exist in the list, empty label*/
1585         bool major = false;
1586         if(i % t->minor_cnt == 0) major = true;
1587 
1588         /*draw a line at moving x position*/
1589         p2.x = p1.x = x_ofs + (int32_t)((int32_t)(w - line_dsc.width) * i) / total_tick_num;
1590 
1591         if(sec_axis) p2.y = p1.y - (major ? t->major_len : t->minor_len);
1592         else p2.y = p1.y + (major ? t->major_len : t->minor_len);
1593 
1594         part_draw_dsc.p1 = &p1;
1595         part_draw_dsc.p2 = &p2;
1596 
1597         /*add text only to major tick*/
1598         int32_t tick_value;
1599         if(chart->type == LV_CHART_TYPE_SCATTER) {
1600             tick_value = lv_map(i, 0, total_tick_num, chart->xmin[sec_axis], chart->xmax[sec_axis]);
1601         }
1602         else {
1603             tick_value = i / t->minor_cnt;
1604         }
1605         part_draw_dsc.value = tick_value;
1606 
1607         if(major && t->label_en) {
1608             char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH];
1609             lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value);
1610             part_draw_dsc.label_dsc = &label_dsc;
1611             part_draw_dsc.text = buf;
1612             part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH;
1613 
1614             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1615 
1616             /*reserve appropriate area*/
1617             lv_point_t size;
1618             lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX,
1619                             LV_TEXT_FLAG_NONE);
1620 
1621             /*set the area at some distance of the major tick len under of the tick*/
1622             lv_area_t a;
1623             a.x1 = (p2.x - size.x / 2);
1624             a.x2 = (p2.x + size.x / 2);
1625             if(sec_axis) {
1626                 a.y2 = p2.y - label_gap;
1627                 a.y1 = a.y2 - size.y;
1628             }
1629             else {
1630                 a.y1 = p2.y + label_gap;
1631                 a.y2 = a.y1 + size.y;
1632             }
1633 
1634             if(a.x2 >= obj->coords.x1 &&
1635                a.x1 <= obj->coords.x2) {
1636                 lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL);
1637             }
1638         }
1639         else {
1640             part_draw_dsc.label_dsc = NULL;
1641             part_draw_dsc.text = NULL;
1642             part_draw_dsc.text_length = 0;
1643             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1644         }
1645 
1646         if(p1.x + line_dsc.width / 2  >= obj->coords.x1 &&
1647            p2.x - line_dsc.width / 2  <= obj->coords.x2) {
1648             lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
1649         }
1650 
1651         lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1652     }
1653 }
1654 
draw_axes(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)1655 static void draw_axes(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
1656 {
1657     draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_Y);
1658     draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_Y);
1659     draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_X);
1660     draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_X);
1661 }
1662 
1663 /**
1664  * Get the nearest index to an X coordinate
1665  * @param chart pointer to a chart object
1666  * @param coord the coordination of the point relative to the series area.
1667  * @return the found index
1668  */
get_index_from_x(lv_obj_t * obj,lv_coord_t x)1669 static uint32_t get_index_from_x(lv_obj_t * obj, lv_coord_t x)
1670 {
1671     lv_chart_t * chart  = (lv_chart_t *)obj;
1672     lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
1673     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1674     x -= pad_left;
1675 
1676     if(x < 0) return 0;
1677     if(x > w) return chart->point_cnt - 1;
1678     if(chart->type == LV_CHART_TYPE_LINE) return (x * (chart->point_cnt - 1) + w / 2) / w;
1679     if(chart->type == LV_CHART_TYPE_BAR) return (x * chart->point_cnt) / w;
1680 
1681     return 0;
1682 }
1683 
invalidate_point(lv_obj_t * obj,uint16_t i)1684 static void invalidate_point(lv_obj_t * obj, uint16_t i)
1685 {
1686     lv_chart_t * chart  = (lv_chart_t *)obj;
1687     if(i >= chart->point_cnt) return;
1688 
1689     lv_coord_t w  = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
1690     lv_coord_t scroll_left = lv_obj_get_scroll_left(obj);
1691 
1692     /*In shift mode the whole chart changes so the whole object*/
1693     if(chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT) {
1694         lv_obj_invalidate(obj);
1695         return;
1696     }
1697 
1698     if(chart->type == LV_CHART_TYPE_LINE) {
1699         lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1700         lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1701         lv_coord_t x_ofs = obj->coords.x1 + pleft + bwidth - scroll_left;
1702         lv_coord_t line_width = lv_obj_get_style_line_width(obj, LV_PART_ITEMS);
1703         lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR);
1704 
1705         lv_area_t coords;
1706         lv_area_copy(&coords, &obj->coords);
1707         coords.y1 -= line_width + point_w;
1708         coords.y2 += line_width + point_w;
1709 
1710         if(i < chart->point_cnt - 1) {
1711             coords.x1 = ((w * i) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
1712             coords.x2 = ((w * (i + 1)) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
1713             lv_obj_invalidate_area(obj, &coords);
1714         }
1715 
1716         if(i > 0) {
1717             coords.x1 = ((w * (i - 1)) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
1718             coords.x2 = ((w * i) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
1719             lv_obj_invalidate_area(obj, &coords);
1720         }
1721     }
1722     else if(chart->type == LV_CHART_TYPE_BAR) {
1723         lv_area_t col_a;
1724         int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
1725                                                                   LV_PART_MAIN) * chart->zoom_x) >> 8;  /*Gap between the column on ~adjacent X*/
1726 
1727         lv_coord_t block_w = (w + block_gap) / chart->point_cnt;
1728 
1729         lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1730         lv_coord_t x_act;
1731         x_act = (int32_t)((int32_t)(block_w) * i) ;
1732         x_act += obj->coords.x1 + bwidth + lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1733 
1734         lv_obj_get_coords(obj, &col_a);
1735         col_a.x1 = x_act - scroll_left;
1736         col_a.x2 = col_a.x1 + block_w;
1737         col_a.x1 -= block_gap;
1738 
1739         lv_obj_invalidate_area(obj, &col_a);
1740     }
1741     else {
1742         lv_obj_invalidate(obj);
1743     }
1744 }
1745 
new_points_alloc(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t cnt,lv_coord_t ** a)1746 static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, lv_coord_t ** a)
1747 {
1748     if((*a) == NULL) return;
1749 
1750     lv_chart_t * chart = (lv_chart_t *) obj;
1751     uint32_t point_cnt_old = chart->point_cnt;
1752     uint32_t i;
1753 
1754     if(ser->start_point != 0) {
1755         lv_coord_t * new_points = lv_mem_alloc(sizeof(lv_coord_t) * cnt);
1756         LV_ASSERT_MALLOC(new_points);
1757         if(new_points == NULL) return;
1758 
1759         if(cnt >= point_cnt_old) {
1760             for(i = 0; i < point_cnt_old; i++) {
1761                 new_points[i] =
1762                     (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
1763             }
1764             for(i = point_cnt_old; i < cnt; i++) {
1765                 new_points[i] = LV_CHART_POINT_NONE; /*Fill up the rest with default value*/
1766             }
1767         }
1768         else {
1769             for(i = 0; i < cnt; i++) {
1770                 new_points[i] =
1771                     (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
1772             }
1773         }
1774 
1775         /*Switch over pointer from old to new*/
1776         lv_mem_free((*a));
1777         (*a) = new_points;
1778     }
1779     else {
1780         (*a) = lv_mem_realloc((*a), sizeof(lv_coord_t) * cnt);
1781         LV_ASSERT_MALLOC((*a));
1782         if((*a) == NULL) return;
1783         /*Initialize the new points*/
1784         if(cnt > point_cnt_old) {
1785             for(i = point_cnt_old - 1; i < cnt; i++) {
1786                 (*a)[i] = LV_CHART_POINT_NONE;
1787             }
1788         }
1789     }
1790 }
1791 
get_tick_gsc(lv_obj_t * obj,lv_chart_axis_t axis)1792 lv_chart_tick_dsc_t * get_tick_gsc(lv_obj_t * obj, lv_chart_axis_t axis)
1793 {
1794     lv_chart_t * chart = (lv_chart_t *) obj;
1795     switch(axis) {
1796         case LV_CHART_AXIS_PRIMARY_Y:
1797             return &chart->tick[0];
1798         case LV_CHART_AXIS_PRIMARY_X:
1799             return &chart->tick[1];
1800         case LV_CHART_AXIS_SECONDARY_Y:
1801             return &chart->tick[2];
1802         case LV_CHART_AXIS_SECONDARY_X:
1803             return &chart->tick[3];
1804         default:
1805             return NULL;
1806     }
1807 }
1808 
1809 
1810 #endif
1811