#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) { return nullptr; 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(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(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); }