Skip to main content

让你的 LVGL 折线图支持浮点数显示

·768 words·2 mins
Kydin
Author
Kydin
自由のために戦え
Table of Contents

今天在做显示屏的时候发现,LVGL V8.2 的折线图 y 轴居然不能支持显示浮点数,于是研究了一下,发现还是有一些奇技淫巧来实现的。

原始代码
#

int main(int tiks) {
  float real = json_object_get_double(val);
  lv_chart_set_next_value(ui.chart_data, ui.chart_data_dot, real);

  lv_chart_set_axis_tick(ui.chart_data, LV_CHART_AXIS_PRIMARY_Y, 10, 5, tiks, 2,
                         true, 100);
  lv_chart_set_range(ui.chart_data, LV_CHART_AXIS_PRIMARY_Y, min_data,
                     max_data);
  lv_obj_add_event_cb(ui.chart_data, chart_event_cb, LV_EVENT_ALL, NULL);
  lv_obj_refresh_ext_draw_size(ui.chart_data);
  lv_chart_set_update_mode(ui.chart_data, LV_CHART_UPDATE_MODE_CIRCULAR);
  lv_chart_set_zoom_x(ui.chart_data, 700);
}

查看图标设置函数定义可以发现,只接受整数

void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(ser);

    lv_chart_t * chart  = (lv_chart_t *)obj;
    ser->y_points[ser->start_point] = value;
    invalidate_point(obj, ser->start_point);
    ser->start_point = (ser->start_point + 1) % chart->point_cnt;
    invalidate_point(obj, ser->start_point);
    lv_chart_refresh(obj);
}
#if LV_USE_LARGE_COORD
typedef int32_t lv_coord_t;
#else
typedef int16_t lv_coord_t;
#endifc

在网上搜了一下,LVGL 似乎不支持图标直接存放浮点数,但是我们可以通过乘以 100 来存入,读出来的时候再除以 100。

实现浮点数显示
#

首先,将 lv_conf.h 中的浮点数支持开启

#define LV_SPRINTF_USE_FLOAT 1

这样 lv_snprintf 函数才能支持%f,存入图表的时候乘以 100 来存入。

int main(int tiks) {
  float real = json_object_get_double(val) * 100;
  lv_chart_set_next_value(ui.chart_data, ui.chart_data_dot, real);

  lv_chart_set_axis_tick(ui.chart_data, LV_CHART_AXIS_PRIMARY_Y, 10, 5, tiks, 2,
                         true, 100);
  lv_chart_set_range(ui.chart_data, LV_CHART_AXIS_PRIMARY_Y, min_data,
                     max_data);
  lv_obj_add_event_cb(ui.chart_data, chart_event_cb, LV_EVENT_ALL, NULL);
  lv_obj_refresh_ext_draw_size(ui.chart_data);
  lv_chart_set_update_mode(ui.chart_data, LV_CHART_UPDATE_MODE_CIRCULAR);
  lv_chart_set_zoom_x(ui.chart_data, 700);
}

lv_chart_set_axis_tick 中,label_en 表示是否显示坐标轴,我们需要将坐标轴以浮点数形式显示。

修改 lvgl/src/extra/widgets/chart/lv_chart.c 的 draw_y_ticks 函数,你的路径可能跟我不一样,但最终要改的函数都是 draw_y_ticks 这个函数。

这个函数用于绘制 y 轴坐标,我们需要将存入的乘以 100 的坐标再改为正常值。


lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value);
改为
lv_snprintf(buf, sizeof(buf), "%.2f", (float)tick_value / 100.0f);

现在显示的页面就是正常的了,但是我还有一个点击事件的需要修改,这是为了让用户在点击或者拖动折线图的时候,点位的上方可以出现一个标签显示该点位的值。先看一下我之前的写法:

/**
 * @description: 折线图点击、拖动事件
 * @param {lv_event_t} *e
 * @return {*}
 */
static void chart_event_cb(lv_event_t *e) {
  lv_event_code_t code = lv_event_get_code(e);
  lv_obj_t *chart = lv_event_get_target(e);

  if (code == LV_EVENT_VALUE_CHANGED) {
    lv_obj_invalidate(chart);
  }
  if (code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
    lv_coord_t *s = lv_event_get_param(e);
    *s = LV_MAX(*s, 20);
  } else if (code == LV_EVENT_DRAW_POST_END) {
    int32_t id = lv_chart_get_pressed_point(chart);
    if (id == LV_CHART_POINT_NONE) return;

    LV_LOG_USER("Selected point %d", (int)id);

    lv_chart_series_t *ser = lv_chart_get_series_next(chart, NULL);
    while (ser) {
      lv_point_t p;
      lv_chart_get_point_pos_by_id(chart, ser, id, &p);

      lv_coord_t *y_array = lv_chart_get_y_array(chart, ser);
      lv_coord_t value = y_array[id];
      if (value == LV_CHART_POINT_NONE) return;

      char buf[32];
      lv_snprintf(buf, sizeof(buf), LV_SYMBOL_DUMMY "%d", value);

      lv_draw_rect_dsc_t draw_rect_dsc;
      lv_draw_rect_dsc_init(&draw_rect_dsc);
      draw_rect_dsc.bg_color = lv_color_black();
      draw_rect_dsc.bg_opa = LV_OPA_50;
      draw_rect_dsc.radius = 3;
      draw_rect_dsc.bg_img_src = buf;
      draw_rect_dsc.bg_img_recolor = lv_color_white();

      lv_area_t a;
      a.x1 = chart->coords.x1 + p.x - 20;
      a.x2 = chart->coords.x1 + p.x + 20;
      a.y1 = chart->coords.y1 + p.y - 30;
      a.y2 = chart->coords.y1 + p.y - 10;

      lv_draw_ctx_t *draw_ctx = lv_event_get_draw_ctx(e);
      lv_draw_rect(draw_ctx, &draw_rect_dsc, &a);

      ser = lv_chart_get_series_next(chart, ser);
    }
  } else if (code == LV_EVENT_RELEASED) {
    lv_obj_invalidate(chart);
  }
}

现在也是一样的,要先除以 100 获取到原本的值再显示:


lv_snprintf(buf, sizeof(buf), LV_SYMBOL_DUMMY "%d", value);
改为
lv_snprintf(buf, sizeof(buf), LV_SYMBOL_DUMMY "%.2f", (float)value / 100.0f);

说在最后
#

查资料的时候发现 LVGL 9 要推出了,希望新版本能提供原生支持吧。

参考链接
#

https://forum.lvgl.io/t/chart-display-floats-temperature-values-with-decimal-places/9097/2