1 /**
2 * @file lv_chart.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_chart_private.h"
10 #include "../../misc/lv_area_private.h"
11 #include "../../draw/lv_draw_private.h"
12 #include "../../core/lv_obj_private.h"
13 #include "../../core/lv_obj_class_private.h"
14 #if LV_USE_CHART != 0
15
16 #include "../../misc/lv_assert.h"
17
18 /*********************
19 * DEFINES
20 *********************/
21 #define MY_CLASS (&lv_chart_class)
22
23 #define LV_CHART_HDIV_DEF 3
24 #define LV_CHART_VDIV_DEF 5
25 #define LV_CHART_POINT_CNT_DEF 10
26 #define LV_CHART_LABEL_MAX_TEXT_LENGTH 16
27
28 /**********************
29 * TYPEDEFS
30 **********************/
31
32 /**********************
33 * STATIC PROTOTYPES
34 **********************/
35 static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
36 static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
37 static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e);
38
39 static void draw_div_lines(lv_obj_t * obj, lv_layer_t * layer);
40 static void draw_series_line(lv_obj_t * obj, lv_layer_t * layer);
41 static void draw_series_bar(lv_obj_t * obj, lv_layer_t * layer);
42 static void draw_series_scatter(lv_obj_t * obj, lv_layer_t * layer);
43 static void draw_cursors(lv_obj_t * obj, lv_layer_t * layer);
44 static uint32_t get_index_from_x(lv_obj_t * obj, int32_t x);
45 static void invalidate_point(lv_obj_t * obj, uint32_t i);
46 static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, int32_t ** a);
47
48 /**********************
49 * STATIC VARIABLES
50 **********************/
51
52 const lv_obj_class_t lv_chart_class = {
53 .constructor_cb = lv_chart_constructor,
54 .destructor_cb = lv_chart_destructor,
55 .event_cb = lv_chart_event,
56 .width_def = LV_PCT(100),
57 .height_def = LV_DPI_DEF * 2,
58 .instance_size = sizeof(lv_chart_t),
59 .base_class = &lv_obj_class,
60 .name = "chart",
61 };
62
63 /**********************
64 * MACROS
65 **********************/
66
67 /**********************
68 * GLOBAL FUNCTIONS
69 **********************/
70
lv_chart_create(lv_obj_t * parent)71 lv_obj_t * lv_chart_create(lv_obj_t * parent)
72 {
73 LV_LOG_INFO("begin");
74 lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
75 lv_obj_class_init_obj(obj);
76 return obj;
77 }
78
lv_chart_set_type(lv_obj_t * obj,lv_chart_type_t type)79 void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type)
80 {
81 LV_ASSERT_OBJ(obj, MY_CLASS);
82
83 lv_chart_t * chart = (lv_chart_t *)obj;
84 if(chart->type == type) return;
85
86 if(chart->type == LV_CHART_TYPE_SCATTER) {
87 lv_chart_series_t * ser;
88 LV_LL_READ_BACK(&chart->series_ll, ser) {
89 lv_free(ser->x_points);
90 ser->x_points = NULL;
91 }
92 }
93
94 if(type == LV_CHART_TYPE_SCATTER) {
95 lv_chart_series_t * ser;
96 LV_LL_READ_BACK(&chart->series_ll, ser) {
97 ser->x_points = lv_malloc(sizeof(int32_t) * chart->point_cnt);
98 LV_ASSERT_MALLOC(ser->x_points);
99 if(ser->x_points == NULL) return;
100 }
101 }
102
103 chart->type = type;
104
105 lv_chart_refresh(obj);
106 }
107
lv_chart_set_point_count(lv_obj_t * obj,uint32_t cnt)108 void lv_chart_set_point_count(lv_obj_t * obj, uint32_t cnt)
109 {
110 LV_ASSERT_OBJ(obj, MY_CLASS);
111
112 lv_chart_t * chart = (lv_chart_t *)obj;
113 if(chart->point_cnt == cnt) return;
114
115 lv_chart_series_t * ser;
116
117 if(cnt < 1) cnt = 1;
118
119 LV_LL_READ_BACK(&chart->series_ll, ser) {
120 if(chart->type == LV_CHART_TYPE_SCATTER) {
121 if(!ser->x_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->x_points);
122 }
123 if(!ser->y_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->y_points);
124 ser->start_point = 0;
125 }
126
127 chart->point_cnt = cnt;
128
129 lv_chart_refresh(obj);
130 }
131
lv_chart_set_range(lv_obj_t * obj,lv_chart_axis_t axis,int32_t min,int32_t max)132 void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, int32_t min, int32_t max)
133 {
134 LV_ASSERT_OBJ(obj, MY_CLASS);
135
136 max = max == min ? max + 1 : max;
137
138 lv_chart_t * chart = (lv_chart_t *)obj;
139 switch(axis) {
140 case LV_CHART_AXIS_PRIMARY_Y:
141 chart->ymin[0] = min;
142 chart->ymax[0] = max;
143 break;
144 case LV_CHART_AXIS_SECONDARY_Y:
145 chart->ymin[1] = min;
146 chart->ymax[1] = max;
147 break;
148 case LV_CHART_AXIS_PRIMARY_X:
149 chart->xmin[0] = min;
150 chart->xmax[0] = max;
151 break;
152 case LV_CHART_AXIS_SECONDARY_X:
153 chart->xmin[1] = min;
154 chart->xmax[1] = max;
155 break;
156 default:
157 LV_LOG_WARN("Invalid axis: %d", axis);
158 return;
159 }
160
161 lv_chart_refresh(obj);
162 }
163
lv_chart_set_update_mode(lv_obj_t * obj,lv_chart_update_mode_t update_mode)164 void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode)
165 {
166 LV_ASSERT_OBJ(obj, MY_CLASS);
167
168 lv_chart_t * chart = (lv_chart_t *)obj;
169 if(chart->update_mode == update_mode) return;
170
171 chart->update_mode = update_mode;
172 lv_obj_invalidate(obj);
173 }
174
lv_chart_set_div_line_count(lv_obj_t * obj,uint8_t hdiv,uint8_t vdiv)175 void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv)
176 {
177 LV_ASSERT_OBJ(obj, MY_CLASS);
178
179 lv_chart_t * chart = (lv_chart_t *)obj;
180 if(chart->hdiv_cnt == hdiv && chart->vdiv_cnt == vdiv) return;
181
182 chart->hdiv_cnt = hdiv;
183 chart->vdiv_cnt = vdiv;
184
185 lv_obj_invalidate(obj);
186 }
187
lv_chart_get_type(const lv_obj_t * obj)188 lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj)
189 {
190 LV_ASSERT_OBJ(obj, MY_CLASS);
191
192 lv_chart_t * chart = (lv_chart_t *)obj;
193 return chart->type;
194 }
195
lv_chart_get_point_count(const lv_obj_t * obj)196 uint32_t lv_chart_get_point_count(const lv_obj_t * obj)
197 {
198 LV_ASSERT_OBJ(obj, MY_CLASS);
199
200 lv_chart_t * chart = (lv_chart_t *)obj;
201 return chart->point_cnt;
202 }
203
lv_chart_get_x_start_point(const lv_obj_t * obj,lv_chart_series_t * ser)204 uint32_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser)
205 {
206 LV_ASSERT_NULL(ser);
207 LV_UNUSED(obj);
208
209 return ser->start_point;
210 }
211
lv_chart_get_point_pos_by_id(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t id,lv_point_t * p_out)212 void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, lv_point_t * p_out)
213 {
214 LV_ASSERT_NULL(obj);
215 LV_ASSERT_NULL(ser);
216 LV_ASSERT_OBJ(obj, MY_CLASS);
217
218 lv_chart_t * chart = (lv_chart_t *)obj;
219 if(id >= chart->point_cnt) {
220 LV_LOG_WARN("Invalid index: %"LV_PRIu32, id);
221 p_out->x = 0;
222 p_out->y = 0;
223 return;
224 }
225
226 int32_t w = lv_obj_get_content_width(obj);
227 int32_t h = lv_obj_get_content_height(obj);
228
229 if(chart->type == LV_CHART_TYPE_LINE) {
230 if(chart->point_cnt > 1) {
231 p_out->x = (w * id) / (chart->point_cnt - 1);
232 }
233 else {
234 p_out->x = 0;
235 }
236 }
237 else if(chart->type == LV_CHART_TYPE_SCATTER) {
238 p_out->x = lv_map(ser->x_points[id], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
239 }
240 else if(chart->type == LV_CHART_TYPE_BAR) {
241 uint32_t ser_cnt = lv_ll_get_len(&chart->series_ll);
242 int32_t ser_gap = lv_obj_get_style_pad_column(obj, LV_PART_ITEMS);
243
244 /*Gap between the columns on adjacent X ticks*/
245 int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
246
247 int32_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;
248
249 if(chart->point_cnt > 1) {
250 p_out->x = (int32_t)((int32_t)(w - block_w) * id) / (chart->point_cnt - 1);
251 }
252 else {
253 p_out->x = 0;
254 }
255
256 lv_chart_series_t * ser_i = NULL;
257 uint32_t ser_idx = 0;
258 LV_LL_READ(&chart->series_ll, ser_i) {
259 if(ser_i == ser) break;
260 ser_idx++;
261 }
262
263 p_out->x = (int32_t)((int32_t)(w + block_gap) * id) / chart->point_cnt;
264 p_out->x += block_w * ser_idx / ser_cnt;
265
266 int32_t col_w = (block_w - (ser_gap * (ser_cnt - 1))) / ser_cnt;
267 p_out->x += col_w / 2;
268 }
269 else {
270 p_out->x = 0;
271 }
272
273 int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
274 p_out->x += lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
275 p_out->x -= lv_obj_get_scroll_left(obj);
276
277 uint32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
278 id = ((int32_t)start_point + id) % chart->point_cnt;
279 int32_t temp_y = 0;
280 temp_y = (int32_t)((int32_t)ser->y_points[id] - chart->ymin[ser->y_axis_sec]) * h;
281 temp_y = temp_y / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
282 p_out->y = h - temp_y;
283 p_out->y += lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
284 p_out->y -= lv_obj_get_scroll_top(obj);
285 }
286
lv_chart_refresh(lv_obj_t * obj)287 void lv_chart_refresh(lv_obj_t * obj)
288 {
289 LV_ASSERT_OBJ(obj, MY_CLASS);
290
291 lv_obj_invalidate(obj);
292 }
293
294 /*======================
295 * Series
296 *=====================*/
297
lv_chart_add_series(lv_obj_t * obj,lv_color_t color,lv_chart_axis_t axis)298 lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis)
299 {
300 LV_LOG_INFO("begin");
301
302 LV_ASSERT_OBJ(obj, MY_CLASS);
303
304 lv_chart_t * chart = (lv_chart_t *)obj;
305
306 /* Allocate space for a new series and add it to the chart series linked list */
307 lv_chart_series_t * ser = lv_ll_ins_tail(&chart->series_ll);
308 LV_ASSERT_MALLOC(ser);
309 if(ser == NULL) return NULL;
310 lv_memzero(ser, sizeof(lv_chart_series_t));
311
312 /* Allocate memory for point_cnt points, handle failure below */
313 ser->y_points = lv_malloc(sizeof(int32_t) * chart->point_cnt);
314 LV_ASSERT_MALLOC(ser->y_points);
315
316 if(chart->type == LV_CHART_TYPE_SCATTER) {
317 ser->x_points = lv_malloc(sizeof(int32_t) * chart->point_cnt);
318 LV_ASSERT_MALLOC(ser->x_points);
319 if(NULL == ser->x_points) {
320 lv_free(ser->y_points);
321 lv_ll_remove(&chart->series_ll, ser);
322 lv_free(ser);
323 return NULL;
324 }
325 }
326 else {
327 ser->x_points = NULL;
328 }
329
330 if(ser->y_points == NULL) {
331 if(ser->x_points) {
332 lv_free(ser->x_points);
333 ser->x_points = NULL;
334 }
335
336 lv_ll_remove(&chart->series_ll, ser);
337 lv_free(ser);
338 return NULL;
339 }
340
341 /* Set series properties on successful allocation */
342 ser->color = color;
343 ser->start_point = 0;
344 ser->y_ext_buf_assigned = false;
345 ser->hidden = 0;
346 ser->x_axis_sec = axis & LV_CHART_AXIS_SECONDARY_X ? 1 : 0;
347 ser->y_axis_sec = axis & LV_CHART_AXIS_SECONDARY_Y ? 1 : 0;
348
349 uint32_t i;
350 const int32_t def = LV_CHART_POINT_NONE;
351 int32_t * p_tmp = ser->y_points;
352 for(i = 0; i < chart->point_cnt; i++) {
353 *p_tmp = def;
354 p_tmp++;
355 }
356
357 return ser;
358 }
359
lv_chart_remove_series(lv_obj_t * obj,lv_chart_series_t * series)360 void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series)
361 {
362 LV_ASSERT_OBJ(obj, MY_CLASS);
363 LV_ASSERT_NULL(series);
364
365 lv_chart_t * chart = (lv_chart_t *)obj;
366 if(!series->y_ext_buf_assigned && series->y_points) lv_free(series->y_points);
367 if(!series->x_ext_buf_assigned && series->x_points) lv_free(series->x_points);
368
369 lv_ll_remove(&chart->series_ll, series);
370 lv_free(series);
371
372 return;
373 }
374
lv_chart_hide_series(lv_obj_t * chart,lv_chart_series_t * series,bool hide)375 void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide)
376 {
377 LV_ASSERT_OBJ(chart, MY_CLASS);
378 LV_ASSERT_NULL(series);
379
380 series->hidden = hide ? 1 : 0;
381 lv_chart_refresh(chart);
382 }
383
lv_chart_set_series_color(lv_obj_t * chart,lv_chart_series_t * series,lv_color_t color)384 void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color)
385 {
386 LV_ASSERT_OBJ(chart, MY_CLASS);
387 LV_ASSERT_NULL(series);
388
389 series->color = color;
390 lv_chart_refresh(chart);
391 }
392
lv_chart_get_series_color(lv_obj_t * chart,const lv_chart_series_t * series)393 lv_color_t lv_chart_get_series_color(lv_obj_t * chart, const lv_chart_series_t * series)
394 {
395 LV_ASSERT_OBJ(chart, MY_CLASS);
396 LV_ASSERT_NULL(series);
397 LV_UNUSED(chart);
398
399 return series->color;
400 }
401
lv_chart_set_x_start_point(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t id)402 void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id)
403 {
404 LV_ASSERT_OBJ(obj, MY_CLASS);
405 LV_ASSERT_NULL(ser);
406
407 lv_chart_t * chart = (lv_chart_t *)obj;
408 if(id >= chart->point_cnt) return;
409 ser->start_point = id;
410 }
411
lv_chart_get_series_next(const lv_obj_t * obj,const lv_chart_series_t * ser)412 lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * obj, const lv_chart_series_t * ser)
413 {
414 LV_ASSERT_OBJ(obj, MY_CLASS);
415
416 lv_chart_t * chart = (lv_chart_t *)obj;
417 if(ser == NULL) return lv_ll_get_head(&chart->series_ll);
418 else return lv_ll_get_next(&chart->series_ll, ser);
419 }
420
421 /*=====================
422 * Cursor
423 *====================*/
424
lv_chart_add_cursor(lv_obj_t * obj,lv_color_t color,lv_dir_t dir)425 lv_chart_cursor_t * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir)
426 {
427 LV_ASSERT_OBJ(obj, MY_CLASS);
428
429 lv_chart_t * chart = (lv_chart_t *)obj;
430 lv_chart_cursor_t * cursor = lv_ll_ins_head(&chart->cursor_ll);
431 LV_ASSERT_MALLOC(cursor);
432 if(cursor == NULL) return NULL;
433
434 lv_point_set(&cursor->pos, LV_CHART_POINT_NONE, LV_CHART_POINT_NONE);
435 cursor->point_id = LV_CHART_POINT_NONE;
436 cursor->pos_set = 0;
437 cursor->color = color;
438 cursor->dir = dir;
439
440 return cursor;
441 }
442
lv_chart_set_cursor_pos(lv_obj_t * chart,lv_chart_cursor_t * cursor,lv_point_t * pos)443 void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos)
444 {
445 LV_ASSERT_NULL(cursor);
446 LV_UNUSED(chart);
447
448 cursor->pos = *pos;
449 cursor->pos_set = 1;
450 lv_chart_refresh(chart);
451 }
452
lv_chart_set_cursor_point(lv_obj_t * chart,lv_chart_cursor_t * cursor,lv_chart_series_t * ser,uint32_t point_id)453 void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser, uint32_t point_id)
454 {
455 LV_ASSERT_NULL(cursor);
456 LV_UNUSED(chart);
457
458 cursor->point_id = point_id;
459 cursor->pos_set = 0;
460 if(ser == NULL) ser = lv_chart_get_series_next(chart, NULL);
461 cursor->ser = ser;
462 lv_chart_refresh(chart);
463 }
464
lv_chart_get_cursor_point(lv_obj_t * chart,lv_chart_cursor_t * cursor)465 lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor)
466 {
467 LV_ASSERT_NULL(cursor);
468 LV_UNUSED(chart);
469
470 return cursor->pos;
471 }
472
473 /*=====================
474 * Set/Get value(s)
475 *====================*/
476
lv_chart_set_all_values(lv_obj_t * obj,lv_chart_series_t * ser,int32_t value)477 void lv_chart_set_all_values(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value)
478 {
479 LV_ASSERT_OBJ(obj, MY_CLASS);
480 LV_ASSERT_NULL(ser);
481
482 lv_chart_t * chart = (lv_chart_t *)obj;
483 uint32_t i;
484 for(i = 0; i < chart->point_cnt; i++) {
485 ser->y_points[i] = value;
486 }
487 ser->start_point = 0;
488 lv_chart_refresh(obj);
489 }
490
lv_chart_set_next_value(lv_obj_t * obj,lv_chart_series_t * ser,int32_t value)491 void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value)
492 {
493 LV_ASSERT_OBJ(obj, MY_CLASS);
494 LV_ASSERT_NULL(ser);
495
496 lv_chart_t * chart = (lv_chart_t *)obj;
497 ser->y_points[ser->start_point] = value;
498 invalidate_point(obj, ser->start_point);
499 ser->start_point = (ser->start_point + 1) % chart->point_cnt;
500 invalidate_point(obj, ser->start_point);
501 }
502
lv_chart_set_next_value2(lv_obj_t * obj,lv_chart_series_t * ser,int32_t x_value,int32_t y_value)503 void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, int32_t x_value, int32_t y_value)
504 {
505 LV_ASSERT_OBJ(obj, MY_CLASS);
506 LV_ASSERT_NULL(ser);
507
508 lv_chart_t * chart = (lv_chart_t *)obj;
509
510 if(chart->type != LV_CHART_TYPE_SCATTER) {
511 LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER");
512 return;
513 }
514
515 ser->x_points[ser->start_point] = x_value;
516 ser->y_points[ser->start_point] = y_value;
517 ser->start_point = (ser->start_point + 1) % chart->point_cnt;
518 invalidate_point(obj, ser->start_point);
519 }
520
lv_chart_set_value_by_id(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t id,int32_t value)521 void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t value)
522 {
523 LV_ASSERT_OBJ(obj, MY_CLASS);
524 LV_ASSERT_NULL(ser);
525 lv_chart_t * chart = (lv_chart_t *)obj;
526
527 if(id >= chart->point_cnt) return;
528 ser->y_points[id] = value;
529 invalidate_point(obj, id);
530 }
531
lv_chart_set_value_by_id2(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t id,int32_t x_value,int32_t y_value)532 void lv_chart_set_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t x_value,
533 int32_t y_value)
534 {
535 LV_ASSERT_OBJ(obj, MY_CLASS);
536 LV_ASSERT_NULL(ser);
537 lv_chart_t * chart = (lv_chart_t *)obj;
538
539 if(chart->type != LV_CHART_TYPE_SCATTER) {
540 LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER");
541 return;
542 }
543
544 if(id >= chart->point_cnt) return;
545 ser->x_points[id] = x_value;
546 ser->y_points[id] = y_value;
547 invalidate_point(obj, id);
548 }
549
lv_chart_set_ext_y_array(lv_obj_t * obj,lv_chart_series_t * ser,int32_t array[])550 void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[])
551 {
552 LV_ASSERT_OBJ(obj, MY_CLASS);
553 LV_ASSERT_NULL(ser);
554
555 if(!ser->y_ext_buf_assigned && ser->y_points) lv_free(ser->y_points);
556 ser->y_ext_buf_assigned = true;
557 ser->y_points = array;
558 lv_obj_invalidate(obj);
559 }
560
lv_chart_set_ext_x_array(lv_obj_t * obj,lv_chart_series_t * ser,int32_t array[])561 void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[])
562 {
563 LV_ASSERT_OBJ(obj, MY_CLASS);
564 LV_ASSERT_NULL(ser);
565
566 if(!ser->x_ext_buf_assigned && ser->x_points) lv_free(ser->x_points);
567 ser->x_ext_buf_assigned = true;
568 ser->x_points = array;
569 lv_obj_invalidate(obj);
570 }
571
lv_chart_get_y_array(const lv_obj_t * obj,lv_chart_series_t * ser)572 int32_t * lv_chart_get_y_array(const lv_obj_t * obj, lv_chart_series_t * ser)
573 {
574 LV_UNUSED(obj);
575 LV_ASSERT_OBJ(obj, MY_CLASS);
576 LV_ASSERT_NULL(ser);
577 return ser->y_points;
578 }
579
lv_chart_get_x_array(const lv_obj_t * obj,lv_chart_series_t * ser)580 int32_t * lv_chart_get_x_array(const lv_obj_t * obj, lv_chart_series_t * ser)
581 {
582 LV_UNUSED(obj);
583 LV_ASSERT_OBJ(obj, MY_CLASS);
584 LV_ASSERT_NULL(ser);
585 return ser->x_points;
586 }
587
lv_chart_get_pressed_point(const lv_obj_t * obj)588 uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj)
589 {
590 lv_chart_t * chart = (lv_chart_t *)obj;
591 return chart->pressed_point_id;
592 }
593
lv_chart_get_first_point_center_offset(lv_obj_t * obj)594 int32_t lv_chart_get_first_point_center_offset(lv_obj_t * obj)
595 {
596 lv_chart_t * chart = (lv_chart_t *)obj;
597
598 int32_t x_ofs = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
599 if(chart->type == LV_CHART_TYPE_BAR) {
600 lv_obj_update_layout(obj);
601 /*Gap between the columns on ~adjacent X*/
602 int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
603 int32_t w = lv_obj_get_content_width(obj);
604 int32_t block_w = (w + block_gap) / (chart->point_cnt);
605
606 x_ofs += (block_w - block_gap) / 2;
607 }
608
609 return x_ofs;
610 }
611
612 /**********************
613 * STATIC FUNCTIONS
614 **********************/
615
lv_chart_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)616 static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
617 {
618 LV_UNUSED(class_p);
619 LV_TRACE_OBJ_CREATE("begin");
620
621 lv_chart_t * chart = (lv_chart_t *)obj;
622
623 lv_ll_init(&chart->series_ll, sizeof(lv_chart_series_t));
624 lv_ll_init(&chart->cursor_ll, sizeof(lv_chart_cursor_t));
625
626 chart->ymin[0] = 0;
627 chart->xmin[0] = 0;
628 chart->ymin[1] = 0;
629 chart->xmin[1] = 0;
630 chart->ymax[0] = 100;
631 chart->xmax[0] = 100;
632 chart->ymax[1] = 100;
633 chart->xmax[1] = 100;
634
635 chart->hdiv_cnt = LV_CHART_HDIV_DEF;
636 chart->vdiv_cnt = LV_CHART_VDIV_DEF;
637 chart->point_cnt = LV_CHART_POINT_CNT_DEF;
638 chart->pressed_point_id = LV_CHART_POINT_NONE;
639 chart->type = LV_CHART_TYPE_LINE;
640 chart->update_mode = LV_CHART_UPDATE_MODE_SHIFT;
641
642 LV_TRACE_OBJ_CREATE("finished");
643 }
644
lv_chart_destructor(const lv_obj_class_t * class_p,lv_obj_t * obj)645 static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
646 {
647 LV_UNUSED(class_p);
648 LV_TRACE_OBJ_CREATE("begin");
649
650 lv_chart_t * chart = (lv_chart_t *)obj;
651 lv_chart_series_t * ser;
652 while(chart->series_ll.head) {
653 ser = lv_ll_get_head(&chart->series_ll);
654 if(!ser) continue;
655
656 if(!ser->y_ext_buf_assigned) lv_free(ser->y_points);
657 if(!ser->x_ext_buf_assigned) lv_free(ser->x_points);
658
659 lv_ll_remove(&chart->series_ll, ser);
660 lv_free(ser);
661 }
662 lv_ll_clear(&chart->series_ll);
663
664 lv_chart_cursor_t * cur;
665 while(chart->cursor_ll.head) {
666 cur = lv_ll_get_head(&chart->cursor_ll);
667 lv_ll_remove(&chart->cursor_ll, cur);
668 lv_free(cur);
669 }
670 lv_ll_clear(&chart->cursor_ll);
671
672 LV_TRACE_OBJ_CREATE("finished");
673 }
674
lv_chart_event(const lv_obj_class_t * class_p,lv_event_t * e)675 static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e)
676 {
677 LV_UNUSED(class_p);
678
679 /*Call the ancestor's event handler*/
680 lv_result_t res;
681
682 res = lv_obj_event_base(MY_CLASS, e);
683 if(res != LV_RESULT_OK) return;
684
685 lv_event_code_t code = lv_event_get_code(e);
686 lv_obj_t * obj = lv_event_get_current_target(e);
687
688 lv_chart_t * chart = (lv_chart_t *)obj;
689 if(code == LV_EVENT_PRESSED) {
690 lv_indev_t * indev = lv_indev_active();
691 lv_point_t p;
692 lv_indev_get_point(indev, &p);
693
694 p.x -= obj->coords.x1;
695 uint32_t id = get_index_from_x(obj, p.x + lv_obj_get_scroll_left(obj));
696 if(id != (uint32_t)chart->pressed_point_id) {
697 invalidate_point(obj, id);
698 invalidate_point(obj, chart->pressed_point_id);
699 chart->pressed_point_id = id;
700 lv_obj_send_event(obj, LV_EVENT_VALUE_CHANGED, NULL);
701 }
702 }
703 else if(code == LV_EVENT_RELEASED) {
704 invalidate_point(obj, chart->pressed_point_id);
705 chart->pressed_point_id = LV_CHART_POINT_NONE;
706 }
707 else if(code == LV_EVENT_DRAW_MAIN) {
708 lv_layer_t * layer = lv_event_get_layer(e);
709 draw_div_lines(obj, layer);
710
711 if(lv_ll_is_empty(&chart->series_ll) == false) {
712 if(chart->type == LV_CHART_TYPE_LINE) draw_series_line(obj, layer);
713 else if(chart->type == LV_CHART_TYPE_BAR) draw_series_bar(obj, layer);
714 else if(chart->type == LV_CHART_TYPE_SCATTER) draw_series_scatter(obj, layer);
715 }
716
717 draw_cursors(obj, layer);
718 }
719 }
720
draw_div_lines(lv_obj_t * obj,lv_layer_t * layer)721 static void draw_div_lines(lv_obj_t * obj, lv_layer_t * layer)
722 {
723 lv_chart_t * chart = (lv_chart_t *)obj;
724
725 lv_area_t series_clip_area;
726 bool mask_ret = lv_area_intersect(&series_clip_area, &obj->coords, &layer->_clip_area);
727 if(mask_ret == false) return;
728
729 const lv_area_t clip_area_ori = layer->_clip_area;
730 layer->_clip_area = series_clip_area;
731
732 int16_t i;
733 int16_t i_start;
734 int16_t i_end;
735 int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
736 int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
737 int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
738 int32_t w = lv_obj_get_content_width(obj);
739 int32_t h = lv_obj_get_content_height(obj);
740
741 lv_draw_line_dsc_t line_dsc;
742 lv_draw_line_dsc_init(&line_dsc);
743 line_dsc.base.layer = layer;
744 lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc);
745
746 lv_opa_t border_opa = lv_obj_get_style_border_opa(obj, LV_PART_MAIN);
747 int32_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
748 lv_border_side_t border_side = lv_obj_get_style_border_side(obj, LV_PART_MAIN);
749
750 int32_t scroll_left = lv_obj_get_scroll_left(obj);
751 int32_t scroll_top = lv_obj_get_scroll_top(obj);
752 if(chart->hdiv_cnt != 0) {
753 int32_t y_ofs = obj->coords.y1 + pad_top - scroll_top;
754 line_dsc.p1.x = obj->coords.x1;
755 line_dsc.p2.x = obj->coords.x2;
756
757 i_start = 0;
758 i_end = chart->hdiv_cnt;
759 if(border_opa > LV_OPA_MIN && border_w > 0) {
760 if((border_side & LV_BORDER_SIDE_TOP) && (lv_obj_get_style_pad_top(obj, LV_PART_MAIN) == 0)) i_start++;
761 if((border_side & LV_BORDER_SIDE_BOTTOM) && (lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN) == 0)) i_end--;
762 }
763
764 for(i = i_start; i < i_end; i++) {
765 line_dsc.p1.y = (int32_t)((int32_t)h * i) / (chart->hdiv_cnt - 1);
766 line_dsc.p1.y += y_ofs;
767 line_dsc.p2.y = line_dsc.p1.y;
768 line_dsc.base.id1 = i;
769
770 lv_draw_line(layer, &line_dsc);
771 }
772 }
773
774 if(chart->vdiv_cnt != 0) {
775 int32_t x_ofs = obj->coords.x1 + pad_left - scroll_left;
776 line_dsc.p1.y = obj->coords.y1;
777 line_dsc.p2.y = obj->coords.y2;
778 i_start = 0;
779 i_end = chart->vdiv_cnt;
780 if(border_opa > LV_OPA_MIN && border_w > 0) {
781 if((border_side & LV_BORDER_SIDE_LEFT) && (lv_obj_get_style_pad_left(obj, LV_PART_MAIN) == 0)) i_start++;
782 if((border_side & LV_BORDER_SIDE_RIGHT) && (lv_obj_get_style_pad_right(obj, LV_PART_MAIN) == 0)) i_end--;
783 }
784
785 for(i = i_start; i < i_end; i++) {
786 line_dsc.p1.x = (int32_t)((int32_t)w * i) / (chart->vdiv_cnt - 1);
787 line_dsc.p1.x += x_ofs;
788 line_dsc.p2.x = line_dsc.p1.x;
789 line_dsc.base.id1 = i;
790
791 lv_draw_line(layer, &line_dsc);
792 }
793 }
794
795 layer->_clip_area = clip_area_ori;
796 }
797
draw_series_line(lv_obj_t * obj,lv_layer_t * layer)798 static void draw_series_line(lv_obj_t * obj, lv_layer_t * layer)
799 {
800 lv_area_t clip_area;
801 if(lv_area_intersect(&clip_area, &obj->coords, &layer->_clip_area) == false) return;
802
803 const lv_area_t clip_area_ori = layer->_clip_area;
804 layer->_clip_area = clip_area;
805
806 lv_chart_t * chart = (lv_chart_t *)obj;
807 if(chart->point_cnt < 2) return;
808
809 uint32_t i;
810 int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
811 int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
812 int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
813 int32_t w = lv_obj_get_content_width(obj);
814 int32_t h = lv_obj_get_content_height(obj);
815 int32_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj);
816 int32_t y_ofs = obj->coords.y1 + pad_top - lv_obj_get_scroll_top(obj);
817 lv_chart_series_t * ser;
818
819 lv_area_t series_clip_area;
820 bool mask_ret = lv_area_intersect(&series_clip_area, &obj->coords, &layer->_clip_area);
821 if(mask_ret == false) return;
822
823 lv_draw_line_dsc_t line_dsc;
824 lv_draw_line_dsc_init(&line_dsc);
825 line_dsc.base.layer = layer;
826 lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc);
827
828 lv_draw_rect_dsc_t point_dsc_default;
829 lv_draw_rect_dsc_init(&point_dsc_default);
830 point_dsc_default.base.layer = layer;
831 lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default);
832
833 int32_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2;
834 int32_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2;
835
836 /*Do not bother with line ending is the point will over it*/
837 if(LV_MIN(point_w, point_h) > line_dsc.width / 2) line_dsc.raw_end = 1;
838 if(line_dsc.width == 1) line_dsc.raw_end = 1;
839
840 /*If there are at least as many points as pixels then draw only vertical lines*/
841 bool crowded_mode = (int32_t)chart->point_cnt >= w;
842
843 line_dsc.base.id1 = lv_ll_get_len(&chart->series_ll) - 1;
844 point_dsc_default.base.id1 = line_dsc.base.id1;
845 /*Go through all data lines*/
846 LV_LL_READ_BACK(&chart->series_ll, ser) {
847 if(ser->hidden) {
848 line_dsc.base.id1--;
849 point_dsc_default.base.id1--;
850 continue;
851 }
852 line_dsc.color = ser->color;
853 point_dsc_default.bg_color = ser->color;
854 line_dsc.base.id2 = 0;
855 point_dsc_default.base.id2 = 0;
856
857 int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
858
859 line_dsc.p1.x = x_ofs;
860 line_dsc.p2.x = x_ofs;
861
862 int32_t p_act = start_point;
863 int32_t p_prev = start_point;
864 int32_t y_tmp = (int32_t)((int32_t)ser->y_points[p_prev] - chart->ymin[ser->y_axis_sec]) * h;
865 y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
866 line_dsc.p2.y = h - y_tmp + y_ofs;
867
868 lv_value_precise_t y_min = line_dsc.p2.y;
869 lv_value_precise_t y_max = line_dsc.p2.y;
870
871 for(i = 0; i < chart->point_cnt; i++) {
872 line_dsc.p1.x = line_dsc.p2.x;
873 line_dsc.p1.y = line_dsc.p2.y;
874
875 if(line_dsc.p1.x > clip_area_ori.x2 + point_w + 1) break;
876 line_dsc.p2.x = (lv_value_precise_t)((w * i) / (chart->point_cnt - 1)) + x_ofs;
877
878 p_act = (start_point + i) % chart->point_cnt;
879
880 y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
881 y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
882 line_dsc.p2.y = h - y_tmp + y_ofs;
883
884 if(line_dsc.p2.x < clip_area_ori.x1 - point_w - 1) {
885 p_prev = p_act;
886 continue;
887 }
888
889 /*Don't draw the first point. A second point is also required to draw the line*/
890 if(i != 0) {
891 if(crowded_mode) {
892 if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
893 /*Draw only one vertical line between the min and max y-values on the same x-value*/
894 y_max = LV_MAX(y_max, line_dsc.p2.y);
895 y_min = LV_MIN(y_min, line_dsc.p2.y);
896 if(line_dsc.p1.x != line_dsc.p2.x) {
897 lv_value_precise_t y_cur = line_dsc.p2.y;
898 line_dsc.p2.x--; /*It's already on the next x value*/
899 line_dsc.p1.x = line_dsc.p2.x;
900 line_dsc.p1.y = y_min;
901 line_dsc.p2.y = y_max;
902 if(line_dsc.p1.y == line_dsc.p2.y) line_dsc.p2.y++; /*If they are the same no line will be drawn*/
903 lv_draw_line(layer, &line_dsc);
904 line_dsc.p2.x++; /*Compensate the previous x--*/
905 y_min = y_cur; /*Start the line of the next x from the current last y*/
906 y_max = y_cur;
907 }
908 }
909 }
910 else {
911 lv_area_t point_area;
912 point_area.x1 = (int32_t)line_dsc.p1.x - point_w;
913 point_area.x2 = (int32_t)line_dsc.p1.x + point_w;
914 point_area.y1 = (int32_t)line_dsc.p1.y - point_h;
915 point_area.y2 = (int32_t)line_dsc.p1.y + point_h;
916
917 if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
918 line_dsc.base.id2 = i;
919 lv_draw_line(layer, &line_dsc);
920 }
921
922 if(point_w && point_h && ser->y_points[p_prev] != LV_CHART_POINT_NONE) {
923 point_dsc_default.base.id2 = i - 1;
924 lv_draw_rect(layer, &point_dsc_default, &point_area);
925 }
926 }
927
928 }
929 p_prev = p_act;
930 }
931
932 /*Draw the last point*/
933 if(!crowded_mode && i == chart->point_cnt) {
934
935 if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
936 lv_area_t point_area;
937 point_area.x1 = (int32_t)line_dsc.p2.x - point_w;
938 point_area.x2 = (int32_t)line_dsc.p2.x + point_w;
939 point_area.y1 = (int32_t)line_dsc.p2.y - point_h;
940 point_area.y2 = (int32_t)line_dsc.p2.y + point_h;
941 point_dsc_default.base.id2 = i - 1;
942 lv_draw_rect(layer, &point_dsc_default, &point_area);
943 }
944 }
945
946 point_dsc_default.base.id1--;
947 line_dsc.base.id1--;
948 }
949
950 layer->_clip_area = clip_area_ori;
951 }
952
draw_series_scatter(lv_obj_t * obj,lv_layer_t * layer)953 static void draw_series_scatter(lv_obj_t * obj, lv_layer_t * layer)
954 {
955
956 lv_area_t clip_area;
957 if(lv_area_intersect(&clip_area, &obj->coords, &layer->_clip_area) == false) return;
958
959 const lv_area_t clip_area_ori = layer->_clip_area;
960 layer->_clip_area = clip_area;
961
962 lv_chart_t * chart = (lv_chart_t *)obj;
963
964 uint32_t i;
965 int32_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
966 int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
967 int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
968 int32_t w = lv_obj_get_content_width(obj);
969 int32_t h = lv_obj_get_content_height(obj);
970 int32_t x_ofs = obj->coords.x1 + pad_left + border_width - lv_obj_get_scroll_left(obj);
971 int32_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj);
972 lv_chart_series_t * ser;
973
974 lv_draw_line_dsc_t line_dsc;
975 lv_draw_line_dsc_init(&line_dsc);
976 line_dsc.base.layer = layer;
977 lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc);
978
979 lv_draw_rect_dsc_t point_dsc_default;
980 lv_draw_rect_dsc_init(&point_dsc_default);
981 point_dsc_default.base.layer = layer;
982 lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default);
983
984 int32_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2;
985 int32_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2;
986
987 /*Do not bother with line ending is the point will over it*/
988 if(LV_MIN(point_w, point_h) > line_dsc.width / 2) line_dsc.raw_end = 1;
989 if(line_dsc.width == 1) line_dsc.raw_end = 1;
990
991 /*Go through all data lines*/
992 LV_LL_READ_BACK(&chart->series_ll, ser) {
993 if(ser->hidden) continue;
994 line_dsc.color = ser->color;
995 point_dsc_default.bg_color = ser->color;
996
997 int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
998
999 line_dsc.p1.x = x_ofs;
1000 line_dsc.p2.x = x_ofs;
1001
1002 int32_t p_act = start_point;
1003 int32_t p_prev = start_point;
1004 if(ser->y_points[p_act] != LV_CHART_POINT_CNT_DEF) {
1005 line_dsc.p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
1006 line_dsc.p2.x += x_ofs;
1007
1008 line_dsc.p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
1009 line_dsc.p2.y = h - line_dsc.p2.y;
1010 line_dsc.p2.y += y_ofs;
1011 }
1012 else {
1013 line_dsc.p2.x = (lv_value_precise_t)LV_COORD_MIN;
1014 line_dsc.p2.y = (lv_value_precise_t)LV_COORD_MIN;
1015 }
1016
1017 for(i = 0; i < chart->point_cnt; i++) {
1018 line_dsc.p1.x = line_dsc.p2.x;
1019 line_dsc.p1.y = line_dsc.p2.y;
1020
1021 p_act = (start_point + i) % chart->point_cnt;
1022 if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1023 line_dsc.p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
1024 line_dsc.p2.y = h - line_dsc.p2.y;
1025 line_dsc.p2.y += y_ofs;
1026
1027 line_dsc.p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
1028 line_dsc.p2.x += x_ofs;
1029 }
1030 else {
1031 p_prev = p_act;
1032 continue;
1033 }
1034
1035 /*Don't draw the first point. A second point is also required to draw the line*/
1036 if(i != 0) {
1037 lv_area_t point_area;
1038 point_area.x1 = (int32_t)line_dsc.p1.x - point_w;
1039 point_area.x2 = (int32_t)line_dsc.p1.x + point_w;
1040 point_area.y1 = (int32_t)line_dsc.p1.y - point_h;
1041 point_area.y2 = (int32_t)line_dsc.p1.y + point_h;
1042
1043 if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1044 line_dsc.base.id2 = i;
1045 lv_draw_line(layer, &line_dsc);
1046 if(point_w && point_h) {
1047 point_dsc_default.base.id2 = i;
1048 lv_draw_rect(layer, &point_dsc_default, &point_area);
1049 }
1050 }
1051
1052 p_prev = p_act;
1053 }
1054
1055 /*Draw the last point*/
1056 if(i == chart->point_cnt) {
1057
1058 if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1059 lv_area_t point_area;
1060 point_area.x1 = (int32_t)line_dsc.p2.x - point_w;
1061 point_area.x2 = (int32_t)line_dsc.p2.x + point_w;
1062 point_area.y1 = (int32_t)line_dsc.p2.y - point_h;
1063 point_area.y2 = (int32_t)line_dsc.p2.y + point_h;
1064
1065 point_dsc_default.base.id2 = i;
1066 lv_draw_rect(layer, &point_dsc_default, &point_area);
1067 }
1068 }
1069 }
1070 line_dsc.base.id1++;
1071 point_dsc_default.base.id1++;
1072 layer->_clip_area = clip_area_ori;
1073 }
1074 }
1075
draw_series_bar(lv_obj_t * obj,lv_layer_t * layer)1076 static void draw_series_bar(lv_obj_t * obj, lv_layer_t * layer)
1077 {
1078 lv_area_t clip_area;
1079 if(lv_area_intersect(&clip_area, &obj->coords, &layer->_clip_area) == false) return;
1080
1081 const lv_area_t clip_area_ori = layer->_clip_area;
1082 layer->_clip_area = clip_area;
1083
1084 lv_chart_t * chart = (lv_chart_t *)obj;
1085
1086 uint32_t i;
1087 lv_area_t col_a;
1088 int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1089 int32_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
1090 int32_t w = lv_obj_get_content_width(obj);
1091 int32_t h = lv_obj_get_content_height(obj);
1092 int32_t y_tmp;
1093 lv_chart_series_t * ser;
1094 uint32_t ser_cnt = lv_ll_get_len(&chart->series_ll);
1095 if(ser_cnt == 0) {
1096 return;
1097 }
1098 int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN); /*Gap between the column on ~adjacent X*/
1099 int32_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;
1100 int32_t ser_gap = lv_obj_get_style_pad_column(obj, LV_PART_ITEMS); /*Gap between the columns on the ~same X*/
1101 int32_t col_w = (block_w - (ser_cnt - 1) * ser_gap) / ser_cnt;
1102 if(col_w < 1) col_w = 1;
1103
1104 int32_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1105 int32_t x_ofs = pad_left - lv_obj_get_scroll_left(obj) + border_w;
1106 int32_t y_ofs = pad_top - lv_obj_get_scroll_top(obj) + border_w;
1107
1108 lv_draw_rect_dsc_t col_dsc;
1109 lv_draw_rect_dsc_init(&col_dsc);
1110 col_dsc.base.layer = layer;
1111 lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc);
1112 col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE;
1113 col_dsc.bg_opa = LV_OPA_COVER;
1114
1115 /*Make the cols longer with `radius` to clip the rounding from the bottom*/
1116 col_a.y2 = obj->coords.y2 + col_dsc.radius;
1117
1118 /*Go through all points*/
1119 for(i = 0; i < chart->point_cnt; i++) {
1120 int32_t x_act;
1121 if(chart->point_cnt <= 1) {
1122 x_act = obj->coords.x1 + x_ofs;
1123 }
1124 else {
1125 x_act = (int32_t)((int32_t)(w - block_w) * i) / (chart->point_cnt - 1) + obj->coords.x1 + x_ofs;
1126 }
1127
1128 col_dsc.base.id2 = i;
1129 col_dsc.base.id1 = 0;
1130
1131 /*Draw the current point of all data line*/
1132 LV_LL_READ(&chart->series_ll, ser) {
1133 if(ser->hidden) continue;
1134
1135 int32_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
1136
1137 col_a.x1 = x_act;
1138 col_a.x2 = col_a.x1 + col_w - 1;
1139 x_act += col_w + ser_gap;
1140
1141 if(col_a.x2 < clip_area.x1) {
1142 col_dsc.base.id1++;
1143 continue;
1144 }
1145 if(col_a.x1 > clip_area.x2) break;
1146
1147 col_dsc.bg_color = ser->color;
1148
1149 int32_t p_act = (start_point + i) % chart->point_cnt;
1150 y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
1151 y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
1152 col_a.y1 = h - y_tmp + obj->coords.y1 + y_ofs;
1153
1154 if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1155 lv_draw_rect(layer, &col_dsc, &col_a);
1156 }
1157 col_dsc.base.id1++;
1158 }
1159 }
1160 layer->_clip_area = clip_area_ori;
1161 }
1162
draw_cursors(lv_obj_t * obj,lv_layer_t * layer)1163 static void draw_cursors(lv_obj_t * obj, lv_layer_t * layer)
1164 {
1165 LV_ASSERT_OBJ(obj, MY_CLASS);
1166
1167 lv_chart_t * chart = (lv_chart_t *)obj;
1168 if(lv_ll_is_empty(&chart->cursor_ll)) return;
1169
1170 lv_area_t clip_area;
1171 if(!lv_area_intersect(&clip_area, &layer->_clip_area, &obj->coords)) return;
1172
1173 const lv_area_t clip_area_ori = layer->_clip_area;
1174 layer->_clip_area = clip_area;
1175
1176 lv_chart_cursor_t * cursor;
1177
1178 lv_draw_line_dsc_t line_dsc_ori;
1179 lv_draw_line_dsc_init(&line_dsc_ori);
1180 line_dsc_ori.base.layer = layer;
1181 lv_obj_init_draw_line_dsc(obj, LV_PART_CURSOR, &line_dsc_ori);
1182
1183 lv_draw_rect_dsc_t point_dsc_ori;
1184 lv_draw_rect_dsc_init(&point_dsc_ori);
1185 point_dsc_ori.base.layer = layer;
1186 lv_obj_init_draw_rect_dsc(obj, LV_PART_CURSOR, &point_dsc_ori);
1187
1188 lv_draw_line_dsc_t line_dsc;
1189 lv_draw_rect_dsc_t point_dsc_tmp;
1190
1191 int32_t point_w = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
1192 int32_t point_h = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
1193
1194 /*Go through all cursor lines*/
1195 LV_LL_READ_BACK(&chart->cursor_ll, cursor) {
1196 lv_memcpy(&line_dsc, &line_dsc_ori, sizeof(lv_draw_line_dsc_t));
1197 lv_memcpy(&point_dsc_tmp, &point_dsc_ori, sizeof(lv_draw_rect_dsc_t));
1198 line_dsc.color = cursor->color;
1199 point_dsc_tmp.bg_color = cursor->color;
1200
1201 int32_t cx;
1202 int32_t cy;
1203 if(cursor->pos_set) {
1204 cx = cursor->pos.x;
1205 cy = cursor->pos.y;
1206 }
1207 else {
1208 if(cursor->point_id == LV_CHART_POINT_NONE) continue;
1209 lv_point_t p;
1210 lv_chart_get_point_pos_by_id(obj, cursor->ser, cursor->point_id, &p);
1211 cx = p.x;
1212 cy = p.y;
1213 }
1214
1215 cx += obj->coords.x1;
1216 cy += obj->coords.y1;
1217
1218 lv_area_t point_area;
1219 bool draw_point = point_w && point_h;
1220 point_area.x1 = cx - point_w;
1221 point_area.x2 = cx + point_w;
1222 point_area.y1 = cy - point_h;
1223 point_area.y2 = cy + point_h;
1224
1225 if(cursor->dir & LV_DIR_HOR) {
1226 line_dsc.p1.x = cursor->dir & LV_DIR_LEFT ? obj->coords.x1 : cx;
1227 line_dsc.p1.y = cy;
1228 line_dsc.p2.x = cursor->dir & LV_DIR_RIGHT ? obj->coords.x2 : cx;
1229 line_dsc.p2.y = line_dsc.p1.y;
1230
1231 line_dsc.base.id2 = 0;
1232 point_dsc_tmp.base.id2 = 0;
1233
1234 lv_draw_line(layer, &line_dsc);
1235
1236 if(draw_point) {
1237 lv_draw_rect(layer, &point_dsc_tmp, &point_area);
1238 }
1239 }
1240
1241 if(cursor->dir & LV_DIR_VER) {
1242 line_dsc.p1.x = cx;
1243 line_dsc.p1.y = cursor->dir & LV_DIR_TOP ? obj->coords.y1 : cy;
1244 line_dsc.p2.x = line_dsc.p1.x;
1245 line_dsc.p2.y = cursor->dir & LV_DIR_BOTTOM ? obj->coords.y2 : cy;
1246
1247 line_dsc.base.id2 = 1;
1248 point_dsc_tmp.base.id2 = 1;
1249
1250 lv_draw_line(layer, &line_dsc);
1251
1252 if(draw_point) {
1253 lv_draw_rect(layer, &point_dsc_tmp, &point_area);
1254 }
1255 }
1256 line_dsc_ori.base.id1++;
1257 point_dsc_ori.base.id1++;
1258 }
1259
1260 layer->_clip_area = clip_area_ori;
1261 }
1262
1263 /**
1264 * Get the nearest index to an X coordinate
1265 * @param chart pointer to a chart object
1266 * @param coord the coordination of the point relative to the series area.
1267 * @return the found index
1268 */
get_index_from_x(lv_obj_t * obj,int32_t x)1269 static uint32_t get_index_from_x(lv_obj_t * obj, int32_t x)
1270 {
1271 lv_chart_t * chart = (lv_chart_t *)obj;
1272 int32_t w = lv_obj_get_content_width(obj);
1273 int32_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1274 x -= pad_left;
1275
1276 if(x < 0) return 0;
1277 if(x > w) return chart->point_cnt - 1;
1278 if(chart->type == LV_CHART_TYPE_LINE) return (x * (chart->point_cnt - 1) + w / 2) / w;
1279 if(chart->type == LV_CHART_TYPE_BAR) return (x * chart->point_cnt) / w;
1280
1281 return 0;
1282 }
1283
invalidate_point(lv_obj_t * obj,uint32_t i)1284 static void invalidate_point(lv_obj_t * obj, uint32_t i)
1285 {
1286 lv_chart_t * chart = (lv_chart_t *)obj;
1287 if(i >= chart->point_cnt) return;
1288
1289 int32_t w = lv_obj_get_content_width(obj);
1290 int32_t scroll_left = lv_obj_get_scroll_left(obj);
1291
1292 /*In shift mode the whole chart changes so the whole object*/
1293 if(chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT) {
1294 lv_obj_invalidate(obj);
1295 return;
1296 }
1297
1298 if(chart->type == LV_CHART_TYPE_LINE) {
1299 int32_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1300 int32_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1301 int32_t x_ofs = obj->coords.x1 + pleft + bwidth - scroll_left;
1302 int32_t line_width = lv_obj_get_style_line_width(obj, LV_PART_ITEMS);
1303 int32_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR);
1304
1305 lv_area_t coords;
1306 lv_area_copy(&coords, &obj->coords);
1307 coords.y1 -= line_width + point_w;
1308 coords.y2 += line_width + point_w;
1309
1310 if(i < chart->point_cnt - 1) {
1311 coords.x1 = ((w * i) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
1312 coords.x2 = ((w * (i + 1)) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
1313 lv_obj_invalidate_area(obj, &coords);
1314 }
1315
1316 if(i > 0) {
1317 coords.x1 = ((w * (i - 1)) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
1318 coords.x2 = ((w * i) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
1319 lv_obj_invalidate_area(obj, &coords);
1320 }
1321 }
1322 else if(chart->type == LV_CHART_TYPE_BAR) {
1323 lv_area_t col_a;
1324 /*Gap between the column on ~adjacent X*/
1325 int32_t block_gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
1326
1327 int32_t block_w = (w + block_gap) / chart->point_cnt;
1328
1329 int32_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1330 int32_t x_act;
1331 x_act = (int32_t)((int32_t)(block_w) * i) ;
1332 x_act += obj->coords.x1 + bwidth + lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1333
1334 lv_obj_get_coords(obj, &col_a);
1335 col_a.x1 = x_act - scroll_left;
1336 col_a.x2 = col_a.x1 + block_w;
1337 col_a.x1 -= block_gap;
1338
1339 lv_obj_invalidate_area(obj, &col_a);
1340 }
1341 else {
1342 lv_obj_invalidate(obj);
1343 }
1344 }
1345
new_points_alloc(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t cnt,int32_t ** a)1346 static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, int32_t ** a)
1347 {
1348 if((*a) == NULL) return;
1349
1350 lv_chart_t * chart = (lv_chart_t *) obj;
1351 uint32_t point_cnt_old = chart->point_cnt;
1352 uint32_t i;
1353
1354 if(ser->start_point != 0) {
1355 int32_t * new_points = lv_malloc(sizeof(int32_t) * cnt);
1356 LV_ASSERT_MALLOC(new_points);
1357 if(new_points == NULL) return;
1358
1359 if(cnt >= point_cnt_old) {
1360 for(i = 0; i < point_cnt_old; i++) {
1361 new_points[i] =
1362 (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
1363 }
1364 for(i = point_cnt_old; i < cnt; i++) {
1365 new_points[i] = LV_CHART_POINT_NONE; /*Fill up the rest with default value*/
1366 }
1367 }
1368 else {
1369 for(i = 0; i < cnt; i++) {
1370 new_points[i] =
1371 (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
1372 }
1373 }
1374
1375 /*Switch over pointer from old to new*/
1376 lv_free((*a));
1377 (*a) = new_points;
1378 }
1379 else {
1380 (*a) = lv_realloc((*a), sizeof(int32_t) * cnt);
1381 LV_ASSERT_MALLOC((*a));
1382 if((*a) == NULL) return;
1383 /*Initialize the new points*/
1384 if(cnt > point_cnt_old) {
1385 for(i = point_cnt_old - 1; i < cnt; i++) {
1386 (*a)[i] = LV_CHART_POINT_NONE;
1387 }
1388 }
1389 }
1390 }
1391
1392 #endif
1393