21. 图表部件
一、图标部件
图表可以将数据可视化,更加直观地显示数据、对比数据、分析数据。LVGL 的图表部件支持两种图形:折线图 和 条形图。
图表部件由六个部分组成:
- 主体部分(
LV_PART_MAIN
):主要设置背景属性和线条(用于分隔线)相关的样式属性。 - 滚动条部分(
LV_PART_SCROLLBAR
):主要设置缩放图表时使用的滚动条。 - 相同的成分(
LV_PART_ITEMS
):折线图:设置线属性,如宽度、高度、bg_color 和半径。条形图:背景属性。 - 指示器(
LV_PART_INDICATOR
):设置折线图和散点图上的点(小圆或小方)的属性。 - 光标(
LV_PART_TICKS
):设置游标,比如它的宽度、高度、bg_color 和半径。
lv_obj_t * lv_chart_create(lv_obj_t * parent); // 创建图表对象
void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type); // 为图表设置一个新的类型
lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj); // 获取图表的类型
void lv_chart_set_point_count(lv_obj_t * obj, uint32_t cnt); // 设置数据线上的点数
uint32_t lv_chart_get_point_count(const lv_obj_t * obj); // 获取图表上每条数据线的数据点数字
void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, int32_t min, int32_t max); // 设置轴上的最小和最大y值
void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode); // 设置图表对象的更新模式
void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv); // 设置水平和垂直分隔线的数量
void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id); // 设置数据数组中x轴起点的索引
uint32_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser); // 获取数据数组中x轴起点的当前索引
void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, lv_point_t * p_out); // 获取图表中一个点的位置
void lv_chart_refresh(lv_obj_t * obj); // 如果图表的数据线发生了变化,请刷新图表
lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis); // 添加数据序列到图表
void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series); // 从图表中释放并删除数据序列
void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide); // 隐藏/取消隐藏图表的单个数据系列
void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color); // 改变一个数据系列的颜色
lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * obj, const lv_chart_series_t * ser); // 下一个数据系列
lv_chart_cursor_t * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir); // 添加一个给定颜色的游标
void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos); // 设置游标在点的坐标
void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser, uint32_t point_id); // 把游标固定在一个点上
lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor); // 获取游标在点的坐标
void lv_chart_set_all_value(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value); // 用一个值初始化序列中的所有数据点
void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value); // 根据更新模式策略设置下一个点的Y值
void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, int32_t x_value, int32_t y_value); // 根据更新模式策略设置下一个点的X和Y值
void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t value); // 直接根据索引设置图表序列中单个点的y值
void lv_chart_set_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t x_value, int32_t y_value); // 根据索引直接设置图表系列的单个点的x和y值(只能在 LV_CHART_TYPE_SCATTER 类型图表设置)
void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[]); // 设置用于图表的x数据点的外部数组
int32_t * lv_chart_get_x_array(const lv_obj_t * obj, lv_chart_series_t * ser); // 获取一个系列的x值的数组
void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[]); // 设置用于图表的y数据点的外部数组
int32_t * lv_chart_get_y_array(const lv_obj_t * obj, lv_chart_series_t * ser); // 获取一个系列的y值的数组
uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj); // 获取当前被压点的指数
我们可以使用 lv_chart_create()
函数 创建图表对象。
/**
* @brief 创建图表对象
*
* @param parent 指向父部件的指针
* @return lv_obj_t* 指向图表部件的指针
*/
lv_obj_t * lv_chart_create(lv_obj_t * parent);
图表的类型分为两种,这两种类型分为 折线图 和 条形图,这些图表的类型是由函数 lv_chart_set_type()
函数设置。
/**
* @brief 为图表设置一个新的类型
*
* @param obj 指向图表部件的指针
* @param type 图标的类型
*/
void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type);
该函数可设置四种模式,这些模式如下表所示:
enum _lv_chart_type_t {
LV_CHART_TYPE_NONE, // 隐藏数据显示
LV_CHART_TYPE_LINE, // 点与点连接成线(折线图)
LV_CHART_TYPE_BAR, // 条形图
LV_CHART_TYPE_SCATTER, // X/Y图画点和点之间的线
};
数据序列 是指绘制在图表上的一组相关值。在不同类型的图表中,数据序列的表示方式各不相同:
- 条形图:数据序列由一系列具有相同填充(颜色或纹理)的条形表示。
- 折线图(也称为图形):数据序列用单线表示。
数据序列简单来讲就是图表具有多少通道绘制数值。图表的数据序列可调用函数 lv_chart_add_series()
添加。
/**
* @brief 添加数据序列到图表
*
* @param obj 指向图表部件的指针
* @param color 设置数据序列的颜色
* @param axis 表示数据序列缩放方向
* @return lv_chart_series_t* 数据序列的指针
*/
lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis);
有关数据缩放方向枚举值如下:
enum _lv_chart_axis_t {
LV_CHART_AXIS_PRIMARY_Y = 0x00, // 左轴
LV_CHART_AXIS_SECONDARY_Y = 0x01, // 右轴
LV_CHART_AXIS_PRIMARY_X = 0x02, // 底部
LV_CHART_AXIS_SECONDARY_X = 0x04, // 顶部
_LV_CHART_AXIS_LAST
};
默认情况下,图表部件只支持 10 个数据点,如果我们具有 11 个数据,那么图表先显示前 10 个数据,而第 11 个数据会将图表的 10 个数据逐一往左移位,最后把第一个数据点的数值去除了。
这些数据点类似于一个数组(其实它是一个连续内存的内存块),该数组默认长度为 10,所以上图的数据点可以存放 10 个元素,如果第 11 给元素加载到数据点中,则该数组的数值往左移位,最后把原本第一位的元素除去,新的元素插入该数组的最后一位。
虽然图表部件默认只支持 10 个数据点,但是我们可以手动设置图表可支持的数据点,这些数据点的数量由用户决定。我们调用函数 lv_chart_set_point_count()
来 设置该图表所支持的数据点。
/**
* @brief 设置数据线上的点数
*
* @param obj 指向图表部件的指针
* @param cnt 数据线上的点数
*/
void lv_chart_set_point_count(lv_obj_t * obj, uint32_t cnt);
我们可以使用 lv_chart_set_ext_y_array()
函数和 lv_chart_set_ext_x_array()
函数 添加数据到图表中。函数 lv_chart_set_ext_y_array()
是为 Y 轴数据点设置一个外部阵列,该函数一般用在折线图和条形图类型。函数 lv_chart_set_ext_x_array()
是为 X 轴数据点设置一个外部阵列,该函数一般用在 LV_CHART_TYPE_SCATTER
类型上。
/**
* @brief 设置用于图表的x数据点的外部数组
*
* @param obj 指向图表部件的指针
* @param ser 指向数据序列的指针
* @param array 外部数据数组
*/
void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[]);
/**
* @brief 设置用于图表的y数据点的外部数组
*
* @param obj 指向图表部件的指针
* @param ser 指向数据序列的指针
* @param array 外部数据数组
*/
void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, int32_t array[]);
我们还可以使用 lv_chart_set_next_value()
函数 设置图表的下一个数值。
/**
* @brief 根据更新模式策略设置下一个点的Y值
*
* @param obj 指向图表部件的指针
* @param ser 指向数据序列的指针
* @param value 下一个点的值
*/
void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value);
上述的模式是由 lv_chart_set_update_mode()
函数设置,图表会根据这些模式来设置添加数据的方式。
/**
* @brief 设置图表对象的更新模式
*
* @param obj 指向图表部件的指针
* @param update_mode 更新模式
*/
void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode);
其中,更新模式 的可选值如下:
enum _lv_chart_update_mode_t {
LV_CHART_UPDATE_MODE_SHIFT, // 将旧数据移到左边,并将新数据添加到右边
LV_CHART_UPDATE_MODE_CIRCULAR, // 以循环的方式添加新数据
};
我们还可以使用 lv_chart_set_all_value()
函数把 所有的数据都初始化为一个数值。
/**
* @brief 用一个值初始化序列中的所有数据点
*
* @param obj 指向图表部件的指针
* @param ser 指向数据序列的指针
* @param value 初始化值
*/
void lv_chart_set_all_value(lv_obj_t * obj, lv_chart_series_t * ser, int32_t value);
我们还可以使用 lv_chart_set_value_by_id()
函数 修改某个数据点的数值。
/**
* @brief 直接根据索引设置图表序列中单个点的y值
*
* @param obj 指向图表部件的指针
* @param ser 指向数据序列的指针
* @param id 数据序列的索引
* @param value 修改后的值
*/
void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id, int32_t value);
我们还可以使用 lv_chart_add_series()
函数所返回的数据序列指针的成员变量 y_points 或者 x_points 数组设置数值。
/**
* @brief 添加数据序列到图表
*
* @param obj 指向图表部件的指针
* @param color 设置数据序列的颜色
* @param axis 表示数据序列的方向
* @return lv_chart_series_t* 数据序列的指针
*/
lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis);
垂直范围 是指图表所添加的数据不能超出这个范围。图表默认的垂直范围为 0 ~ 100,我们可以手动调用函数 lv_chart_set_range()
函数 设置垂直范围。设置垂直范围的数据序列必须是 LV_CHART_AXIS_PRIMARY
和 LV_CHART_AXIS_SECONDARY
。
/**
* @brief 设置轴上的最小和最大y值
*
* @param obj 指向图表部件的指针
* @param axis 轴
* @param min 最小值
* @param max 最大值
*/
void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, int32_t min, int32_t max);
分隔线是用于辅助数据图表提升投射关联的。应用分隔线能够提升数据信息的可阅读性,分隔线给予了二种作用:一是拓宽标值标尺至数据可视化目标中,便于观查数据信息值之尺寸;二是提升数据可视化目标中间的较为基本。
LVGL 的图表部件默认设置为 3 条水平分隔线和 5 条垂直分隔线,如果某边有可见的边框且该边没有填充,则分割线将绘制在边框的顶部。图表部件 分割线 是调用函数 lv_chart_set_div_line_count()
设置。
/**
* @brief 设置水平和垂直分隔线的数量
*
* @param obj 指向图表部件的指针
* @param hdiv 水平分割线的数量
* @param vdiv 垂直分割线的数量
*/
void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv);
如果用户想让绘图从一个点开始而不是默认值 point[0] 点开始,我们可以使用函数 lv_chart_set_x_start_point()
设置一个替代索引。
/**
* @brief 设置数据数组中x轴起点的索引
*
* @param obj 指向图表部件的指针
* @param ser 指向数据序列的指针
* @param id 起始点的索引
*/
void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t id);
二、实验例程
#include "lvgl.h"
#include "lv_port_disp_template.h"
#include "lv_port_indev_template.h"
int main(void)
{
int32_t data[15] = {0};
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
Delay_Init(168);
SPI_Simulate_Init();
// SRAM_Init();
TIM_Base_Init(&g_tim6_handle, TIM6, 83, 999);
__HAL_TIM_CLEAR_IT(&g_tim6_handle, TIM_IT_UPDATE); // 清除更新中断标志位
HAL_TIM_Base_Start_IT(&g_tim6_handle); // 使能更新中断,并启动计数器
lv_init();
lv_port_disp_init();
lv_port_indev_init();
// 测试代码
for (uint32_t i = 0; i < sizeof(data) / sizeof(data[0]); i++)
{
data[i] = i + 1;
}
lv_obj_t *chart = lv_chart_create(lv_scr_act()); // 创建图表部件
lv_obj_center(chart); // 设置图表部件居中对齐
lv_chart_set_type(chart, LV_CHART_TYPE_LINE); // 设置图表类型
lv_chart_set_point_count(chart, 15); // 设置数据轴上的点
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, -30, 30); // 设置左轴 Y 方向的最大最小值
lv_chart_series_t *series = lv_chart_add_series(chart, lv_color_hex(0x00BFFF), LV_CHART_AXIS_PRIMARY_Y); // 添加数据序列到图表
lv_chart_set_ext_y_array(chart, series, data); // 设置数据序列
while (1)
{
lv_timer_handler();
Delay_ms(5);
}
return 0;
}