1 /**
2  * @file lv_chart.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_chart.h"
10 #if LV_USE_CHART != 0
11 
12 #include "../lv_core/lv_refr.h"
13 #include "../lv_draw/lv_draw.h"
14 #include "../lv_themes/lv_theme.h"
15 
16 /*********************
17  *      DEFINES
18  *********************/
19 #define LV_CHART_YMIN_DEF 0
20 #define LV_CHART_YMAX_DEF 100
21 #define LV_CHART_HDIV_DEF 3
22 #define LV_CHART_VDIV_DEF 5
23 #define LV_CHART_PNUM_DEF 10
24 #define LV_CHART_AXIS_TO_LABEL_DISTANCE 4
25 #define LV_CHART_AXIS_MAJOR_TICK_LEN_COE 1 / 15
26 #define LV_CHART_AXIS_MINOR_TICK_LEN_COE 2 / 3
27 
28 /**********************
29  *      TYPEDEFS
30  **********************/
31 
32 /**********************
33  *  STATIC PROTOTYPES
34  **********************/
35 static bool lv_chart_design(lv_obj_t * chart, const lv_area_t * mask, lv_design_mode_t mode);
36 static lv_res_t lv_chart_signal(lv_obj_t * chart, lv_signal_t sign, void * param);
37 static void lv_chart_draw_div(lv_obj_t * chart, const lv_area_t * mask);
38 static void lv_chart_draw_lines(lv_obj_t * chart, const lv_area_t * mask);
39 static void lv_chart_draw_points(lv_obj_t * chart, const lv_area_t * mask);
40 static void lv_chart_draw_cols(lv_obj_t * chart, const lv_area_t * mask);
41 static void lv_chart_draw_vertical_lines(lv_obj_t * chart, const lv_area_t * mask);
42 static void lv_chart_draw_areas(lv_obj_t * chart, const lv_area_t * mask);
43 static void lv_chart_draw_axes(lv_obj_t * chart, const lv_area_t * mask);
44 static void lv_chart_inv_lines(lv_obj_t * chart, uint16_t i);
45 static void lv_chart_inv_points(lv_obj_t * chart, uint16_t i);
46 static void lv_chart_inv_cols(lv_obj_t * chart, uint16_t i);
47 
48 /**********************
49  *  STATIC VARIABLES
50  **********************/
51 static lv_design_cb_t ancestor_design_f;
52 static lv_signal_cb_t ancestor_signal;
53 
54 /**********************
55  *      MACROS
56  **********************/
57 
58 /**********************
59  *   GLOBAL FUNCTIONS
60  **********************/
61 
62 /**
63  * Create a chart background objects
64  * @param par pointer to an object, it will be the parent of the new chart background
65  * @param copy pointer to a chart background object, if not NULL then the new object will be copied
66  * from it
67  * @return pointer to the created chart background
68  */
lv_chart_create(lv_obj_t * par,const lv_obj_t * copy)69 lv_obj_t * lv_chart_create(lv_obj_t * par, const lv_obj_t * copy)
70 {
71     LV_LOG_TRACE("chart create started");
72 
73     /*Create the ancestor basic object*/
74     lv_obj_t * new_chart = lv_obj_create(par, copy);
75     lv_mem_assert(new_chart);
76     if(new_chart == NULL) return NULL;
77 
78     /*Allocate the object type specific extended data*/
79     lv_chart_ext_t * ext = lv_obj_allocate_ext_attr(new_chart, sizeof(lv_chart_ext_t));
80     lv_mem_assert(ext);
81     if(ext == NULL) return NULL;
82 
83     lv_ll_init(&ext->series_ll, sizeof(lv_chart_series_t));
84 
85     ext->series.num            = 0;
86     ext->ymin                  = LV_CHART_YMIN_DEF;
87     ext->ymax                  = LV_CHART_YMAX_DEF;
88     ext->hdiv_cnt              = LV_CHART_HDIV_DEF;
89     ext->vdiv_cnt              = LV_CHART_VDIV_DEF;
90     ext->point_cnt             = LV_CHART_PNUM_DEF;
91     ext->type                  = LV_CHART_TYPE_LINE;
92     ext->update_mode           = LV_CHART_UPDATE_MODE_SHIFT;
93     ext->series.opa            = LV_OPA_COVER;
94     ext->series.dark           = LV_OPA_50;
95     ext->series.width          = 2;
96     ext->margin                = 0;
97     memset(&ext->x_axis, 0, sizeof(ext->x_axis));
98     memset(&ext->y_axis, 0, sizeof(ext->y_axis));
99     ext->x_axis.major_tick_len = LV_CHART_TICK_LENGTH_AUTO;
100     ext->x_axis.minor_tick_len = LV_CHART_TICK_LENGTH_AUTO;
101     ext->y_axis.major_tick_len = LV_CHART_TICK_LENGTH_AUTO;
102     ext->y_axis.minor_tick_len = LV_CHART_TICK_LENGTH_AUTO;
103 
104     if(ancestor_design_f == NULL) ancestor_design_f = lv_obj_get_design_cb(new_chart);
105     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(new_chart);
106 
107     lv_obj_set_signal_cb(new_chart, lv_chart_signal);
108     lv_obj_set_design_cb(new_chart, lv_chart_design);
109 
110     /*Init the new chart background object*/
111     if(copy == NULL) {
112         lv_obj_set_size(new_chart, LV_DPI * 3, LV_DPI * 2);
113 
114         /*Set the default styles*/
115         lv_theme_t * th = lv_theme_get_current();
116         if(th) {
117             lv_chart_set_style(new_chart, LV_CHART_STYLE_MAIN, th->style.chart);
118         } else {
119             lv_chart_set_style(new_chart, LV_CHART_STYLE_MAIN, &lv_style_pretty);
120         }
121 
122     } else {
123         lv_chart_ext_t * ext_copy = lv_obj_get_ext_attr(copy);
124 
125         ext->type       = ext_copy->type;
126         ext->ymin       = ext_copy->ymin;
127         ext->ymax       = ext_copy->ymax;
128         ext->hdiv_cnt   = ext_copy->hdiv_cnt;
129         ext->vdiv_cnt   = ext_copy->vdiv_cnt;
130         ext->point_cnt  = ext_copy->point_cnt;
131         ext->series.opa = ext_copy->series.opa;
132         ext->margin     = ext_copy->margin;
133         memcpy(&ext->x_axis, &ext_copy->x_axis, sizeof(lv_chart_axis_cfg_t));
134         memcpy(&ext->y_axis, &ext_copy->y_axis, sizeof(lv_chart_axis_cfg_t));
135 
136         /*Refresh the style with new signal function*/
137         lv_obj_refresh_style(new_chart);
138     }
139 
140     LV_LOG_INFO("chart created");
141 
142     return new_chart;
143 }
144 
145 /*======================
146  * Add/remove functions
147  *=====================*/
148 
149 /**
150  * Allocate and add a data series to the chart
151  * @param chart pointer to a chart object
152  * @param color color of the data series
153  * @return pointer to the allocated data series
154  */
lv_chart_add_series(lv_obj_t * chart,lv_color_t color)155 lv_chart_series_t * lv_chart_add_series(lv_obj_t * chart, lv_color_t color)
156 {
157     lv_chart_ext_t * ext    = lv_obj_get_ext_attr(chart);
158     lv_chart_series_t * ser = lv_ll_ins_head(&ext->series_ll);
159     lv_mem_assert(ser);
160     if(ser == NULL) return NULL;
161 
162     lv_coord_t def = LV_CHART_POINT_DEF;
163 
164     if(ser == NULL) return NULL;
165 
166     ser->color  = color;
167     ser->points = lv_mem_alloc(sizeof(lv_coord_t) * ext->point_cnt);
168     lv_mem_assert(ser->points);
169     if(ser->points == NULL) {
170         lv_ll_rem(&ext->series_ll, ser);
171         lv_mem_free(ser);
172         return NULL;
173     }
174 
175     ser->start_point = 0;
176 
177     uint16_t i;
178     lv_coord_t * p_tmp = ser->points;
179     for(i = 0; i < ext->point_cnt; i++) {
180         *p_tmp = def;
181         p_tmp++;
182     }
183 
184     ext->series.num++;
185 
186     return ser;
187 }
188 
189 /**
190  * Clear the point of a serie
191  * @param chart pointer to a chart object
192  * @param serie pointer to the chart's serie to clear
193  */
lv_chart_clear_serie(lv_obj_t * chart,lv_chart_series_t * serie)194 void lv_chart_clear_serie(lv_obj_t * chart, lv_chart_series_t * serie)
195 {
196     if(chart == NULL || serie == NULL) return;
197     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
198     if(ext == NULL) return;
199 
200     uint32_t i;
201     for(i = 0; i < ext->point_cnt; i++) {
202         serie->points[i] = LV_CHART_POINT_DEF;
203     }
204 
205     serie->start_point = 0;
206 }
207 
208 /*=====================
209  * Setter functions
210  *====================*/
211 
212 /**
213  * Set the number of horizontal and vertical division lines
214  * @param chart pointer to a graph background object
215  * @param hdiv number of horizontal division lines
216  * @param vdiv number of vertical division lines
217  */
lv_chart_set_div_line_count(lv_obj_t * chart,uint8_t hdiv,uint8_t vdiv)218 void lv_chart_set_div_line_count(lv_obj_t * chart, uint8_t hdiv, uint8_t vdiv)
219 {
220     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
221     if(ext->hdiv_cnt == hdiv && ext->vdiv_cnt == vdiv) return;
222 
223     ext->hdiv_cnt = hdiv;
224     ext->vdiv_cnt = vdiv;
225 
226     lv_obj_invalidate(chart);
227 }
228 
229 /**
230  * Set the minimal and maximal y values
231  * @param chart pointer to a graph background object
232  * @param ymin y minimum value
233  * @param ymax y maximum value
234  */
lv_chart_set_range(lv_obj_t * chart,lv_coord_t ymin,lv_coord_t ymax)235 void lv_chart_set_range(lv_obj_t * chart, lv_coord_t ymin, lv_coord_t ymax)
236 {
237     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
238     if(ext->ymin == ymin && ext->ymax == ymax) return;
239 
240     ext->ymin = ymin;
241     ext->ymax = ymax;
242 
243     lv_chart_refresh(chart);
244 }
245 
246 /**
247  * Set a new type for a chart
248  * @param chart pointer to a chart object
249  * @param type new type of the chart (from 'lv_chart_type_t' enum)
250  */
lv_chart_set_type(lv_obj_t * chart,lv_chart_type_t type)251 void lv_chart_set_type(lv_obj_t * chart, lv_chart_type_t type)
252 {
253     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
254     if(ext->type == type) return;
255 
256     ext->type = type;
257 
258     lv_chart_refresh(chart);
259 }
260 
261 /**
262  * Set the number of points on a data line on a chart
263  * @param chart pointer r to chart object
264  * @param point_cnt new number of points on the data lines
265  */
lv_chart_set_point_count(lv_obj_t * chart,uint16_t point_cnt)266 void lv_chart_set_point_count(lv_obj_t * chart, uint16_t point_cnt)
267 {
268     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
269     if(ext->point_cnt == point_cnt) return;
270 
271     lv_chart_series_t * ser;
272     uint16_t point_cnt_old = ext->point_cnt;
273     uint16_t i;
274     lv_coord_t def = LV_CHART_POINT_DEF;
275 
276     if(point_cnt < 1) point_cnt = 1;
277 
278     LV_LL_READ_BACK(ext->series_ll, ser)
279     {
280         if(ser->start_point != 0) {
281             lv_coord_t * new_points = lv_mem_alloc(sizeof(lv_coord_t) * point_cnt);
282             lv_mem_assert(new_points);
283             if(new_points == NULL) return;
284 
285             if(point_cnt >= point_cnt_old) {
286                 for(i = 0; i < point_cnt_old; i++) {
287                     new_points[i] =
288                         ser->points[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
289                 }
290                 for(i = point_cnt_old; i < point_cnt; i++) {
291                     new_points[i] = def; /*Fill up the rest with default value*/
292                 }
293             } else {
294                 for(i = 0; i < point_cnt; i++) {
295                     new_points[i] =
296                         ser->points[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
297                 }
298             }
299 
300             /*Switch over pointer from old to new*/
301             lv_mem_free(ser->points);
302             ser->points = new_points;
303         } else {
304             ser->points = lv_mem_realloc(ser->points, sizeof(lv_coord_t) * point_cnt);
305             lv_mem_assert(ser->points);
306             if(ser->points == NULL) return;
307             /*Initialize the new points*/
308             if(point_cnt > point_cnt_old) {
309                 for(i = point_cnt_old - 1; i < point_cnt; i++) {
310                     ser->points[i] = def;
311                 }
312             }
313         }
314 
315         ser->start_point = 0;
316     }
317 
318     ext->point_cnt = point_cnt;
319 
320     lv_chart_refresh(chart);
321 }
322 
323 /**
324  * Set the opacity of the data series
325  * @param chart pointer to a chart object
326  * @param opa opacity of the data series
327  */
lv_chart_set_series_opa(lv_obj_t * chart,lv_opa_t opa)328 void lv_chart_set_series_opa(lv_obj_t * chart, lv_opa_t opa)
329 {
330     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
331     if(ext->series.opa == opa) return;
332 
333     ext->series.opa = opa;
334     lv_obj_invalidate(chart);
335 }
336 
337 /**
338  * Set the line width or point radius of the data series
339  * @param chart pointer to a chart object
340  * @param width the new width
341  */
lv_chart_set_series_width(lv_obj_t * chart,lv_coord_t width)342 void lv_chart_set_series_width(lv_obj_t * chart, lv_coord_t width)
343 {
344     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
345     if(ext->series.width == width) return;
346 
347     ext->series.width = width;
348     lv_obj_invalidate(chart);
349 }
350 /**
351  * Set the dark effect on the bottom of the points or columns
352  * @param chart pointer to a chart object
353  * @param dark_eff dark effect level (LV_OPA_TRANSP to turn off)
354  */
lv_chart_set_series_darking(lv_obj_t * chart,lv_opa_t dark_eff)355 void lv_chart_set_series_darking(lv_obj_t * chart, lv_opa_t dark_eff)
356 {
357     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
358     if(ext->series.dark == dark_eff) return;
359 
360     ext->series.dark = dark_eff;
361     lv_obj_invalidate(chart);
362 }
363 
364 /**
365  * Initialize all data points with a value
366  * @param chart pointer to chart object
367  * @param ser pointer to a data series on 'chart'
368  * @param y the new value  for all points
369  */
lv_chart_init_points(lv_obj_t * chart,lv_chart_series_t * ser,lv_coord_t y)370 void lv_chart_init_points(lv_obj_t * chart, lv_chart_series_t * ser, lv_coord_t y)
371 {
372     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
373     uint16_t i;
374     for(i = 0; i < ext->point_cnt; i++) {
375         ser->points[i] = y;
376     }
377     ser->start_point = 0;
378     lv_chart_refresh(chart);
379 }
380 
381 /**
382  * Set the value of points from an array
383  * @param chart pointer to chart object
384  * @param ser pointer to a data series on 'chart'
385  * @param y_array array of 'lv_coord_t' points (with 'points count' elements )
386  */
lv_chart_set_points(lv_obj_t * chart,lv_chart_series_t * ser,lv_coord_t y_array[])387 void lv_chart_set_points(lv_obj_t * chart, lv_chart_series_t * ser, lv_coord_t y_array[])
388 {
389     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
390     memcpy(ser->points, y_array, ext->point_cnt * (sizeof(lv_coord_t)));
391     ser->start_point = 0;
392     lv_chart_refresh(chart);
393 }
394 
395 /**
396  * Shift all data left and set the rightmost data on a data line
397  * @param chart pointer to chart object
398  * @param ser pointer to a data series on 'chart'
399  * @param y the new value of the rightmost data
400  */
lv_chart_set_next(lv_obj_t * chart,lv_chart_series_t * ser,lv_coord_t y)401 void lv_chart_set_next(lv_obj_t * chart, lv_chart_series_t * ser, lv_coord_t y)
402 {
403     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
404     if(ext->update_mode == LV_CHART_UPDATE_MODE_SHIFT) {
405         ser->points[ser->start_point] =
406             y; /*This was the place of the former left most value, after shifting it is the rightmost*/
407         ser->start_point = (ser->start_point + 1) % ext->point_cnt;
408         lv_chart_refresh(chart);
409     } else if(ext->update_mode == LV_CHART_UPDATE_MODE_CIRCULAR) {
410         ser->points[ser->start_point] = y;
411 
412         if(ext->type & LV_CHART_TYPE_LINE) lv_chart_inv_lines(chart, ser->start_point);
413         if(ext->type & LV_CHART_TYPE_COLUMN) lv_chart_inv_cols(chart, ser->start_point);
414         if(ext->type & LV_CHART_TYPE_POINT) lv_chart_inv_points(chart, ser->start_point);
415         if(ext->type & LV_CHART_TYPE_VERTICAL_LINE) lv_chart_inv_lines(chart, ser->start_point);
416         if(ext->type & LV_CHART_TYPE_AREA) lv_chart_inv_lines(chart, ser->start_point);
417 
418         ser->start_point = (ser->start_point + 1) % ext->point_cnt; /*update the x for next incoming y*/
419     }
420 }
421 
422 /**
423  * Set update mode of the chart object.
424  * @param chart pointer to a chart object
425  * @param update mode
426  */
lv_chart_set_update_mode(lv_obj_t * chart,lv_chart_update_mode_t update_mode)427 void lv_chart_set_update_mode(lv_obj_t * chart, lv_chart_update_mode_t update_mode)
428 {
429     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
430     if(ext->update_mode == update_mode) return;
431 
432     ext->update_mode = update_mode;
433     lv_obj_invalidate(chart);
434 }
435 
436 /**
437  * Set the length of the tick marks on the x axis
438  * @param chart pointer to the chart
439  * @param major_tick_len the length of the major tick or `LV_CHART_TICK_LENGTH_AUTO` to set automatically
440  *                       (where labels are added)
441  * @param minor_tick_len the length of the minor tick, `LV_CHART_TICK_LENGTH_AUTO` to set automatically
442  *                       (where no labels are added)
443  */
lv_chart_set_x_tick_length(lv_obj_t * chart,uint8_t major_tick_len,uint8_t minor_tick_len)444 void lv_chart_set_x_tick_length(lv_obj_t * chart, uint8_t major_tick_len, uint8_t minor_tick_len)
445 {
446     lv_chart_ext_t * ext       = lv_obj_get_ext_attr(chart);
447     ext->x_axis.major_tick_len = major_tick_len;
448     ext->x_axis.minor_tick_len = minor_tick_len;
449 }
450 
451 /**
452  * Set the length of the tick marks on the y axis
453  * @param chart pointer to the chart
454  * @param major_tick_len the length of the major tick or `LV_CHART_TICK_LENGTH_AUTO` to set automatically
455  *                       (where labels are added)
456  * @param minor_tick_len the length of the minor tick, `LV_CHART_TICK_LENGTH_AUTO` to set automatically
457  *                       (where no labels are added)
458  */
lv_chart_set_y_tick_length(lv_obj_t * chart,uint8_t major_tick_len,uint8_t minor_tick_len)459 void lv_chart_set_y_tick_length(lv_obj_t * chart, uint8_t major_tick_len, uint8_t minor_tick_len)
460 {
461     lv_chart_ext_t * ext       = lv_obj_get_ext_attr(chart);
462     ext->y_axis.major_tick_len = major_tick_len;
463     ext->y_axis.minor_tick_len = minor_tick_len;
464 }
465 
466 /**
467  * Set the x-axis tick count and labels of a chart
468  * @param chart 			pointer to a chart object
469  * @param list_of_values 	list of string values, terminated with \n, except the last
470  * @param num_tick_marks 	if list_of_values is NULL: total number of ticks per axis
471  * 							else number of ticks between two value labels
472  * @param options			extra options
473  */
lv_chart_set_x_tick_texts(lv_obj_t * chart,const char * list_of_values,uint8_t num_tick_marks,lv_chart_axis_options_t options)474 void lv_chart_set_x_tick_texts(lv_obj_t * chart, const char * list_of_values, uint8_t num_tick_marks,
475                                lv_chart_axis_options_t options)
476 {
477     lv_chart_ext_t * ext       = lv_obj_get_ext_attr(chart);
478     ext->x_axis.num_tick_marks = num_tick_marks;
479     ext->x_axis.list_of_values = list_of_values;
480     ext->x_axis.options        = options;
481 }
482 
483 /**
484  * Set the y-axis tick count and labels of a chart
485  * @param chart             pointer to a chart object
486  * @param list_of_values    list of string values, terminated with \n, except the last
487  * @param num_tick_marks    if list_of_values is NULL: total number of ticks per axis
488  *                          else number of ticks between two value labels
489  * @param options           extra options
490  */
lv_chart_set_y_tick_texts(lv_obj_t * chart,const char * list_of_values,uint8_t num_tick_marks,lv_chart_axis_options_t options)491 void lv_chart_set_y_tick_texts(lv_obj_t * chart, const char * list_of_values, uint8_t num_tick_marks,
492                                lv_chart_axis_options_t options)
493 {
494     lv_chart_ext_t * ext       = lv_obj_get_ext_attr(chart);
495     ext->y_axis.num_tick_marks = num_tick_marks;
496     ext->y_axis.list_of_values = list_of_values;
497     ext->y_axis.options        = options;
498 }
499 
500 /**
501  * Set the margin around the chart, used for axes value and ticks
502  * @param chart     pointer to an chart object
503  * @param margin    value of the margin [px]
504  */
lv_chart_set_margin(lv_obj_t * chart,uint16_t margin)505 void lv_chart_set_margin(lv_obj_t * chart, uint16_t margin)
506 {
507     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
508     ext->margin          = margin;
509     lv_obj_refresh_ext_draw_pad(chart);
510 }
511 
512 /*=====================
513  * Getter functions
514  *====================*/
515 
516 /**
517  * Get the type of a chart
518  * @param chart pointer to chart object
519  * @return type of the chart (from 'lv_chart_t' enum)
520  */
lv_chart_get_type(const lv_obj_t * chart)521 lv_chart_type_t lv_chart_get_type(const lv_obj_t * chart)
522 {
523     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
524     return ext->type;
525 }
526 
527 /**
528  * Get the data point number per data line on chart
529  * @param chart pointer to chart object
530  * @return point number on each data line
531  */
lv_chart_get_point_cnt(const lv_obj_t * chart)532 uint16_t lv_chart_get_point_cnt(const lv_obj_t * chart)
533 {
534     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
535     return ext->point_cnt;
536 }
537 
538 /**
539  * Get the opacity of the data series
540  * @param chart pointer to chart object
541  * @return the opacity of the data series
542  */
lv_chart_get_series_opa(const lv_obj_t * chart)543 lv_opa_t lv_chart_get_series_opa(const lv_obj_t * chart)
544 {
545     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
546     return ext->series.opa;
547 }
548 
549 /**
550  * Get the data series width
551  * @param chart pointer to chart object
552  * @return the width the data series (lines or points)
553  */
lv_chart_get_series_width(const lv_obj_t * chart)554 lv_coord_t lv_chart_get_series_width(const lv_obj_t * chart)
555 {
556     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
557     return ext->series.width;
558 }
559 
560 /**
561  * Get the dark effect level on the bottom of the points or columns
562  * @param chart pointer to chart object
563  * @return dark effect level (LV_OPA_TRANSP to turn off)
564  */
lv_chart_get_series_darking(const lv_obj_t * chart)565 lv_opa_t lv_chart_get_series_darking(const lv_obj_t * chart)
566 {
567     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
568     return ext->series.dark;
569 }
570 
571 /*=====================
572  * Other functions
573  *====================*/
574 
575 /**
576  * Refresh a chart if its data line has changed
577  * @param chart pointer to chart object
578  */
lv_chart_refresh(lv_obj_t * chart)579 void lv_chart_refresh(lv_obj_t * chart)
580 {
581     lv_obj_invalidate(chart);
582 }
583 
584 /**
585  * Get the margin around the chart, used for axes value and labels
586  * @param chart pointer to an chart object
587  * @param return value of the margin
588  */
lv_chart_get_margin(lv_obj_t * chart)589 uint16_t lv_chart_get_margin(lv_obj_t * chart)
590 {
591     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
592     return ext->margin;
593 }
594 
595 /**********************
596  *   STATIC FUNCTIONS
597  **********************/
598 
599 /**
600  * Handle the drawing related tasks of the chart backgrounds
601  * @param chart pointer to an object
602  * @param mask the object will be drawn only in this area
603  * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
604  *                                  (return 'true' if yes)
605  *             LV_DESIGN_DRAW: draw the object (always return 'true')
606  *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
607  * @param return true/false, depends on 'mode'
608  */
lv_chart_design(lv_obj_t * chart,const lv_area_t * mask,lv_design_mode_t mode)609 static bool lv_chart_design(lv_obj_t * chart, const lv_area_t * mask, lv_design_mode_t mode)
610 {
611     if(mode == LV_DESIGN_COVER_CHK) {
612         /*Return false if the object is not covers the mask_p area*/
613         return ancestor_design_f(chart, mask, mode);
614     } else if(mode == LV_DESIGN_DRAW_MAIN) {
615         /*Draw the background*/
616         lv_draw_rect(&chart->coords, mask, lv_obj_get_style(chart), lv_obj_get_opa_scale(chart));
617 
618         lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
619 
620         lv_chart_draw_div(chart, mask);
621 
622         /* Adjust the mask to remove the margin (clips chart contents to be within background) */
623 
624         lv_area_t mask_tmp, adjusted_mask;
625         lv_obj_get_coords(chart, &mask_tmp);
626 
627         bool union_ok = lv_area_intersect(&adjusted_mask, mask, &mask_tmp);
628 
629         if(union_ok) {
630                 if(ext->type & LV_CHART_TYPE_LINE) lv_chart_draw_lines(chart, &adjusted_mask);
631                 if(ext->type & LV_CHART_TYPE_COLUMN) lv_chart_draw_cols(chart, &adjusted_mask);
632                 if(ext->type & LV_CHART_TYPE_POINT) lv_chart_draw_points(chart, &adjusted_mask);
633                 if(ext->type & LV_CHART_TYPE_VERTICAL_LINE) lv_chart_draw_vertical_lines(chart, &adjusted_mask);
634                 if(ext->type & LV_CHART_TYPE_AREA) lv_chart_draw_areas(chart, &adjusted_mask);
635         }
636 
637         lv_chart_draw_axes(chart, mask);
638     }
639     return true;
640 }
641 
642 /**
643  * Signal function of the chart background
644  * @param chart pointer to a chart background object
645  * @param sign a signal type from lv_signal_t enum
646  * @param param pointer to a signal specific variable
647  */
lv_chart_signal(lv_obj_t * chart,lv_signal_t sign,void * param)648 static lv_res_t lv_chart_signal(lv_obj_t * chart, lv_signal_t sign, void * param)
649 {
650     lv_res_t res;
651     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
652 
653     /* Include the ancient signal function */
654     res = ancestor_signal(chart, sign, param);
655     if(res != LV_RES_OK) return res;
656 
657     if(sign == LV_SIGNAL_CLEANUP) {
658         lv_coord_t ** datal;
659         LV_LL_READ(ext->series_ll, datal)
660         {
661             lv_mem_free(*datal);
662         }
663         lv_ll_clear(&ext->series_ll);
664     } else if(sign == LV_SIGNAL_GET_TYPE) {
665         lv_obj_type_t * buf = param;
666         uint8_t i;
667         for(i = 0; i < LV_MAX_ANCESTOR_NUM - 1; i++) { /*Find the last set data*/
668             if(buf->type[i] == NULL) break;
669         }
670         buf->type[i] = "lv_chart";
671     } else if(sign == LV_SIGNAL_REFR_EXT_DRAW_PAD) {
672         /*Provide extra px draw area around the chart*/
673         chart->ext_draw_pad = ext->margin;
674     }
675 
676     return res;
677 }
678 
679 /**
680  * Draw the division lines on chart background
681  * @param chart pointer to chart object
682  * @param mask mask, inherited from the design function
683  */
lv_chart_draw_div(lv_obj_t * chart,const lv_area_t * mask)684 static void lv_chart_draw_div(lv_obj_t * chart, const lv_area_t * mask)
685 {
686     lv_chart_ext_t * ext     = lv_obj_get_ext_attr(chart);
687     const lv_style_t * style = lv_obj_get_style(chart);
688     lv_opa_t opa_scale       = lv_obj_get_opa_scale(chart);
689 
690     uint8_t div_i;
691     uint8_t div_i_end;
692     uint8_t div_i_start;
693     lv_point_t p1;
694     lv_point_t p2;
695     lv_coord_t w     = lv_obj_get_width(chart);
696     lv_coord_t h     = lv_obj_get_height(chart);
697     lv_coord_t x_ofs = chart->coords.x1;
698     lv_coord_t y_ofs = chart->coords.y1;
699 
700     if(ext->hdiv_cnt != 0) {
701         /*Draw side lines if no border*/
702         if(style->body.border.width != 0) {
703             div_i_start = 1;
704             div_i_end   = ext->hdiv_cnt;
705         } else {
706             div_i_start = 0;
707             div_i_end   = ext->hdiv_cnt + 1;
708         }
709 
710         p1.x = 0 + x_ofs;
711         p2.x = w + x_ofs;
712         for(div_i = div_i_start; div_i <= div_i_end; div_i++) {
713             p1.y = (int32_t)((int32_t)(h - style->line.width) * div_i) / (ext->hdiv_cnt + 1);
714             p1.y += y_ofs;
715             p2.y = p1.y;
716             lv_draw_line(&p1, &p2, mask, style, opa_scale);
717         }
718     }
719 
720     if(ext->vdiv_cnt != 0) {
721         /*Draw side lines if no border*/
722         if(style->body.border.width != 0) {
723             div_i_start = 1;
724             div_i_end   = ext->vdiv_cnt;
725         } else {
726             div_i_start = 0;
727             div_i_end   = ext->vdiv_cnt + 1;
728         }
729 
730         p1.y = 0 + y_ofs;
731         p2.y = h + y_ofs;
732         for(div_i = div_i_start; div_i <= div_i_end; div_i++) {
733             p1.x = (int32_t)((int32_t)(w - style->line.width) * div_i) / (ext->vdiv_cnt + 1);
734             p1.x += x_ofs;
735             p2.x = p1.x;
736             lv_draw_line(&p1, &p2, mask, style, opa_scale);
737         }
738     }
739 }
740 
741 /**
742  * Draw the data lines as lines on a chart
743  * @param obj pointer to chart object
744  */
lv_chart_draw_lines(lv_obj_t * chart,const lv_area_t * mask)745 static void lv_chart_draw_lines(lv_obj_t * chart, const lv_area_t * mask)
746 {
747     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
748 
749     uint16_t i;
750     lv_point_t p1;
751     lv_point_t p2;
752     lv_coord_t w     = lv_obj_get_width(chart);
753     lv_coord_t h     = lv_obj_get_height(chart);
754     lv_coord_t x_ofs = chart->coords.x1;
755     lv_coord_t y_ofs = chart->coords.y1;
756     int32_t y_tmp;
757     lv_coord_t p_prev;
758     lv_coord_t p_act;
759     lv_chart_series_t * ser;
760     lv_opa_t opa_scale = lv_obj_get_opa_scale(chart);
761     lv_style_t style;
762     lv_style_copy(&style, &lv_style_plain);
763     style.line.opa   = ext->series.opa;
764     style.line.width = ext->series.width;
765 
766     /*Go through all data lines*/
767     LV_LL_READ_BACK(ext->series_ll, ser)
768     {
769         style.line.color = ser->color;
770 
771         lv_coord_t start_point = ext->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
772 
773         p1.x = 0 + x_ofs;
774         p2.x = 0 + x_ofs;
775 
776         p_prev = start_point;
777         y_tmp  = (int32_t)((int32_t)ser->points[p_prev] - ext->ymin) * h;
778         y_tmp  = y_tmp / (ext->ymax - ext->ymin);
779         p2.y   = h - y_tmp + y_ofs;
780 
781         for(i = 1; i < ext->point_cnt; i++) {
782             p1.x = p2.x;
783             p1.y = p2.y;
784 
785             p2.x = ((w * i) / (ext->point_cnt - 1)) + x_ofs;
786 
787             p_act = (start_point + i) % ext->point_cnt;
788 
789             y_tmp = (int32_t)((int32_t)ser->points[p_act] - ext->ymin) * h;
790             y_tmp = y_tmp / (ext->ymax - ext->ymin);
791             p2.y  = h - y_tmp + y_ofs;
792 
793             if(ser->points[p_prev] != LV_CHART_POINT_DEF && ser->points[p_act] != LV_CHART_POINT_DEF)
794                 lv_draw_line(&p1, &p2, mask, &style, opa_scale);
795 
796             p_prev = p_act;
797         }
798     }
799 }
800 
801 /**
802  * Draw the data lines as points on a chart
803  * @param chart pointer to chart object
804  * @param mask mask, inherited from the design function
805  */
lv_chart_draw_points(lv_obj_t * chart,const lv_area_t * mask)806 static void lv_chart_draw_points(lv_obj_t * chart, const lv_area_t * mask)
807 {
808     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
809 
810     uint16_t i;
811     lv_area_t cir_a;
812     lv_coord_t w     = lv_obj_get_width(chart);
813     lv_coord_t h     = lv_obj_get_height(chart);
814     lv_coord_t x_ofs = chart->coords.x1;
815     lv_coord_t y_ofs = chart->coords.y1;
816     int32_t y_tmp;
817     lv_coord_t p_act;
818     lv_chart_series_t * ser;
819     uint8_t series_cnt = 0;
820     lv_style_t style_point;
821     lv_style_copy(&style_point, &lv_style_plain);
822 
823     style_point.body.border.width = 0;
824     style_point.body.radius       = LV_RADIUS_CIRCLE;
825     style_point.body.opa          = ext->series.opa;
826     style_point.body.radius       = ext->series.width;
827 
828     /*Go through all data lines*/
829 
830     LV_LL_READ_BACK(ext->series_ll, ser)
831     {
832         lv_coord_t start_point = ext->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
833 
834         style_point.body.main_color = ser->color;
835         style_point.body.grad_color = lv_color_mix(LV_COLOR_BLACK, ser->color, ext->series.dark);
836 
837         for(i = 0; i < ext->point_cnt; i++) {
838             cir_a.x1 = ((w * i) / (ext->point_cnt - 1)) + x_ofs;
839             cir_a.x2 = cir_a.x1 + style_point.body.radius;
840             cir_a.x1 -= style_point.body.radius;
841 
842             p_act = (start_point + i) % ext->point_cnt;
843             y_tmp = (int32_t)((int32_t)ser->points[p_act] - ext->ymin) * h;
844             y_tmp = y_tmp / (ext->ymax - ext->ymin);
845 
846             cir_a.y1 = h - y_tmp + y_ofs;
847             cir_a.y2 = cir_a.y1 + style_point.body.radius;
848             cir_a.y1 -= style_point.body.radius;
849 
850             if(ser->points[p_act] != LV_CHART_POINT_DEF)
851                 lv_draw_rect(&cir_a, mask, &style_point, lv_obj_get_opa_scale(chart));
852         }
853         series_cnt++;
854     }
855 }
856 
857 /**
858  * Draw the data lines as columns on a chart
859  * @param chart pointer to chart object
860  * @param mask mask, inherited from the design function
861  */
lv_chart_draw_cols(lv_obj_t * chart,const lv_area_t * mask)862 static void lv_chart_draw_cols(lv_obj_t * chart, const lv_area_t * mask)
863 {
864     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
865 
866     uint16_t i;
867     lv_area_t col_a;
868     lv_area_t col_mask;
869     bool mask_ret;
870     lv_coord_t w = lv_obj_get_width(chart);
871     lv_coord_t h = lv_obj_get_height(chart);
872     int32_t y_tmp;
873     lv_chart_series_t * ser;
874     lv_style_t rects;
875     lv_coord_t col_w = w / ((ext->series.num + 1) * ext->point_cnt); /* Suppose + 1 series as separator*/
876     lv_coord_t x_ofs = col_w / 2;                                    /*Shift with a half col.*/
877 
878     lv_style_copy(&rects, &lv_style_plain);
879     rects.body.border.width = 0;
880     rects.body.radius       = 0;
881     rects.body.opa          = ext->series.opa;
882 
883     col_a.y2 = chart->coords.y2;
884 
885     lv_coord_t x_act;
886 
887     /*Go through all points*/
888     for(i = 0; i < ext->point_cnt; i++) {
889         x_act = (int32_t)((int32_t)w * i) / ext->point_cnt;
890         x_act += chart->coords.x1 + x_ofs;
891 
892         /*Draw the current point of all data line*/
893         LV_LL_READ_BACK(ext->series_ll, ser)
894         {
895             lv_coord_t start_point = ext->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
896 
897             col_a.x1 = x_act;
898             col_a.x2 = col_a.x1 + col_w;
899             x_act += col_w;
900 
901             if(col_a.x2 < mask->x1) continue;
902             if(col_a.x1 > mask->x2) break;
903 
904             rects.body.main_color = ser->color;
905             rects.body.grad_color = lv_color_mix(LV_COLOR_BLACK, ser->color, ext->series.dark);
906 
907             lv_coord_t p_act = (start_point + i) % ext->point_cnt;
908             y_tmp            = (int32_t)((int32_t)ser->points[p_act] - ext->ymin) * h;
909             y_tmp            = y_tmp / (ext->ymax - ext->ymin);
910             col_a.y1         = h - y_tmp + chart->coords.y1;
911 
912             mask_ret = lv_area_intersect(&col_mask, mask, &col_a);
913             if(mask_ret != false && ser->points[p_act] != LV_CHART_POINT_DEF) {
914                 lv_draw_rect(&chart->coords, &col_mask, &rects, lv_obj_get_opa_scale(chart));
915             }
916         }
917     }
918 }
919 
920 /**
921  * Draw the data lines as vertical lines on a chart if there is only 1px between point
922  * @param obj pointer to chart object
923  */
lv_chart_draw_vertical_lines(lv_obj_t * chart,const lv_area_t * mask)924 static void lv_chart_draw_vertical_lines(lv_obj_t * chart, const lv_area_t * mask)
925 {
926 
927     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
928     lv_coord_t w         = lv_obj_get_width(chart);
929     /*Vertical lines works only if the width == point count. Else use the normal line type*/
930     if(ext->point_cnt != w) {
931         lv_chart_draw_lines(chart, mask);
932         return;
933     }
934 
935     uint16_t i;
936     lv_point_t p1;
937     lv_point_t p2;
938     lv_coord_t p_act;
939     lv_coord_t h     = lv_obj_get_height(chart);
940     lv_coord_t x_ofs = chart->coords.x1;
941     lv_coord_t y_ofs = chart->coords.y1;
942     int32_t y_tmp;
943     lv_chart_series_t * ser;
944     lv_opa_t opa_scale = lv_obj_get_opa_scale(chart);
945     lv_style_t style;
946     lv_style_copy(&style, &lv_style_plain);
947     style.line.opa   = ext->series.opa;
948     style.line.width = ext->series.width;
949 
950     /*Go through all data lines*/
951     LV_LL_READ_BACK(ext->series_ll, ser)
952     {
953         lv_coord_t start_point = ext->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
954         style.line.color       = ser->color;
955 
956         p1.x  = 0 + x_ofs;
957         p2.x  = 0 + x_ofs;
958         y_tmp = (int32_t)((int32_t)ser->points[0] - ext->ymin) * h;
959         y_tmp = y_tmp / (ext->ymax - ext->ymin);
960         p2.y  = h - y_tmp + y_ofs;
961         p1.y  = p2.y;
962 
963         for(i = 0; i < ext->point_cnt; i++) {
964             p_act = (start_point + i) % ext->point_cnt;
965 
966             y_tmp = (int32_t)((int32_t)ser->points[p_act] - ext->ymin) * h;
967             y_tmp = y_tmp / (ext->ymax - ext->ymin);
968             p2.y  = h - y_tmp + y_ofs;
969 
970             if(p1.y == p2.y) {
971                 p2.x++;
972             }
973 
974             if(ser->points[p_act] != LV_CHART_POINT_DEF) {
975                 lv_draw_line(&p1, &p2, mask, &style, opa_scale);
976             }
977 
978             p2.x = ((w * p_act) / (ext->point_cnt - 1)) + x_ofs;
979             p1.x = p2.x;
980             p1.y = p2.y;
981         }
982     }
983 }
984 
985 /**
986  * Draw the data lines as areas on a chart
987  * @param obj pointer to chart object
988  */
lv_chart_draw_areas(lv_obj_t * chart,const lv_area_t * mask)989 static void lv_chart_draw_areas(lv_obj_t * chart, const lv_area_t * mask)
990 {
991     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
992 
993     uint16_t i;
994     lv_point_t p1;
995     lv_point_t p2;
996     lv_coord_t w     = lv_obj_get_width(chart);
997     lv_coord_t h     = lv_obj_get_height(chart);
998     lv_coord_t x_ofs = chart->coords.x1;
999     lv_coord_t y_ofs = chart->coords.y1;
1000     int32_t y_tmp;
1001     lv_coord_t p_prev;
1002     lv_coord_t p_act;
1003     lv_chart_series_t * ser;
1004     lv_opa_t opa_scale = lv_obj_get_opa_scale(chart);
1005     lv_style_t style;
1006     lv_style_copy(&style, &lv_style_plain);
1007 
1008     /*Go through all data lines*/
1009     LV_LL_READ_BACK(ext->series_ll, ser)
1010     {
1011         lv_coord_t start_point = ext->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
1012         style.body.main_color  = ser->color;
1013         style.body.opa         = ext->series.opa;
1014 
1015         p2.x = 0 + x_ofs;
1016 
1017         p_prev = start_point;
1018         y_tmp  = (int32_t)((int32_t)ser->points[p_prev] - ext->ymin) * h;
1019         y_tmp  = y_tmp / (ext->ymax - ext->ymin);
1020         p2.y   = h - y_tmp + y_ofs;
1021 
1022         for(i = 1; i < ext->point_cnt; i++) {
1023             p1.x = p2.x;
1024             p1.y = p2.y;
1025 
1026             p_act = (start_point + i) % ext->point_cnt;
1027             p2.x  = ((w * i) / (ext->point_cnt - 1)) + x_ofs;
1028 
1029             y_tmp = (int32_t)((int32_t)ser->points[p_act] - ext->ymin) * h;
1030             y_tmp = y_tmp / (ext->ymax - ext->ymin);
1031             p2.y  = h - y_tmp + y_ofs;
1032 
1033             if(ser->points[p_prev] != LV_CHART_POINT_DEF && ser->points[p_act] != LV_CHART_POINT_DEF) {
1034                 lv_point_t triangle_points[3];
1035                 triangle_points[0]   = p1;
1036                 triangle_points[1]   = p2;
1037                 triangle_points[2].x = p1.x;
1038                 triangle_points[2].y = chart->coords.y2;
1039                 lv_draw_triangle(triangle_points, mask, &style, opa_scale);
1040                 triangle_points[2].x = p2.x;
1041                 triangle_points[0].y = chart->coords.y2;
1042                 lv_draw_triangle(triangle_points, mask, &style, opa_scale);
1043             }
1044             p_prev = p_act;
1045         }
1046     }
1047 }
1048 
lv_chart_draw_y_ticks(lv_obj_t * chart,const lv_area_t * mask)1049 static void lv_chart_draw_y_ticks(lv_obj_t * chart, const lv_area_t * mask)
1050 {
1051     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
1052 
1053     if(ext->y_axis.list_of_values != NULL || ext->y_axis.num_tick_marks != 0) {
1054 
1055         const lv_style_t * style = lv_obj_get_style(chart);
1056         lv_opa_t opa_scale       = lv_obj_get_opa_scale(chart);
1057 
1058         uint8_t i, j;
1059         uint8_t list_index;
1060         uint8_t num_of_labels;
1061         uint8_t num_scale_ticks;
1062         uint8_t major_tick_len, minor_tick_len;
1063         lv_point_t p1;
1064         lv_point_t p2;
1065         lv_coord_t x_ofs = chart->coords.x1;
1066         lv_coord_t y_ofs = chart->coords.y1;
1067         lv_coord_t h     = lv_obj_get_height(chart);
1068         lv_coord_t w     = lv_obj_get_width(chart);
1069         char buf[LV_CHART_AXIS_TICK_LABEL_MAX_LEN + 1]; /* up to N symbols per label + null terminator */
1070 
1071         /* calculate the size of tick marks */
1072         if(ext->y_axis.major_tick_len == LV_CHART_TICK_LENGTH_AUTO)
1073             major_tick_len = (int32_t)w * LV_CHART_AXIS_MAJOR_TICK_LEN_COE;
1074         else
1075             major_tick_len = ext->y_axis.major_tick_len;
1076 
1077         if(ext->y_axis.minor_tick_len == LV_CHART_TICK_LENGTH_AUTO)
1078             minor_tick_len = major_tick_len * LV_CHART_AXIS_MINOR_TICK_LEN_COE;
1079         else
1080             minor_tick_len = ext->y_axis.minor_tick_len;
1081 
1082         /* count the '\n'-s to determine the number of options */
1083         list_index    = 0;
1084         num_of_labels = 0;
1085         if(ext->y_axis.list_of_values != NULL) {
1086             for(j = 0; ext->y_axis.list_of_values[j] != '\0'; j++) {
1087                 if(ext->y_axis.list_of_values[j] == '\n') num_of_labels++;
1088             }
1089 
1090             num_of_labels++; /* last option in the at row*/
1091         }
1092 
1093         /* we can't have string labels without ticks step, set to 1 if not specified */
1094         if(ext->y_axis.num_tick_marks == 0) ext->y_axis.num_tick_marks = 1;
1095 
1096         /* calculate total number of ticks */
1097         if(num_of_labels < 2)
1098             num_scale_ticks = ext->y_axis.num_tick_marks;
1099         else
1100             num_scale_ticks = (ext->y_axis.num_tick_marks * (num_of_labels - 1));
1101 
1102         for(i = 0; i < (num_scale_ticks + 1); i++) { /* one extra loop - it may not exist in the list, empty label */
1103                                                      /* first point of the tick */
1104             p1.x = x_ofs - 1;
1105 
1106             /* second point of the tick */
1107             if((num_of_labels != 0) && (i == 0 || i % ext->y_axis.num_tick_marks == 0))
1108                 p2.x = p1.x - major_tick_len; /* major tick */
1109             else
1110                 p2.x = p1.x - minor_tick_len; /* minor tick */
1111 
1112             /* draw a line at moving y position */
1113             p2.y = p1.y =
1114                 y_ofs + (int32_t)((int32_t)(h - style->line.width) * i) / num_scale_ticks;
1115 
1116             if(i != num_scale_ticks)
1117                 lv_draw_line(&p1, &p2, mask, style, opa_scale);
1118             else if((ext->y_axis.options & LV_CHART_AXIS_DRAW_LAST_TICK) != 0)
1119                 lv_draw_line(&p1, &p2, mask, style, opa_scale);
1120 
1121             /* draw values if available */
1122             if(num_of_labels != 0) {
1123                 /* add text only to major tick */
1124                 if(i == 0 || i % ext->y_axis.num_tick_marks == 0) {
1125                     /* search for tick string */
1126                     j = 0;
1127                     while(ext->y_axis.list_of_values[list_index] != '\n' &&
1128                           ext->y_axis.list_of_values[list_index] != '\0') {
1129                         /* do not overflow the buffer, but move to the end of the current label */
1130                         if(j < LV_CHART_AXIS_TICK_LABEL_MAX_LEN)
1131                             buf[j++] = ext->y_axis.list_of_values[list_index++];
1132                         else
1133                             list_index++;
1134                     }
1135 
1136                     /* this was a string, but not end of the list, so jump to the next string */
1137                     if(ext->y_axis.list_of_values[list_index] == '\n') list_index++;
1138 
1139                     /* terminate the string */
1140                     buf[j] = '\0';
1141 
1142                     /* reserve appropriate area */
1143                     lv_point_t size;
1144                     lv_txt_get_size(&size, buf, style->text.font, style->text.letter_space, style->text.line_space,
1145                                     LV_COORD_MAX, LV_TXT_FLAG_CENTER);
1146 
1147                     /* set the area at some distance of the major tick len left of the tick */
1148                     lv_area_t a = {(p2.x - size.x - LV_CHART_AXIS_TO_LABEL_DISTANCE), (p2.y - size.y / 2),
1149                                    (p2.x - LV_CHART_AXIS_TO_LABEL_DISTANCE), (p2.y + size.y / 2)};
1150                     lv_draw_label(&a, mask, style, opa_scale, buf, LV_TXT_FLAG_CENTER, NULL, -1, -1, NULL);
1151                 }
1152             }
1153 
1154         }
1155     }
1156 }
1157 
lv_chart_draw_x_ticks(lv_obj_t * chart,const lv_area_t * mask)1158 static void lv_chart_draw_x_ticks(lv_obj_t * chart, const lv_area_t * mask)
1159 {
1160     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
1161 
1162     if(ext->x_axis.list_of_values != NULL || ext->x_axis.num_tick_marks != 0) {
1163 
1164         const lv_style_t * style = lv_obj_get_style(chart);
1165         lv_opa_t opa_scale       = lv_obj_get_opa_scale(chart);
1166 
1167         uint8_t i, j;
1168         uint8_t list_index;
1169         uint8_t num_of_labels;
1170         uint8_t num_scale_ticks;
1171         uint8_t major_tick_len, minor_tick_len;
1172         lv_point_t p1;
1173         lv_point_t p2;
1174         lv_coord_t x_ofs = chart->coords.x1;
1175         lv_coord_t y_ofs = chart->coords.y1;
1176         lv_coord_t h     = lv_obj_get_height(chart);
1177         lv_coord_t w     = lv_obj_get_width(chart);
1178         char buf[LV_CHART_AXIS_TICK_LABEL_MAX_LEN + 1]; /* up to N symbols per label + null terminator */
1179 
1180         /* calculate the size of tick marks */
1181         if(ext->x_axis.major_tick_len == LV_CHART_TICK_LENGTH_AUTO)
1182             major_tick_len = (int32_t)w * LV_CHART_AXIS_MAJOR_TICK_LEN_COE;
1183         else
1184             major_tick_len = ext->x_axis.major_tick_len;
1185 
1186         if(ext->x_axis.minor_tick_len == LV_CHART_TICK_LENGTH_AUTO)
1187             minor_tick_len = major_tick_len * LV_CHART_AXIS_MINOR_TICK_LEN_COE;
1188         else
1189             minor_tick_len = ext->x_axis.minor_tick_len;
1190 
1191         /* count the '\n'-s to determine the number of options */
1192         list_index    = 0;
1193         num_of_labels = 0;
1194         if(ext->x_axis.list_of_values != NULL) {
1195             for(j = 0; ext->x_axis.list_of_values[j] != '\0'; j++) {
1196                 if(ext->x_axis.list_of_values[j] == '\n') num_of_labels++;
1197             }
1198 
1199             num_of_labels++; /* last option in the at row*/
1200         }
1201 
1202         /* we can't have string labels without ticks step, set to 1 if not specified */
1203         if(ext->x_axis.num_tick_marks == 0) ext->x_axis.num_tick_marks = 1;
1204 
1205         /* calculate total number of marks */
1206         if(num_of_labels < 2)
1207             num_scale_ticks = ext->x_axis.num_tick_marks;
1208         else
1209             num_scale_ticks = (ext->x_axis.num_tick_marks * (num_of_labels - 1));
1210 
1211         for(i = 0; i < (num_scale_ticks + 1); i++) { /* one extra loop - it may not exist in the list, empty label */
1212                                                      /* first point of the tick */
1213             p1.y = h + y_ofs;
1214 
1215             /* second point of the tick */
1216             if((num_of_labels != 0) && (i == 0 || i % ext->x_axis.num_tick_marks == 0))
1217                 p2.y = p1.y + major_tick_len; /* major tick */
1218             else
1219                 p2.y = p1.y + minor_tick_len; /* minor tick */
1220 
1221             /* draw a line at moving x position */
1222             p2.x = p1.x = x_ofs + (int32_t)((int32_t)(w - style->line.width) * i) / num_scale_ticks;
1223 
1224             if(i != num_scale_ticks)
1225                 lv_draw_line(&p1, &p2, mask, style, opa_scale);
1226             else if((ext->x_axis.options & LV_CHART_AXIS_DRAW_LAST_TICK) != 0)
1227                 lv_draw_line(&p1, &p2, mask, style, opa_scale);
1228 
1229             /* draw values if available */
1230             if(num_of_labels != 0) {
1231                 /* add text only to major tick */
1232                 if(i == 0 || i % ext->x_axis.num_tick_marks == 0) {
1233                     /* search for tick string */
1234                     j = 0;
1235                     while(ext->x_axis.list_of_values[list_index] != '\n' &&
1236                           ext->x_axis.list_of_values[list_index] != '\0') {
1237                         /* do not overflow the buffer, but move to the end of the current label */
1238                         if(j < LV_CHART_AXIS_TICK_LABEL_MAX_LEN)
1239                             buf[j++] = ext->x_axis.list_of_values[list_index++];
1240                         else
1241                             list_index++;
1242                     }
1243 
1244                     /* this was a string, but not end of the list, so jump to the next string */
1245                     if(ext->x_axis.list_of_values[list_index] == '\n') list_index++;
1246 
1247                     /* terminate the string */
1248                     buf[j] = '\0';
1249 
1250                     /* reserve appropriate area */
1251                     lv_point_t size;
1252                     lv_txt_get_size(&size, buf, style->text.font, style->text.letter_space, style->text.line_space,
1253                                     LV_COORD_MAX, LV_TXT_FLAG_CENTER);
1254 
1255                     /* set the area at some distance of the major tick len under of the tick */
1256                     lv_area_t a = {(p2.x - size.x / 2), (p2.y + LV_CHART_AXIS_TO_LABEL_DISTANCE), (p2.x + size.x / 2),
1257                                    (p2.y + size.y + LV_CHART_AXIS_TO_LABEL_DISTANCE)};
1258                     lv_draw_label(&a, mask, style, opa_scale, buf, LV_TXT_FLAG_CENTER, NULL, -1, -1, NULL);
1259                 }
1260             }
1261         }
1262     }
1263 }
1264 
lv_chart_draw_axes(lv_obj_t * chart,const lv_area_t * mask)1265 static void lv_chart_draw_axes(lv_obj_t * chart, const lv_area_t * mask)
1266 {
1267     lv_chart_draw_y_ticks(chart, mask);
1268     lv_chart_draw_x_ticks(chart, mask);
1269 }
1270 
1271 /**
1272  * invalid area of the new line data lines on a chart
1273  * @param obj pointer to chart object
1274  */
lv_chart_inv_lines(lv_obj_t * chart,uint16_t i)1275 static void lv_chart_inv_lines(lv_obj_t * chart, uint16_t i)
1276 {
1277     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
1278 
1279     lv_coord_t w     = lv_obj_get_width(chart);
1280     lv_coord_t x_ofs = chart->coords.x1;
1281 
1282     if(i < ext->point_cnt) {
1283         lv_area_t coords;
1284         lv_obj_get_coords(chart, &coords);
1285         if(i < ext->point_cnt - 1) {
1286             coords.x1 = ((w * i) / (ext->point_cnt - 1)) + x_ofs - ext->series.width;
1287             coords.x2 = ((w * (i + 1)) / (ext->point_cnt - 1)) + x_ofs + ext->series.width;
1288             lv_inv_area(lv_obj_get_disp(chart), &coords);
1289         }
1290 
1291         if(i > 0) {
1292             coords.x1 = ((w * (i - 1)) / (ext->point_cnt - 1)) + x_ofs - ext->series.width;
1293             coords.x2 = ((w * i) / (ext->point_cnt - 1)) + x_ofs + ext->series.width;
1294             lv_inv_area(lv_obj_get_disp(chart), &coords);
1295         }
1296     }
1297 }
1298 
1299 /**
1300  * invalid area of the new point data lines on a chart
1301  * @param chart pointer to chart object
1302  * @param mask mask, inherited from the design function
1303  */
lv_chart_inv_points(lv_obj_t * chart,uint16_t i)1304 static void lv_chart_inv_points(lv_obj_t * chart, uint16_t i)
1305 {
1306     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
1307 
1308     lv_area_t cir_a;
1309     lv_coord_t w     = lv_obj_get_width(chart);
1310     lv_coord_t x_ofs = chart->coords.x1;
1311 
1312     lv_obj_get_coords(chart, &cir_a);
1313     cir_a.x1 = ((w * i) / (ext->point_cnt - 1)) + x_ofs;
1314     cir_a.x2 = cir_a.x1 + ext->series.width;
1315     cir_a.x1 -= ext->series.width;
1316 
1317     lv_inv_area(lv_obj_get_disp(chart), &cir_a);
1318 }
1319 
1320 /**
1321  * invalid area of the new column data lines on a chart
1322  * @param chart pointer to chart object
1323  * @param mask mask, inherited from the design function
1324  */
lv_chart_inv_cols(lv_obj_t * chart,uint16_t i)1325 static void lv_chart_inv_cols(lv_obj_t * chart, uint16_t i)
1326 {
1327     lv_chart_ext_t * ext = lv_obj_get_ext_attr(chart);
1328 
1329     lv_area_t col_a;
1330     lv_coord_t w     = lv_obj_get_width(chart);
1331     lv_coord_t col_w = w / ((ext->series.num + 1) * ext->point_cnt); /* Suppose + 1 series as separator*/
1332     lv_coord_t x_ofs = col_w / 2;                                    /*Shift with a half col.*/
1333 
1334     lv_coord_t x_act;
1335 
1336     x_act = (int32_t)((int32_t)w * i) / ext->point_cnt;
1337     x_act += chart->coords.x1 + x_ofs;
1338 
1339     lv_obj_get_coords(chart, &col_a);
1340     col_a.x1 = x_act;
1341     col_a.x2 = col_a.x1 + col_w;
1342 
1343     lv_inv_area(lv_obj_get_disp(chart), &col_a);
1344 }
1345 
1346 #endif
1347