/**
 * @file lv_chart.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_chart_private.h"
#include "../../misc/lv_area_private.h"
#include "../../draw/lv_draw_private.h"
#include "../../core/lv_obj_private.h"
#include "../../core/lv_obj_class_private.h"
#include "../../core/lv_obj_draw_private.h"
#if LV_USE_CHART != 0

#include "../../misc/lv_assert.h"

/*********************
 *      DEFINES
 *********************/
#define MY_CLASS (&lv_chart_class)

#define LV_CHART_HDIV_DEF 3
#define LV_CHART_VDIV_DEF 5
#define LV_CHART_POINT_CNT_DEF 10
#define LV_CHART_LABEL_MAX_TEXT_LENGTH 16

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e);

static void draw_div_lines(lv_obj_t * obj, lv_layer_t * layer);
static void draw_series_line(lv_obj_t * obj, lv_layer_t * layer);
static void draw_series_bar(lv_obj_t * obj, lv_layer_t * layer);
static void draw_series_stacked(lv_obj_t * obj, lv_layer_t * layer);
static void draw_series_scatter(lv_obj_t * obj, lv_layer_t * layer);
static void draw_cursors(lv_obj_t * obj, lv_layer_t * layer);
static uint32_t get_index_from_x(lv_obj_t * obj, int32_t x);
static void invalidate_point(lv_obj_t * obj, uint32_t i);
static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, int32_t ** a);
static int32_t value_to_y(lv_obj_t * obj, lv_chart_series_t * ser, int32_t v, int32_t h);

/**********************
 *  STATIC VARIABLES
 **********************/

const lv_obj_class_t lv_chart_class = {
    .constructor_cb = lv_chart_constructor,
    .destructor_cb = lv_chart_destructor,
    .event_cb = lv_chart_event,
    .width_def = LV_PCT(100),
    .height_def = LV_DPI_DEF * 2,
    .instance_size = sizeof(lv_chart_t),
    .base_class = &lv_obj_class,
    .name = "lv_chart",
};

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

lv_obj_t * lv_chart_create(lv_obj_t * parent)
{
    LV_LOG_INFO("begin");
    lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
    lv_obj_class_init_obj(obj);
    return obj;
}

void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(chart->type == type) return;

    if(chart->type == LV_CHART_TYPE_SCATTER) {
        lv_chart_series_t * ser;
        LV_LL_READ_BACK(&chart->series_ll, ser) {
            lv_free(ser->x_points);
            ser->x_points = NULL;
        }
    }

    if(type == LV_CHART_TYPE_SCATTER) {
        lv_chart_series_t * ser;
        LV_LL_READ_BACK(&chart->series_ll, ser) {
            ser->x_points = lv_malloc(sizeof(int32_t) * chart->point_cnt);
            LV_ASSERT_MALLOC(ser->x_points);
            if(ser->x_points == NULL) return;
        }
    }

    chart->type = type;

    lv_chart_refresh(obj);
}

void lv_chart_set_point_count(lv_obj_t * obj, uint32_t cnt)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(chart->point_cnt == cnt) return;

    lv_chart_series_t * ser;

    if(cnt < 1) cnt = 1;

    LV_LL_READ_BACK(&chart->series_ll, ser) {
        if(chart->type == LV_CHART_TYPE_SCATTER) {
            if(!ser->x_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->x_points);
        }
        if(!ser->y_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->y_points);
        ser->start_point = 0;
    }

    chart->point_cnt = cnt;

    lv_chart_refresh(obj);
}

void lv_chart_set_axis_min_value(lv_obj_t * obj, lv_chart_axis_t axis, int32_t min)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;

    switch(axis) {
        case LV_CHART_AXIS_PRIMARY_Y:
            if(chart->ymin[0] == min) return;
            chart->ymin[0] = min;
            break;
        case LV_CHART_AXIS_SECONDARY_Y:
            if(chart->ymin[1] == min) return;
            chart->ymin[1] = min;
            break;
        case LV_CHART_AXIS_PRIMARY_X:
            if(chart->xmin[0] == min) return;
            chart->xmin[0] = min;
            break;
        case LV_CHART_AXIS_SECONDARY_X:
            if(chart->xmin[1] == min) return;
            chart->xmin[1] = min;
            break;
        default:
            LV_LOG_WARN("Invalid axis: %d", axis);
            return;
    }

    lv_chart_refresh(obj);
}

void lv_chart_set_axis_max_value(lv_obj_t * obj, lv_chart_axis_t axis, int32_t max)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    switch(axis) {
        case LV_CHART_AXIS_PRIMARY_Y:
            if(chart->ymax[0] == max) return;
            chart->ymax[0] = max;
            break;
        case LV_CHART_AXIS_SECONDARY_Y:
            if(chart->ymax[1] == max) return;
            chart->ymax[1] = max;
            break;
        case LV_CHART_AXIS_PRIMARY_X:
            if(chart->xmax[0] == max) return;
            chart->xmax[0] = max;
            break;
        case LV_CHART_AXIS_SECONDARY_X:
            if(chart->xmax[1] == max) return;
            chart->xmax[1] = max;
            break;
        default:
            LV_LOG_WARN("Invalid axis: %d", axis);
            return;
    }

    lv_chart_refresh(obj);
}

void lv_chart_set_axis_range(lv_obj_t * obj, lv_chart_axis_t axis, int32_t min, int32_t max)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_set_axis_min_value(obj, axis, min);
    lv_chart_set_axis_max_value(obj, axis, max);
}

void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(chart->update_mode == update_mode) return;

    chart->update_mode = update_mode;
    lv_obj_invalidate(obj);
}

void lv_chart_set_div_line_count(lv_obj_t * obj, uint32_t hdiv, uint32_t vdiv)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(chart->hdiv_cnt == hdiv && chart->vdiv_cnt == vdiv) return;

    chart->hdiv_cnt = hdiv;
    chart->vdiv_cnt = vdiv;

    lv_obj_invalidate(obj);
}

void lv_chart_set_hor_div_line_count(lv_obj_t * obj, uint32_t cnt)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(chart->hdiv_cnt == cnt) return;
    chart->hdiv_cnt = cnt;
    lv_obj_invalidate(obj);
}

void lv_chart_set_ver_div_line_count(lv_obj_t * obj, uint32_t cnt)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(chart->vdiv_cnt == cnt) return;
    chart->vdiv_cnt = cnt;
    lv_obj_invalidate(obj);
}

lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    return chart->type;
}

uint32_t lv_chart_get_point_count(const lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    return chart->point_cnt;
}

uint32_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser)
{
    LV_ASSERT_NULL(ser);
    LV_UNUSED(obj);

    return ser->start_point;
}

