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