acid-drop- Hacking the planet from a LilyGo T-Deck using custom firmware |
git clone git://git.acid.vegas/acid-drop.git |
Log | Files | Refs | Archive | README | LICENSE |
lv_chart.c (61841B)
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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 */ 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 */ 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 */ 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 */ 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 more points than pixels 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 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 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 1186 lv_chart_t * chart = (lv_chart_t *)obj; 1187 1188 uint16_t i; 1189 lv_area_t col_a; 1190 lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); 1191 lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN); 1192 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; 1193 lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; 1194 int32_t y_tmp; 1195 lv_chart_series_t * ser; 1196 uint32_t ser_cnt = _lv_ll_get_len(&chart->series_ll); 1197 int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, 1198 LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the column on ~adjacent X*/ 1199 lv_coord_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt; 1200 lv_coord_t col_w = block_w / ser_cnt; 1201 int32_t ser_gap = ((int32_t)lv_obj_get_style_pad_column(obj, 1202 LV_PART_ITEMS) * chart->zoom_x) >> 8; /*Gap between the column on the ~same X*/ 1203 lv_coord_t x_ofs = pad_left - lv_obj_get_scroll_left(obj); 1204 lv_coord_t y_ofs = pad_top - lv_obj_get_scroll_top(obj); 1205 1206 lv_draw_rect_dsc_t col_dsc; 1207 lv_draw_rect_dsc_init(&col_dsc); 1208 lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc); 1209 col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE; 1210 col_dsc.bg_opa = LV_OPA_COVER; 1211 1212 /*Make the cols longer with `radius` to clip the rounding from the bottom*/ 1213 col_a.y2 = obj->coords.y2 + col_dsc.radius; 1214 1215 lv_obj_draw_part_dsc_t part_draw_dsc; 1216 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); 1217 part_draw_dsc.part = LV_PART_ITEMS; 1218 part_draw_dsc.class_p = MY_CLASS; 1219 part_draw_dsc.type = LV_CHART_DRAW_PART_BAR; 1220 1221 /*Go through all points*/ 1222 for(i = 0; i < chart->point_cnt; i++) { 1223 lv_coord_t x_act = (int32_t)((int32_t)(w + block_gap) * i) / (chart->point_cnt) + obj->coords.x1 + x_ofs; 1224 1225 part_draw_dsc.id = i; 1226 1227 /*Draw the current point of all data line*/ 1228 _LV_LL_READ_BACK(&chart->series_ll, ser) { 1229 if(ser->hidden) continue; 1230 lv_coord_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0; 1231 1232 col_a.x1 = x_act; 1233 col_a.x2 = col_a.x1 + col_w - ser_gap - 1; 1234 x_act += col_w; 1235 1236 if(col_a.x2 < clip_area.x1) continue; 1237 if(col_a.x1 > clip_area.x2) break; 1238 1239 col_dsc.bg_color = ser->color; 1240 1241 lv_coord_t p_act = (start_point + i) % chart->point_cnt; 1242 y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h; 1243 y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]); 1244 col_a.y1 = h - y_tmp + obj->coords.y1 + y_ofs; 1245 1246 if(ser->y_points[p_act] != LV_CHART_POINT_NONE) { 1247 part_draw_dsc.draw_area = &col_a; 1248 part_draw_dsc.rect_dsc = &col_dsc; 1249 part_draw_dsc.sub_part_ptr = ser; 1250 part_draw_dsc.value = ser->y_points[p_act]; 1251 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); 1252 lv_draw_rect(draw_ctx, &col_dsc, &col_a); 1253 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); 1254 } 1255 } 1256 } 1257 draw_ctx->clip_area = clip_area_ori; 1258 } 1259 1260 static void draw_cursors(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) 1261 { 1262 LV_ASSERT_OBJ(obj, MY_CLASS); 1263 1264 lv_chart_t * chart = (lv_chart_t *)obj; 1265 if(_lv_ll_is_empty(&chart->cursor_ll)) return; 1266 1267 lv_area_t clip_area; 1268 if(!_lv_area_intersect(&clip_area, draw_ctx->clip_area, &obj->coords)) return; 1269 1270 const lv_area_t * clip_area_ori = draw_ctx->clip_area; 1271 draw_ctx->clip_area = &clip_area; 1272 1273 lv_point_t p1; 1274 lv_point_t p2; 1275 lv_chart_cursor_t * cursor; 1276 1277 lv_draw_line_dsc_t line_dsc_ori; 1278 lv_draw_line_dsc_init(&line_dsc_ori); 1279 lv_obj_init_draw_line_dsc(obj, LV_PART_CURSOR, &line_dsc_ori); 1280 1281 lv_draw_rect_dsc_t point_dsc_ori; 1282 lv_draw_rect_dsc_init(&point_dsc_ori); 1283 point_dsc_ori.bg_opa = line_dsc_ori.opa; 1284 point_dsc_ori.radius = LV_RADIUS_CIRCLE; 1285 1286 lv_draw_line_dsc_t line_dsc_tmp; 1287 lv_draw_rect_dsc_t point_dsc_tmp; 1288 1289 lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2; 1290 lv_coord_t point_h = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2; 1291 1292 lv_obj_draw_part_dsc_t part_draw_dsc; 1293 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); 1294 part_draw_dsc.line_dsc = &line_dsc_tmp; 1295 part_draw_dsc.rect_dsc = &point_dsc_tmp; 1296 part_draw_dsc.part = LV_PART_CURSOR; 1297 part_draw_dsc.class_p = MY_CLASS; 1298 part_draw_dsc.type = LV_CHART_DRAW_PART_CURSOR; 1299 1300 /*Go through all cursor lines*/ 1301 _LV_LL_READ_BACK(&chart->cursor_ll, cursor) { 1302 lv_memcpy(&line_dsc_tmp, &line_dsc_ori, sizeof(lv_draw_line_dsc_t)); 1303 lv_memcpy(&point_dsc_tmp, &point_dsc_ori, sizeof(lv_draw_rect_dsc_t)); 1304 line_dsc_tmp.color = cursor->color; 1305 point_dsc_tmp.bg_color = cursor->color; 1306 1307 part_draw_dsc.p1 = &p1; 1308 part_draw_dsc.p2 = &p2; 1309 1310 lv_coord_t cx; 1311 lv_coord_t cy; 1312 if(cursor->pos_set) { 1313 cx = cursor->pos.x; 1314 cy = cursor->pos.y; 1315 } 1316 else { 1317 if(cursor->point_id == LV_CHART_POINT_NONE) continue; 1318 lv_point_t p; 1319 lv_chart_get_point_pos_by_id(obj, cursor->ser, cursor->point_id, &p); 1320 cx = p.x; 1321 cy = p.y; 1322 } 1323 1324 cx += obj->coords.x1; 1325 cy += obj->coords.y1; 1326 1327 lv_area_t point_area; 1328 if(point_w && point_h) { 1329 point_area.x1 = cx - point_w; 1330 point_area.x2 = cx + point_w; 1331 point_area.y1 = cy - point_h; 1332 point_area.y2 = cy + point_h; 1333 1334 part_draw_dsc.draw_area = &point_area; 1335 } 1336 else { 1337 part_draw_dsc.draw_area = NULL; 1338 } 1339 1340 if(cursor->dir & LV_DIR_HOR) { 1341 p1.x = cursor->dir & LV_DIR_LEFT ? obj->coords.x1 : cx; 1342 p1.y = cy; 1343 p2.x = cursor->dir & LV_DIR_RIGHT ? obj->coords.x2 : cx; 1344 p2.y = p1.y; 1345 1346 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); 1347 lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2); 1348 lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area); 1349 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); 1350 } 1351 1352 if(cursor->dir & LV_DIR_VER) { 1353 p1.x = cx; 1354 p1.y = cursor->dir & LV_DIR_TOP ? obj->coords.y1 : cy; 1355 p2.x = p1.x; 1356 p2.y = cursor->dir & LV_DIR_BOTTOM ? obj->coords.y2 : cy; 1357 1358 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); 1359 lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2); 1360 lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area); 1361 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); 1362 } 1363 } 1364 1365 draw_ctx->clip_area = clip_area_ori; 1366 } 1367 1368 static void draw_y_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis) 1369 { 1370 lv_chart_t * chart = (lv_chart_t *)obj; 1371 1372 lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis); 1373 1374 if(t->major_cnt <= 1) return; 1375 if(!t->label_en && !t->major_len && !t->minor_len) return; 1376 1377 uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_Y ? 0 : 1; 1378 1379 uint32_t i; 1380 1381 lv_point_t p1; 1382 lv_point_t p2; 1383 1384 lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); 1385 lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN); 1386 lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; 1387 lv_coord_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj); 1388 1389 lv_coord_t label_gap; 1390 lv_coord_t x_ofs; 1391 if(axis == LV_CHART_AXIS_PRIMARY_Y) { 1392 label_gap = lv_obj_get_style_pad_left(obj, LV_PART_TICKS); 1393 x_ofs = obj->coords.x1; 1394 } 1395 else { 1396 label_gap = lv_obj_get_style_pad_right(obj, LV_PART_TICKS); 1397 x_ofs = obj->coords.x2; 1398 } 1399 1400 lv_coord_t major_len = t->major_len; 1401 lv_coord_t minor_len = t->minor_len; 1402 /*tick lines on secondary y axis are drawn in other direction*/ 1403 if(axis == LV_CHART_AXIS_SECONDARY_Y) { 1404 major_len *= -1; 1405 minor_len *= -1; 1406 } 1407 1408 lv_draw_line_dsc_t line_dsc; 1409 lv_draw_line_dsc_init(&line_dsc); 1410 lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc); 1411 1412 lv_draw_label_dsc_t label_dsc; 1413 lv_draw_label_dsc_init(&label_dsc); 1414 lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc); 1415 1416 lv_obj_draw_part_dsc_t part_draw_dsc; 1417 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); 1418 part_draw_dsc.class_p = MY_CLASS; 1419 part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL; 1420 part_draw_dsc.id = axis; 1421 part_draw_dsc.part = LV_PART_TICKS; 1422 part_draw_dsc.line_dsc = &line_dsc; 1423 part_draw_dsc.label_dsc = &label_dsc; 1424 1425 uint32_t total_tick_num = (t->major_cnt - 1) * (t->minor_cnt); 1426 for(i = 0; i <= total_tick_num; i++) { 1427 /*draw a line at moving y position*/ 1428 p2.y = p1.y = y_ofs + (int32_t)((int32_t)(h - line_dsc.width) * i) / total_tick_num; 1429 1430 /*first point of the tick*/ 1431 p1.x = x_ofs; 1432 1433 /*move extra pixel out of chart boundary*/ 1434 if(axis == LV_CHART_AXIS_PRIMARY_Y) p1.x--; 1435 else p1.x++; 1436 1437 /*second point of the tick*/ 1438 bool major = false; 1439 if(i % t->minor_cnt == 0) major = true; 1440 1441 if(major) p2.x = p1.x - major_len; /*major tick*/ 1442 else p2.x = p1.x - minor_len; /*minor tick*/ 1443 1444 part_draw_dsc.p1 = &p1; 1445 part_draw_dsc.p2 = &p2; 1446 1447 int32_t tick_value = lv_map(total_tick_num - i, 0, total_tick_num, chart->ymin[sec_axis], chart->ymax[sec_axis]); 1448 part_draw_dsc.value = tick_value; 1449 1450 /*add text only to major tick*/ 1451 if(major && t->label_en) { 1452 char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH]; 1453 lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value); 1454 part_draw_dsc.label_dsc = &label_dsc; 1455 part_draw_dsc.text = buf; 1456 part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH; 1457 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); 1458 1459 /*reserve appropriate area*/ 1460 lv_point_t size; 1461 lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX, 1462 LV_TEXT_FLAG_NONE); 1463 1464 /*set the area at some distance of the major tick len left of the tick*/ 1465 lv_area_t a; 1466 a.y1 = p2.y - size.y / 2; 1467 a.y2 = p2.y + size.y / 2; 1468 1469 if(!sec_axis) { 1470 a.x1 = p2.x - size.x - label_gap; 1471 a.x2 = p2.x - label_gap; 1472 } 1473 else { 1474 a.x1 = p2.x + label_gap; 1475 a.x2 = p2.x + size.x + label_gap; 1476 } 1477 1478 if(a.y2 >= obj->coords.y1 && 1479 a.y1 <= obj->coords.y2) { 1480 lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL); 1481 } 1482 } 1483 else { 1484 part_draw_dsc.label_dsc = NULL; 1485 part_draw_dsc.text = NULL; 1486 part_draw_dsc.text_length = 0; 1487 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); 1488 } 1489 1490 if(p1.y + line_dsc.width / 2 >= obj->coords.y1 && 1491 p2.y - line_dsc.width / 2 <= obj->coords.y2) { 1492 lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); 1493 } 1494 1495 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); 1496 } 1497 } 1498 1499 static void draw_x_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis) 1500 { 1501 lv_chart_t * chart = (lv_chart_t *)obj; 1502 1503 lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis); 1504 if(t->major_cnt <= 1) return; 1505 if(!t->label_en && !t->major_len && !t->minor_len) return; 1506 1507 uint32_t i; 1508 lv_point_t p1; 1509 lv_point_t p2; 1510 1511 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); 1512 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; 1513 1514 1515 lv_draw_label_dsc_t label_dsc; 1516 lv_draw_label_dsc_init(&label_dsc); 1517 lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc); 1518 1519 lv_coord_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj); 1520 lv_coord_t y_ofs; 1521 lv_coord_t label_gap; 1522 if(axis == LV_CHART_AXIS_PRIMARY_X) { 1523 label_gap = t->label_en ? lv_obj_get_style_pad_bottom(obj, LV_PART_TICKS) : 0; 1524 y_ofs = obj->coords.y2; 1525 } 1526 else { 1527 label_gap = t->label_en ? lv_obj_get_style_pad_top(obj, LV_PART_TICKS) : 0; 1528 y_ofs = obj->coords.y1; 1529 } 1530 1531 if(axis == LV_CHART_AXIS_PRIMARY_X) { 1532 if(y_ofs > draw_ctx->clip_area->y2) return; 1533 if(y_ofs + label_gap + label_dsc.font->line_height + t->major_len < draw_ctx->clip_area->y1) return; 1534 } 1535 1536 lv_draw_line_dsc_t line_dsc; 1537 lv_draw_line_dsc_init(&line_dsc); 1538 lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc); 1539 line_dsc.dash_gap = 0; 1540 line_dsc.dash_width = 0; 1541 1542 lv_obj_draw_part_dsc_t part_draw_dsc; 1543 lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); 1544 part_draw_dsc.class_p = MY_CLASS; 1545 part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL; 1546 part_draw_dsc.id = LV_CHART_AXIS_PRIMARY_X; 1547 part_draw_dsc.part = LV_PART_TICKS; 1548 part_draw_dsc.label_dsc = &label_dsc; 1549 part_draw_dsc.line_dsc = &line_dsc; 1550 1551 uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_X ? 0 : 1; 1552 1553 /*The columns ticks should be aligned to the center of blocks*/ 1554 if(chart->type == LV_CHART_TYPE_BAR) { 1555 int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, 1556 LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the columns on ~adjacent X*/ 1557 lv_coord_t block_w = (w + block_gap) / (chart->point_cnt); 1558 x_ofs += (block_w - block_gap) / 2; 1559 w -= block_w - block_gap; 1560 } 1561 1562 p1.y = y_ofs; 1563 uint32_t total_tick_num = (t->major_cnt - 1) * t->minor_cnt; 1564 for(i = 0; i <= total_tick_num; i++) { /*one extra loop - it may not exist in the list, empty label*/ 1565 bool major = false; 1566 if(i % t->minor_cnt == 0) major = true; 1567 1568 /*draw a line at moving x position*/ 1569 p2.x = p1.x = x_ofs + (int32_t)((int32_t)(w - line_dsc.width) * i) / total_tick_num; 1570 1571 if(sec_axis) p2.y = p1.y - (major ? t->major_len : t->minor_len); 1572 else p2.y = p1.y + (major ? t->major_len : t->minor_len); 1573 1574 part_draw_dsc.p1 = &p1; 1575 part_draw_dsc.p2 = &p2; 1576 1577 /*add text only to major tick*/ 1578 int32_t tick_value; 1579 if(chart->type == LV_CHART_TYPE_SCATTER) { 1580 tick_value = lv_map(i, 0, total_tick_num, chart->xmin[sec_axis], chart->xmax[sec_axis]); 1581 } 1582 else { 1583 tick_value = i / t->minor_cnt; 1584 } 1585 part_draw_dsc.value = tick_value; 1586 1587 if(major && t->label_en) { 1588 char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH]; 1589 lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value); 1590 part_draw_dsc.label_dsc = &label_dsc; 1591 part_draw_dsc.text = buf; 1592 part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH; 1593 1594 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); 1595 1596 /*reserve appropriate area*/ 1597 lv_point_t size; 1598 lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX, 1599 LV_TEXT_FLAG_NONE); 1600 1601 /*set the area at some distance of the major tick len under of the tick*/ 1602 lv_area_t a; 1603 a.x1 = (p2.x - size.x / 2); 1604 a.x2 = (p2.x + size.x / 2); 1605 if(sec_axis) { 1606 a.y2 = p2.y - label_gap; 1607 a.y1 = a.y2 - size.y; 1608 } 1609 else { 1610 a.y1 = p2.y + label_gap; 1611 a.y2 = a.y1 + size.y; 1612 } 1613 1614 if(a.x2 >= obj->coords.x1 && 1615 a.x1 <= obj->coords.x2) { 1616 lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL); 1617 } 1618 } 1619 else { 1620 part_draw_dsc.label_dsc = NULL; 1621 part_draw_dsc.text = NULL; 1622 part_draw_dsc.text_length = 0; 1623 lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); 1624 } 1625 1626 1627 if(p1.x + line_dsc.width / 2 >= obj->coords.x1 && 1628 p2.x - line_dsc.width / 2 <= obj->coords.x2) { 1629 lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); 1630 } 1631 1632 lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); 1633 } 1634 } 1635 1636 static void draw_axes(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) 1637 { 1638 draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_Y); 1639 draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_Y); 1640 draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_X); 1641 draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_X); 1642 } 1643 1644 /** 1645 * Get the nearest index to an X coordinate 1646 * @param chart pointer to a chart object 1647 * @param coord the coordination of the point relative to the series area. 1648 * @return the found index 1649 */ 1650 static uint32_t get_index_from_x(lv_obj_t * obj, lv_coord_t x) 1651 { 1652 lv_chart_t * chart = (lv_chart_t *)obj; 1653 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; 1654 lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); 1655 x -= pad_left; 1656 1657 if(x < 0) return 0; 1658 if(x > w) return chart->point_cnt - 1; 1659 if(chart->type == LV_CHART_TYPE_LINE) return (x * (chart->point_cnt - 1) + w / 2) / w; 1660 if(chart->type == LV_CHART_TYPE_BAR) return (x * chart->point_cnt) / w; 1661 1662 return 0; 1663 } 1664 1665 static void invalidate_point(lv_obj_t * obj, uint16_t i) 1666 { 1667 lv_chart_t * chart = (lv_chart_t *)obj; 1668 if(i >= chart->point_cnt) return; 1669 1670 lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; 1671 lv_coord_t scroll_left = lv_obj_get_scroll_left(obj); 1672 1673 /*In shift mode the whole chart changes so the whole object*/ 1674 if(chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT) { 1675 lv_obj_invalidate(obj); 1676 return; 1677 } 1678 1679 if(chart->type == LV_CHART_TYPE_LINE) { 1680 lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN); 1681 lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); 1682 lv_coord_t x_ofs = obj->coords.x1 + pleft + bwidth - scroll_left; 1683 lv_coord_t line_width = lv_obj_get_style_line_width(obj, LV_PART_ITEMS); 1684 lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR); 1685 1686 lv_area_t coords; 1687 lv_area_copy(&coords, &obj->coords); 1688 coords.y1 -= line_width + point_w; 1689 coords.y2 += line_width + point_w; 1690 1691 if(i < chart->point_cnt - 1) { 1692 coords.x1 = ((w * i) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w; 1693 coords.x2 = ((w * (i + 1)) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w; 1694 lv_obj_invalidate_area(obj, &coords); 1695 } 1696 1697 if(i > 0) { 1698 coords.x1 = ((w * (i - 1)) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w; 1699 coords.x2 = ((w * i) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w; 1700 lv_obj_invalidate_area(obj, &coords); 1701 } 1702 } 1703 else if(chart->type == LV_CHART_TYPE_BAR) { 1704 lv_area_t col_a; 1705 int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, 1706 LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the column on ~adjacent X*/ 1707 lv_coord_t block_w = (w + block_gap) / chart->point_cnt; 1708 1709 lv_coord_t x_act; 1710 x_act = (int32_t)((int32_t)(block_w) * i) ; 1711 x_act += obj->coords.x1 + lv_obj_get_style_pad_left(obj, LV_PART_MAIN); 1712 1713 lv_obj_get_coords(obj, &col_a); 1714 col_a.x1 = x_act - scroll_left; 1715 col_a.x2 = col_a.x1 + block_w; 1716 col_a.x1 -= block_gap; 1717 1718 lv_obj_invalidate_area(obj, &col_a); 1719 } 1720 else { 1721 lv_obj_invalidate(obj); 1722 } 1723 } 1724 1725 static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, lv_coord_t ** a) 1726 { 1727 if((*a) == NULL) return; 1728 1729 lv_chart_t * chart = (lv_chart_t *) obj; 1730 uint32_t point_cnt_old = chart->point_cnt; 1731 uint32_t i; 1732 1733 if(ser->start_point != 0) { 1734 lv_coord_t * new_points = lv_mem_alloc(sizeof(lv_coord_t) * cnt); 1735 LV_ASSERT_MALLOC(new_points); 1736 if(new_points == NULL) return; 1737 1738 if(cnt >= point_cnt_old) { 1739 for(i = 0; i < point_cnt_old; i++) { 1740 new_points[i] = 1741 (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/ 1742 } 1743 for(i = point_cnt_old; i < cnt; i++) { 1744 new_points[i] = LV_CHART_POINT_NONE; /*Fill up the rest with default value*/ 1745 } 1746 } 1747 else { 1748 for(i = 0; i < cnt; i++) { 1749 new_points[i] = 1750 (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/ 1751 } 1752 } 1753 1754 /*Switch over pointer from old to new*/ 1755 lv_mem_free((*a)); 1756 (*a) = new_points; 1757 } 1758 else { 1759 (*a) = lv_mem_realloc((*a), sizeof(lv_coord_t) * cnt); 1760 LV_ASSERT_MALLOC((*a)); 1761 if((*a) == NULL) return; 1762 /*Initialize the new points*/ 1763 if(cnt > point_cnt_old) { 1764 for(i = point_cnt_old - 1; i < cnt; i++) { 1765 (*a)[i] = LV_CHART_POINT_NONE; 1766 } 1767 } 1768 } 1769 } 1770 1771 lv_chart_tick_dsc_t * get_tick_gsc(lv_obj_t * obj, lv_chart_axis_t axis) 1772 { 1773 lv_chart_t * chart = (lv_chart_t *) obj; 1774 switch(axis) { 1775 case LV_CHART_AXIS_PRIMARY_Y: 1776 return &chart->tick[0]; 1777 case LV_CHART_AXIS_PRIMARY_X: 1778 return &chart->tick[1]; 1779 case LV_CHART_AXIS_SECONDARY_Y: 1780 return &chart->tick[2]; 1781 case LV_CHART_AXIS_SECONDARY_X: 1782 return &chart->tick[3]; 1783 default: 1784 return NULL; 1785 } 1786 } 1787 1788 1789 #endif