void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, lv_point_t * p_out)
{
    LV_ASSERT_NULL(obj);
    LV_ASSERT_NULL(ser);
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(id >= chart->point_cnt) {
        LV_LOG_WARN("Invalid index: %"LV_PRIu32, id);
        p_out->x = 0;
        p_out->y = 0;
        return;
    }

    int32_t w = lv_obj_get_content_width(obj);
    int32_t h = lv_obj_get_content_height(obj);

    if(chart->type == LV_CHART_TYPE_LINE) {
        if(chart->point_cnt > 1) {
            p_out->x = (w * id) / (chart->point_cnt - 1);
        }
        else {
            p_out->x = 0;
        }
        int32_t temp_y = value_to_y(obj, ser, ser->y_points[id], h);
        p_out->y = h - temp_y;
    }
    else if(chart->type == LV_CHART_TYPE_SCATTER) {
        p_out->x = lv_map(ser->x_points[id], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
        int32_t temp_y = value_to_y(obj, ser, ser->y_points[id], h);
        p_out->y = h - temp_y;
    }
    else if(chart->type == LV_CHART_TYPE_BAR) {
        uint32_t ser_cnt = lv_ll_get_len(&chart->series_ll);
        int32_t ser_gap = lv_obj_get_style_pad_column(obj, LV_PART_ITEMS);

        /*Gap between the columns on adjacent X ticks*/
        int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);

        int32_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;

        if(chart->point_cnt > 1) {
            p_out->x = (int32_t)((int32_t)(w - block_w) * id) / (chart->point_cnt - 1);
        }
        else {
            p_out->x = 0;
        }

        lv_chart_series_t * ser_i = NULL;
        uint32_t ser_idx = 0;
        LV_LL_READ(&chart->series_ll, ser_i) {
            if(ser_i == ser) break;
            ser_idx++;
        }

        p_out->x = (int32_t)((int32_t)(w + block_gap) * id) / chart->point_cnt;
        if(ser_cnt > 0) {
            p_out->x += block_w * ser_idx / ser_cnt;

            int32_t col_w = (block_w - (ser_gap * (ser_cnt - 1))) / ser_cnt;
            p_out->x += col_w / 2;
        }
        else {
            LV_LOG_WARN("bar chart series count is zero");
        }

        int32_t temp_y = value_to_y(obj, ser, ser->y_points[id], h);
        p_out->y = h - temp_y;
    }
    else if(chart->type == LV_CHART_TYPE_STACKED) {
        /*Gap between the columns on adjacent X ticks*/
        int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);

        int32_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;

        if(chart->point_cnt > 1) {
            p_out->x = (int32_t)((int32_t)(w - block_w) * id) / (chart->point_cnt - 1);
        }
        else {
            p_out->x = 0;
        }

        p_out->x += block_w / 2;

        int32_t v_sum = 0;
        lv_chart_series_t * s;
        LV_LL_READ(&chart->series_ll, s) {
            if(s->hidden) continue;
            int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? s->start_point : 0;
            int32_t p_act = (start_point + id) % chart->point_cnt;
            int32_t v_act = s->y_points[p_act];
            if(s->y_points[p_act] == LV_CHART_POINT_NONE) continue;

            /* Skip negative values in stacked charts. Negative values are not supported
             * in stacked charts as they cannot be visually represented in the stacking logic. */
            if(v_act <= 0) {
                LV_LOG_WARN("Stacked chart doesn't support negative values.");
                continue;
            }
            v_sum += v_act;
            if(s == ser) break;
        }

        int32_t temp_y = value_to_y(obj, ser, v_sum, h);
        p_out->y = h - temp_y;
    }
    /*LV_CHART_TYPE_NONE*/
    else {
        p_out->x = 0;
        p_out->y = 0;
    }

    int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
    p_out->x += lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
    p_out->x -= lv_obj_get_scroll_left(obj);

    uint32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
    id = ((int32_t)start_point + id) % chart->point_cnt;

    p_out->y += lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
    p_out->y -= lv_obj_get_scroll_top(obj);
}

void lv_chart_refresh(lv_obj_t * obj)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_obj_invalidate(obj);
}

/*======================
 * Series
 *=====================*/

lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis)
{
    LV_LOG_INFO("begin");

    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart    = (lv_chart_t *)obj;

    /* Allocate space for a new series and add it to the chart series linked list */
    lv_chart_series_t * ser = lv_ll_ins_tail(&chart->series_ll);
    LV_ASSERT_MALLOC(ser);
    if(ser == NULL) return NULL;
    lv_memzero(ser, sizeof(lv_chart_series_t));

    /* Allocate memory for point_cnt points, handle failure below */
    ser->y_points = lv_malloc(sizeof(int32_t) * chart->point_cnt);
    LV_ASSERT_MALLOC(ser->y_points);

    if(chart->type == LV_CHART_TYPE_SCATTER) {
        ser->x_points = lv_malloc(sizeof(int32_t) * chart->point_cnt);
        LV_ASSERT_MALLOC(ser->x_points);
        if(NULL == ser->x_points) {
            lv_free(ser->y_points);
            lv_ll_remove(&chart->series_ll, ser);
            lv_free(ser);
            return NULL;
        }
    }
    else {
        ser->x_points = NULL;
    }

    if(ser->y_points == NULL) {
        if(ser->x_points) {
            lv_free(ser->x_points);
            ser->x_points = NULL;
        }

        lv_ll_remove(&chart->series_ll, ser);
        lv_free(ser);
        return NULL;
    }

    /* Set series properties on successful allocation */
    ser->color = color;
    ser->start_point = 0;
    ser->y_ext_buf_assigned = false;
    ser->hidden = 0;
    ser->x_axis_sec = axis & LV_CHART_AXIS_SECONDARY_X ? 1 : 0;
    ser->y_axis_sec = axis & LV_CHART_AXIS_SECONDARY_Y ? 1 : 0;

    uint32_t i;
    const int32_t def = LV_CHART_POINT_NONE;
    int32_t * p_tmp = ser->y_points;
    for(i = 0; i < chart->point_cnt; i++) {
        *p_tmp = def;
        p_tmp++;
    }

    return ser;
}

void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(series);

    lv_chart_t * chart    = (lv_chart_t *)obj;
    if(!series->y_ext_buf_assigned && series->y_points) lv_free(series->y_points);
    if(!series->x_ext_buf_assigned && series->x_points) lv_free(series->x_points);

    lv_ll_remove(&chart->series_ll, series);
    lv_free(series);

    return;
}

