acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

lv_chart.c (61841B)

      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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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  */
    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  */
    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  */
    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  */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 more points than pixels 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 
   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 
   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 
   1186     lv_chart_t * chart  = (lv_chart_t *)obj;
   1187 
   1188     uint16_t i;
   1189     lv_area_t col_a;
   1190     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
   1191     lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
   1192     lv_coord_t w     = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
   1193     lv_coord_t h     = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
   1194     int32_t y_tmp;
   1195     lv_chart_series_t * ser;
   1196     uint32_t ser_cnt = _lv_ll_get_len(&chart->series_ll);
   1197     int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
   1198                                                               LV_PART_MAIN) * chart->zoom_x) >> 8;  /*Gap between the column on ~adjacent X*/
   1199     lv_coord_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;
   1200     lv_coord_t col_w = block_w / ser_cnt;
   1201     int32_t ser_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
   1202                                                             LV_PART_ITEMS) * chart->zoom_x) >> 8; /*Gap between the column on the ~same X*/
   1203     lv_coord_t x_ofs = pad_left - lv_obj_get_scroll_left(obj);
   1204     lv_coord_t y_ofs = pad_top - lv_obj_get_scroll_top(obj);
   1205 
   1206     lv_draw_rect_dsc_t col_dsc;
   1207     lv_draw_rect_dsc_init(&col_dsc);
   1208     lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc);
   1209     col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE;
   1210     col_dsc.bg_opa = LV_OPA_COVER;
   1211 
   1212     /*Make the cols longer with `radius` to clip the rounding from the bottom*/
   1213     col_a.y2 = obj->coords.y2 + col_dsc.radius;
   1214 
   1215     lv_obj_draw_part_dsc_t part_draw_dsc;
   1216     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
   1217     part_draw_dsc.part = LV_PART_ITEMS;
   1218     part_draw_dsc.class_p = MY_CLASS;
   1219     part_draw_dsc.type = LV_CHART_DRAW_PART_BAR;
   1220 
   1221     /*Go through all points*/
   1222     for(i = 0; i < chart->point_cnt; i++) {
   1223         lv_coord_t x_act = (int32_t)((int32_t)(w + block_gap) * i) / (chart->point_cnt) + obj->coords.x1 + x_ofs;
   1224 
   1225         part_draw_dsc.id = i;
   1226 
   1227         /*Draw the current point of all data line*/
   1228         _LV_LL_READ_BACK(&chart->series_ll, ser) {
   1229             if(ser->hidden) continue;
   1230             lv_coord_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
   1231 
   1232             col_a.x1 = x_act;
   1233             col_a.x2 = col_a.x1 + col_w - ser_gap - 1;
   1234             x_act += col_w;
   1235 
   1236             if(col_a.x2 < clip_area.x1) continue;
   1237             if(col_a.x1 > clip_area.x2) break;
   1238 
   1239             col_dsc.bg_color = ser->color;
   1240 
   1241             lv_coord_t p_act = (start_point + i) % chart->point_cnt;
   1242             y_tmp            = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
   1243             y_tmp            = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
   1244             col_a.y1         = h - y_tmp + obj->coords.y1 + y_ofs;
   1245 
   1246             if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
   1247                 part_draw_dsc.draw_area = &col_a;
   1248                 part_draw_dsc.rect_dsc = &col_dsc;
   1249                 part_draw_dsc.sub_part_ptr = ser;
   1250                 part_draw_dsc.value = ser->y_points[p_act];
   1251                 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
   1252                 lv_draw_rect(draw_ctx, &col_dsc, &col_a);
   1253                 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
   1254             }
   1255         }
   1256     }
   1257     draw_ctx->clip_area = clip_area_ori;
   1258 }
   1259 
   1260 static void draw_cursors(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
   1261 {
   1262     LV_ASSERT_OBJ(obj, MY_CLASS);
   1263 
   1264     lv_chart_t * chart  = (lv_chart_t *)obj;
   1265     if(_lv_ll_is_empty(&chart->cursor_ll)) return;
   1266 
   1267     lv_area_t clip_area;
   1268     if(!_lv_area_intersect(&clip_area, draw_ctx->clip_area, &obj->coords)) return;
   1269 
   1270     const lv_area_t * clip_area_ori = draw_ctx->clip_area;
   1271     draw_ctx->clip_area = &clip_area;
   1272 
   1273     lv_point_t p1;
   1274     lv_point_t p2;
   1275     lv_chart_cursor_t * cursor;
   1276 
   1277     lv_draw_line_dsc_t line_dsc_ori;
   1278     lv_draw_line_dsc_init(&line_dsc_ori);
   1279     lv_obj_init_draw_line_dsc(obj, LV_PART_CURSOR, &line_dsc_ori);
   1280 
   1281     lv_draw_rect_dsc_t point_dsc_ori;
   1282     lv_draw_rect_dsc_init(&point_dsc_ori);
   1283     point_dsc_ori.bg_opa = line_dsc_ori.opa;
   1284     point_dsc_ori.radius = LV_RADIUS_CIRCLE;
   1285 
   1286     lv_draw_line_dsc_t line_dsc_tmp;
   1287     lv_draw_rect_dsc_t point_dsc_tmp;
   1288 
   1289     lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
   1290     lv_coord_t point_h = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
   1291 
   1292     lv_obj_draw_part_dsc_t part_draw_dsc;
   1293     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
   1294     part_draw_dsc.line_dsc = &line_dsc_tmp;
   1295     part_draw_dsc.rect_dsc = &point_dsc_tmp;
   1296     part_draw_dsc.part = LV_PART_CURSOR;
   1297     part_draw_dsc.class_p = MY_CLASS;
   1298     part_draw_dsc.type = LV_CHART_DRAW_PART_CURSOR;
   1299 
   1300     /*Go through all cursor lines*/
   1301     _LV_LL_READ_BACK(&chart->cursor_ll, cursor) {
   1302         lv_memcpy(&line_dsc_tmp, &line_dsc_ori, sizeof(lv_draw_line_dsc_t));
   1303         lv_memcpy(&point_dsc_tmp, &point_dsc_ori, sizeof(lv_draw_rect_dsc_t));
   1304         line_dsc_tmp.color = cursor->color;
   1305         point_dsc_tmp.bg_color = cursor->color;
   1306 
   1307         part_draw_dsc.p1 = &p1;
   1308         part_draw_dsc.p2 = &p2;
   1309 
   1310         lv_coord_t cx;
   1311         lv_coord_t cy;
   1312         if(cursor->pos_set) {
   1313             cx = cursor->pos.x;
   1314             cy = cursor->pos.y;
   1315         }
   1316         else {
   1317             if(cursor->point_id == LV_CHART_POINT_NONE) continue;
   1318             lv_point_t p;
   1319             lv_chart_get_point_pos_by_id(obj, cursor->ser, cursor->point_id, &p);
   1320             cx = p.x;
   1321             cy = p.y;
   1322         }
   1323 
   1324         cx += obj->coords.x1;
   1325         cy += obj->coords.y1;
   1326 
   1327         lv_area_t point_area;
   1328         if(point_w && point_h) {
   1329             point_area.x1 = cx - point_w;
   1330             point_area.x2 = cx + point_w;
   1331             point_area.y1 = cy - point_h;
   1332             point_area.y2 = cy + point_h;
   1333 
   1334             part_draw_dsc.draw_area = &point_area;
   1335         }
   1336         else {
   1337             part_draw_dsc.draw_area = NULL;
   1338         }
   1339 
   1340         if(cursor->dir & LV_DIR_HOR) {
   1341             p1.x = cursor->dir & LV_DIR_LEFT ? obj->coords.x1 : cx;
   1342             p1.y = cy;
   1343             p2.x = cursor->dir & LV_DIR_RIGHT ? obj->coords.x2 : cx;
   1344             p2.y = p1.y;
   1345 
   1346             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
   1347             lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2);
   1348             lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area);
   1349             lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
   1350         }
   1351 
   1352         if(cursor->dir & LV_DIR_VER) {
   1353             p1.x = cx;
   1354             p1.y = cursor->dir & LV_DIR_TOP ? obj->coords.y1 : cy;
   1355             p2.x = p1.x;
   1356             p2.y = cursor->dir & LV_DIR_BOTTOM ? obj->coords.y2 : cy;
   1357 
   1358             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
   1359             lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2);
   1360             lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area);
   1361             lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
   1362         }
   1363     }
   1364 
   1365     draw_ctx->clip_area = clip_area_ori;
   1366 }
   1367 
   1368 static void draw_y_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis)
   1369 {
   1370     lv_chart_t * chart  = (lv_chart_t *)obj;
   1371 
   1372     lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis);
   1373 
   1374     if(t->major_cnt <= 1) return;
   1375     if(!t->label_en && !t->major_len && !t->minor_len) return;
   1376 
   1377     uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_Y ? 0 : 1;
   1378 
   1379     uint32_t i;
   1380 
   1381     lv_point_t p1;
   1382     lv_point_t p2;
   1383 
   1384     lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
   1385     lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
   1386     lv_coord_t h     = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
   1387     lv_coord_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj);
   1388 
   1389     lv_coord_t label_gap;
   1390     lv_coord_t x_ofs;
   1391     if(axis == LV_CHART_AXIS_PRIMARY_Y) {
   1392         label_gap = lv_obj_get_style_pad_left(obj, LV_PART_TICKS);
   1393         x_ofs = obj->coords.x1;
   1394     }
   1395     else {
   1396         label_gap = lv_obj_get_style_pad_right(obj, LV_PART_TICKS);
   1397         x_ofs = obj->coords.x2;
   1398     }
   1399 
   1400     lv_coord_t major_len = t->major_len;
   1401     lv_coord_t minor_len = t->minor_len;
   1402     /*tick lines on secondary y axis are drawn in other direction*/
   1403     if(axis == LV_CHART_AXIS_SECONDARY_Y) {
   1404         major_len *= -1;
   1405         minor_len *= -1;
   1406     }
   1407 
   1408     lv_draw_line_dsc_t line_dsc;
   1409     lv_draw_line_dsc_init(&line_dsc);
   1410     lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc);
   1411 
   1412     lv_draw_label_dsc_t label_dsc;
   1413     lv_draw_label_dsc_init(&label_dsc);
   1414     lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc);
   1415 
   1416     lv_obj_draw_part_dsc_t part_draw_dsc;
   1417     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
   1418     part_draw_dsc.class_p = MY_CLASS;
   1419     part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL;
   1420     part_draw_dsc.id = axis;
   1421     part_draw_dsc.part = LV_PART_TICKS;
   1422     part_draw_dsc.line_dsc = &line_dsc;
   1423     part_draw_dsc.label_dsc = &label_dsc;
   1424 
   1425     uint32_t total_tick_num = (t->major_cnt - 1) * (t->minor_cnt);
   1426     for(i = 0; i <= total_tick_num; i++) {
   1427         /*draw a line at moving y position*/
   1428         p2.y = p1.y = y_ofs + (int32_t)((int32_t)(h - line_dsc.width) * i) / total_tick_num;
   1429 
   1430         /*first point of the tick*/
   1431         p1.x = x_ofs;
   1432 
   1433         /*move extra pixel out of chart boundary*/
   1434         if(axis == LV_CHART_AXIS_PRIMARY_Y) p1.x--;
   1435         else p1.x++;
   1436 
   1437         /*second point of the tick*/
   1438         bool major = false;
   1439         if(i % t->minor_cnt == 0) major = true;
   1440 
   1441         if(major) p2.x = p1.x - major_len; /*major tick*/
   1442         else p2.x = p1.x - minor_len; /*minor tick*/
   1443 
   1444         part_draw_dsc.p1 = &p1;
   1445         part_draw_dsc.p2 = &p2;
   1446 
   1447         int32_t tick_value = lv_map(total_tick_num - i, 0, total_tick_num, chart->ymin[sec_axis], chart->ymax[sec_axis]);
   1448         part_draw_dsc.value = tick_value;
   1449 
   1450         /*add text only to major tick*/
   1451         if(major && t->label_en)  {
   1452             char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH];
   1453             lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value);
   1454             part_draw_dsc.label_dsc = &label_dsc;
   1455             part_draw_dsc.text = buf;
   1456             part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH;
   1457             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
   1458 
   1459             /*reserve appropriate area*/
   1460             lv_point_t size;
   1461             lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX,
   1462                             LV_TEXT_FLAG_NONE);
   1463 
   1464             /*set the area at some distance of the major tick len left of the tick*/
   1465             lv_area_t a;
   1466             a.y1 = p2.y - size.y / 2;
   1467             a.y2 = p2.y + size.y / 2;
   1468 
   1469             if(!sec_axis) {
   1470                 a.x1 = p2.x - size.x - label_gap;
   1471                 a.x2 = p2.x - label_gap;
   1472             }
   1473             else {
   1474                 a.x1 = p2.x + label_gap;
   1475                 a.x2 = p2.x + size.x + label_gap;
   1476             }
   1477 
   1478             if(a.y2 >= obj->coords.y1 &&
   1479                a.y1  <= obj->coords.y2) {
   1480                 lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL);
   1481             }
   1482         }
   1483         else {
   1484             part_draw_dsc.label_dsc = NULL;
   1485             part_draw_dsc.text = NULL;
   1486             part_draw_dsc.text_length = 0;
   1487             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
   1488         }
   1489 
   1490         if(p1.y + line_dsc.width / 2  >= obj->coords.y1 &&
   1491            p2.y - line_dsc.width / 2  <= obj->coords.y2) {
   1492             lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
   1493         }
   1494 
   1495         lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
   1496     }
   1497 }
   1498 
   1499 static void draw_x_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis)
   1500 {
   1501     lv_chart_t * chart  = (lv_chart_t *)obj;
   1502 
   1503     lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis);
   1504     if(t->major_cnt <= 1) return;
   1505     if(!t->label_en && !t->major_len && !t->minor_len) return;
   1506 
   1507     uint32_t i;
   1508     lv_point_t p1;
   1509     lv_point_t p2;
   1510 
   1511     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);
   1512     lv_coord_t w     = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
   1513 
   1514 
   1515     lv_draw_label_dsc_t label_dsc;
   1516     lv_draw_label_dsc_init(&label_dsc);
   1517     lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc);
   1518 
   1519     lv_coord_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj);
   1520     lv_coord_t y_ofs;
   1521     lv_coord_t label_gap;
   1522     if(axis == LV_CHART_AXIS_PRIMARY_X) {
   1523         label_gap = t->label_en ? lv_obj_get_style_pad_bottom(obj, LV_PART_TICKS) : 0;
   1524         y_ofs = obj->coords.y2;
   1525     }
   1526     else {
   1527         label_gap = t->label_en ? lv_obj_get_style_pad_top(obj, LV_PART_TICKS) : 0;
   1528         y_ofs = obj->coords.y1;
   1529     }
   1530 
   1531     if(axis == LV_CHART_AXIS_PRIMARY_X) {
   1532         if(y_ofs > draw_ctx->clip_area->y2) return;
   1533         if(y_ofs + label_gap  + label_dsc.font->line_height + t->major_len < draw_ctx->clip_area->y1) return;
   1534     }
   1535 
   1536     lv_draw_line_dsc_t line_dsc;
   1537     lv_draw_line_dsc_init(&line_dsc);
   1538     lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc);
   1539     line_dsc.dash_gap = 0;
   1540     line_dsc.dash_width = 0;
   1541 
   1542     lv_obj_draw_part_dsc_t part_draw_dsc;
   1543     lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
   1544     part_draw_dsc.class_p = MY_CLASS;
   1545     part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL;
   1546     part_draw_dsc.id = LV_CHART_AXIS_PRIMARY_X;
   1547     part_draw_dsc.part = LV_PART_TICKS;
   1548     part_draw_dsc.label_dsc = &label_dsc;
   1549     part_draw_dsc.line_dsc = &line_dsc;
   1550 
   1551     uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_X ? 0 : 1;
   1552 
   1553     /*The columns ticks should be aligned to the center of blocks*/
   1554     if(chart->type == LV_CHART_TYPE_BAR) {
   1555         int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
   1556                                                                   LV_PART_MAIN) * chart->zoom_x) >> 8;  /*Gap between the columns on ~adjacent X*/
   1557         lv_coord_t block_w = (w + block_gap) / (chart->point_cnt);
   1558         x_ofs += (block_w - block_gap) / 2;
   1559         w -= block_w - block_gap;
   1560     }
   1561 
   1562     p1.y = y_ofs;
   1563     uint32_t total_tick_num = (t->major_cnt - 1) * t->minor_cnt;
   1564     for(i = 0; i <= total_tick_num; i++) { /*one extra loop - it may not exist in the list, empty label*/
   1565         bool major = false;
   1566         if(i % t->minor_cnt == 0) major = true;
   1567 
   1568         /*draw a line at moving x position*/
   1569         p2.x = p1.x = x_ofs + (int32_t)((int32_t)(w - line_dsc.width) * i) / total_tick_num;
   1570 
   1571         if(sec_axis) p2.y = p1.y - (major ? t->major_len : t->minor_len);
   1572         else p2.y = p1.y + (major ? t->major_len : t->minor_len);
   1573 
   1574         part_draw_dsc.p1 = &p1;
   1575         part_draw_dsc.p2 = &p2;
   1576 
   1577         /*add text only to major tick*/
   1578         int32_t tick_value;
   1579         if(chart->type == LV_CHART_TYPE_SCATTER) {
   1580             tick_value = lv_map(i, 0, total_tick_num, chart->xmin[sec_axis], chart->xmax[sec_axis]);
   1581         }
   1582         else {
   1583             tick_value = i / t->minor_cnt;
   1584         }
   1585         part_draw_dsc.value = tick_value;
   1586 
   1587         if(major && t->label_en) {
   1588             char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH];
   1589             lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value);
   1590             part_draw_dsc.label_dsc = &label_dsc;
   1591             part_draw_dsc.text = buf;
   1592             part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH;
   1593 
   1594             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
   1595 
   1596             /*reserve appropriate area*/
   1597             lv_point_t size;
   1598             lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX,
   1599                             LV_TEXT_FLAG_NONE);
   1600 
   1601             /*set the area at some distance of the major tick len under of the tick*/
   1602             lv_area_t a;
   1603             a.x1 = (p2.x - size.x / 2);
   1604             a.x2 = (p2.x + size.x / 2);
   1605             if(sec_axis) {
   1606                 a.y2 = p2.y - label_gap;
   1607                 a.y1 = a.y2 - size.y;
   1608             }
   1609             else {
   1610                 a.y1 = p2.y + label_gap;
   1611                 a.y2 = a.y1 + size.y;
   1612             }
   1613 
   1614             if(a.x2 >= obj->coords.x1 &&
   1615                a.x1 <= obj->coords.x2) {
   1616                 lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL);
   1617             }
   1618         }
   1619         else {
   1620             part_draw_dsc.label_dsc = NULL;
   1621             part_draw_dsc.text = NULL;
   1622             part_draw_dsc.text_length = 0;
   1623             lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
   1624         }
   1625 
   1626 
   1627         if(p1.x + line_dsc.width / 2  >= obj->coords.x1 &&
   1628            p2.x - line_dsc.width / 2  <= obj->coords.x2) {
   1629             lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
   1630         }
   1631 
   1632         lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
   1633     }
   1634 }
   1635 
   1636 static void draw_axes(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
   1637 {
   1638     draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_Y);
   1639     draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_Y);
   1640     draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_X);
   1641     draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_X);
   1642 }
   1643 
   1644 /**
   1645  * Get the nearest index to an X coordinate
   1646  * @param chart pointer to a chart object
   1647  * @param coord the coordination of the point relative to the series area.
   1648  * @return the found index
   1649  */
   1650 static uint32_t get_index_from_x(lv_obj_t * obj, lv_coord_t x)
   1651 {
   1652     lv_chart_t * chart  = (lv_chart_t *)obj;
   1653     lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
   1654     lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
   1655     x -= pad_left;
   1656 
   1657     if(x < 0) return 0;
   1658     if(x > w) return chart->point_cnt - 1;
   1659     if(chart->type == LV_CHART_TYPE_LINE) return (x * (chart->point_cnt - 1) + w / 2) / w;
   1660     if(chart->type == LV_CHART_TYPE_BAR) return (x * chart->point_cnt) / w;
   1661 
   1662     return 0;
   1663 }
   1664 
   1665 static void invalidate_point(lv_obj_t * obj, uint16_t i)
   1666 {
   1667     lv_chart_t * chart  = (lv_chart_t *)obj;
   1668     if(i >= chart->point_cnt) return;
   1669 
   1670     lv_coord_t w  = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
   1671     lv_coord_t scroll_left = lv_obj_get_scroll_left(obj);
   1672 
   1673     /*In shift mode the whole chart changes so the whole object*/
   1674     if(chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT) {
   1675         lv_obj_invalidate(obj);
   1676         return;
   1677     }
   1678 
   1679     if(chart->type == LV_CHART_TYPE_LINE) {
   1680         lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
   1681         lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
   1682         lv_coord_t x_ofs = obj->coords.x1 + pleft + bwidth - scroll_left;
   1683         lv_coord_t line_width = lv_obj_get_style_line_width(obj, LV_PART_ITEMS);
   1684         lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR);
   1685 
   1686         lv_area_t coords;
   1687         lv_area_copy(&coords, &obj->coords);
   1688         coords.y1 -= line_width + point_w;
   1689         coords.y2 += line_width + point_w;
   1690 
   1691         if(i < chart->point_cnt - 1) {
   1692             coords.x1 = ((w * i) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
   1693             coords.x2 = ((w * (i + 1)) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
   1694             lv_obj_invalidate_area(obj, &coords);
   1695         }
   1696 
   1697         if(i > 0) {
   1698             coords.x1 = ((w * (i - 1)) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
   1699             coords.x2 = ((w * i) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
   1700             lv_obj_invalidate_area(obj, &coords);
   1701         }
   1702     }
   1703     else if(chart->type == LV_CHART_TYPE_BAR) {
   1704         lv_area_t col_a;
   1705         int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
   1706                                                                   LV_PART_MAIN) * chart->zoom_x) >> 8;  /*Gap between the column on ~adjacent X*/
   1707         lv_coord_t block_w = (w + block_gap) / chart->point_cnt;
   1708 
   1709         lv_coord_t x_act;
   1710         x_act = (int32_t)((int32_t)(block_w) * i) ;
   1711         x_act += obj->coords.x1 + lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
   1712 
   1713         lv_obj_get_coords(obj, &col_a);
   1714         col_a.x1 = x_act - scroll_left;
   1715         col_a.x2 = col_a.x1 + block_w;
   1716         col_a.x1 -= block_gap;
   1717 
   1718         lv_obj_invalidate_area(obj, &col_a);
   1719     }
   1720     else {
   1721         lv_obj_invalidate(obj);
   1722     }
   1723 }
   1724 
   1725 static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, lv_coord_t ** a)
   1726 {
   1727     if((*a) == NULL) return;
   1728 
   1729     lv_chart_t * chart = (lv_chart_t *) obj;
   1730     uint32_t point_cnt_old = chart->point_cnt;
   1731     uint32_t i;
   1732 
   1733     if(ser->start_point != 0) {
   1734         lv_coord_t * new_points = lv_mem_alloc(sizeof(lv_coord_t) * cnt);
   1735         LV_ASSERT_MALLOC(new_points);
   1736         if(new_points == NULL) return;
   1737 
   1738         if(cnt >= point_cnt_old) {
   1739             for(i = 0; i < point_cnt_old; i++) {
   1740                 new_points[i] =
   1741                     (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
   1742             }
   1743             for(i = point_cnt_old; i < cnt; i++) {
   1744                 new_points[i] = LV_CHART_POINT_NONE; /*Fill up the rest with default value*/
   1745             }
   1746         }
   1747         else {
   1748             for(i = 0; i < cnt; i++) {
   1749                 new_points[i] =
   1750                     (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
   1751             }
   1752         }
   1753 
   1754         /*Switch over pointer from old to new*/
   1755         lv_mem_free((*a));
   1756         (*a) = new_points;
   1757     }
   1758     else {
   1759         (*a) = lv_mem_realloc((*a), sizeof(lv_coord_t) * cnt);
   1760         LV_ASSERT_MALLOC((*a));
   1761         if((*a) == NULL) return;
   1762         /*Initialize the new points*/
   1763         if(cnt > point_cnt_old) {
   1764             for(i = point_cnt_old - 1; i < cnt; i++) {
   1765                 (*a)[i] = LV_CHART_POINT_NONE;
   1766             }
   1767         }
   1768     }
   1769 }
   1770 
   1771 lv_chart_tick_dsc_t * get_tick_gsc(lv_obj_t * obj, lv_chart_axis_t axis)
   1772 {
   1773     lv_chart_t * chart = (lv_chart_t *) obj;
   1774     switch(axis) {
   1775         case LV_CHART_AXIS_PRIMARY_Y:
   1776             return &chart->tick[0];
   1777         case LV_CHART_AXIS_PRIMARY_X:
   1778             return &chart->tick[1];
   1779         case LV_CHART_AXIS_SECONDARY_Y:
   1780             return &chart->tick[2];
   1781         case LV_CHART_AXIS_SECONDARY_X:
   1782             return &chart->tick[3];
   1783         default:
   1784             return NULL;
   1785     }
   1786 }
   1787 
   1788 
   1789 #endif