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