void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide)
{
    LV_ASSERT_OBJ(chart, MY_CLASS);
    LV_ASSERT_NULL(series);

    series->hidden = hide ? 1 : 0;
    lv_chart_refresh(chart);
}

void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color)
{
    LV_ASSERT_OBJ(chart, MY_CLASS);
    LV_ASSERT_NULL(series);

    series->color = color;
    lv_chart_refresh(chart);
}

lv_color_t lv_chart_get_series_color(lv_obj_t * chart, const lv_chart_series_t * series)
{
    LV_ASSERT_OBJ(chart, MY_CLASS);
    LV_ASSERT_NULL(series);
    LV_UNUSED(chart);

    return series->color;
}

void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(id >= chart->point_cnt) return;
    ser->start_point = id;
}

lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * obj, const lv_chart_series_t * ser)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(ser == NULL) return lv_ll_get_head(&chart->series_ll);
    else return lv_ll_get_next(&chart->series_ll, ser);
}

/*=====================
 * Cursor
 *====================*/

lv_chart_cursor_t  * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    lv_chart_cursor_t * cursor = lv_ll_ins_head(&chart->cursor_ll);
    LV_ASSERT_MALLOC(cursor);
    if(cursor == NULL) return NULL;

    lv_point_set(&cursor->pos, LV_CHART_POINT_NONE, LV_CHART_POINT_NONE);
    cursor->point_id = LV_CHART_POINT_NONE;
    cursor->pos_set = 0;
    cursor->color = color;
    cursor->dir = dir;

    return cursor;
}

void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos)
{
    LV_ASSERT_NULL(cursor);
    LV_UNUSED(chart);

    cursor->pos = *pos;
    cursor->pos_set = 1;
    lv_chart_refresh(chart);
}

void lv_chart_set_cursor_pos_x(lv_obj_t * chart, lv_chart_cursor_t * cursor, int32_t x)
{
    LV_ASSERT_NULL(cursor);
    LV_UNUSED(chart);

    cursor->pos.x = x;
    cursor->pos_set = 1;
    lv_chart_refresh(chart);
}

void lv_chart_set_cursor_pos_y(lv_obj_t * chart, lv_chart_cursor_t * cursor, int32_t y)
{
    LV_ASSERT_NULL(cursor);
    LV_UNUSED(chart);

    cursor->pos.y = y;
    cursor->pos_set = 1;
    lv_chart_refresh(chart);
}

void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser, uint32_t point_id)
{
    LV_ASSERT_NULL(cursor);
    LV_UNUSED(chart);

    cursor->point_id = point_id;
    cursor->pos_set = 0;
    if(ser == NULL) ser = lv_chart_get_series_next(chart, NULL);
    cursor->ser = ser;
    lv_chart_refresh(chart);
}

lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor)
{
    LV_ASSERT_NULL(cursor);
    LV_UNUSED(chart);

    return cursor->pos;
}

/*=====================
 * Set/Get value(s)
 *====================*/

void lv_chart_set_all_values(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    uint32_t i;
    for(i = 0; i < chart->point_cnt; i++) {
        ser->y_points[i] = value;
    }
    ser->start_point = 0;
    lv_chart_refresh(obj);
}


void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);

    lv_chart_t * chart  = (lv_chart_t *)obj;

    ser->y_points[ser->start_point] = value;
    invalidate_point(obj, ser->start_point);
    ser->start_point = (ser->start_point + 1) % chart->point_cnt;
}

void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, int32_t x_value, int32_t y_value)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);

    lv_chart_t * chart  = (lv_chart_t *)obj;

    if(chart->type != LV_CHART_TYPE_SCATTER) {
        LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER");
        return;
    }

    ser->x_points[ser->start_point] = x_value;
    ser->y_points[ser->start_point] = y_value;
    ser->start_point = (ser->start_point + 1) % chart->point_cnt;
    invalidate_point(obj, ser->start_point);
}

void lv_chart_set_series_values(lv_obj_t * obj, lv_chart_series_t * ser, const int32_t values[], size_t values_cnt)
{
    size_t i;
    for(i = 0; i < values_cnt; i++) {
        lv_chart_set_next_value(obj, ser, values[i]);
    }
}

void lv_chart_set_series_values2(lv_obj_t * obj, lv_chart_series_t * ser, const int32_t x_values[],
                                 const int32_t y_values[], size_t values_cnt)
{
    size_t i;
    for(i = 0; i < values_cnt; i++) {
        lv_chart_set_next_value2(obj, ser, x_values[i], y_values[i]);
    }
}


void lv_chart_set_series_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t value)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);
    lv_chart_t * chart  = (lv_chart_t *)obj;

    if(id >= chart->point_cnt) return;
    ser->y_points[id] = value;
    invalidate_point(obj, id);
}

void lv_chart_set_series_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t x_value,
                                      int32_t y_value)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);
    lv_chart_t * chart  = (lv_chart_t *)obj;

    if(chart->type != LV_CHART_TYPE_SCATTER) {
        LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER");
        return;
    }

    if(id >= chart->point_cnt) return;
    ser->x_points[id] = x_value;
    ser->y_points[id] = y_value;
    invalidate_point(obj, id);
}

void lv_chart_set_series_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[])
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);

    if(!ser->y_ext_buf_assigned && ser->y_points) lv_free(ser->y_points);
    ser->y_ext_buf_assigned = true;
    ser->y_points = array;
    lv_obj_invalidate(obj);
}

void lv_chart_set_series_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[])
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);

    if(!ser->x_ext_buf_assigned && ser->x_points) lv_free(ser->x_points);
    ser->x_ext_buf_assigned = true;
    ser->x_points = array;
    lv_obj_invalidate(obj);
}

int32_t * lv_chart_get_series_y_array(const lv_obj_t * obj, lv_chart_series_t * ser)
{
    LV_UNUSED(obj);
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);
    return ser->y_points;
}

int32_t * lv_chart_get_series_x_array(const lv_obj_t * obj, lv_chart_series_t * ser)
{
    LV_UNUSED(obj);
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);
    return ser->x_points;
}

uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj)
{
    lv_chart_t * chart = (lv_chart_t *)obj;
    return chart->pressed_point_id;
}

