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