#include "ChartWidget.hpp" #include "../Fonts.hpp" #include "lvgl.h" #include 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 bool hasYAxis = config_.chartShowYLine || config_.chartShowYLabels; const bool hasXAxis = config_.chartShowXLine || config_.chartShowXLabels; const int32_t yAxisWidth = hasYAxis ? 36 : 0; const int32_t xAxisHeight = hasXAxis ? 24 : 0; const int32_t topPad = 8; int32_t chartWidth = width - yAxisWidth; int32_t chartHeight = height - xAxisHeight - topPad; if (chartWidth < 20) chartWidth = 20; if (chartHeight < 20) chartHeight = 20; if (hasYAxis) { 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_, config_.chartShowYLabels); lv_scale_set_range(yScale_, 0, 100); lv_obj_set_pos(yScale_, 0, topPad); lv_obj_set_size(yScale_, yAxisWidth, chartHeight); } } if (hasXAxis) { xScale_ = lv_scale_create(obj_); if (xScale_) { lv_scale_set_mode(xScale_, LV_SCALE_MODE_HORIZONTAL_BOTTOM); lv_scale_set_label_show(xScale_, config_.chartShowXLabels); lv_obj_set_pos(xScale_, yAxisWidth, topPad + chartHeight); lv_obj_set_size(xScale_, chartWidth, xAxisHeight); } } chart_ = lv_chart_create(obj_); if (!chart_) { return obj_; } lv_obj_set_pos(chart_, yAxisWidth, topPad); 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); uint8_t pointCount = config_.chartPointCount; if (pointCount < 10) pointCount = 10; if (pointCount > HistoryStore::CHART_POINT_COUNT) pointCount = HistoryStore::CHART_POINT_COUNT; pointCount_ = pointCount; lv_chart_set_point_count(chart_, pointCount); lv_chart_set_div_line_count(chart_, config_.chartShowGrid ? 4 : 0, config_.chartShowGrid ? 6 : 0); 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_pad_all(obj_, 0, 0); if (chart_) { lv_obj_set_style_border_width(chart_, 0, 0); lv_obj_set_style_pad_left(chart_, 2, 0); lv_obj_set_style_pad_right(chart_, 4, 0); lv_obj_set_style_pad_top(chart_, 4, 0); lv_obj_set_style_pad_bottom(chart_, 4, 0); uint8_t lw = config_.chartLineWidth; if (lw == 0) lw = 2; lv_obj_set_style_line_width(chart_, lw, LV_PART_ITEMS); if (config_.chartShowPoints) { uint8_t ps = config_.chartPointSize; if (ps == 0) ps = 4; lv_obj_set_style_size(chart_, ps, ps, LV_PART_INDICATOR); } else { lv_obj_set_style_size(chart_, 0, 0, LV_PART_INDICATOR); } if (!config_.chartShowBg) { lv_obj_set_style_bg_opa(chart_, LV_OPA_TRANSP, 0); } } 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 (!config_.chartShowYLine) { lv_obj_set_style_line_opa(yScale_, LV_OPA_TRANSP, LV_PART_MAIN); lv_obj_set_style_line_opa(yScale_, LV_OPA_TRANSP, LV_PART_INDICATOR); } } 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); if (!config_.chartShowXLine) { lv_obj_set_style_line_opa(xScale_, LV_OPA_TRANSP, LV_PART_MAIN); lv_obj_set_style_line_opa(xScale_, LV_OPA_TRANSP, LV_PART_INDICATOR); } } } 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(config_.chartPeriod), seriesData_[i].data(), pointCount_); for (size_t j = 0; j < pointCount_; ++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(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); }