int32_t lv_chart_get_first_point_center_offset(lv_obj_t * obj)
{
    lv_chart_t * chart = (lv_chart_t *)obj;

    int32_t x_ofs = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
    if(chart->type == LV_CHART_TYPE_BAR || chart->type == LV_CHART_TYPE_STACKED) {
        lv_obj_update_layout(obj);
        /*Gap between the columns on ~adjacent X*/
        int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
        int32_t w = lv_obj_get_content_width(obj);
        int32_t block_w = (w + block_gap) / (chart->point_cnt);

        x_ofs += (block_w - block_gap) / 2;
    }

    return x_ofs;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
    LV_UNUSED(class_p);
    LV_TRACE_OBJ_CREATE("begin");

    lv_chart_t * chart = (lv_chart_t *)obj;

    lv_ll_init(&chart->series_ll, sizeof(lv_chart_series_t));
    lv_ll_init(&chart->cursor_ll, sizeof(lv_chart_cursor_t));

    chart->ymin[0] = 0;
    chart->xmin[0] = 0;
    chart->ymin[1] = 0;
    chart->xmin[1] = 0;
    chart->ymax[0] = 100;
    chart->xmax[0] = 100;
    chart->ymax[1] = 100;
    chart->xmax[1] = 100;

    chart->hdiv_cnt    = LV_CHART_HDIV_DEF;
    chart->vdiv_cnt    = LV_CHART_VDIV_DEF;
    chart->point_cnt   = LV_CHART_POINT_CNT_DEF;
    chart->pressed_point_id  = LV_CHART_POINT_NONE;
    chart->type        = LV_CHART_TYPE_LINE;
    chart->update_mode = LV_CHART_UPDATE_MODE_SHIFT;

    LV_TRACE_OBJ_CREATE("finished");
}

static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
    LV_UNUSED(class_p);
    LV_TRACE_OBJ_CREATE("begin");

    lv_chart_t * chart = (lv_chart_t *)obj;
    lv_chart_series_t * ser;
    while(chart->series_ll.head) {
        ser = lv_ll_get_head(&chart->series_ll);
        if(!ser) continue;

        if(!ser->y_ext_buf_assigned) lv_free(ser->y_points);
        if(!ser->x_ext_buf_assigned) lv_free(ser->x_points);

        lv_ll_remove(&chart->series_ll, ser);
        lv_free(ser);
    }
    lv_ll_clear(&chart->series_ll);

    lv_chart_cursor_t * cur;
    while(chart->cursor_ll.head) {
        cur = lv_ll_get_head(&chart->cursor_ll);
        lv_ll_remove(&chart->cursor_ll, cur);
        lv_free(cur);
    }
    lv_ll_clear(&chart->cursor_ll);

    LV_TRACE_OBJ_CREATE("finished");
}

static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e)
{
    LV_UNUSED(class_p);

    /*Call the ancestor's event handler*/
    lv_result_t res;

    res = lv_obj_event_base(MY_CLASS, e);
    if(res != LV_RESULT_OK) return;

    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * obj = lv_event_get_current_target(e);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(code == LV_EVENT_PRESSED) {
        lv_indev_t * indev = lv_indev_active();
        lv_point_t p;
        lv_indev_get_point(indev, &p);

        p.x -= obj->coords.x1;
        uint32_t id = get_index_from_x(obj, p.x + lv_obj_get_scroll_left(obj));
        if(id != (uint32_t)chart->pressed_point_id) {
            invalidate_point(obj, id);
            invalidate_point(obj, chart->pressed_point_id);
            chart->pressed_point_id = id;
            lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL);
        }
    }
    else if(code == LV_EVENT_RELEASED) {
        invalidate_point(obj, chart->pressed_point_id);
        chart->pressed_point_id = LV_CHART_POINT_NONE;
    }
    else if(code == LV_EVENT_DRAW_MAIN) {
        lv_layer_t * layer = lv_event_get_layer(e);

        lv_area_t ext_coords;
        lv_obj_get_coords(obj, &ext_coords);
        int32_t ext_draw_size = lv_obj_get_ext_draw_size(obj);
        lv_area_increase(&ext_coords, ext_draw_size, ext_draw_size);

        lv_area_t clip_area;
        if(lv_area_intersect(&clip_area, &ext_coords, &layer->_clip_area)) {
            const lv_area_t clip_area_ori = layer->_clip_area;
            layer->_clip_area = clip_area;

            draw_div_lines(obj, layer);

            if(lv_ll_is_empty(&chart->series_ll) == false) {
                if(chart->type == LV_CHART_TYPE_LINE) draw_series_line(obj, layer);
                else if(chart->type == LV_CHART_TYPE_BAR) draw_series_bar(obj, layer);
                else if(chart->type == LV_CHART_TYPE_STACKED) draw_series_stacked(obj, layer);
                else if(chart->type == LV_CHART_TYPE_SCATTER) draw_series_scatter(obj, layer);
            }

            draw_cursors(obj, layer);

            layer->_clip_area = clip_area_ori;
        }
    }
}

static void draw_div_lines(lv_obj_t * obj, lv_layer_t * layer)
{
    lv_chart_t * chart  = (lv_chart_t *)obj;

    int16_t i;
    int16_t i_start;
    int16_t i_end;
    int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
    int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
    int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
    int32_t w = lv_obj_get_content_width(obj);
    int32_t h = lv_obj_get_content_height(obj);

    lv_draw_line_dsc_t line_dsc;
    lv_draw_line_dsc_init(&line_dsc);
    line_dsc.base.layer = layer;
    lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc);

    lv_opa_t border_opa = lv_obj_get_style_border_opa(obj, LV_PART_MAIN);
    int32_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
    lv_border_side_t border_side = lv_obj_get_style_border_side(obj, LV_PART_MAIN);

    int32_t scroll_left = lv_obj_get_scroll_left(obj);
    int32_t scroll_top = lv_obj_get_scroll_top(obj);
    if(chart->hdiv_cnt != 0) {
        int32_t y_ofs = obj->coords.y1 + pad_top - scroll_top;
        line_dsc.p1.x = obj->coords.x1;
        line_dsc.p2.x = obj->coords.x2;

        i_start = 0;
        i_end = chart->hdiv_cnt;
        if(border_opa > LV_OPA_MIN && border_w > 0) {
            if((border_side & LV_BORDER_SIDE_TOP) && (lv_obj_get_style_pad_top(obj, LV_PART_MAIN) == 0)) i_start++;
            if((border_side & LV_BORDER_SIDE_BOTTOM) && (lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN) == 0)) i_end--;
        }

        for(i = i_start; i < i_end; i++) {
            line_dsc.p1.y = (int32_t)((int32_t)h * i) / (chart->hdiv_cnt - 1);
            line_dsc.p1.y += y_ofs;
            line_dsc.p2.y = line_dsc.p1.y;
            line_dsc.base.id1 = i;

            lv_draw_line(layer, &line_dsc);
        }
    }

    if(chart->vdiv_cnt != 0) {
        int32_t x_ofs = obj->coords.x1 + pad_left - scroll_left;
        line_dsc.p1.y = obj->coords.y1;
        line_dsc.p2.y = obj->coords.y2;
        i_start = 0;
        i_end = chart->vdiv_cnt;
        if(border_opa > LV_OPA_MIN && border_w > 0) {
            if((border_side & LV_BORDER_SIDE_LEFT) && (lv_obj_get_style_pad_left(obj, LV_PART_MAIN) == 0)) i_start++;
            if((border_side & LV_BORDER_SIDE_RIGHT) && (lv_obj_get_style_pad_right(obj, LV_PART_MAIN) == 0)) i_end--;
        }

        for(i = i_start; i < i_end; i++) {
            line_dsc.p1.x = (int32_t)((int32_t)w * i) / (chart->vdiv_cnt - 1);
            line_dsc.p1.x += x_ofs;
            line_dsc.p2.x = line_dsc.p1.x;
            line_dsc.base.id1 = i;

            lv_draw_line(layer, &line_dsc);
        }
    }
}

