diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a0a7c944fe..174e849bc6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,10 +1,10 @@ # Changelog ## v8.1.0 (In progress) +- feat(example) stacked area chart lv_example_chart_8 added. - renamed lv_obj_get_child_id(obj) to lv_obj_get_index(obj). - added lv_obj_move_to_index(obj, index). (#2461) - redefined lv_obj_move_foreground(obj) and lv_obj_move_background(obj) as inline functions now calling lv_obj_move_to_index(obj, index). -- lv_obj_swap(obj1, obj2) added. (#2461) - feat(anim) add interface for handling lv_anim user data. (#2415) - feat(obj) add lv_is_initialized (#2402) - feat(obj) Backport keypad and encoder scrolling from v7 `lv_page` to v8 `lv_obj` (#2390) @@ -21,7 +21,6 @@ - feat(timer) check if lv_tick_inc is called aa6641a6 - feat(docs) add view on GitHub link a716ac6e - feat(event) pass the scroll aniamtion to LV_EVENT_SCROLL_BEGIN ca54ecfe - - perf(draw) reimplement rectangle drawing algorithms - perf(draw) reimplement circle drawing algorithms (#2374) (Also [change masking](https://docs.lvgl.io/master/overview/drawing.html#masking)) - fix(draw) false assertion error in lv_draw_mask caused by wrong pointer diff --git a/examples/widgets/chart/index.rst b/examples/widgets/chart/index.rst index a0326925eb..92492fec78 100644 --- a/examples/widgets/chart/index.rst +++ b/examples/widgets/chart/index.rst @@ -42,4 +42,8 @@ Scatter chart .. lv_example:: widgets/chart/lv_example_chart_7 :language: c +Stacked area chart +""""""""""""""""""""""""""""""""""" +.. lv_example:: widgets/chart/lv_example_chart_8 + :language: diff --git a/examples/widgets/chart/lv_example_chart_8.c b/examples/widgets/chart/lv_example_chart_8.c new file mode 100644 index 0000000000..932dad4411 --- /dev/null +++ b/examples/widgets/chart/lv_example_chart_8.c @@ -0,0 +1,130 @@ +#include "../../lv_examples.h" +#if LV_USE_CHART && LV_DRAW_COMPLEX && LV_BUILD_EXAMPLES + +/* A struct is used to keep track of the series list because later we need to draw to the series in the reverse order to which they were initialised. */ +typedef struct +{ + lv_obj_t *obj; + lv_chart_series_t *series_list[3]; +} stacked_area_chart_t; + +static stacked_area_chart_t stacked_area_chart; + +/** + * Callback which draws the blocks of colour under the lines + **/ +static void draw_event_cb(lv_event_t *e) +{ + lv_obj_t *obj = lv_event_get_target(e); + + /*Add the faded area before the lines are drawn*/ + lv_obj_draw_part_dsc_t *dsc = lv_event_get_draw_part_dsc(e); + if (dsc->part == LV_PART_ITEMS) + { + if (!dsc->p1 || !dsc->p2) + return; + + /*Add a line mask that keeps the area below the line*/ + lv_draw_mask_line_param_t line_mask_param; + lv_draw_mask_line_points_init(&line_mask_param, dsc->p1->x, dsc->p1->y, dsc->p2->x, dsc->p2->y, LV_DRAW_MASK_LINE_SIDE_BOTTOM); + int16_t line_mask_id = lv_draw_mask_add(&line_mask_param, NULL); + + /*Draw a rectangle that will be affected by the mask*/ + lv_draw_rect_dsc_t draw_rect_dsc; + lv_draw_rect_dsc_init(&draw_rect_dsc); + draw_rect_dsc.bg_opa = LV_OPA_COVER; + draw_rect_dsc.bg_color = dsc->line_dsc->color; + + lv_area_t a; + a.x1 = dsc->p1->x; + a.x2 = dsc->p2->x; + a.y1 = LV_MIN(dsc->p1->y, dsc->p2->y); + a.y2 = obj->coords.y2 - 13; /* -13 cuts off where the rectangle draws over the chart margin. Without this an area of 0 doesnt look like 0 */ + lv_draw_rect(&a, dsc->clip_area, &draw_rect_dsc); + + /*Remove the mask*/ + lv_draw_mask_free_param(&line_mask_param); + lv_draw_mask_remove_id(line_mask_id); + } +} + +/** + * Helper function to round a fixed point number + **/ +static int32_t round_fixed_point(int32_t n, int8_t shift) +{ + /* Create a bitmask to isolates the decimal part of the fixed point number */ + int32_t mask = 1; + for (int32_t bit_pos = 0; bit_pos < shift; bit_pos++) + { + mask = (mask << 1) + 1; + } + + int32_t decimal_part = n & mask; + + /* Get 0.5 as fixed point */ + int32_t rounding_boundary = 1 << (shift - 1); + + /* Return either the integer part of n or the integer part + 1 */ + return (decimal_part < rounding_boundary) ? (n & ~mask) : ((n >> shift) + 1) << shift; +} + +/** + * Stacked area chart + */ +void lv_example_chart_8(void) +{ + /*Create a stacked_area_chart.obj*/ + stacked_area_chart.obj = lv_chart_create(lv_scr_act()); + lv_obj_set_size(stacked_area_chart.obj, 200, 150); + lv_obj_center(stacked_area_chart.obj); + lv_chart_set_type(stacked_area_chart.obj, LV_CHART_TYPE_LINE); + lv_chart_set_div_line_count(stacked_area_chart.obj, 5, 7); + lv_obj_add_event_cb(stacked_area_chart.obj, draw_event_cb, LV_EVENT_DRAW_PART_BEGIN, NULL); + + /* Set range to 0 to 100 for percentages. Draw ticks */ + lv_chart_set_range(stacked_area_chart.obj,LV_CHART_AXIS_PRIMARY_Y,0,100); + lv_chart_set_axis_tick(stacked_area_chart.obj, LV_CHART_AXIS_PRIMARY_Y, 3, 0, 5, 1, true, 30); + + /*Set point size to 0 so the lines are smooth */ + lv_obj_set_style_size(stacked_area_chart.obj, 0, LV_PART_INDICATOR); + + /*Add some data series*/ + stacked_area_chart.series_list[0] = lv_chart_add_series(stacked_area_chart.obj, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y); + stacked_area_chart.series_list[1] = lv_chart_add_series(stacked_area_chart.obj, lv_palette_main(LV_PALETTE_BLUE), LV_CHART_AXIS_PRIMARY_Y); + stacked_area_chart.series_list[2] = lv_chart_add_series(stacked_area_chart.obj, lv_palette_main(LV_PALETTE_GREEN), LV_CHART_AXIS_PRIMARY_Y); + + for (int point = 0; point < 10; point++) + { + /* Make some random data */ + uint32_t vals[3] = {lv_rand(10, 20), lv_rand(20, 30), lv_rand(20, 30)}; + + int8_t fixed_point_shift = 5; + uint32_t total = vals[0] + vals[1] + vals[2]; + uint32_t draw_heights[3]; + uint32_t int_sum = 0; + uint32_t decimal_sum = 0; + + /* Fixed point cascade rounding ensures percentages add to 100 */ + for (int32_t series_index = 0; series_index < 3; series_index++) + { + decimal_sum += (((vals[series_index] * 100) << fixed_point_shift) / total); + int_sum += (vals[series_index] * 100) / total; + + int32_t modifier = (round_fixed_point(decimal_sum, fixed_point_shift) >> fixed_point_shift) - int_sum; + + /* The draw heights are equal to the percentage of the total each value is + the cumulative sum of the previous percentages. + The accumulation is how the values get "stacked" */ + draw_heights[series_index] = int_sum + modifier; + + /* Draw to the series in the reverse order to which they were initialised. + Without this the higher values will draw on top of the lower ones. + This is because the Z-height of a series matches the order it was initialsied */ + lv_chart_set_next_value(stacked_area_chart.obj, stacked_area_chart.series_list[3 - series_index - 1], draw_heights[series_index]); + } + } + + lv_chart_refresh(stacked_area_chart.obj); +} + +#endif \ No newline at end of file diff --git a/examples/widgets/lv_example_widgets.h b/examples/widgets/lv_example_widgets.h index 763dd924b5..0d7feb9141 100644 --- a/examples/widgets/lv_example_widgets.h +++ b/examples/widgets/lv_example_widgets.h @@ -57,6 +57,7 @@ void lv_example_chart_4(void); void lv_example_chart_5(void); void lv_example_chart_6(void); void lv_example_chart_7(void); +void lv_example_chart_8(void); void lv_example_checkbox_1(void);