knxdisplay/main/widgets/ChartWidget.cpp
2026-01-30 11:15:56 +01:00

209 lines
6.9 KiB
C++

#include "ChartWidget.hpp"
#include "../Fonts.hpp"
#include "lvgl.h"
#include <algorithm>
ChartWidget::ChartWidget(const WidgetConfig& config)
: Widget(config) {
}
lv_obj_t* ChartWidget::create(lv_obj_t* parent) {
obj_ = lv_obj_create(parent);
if (!obj_) return nullptr;
lv_obj_remove_style_all(obj_);
int32_t width = config_.width > 0 ? config_.width : 240;
int32_t height = config_.height > 0 ? config_.height : 160;
lv_obj_set_pos(obj_, config_.x, config_.y);
lv_obj_set_size(obj_, width, height);
lv_obj_clear_flag(obj_, LV_OBJ_FLAG_SCROLLABLE);
const int32_t yAxisWidth = 48;
const int32_t xAxisHeight = 26;
int32_t chartWidth = width - yAxisWidth;
int32_t chartHeight = height - xAxisHeight;
if (chartWidth < 20) chartWidth = 20;
if (chartHeight < 20) chartHeight = 20;
yScale_ = lv_scale_create(obj_);
if (yScale_) {
lv_scale_set_mode(yScale_, LV_SCALE_MODE_VERTICAL_LEFT);
lv_scale_set_total_tick_count(yScale_, 5);
lv_scale_set_major_tick_every(yScale_, 1);
lv_scale_set_label_show(yScale_, true);
lv_scale_set_range(yScale_, 0, 100);
lv_obj_set_pos(yScale_, 0, 0);
lv_obj_set_size(yScale_, yAxisWidth, chartHeight);
}
xScale_ = lv_scale_create(obj_);
if (xScale_) {
lv_scale_set_mode(xScale_, LV_SCALE_MODE_HORIZONTAL_BOTTOM);
lv_scale_set_label_show(xScale_, true);
lv_obj_set_pos(xScale_, yAxisWidth, chartHeight);
lv_obj_set_size(xScale_, chartWidth, xAxisHeight);
}
chart_ = lv_chart_create(obj_);
if (!chart_) {
return obj_;
}
lv_obj_set_pos(chart_, yAxisWidth, 0);
lv_obj_set_size(chart_, chartWidth, chartHeight);
lv_obj_clear_flag(chart_, LV_OBJ_FLAG_SCROLLABLE);
lv_chart_set_type(chart_, LV_CHART_TYPE_LINE);
lv_chart_set_point_count(chart_, HistoryStore::CHART_POINT_COUNT);
lv_chart_set_div_line_count(chart_, 4, 6);
uint8_t count = config_.chartSeriesCount;
if (count > CHART_MAX_SERIES) count = CHART_MAX_SERIES;
for (uint8_t i = 0; i < count; ++i) {
lv_color_t color = lv_color_make(
config_.chartSeriesColor[i].r,
config_.chartSeriesColor[i].g,
config_.chartSeriesColor[i].b);
series_[i] = lv_chart_add_series(chart_, color, LV_CHART_AXIS_PRIMARY_Y);
if (series_[i]) {
lv_chart_set_series_ext_y_array(chart_, series_[i], seriesData_[i].data());
std::fill(seriesData_[i].begin(), seriesData_[i].end(), HistoryStore::NO_POINT);
}
}
applyAxisLabels();
refreshData();
return obj_;
}
void ChartWidget::applyStyle() {
if (!obj_) return;
Widget::applyStyle();
lv_obj_set_style_border_width(obj_, 0, 0);
lv_obj_set_style_pad_all(obj_, 0, 0);
if (chart_) {
lv_obj_set_style_border_width(chart_, 0, 0);
lv_obj_set_style_pad_all(chart_, 6, 0);
lv_obj_set_style_line_width(chart_, 2, LV_PART_ITEMS);
}
const lv_color_t textColor = lv_color_make(
config_.textColor.r, config_.textColor.g, config_.textColor.b);
const lv_font_t* axisFont = Fonts::bySizeIndex(0);
if (yScale_) {
lv_obj_set_style_text_color(yScale_, textColor, LV_PART_INDICATOR);
lv_obj_set_style_text_font(yScale_, axisFont, LV_PART_INDICATOR);
lv_obj_set_style_bg_opa(yScale_, LV_OPA_TRANSP, 0);
lv_obj_set_style_border_width(yScale_, 0, 0);
}
if (xScale_) {
lv_obj_set_style_text_color(xScale_, textColor, LV_PART_INDICATOR);
lv_obj_set_style_text_font(xScale_, axisFont, LV_PART_INDICATOR);
lv_obj_set_style_bg_opa(xScale_, LV_OPA_TRANSP, 0);
lv_obj_set_style_border_width(xScale_, 0, 0);
}
}
void ChartWidget::onHistoryUpdate() {
refreshData();
}
void ChartWidget::refreshData() {
if (!chart_) return;
uint8_t count = config_.chartSeriesCount;
if (count > CHART_MAX_SERIES) count = CHART_MAX_SERIES;
bool hasAny = false;
int32_t globalMin = 0;
int32_t globalMax = 0;
for (uint8_t i = 0; i < count; ++i) {
if (!series_[i]) continue;
HistoryStore::instance().fillChartSeries(
config_.chartKnxAddress[i],
config_.chartTextSource[i],
static_cast<ChartPeriod>(config_.chartPeriod),
seriesData_[i].data(),
seriesData_[i].size());
for (size_t j = 0; j < seriesData_[i].size(); ++j) {
int32_t value = seriesData_[i][j];
if (value == HistoryStore::NO_POINT) continue;
if (!hasAny) {
globalMin = value;
globalMax = value;
hasAny = true;
} else {
globalMin = std::min(globalMin, value);
globalMax = std::max(globalMax, value);
}
}
}
if (hasAny) {
int32_t range = globalMax - globalMin;
if (range < 1) range = 1;
int32_t pad = range / 10;
if (pad < 1) pad = 1;
int32_t minVal = globalMin - pad;
int32_t maxVal = globalMax + pad;
lv_chart_set_axis_range(chart_, LV_CHART_AXIS_PRIMARY_Y, minVal, maxVal);
if (yScale_) {
lv_scale_set_range(yScale_, minVal, maxVal);
}
} else {
lv_chart_set_axis_range(chart_, LV_CHART_AXIS_PRIMARY_Y, 0, 100);
if (yScale_) {
lv_scale_set_range(yScale_, 0, 100);
}
}
lv_chart_refresh(chart_);
}
const char** ChartWidget::labelsForPeriod(ChartPeriod period, uint8_t* count) {
static const char* k1h[] = { "-60m", "-45m", "-30m", "-15m", "0", nullptr };
static const char* k3h[] = { "-3h", "-2h", "-1h", "-30m", "0", nullptr };
static const char* k5h[] = { "-5h", "-4h", "-3h", "-2h", "-1h", "0", nullptr };
static const char* k12h[] = { "-12h", "-9h", "-6h", "-3h", "0", nullptr };
static const char* k24h[] = { "-24h", "-18h", "-12h", "-6h", "0", nullptr };
static const char* k1m[] = { "-30d", "-21d", "-14d", "-7d", "0", nullptr };
switch (period) {
case ChartPeriod::HOUR_1:
if (count) *count = 5;
return k1h;
case ChartPeriod::HOUR_3:
if (count) *count = 5;
return k3h;
case ChartPeriod::HOUR_5:
if (count) *count = 6;
return k5h;
case ChartPeriod::HOUR_12:
if (count) *count = 5;
return k12h;
case ChartPeriod::HOUR_24:
if (count) *count = 5;
return k24h;
case ChartPeriod::MONTH_1:
if (count) *count = 5;
return k1m;
default:
if (count) *count = 5;
return k1h;
}
}
void ChartWidget::applyAxisLabels() {
if (!xScale_) return;
uint8_t count = 5;
const char** labels = labelsForPeriod(static_cast<ChartPeriod>(config_.chartPeriod), &count);
lv_scale_set_total_tick_count(xScale_, count);
lv_scale_set_major_tick_every(xScale_, 1);
lv_scale_set_text_src(xScale_, labels);
}