static void draw_series_line(lv_obj_t * obj, lv_layer_t * layer)
{
    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(chart->point_cnt < 2) return;

    uint32_t i;
    int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
    int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
    int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
    int32_t w     = lv_obj_get_content_width(obj);
    int32_t h     = lv_obj_get_content_height(obj);
    int32_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj);
    int32_t y_ofs = obj->coords.y1 + pad_top - lv_obj_get_scroll_top(obj);
    lv_chart_series_t * ser;

    lv_draw_line_dsc_t line_dsc;
    lv_draw_line_dsc_init(&line_dsc);
    line_dsc.base.layer = layer;
    lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc);

    lv_draw_rect_dsc_t point_dsc_default;
    lv_draw_rect_dsc_init(&point_dsc_default);
    point_dsc_default.base.layer = layer;
    lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default);

    int32_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2;
    int32_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2;

    /*If there are at least as many points as pixels then draw only vertical lines*/
    bool crowded_mode = (int32_t)chart->point_cnt >= w;

    uint32_t ser_cnt = lv_ll_get_len(&chart->series_ll);
    if(ser_cnt == 0) {
        return;
    }

    line_dsc.base.id1 = ser_cnt - 1;
    point_dsc_default.base.id1 = line_dsc.base.id1;
    /*Go through all data lines*/
    LV_LL_READ_BACK(&chart->series_ll, ser) {
        if(ser->hidden) {
            if(line_dsc.base.id1 > 0) {
                line_dsc.base.id1--;
                point_dsc_default.base.id1--;
            }
            continue;
        }
        line_dsc.color = ser->color;
        point_dsc_default.bg_color = ser->color;
        line_dsc.base.id2 = 0;
        point_dsc_default.base.id2 = 0;

        int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;

        line_dsc.p1.x = x_ofs;
        line_dsc.p2.x = x_ofs;

        int32_t p_act = start_point;
        int32_t p_prev = start_point;
        int32_t y_tmp = (int32_t)((int32_t)ser->y_points[p_prev] - chart->ymin[ser->y_axis_sec]) * h;
        y_tmp  = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
        line_dsc.p2.y   = h - y_tmp + y_ofs;

        lv_value_precise_t y_min = line_dsc.p2.y;
        lv_value_precise_t y_max = line_dsc.p2.y;

        for(i = 0; i < chart->point_cnt; i++) {
            line_dsc.p1.x = line_dsc.p2.x;
            line_dsc.p1.y = line_dsc.p2.y;

            if(line_dsc.p1.x > layer->_clip_area.x2 + point_w + 1) break;
            line_dsc.p2.x = (lv_value_precise_t)((w * i) / (chart->point_cnt - 1)) + x_ofs;

            p_act = (start_point + i) % chart->point_cnt;

            y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
            y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
            line_dsc.p2.y  = h - y_tmp + y_ofs;

            if(line_dsc.p2.x < layer->_clip_area.x1 - point_w - 1) {
                p_prev = p_act;
                continue;
            }

            /*Don't draw the first point. A second point is also required to draw the line*/
            if(i != 0) {
                if(crowded_mode) {
                    if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
                        /*Draw only one vertical line between the min and max y-values on the same x-value*/
                        y_max = LV_MAX(y_max, line_dsc.p2.y);
                        y_min = LV_MIN(y_min, line_dsc.p2.y);
                        if(line_dsc.p1.x != line_dsc.p2.x) {
                            lv_value_precise_t y_cur = line_dsc.p2.y;
                            line_dsc.p2.x--;         /*It's already on the next x value*/
                            line_dsc.p1.x = line_dsc.p2.x;
                            line_dsc.p1.y = y_min;
                            line_dsc.p2.y = y_max;
                            if(line_dsc.p1.y == line_dsc.p2.y) line_dsc.p2.y++;    /*If they are the same no line will be drawn*/
                            lv_draw_line(layer, &line_dsc);
                            line_dsc.p2.x++;         /*Compensate the previous x--*/
                            y_min = y_cur;  /*Start the line of the next x from the current last y*/
                            y_max = y_cur;
                        }
                    }
                }
                else {
                    lv_area_t point_area;
                    point_area.x1 = (int32_t)line_dsc.p1.x - point_w;
                    point_area.x2 = (int32_t)line_dsc.p1.x + point_w;
                    point_area.y1 = (int32_t)line_dsc.p1.y - point_h;
                    point_area.y2 = (int32_t)line_dsc.p1.y + point_h;

                    if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
                        line_dsc.base.id2 = i;
                        lv_draw_line(layer, &line_dsc);
                    }

                    if(point_w && point_h && ser->y_points[p_prev] != LV_CHART_POINT_NONE) {
                        point_dsc_default.base.id2 = i - 1;
                        lv_draw_rect(layer, &point_dsc_default, &point_area);
                    }
                }

            }
            p_prev = p_act;
        }

        /*Draw the last point*/
        if(!crowded_mode && i == chart->point_cnt) {

            if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
                lv_area_t point_area;
                point_area.x1 = (int32_t)line_dsc.p2.x - point_w;
                point_area.x2 = (int32_t)line_dsc.p2.x + point_w;
                point_area.y1 = (int32_t)line_dsc.p2.y - point_h;
                point_area.y2 = (int32_t)line_dsc.p2.y + point_h;
                point_dsc_default.base.id2 = i - 1;
                lv_draw_rect(layer, &point_dsc_default, &point_area);
            }
        }

        if(line_dsc.base.id1 > 0) {
            point_dsc_default.base.id1--;
            line_dsc.base.id1--;
        }
    }
}

