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