mirror of
https://github.com/lvgl/lvgl.git
synced 2026-05-10 04:37:55 +08:00
feat(chart): add stacked chart support (#8573)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: liamHowatt <liamjmh0@gmail.com>
This commit is contained in:
committed by
GitHub
parent
709461813b
commit
e6864400a2
@@ -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};
|
||||
|
||||
@@ -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 <typical bg props>` 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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+197
-21
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user