static void draw_series_scatter(lv_obj_t * obj, lv_layer_t * layer)
{
    lv_chart_t * chart  = (lv_chart_t *)obj;

    uint32_t i;
    int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
    int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
    int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
    int32_t w     = lv_obj_get_content_width(obj);
    int32_t h     = lv_obj_get_content_height(obj);
    int32_t x_ofs = obj->coords.x1 + pad_left + border_width - lv_obj_get_scroll_left(obj);
    int32_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj);
    lv_chart_series_t * ser;

    lv_draw_line_dsc_t line_dsc;
    lv_draw_line_dsc_init(&line_dsc);
    line_dsc.base.layer = layer;
    lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc);

    lv_draw_rect_dsc_t point_dsc_default;
    lv_draw_rect_dsc_init(&point_dsc_default);
    point_dsc_default.base.layer = layer;
    lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default);

    int32_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2;
    int32_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2;

    /*Do not bother with line ending is the point will over it*/
    if(LV_MIN(point_w, point_h) > line_dsc.width / 2) line_dsc.raw_end = 1;
    if(line_dsc.width == 1) line_dsc.raw_end = 1;

    /*Go through all data lines*/
    LV_LL_READ_BACK(&chart->series_ll, ser) {
        if(ser->hidden) continue;
        line_dsc.color = ser->color;
        point_dsc_default.bg_color = ser->color;

        int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;

        line_dsc.p1.x = x_ofs;
        line_dsc.p2.x = x_ofs;

        int32_t p_act = start_point;
        int32_t p_prev = start_point;
        if(ser->y_points[p_act] != LV_CHART_POINT_CNT_DEF) {
            line_dsc.p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
            line_dsc.p2.x += x_ofs;

            line_dsc.p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
            line_dsc.p2.y = h - line_dsc.p2.y;
            line_dsc.p2.y += y_ofs;
        }
        else {
            line_dsc.p2.x = (lv_value_precise_t)LV_COORD_MIN;
            line_dsc.p2.y = (lv_value_precise_t)LV_COORD_MIN;
        }

        for(i = 0; i < chart->point_cnt; i++) {
            line_dsc.p1.x = line_dsc.p2.x;
            line_dsc.p1.y = line_dsc.p2.y;

            p_act = (start_point + i) % chart->point_cnt;
            if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
                line_dsc.p2.y =  lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
                line_dsc.p2.y = h - line_dsc.p2.y;
                line_dsc.p2.y += y_ofs;

                line_dsc.p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
                line_dsc.p2.x += x_ofs;
            }
            else {
                p_prev = p_act;
                continue;
            }

            if(i != 0) { /*Don't draw line *to* the first point.*/
                lv_area_t point_area;
                point_area.x1 = (int32_t)line_dsc.p1.x - point_w;
                point_area.x2 = (int32_t)line_dsc.p1.x + point_w;
                point_area.y1 = (int32_t)line_dsc.p1.y - point_h;
                point_area.y2 = (int32_t)line_dsc.p1.y + point_h;

                if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
                    line_dsc.base.id2 = i - 1;
                    lv_draw_line(layer, &line_dsc);
                    if(point_w && point_h) {
                        point_dsc_default.base.id2 = i - 1;
                        lv_draw_rect(layer, &point_dsc_default, &point_area);
                    }
                }

                p_prev = p_act;
            }

            /*Draw the last point*/
            if(i == chart->point_cnt - 1) {

                if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
                    lv_area_t point_area;
                    point_area.x1 = (int32_t)line_dsc.p2.x - point_w;
                    point_area.x2 = (int32_t)line_dsc.p2.x + point_w;
                    point_area.y1 = (int32_t)line_dsc.p2.y - point_h;
                    point_area.y2 = (int32_t)line_dsc.p2.y + point_h;

                    point_dsc_default.base.id2 = i;
                    lv_draw_rect(layer, &point_dsc_default, &point_area);
                }
            }
        }
        line_dsc.base.id1++;
        point_dsc_default.base.id1++;
    }
}

static void draw_series_bar(lv_obj_t * obj, lv_layer_t * layer)
{
    lv_chart_t * chart  = (lv_chart_t *)obj;

    uint32_t i;
    lv_area_t col_a;
    int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
    int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
    int32_t w = lv_obj_get_content_width(obj);
    int32_t h  = lv_obj_get_content_height(obj);
    int32_t y_tmp;
    lv_chart_series_t * ser;
    uint32_t ser_cnt = lv_ll_get_len(&chart->series_ll);
    if(ser_cnt == 0) {
        return;
    }
    int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);  /*Gap between the column on ~adjacent X*/
    int32_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;
    int32_t ser_gap = lv_obj_get_style_pad_column(obj, LV_PART_ITEMS); /*Gap between the columns on the ~same X*/
    int32_t col_w = (block_w - (ser_cnt - 1) * ser_gap) / ser_cnt;
    if(col_w < 1) col_w  = 1;

    int32_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
    int32_t x_ofs = pad_left - lv_obj_get_scroll_left(obj) + border_w;
    int32_t y_ofs = pad_top - lv_obj_get_scroll_top(obj) + border_w;

    lv_draw_rect_dsc_t col_dsc;
    lv_draw_rect_dsc_init(&col_dsc);
    col_dsc.base.layer = layer;
    lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc);
    col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE;
    col_dsc.bg_opa = LV_OPA_COVER;

    /*Make the cols longer with `radius` to clip the rounding from the bottom*/
    col_a.y2 = obj->coords.y2 + col_dsc.radius;

    /*Go through all points*/
    for(i = 0; i < chart->point_cnt; i++) {
        int32_t x_act;
        if(chart->point_cnt <= 1) {
            x_act = obj->coords.x1 + x_ofs;
        }
        else {
            x_act = (int32_t)((int32_t)(w - block_w) * i) / (chart->point_cnt - 1) + obj->coords.x1 + x_ofs;
        }

        col_dsc.base.id2 = i;
        col_dsc.base.id1 = 0;

        /*Draw the current point of all data line*/
        LV_LL_READ(&chart->series_ll, ser) {
            if(ser->hidden) continue;

            int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;

            col_a.x1 = x_act;
            col_a.x2 = col_a.x1 + col_w - 1;
            x_act += col_w + ser_gap;

            if(col_a.x2 < layer->_clip_area.x1) {
                col_dsc.base.id1++;
                continue;
            }
            if(col_a.x1 > layer->_clip_area.x2) break;

            col_dsc.bg_color = ser->color;

            int32_t p_act = (start_point + i) % chart->point_cnt;
            y_tmp            = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
            y_tmp            = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
            col_a.y1         = h - y_tmp + obj->coords.y1 + y_ofs;

            if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
                lv_draw_rect(layer, &col_dsc, &col_a);
            }
            col_dsc.base.id1++;
        }
    }
}

