1import display_driver
2import lvgl as lv
3
4# A class is used to keep track of the series list because later we
5#  need to draw to the series in the reverse order to which they were initialised.
6class StackedAreaChart:
7    def __init__(self):
8        self.obj = None
9        self.series_list = [None, None, None]
10
11stacked_area_chart = StackedAreaChart()
12
13#
14# Callback which draws the blocks of colour under the lines
15#
16def draw_event_cb(e):
17
18    obj = e.get_target()
19    cont_a = lv.area_t()
20    obj.get_coords(cont_a)
21
22    #Add the faded area before the lines are drawn
23    dsc = e.get_draw_part_dsc()
24    if dsc.part == lv.PART.ITEMS:
25        if not dsc.p1 or not dsc.p2:
26            return
27
28        # Add a line mask that keeps the area below the line
29        line_mask_param = lv.draw_mask_line_param_t()
30        line_mask_param.points_init(dsc.p1.x, dsc.p1.y, dsc.p2.x, dsc.p2.y, lv.DRAW_MASK_LINE_SIDE.BOTTOM)
31        line_mask_id = lv.draw_mask_add(line_mask_param, None)
32
33        #Draw a rectangle that will be affected by the mask
34        draw_rect_dsc = lv.draw_rect_dsc_t()
35        draw_rect_dsc.init()
36        draw_rect_dsc.bg_opa = lv.OPA.COVER
37        draw_rect_dsc.bg_color = dsc.line_dsc.color
38
39        a = lv.area_t()
40        a.x1 = dsc.p1.x
41        a.x2 = dsc.p2.x
42        a.y1 = min(dsc.p1.y, dsc.p2.y)
43        a.y2 = cont_a.y2 - 13 # -13 cuts off where the rectangle draws over the chart margin. Without this an area of 0 doesn't look like 0
44        dsc.draw_ctx.rect(draw_rect_dsc, a)
45
46        # Remove the mask
47        lv.draw_mask_free_param(line_mask_param)
48        lv.draw_mask_remove_id(line_mask_id)
49
50
51#
52# Helper function to round a fixed point number
53#
54def round_fixed_point(n, shift):
55    # Create a bitmask to isolates the decimal part of the fixed point number
56    mask = 1
57    for bit_pos in range(shift):
58        mask = (mask << 1) + 1
59
60    decimal_part = n & mask
61
62    # Get 0.5 as fixed point
63    rounding_boundary = 1 << (shift - 1)
64
65    # Return either the integer part of n or the integer part + 1
66    if decimal_part < rounding_boundary:
67        return (n & ~mask)
68    return ((n >> shift) + 1) << shift
69
70
71#
72# Stacked area chart
73#
74def lv_example_chart_8():
75
76    #Create a stacked_area_chart.obj
77    stacked_area_chart.obj = lv.chart(lv.scr_act())
78    stacked_area_chart.obj.set_size(200, 150)
79    stacked_area_chart.obj.center()
80    stacked_area_chart.obj.set_type( lv.chart.TYPE.LINE)
81    stacked_area_chart.obj.set_div_line_count(5, 7)
82    stacked_area_chart.obj.add_event_cb( draw_event_cb, lv.EVENT.DRAW_PART_BEGIN, None)
83
84    # Set range to 0 to 100 for percentages. Draw ticks
85    stacked_area_chart.obj.set_range(lv.chart.AXIS.PRIMARY_Y,0,100)
86    stacked_area_chart.obj.set_axis_tick(lv.chart.AXIS.PRIMARY_Y, 3, 0, 5, 1, True, 30)
87
88    #Set point size to 0 so the lines are smooth
89    stacked_area_chart.obj.set_style_size(0, lv.PART.INDICATOR)
90
91    # Add some data series
92    stacked_area_chart.series_list[0] = stacked_area_chart.obj.add_series(lv.palette_main(lv.PALETTE.RED), lv.chart.AXIS.PRIMARY_Y)
93    stacked_area_chart.series_list[1] = stacked_area_chart.obj.add_series(lv.palette_main(lv.PALETTE.BLUE), lv.chart.AXIS.PRIMARY_Y)
94    stacked_area_chart.series_list[2] = stacked_area_chart.obj.add_series(lv.palette_main(lv.PALETTE.GREEN), lv.chart.AXIS.PRIMARY_Y)
95
96    for point in range(10):
97        # Make some random data
98        vals = [lv.rand(10, 20), lv.rand(20, 30), lv.rand(20, 30)]
99
100        fixed_point_shift = 5
101        total = vals[0] + vals[1] + vals[2]
102        draw_heights = [0, 0, 0]
103        int_sum = 0
104        decimal_sum = 0
105
106        # Fixed point cascade rounding ensures percentages add to 100
107        for series_index in range(3):
108            decimal_sum += int(((vals[series_index] * 100) << fixed_point_shift) // total)
109            int_sum += int((vals[series_index] * 100) / total)
110
111            modifier = (round_fixed_point(decimal_sum, fixed_point_shift) >> fixed_point_shift) - int_sum
112
113            #  The draw heights are equal to the percentage of the total each value is + the cumulative sum of the previous percentages.
114            #   The accumulation is how the values get "stacked"
115            draw_heights[series_index] = int(int_sum + modifier)
116
117            #  Draw to the series in the reverse order to which they were initialised.
118            #   Without this the higher values will draw on top of the lower ones.
119            #   This is because the Z-height of a series matches the order it was initialised
120            stacked_area_chart.obj.set_next_value( stacked_area_chart.series_list[3 - series_index - 1], draw_heights[series_index])
121
122    stacked_area_chart.obj.refresh()
123
124lv_example_chart_8()
125