diff --git a/demos/widgets/lv_demo_widgets_shop.c b/demos/widgets/lv_demo_widgets_shop.c index d05194606f..3514199e76 100644 --- a/demos/widgets/lv_demo_widgets_shop.c +++ b/demos/widgets/lv_demo_widgets_shop.c @@ -31,8 +31,6 @@ static void shop_chart_event_cb(lv_event_t * e); **********************/ static lv_obj_t * chart3; -static lv_chart_series_t * ser4; - /********************** * GLOBAL VARIABLES **********************/ @@ -66,19 +64,21 @@ void lv_demo_widgets_shop_create(lv_obj_t * parent) lv_obj_set_style_text_color(hint, lv_palette_main(LV_PALETTE_GREEN), 0); chart3 = lv_chart_create(panel1); - lv_chart_set_type(chart3, LV_CHART_TYPE_BAR); + lv_chart_set_type(chart3, LV_CHART_TYPE_STACKED); lv_chart_set_div_line_count(chart3, 6, 0); lv_chart_set_point_count(chart3, 7); lv_obj_add_event_cb(chart3, shop_chart_event_cb, LV_EVENT_ALL, NULL); - ser4 = lv_chart_add_series(chart3, lv_theme_get_color_primary(chart3), LV_CHART_AXIS_PRIMARY_Y); - lv_chart_set_next_value(chart3, ser4, lv_rand(60, 90)); - lv_chart_set_next_value(chart3, ser4, lv_rand(60, 90)); - lv_chart_set_next_value(chart3, ser4, lv_rand(60, 90)); - lv_chart_set_next_value(chart3, ser4, lv_rand(60, 90)); - lv_chart_set_next_value(chart3, ser4, lv_rand(60, 90)); - lv_chart_set_next_value(chart3, ser4, lv_rand(60, 90)); - lv_chart_set_next_value(chart3, ser4, lv_rand(60, 90)); + lv_chart_series_t * ser4 = lv_chart_add_series(chart3, lv_palette_main(LV_PALETTE_GREEN), LV_CHART_AXIS_PRIMARY_Y); + lv_chart_series_t * ser5 = lv_chart_add_series(chart3, lv_palette_main(LV_PALETTE_BLUE), LV_CHART_AXIS_PRIMARY_Y); + lv_chart_series_t * ser6 = lv_chart_add_series(chart3, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y); + + uint32_t i; + for(i = 0; i < 8; i++) { + lv_chart_set_next_value(chart3, ser4, lv_rand(20, 40)); + lv_chart_set_next_value(chart3, ser5, lv_rand(15, 30)); + lv_chart_set_next_value(chart3, ser6, lv_rand(15, 30)); + } if(disp_size == DISP_LARGE) { static int32_t grid1_col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST}; diff --git a/docs/src/details/widgets/chart.rst b/docs/src/details/widgets/chart.rst index e3b1abfdef..2dceba8bd1 100644 --- a/docs/src/details/widgets/chart.rst +++ b/docs/src/details/widgets/chart.rst @@ -38,7 +38,7 @@ Each chart has the following attributes (over and above attributes found in Type (governs how a chart's data series are drawn) -- Can be LINE (default), BAR, SCATTER, or none. +- Can be LINE (default), BAR, STACKED, SCATTER, or NONE. - You can change the chart's type at any point during its life. Horizontal and Vertical division lines @@ -60,8 +60,8 @@ Point count (number of data points in all data series added to the chart) - default 10 - If you provide your own data-value arrays, each array so provided must contain at least this number of values. -- For LINE- and BAR-charts, this is the number of points on the X axis. -- LINE- and BAR-charts require only one data-value array to supply Y-values for each data point. +- For LINE-, BAR-, STACKED-charts, this is the number of points on the X axis. +- LINE-, BAR-, STACKED-charts require only one data-value array to supply Y-values for each data point. - For SCATTER charts, this is the number of scatter-points in the data series. - SCATTER charts have separate data-value arrays for both X-values and Y-values. @@ -121,7 +121,7 @@ Parts and Styles - :cpp:enumerator:`LV_PART_MAIN` The background of the chart. Uses the :ref:`typical background ` and line style properties (for division lines). - *Padding* makes the series area smaller. For BAR charts ``pad_column`` sets the + *Padding* makes the series area smaller. For BAR and STACKED charts ``pad_column`` sets the space between bars in the same data series. - :cpp:enumerator:`LV_PART_SCROLLBAR` A scrollbar used if the chart is zoomed. See :ref:`base_widget`'s documentation for details. @@ -157,7 +157,9 @@ A chart can be one of the following types: can also be illustrated if their ``width``, ``height``, ``bg_color`` and ``radius`` styles (for :cpp:enumerator:`LV_PART_ITEMS`) are set and both ``width`` and ``height`` have non-zero values. -- :cpp:enumerator:`LV_CHART_TYPE_BAR`: Draw bars. +- :cpp:enumerator:`LV_CHART_TYPE_BAR`: Draw individual vertical bars for each point in each series. +- :cpp:enumerator:`LV_CHART_TYPE_STACKED`: Draw vertical stacked bars where multiple data series + are displayed as segments within a single bar for each data point. Supports only positive values. - :cpp:enumerator:`LV_CHART_TYPE_SCATTER`: X/Y chart drawing point's and optionally lines between the points if line-width style values for :cpp:enumerator:`LV_PART_ITEMS` is a non-zero value, and the point's Y-value is diff --git a/examples/widgets/chart/lv_example_chart_3.c b/examples/widgets/chart/lv_example_chart_3.c index e1e624d277..4e4458e75a 100644 --- a/examples/widgets/chart/lv_example_chart_3.c +++ b/examples/widgets/chart/lv_example_chart_3.c @@ -20,44 +20,49 @@ static void event_cb(lv_event_t * e) LV_LOG_USER("Selected point %d", (int)id); lv_chart_series_t * ser = lv_chart_get_series_next(chart, NULL); + int32_t value = 0; while(ser) { lv_point_t p; lv_chart_get_point_pos_by_id(chart, ser, id, &p); int32_t * y_array = lv_chart_get_series_y_array(chart, ser); - int32_t value = y_array[id]; + if(y_array[id] != LV_CHART_POINT_NONE && y_array[id] >= 0) { - /*Draw a rectangle above the clicked point*/ - lv_layer_t * layer = lv_event_get_layer(e); - lv_draw_rect_dsc_t draw_rect_dsc; - lv_draw_rect_dsc_init(&draw_rect_dsc); - draw_rect_dsc.bg_color = lv_color_black(); - draw_rect_dsc.bg_opa = LV_OPA_50; - draw_rect_dsc.radius = 3; + /*Accumulate the values to show the rectangles at the top of each segment*/ + value += y_array[id]; - lv_area_t chart_obj_coords; - lv_obj_get_coords(chart, &chart_obj_coords); - lv_area_t rect_area; - rect_area.x1 = chart_obj_coords.x1 + p.x - 20; - rect_area.x2 = chart_obj_coords.x1 + p.x + 20; - rect_area.y1 = chart_obj_coords.y1 + p.y - 30; - rect_area.y2 = chart_obj_coords.y1 + p.y - 10; - lv_draw_rect(layer, &draw_rect_dsc, &rect_area); + /*Draw a rectangle above the clicked point*/ + lv_layer_t * layer = lv_event_get_layer(e); + lv_draw_rect_dsc_t draw_rect_dsc; + lv_draw_rect_dsc_init(&draw_rect_dsc); + draw_rect_dsc.bg_color = lv_color_black(); + draw_rect_dsc.bg_opa = LV_OPA_50; + draw_rect_dsc.radius = 3; - /*Draw the value as label to the center of the rectangle*/ - char buf[16]; - lv_snprintf(buf, sizeof(buf), LV_SYMBOL_DUMMY"$%d", value); + lv_area_t chart_obj_coords; + lv_obj_get_coords(chart, &chart_obj_coords); + lv_area_t rect_area; + rect_area.x1 = chart_obj_coords.x1 + p.x - 20; + rect_area.x2 = chart_obj_coords.x1 + p.x + 20; + rect_area.y1 = chart_obj_coords.y1 + p.y - 10; + rect_area.y2 = chart_obj_coords.y1 + p.y + 10; + lv_draw_rect(layer, &draw_rect_dsc, &rect_area); - lv_draw_label_dsc_t draw_label_dsc; - lv_draw_label_dsc_init(&draw_label_dsc); - draw_label_dsc.color = lv_color_white(); - draw_label_dsc.text = buf; - draw_label_dsc.text_local = 1; - draw_label_dsc.align = LV_TEXT_ALIGN_CENTER; - lv_area_t label_area = rect_area; - lv_area_set_height(&label_area, lv_font_get_line_height(draw_label_dsc.font)); - lv_area_align(&rect_area, &label_area, LV_ALIGN_CENTER, 0, 0); - lv_draw_label(layer, &draw_label_dsc, &label_area); + /*Draw the value as label to the center of the rectangle*/ + char buf[16]; + lv_snprintf(buf, sizeof(buf), LV_SYMBOL_DUMMY"$%d", value); + + lv_draw_label_dsc_t draw_label_dsc; + lv_draw_label_dsc_init(&draw_label_dsc); + draw_label_dsc.color = lv_color_white(); + draw_label_dsc.text = buf; + draw_label_dsc.text_local = 1; + draw_label_dsc.align = LV_TEXT_ALIGN_CENTER; + lv_area_t label_area = rect_area; + lv_area_set_height(&label_area, lv_font_get_line_height(draw_label_dsc.font)); + lv_area_align(&rect_area, &label_area, LV_ALIGN_CENTER, 0, 0); + lv_draw_label(layer, &draw_label_dsc, &label_area); + } ser = lv_chart_get_series_next(chart, ser); } @@ -75,22 +80,23 @@ void lv_example_chart_3(void) /*Create a chart*/ lv_obj_t * chart; chart = lv_chart_create(lv_screen_active()); - lv_obj_set_size(chart, 200, 150); + lv_obj_set_size(chart, 280, 180); + lv_obj_set_style_pad_column(chart, 4, 0); lv_obj_center(chart); + lv_chart_set_type(chart, LV_CHART_TYPE_STACKED); lv_obj_add_event_cb(chart, event_cb, LV_EVENT_ALL, NULL); lv_obj_refresh_ext_draw_size(chart); - /*Zoom in a little in X*/ - // lv_chart_set_scale_x(chart, 800); - /*Add two data series*/ lv_chart_series_t * ser1 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y); lv_chart_series_t * ser2 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_GREEN), LV_CHART_AXIS_PRIMARY_Y); + lv_chart_series_t * ser3 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_BLUE), LV_CHART_AXIS_PRIMARY_Y); uint32_t i; for(i = 0; i < 10; i++) { - lv_chart_set_next_value(chart, ser1, (int32_t)lv_rand(60, 90)); - lv_chart_set_next_value(chart, ser2, (int32_t)lv_rand(10, 40)); + lv_chart_set_next_value(chart, ser1, lv_rand(15, 30)); + lv_chart_set_next_value(chart, ser2, lv_rand(15, 30)); + lv_chart_set_next_value(chart, ser3, lv_rand(15, 30)); } } diff --git a/src/widgets/chart/lv_chart.c b/src/widgets/chart/lv_chart.c index af1b913b96..5788a56177 100644 --- a/src/widgets/chart/lv_chart.c +++ b/src/widgets/chart/lv_chart.c @@ -40,11 +40,13 @@ 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 @@ -291,9 +293,13 @@ void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint3 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); @@ -328,9 +334,51 @@ void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint3 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); @@ -339,10 +387,7 @@ void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint3 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); } @@ -578,10 +623,10 @@ void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, int32_t va 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) @@ -698,7 +743,7 @@ 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) { + 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); @@ -824,6 +869,7 @@ static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e) 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); } @@ -1113,7 +1159,7 @@ static void draw_series_scatter(lv_obj_t * obj, lv_layer_t * layer) 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 = 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; @@ -1244,6 +1290,118 @@ 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) +{ + 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); @@ -1352,7 +1510,7 @@ static uint32_t get_index_from_x(lv_obj_t * obj, int32_t x) 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; + 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); @@ -1378,19 +1536,19 @@ 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; } + 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 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); @@ -1399,6 +1557,7 @@ static void invalidate_point(lv_obj_t * obj, uint32_t i) 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; @@ -1411,21 +1570,23 @@ static void invalidate_point(lv_obj_t * obj, uint32_t i) lv_obj_invalidate_area(obj, &coords); } } - else if(chart->type == LV_CHART_TYPE_BAR) { + 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 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); + 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 - scroll_left; - col_a.x2 = col_a.x1 + block_w; + 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); @@ -1481,4 +1642,19 @@ static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t c } } +/** + * 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 diff --git a/src/widgets/chart/lv_chart.h b/src/widgets/chart/lv_chart.h index d62c48cc03..34903cfb43 100644 --- a/src/widgets/chart/lv_chart.h +++ b/src/widgets/chart/lv_chart.h @@ -36,7 +36,8 @@ LV_EXPORT_CONST_INT(LV_CHART_POINT_NONE); typedef enum { 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_BAR, /**< Draw bars for each series*/ + LV_CHART_TYPE_STACKED, /**< Draw a single stacked bar for each data point. Supports only positive values*/ LV_CHART_TYPE_SCATTER, /**< Draw points and lines in 2D (x,y coordinates)*/ } lv_chart_type_t; diff --git a/src/widgets/chart/lv_chart_private.h b/src/widgets/chart/lv_chart_private.h index 7ccda787d1..b2f0bcc42a 100644 --- a/src/widgets/chart/lv_chart_private.h +++ b/src/widgets/chart/lv_chart_private.h @@ -63,7 +63,7 @@ struct _lv_chart_t { uint32_t hdiv_cnt; /**< Number of horizontal division lines */ uint32_t vdiv_cnt; /**< Number of vertical division lines */ uint32_t point_cnt; /**< Number of points in all series */ - lv_chart_type_t type : 3; /**< Chart type */ + lv_chart_type_t type : 4; /**< Chart type */ lv_chart_update_mode_t update_mode : 2; };