static void draw_series_stacked(lv_obj_t * obj, lv_layer_t * layer)
{
    lv_chart_t * chart  = (lv_chart_t *)obj;

    uint32_t i;
    int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
    int32_t pad_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN);
    int32_t w = lv_obj_get_content_width(obj);
    int32_t h  = lv_obj_get_content_height(obj);
    lv_chart_series_t * ser;
    uint32_t ser_cnt = lv_ll_get_len(&chart->series_ll);
    if(ser_cnt == 0) {
        return;
    }

    int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);  /*Gap between the columns on adjacent X ticks*/
    int32_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;

    int32_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
    int32_t x_ofs = pad_left - lv_obj_get_scroll_left(obj) + border_w;
    int32_t y_ofs = pad_bottom - lv_obj_get_scroll_top(obj) + border_w;

    lv_draw_rect_dsc_t col_dsc;
    lv_draw_rect_dsc_init(&col_dsc);
    col_dsc.base.layer = layer;
    lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc);
    col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE;
    col_dsc.bg_opa = LV_OPA_COVER;

    lv_area_t clip_area_ori = layer->_clip_area;

    /*Go through all points*/
    lv_area_t bar_full_area;
    for(i = 0; i < chart->point_cnt; i++) {
        col_dsc.base.id2 = i;
        col_dsc.base.id1 = 0;

        /*Get the total bar height. All segments (series) will be drawn in the height*/
        int32_t v_sum_all = 0;
        LV_LL_READ(&chart->series_ll, ser) {
            if(ser->hidden) continue;
            int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
            int32_t p_act = (start_point + i) % chart->point_cnt;
            int32_t v_act = ser->y_points[p_act];
            if(ser->y_points[p_act] == LV_CHART_POINT_NONE) continue;

            /* Skip negative values in stacked charts. Negative values are not supported
             * in stacked charts as they cannot be visually represented in the stacking logic. */
            if(v_act <= 0) {
                LV_LOG_WARN("Stacked chart doesn't support negative values.");
                continue;
            }
            v_sum_all += v_act;
        }

        int32_t total_bar_height = value_to_y(obj, lv_ll_get_head(&chart->series_ll), v_sum_all, h);
        if(total_bar_height <= 0) continue;

        if(chart->point_cnt <= 1) {
            bar_full_area.x1 = obj->coords.x1 + x_ofs;
        }
        else {
            bar_full_area.x1 = (int32_t)((int32_t)(w - block_w) * i) / (chart->point_cnt - 1) + obj->coords.x1 + x_ofs;
        }
        bar_full_area.x2 = bar_full_area.x1 + block_w - 1;

        /*No in the clip area yet*/
        if(bar_full_area.x2 < clip_area_ori.x1) continue;

        /*Out of the clip area already*/
        if(bar_full_area.x1 > clip_area_ori.x2) break;

        /*Draw the full_bar_area and set the clip area to clip the segments*/
        bar_full_area.y2 = obj->coords.y2 + col_dsc.radius;
        bar_full_area.y1 = obj->coords.y2 - y_ofs - total_bar_height + 1;

        lv_area_t bar_clip_area = bar_full_area;
        int32_t y_prev = obj->coords.y2 + 1;

        /*Draw the current point of all data line*/
        int32_t v_sum_act = 0;
        LV_LL_READ(&chart->series_ll, ser) {
            int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;

            col_dsc.bg_color = ser->color;

            int32_t p_act = (start_point + i) % chart->point_cnt;
            int32_t v_act = ser->y_points[p_act];
            /*Can't show negative values on a stacked chart*/
            if(ser->y_points[p_act] == LV_CHART_POINT_NONE ||
               v_act <= 0 ||
               ser->hidden) {
                col_dsc.base.id1++;
                continue;
            }

            v_sum_act += v_act; /*Use the summed value to get its `y` to avoid rounding errors*/

            int32_t segment_y = value_to_y(obj, ser, v_sum_act, h);
            bar_clip_area.y2 = y_prev - 1;
            bar_clip_area.y1 = obj->coords.y2 - y_ofs - segment_y + 1;
            y_prev = bar_clip_area.y1;
            if(lv_area_intersect(&layer->_clip_area, &clip_area_ori, &bar_clip_area)) {
                lv_draw_rect(layer, &col_dsc, &bar_full_area);
            }
            col_dsc.base.id1++;
        }

    }
    layer->_clip_area = clip_area_ori;
}

static void draw_cursors(lv_obj_t * obj, lv_layer_t * layer)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(lv_ll_is_empty(&chart->cursor_ll)) return;

    lv_chart_cursor_t * cursor;

    lv_draw_line_dsc_t line_dsc_ori;
    lv_draw_line_dsc_init(&line_dsc_ori);
    line_dsc_ori.base.layer = layer;
    lv_obj_init_draw_line_dsc(obj, LV_PART_CURSOR, &line_dsc_ori);

    lv_draw_rect_dsc_t point_dsc_ori;
    lv_draw_rect_dsc_init(&point_dsc_ori);
    point_dsc_ori.base.layer = layer;
    lv_obj_init_draw_rect_dsc(obj, LV_PART_CURSOR, &point_dsc_ori);

    lv_draw_line_dsc_t line_dsc;
    lv_draw_rect_dsc_t point_dsc_tmp;

    int32_t point_w = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
    int32_t point_h = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;

    /*Go through all cursor lines*/
    LV_LL_READ_BACK(&chart->cursor_ll, cursor) {
        lv_memcpy(&line_dsc, &line_dsc_ori, sizeof(lv_draw_line_dsc_t));
        lv_memcpy(&point_dsc_tmp, &point_dsc_ori, sizeof(lv_draw_rect_dsc_t));
        line_dsc.color = cursor->color;
        point_dsc_tmp.bg_color = cursor->color;

        int32_t cx;
        int32_t cy;
        if(cursor->pos_set) {
            cx = cursor->pos.x;
            cy = cursor->pos.y;
        }
        else {
            if(cursor->point_id == LV_CHART_POINT_NONE) continue;
            lv_point_t p;
            lv_chart_get_point_pos_by_id(obj, cursor->ser, cursor->point_id, &p);
            cx = p.x;
            cy = p.y;
        }

        cx += obj->coords.x1;
        cy += obj->coords.y1;

        lv_area_t point_area;
        bool draw_point = point_w && point_h;
        point_area.x1 = cx - point_w;
        point_area.x2 = cx + point_w;
        point_area.y1 = cy - point_h;
        point_area.y2 = cy + point_h;

        if(cursor->dir & LV_DIR_HOR) {
            line_dsc.p1.x = cursor->dir & LV_DIR_LEFT ? obj->coords.x1 : cx;
            line_dsc.p1.y = cy;
            line_dsc.p2.x = cursor->dir & LV_DIR_RIGHT ? obj->coords.x2 : cx;
            line_dsc.p2.y = line_dsc.p1.y;

            line_dsc.base.id2 = 0;
            point_dsc_tmp.base.id2 = 0;

            lv_draw_line(layer, &line_dsc);

            if(draw_point) {
                lv_draw_rect(layer, &point_dsc_tmp, &point_area);
            }
        }

        if(cursor->dir & LV_DIR_VER) {
            line_dsc.p1.x = cx;
            line_dsc.p1.y = cursor->dir & LV_DIR_TOP ? obj->coords.y1 : cy;
            line_dsc.p2.x = line_dsc.p1.x;
            line_dsc.p2.y = cursor->dir & LV_DIR_BOTTOM ? obj->coords.y2 : cy;

            line_dsc.base.id2 = 1;
            point_dsc_tmp.base.id2 = 1;

            lv_draw_line(layer, &line_dsc);

            if(draw_point) {
                lv_draw_rect(layer, &point_dsc_tmp, &point_area);
            }
        }
        line_dsc_ori.base.id1++;
        point_dsc_ori.base.id1++;
    }
}

