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 "../../../misc/lv_assert.h"
13
14 /*********************
15 * DEFINES
16 *********************/
17 #define MY_CLASS &lv_chart_class
18
19 #define LV_CHART_HDIV_DEF 3
20 #define LV_CHART_VDIV_DEF 5
21 #define LV_CHART_POINT_CNT_DEF 10
22 #define LV_CHART_LABEL_MAX_TEXT_LENGTH 16
23
24 /**********************
25 * TYPEDEFS
26 **********************/
27
28 /**********************
29 * STATIC PROTOTYPES
30 **********************/
31 static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
32 static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
33 static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e);
34
35 static void draw_div_lines(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
36 static void draw_series_line(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
37 static void draw_series_bar(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
38 static void draw_series_scatter(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
39 static void draw_cursors(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
40 static void draw_axes(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx);
41 static uint32_t get_index_from_x(lv_obj_t * obj, lv_coord_t x);
42 static void invalidate_point(lv_obj_t * obj, uint16_t i);
43 static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, lv_coord_t ** a);
44 lv_chart_tick_dsc_t * get_tick_gsc(lv_obj_t * obj, lv_chart_axis_t axis);
45
46 /**********************
47 * STATIC VARIABLES
48 **********************/
49 const lv_obj_class_t lv_chart_class = {
50 .constructor_cb = lv_chart_constructor,
51 .destructor_cb = lv_chart_destructor,
52 .event_cb = lv_chart_event,
53 .width_def = LV_PCT(100),
54 .height_def = LV_DPI_DEF * 2,
55 .instance_size = sizeof(lv_chart_t),
56 .base_class = &lv_obj_class
57 };
58
59 /**********************
60 * MACROS
61 **********************/
62
63 /**********************
64 * GLOBAL FUNCTIONS
65 **********************/
66
lv_chart_create(lv_obj_t * parent)67 lv_obj_t * lv_chart_create(lv_obj_t * parent)
68 {
69 LV_LOG_INFO("begin");
70 lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
71 lv_obj_class_init_obj(obj);
72 return obj;
73 }
74
lv_chart_set_type(lv_obj_t * obj,lv_chart_type_t type)75 void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type)
76 {
77 LV_ASSERT_OBJ(obj, MY_CLASS);
78
79 lv_chart_t * chart = (lv_chart_t *)obj;
80 if(chart->type == type) return;
81
82 if(chart->type == LV_CHART_TYPE_SCATTER) {
83 lv_chart_series_t * ser;
84 _LV_LL_READ_BACK(&chart->series_ll, ser) {
85 lv_mem_free(ser->x_points);
86 ser->x_points = NULL;
87 }
88 }
89
90 if(type == LV_CHART_TYPE_SCATTER) {
91 lv_chart_series_t * ser;
92 _LV_LL_READ_BACK(&chart->series_ll, ser) {
93 ser->x_points = lv_mem_alloc(sizeof(lv_point_t) * chart->point_cnt);
94 LV_ASSERT_MALLOC(ser->x_points);
95 if(ser->x_points == NULL) return;
96 }
97 }
98
99 chart->type = type;
100
101 lv_chart_refresh(obj);
102 }
103
lv_chart_set_point_count(lv_obj_t * obj,uint16_t cnt)104 void lv_chart_set_point_count(lv_obj_t * obj, uint16_t cnt)
105 {
106 LV_ASSERT_OBJ(obj, MY_CLASS);
107
108 lv_chart_t * chart = (lv_chart_t *)obj;
109 if(chart->point_cnt == cnt) return;
110
111 lv_chart_series_t * ser;
112
113 if(cnt < 1) cnt = 1;
114
115 _LV_LL_READ_BACK(&chart->series_ll, ser) {
116 if(chart->type == LV_CHART_TYPE_SCATTER) {
117 if(!ser->x_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->x_points);
118 }
119 if(!ser->y_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->y_points);
120 ser->start_point = 0;
121 }
122
123 chart->point_cnt = cnt;
124
125 lv_chart_refresh(obj);
126 }
127
lv_chart_set_range(lv_obj_t * obj,lv_chart_axis_t axis,lv_coord_t min,lv_coord_t max)128 void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t min, lv_coord_t max)
129 {
130 LV_ASSERT_OBJ(obj, MY_CLASS);
131
132 max = max == min ? max + 1 : max;
133
134 lv_chart_t * chart = (lv_chart_t *)obj;
135 switch(axis) {
136 case LV_CHART_AXIS_PRIMARY_Y:
137 chart->ymin[0] = min;
138 chart->ymax[0] = max;
139 break;
140 case LV_CHART_AXIS_SECONDARY_Y:
141 chart->ymin[1] = min;
142 chart->ymax[1] = max;
143 break;
144 case LV_CHART_AXIS_PRIMARY_X:
145 chart->xmin[0] = min;
146 chart->xmax[0] = max;
147 break;
148 case LV_CHART_AXIS_SECONDARY_X:
149 chart->xmin[1] = min;
150 chart->xmax[1] = max;
151 break;
152 default:
153 LV_LOG_WARN("Invalid axis: %d", axis);
154 return;
155 }
156
157 lv_chart_refresh(obj);
158 }
159
lv_chart_set_update_mode(lv_obj_t * obj,lv_chart_update_mode_t update_mode)160 void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode)
161 {
162 LV_ASSERT_OBJ(obj, MY_CLASS);
163
164 lv_chart_t * chart = (lv_chart_t *)obj;
165 if(chart->update_mode == update_mode) return;
166
167 chart->update_mode = update_mode;
168 lv_obj_invalidate(obj);
169 }
170
lv_chart_set_div_line_count(lv_obj_t * obj,uint8_t hdiv,uint8_t vdiv)171 void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv)
172 {
173 LV_ASSERT_OBJ(obj, MY_CLASS);
174
175 lv_chart_t * chart = (lv_chart_t *)obj;
176 if(chart->hdiv_cnt == hdiv && chart->vdiv_cnt == vdiv) return;
177
178 chart->hdiv_cnt = hdiv;
179 chart->vdiv_cnt = vdiv;
180
181 lv_obj_invalidate(obj);
182 }
183
lv_chart_set_zoom_x(lv_obj_t * obj,uint16_t zoom_x)184 void lv_chart_set_zoom_x(lv_obj_t * obj, uint16_t zoom_x)
185 {
186 LV_ASSERT_OBJ(obj, MY_CLASS);
187
188 lv_chart_t * chart = (lv_chart_t *)obj;
189 if(chart->zoom_x == zoom_x) return;
190
191 chart->zoom_x = zoom_x;
192 lv_obj_refresh_self_size(obj);
193 /*Be the chart doesn't remain scrolled out*/
194 lv_obj_readjust_scroll(obj, LV_ANIM_OFF);
195 lv_obj_invalidate(obj);
196 }
197
lv_chart_set_zoom_y(lv_obj_t * obj,uint16_t zoom_y)198 void lv_chart_set_zoom_y(lv_obj_t * obj, uint16_t zoom_y)
199 {
200 LV_ASSERT_OBJ(obj, MY_CLASS);
201
202 lv_chart_t * chart = (lv_chart_t *)obj;
203 if(chart->zoom_y == zoom_y) return;
204
205 chart->zoom_y = zoom_y;
206 lv_obj_refresh_self_size(obj);
207 /*Be the chart doesn't remain scrolled out*/
208 lv_obj_readjust_scroll(obj, LV_ANIM_OFF);
209 lv_obj_invalidate(obj);
210 }
211
lv_chart_get_zoom_x(const lv_obj_t * obj)212 uint16_t lv_chart_get_zoom_x(const lv_obj_t * obj)
213 {
214 LV_ASSERT_OBJ(obj, MY_CLASS);
215
216 lv_chart_t * chart = (lv_chart_t *)obj;
217 return chart->zoom_x;
218 }
219
lv_chart_get_zoom_y(const lv_obj_t * obj)220 uint16_t lv_chart_get_zoom_y(const lv_obj_t * obj)
221 {
222 LV_ASSERT_OBJ(obj, MY_CLASS);
223
224 lv_chart_t * chart = (lv_chart_t *)obj;
225 return chart->zoom_y;
226 }
227
lv_chart_set_axis_tick(lv_obj_t * obj,lv_chart_axis_t axis,lv_coord_t major_len,lv_coord_t minor_len,lv_coord_t major_cnt,lv_coord_t minor_cnt,bool label_en,lv_coord_t draw_size)228 void lv_chart_set_axis_tick(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t major_len, lv_coord_t minor_len,
229 lv_coord_t major_cnt, lv_coord_t minor_cnt, bool label_en, lv_coord_t draw_size)
230 {
231 LV_ASSERT_OBJ(obj, MY_CLASS);
232
233 lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis);
234 t->major_len = major_len;
235 t->minor_len = minor_len;
236 t->minor_cnt = minor_cnt;
237 t->major_cnt = major_cnt;
238 t->label_en = label_en;
239 t->draw_size = draw_size;
240
241 lv_obj_refresh_ext_draw_size(obj);
242 lv_obj_invalidate(obj);
243 }
244
lv_chart_get_type(const lv_obj_t * obj)245 lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj)
246 {
247 LV_ASSERT_OBJ(obj, MY_CLASS);
248
249 lv_chart_t * chart = (lv_chart_t *)obj;
250 return chart->type;
251 }
252
lv_chart_get_point_count(const lv_obj_t * obj)253 uint16_t lv_chart_get_point_count(const lv_obj_t * obj)
254 {
255 LV_ASSERT_OBJ(obj, MY_CLASS);
256
257 lv_chart_t * chart = (lv_chart_t *)obj;
258 return chart->point_cnt;
259 }
260
lv_chart_get_x_start_point(const lv_obj_t * obj,lv_chart_series_t * ser)261 uint16_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser)
262 {
263 LV_ASSERT_NULL(ser);
264 lv_chart_t * chart = (lv_chart_t *)obj;
265
266 return chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0;
267 }
268
lv_chart_get_point_pos_by_id(lv_obj_t * obj,lv_chart_series_t * ser,uint16_t id,lv_point_t * p_out)269 void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_point_t * p_out)
270 {
271 LV_ASSERT_NULL(obj);
272 LV_ASSERT_NULL(ser);
273 LV_ASSERT_OBJ(obj, MY_CLASS);
274
275 lv_chart_t * chart = (lv_chart_t *)obj;
276 if(id >= chart->point_cnt) {
277 LV_LOG_WARN("Invalid index: %d", id);
278 p_out->x = 0;
279 p_out->y = 0;
280 return;
281 }
282
283 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
284 lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
285
286 if(chart->type == LV_CHART_TYPE_LINE) {
287 p_out->x = (w * id) / (chart->point_cnt - 1);
288 }
289 else if(chart->type == LV_CHART_TYPE_SCATTER) {
290 p_out->x = lv_map(ser->x_points[id], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
291 }
292 else if(chart->type == LV_CHART_TYPE_BAR) {
293 uint32_t ser_cnt = _lv_ll_get_len(&chart->series_ll);
294 /*Gap between the column on the X tick*/
295 int32_t ser_gap = ((int32_t)lv_obj_get_style_pad_column(obj, LV_PART_ITEMS) * chart->zoom_x) >> 8;
296
297 /*Gap between the columns on adjacent X ticks*/
298 int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, LV_PART_MAIN) * chart->zoom_x) >> 8;
299
300 lv_coord_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;
301
302 lv_chart_series_t * ser_i = NULL;
303 uint32_t ser_idx = 0;
304 _LV_LL_READ_BACK(&chart->series_ll, ser_i) {
305 if(ser_i == ser) break;
306 ser_idx++;
307 }
308
309 p_out->x = (int32_t)((int32_t)(w + block_gap) * id) / chart->point_cnt;
310 p_out->x += block_w * ser_idx / ser_cnt;
311
312 lv_coord_t col_w = (block_w - (ser_gap * (ser_cnt - 1))) / ser_cnt;
313 p_out->x += col_w / 2;
314 }
315
316 lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
317 p_out->x += lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
318 p_out->x -= lv_obj_get_scroll_left(obj);
319
320 uint32_t start_point = lv_chart_get_x_start_point(obj, ser);
321 id = ((int32_t)start_point + id) % chart->point_cnt;
322 int32_t temp_y = 0;
323 temp_y = (int32_t)((int32_t)ser->y_points[id] - chart->ymin[ser->y_axis_sec]) * h;
324 temp_y = temp_y / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
325 p_out->y = h - temp_y;
326 p_out->y += lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
327 p_out->y -= lv_obj_get_scroll_top(obj);
328 }
329
lv_chart_refresh(lv_obj_t * obj)330 void lv_chart_refresh(lv_obj_t * obj)
331 {
332 LV_ASSERT_OBJ(obj, MY_CLASS);
333
334 lv_obj_invalidate(obj);
335 }
336
337 /*======================
338 * Series
339 *=====================*/
340
lv_chart_add_series(lv_obj_t * obj,lv_color_t color,lv_chart_axis_t axis)341 lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis)
342 {
343 LV_LOG_INFO("begin");
344
345 LV_ASSERT_OBJ(obj, MY_CLASS);
346
347 lv_chart_t * chart = (lv_chart_t *)obj;
348 lv_chart_series_t * ser = _lv_ll_ins_head(&chart->series_ll);
349 LV_ASSERT_MALLOC(ser);
350 if(ser == NULL) return NULL;
351
352 lv_coord_t def = LV_CHART_POINT_NONE;
353
354 ser->color = color;
355 ser->y_points = lv_mem_alloc(sizeof(lv_coord_t) * chart->point_cnt);
356 LV_ASSERT_MALLOC(ser->y_points);
357
358 if(chart->type == LV_CHART_TYPE_SCATTER) {
359 ser->x_points = lv_mem_alloc(sizeof(lv_coord_t) * chart->point_cnt);
360 LV_ASSERT_MALLOC(ser->x_points);
361 }
362 if(ser->y_points == NULL) {
363 _lv_ll_remove(&chart->series_ll, ser);
364 lv_mem_free(ser);
365 return NULL;
366 }
367
368 ser->start_point = 0;
369 ser->y_ext_buf_assigned = false;
370 ser->hidden = 0;
371 ser->x_axis_sec = axis & LV_CHART_AXIS_SECONDARY_X ? 1 : 0;
372 ser->y_axis_sec = axis & LV_CHART_AXIS_SECONDARY_Y ? 1 : 0;
373
374 uint16_t i;
375 lv_coord_t * p_tmp = ser->y_points;
376 for(i = 0; i < chart->point_cnt; i++) {
377 *p_tmp = def;
378 p_tmp++;
379 }
380
381 return ser;
382 }
383
lv_chart_remove_series(lv_obj_t * obj,lv_chart_series_t * series)384 void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series)
385 {
386 LV_ASSERT_OBJ(obj, MY_CLASS);
387 LV_ASSERT_NULL(series);
388
389 lv_chart_t * chart = (lv_chart_t *)obj;
390 if(!series->y_ext_buf_assigned && series->y_points) lv_mem_free(series->y_points);
391 if(!series->x_ext_buf_assigned && series->x_points) lv_mem_free(series->x_points);
392
393 _lv_ll_remove(&chart->series_ll, series);
394 lv_mem_free(series);
395
396 return;
397 }
398
lv_chart_hide_series(lv_obj_t * chart,lv_chart_series_t * series,bool hide)399 void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide)
400 {
401 LV_ASSERT_OBJ(chart, MY_CLASS);
402 LV_ASSERT_NULL(series);
403
404 series->hidden = hide ? 1 : 0;
405 lv_chart_refresh(chart);
406 }
407
lv_chart_set_series_color(lv_obj_t * chart,lv_chart_series_t * series,lv_color_t color)408 void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color)
409 {
410 LV_ASSERT_OBJ(chart, MY_CLASS);
411 LV_ASSERT_NULL(series);
412
413 series->color = color;
414 lv_chart_refresh(chart);
415 }
416
lv_chart_set_x_start_point(lv_obj_t * obj,lv_chart_series_t * ser,uint16_t id)417 void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id)
418 {
419 LV_ASSERT_OBJ(obj, MY_CLASS);
420 LV_ASSERT_NULL(ser);
421
422 lv_chart_t * chart = (lv_chart_t *)obj;
423 if(id >= chart->point_cnt) return;
424 ser->start_point = id;
425 }
426
lv_chart_get_series_next(const lv_obj_t * obj,const lv_chart_series_t * ser)427 lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * obj, const lv_chart_series_t * ser)
428 {
429 LV_ASSERT_OBJ(obj, MY_CLASS);
430
431 lv_chart_t * chart = (lv_chart_t *)obj;
432 if(ser == NULL) return _lv_ll_get_head(&chart->series_ll);
433 else return _lv_ll_get_next(&chart->series_ll, ser);
434 }
435
436 /*=====================
437 * Cursor
438 *====================*/
439
440 /**
441 * Add a cursor with a given color
442 * @param chart pointer to chart object
443 * @param color color of the cursor
444 * @param dir direction of the cursor. `LV_DIR_RIGHT/LEFT/TOP/DOWN/HOR/VER/ALL`. OR-ed values are possible
445 * @return pointer to the created cursor
446 */
lv_chart_add_cursor(lv_obj_t * obj,lv_color_t color,lv_dir_t dir)447 lv_chart_cursor_t * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir)
448 {
449 LV_ASSERT_OBJ(obj, MY_CLASS);
450
451 lv_chart_t * chart = (lv_chart_t *)obj;
452 lv_chart_cursor_t * cursor = _lv_ll_ins_head(&chart->cursor_ll);
453 LV_ASSERT_MALLOC(cursor);
454 if(cursor == NULL) return NULL;
455
456 cursor->pos.x = LV_CHART_POINT_NONE;
457 cursor->pos.y = LV_CHART_POINT_NONE;
458 cursor->point_id = LV_CHART_POINT_NONE;
459 cursor->pos_set = 0;
460 cursor->color = color;
461 cursor->dir = dir;
462
463 return cursor;
464 }
465
466 /**
467 * Set the coordinate of the cursor with respect
468 * to the origin of series area of the chart.
469 * @param chart pointer to a chart object.
470 * @param cursor pointer to the cursor.
471 * @param pos the new coordinate of cursor relative to the series area
472 */
lv_chart_set_cursor_pos(lv_obj_t * chart,lv_chart_cursor_t * cursor,lv_point_t * pos)473 void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos)
474 {
475 LV_ASSERT_NULL(cursor);
476 LV_UNUSED(chart);
477
478 cursor->pos.x = pos->x;
479 cursor->pos.y = pos->y;
480 cursor->pos_set = 1;
481 lv_chart_refresh(chart);
482 }
483
484 /**
485 * Set the coordinate of the cursor with respect
486 * to the origin of series area of the chart.
487 * @param chart pointer to a chart object.
488 * @param cursor pointer to the cursor.
489 * @param pos the new coordinate of cursor relative to the series area
490 */
lv_chart_set_cursor_point(lv_obj_t * chart,lv_chart_cursor_t * cursor,lv_chart_series_t * ser,uint16_t point_id)491 void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser, uint16_t point_id)
492 {
493 LV_ASSERT_NULL(cursor);
494 LV_UNUSED(chart);
495
496 cursor->point_id = point_id;
497 cursor->pos_set = 0;
498 if(ser == NULL) ser = lv_chart_get_series_next(chart, NULL);
499 cursor->ser = ser;
500 lv_chart_refresh(chart);
501 }
502 /**
503 * Get the coordinate of the cursor with respect
504 * to the origin of series area of the chart.
505 * @param chart pointer to a chart object
506 * @param cursor pointer to cursor
507 * @return coordinate of the cursor as lv_point_t
508 */
lv_chart_get_cursor_point(lv_obj_t * chart,lv_chart_cursor_t * cursor)509 lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor)
510 {
511 LV_ASSERT_NULL(cursor);
512 LV_UNUSED(chart);
513
514 return cursor->pos;
515 }
516
517 /*=====================
518 * Set/Get value(s)
519 *====================*/
520
lv_chart_set_all_value(lv_obj_t * obj,lv_chart_series_t * ser,lv_coord_t value)521 void lv_chart_set_all_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value)
522 {
523 LV_ASSERT_OBJ(obj, MY_CLASS);
524 LV_ASSERT_NULL(ser);
525
526 lv_chart_t * chart = (lv_chart_t *)obj;
527 uint16_t i;
528 for(i = 0; i < chart->point_cnt; i++) {
529 ser->y_points[i] = value;
530 }
531 ser->start_point = 0;
532 lv_chart_refresh(obj);
533 }
534
lv_chart_set_next_value(lv_obj_t * obj,lv_chart_series_t * ser,lv_coord_t value)535 void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value)
536 {
537 LV_ASSERT_OBJ(obj, MY_CLASS);
538 LV_ASSERT_NULL(ser);
539
540 lv_chart_t * chart = (lv_chart_t *)obj;
541 ser->y_points[ser->start_point] = value;
542 invalidate_point(obj, ser->start_point);
543 ser->start_point = (ser->start_point + 1) % chart->point_cnt;
544 invalidate_point(obj, ser->start_point);
545 }
546
lv_chart_set_next_value2(lv_obj_t * obj,lv_chart_series_t * ser,lv_coord_t x_value,lv_coord_t y_value)547 void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t x_value, lv_coord_t y_value)
548 {
549 LV_ASSERT_OBJ(obj, MY_CLASS);
550 LV_ASSERT_NULL(ser);
551
552 lv_chart_t * chart = (lv_chart_t *)obj;
553
554 if(chart->type != LV_CHART_TYPE_SCATTER) {
555 LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER");
556 return;
557 }
558
559 ser->x_points[ser->start_point] = x_value;
560 ser->y_points[ser->start_point] = y_value;
561 ser->start_point = (ser->start_point + 1) % chart->point_cnt;
562 invalidate_point(obj, ser->start_point);
563 }
564
lv_chart_set_value_by_id(lv_obj_t * obj,lv_chart_series_t * ser,uint16_t id,lv_coord_t value)565 void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t value)
566 {
567 LV_ASSERT_OBJ(obj, MY_CLASS);
568 LV_ASSERT_NULL(ser);
569 lv_chart_t * chart = (lv_chart_t *)obj;
570
571 if(id >= chart->point_cnt) return;
572 ser->y_points[id] = value;
573 invalidate_point(obj, id);
574 }
575
lv_chart_set_value_by_id2(lv_obj_t * obj,lv_chart_series_t * ser,uint16_t id,lv_coord_t x_value,lv_coord_t y_value)576 void lv_chart_set_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t x_value,
577 lv_coord_t y_value)
578 {
579 LV_ASSERT_OBJ(obj, MY_CLASS);
580 LV_ASSERT_NULL(ser);
581 lv_chart_t * chart = (lv_chart_t *)obj;
582
583 if(chart->type != LV_CHART_TYPE_SCATTER) {
584 LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER");
585 return;
586 }
587
588 if(id >= chart->point_cnt) return;
589 ser->x_points[id] = x_value;
590 ser->y_points[id] = y_value;
591 invalidate_point(obj, id);
592 }
593
lv_chart_set_ext_y_array(lv_obj_t * obj,lv_chart_series_t * ser,lv_coord_t array[])594 void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[])
595 {
596 LV_ASSERT_OBJ(obj, MY_CLASS);
597 LV_ASSERT_NULL(ser);
598
599 if(!ser->y_ext_buf_assigned && ser->y_points) lv_mem_free(ser->y_points);
600 ser->y_ext_buf_assigned = true;
601 ser->y_points = array;
602 lv_obj_invalidate(obj);
603 }
604
lv_chart_set_ext_x_array(lv_obj_t * obj,lv_chart_series_t * ser,lv_coord_t array[])605 void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[])
606 {
607 LV_ASSERT_OBJ(obj, MY_CLASS);
608 LV_ASSERT_NULL(ser);
609
610 if(!ser->x_ext_buf_assigned && ser->x_points) lv_mem_free(ser->x_points);
611 ser->x_ext_buf_assigned = true;
612 ser->x_points = array;
613 lv_obj_invalidate(obj);
614 }
615
lv_chart_get_y_array(const lv_obj_t * obj,lv_chart_series_t * ser)616 lv_coord_t * lv_chart_get_y_array(const lv_obj_t * obj, lv_chart_series_t * ser)
617 {
618 LV_UNUSED(obj);
619 LV_ASSERT_OBJ(obj, MY_CLASS);
620 LV_ASSERT_NULL(ser);
621 return ser->y_points;
622 }
623
lv_chart_get_x_array(const lv_obj_t * obj,lv_chart_series_t * ser)624 lv_coord_t * lv_chart_get_x_array(const lv_obj_t * obj, lv_chart_series_t * ser)
625 {
626 LV_UNUSED(obj);
627 LV_ASSERT_OBJ(obj, MY_CLASS);
628 LV_ASSERT_NULL(ser);
629 return ser->x_points;
630 }
631
lv_chart_get_pressed_point(const lv_obj_t * obj)632 uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj)
633 {
634 lv_chart_t * chart = (lv_chart_t *)obj;
635 return chart->pressed_point_id;
636 }
637
638 /**********************
639 * STATIC FUNCTIONS
640 **********************/
641
lv_chart_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)642 static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
643 {
644 LV_UNUSED(class_p);
645 LV_TRACE_OBJ_CREATE("begin");
646
647 lv_chart_t * chart = (lv_chart_t *)obj;
648
649 _lv_ll_init(&chart->series_ll, sizeof(lv_chart_series_t));
650 _lv_ll_init(&chart->cursor_ll, sizeof(lv_chart_cursor_t));
651
652 chart->ymin[0] = 0;
653 chart->xmin[0] = 0;
654 chart->ymin[1] = 0;
655 chart->xmin[1] = 0;
656 chart->ymax[0] = 100;
657 chart->xmax[0] = 100;
658 chart->ymax[1] = 100;
659 chart->xmax[1] = 100;
660
661 chart->hdiv_cnt = LV_CHART_HDIV_DEF;
662 chart->vdiv_cnt = LV_CHART_VDIV_DEF;
663 chart->point_cnt = LV_CHART_POINT_CNT_DEF;
664 chart->pressed_point_id = LV_CHART_POINT_NONE;
665 chart->type = LV_CHART_TYPE_LINE;
666 chart->update_mode = LV_CHART_UPDATE_MODE_SHIFT;
667 chart->zoom_x = LV_IMG_ZOOM_NONE;
668 chart->zoom_y = LV_IMG_ZOOM_NONE;
669
670 LV_TRACE_OBJ_CREATE("finished");
671 }
672
lv_chart_destructor(const lv_obj_class_t * class_p,lv_obj_t * obj)673 static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
674 {
675 LV_UNUSED(class_p);
676 LV_TRACE_OBJ_CREATE("begin");
677
678 lv_chart_t * chart = (lv_chart_t *)obj;
679 lv_chart_series_t * ser;
680 while(chart->series_ll.head) {
681 ser = _lv_ll_get_head(&chart->series_ll);
682
683 if(!ser->y_ext_buf_assigned) lv_mem_free(ser->y_points);
684
685 _lv_ll_remove(&chart->series_ll, ser);
686 lv_mem_free(ser);
687 }
688 _lv_ll_clear(&chart->series_ll);
689
690 lv_chart_cursor_t * cur;
691 while(chart->cursor_ll.head) {
692 cur = _lv_ll_get_head(&chart->cursor_ll);
693 _lv_ll_remove(&chart->cursor_ll, cur);
694 lv_mem_free(cur);
695 }
696 _lv_ll_clear(&chart->cursor_ll);
697
698 LV_TRACE_OBJ_CREATE("finished");
699 }
700
lv_chart_event(const lv_obj_class_t * class_p,lv_event_t * e)701 static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e)
702 {
703 LV_UNUSED(class_p);
704
705 /*Call the ancestor's event handler*/
706 lv_res_t res;
707
708 res = lv_obj_event_base(MY_CLASS, e);
709 if(res != LV_RES_OK) return;
710
711 lv_event_code_t code = lv_event_get_code(e);
712 lv_obj_t * obj = lv_event_get_target(e);
713
714 lv_chart_t * chart = (lv_chart_t *)obj;
715 if(code == LV_EVENT_PRESSED) {
716 lv_indev_t * indev = lv_indev_get_act();
717 lv_point_t p;
718 lv_indev_get_point(indev, &p);
719
720 p.x -= obj->coords.x1;
721 uint32_t id = get_index_from_x(obj, p.x + lv_obj_get_scroll_left(obj));
722 if(id != (uint32_t)chart->pressed_point_id) {
723 invalidate_point(obj, id);
724 invalidate_point(obj, chart->pressed_point_id);
725 chart->pressed_point_id = id;
726 lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
727 }
728 }
729 else if(code == LV_EVENT_RELEASED) {
730 invalidate_point(obj, chart->pressed_point_id);
731 chart->pressed_point_id = LV_CHART_POINT_NONE;
732 }
733 else if(code == LV_EVENT_SIZE_CHANGED) {
734 lv_obj_refresh_self_size(obj);
735 }
736 else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
737 lv_event_set_ext_draw_size(e, LV_MAX4(chart->tick[0].draw_size, chart->tick[1].draw_size, chart->tick[2].draw_size,
738 chart->tick[3].draw_size));
739 }
740 else if(code == LV_EVENT_GET_SELF_SIZE) {
741 lv_point_t * p = lv_event_get_param(e);
742 p->x = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
743 p->y = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
744 }
745 else if(code == LV_EVENT_DRAW_MAIN) {
746 lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e);
747 draw_div_lines(obj, draw_ctx);
748 draw_axes(obj, draw_ctx);
749
750 if(_lv_ll_is_empty(&chart->series_ll) == false) {
751 if(chart->type == LV_CHART_TYPE_LINE) draw_series_line(obj, draw_ctx);
752 else if(chart->type == LV_CHART_TYPE_BAR) draw_series_bar(obj, draw_ctx);
753 else if(chart->type == LV_CHART_TYPE_SCATTER) draw_series_scatter(obj, draw_ctx);
754 }
755
756 draw_cursors(obj, draw_ctx);
757 }
758 }
759
draw_div_lines(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)760 static void draw_div_lines(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
761 {
762 lv_chart_t * chart = (lv_chart_t *)obj;
763
764 lv_area_t series_clip_area;
765 bool mask_ret = _lv_area_intersect(&series_clip_area, &obj->coords, draw_ctx->clip_area);
766 if(mask_ret == false) return;
767
768 const lv_area_t * clip_area_ori = draw_ctx->clip_area;
769 draw_ctx->clip_area = &series_clip_area;
770
771 int16_t i;
772 int16_t i_start;
773 int16_t i_end;
774 lv_point_t p1;
775 lv_point_t p2;
776 lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
777 lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
778 lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
779 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
780 lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
781
782 lv_draw_line_dsc_t line_dsc;
783 lv_draw_line_dsc_init(&line_dsc);
784 lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc);
785
786 lv_obj_draw_part_dsc_t part_draw_dsc;
787 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
788 part_draw_dsc.part = LV_PART_MAIN;
789 part_draw_dsc.class_p = MY_CLASS;
790 part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_INIT;
791 part_draw_dsc.line_dsc = &line_dsc;
792 part_draw_dsc.id = 0xFFFFFFFF;
793 part_draw_dsc.p1 = NULL;
794 part_draw_dsc.p2 = NULL;
795 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
796
797 lv_opa_t border_opa = lv_obj_get_style_border_opa(obj, LV_PART_MAIN);
798 lv_coord_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
799 lv_border_side_t border_side = lv_obj_get_style_border_side(obj, LV_PART_MAIN);
800
801 lv_coord_t scroll_left = lv_obj_get_scroll_left(obj);
802 lv_coord_t scroll_top = lv_obj_get_scroll_top(obj);
803 if(chart->hdiv_cnt != 0) {
804 lv_coord_t y_ofs = obj->coords.y1 + pad_top - scroll_top;
805 p1.x = obj->coords.x1;
806 p2.x = obj->coords.x2;
807
808 i_start = 0;
809 i_end = chart->hdiv_cnt;
810 if(border_opa > LV_OPA_MIN && border_w > 0) {
811 if((border_side & LV_BORDER_SIDE_TOP) && (lv_obj_get_style_pad_top(obj, LV_PART_MAIN) == 0)) i_start++;
812 if((border_side & LV_BORDER_SIDE_BOTTOM) && (lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN) == 0)) i_end--;
813 }
814
815 for(i = i_start; i < i_end; i++) {
816 p1.y = (int32_t)((int32_t)h * i) / (chart->hdiv_cnt - 1);
817 p1.y += y_ofs;
818 p2.y = p1.y;
819
820 part_draw_dsc.class_p = MY_CLASS;
821 part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_HOR;
822 part_draw_dsc.p1 = &p1;
823 part_draw_dsc.p2 = &p2;
824 part_draw_dsc.id = i;
825
826 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
827 lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
828 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
829 }
830 }
831
832 if(chart->vdiv_cnt != 0) {
833 lv_coord_t x_ofs = obj->coords.x1 + pad_left - scroll_left;
834 p1.y = obj->coords.y1;
835 p2.y = obj->coords.y2;
836 i_start = 0;
837 i_end = chart->vdiv_cnt;
838 if(border_opa > LV_OPA_MIN && border_w > 0) {
839 if((border_side & LV_BORDER_SIDE_LEFT) && (lv_obj_get_style_pad_left(obj, LV_PART_MAIN) == 0)) i_start++;
840 if((border_side & LV_BORDER_SIDE_RIGHT) && (lv_obj_get_style_pad_right(obj, LV_PART_MAIN) == 0)) i_end--;
841 }
842
843 for(i = i_start; i < i_end; i++) {
844 p1.x = (int32_t)((int32_t)w * i) / (chart->vdiv_cnt - 1);
845 p1.x += x_ofs;
846 p2.x = p1.x;
847
848 part_draw_dsc.class_p = MY_CLASS;
849 part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_VER;
850 part_draw_dsc.p1 = &p1;
851 part_draw_dsc.p2 = &p2;
852 part_draw_dsc.id = i;
853
854 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
855 lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
856 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
857 }
858 }
859
860 part_draw_dsc.id = 0xFFFFFFFF;
861 part_draw_dsc.p1 = NULL;
862 part_draw_dsc.p2 = NULL;
863 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
864
865 draw_ctx->clip_area = clip_area_ori;
866 }
867
draw_series_line(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)868 static void draw_series_line(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
869 {
870 lv_area_t clip_area;
871 if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return;
872
873 const lv_area_t * clip_area_ori = draw_ctx->clip_area;
874 draw_ctx->clip_area = &clip_area;
875
876 lv_chart_t * chart = (lv_chart_t *)obj;
877 if(chart->point_cnt < 2) return;
878
879 uint16_t i;
880 lv_point_t p1;
881 lv_point_t p2;
882 lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
883 lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width;
884 lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width;
885 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
886 lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
887 lv_coord_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj);
888 lv_coord_t y_ofs = obj->coords.y1 + pad_top - lv_obj_get_scroll_top(obj);
889 lv_chart_series_t * ser;
890
891 lv_area_t series_clip_area;
892 bool mask_ret = _lv_area_intersect(&series_clip_area, &obj->coords, draw_ctx->clip_area);
893 if(mask_ret == false) return;
894
895 lv_draw_line_dsc_t line_dsc_default;
896 lv_draw_line_dsc_init(&line_dsc_default);
897 lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc_default);
898
899 lv_draw_rect_dsc_t point_dsc_default;
900 lv_draw_rect_dsc_init(&point_dsc_default);
901 lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default);
902
903 lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2;
904 lv_coord_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2;
905
906 /*Do not bother with line ending is the point will over it*/
907 if(LV_MIN(point_w, point_h) > line_dsc_default.width / 2) line_dsc_default.raw_end = 1;
908 if(line_dsc_default.width == 1) line_dsc_default.raw_end = 1;
909
910 /*If there are at least as much points as pixels then draw only vertical lines*/
911 bool crowded_mode = chart->point_cnt >= w ? true : false;
912
913 /*Go through all data lines*/
914 _LV_LL_READ_BACK(&chart->series_ll, ser) {
915 if(ser->hidden) continue;
916 line_dsc_default.color = ser->color;
917 point_dsc_default.bg_color = ser->color;
918
919 lv_coord_t start_point = lv_chart_get_x_start_point(obj, ser);
920
921 p1.x = x_ofs;
922 p2.x = x_ofs;
923
924 lv_coord_t p_act = start_point;
925 lv_coord_t p_prev = start_point;
926 int32_t y_tmp = (int32_t)((int32_t)ser->y_points[p_prev] - chart->ymin[ser->y_axis_sec]) * h;
927 y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
928 p2.y = h - y_tmp + y_ofs;
929
930 lv_obj_draw_part_dsc_t part_draw_dsc;
931 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
932 part_draw_dsc.class_p = MY_CLASS;
933 part_draw_dsc.type = LV_CHART_DRAW_PART_LINE_AND_POINT;
934 part_draw_dsc.part = LV_PART_ITEMS;
935 part_draw_dsc.line_dsc = &line_dsc_default;
936 part_draw_dsc.rect_dsc = &point_dsc_default;
937 part_draw_dsc.sub_part_ptr = ser;
938
939 lv_coord_t y_min = p2.y;
940 lv_coord_t y_max = p2.y;
941
942 for(i = 0; i < chart->point_cnt; i++) {
943 p1.x = p2.x;
944 p1.y = p2.y;
945
946 if(p1.x > clip_area_ori->x2 + point_w + 1) break;
947 p2.x = ((w * i) / (chart->point_cnt - 1)) + x_ofs;
948
949 p_act = (start_point + i) % chart->point_cnt;
950
951 y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
952 y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
953 p2.y = h - y_tmp + y_ofs;
954
955 if(p2.x < clip_area_ori->x1 - point_w - 1) {
956 p_prev = p_act;
957 continue;
958 }
959
960 /*Don't draw the first point. A second point is also required to draw the line*/
961 if(i != 0) {
962 if(crowded_mode) {
963 if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
964 /*Draw only one vertical line between the min and max y-values on the same x-value*/
965 y_max = LV_MAX(y_max, p2.y);
966 y_min = LV_MIN(y_min, p2.y);
967 if(p1.x != p2.x) {
968 lv_coord_t y_cur = p2.y;
969 p2.x--; /*It's already on the next x value*/
970 p1.x = p2.x;
971 p1.y = y_min;
972 p2.y = y_max;
973 if(p1.y == p2.y) p2.y++; /*If they are the same no line will be drawn*/
974 lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2);
975 p2.x++; /*Compensate the previous x--*/
976 y_min = y_cur; /*Start the line of the next x from the current last y*/
977 y_max = y_cur;
978 }
979 }
980 }
981 else {
982 lv_area_t point_area;
983 point_area.x1 = p1.x - point_w;
984 point_area.x2 = p1.x + point_w;
985 point_area.y1 = p1.y - point_h;
986 point_area.y2 = p1.y + point_h;
987
988 part_draw_dsc.id = i - 1;
989 part_draw_dsc.p1 = ser->y_points[p_prev] != LV_CHART_POINT_NONE ? &p1 : NULL;
990 part_draw_dsc.p2 = ser->y_points[p_act] != LV_CHART_POINT_NONE ? &p2 : NULL;
991 part_draw_dsc.draw_area = &point_area;
992 part_draw_dsc.value = ser->y_points[p_prev];
993
994 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
995
996 if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
997 lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2);
998 }
999
1000 if(point_w && point_h && ser->y_points[p_prev] != LV_CHART_POINT_NONE) {
1001 lv_draw_rect(draw_ctx, &point_dsc_default, &point_area);
1002 }
1003
1004 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1005 }
1006
1007 }
1008 p_prev = p_act;
1009 }
1010
1011 /*Draw the last point*/
1012 if(!crowded_mode && i == chart->point_cnt) {
1013
1014 if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1015 lv_area_t point_area;
1016 point_area.x1 = p2.x - point_w;
1017 point_area.x2 = p2.x + point_w;
1018 point_area.y1 = p2.y - point_h;
1019 point_area.y2 = p2.y + point_h;
1020
1021 part_draw_dsc.id = i - 1;
1022 part_draw_dsc.p1 = NULL;
1023 part_draw_dsc.p2 = NULL;
1024 part_draw_dsc.draw_area = &point_area;
1025 part_draw_dsc.value = ser->y_points[p_act];
1026
1027 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1028 lv_draw_rect(draw_ctx, &point_dsc_default, &point_area);
1029 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1030 }
1031 }
1032 }
1033
1034 draw_ctx->clip_area = clip_area_ori;
1035 }
1036
draw_series_scatter(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)1037 static void draw_series_scatter(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
1038 {
1039
1040 lv_area_t clip_area;
1041 if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return;
1042
1043 const lv_area_t * clip_area_ori = draw_ctx->clip_area;
1044 draw_ctx->clip_area = &clip_area;
1045
1046 lv_chart_t * chart = (lv_chart_t *)obj;
1047
1048 uint16_t i;
1049 lv_point_t p1;
1050 lv_point_t p2;
1051 lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1052 lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1053 lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
1054 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
1055 lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
1056 lv_coord_t x_ofs = obj->coords.x1 + pad_left + border_width - lv_obj_get_scroll_left(obj);
1057 lv_coord_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj);
1058 lv_chart_series_t * ser;
1059
1060 lv_draw_line_dsc_t line_dsc_default;
1061 lv_draw_line_dsc_init(&line_dsc_default);
1062 lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc_default);
1063
1064 lv_draw_rect_dsc_t point_dsc_default;
1065 lv_draw_rect_dsc_init(&point_dsc_default);
1066 lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default);
1067
1068 lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2;
1069 lv_coord_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2;
1070
1071 /*Do not bother with line ending is the point will over it*/
1072 if(LV_MIN(point_w, point_h) > line_dsc_default.width / 2) line_dsc_default.raw_end = 1;
1073 if(line_dsc_default.width == 1) line_dsc_default.raw_end = 1;
1074
1075 /*Go through all data lines*/
1076 _LV_LL_READ_BACK(&chart->series_ll, ser) {
1077 if(ser->hidden) continue;
1078 line_dsc_default.color = ser->color;
1079 point_dsc_default.bg_color = ser->color;
1080
1081 lv_coord_t start_point = lv_chart_get_x_start_point(obj, ser);
1082
1083 p1.x = x_ofs;
1084 p2.x = x_ofs;
1085
1086 lv_coord_t p_act = start_point;
1087 lv_coord_t p_prev = start_point;
1088 if(ser->y_points[p_act] != LV_CHART_POINT_CNT_DEF) {
1089 p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
1090 p2.x += x_ofs;
1091
1092 p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
1093 p2.y = h - p2.y;
1094 p2.y += y_ofs;
1095 }
1096 else {
1097 p2.x = LV_COORD_MIN;
1098 p2.y = LV_COORD_MIN;
1099 }
1100
1101 lv_obj_draw_part_dsc_t part_draw_dsc;
1102 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
1103 part_draw_dsc.part = LV_PART_ITEMS;
1104 part_draw_dsc.class_p = MY_CLASS;
1105 part_draw_dsc.type = LV_CHART_DRAW_PART_LINE_AND_POINT;
1106 part_draw_dsc.line_dsc = &line_dsc_default;
1107 part_draw_dsc.rect_dsc = &point_dsc_default;
1108 part_draw_dsc.sub_part_ptr = ser;
1109
1110 for(i = 0; i < chart->point_cnt; i++) {
1111 p1.x = p2.x;
1112 p1.y = p2.y;
1113
1114 p_act = (start_point + i) % chart->point_cnt;
1115 if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1116 p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h);
1117 p2.y = h - p2.y;
1118 p2.y += y_ofs;
1119
1120 p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w);
1121 p2.x += x_ofs;
1122 }
1123 else {
1124 p_prev = p_act;
1125 continue;
1126 }
1127
1128 /*Don't draw the first point. A second point is also required to draw the line*/
1129 if(i != 0) {
1130 lv_area_t point_area;
1131 point_area.x1 = p1.x - point_w;
1132 point_area.x2 = p1.x + point_w;
1133 point_area.y1 = p1.y - point_h;
1134 point_area.y2 = p1.y + point_h;
1135
1136 part_draw_dsc.id = i - 1;
1137 part_draw_dsc.p1 = ser->y_points[p_prev] != LV_CHART_POINT_NONE ? &p1 : NULL;
1138 part_draw_dsc.p2 = ser->y_points[p_act] != LV_CHART_POINT_NONE ? &p2 : NULL;
1139 part_draw_dsc.draw_area = &point_area;
1140 part_draw_dsc.value = ser->y_points[p_prev];
1141
1142 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1143
1144 if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1145 lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2);
1146 if(point_w && point_h) {
1147 lv_draw_rect(draw_ctx, &point_dsc_default, &point_area);
1148 }
1149 }
1150
1151 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1152 }
1153 p_prev = p_act;
1154 }
1155
1156 /*Draw the last point*/
1157 if(i == chart->point_cnt) {
1158
1159 if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1160 lv_area_t point_area;
1161 point_area.x1 = p2.x - point_w;
1162 point_area.x2 = p2.x + point_w;
1163 point_area.y1 = p2.y - point_h;
1164 point_area.y2 = p2.y + point_h;
1165
1166 part_draw_dsc.id = i - 1;
1167 part_draw_dsc.p1 = NULL;
1168 part_draw_dsc.p2 = NULL;
1169 part_draw_dsc.draw_area = &point_area;
1170 part_draw_dsc.value = ser->y_points[p_act];
1171 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1172 lv_draw_rect(draw_ctx, &point_dsc_default, &point_area);
1173 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1174 }
1175 }
1176 }
1177 draw_ctx->clip_area = clip_area_ori;
1178 }
1179
draw_series_bar(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)1180 static void draw_series_bar(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
1181 {
1182 lv_area_t clip_area;
1183 if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return;
1184
1185 const lv_area_t * clip_area_ori = draw_ctx->clip_area;
1186 draw_ctx->clip_area = &clip_area;
1187
1188 lv_chart_t * chart = (lv_chart_t *)obj;
1189
1190 uint16_t i;
1191 lv_area_t col_a;
1192 lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1193 lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
1194 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
1195 lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
1196 int32_t y_tmp;
1197 lv_chart_series_t * ser;
1198 uint32_t ser_cnt = _lv_ll_get_len(&chart->series_ll);
1199 int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
1200 LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the column on ~adjacent X*/
1201 lv_coord_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt;
1202 int32_t ser_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
1203 LV_PART_ITEMS) * chart->zoom_x) >> 8; /*Gap between the columns on the ~same X*/
1204 lv_coord_t col_w = (block_w - (ser_cnt - 1) * ser_gap) / ser_cnt;
1205 if(col_w < 1) col_w = 1;
1206
1207 lv_coord_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1208 lv_coord_t x_ofs = pad_left - lv_obj_get_scroll_left(obj) + border_w;
1209 lv_coord_t y_ofs = pad_top - lv_obj_get_scroll_top(obj) + border_w;
1210
1211 lv_draw_rect_dsc_t col_dsc;
1212 lv_draw_rect_dsc_init(&col_dsc);
1213 lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc);
1214 col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE;
1215 col_dsc.bg_opa = LV_OPA_COVER;
1216
1217 /*Make the cols longer with `radius` to clip the rounding from the bottom*/
1218 col_a.y2 = obj->coords.y2 + col_dsc.radius;
1219
1220 lv_obj_draw_part_dsc_t part_draw_dsc;
1221 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
1222 part_draw_dsc.part = LV_PART_ITEMS;
1223 part_draw_dsc.class_p = MY_CLASS;
1224 part_draw_dsc.type = LV_CHART_DRAW_PART_BAR;
1225
1226 /*Go through all points*/
1227 for(i = 0; i < chart->point_cnt; i++) {
1228 lv_coord_t x_act = (int32_t)((int32_t)(w - block_w) * i) / (chart->point_cnt - 1) + obj->coords.x1 + x_ofs;
1229
1230 part_draw_dsc.id = i;
1231
1232 /*Draw the current point of all data line*/
1233 _LV_LL_READ_BACK(&chart->series_ll, ser) {
1234 if(ser->hidden) continue;
1235
1236 lv_coord_t start_point = lv_chart_get_x_start_point(obj, ser);
1237
1238 col_a.x1 = x_act;
1239 col_a.x2 = col_a.x1 + col_w - 1;
1240 x_act += col_w + ser_gap;
1241
1242 if(col_a.x2 < clip_area.x1) continue;
1243 if(col_a.x1 > clip_area.x2) break;
1244
1245 col_dsc.bg_color = ser->color;
1246
1247 lv_coord_t p_act = (start_point + i) % chart->point_cnt;
1248 y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h;
1249 y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]);
1250 col_a.y1 = h - y_tmp + obj->coords.y1 + y_ofs;
1251
1252 if(ser->y_points[p_act] != LV_CHART_POINT_NONE) {
1253 part_draw_dsc.draw_area = &col_a;
1254 part_draw_dsc.rect_dsc = &col_dsc;
1255 part_draw_dsc.sub_part_ptr = ser;
1256 part_draw_dsc.value = ser->y_points[p_act];
1257 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1258 lv_draw_rect(draw_ctx, &col_dsc, &col_a);
1259 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1260 }
1261 }
1262 }
1263 draw_ctx->clip_area = clip_area_ori;
1264 }
1265
draw_cursors(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)1266 static void draw_cursors(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
1267 {
1268 LV_ASSERT_OBJ(obj, MY_CLASS);
1269
1270 lv_chart_t * chart = (lv_chart_t *)obj;
1271 if(_lv_ll_is_empty(&chart->cursor_ll)) return;
1272
1273 lv_area_t clip_area;
1274 if(!_lv_area_intersect(&clip_area, draw_ctx->clip_area, &obj->coords)) return;
1275
1276 const lv_area_t * clip_area_ori = draw_ctx->clip_area;
1277 draw_ctx->clip_area = &clip_area;
1278
1279 lv_point_t p1;
1280 lv_point_t p2;
1281 lv_chart_cursor_t * cursor;
1282
1283 lv_draw_line_dsc_t line_dsc_ori;
1284 lv_draw_line_dsc_init(&line_dsc_ori);
1285 lv_obj_init_draw_line_dsc(obj, LV_PART_CURSOR, &line_dsc_ori);
1286
1287 lv_draw_rect_dsc_t point_dsc_ori;
1288 lv_draw_rect_dsc_init(&point_dsc_ori);
1289 point_dsc_ori.bg_opa = line_dsc_ori.opa;
1290 point_dsc_ori.radius = LV_RADIUS_CIRCLE;
1291
1292 lv_draw_line_dsc_t line_dsc_tmp;
1293 lv_draw_rect_dsc_t point_dsc_tmp;
1294
1295 lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
1296 lv_coord_t point_h = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2;
1297
1298 lv_obj_draw_part_dsc_t part_draw_dsc;
1299 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
1300 part_draw_dsc.line_dsc = &line_dsc_tmp;
1301 part_draw_dsc.rect_dsc = &point_dsc_tmp;
1302 part_draw_dsc.part = LV_PART_CURSOR;
1303 part_draw_dsc.class_p = MY_CLASS;
1304 part_draw_dsc.type = LV_CHART_DRAW_PART_CURSOR;
1305
1306 /*Go through all cursor lines*/
1307 _LV_LL_READ_BACK(&chart->cursor_ll, cursor) {
1308 lv_memcpy(&line_dsc_tmp, &line_dsc_ori, sizeof(lv_draw_line_dsc_t));
1309 lv_memcpy(&point_dsc_tmp, &point_dsc_ori, sizeof(lv_draw_rect_dsc_t));
1310 line_dsc_tmp.color = cursor->color;
1311 point_dsc_tmp.bg_color = cursor->color;
1312
1313 part_draw_dsc.p1 = &p1;
1314 part_draw_dsc.p2 = &p2;
1315
1316 lv_coord_t cx;
1317 lv_coord_t cy;
1318 if(cursor->pos_set) {
1319 cx = cursor->pos.x;
1320 cy = cursor->pos.y;
1321 }
1322 else {
1323 if(cursor->point_id == LV_CHART_POINT_NONE) continue;
1324 lv_point_t p;
1325 lv_chart_get_point_pos_by_id(obj, cursor->ser, cursor->point_id, &p);
1326 cx = p.x;
1327 cy = p.y;
1328 }
1329
1330 cx += obj->coords.x1;
1331 cy += obj->coords.y1;
1332
1333 lv_area_t point_area;
1334 bool draw_point = point_w && point_h;
1335 if(draw_point) {
1336 point_area.x1 = cx - point_w;
1337 point_area.x2 = cx + point_w;
1338 point_area.y1 = cy - point_h;
1339 point_area.y2 = cy + point_h;
1340
1341 part_draw_dsc.draw_area = &point_area;
1342 }
1343 else {
1344 part_draw_dsc.draw_area = NULL;
1345 }
1346
1347 if(cursor->dir & LV_DIR_HOR) {
1348 p1.x = cursor->dir & LV_DIR_LEFT ? obj->coords.x1 : cx;
1349 p1.y = cy;
1350 p2.x = cursor->dir & LV_DIR_RIGHT ? obj->coords.x2 : cx;
1351 p2.y = p1.y;
1352
1353 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1354 lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2);
1355
1356 if(draw_point) {
1357 lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area);
1358 }
1359
1360 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1361 }
1362
1363 if(cursor->dir & LV_DIR_VER) {
1364 p1.x = cx;
1365 p1.y = cursor->dir & LV_DIR_TOP ? obj->coords.y1 : cy;
1366 p2.x = p1.x;
1367 p2.y = cursor->dir & LV_DIR_BOTTOM ? obj->coords.y2 : cy;
1368
1369 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1370 lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2);
1371
1372 if(draw_point) {
1373 lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area);
1374 }
1375
1376 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1377 }
1378 }
1379
1380 draw_ctx->clip_area = clip_area_ori;
1381 }
1382
draw_y_ticks(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx,lv_chart_axis_t axis)1383 static void draw_y_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis)
1384 {
1385 lv_chart_t * chart = (lv_chart_t *)obj;
1386
1387 lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis);
1388
1389 if(!t->label_en && !t->major_len && !t->minor_len) return;
1390 if(t->major_cnt <= 1) return;
1391 uint32_t total_tick_num = (t->major_cnt - 1) * (t->minor_cnt);
1392 if(total_tick_num == 0) return;
1393
1394 uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_Y ? 0 : 1;
1395
1396 uint32_t i;
1397
1398 lv_point_t p1;
1399 lv_point_t p2;
1400
1401 lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1402 lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN);
1403 lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8;
1404 lv_coord_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj);
1405
1406 lv_coord_t label_gap;
1407 lv_coord_t x_ofs;
1408 if(axis == LV_CHART_AXIS_PRIMARY_Y) {
1409 label_gap = lv_obj_get_style_pad_left(obj, LV_PART_TICKS);
1410 x_ofs = obj->coords.x1;
1411 }
1412 else {
1413 label_gap = lv_obj_get_style_pad_right(obj, LV_PART_TICKS);
1414 x_ofs = obj->coords.x2;
1415 }
1416
1417 lv_coord_t major_len = t->major_len;
1418 lv_coord_t minor_len = t->minor_len;
1419 /*tick lines on secondary y axis are drawn in other direction*/
1420 if(axis == LV_CHART_AXIS_SECONDARY_Y) {
1421 major_len *= -1;
1422 minor_len *= -1;
1423 }
1424
1425 lv_draw_line_dsc_t line_dsc;
1426 lv_draw_line_dsc_init(&line_dsc);
1427 lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc);
1428
1429 lv_draw_label_dsc_t label_dsc;
1430 lv_draw_label_dsc_init(&label_dsc);
1431 lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc);
1432
1433 lv_obj_draw_part_dsc_t part_draw_dsc;
1434 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
1435 part_draw_dsc.class_p = MY_CLASS;
1436 part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL;
1437 part_draw_dsc.id = axis;
1438 part_draw_dsc.part = LV_PART_TICKS;
1439 part_draw_dsc.line_dsc = &line_dsc;
1440 part_draw_dsc.label_dsc = &label_dsc;
1441
1442 for(i = 0; i <= total_tick_num; i++) {
1443 /*draw a line at moving y position*/
1444 p2.y = p1.y = y_ofs + (int32_t)((int32_t)(h - line_dsc.width) * i) / total_tick_num;
1445
1446 /*first point of the tick*/
1447 p1.x = x_ofs;
1448
1449 /*move extra pixel out of chart boundary*/
1450 if(axis == LV_CHART_AXIS_PRIMARY_Y) p1.x--;
1451 else p1.x++;
1452
1453 /*second point of the tick*/
1454 bool major = false;
1455 if(i % t->minor_cnt == 0) major = true;
1456
1457 if(major) p2.x = p1.x - major_len; /*major tick*/
1458 else p2.x = p1.x - minor_len; /*minor tick*/
1459
1460 part_draw_dsc.p1 = &p1;
1461 part_draw_dsc.p2 = &p2;
1462
1463 int32_t tick_value = lv_map(total_tick_num - i, 0, total_tick_num, chart->ymin[sec_axis], chart->ymax[sec_axis]);
1464 part_draw_dsc.value = tick_value;
1465
1466 /*add text only to major tick*/
1467 if(major && t->label_en) {
1468 char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH];
1469 lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value);
1470 part_draw_dsc.label_dsc = &label_dsc;
1471 part_draw_dsc.text = buf;
1472 part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH;
1473 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1474
1475 /*reserve appropriate area*/
1476 lv_point_t size;
1477 lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX,
1478 LV_TEXT_FLAG_NONE);
1479
1480 /*set the area at some distance of the major tick len left of the tick*/
1481 lv_area_t a;
1482 a.y1 = p2.y - size.y / 2;
1483 a.y2 = p2.y + size.y / 2;
1484
1485 if(!sec_axis) {
1486 a.x1 = p2.x - size.x - label_gap;
1487 a.x2 = p2.x - label_gap;
1488 }
1489 else {
1490 a.x1 = p2.x + label_gap;
1491 a.x2 = p2.x + size.x + label_gap;
1492 }
1493
1494 if(a.y2 >= obj->coords.y1 &&
1495 a.y1 <= obj->coords.y2) {
1496 lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL);
1497 }
1498 }
1499 else {
1500 part_draw_dsc.label_dsc = NULL;
1501 part_draw_dsc.text = NULL;
1502 part_draw_dsc.text_length = 0;
1503 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1504 }
1505
1506 if(p1.y + line_dsc.width / 2 >= obj->coords.y1 &&
1507 p2.y - line_dsc.width / 2 <= obj->coords.y2) {
1508 lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
1509 }
1510
1511 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1512 }
1513 }
1514
draw_x_ticks(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx,lv_chart_axis_t axis)1515 static void draw_x_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis)
1516 {
1517 lv_chart_t * chart = (lv_chart_t *)obj;
1518
1519 lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis);
1520 if(t->major_cnt <= 1) return;
1521 if(!t->label_en && !t->major_len && !t->minor_len) return;
1522 uint32_t total_tick_num = (t->major_cnt - 1) * (t->minor_cnt);
1523 if(total_tick_num == 0) return;
1524
1525 uint32_t i;
1526 lv_point_t p1;
1527 lv_point_t p2;
1528
1529 lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1530 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
1531
1532 lv_draw_label_dsc_t label_dsc;
1533 lv_draw_label_dsc_init(&label_dsc);
1534 lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc);
1535
1536 lv_coord_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj);
1537 lv_coord_t y_ofs;
1538 lv_coord_t label_gap;
1539 if(axis == LV_CHART_AXIS_PRIMARY_X) {
1540 label_gap = t->label_en ? lv_obj_get_style_pad_bottom(obj, LV_PART_TICKS) : 0;
1541 y_ofs = obj->coords.y2 + 1;
1542 }
1543 else {
1544 label_gap = t->label_en ? lv_obj_get_style_pad_top(obj, LV_PART_TICKS) : 0;
1545 y_ofs = obj->coords.y1 - 1;
1546 }
1547
1548 if(axis == LV_CHART_AXIS_PRIMARY_X) {
1549 if(y_ofs > draw_ctx->clip_area->y2) return;
1550 if(y_ofs + label_gap + label_dsc.font->line_height + t->major_len < draw_ctx->clip_area->y1) return;
1551 }
1552
1553 lv_draw_line_dsc_t line_dsc;
1554 lv_draw_line_dsc_init(&line_dsc);
1555 lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc);
1556 line_dsc.dash_gap = 0;
1557 line_dsc.dash_width = 0;
1558
1559 lv_obj_draw_part_dsc_t part_draw_dsc;
1560 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx);
1561 part_draw_dsc.class_p = MY_CLASS;
1562 part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL;
1563 part_draw_dsc.id = LV_CHART_AXIS_PRIMARY_X;
1564 part_draw_dsc.part = LV_PART_TICKS;
1565 part_draw_dsc.label_dsc = &label_dsc;
1566 part_draw_dsc.line_dsc = &line_dsc;
1567
1568 uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_X ? 0 : 1;
1569
1570 /*The columns ticks should be aligned to the center of blocks*/
1571 if(chart->type == LV_CHART_TYPE_BAR) {
1572 int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
1573 LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the columns on ~adjacent X*/
1574 lv_coord_t block_w = (w + block_gap) / (chart->point_cnt);
1575
1576 x_ofs += (block_w - block_gap) / 2;
1577 w -= block_w - block_gap;
1578 }
1579
1580 p1.y = y_ofs;
1581 for(i = 0; i <= total_tick_num; i++) { /*one extra loop - it may not exist in the list, empty label*/
1582 bool major = false;
1583 if(i % t->minor_cnt == 0) major = true;
1584
1585 /*draw a line at moving x position*/
1586 p2.x = p1.x = x_ofs + (int32_t)((int32_t)(w - line_dsc.width) * i) / total_tick_num;
1587
1588 if(sec_axis) p2.y = p1.y - (major ? t->major_len : t->minor_len);
1589 else p2.y = p1.y + (major ? t->major_len : t->minor_len);
1590
1591 part_draw_dsc.p1 = &p1;
1592 part_draw_dsc.p2 = &p2;
1593
1594 /*add text only to major tick*/
1595 int32_t tick_value;
1596 if(chart->type == LV_CHART_TYPE_SCATTER) {
1597 tick_value = lv_map(i, 0, total_tick_num, chart->xmin[sec_axis], chart->xmax[sec_axis]);
1598 }
1599 else {
1600 tick_value = i / t->minor_cnt;
1601 }
1602 part_draw_dsc.value = tick_value;
1603
1604 if(major && t->label_en) {
1605 char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH];
1606 lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value);
1607 part_draw_dsc.label_dsc = &label_dsc;
1608 part_draw_dsc.text = buf;
1609 part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH;
1610
1611 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1612
1613 /*reserve appropriate area*/
1614 lv_point_t size;
1615 lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX,
1616 LV_TEXT_FLAG_NONE);
1617
1618 /*set the area at some distance of the major tick len under of the tick*/
1619 lv_area_t a;
1620 a.x1 = (p2.x - size.x / 2);
1621 a.x2 = (p2.x + size.x / 2);
1622 if(sec_axis) {
1623 a.y2 = p2.y - label_gap;
1624 a.y1 = a.y2 - size.y;
1625 }
1626 else {
1627 a.y1 = p2.y + label_gap;
1628 a.y2 = a.y1 + size.y;
1629 }
1630
1631 if(a.x2 >= obj->coords.x1 &&
1632 a.x1 <= obj->coords.x2) {
1633 lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL);
1634 }
1635 }
1636 else {
1637 part_draw_dsc.label_dsc = NULL;
1638 part_draw_dsc.text = NULL;
1639 part_draw_dsc.text_length = 0;
1640 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
1641 }
1642
1643 if(p1.x + line_dsc.width / 2 >= obj->coords.x1 &&
1644 p2.x - line_dsc.width / 2 <= obj->coords.x2) {
1645 lv_draw_line(draw_ctx, &line_dsc, &p1, &p2);
1646 }
1647
1648 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
1649 }
1650 }
1651
draw_axes(lv_obj_t * obj,lv_draw_ctx_t * draw_ctx)1652 static void draw_axes(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
1653 {
1654 draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_Y);
1655 draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_Y);
1656 draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_X);
1657 draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_X);
1658 }
1659
1660 /**
1661 * Get the nearest index to an X coordinate
1662 * @param chart pointer to a chart object
1663 * @param coord the coordination of the point relative to the series area.
1664 * @return the found index
1665 */
get_index_from_x(lv_obj_t * obj,lv_coord_t x)1666 static uint32_t get_index_from_x(lv_obj_t * obj, lv_coord_t x)
1667 {
1668 lv_chart_t * chart = (lv_chart_t *)obj;
1669 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
1670 lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1671 x -= pad_left;
1672
1673 if(x < 0) return 0;
1674 if(x > w) return chart->point_cnt - 1;
1675 if(chart->type == LV_CHART_TYPE_LINE) return (x * (chart->point_cnt - 1) + w / 2) / w;
1676 if(chart->type == LV_CHART_TYPE_BAR) return (x * chart->point_cnt) / w;
1677
1678 return 0;
1679 }
1680
invalidate_point(lv_obj_t * obj,uint16_t i)1681 static void invalidate_point(lv_obj_t * obj, uint16_t i)
1682 {
1683 lv_chart_t * chart = (lv_chart_t *)obj;
1684 if(i >= chart->point_cnt) return;
1685
1686 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8;
1687 lv_coord_t scroll_left = lv_obj_get_scroll_left(obj);
1688
1689 /*In shift mode the whole chart changes so the whole object*/
1690 if(chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT) {
1691 lv_obj_invalidate(obj);
1692 return;
1693 }
1694
1695 if(chart->type == LV_CHART_TYPE_LINE) {
1696 lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1697 lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1698 lv_coord_t x_ofs = obj->coords.x1 + pleft + bwidth - scroll_left;
1699 lv_coord_t line_width = lv_obj_get_style_line_width(obj, LV_PART_ITEMS);
1700 lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR);
1701
1702 lv_area_t coords;
1703 lv_area_copy(&coords, &obj->coords);
1704 coords.y1 -= line_width + point_w;
1705 coords.y2 += line_width + point_w;
1706
1707 if(i < chart->point_cnt - 1) {
1708 coords.x1 = ((w * i) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
1709 coords.x2 = ((w * (i + 1)) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
1710 lv_obj_invalidate_area(obj, &coords);
1711 }
1712
1713 if(i > 0) {
1714 coords.x1 = ((w * (i - 1)) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w;
1715 coords.x2 = ((w * i) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w;
1716 lv_obj_invalidate_area(obj, &coords);
1717 }
1718 }
1719 else if(chart->type == LV_CHART_TYPE_BAR) {
1720 lv_area_t col_a;
1721 int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj,
1722 LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the column on ~adjacent X*/
1723
1724 lv_coord_t block_w = (w + block_gap) / chart->point_cnt;
1725
1726 lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN);
1727 lv_coord_t x_act;
1728 x_act = (int32_t)((int32_t)(block_w) * i) ;
1729 x_act += obj->coords.x1 + bwidth + lv_obj_get_style_pad_left(obj, LV_PART_MAIN);
1730
1731 lv_obj_get_coords(obj, &col_a);
1732 col_a.x1 = x_act - scroll_left;
1733 col_a.x2 = col_a.x1 + block_w;
1734 col_a.x1 -= block_gap;
1735
1736 lv_obj_invalidate_area(obj, &col_a);
1737 }
1738 else {
1739 lv_obj_invalidate(obj);
1740 }
1741 }
1742
new_points_alloc(lv_obj_t * obj,lv_chart_series_t * ser,uint32_t cnt,lv_coord_t ** a)1743 static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, lv_coord_t ** a)
1744 {
1745 if((*a) == NULL) return;
1746
1747 lv_chart_t * chart = (lv_chart_t *) obj;
1748 uint32_t point_cnt_old = chart->point_cnt;
1749 uint32_t i;
1750
1751 if(ser->start_point != 0) {
1752 lv_coord_t * new_points = lv_mem_alloc(sizeof(lv_coord_t) * cnt);
1753 LV_ASSERT_MALLOC(new_points);
1754 if(new_points == NULL) return;
1755
1756 if(cnt >= point_cnt_old) {
1757 for(i = 0; i < point_cnt_old; i++) {
1758 new_points[i] =
1759 (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
1760 }
1761 for(i = point_cnt_old; i < cnt; i++) {
1762 new_points[i] = LV_CHART_POINT_NONE; /*Fill up the rest with default value*/
1763 }
1764 }
1765 else {
1766 for(i = 0; i < cnt; i++) {
1767 new_points[i] =
1768 (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/
1769 }
1770 }
1771
1772 /*Switch over pointer from old to new*/
1773 lv_mem_free((*a));
1774 (*a) = new_points;
1775 }
1776 else {
1777 (*a) = lv_mem_realloc((*a), sizeof(lv_coord_t) * cnt);
1778 LV_ASSERT_MALLOC((*a));
1779 if((*a) == NULL) return;
1780 /*Initialize the new points*/
1781 if(cnt > point_cnt_old) {
1782 for(i = point_cnt_old - 1; i < cnt; i++) {
1783 (*a)[i] = LV_CHART_POINT_NONE;
1784 }
1785 }
1786 }
1787 }
1788
get_tick_gsc(lv_obj_t * obj,lv_chart_axis_t axis)1789 lv_chart_tick_dsc_t * get_tick_gsc(lv_obj_t * obj, lv_chart_axis_t axis)
1790 {
1791 lv_chart_t * chart = (lv_chart_t *) obj;
1792 switch(axis) {
1793 case LV_CHART_AXIS_PRIMARY_Y:
1794 return &chart->tick[0];
1795 case LV_CHART_AXIS_PRIMARY_X:
1796 return &chart->tick[1];
1797 case LV_CHART_AXIS_SECONDARY_Y:
1798 return &chart->tick[2];
1799 case LV_CHART_AXIS_SECONDARY_X:
1800 return &chart->tick[3];
1801 default:
1802 return NULL;
1803 }
1804 }
1805
1806 #endif
1807