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