/**
 * Get the nearest index to an X coordinate
 * @param chart pointer to a chart object
 * @param coord the coordination of the point relative to the series area.
 * @return the found index
 */
static uint32_t get_index_from_x(lv_obj_t * obj, int32_t x)
{
    lv_chart_t * chart  = (lv_chart_t *)obj;
    int32_t w = lv_obj_get_content_width(obj);
    int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
    x -= pad_left;

    if(x < 0) return 0;
    if(x > w) return chart->point_cnt - 1;
    if(chart->type == LV_CHART_TYPE_LINE) return (x * (chart->point_cnt - 1) + w / 2) / w;
    if(chart->type == LV_CHART_TYPE_BAR || chart->type == LV_CHART_TYPE_STACKED) return (x * chart->point_cnt) / w;
    if(chart->type == LV_CHART_TYPE_SCATTER) {
        /*For scatter charts, the nearest id could be different depending on the series. Just check the first series.*/
        lv_chart_series_t * ser = lv_chart_get_series_next(obj, NULL);
        if(ser) {
            int32_t best_dist = INT32_MAX;
            uint32_t best_index = 0;
            for(uint32_t i = 0; i < chart->point_cnt; i++) {
                int32_t dist = LV_ABS(x - lv_map(ser->x_points[i], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w));
                if(dist < best_dist) {
                    best_dist = dist;
                    best_index = i;
                }
            }
            return best_index;
        }
    }

    return 0;
}

static void invalidate_point(lv_obj_t * obj, uint32_t i)
{
    lv_chart_t * chart  = (lv_chart_t *)obj;
    if(i >= chart->point_cnt) return;


    /*In shift mode the whole chart changes so the whole object*/
    if(chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT) {
        lv_obj_invalidate(obj);
        return;
    }
    int32_t w  = lv_obj_get_content_width(obj);
    int32_t scroll_left = lv_obj_get_scroll_left(obj);
    int32_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
    int32_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
    int32_t x_ofs = obj->coords.x1 + pleft + bwidth - scroll_left;

    if(chart->type == LV_CHART_TYPE_LINE) {
        int32_t line_width = lv_obj_get_style_line_width(obj, LV_PART_ITEMS);
        int32_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR);

        lv_area_t coords;
        lv_area_copy(&coords, &obj->coords);
        coords.y1 -= line_width + point_w;
        coords.y2 += line_width + point_w;

        /*Invalidate the area between the previous and the next points*/
        if(i < chart->point_cnt - 1) {
            coords.x1 = ((w * i) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
            coords.x2 = ((w * (i + 1)) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
            lv_obj_invalidate_area(obj, &coords);
        }

        if(i > 0) {
            coords.x1 = ((w * (i - 1)) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
            coords.x2 = ((w * i) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
            lv_obj_invalidate_area(obj, &coords);
        }
    }
    else if(chart->type == LV_CHART_TYPE_BAR || chart->type == LV_CHART_TYPE_STACKED) {
        lv_area_t col_a;
        /*Gap between the column on ~adjacent X*/
        int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
        int32_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;

        int32_t x_act;
        if(chart->point_cnt > 1) {
            x_act = (int32_t)((int32_t)(w - block_w) * i) / (chart->point_cnt - 1);
        }
        else {
            x_act = 0;
        }

        lv_obj_get_coords(obj, &col_a);
        col_a.x1 = x_act + x_ofs;
        col_a.x2 = col_a.x1 + block_w + block_gap;
        col_a.x1 -= block_gap;

        lv_obj_invalidate_area(obj, &col_a);
    }
    else {
        lv_obj_invalidate(obj);
    }
}

static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, int32_t ** a)
{
    if((*a) == NULL) return;

    lv_chart_t * chart = (lv_chart_t *) obj;
    uint32_t point_cnt_old = chart->point_cnt;
    uint32_t i;

    if(ser->start_point != 0) {
        int32_t * new_points = lv_malloc(sizeof(int32_t) * cnt);
        LV_ASSERT_MALLOC(new_points);
        if(new_points == NULL) return;

        if(cnt >= point_cnt_old) {
            for(i = 0; i < point_cnt_old; i++) {
                new_points[i] =
                    (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
            }
            for(i = point_cnt_old; i < cnt; i++) {
                new_points[i] = LV_CHART_POINT_NONE; /*Fill up the rest with default value*/
            }
        }
        else {
            for(i = 0; i < cnt; i++) {
                new_points[i] =
                    (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
            }
        }

        /*Switch over pointer from old to new*/
        lv_free((*a));
        (*a) = new_points;
    }
    else {
        (*a) = lv_realloc((*a), sizeof(int32_t) * cnt);
        LV_ASSERT_MALLOC((*a));
        if((*a) == NULL) return;
        /*Initialize the new points*/
        if(cnt > point_cnt_old) {
            for(i = point_cnt_old - 1; i < cnt; i++) {
                (*a)[i] = LV_CHART_POINT_NONE;
            }
        }
    }
}

/**
 * Map a value to a height
 * @param obj   pointer to a chart
 * @param ser   pointer to the series
 * @param v     the value to map
 * @param h     the height to which the value needs to be mapped
 * @return      the mapped y-coordinate value corresponding to the input value
 */
static int32_t value_to_y(lv_obj_t * obj, lv_chart_series_t * ser, int32_t v, int32_t h)
{
    lv_chart_t * chart = (lv_chart_t *) obj;

    return lv_map(v, chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
}

#endif
