summaryrefslogtreecommitdiff
path: root/lib/lvgl/src/widgets/chart
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-06-12 17:54:40 +1000
committerjacqueline <me@jacqueline.id.au>2024-06-12 17:54:40 +1000
commit64bd9053a25297f7a442ca831c7da5b44bd33f84 (patch)
treea90c6cad25a12028302ab1a5334510fba0229bae /lib/lvgl/src/widgets/chart
parent611176ed667c4ed7ee9f609e958f9404f4aee91d (diff)
downloadtangara-fw-64bd9053a25297f7a442ca831c7da5b44bd33f84.tar.gz
Update LVGL to v9.1.0
Diffstat (limited to 'lib/lvgl/src/widgets/chart')
-rw-r--r--lib/lvgl/src/widgets/chart/lv_chart.c1341
-rw-r--r--lib/lvgl/src/widgets/chart/lv_chart.h408
2 files changed, 1749 insertions, 0 deletions
diff --git a/lib/lvgl/src/widgets/chart/lv_chart.c b/lib/lvgl/src/widgets/chart/lv_chart.c
new file mode 100644
index 00000000..782b7c5a
--- /dev/null
+++ b/lib/lvgl/src/widgets/chart/lv_chart.c
@@ -0,0 +1,1341 @@
+/**
+ * @file lv_chart.c
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "lv_chart.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_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 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 = "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(lv_point_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_range(lv_obj_t * obj, lv_chart_axis_t axis, int32_t min, int32_t max)
+{
+ LV_ASSERT_OBJ(obj, MY_CLASS);
+
+ max = max == min ? max + 1 : max;
+
+ lv_chart_t * chart = (lv_chart_t *)obj;
+ switch(axis) {
+ case LV_CHART_AXIS_PRIMARY_Y:
+ chart->ymin[0] = min;
+ chart->ymax[0] = max;
+ break;
+ case LV_CHART_AXIS_SECONDARY_Y:
+ chart->ymin[1] = min;
+ chart->ymax[1] = max;
+ break;
+ case LV_CHART_AXIS_PRIMARY_X:
+ chart->xmin[0] = min;
+ chart->xmax[0] = max;
+ break;
+ case LV_CHART_AXIS_SECONDARY_X:
+ chart->xmin[1] = min;
+ chart->xmax[1] = max;
+ break;
+ default:
+ LV_LOG_WARN("Invalid axis: %d", axis);
+ return;
+ }
+
+ lv_chart_refresh(obj);
+}
+
+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, uint8_t hdiv, uint8_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);
+}
+
+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) {
+ p_out->x = (w * id) / (chart->point_cnt - 1);
+ }
+ 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);
+ }
+ 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;
+
+ p_out->x = (int32_t)((int32_t)(w - block_w) * id) / (chart->point_cnt - 1);
+ lv_chart_series_t * ser_i = NULL;
+ uint32_t ser_idx = 0;
+ _LV_LL_READ_BACK(&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;
+ 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;
+ }
+
+ 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;
+ int32_t temp_y = 0;
+ temp_y = (int32_t)((int32_t)ser->y_points[id] - chart->ymin[ser->y_axis_sec]) * h;
+ temp_y = temp_y / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
+ p_out->y = h - temp_y;
+ 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;
+
+ /* 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) {
+ _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);
+}
+
+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_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_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;
+ 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;
+ invalidate_point(obj, ser->start_point);
+}
+
+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_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_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_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_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_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_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) {
+ 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);
+ 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_SCATTER) draw_series_scatter(obj, layer);
+ }
+
+ draw_cursors(obj, layer);
+ }
+}
+
+static void draw_div_lines(lv_obj_t * obj, lv_layer_t * layer)
+{
+ lv_chart_t * chart = (lv_chart_t *)obj;
+
+ lv_area_t series_clip_area;
+ bool mask_ret = _lv_area_intersect(&series_clip_area, &obj->coords, &layer->_clip_area);
+ if(mask_ret == false) return;
+
+ const lv_area_t clip_area_ori = layer->_clip_area;
+ layer->_clip_area = series_clip_area;
+
+ 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);
+ 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);
+ }
+ }
+
+ layer->_clip_area = clip_area_ori;
+}
+
+static void draw_series_line(lv_obj_t * obj, lv_layer_t * layer)
+{
+ lv_area_t clip_area;
+ if(_lv_area_intersect(&clip_area, &obj->coords, &layer->_clip_area) == false) return;
+
+ const lv_area_t clip_area_ori = layer->_clip_area;
+ layer->_clip_area = clip_area;
+
+ 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_area_t series_clip_area;
+ bool mask_ret = _lv_area_intersect(&series_clip_area, &obj->coords, &layer->_clip_area);
+ if(mask_ret == false) return;
+
+ lv_draw_line_dsc_t line_dsc;
+ lv_draw_line_dsc_init(&line_dsc);
+ 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);
+ 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;
+
+ /*If there are at least as many points as pixels then draw only vertical lines*/
+ bool crowded_mode = (int32_t)chart->point_cnt >= w;
+
+ line_dsc.base.id1 = _lv_ll_get_len(&chart->series_ll) - 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) {
+ 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 > clip_area_ori.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 < clip_area_ori.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);
+ }
+ }
+
+ point_dsc_default.base.id1--;
+ line_dsc.base.id1--;
+ }
+
+ layer->_clip_area = clip_area_ori;
+}
+
+static void draw_series_scatter(lv_obj_t * obj, lv_layer_t * layer)
+{
+
+ lv_area_t clip_area;
+ if(_lv_area_intersect(&clip_area, &obj->coords, &layer->_clip_area) == false) return;
+
+ const lv_area_t clip_area_ori = layer->_clip_area;
+ layer->_clip_area = clip_area;
+
+ 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);
+ 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);
+ 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;
+ }
+
+ /*Don't draw the first point. A second point is also required to draw the line*/
+ if(i != 0) {
+ 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) {
+ point_dsc_default.base.id2 = i;
+ lv_draw_rect(layer, &point_dsc_default, &point_area);
+ }
+ }
+
+ p_prev = p_act;
+ }
+
+ /*Draw the last point*/
+ if(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;
+ lv_draw_rect(layer, &point_dsc_default, &point_area);
+ }
+ }
+ }
+ line_dsc.base.id1++;
+ point_dsc_default.base.id1++;
+ layer->_clip_area = clip_area_ori;
+ }
+}
+
+static void draw_series_bar(lv_obj_t * obj, lv_layer_t * layer)
+{
+ lv_area_t clip_area;
+ if(_lv_area_intersect(&clip_area, &obj->coords, &layer->_clip_area) == false) return;
+
+ const lv_area_t clip_area_ori = layer->_clip_area;
+ layer->_clip_area = clip_area;
+
+ 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);
+ 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);
+ 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 = (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 < clip_area.x1) {
+ col_dsc.base.id1++;
+ continue;
+ }
+ if(col_a.x1 > 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++;
+ }
+ }
+ 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_area_t clip_area;
+ if(!_lv_area_intersect(&clip_area, &layer->_clip_area, &obj->coords)) return;
+
+ const lv_area_t clip_area_ori = layer->_clip_area;
+ layer->_clip_area = clip_area;
+
+ lv_chart_cursor_t * cursor;
+
+ lv_draw_line_dsc_t line_dsc_ori;
+ lv_draw_line_dsc_init(&line_dsc_ori);
+ 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);
+ 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++;
+ }
+
+ layer->_clip_area = clip_area_ori;
+}
+
+/**
+ * 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) return (x * chart->point_cnt) / w;
+
+ 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;
+
+ int32_t w = lv_obj_get_content_width(obj);
+ int32_t scroll_left = lv_obj_get_scroll_left(obj);
+
+ /*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;
+ }
+
+ if(chart->type == LV_CHART_TYPE_LINE) {
+ 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;
+ 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;
+
+ 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) {
+ 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 + block_gap) / chart->point_cnt;
+
+ int32_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
+ int32_t x_act;
+ x_act = (int32_t)((int32_t)(block_w) * i) ;
+ x_act += obj->coords.x1 + bwidth + lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
+
+ lv_obj_get_coords(obj, &col_a);
+ col_a.x1 = x_act - scroll_left;
+ col_a.x2 = col_a.x1 + block_w;
+ 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;
+ }
+ }
+ }
+}
+
+#endif
diff --git a/lib/lvgl/src/widgets/chart/lv_chart.h b/lib/lvgl/src/widgets/chart/lv_chart.h
new file mode 100644
index 00000000..a010bfda
--- /dev/null
+++ b/lib/lvgl/src/widgets/chart/lv_chart.h
@@ -0,0 +1,408 @@
+/**
+ * @file lv_chart.h
+ *
+ */
+
+#ifndef LV_CHART_H
+#define LV_CHART_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "../../lv_conf_internal.h"
+#include "../../core/lv_obj.h"
+
+#if LV_USE_CHART != 0
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**Default value of points. Can be used to not draw a point*/
+#define LV_CHART_POINT_NONE (INT32_MAX)
+LV_EXPORT_CONST_INT(LV_CHART_POINT_NONE);
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**
+ * Chart types
+ */
+enum _lv_chart_type_t {
+ LV_CHART_TYPE_NONE, /**< Don't draw the series*/
+ LV_CHART_TYPE_LINE, /**< Connect the points with lines*/
+ LV_CHART_TYPE_BAR, /**< Draw columns*/
+ LV_CHART_TYPE_SCATTER, /**< Draw points and lines in 2D (x,y coordinates)*/
+};
+
+#ifdef DOXYGEN
+typedef _lv_chart_type_t lv_chart_type_t;
+#else
+typedef uint8_t lv_chart_type_t;
+#endif /*DOXYGEN*/
+
+/**
+ * Chart update mode for `lv_chart_set_next`
+ */
+enum _lv_chart_update_mode_t {
+ LV_CHART_UPDATE_MODE_SHIFT, /**< Shift old data to the left and add the new one the right*/
+ LV_CHART_UPDATE_MODE_CIRCULAR, /**< Add the new data in a circular way*/
+};
+
+#ifdef DOXYGEN
+typedef _lv_chart_update_mode_t lv_chart_update_mode_t;
+#else
+typedef uint8_t lv_chart_update_mode_t;
+#endif /*DOXYGEN*/
+
+/**
+ * Enumeration of the axis'
+ */
+enum _lv_chart_axis_t {
+ LV_CHART_AXIS_PRIMARY_Y = 0x00,
+ LV_CHART_AXIS_SECONDARY_Y = 0x01,
+ LV_CHART_AXIS_PRIMARY_X = 0x02,
+ LV_CHART_AXIS_SECONDARY_X = 0x04,
+ _LV_CHART_AXIS_LAST
+};
+
+#ifdef DOXYGEN
+typedef _lv_chart_axis_t lv_chart_axis_t;
+#else
+typedef uint8_t lv_chart_axis_t;
+#endif /*DOXYGEN*/
+
+/**
+ * Descriptor a chart series
+ */
+typedef struct {
+ int32_t * x_points;
+ int32_t * y_points;
+ lv_color_t color;
+ uint32_t start_point;
+ uint32_t hidden : 1;
+ uint32_t x_ext_buf_assigned : 1;
+ uint32_t y_ext_buf_assigned : 1;
+ uint32_t x_axis_sec : 1;
+ uint32_t y_axis_sec : 1;
+} lv_chart_series_t;
+
+typedef struct {
+ lv_point_t pos;
+ int32_t point_id;
+ lv_color_t color;
+ lv_chart_series_t * ser;
+ lv_dir_t dir;
+ uint32_t pos_set: 1; /*1: pos is set; 0: point_id is set*/
+} lv_chart_cursor_t;
+
+typedef struct {
+ lv_obj_t obj;
+ lv_ll_t series_ll; /**< Linked list for the series (stores lv_chart_series_t)*/
+ lv_ll_t cursor_ll; /**< Linked list for the cursors (stores lv_chart_cursor_t)*/
+ int32_t ymin[2];
+ int32_t ymax[2];
+ int32_t xmin[2];
+ int32_t xmax[2];
+ int32_t pressed_point_id;
+ uint32_t hdiv_cnt; /**< Number of horizontal division lines*/
+ uint32_t vdiv_cnt; /**< Number of vertical division lines*/
+ uint32_t point_cnt; /**< Point number in a data line*/
+ lv_chart_type_t type : 3; /**< Line or column chart*/
+ lv_chart_update_mode_t update_mode : 1;
+} lv_chart_t;
+
+LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_chart_class;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+/**
+ * Create a chart object
+ * @param parent pointer to an object, it will be the parent of the new chart
+ * @return pointer to the created chart
+ */
+lv_obj_t * lv_chart_create(lv_obj_t * parent);
+
+/**
+ * Set a new type for a chart
+ * @param obj pointer to a chart object
+ * @param type new type of the chart (from 'lv_chart_type_t' enum)
+ */
+void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type);
+/**
+ * Set the number of points on a data line on a chart
+ * @param obj pointer to a chart object
+ * @param cnt new number of points on the data lines
+ */
+void lv_chart_set_point_count(lv_obj_t * obj, uint32_t cnt);
+
+/**
+ * Set the minimal and maximal y values on an axis
+ * @param obj pointer to a chart object
+ * @param axis `LV_CHART_AXIS_PRIMARY_Y` or `LV_CHART_AXIS_SECONDARY_Y`
+ * @param min minimum value of the y axis
+ * @param max maximum value of the y axis
+ */
+void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, int32_t min, int32_t max);
+
+/**
+ * Set update mode of the chart object. Affects
+ * @param obj pointer to a chart object
+ * @param update_mode the update mode
+ */
+void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode);
+
+/**
+ * Set the number of horizontal and vertical division lines
+ * @param obj pointer to a chart object
+ * @param hdiv number of horizontal division lines
+ * @param vdiv number of vertical division lines
+ */
+void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv);
+
+/**
+ * Get the type of a chart
+ * @param obj pointer to chart object
+ * @return type of the chart (from 'lv_chart_t' enum)
+ */
+lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj);
+
+/**
+ * Get the data point number per data line on chart
+ * @param obj pointer to chart object
+ * @return point number on each data line
+ */
+uint32_t lv_chart_get_point_count(const lv_obj_t * obj);
+
+/**
+ * Get the current index of the x-axis start point in the data array
+ * @param obj pointer to a chart object
+ * @param ser pointer to a data series on 'chart'
+ * @return the index of the current x start point in the data array
+ */
+uint32_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser);
+
+/**
+ * Get the position of a point to the chart.
+ * @param obj pointer to a chart object
+ * @param ser pointer to series
+ * @param id the index.
+ * @param p_out store the result position here
+ */
+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);
+
+/**
+ * Refresh a chart if its data line has changed
+ * @param obj pointer to chart object
+ */
+void lv_chart_refresh(lv_obj_t * obj);
+
+/*======================
+ * Series
+ *=====================*/
+
+/**
+ * Allocate and add a data series to the chart
+ * @param obj pointer to a chart object
+ * @param color color of the data series
+ * @param axis the y axis to which the series should be attached (::LV_CHART_AXIS_PRIMARY_Y or ::LV_CHART_AXIS_SECONDARY_Y)
+ * @return pointer to the allocated data series or NULL on failure
+ */
+lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis);
+
+/**
+ * Deallocate and remove a data series from a chart
+ * @param obj pointer to a chart object
+ * @param series pointer to a data series on 'chart'
+ */
+void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series);
+
+/**
+ * Hide/Unhide a single series of a chart.
+ * @param chart pointer to a chart object.
+ * @param series pointer to a series object
+ * @param hide true: hide the series
+ */
+void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide);
+
+/**
+ * Change the color of a series
+ * @param chart pointer to a chart object.
+ * @param series pointer to a series object
+ * @param color the new color of the series
+ */
+void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color);
+
+/**
+ * Set the index of the x-axis start point in the data array.
+ * This point will be considers the first (left) point and the other points will be drawn after it.
+ * @param obj pointer to a chart object
+ * @param ser pointer to a data series on 'chart'
+ * @param id the index of the x point in the data array
+ */
+void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id);
+
+/**
+ * Get the next series.
+ * @param chart pointer to a chart
+ * @param ser the previous series or NULL to get the first
+ * @return the next series or NULL if there is no more.
+ */
+lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * chart, const lv_chart_series_t * ser);
+
+/*=====================
+ * Cursor
+ *====================*/
+
+/**
+ * Add a cursor with a given color
+ * @param obj pointer to chart object
+ * @param color color of the cursor
+ * @param dir direction of the cursor. `LV_DIR_RIGHT/LEFT/TOP/DOWN/HOR/VER/ALL`. OR-ed values are possible
+ * @return pointer to the created cursor
+ */
+lv_chart_cursor_t * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir);
+
+/**
+ * Set the coordinate of the cursor with respect to the paddings
+ * @param chart pointer to a chart object
+ * @param cursor pointer to the cursor
+ * @param pos the new coordinate of cursor relative to the chart
+ */
+void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos);
+
+/**
+ * Stick the cursor to a point
+ * @param chart pointer to a chart object
+ * @param cursor pointer to the cursor
+ * @param ser pointer to a series
+ * @param point_id the point's index or `LV_CHART_POINT_NONE` to not assign to any points.
+ */
+void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser,
+ uint32_t point_id);
+
+/**
+ * Get the coordinate of the cursor with respect to the paddings
+ * @param chart pointer to a chart object
+ * @param cursor pointer to cursor
+ * @return coordinate of the cursor as lv_point_t
+ */
+lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor);
+
+/*=====================
+ * Set/Get value(s)
+ *====================*/
+
+/**
+ * Initialize all data points of a series with a value
+ * @param obj pointer to chart object
+ * @param ser pointer to a data series on 'chart'
+ * @param value the new value for all points. `LV_CHART_POINT_NONE` can be used to hide the points.
+ */
+void lv_chart_set_all_value(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value);
+
+/**
+ * Set the next point's Y value according to the update mode policy.
+ * @param obj pointer to chart object
+ * @param ser pointer to a data series on 'chart'
+ * @param value the new value of the next data
+ */
+void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value);
+
+/**
+ * Set the next point's X and Y value according to the update mode policy.
+ * @param obj pointer to chart object
+ * @param ser pointer to a data series on 'chart'
+ * @param x_value the new X value of the next data
+ * @param y_value the new Y value of the next data
+ */
+void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, int32_t x_value, int32_t y_value);
+
+/**
+ * Set an individual point's y value of a chart's series directly based on its index
+ * @param obj pointer to a chart object
+ * @param ser pointer to a data series on 'chart'
+ * @param id the index of the x point in the array
+ * @param value value to assign to array point
+ */
+void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t value);
+
+/**
+ * Set an individual point's x and y value of a chart's series directly based on its index
+ * Can be used only with `LV_CHART_TYPE_SCATTER`.
+ * @param obj pointer to chart object
+ * @param ser pointer to a data series on 'chart'
+ * @param id the index of the x point in the array
+ * @param x_value the new X value of the next data
+ * @param y_value the new Y value of the next data
+ */
+void lv_chart_set_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t x_value,
+ int32_t y_value);
+
+/**
+ * Set an external array for the y data points to use for the chart
+ * NOTE: It is the users responsibility to make sure the `point_cnt` matches the external array size.
+ * @param obj pointer to a chart object
+ * @param ser pointer to a data series on 'chart'
+ * @param array external array of points for chart
+ */
+void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[]);
+
+/**
+ * Set an external array for the x data points to use for the chart
+ * NOTE: It is the users responsibility to make sure the `point_cnt` matches the external array size.
+ * @param obj pointer to a chart object
+ * @param ser pointer to a data series on 'chart'
+ * @param array external array of points for chart
+ */
+void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[]);
+
+/**
+ * Get the array of y values of a series
+ * @param obj pointer to a chart object
+ * @param ser pointer to a data series on 'chart'
+ * @return the array of values with 'point_count' elements
+ */
+int32_t * lv_chart_get_y_array(const lv_obj_t * obj, lv_chart_series_t * ser);
+
+/**
+ * Get the array of x values of a series
+ * @param obj pointer to a chart object
+ * @param ser pointer to a data series on 'chart'
+ * @return the array of values with 'point_count' elements
+ */
+int32_t * lv_chart_get_x_array(const lv_obj_t * obj, lv_chart_series_t * ser);
+
+/**
+ * Get the index of the currently pressed point. It's the same for every series.
+ * @param obj pointer to a chart object
+ * @return the index of the point [0 .. point count] or LV_CHART_POINT_ID_NONE if no point is being pressed
+ */
+uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj);
+
+/**
+ * Get the overall offset from the chart's side to the center of the first point.
+ * In case of a bar chart it will be the center of the first column group
+ * @param obj pointer to a chart object
+ * @return the offset of the center
+ */
+int32_t lv_chart_get_first_point_center_offset(lv_obj_t * obj);
+
+/**********************
+ * MACROS
+ **********************/
+
+#endif /*LV_USE_CHART*/
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*LV_CHART_H*/