diff --git a/.cache/clangd/index/ChartWidget.cpp.344F8561CCF752B4.idx b/.cache/clangd/index/ChartWidget.cpp.344F8561CCF752B4.idx index 2cf07f5..8b9561a 100644 Binary files a/.cache/clangd/index/ChartWidget.cpp.344F8561CCF752B4.idx and b/.cache/clangd/index/ChartWidget.cpp.344F8561CCF752B4.idx differ diff --git a/.cache/clangd/index/Eth.cpp.9E0D1973A86A173A.idx b/.cache/clangd/index/Eth.cpp.9E0D1973A86A173A.idx index 52c8849..1dd6e63 100644 Binary files a/.cache/clangd/index/Eth.cpp.9E0D1973A86A173A.idx and b/.cache/clangd/index/Eth.cpp.9E0D1973A86A173A.idx differ diff --git a/.cache/clangd/index/Eth.hpp.08D328E5C3BAEBCA.idx b/.cache/clangd/index/Eth.hpp.08D328E5C3BAEBCA.idx index 5c6c14a..768d812 100644 Binary files a/.cache/clangd/index/Eth.hpp.08D328E5C3BAEBCA.idx and b/.cache/clangd/index/Eth.hpp.08D328E5C3BAEBCA.idx differ diff --git a/.cache/clangd/index/HistoryStore.cpp.957860D36959A6A5.idx b/.cache/clangd/index/HistoryStore.cpp.957860D36959A6A5.idx index 554ff76..440e666 100644 Binary files a/.cache/clangd/index/HistoryStore.cpp.957860D36959A6A5.idx and b/.cache/clangd/index/HistoryStore.cpp.957860D36959A6A5.idx differ diff --git a/.cache/clangd/index/KnxWorker.cpp.DE6DF2C0B88ED24E.idx b/.cache/clangd/index/KnxWorker.cpp.DE6DF2C0B88ED24E.idx index 0a31b2e..d1bf2dc 100644 Binary files a/.cache/clangd/index/KnxWorker.cpp.DE6DF2C0B88ED24E.idx and b/.cache/clangd/index/KnxWorker.cpp.DE6DF2C0B88ED24E.idx differ diff --git a/.cache/clangd/index/LabelWidget.cpp.86E5A1BF3C34B7BC.idx b/.cache/clangd/index/LabelWidget.cpp.86E5A1BF3C34B7BC.idx index 9261bf6..1f0568e 100644 Binary files a/.cache/clangd/index/LabelWidget.cpp.86E5A1BF3C34B7BC.idx and b/.cache/clangd/index/LabelWidget.cpp.86E5A1BF3C34B7BC.idx differ diff --git a/.cache/clangd/index/NetworkConfig.cpp.28689B151DE5324A.idx b/.cache/clangd/index/NetworkConfig.cpp.28689B151DE5324A.idx new file mode 100644 index 0000000..e360a04 Binary files /dev/null and b/.cache/clangd/index/NetworkConfig.cpp.28689B151DE5324A.idx differ diff --git a/.cache/clangd/index/NetworkConfig.hpp.3283725D56F161D1.idx b/.cache/clangd/index/NetworkConfig.hpp.3283725D56F161D1.idx new file mode 100644 index 0000000..7c4e4eb Binary files /dev/null and b/.cache/clangd/index/NetworkConfig.hpp.3283725D56F161D1.idx differ diff --git a/.cache/clangd/index/PowerLinkWidget.cpp.481D6AFA808A3AE1.idx b/.cache/clangd/index/PowerLinkWidget.cpp.481D6AFA808A3AE1.idx index a2db5f9..4f8cbbc 100644 Binary files a/.cache/clangd/index/PowerLinkWidget.cpp.481D6AFA808A3AE1.idx and b/.cache/clangd/index/PowerLinkWidget.cpp.481D6AFA808A3AE1.idx differ diff --git a/.cache/clangd/index/PowerNodeWidget.cpp.D068C7972720D9A3.idx b/.cache/clangd/index/PowerNodeWidget.cpp.D068C7972720D9A3.idx index 8c85d71..39f5663 100644 Binary files a/.cache/clangd/index/PowerNodeWidget.cpp.D068C7972720D9A3.idx and b/.cache/clangd/index/PowerNodeWidget.cpp.D068C7972720D9A3.idx differ diff --git a/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx b/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx index 7522493..f0b4664 100644 Binary files a/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx and b/.cache/clangd/index/WidgetConfig.hpp.CAEFE2EEEB2A6996.idx differ diff --git a/.cache/clangd/index/Wifi.cpp.C29FF4A35AE66387.idx b/.cache/clangd/index/Wifi.cpp.C29FF4A35AE66387.idx index 7069e79..983e751 100644 Binary files a/.cache/clangd/index/Wifi.cpp.C29FF4A35AE66387.idx and b/.cache/clangd/index/Wifi.cpp.C29FF4A35AE66387.idx differ diff --git a/.cache/clangd/index/Wifi.hpp.162A78F89BACD645.idx b/.cache/clangd/index/Wifi.hpp.162A78F89BACD645.idx index b7814ed..3e7adff 100644 Binary files a/.cache/clangd/index/Wifi.hpp.162A78F89BACD645.idx and b/.cache/clangd/index/Wifi.hpp.162A78F89BACD645.idx differ diff --git a/.cache/clangd/index/main.cpp.7C677863E2582AB3.idx b/.cache/clangd/index/main.cpp.7C677863E2582AB3.idx index 749f9b7..8375adc 100644 Binary files a/.cache/clangd/index/main.cpp.7C677863E2582AB3.idx and b/.cache/clangd/index/main.cpp.7C677863E2582AB3.idx differ diff --git a/main/WidgetConfig.cpp b/main/WidgetConfig.cpp index d3bec2e..0870c69 100644 --- a/main/WidgetConfig.cpp +++ b/main/WidgetConfig.cpp @@ -73,6 +73,17 @@ void WidgetConfig::serialize(uint8_t* buf) const { buf[pos++] = chartSeriesColor[i].g; buf[pos++] = chartSeriesColor[i].b; } + // Chart flags (1 byte) + buf[pos++] = (chartShowXLine ? 0x01 : 0) | + (chartShowBg ? 0x02 : 0) | + (chartShowGrid ? 0x04 : 0) | + (chartShowYLine ? 0x08 : 0) | + (chartShowXLabels ? 0x10 : 0) | + (chartShowYLabels ? 0x20 : 0); + buf[pos++] = chartLineWidth; + buf[pos++] = chartPointCount; + buf[pos++] = chartShowPoints ? 1 : 0; + buf[pos++] = chartPointSize; // Secondary KNX address (left value) buf[pos++] = knxAddress2 & 0xFF; @@ -238,6 +249,37 @@ void WidgetConfig::deserialize(const uint8_t* buf) { chartSeriesColor[i].g = buf[pos++]; chartSeriesColor[i].b = buf[pos++]; } + // Chart flags + if (pos + 1 <= SERIALIZED_SIZE) { + uint8_t chartFlags = buf[pos++]; + chartShowXLine = (chartFlags & 0x01) != 0; + chartShowBg = (chartFlags & 0x02) != 0; + chartShowGrid = (chartFlags & 0x04) != 0; + chartShowYLine = (chartFlags & 0x08) != 0; + chartShowXLabels = (chartFlags & 0x10) != 0; + chartShowYLabels = (chartFlags & 0x20) != 0; + } else { + chartShowXLine = true; + chartShowYLine = true; + chartShowXLabels = true; + chartShowYLabels = true; + chartShowBg = true; + chartShowGrid = true; + } + if (pos + 4 <= SERIALIZED_SIZE) { + chartLineWidth = buf[pos++]; + if (chartLineWidth == 0) chartLineWidth = 2; + chartPointCount = buf[pos++]; + if (chartPointCount == 0) chartPointCount = 120; + chartShowPoints = buf[pos++] != 0; + chartPointSize = buf[pos++]; + if (chartPointSize == 0) chartPointSize = 4; + } else { + chartLineWidth = 2; + chartPointCount = 120; + chartShowPoints = false; + chartPointSize = 4; + } // Secondary KNX address (left value) - check bounds for backward compatibility if (pos + 19 <= SERIALIZED_SIZE) { diff --git a/main/WidgetConfig.hpp b/main/WidgetConfig.hpp index e7deca3..187a29b 100644 --- a/main/WidgetConfig.hpp +++ b/main/WidgetConfig.hpp @@ -307,6 +307,16 @@ struct WidgetConfig { uint16_t chartKnxAddress[CHART_MAX_SERIES]; TextSource chartTextSource[CHART_MAX_SERIES]; Color chartSeriesColor[CHART_MAX_SERIES]; + bool chartShowXLine; // Show X-axis line/ticks + bool chartShowYLine; // Show Y-axis line/ticks + bool chartShowXLabels; // Show X-axis labels + bool chartShowYLabels; // Show Y-axis labels + bool chartShowBg; // Show chart background + bool chartShowGrid; // Show grid/div lines + uint8_t chartLineWidth; // Line thickness (1-10, default 2) + uint8_t chartPointCount; // Number of data points (10-120, default 120) + bool chartShowPoints; // Show point markers + uint8_t chartPointSize; // Point diameter (2-20, default 4) // Secondary KNX address (for PowerNode LEFT value) uint16_t knxAddress2; @@ -348,7 +358,7 @@ struct WidgetConfig { bool themeFixed; // true = colors stay fixed in night mode // Serialization size (fixed for NVS storage) - static constexpr size_t SERIALIZED_SIZE = 406; + static constexpr size_t SERIALIZED_SIZE = 411; void serialize(uint8_t* buf) const; void deserialize(const uint8_t* buf); diff --git a/main/WidgetManager.cpp b/main/WidgetManager.cpp index fb413e9..4483fcb 100644 --- a/main/WidgetManager.cpp +++ b/main/WidgetManager.cpp @@ -1738,6 +1738,16 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { if (w.type == WidgetType::CHART) { cJSON* chart = cJSON_AddObjectToObject(widget, "chart"); cJSON_AddNumberToObject(chart, "period", w.chartPeriod); + cJSON_AddBoolToObject(chart, "showXLine", w.chartShowXLine); + cJSON_AddBoolToObject(chart, "showYLine", w.chartShowYLine); + cJSON_AddBoolToObject(chart, "showXLabels", w.chartShowXLabels); + cJSON_AddBoolToObject(chart, "showYLabels", w.chartShowYLabels); + cJSON_AddBoolToObject(chart, "showBg", w.chartShowBg); + cJSON_AddBoolToObject(chart, "showGrid", w.chartShowGrid); + cJSON_AddNumberToObject(chart, "lineWidth", w.chartLineWidth); + cJSON_AddNumberToObject(chart, "pointCount", w.chartPointCount); + cJSON_AddBoolToObject(chart, "showPoints", w.chartShowPoints); + cJSON_AddNumberToObject(chart, "pointSize", w.chartPointSize); cJSON* series = cJSON_AddArrayToObject(chart, "series"); uint8_t seriesCount = w.chartSeriesCount; if (seriesCount > CHART_MAX_SERIES) seriesCount = CHART_MAX_SERIES; @@ -1861,6 +1871,16 @@ bool WidgetManager::updateConfigFromJson(const char* json) { w.isContainer = false; w.chartPeriod = static_cast(ChartPeriod::HOUR_1); w.chartSeriesCount = 1; + w.chartShowXLine = true; + w.chartShowYLine = true; + w.chartShowXLabels = true; + w.chartShowYLabels = true; + w.chartShowBg = true; + w.chartShowGrid = true; + w.chartLineWidth = 2; + w.chartPointCount = 120; + w.chartShowPoints = false; + w.chartPointSize = 4; for (size_t i = 0; i < CHART_MAX_SERIES; ++i) { w.chartKnxAddress[i] = 0; w.chartTextSource[i] = TextSource::KNX_DPT_TEMP; @@ -2151,6 +2171,30 @@ bool WidgetManager::updateConfigFromJson(const char* json) { w.chartPeriod = static_cast(periodVal); } + cJSON* showXLine = cJSON_GetObjectItem(chart, "showXLine"); + w.chartShowXLine = cJSON_IsBool(showXLine) ? cJSON_IsTrue(showXLine) : true; + cJSON* showYLine = cJSON_GetObjectItem(chart, "showYLine"); + w.chartShowYLine = cJSON_IsBool(showYLine) ? cJSON_IsTrue(showYLine) : true; + cJSON* showXLabels = cJSON_GetObjectItem(chart, "showXLabels"); + w.chartShowXLabels = cJSON_IsBool(showXLabels) ? cJSON_IsTrue(showXLabels) : true; + cJSON* showYLabels = cJSON_GetObjectItem(chart, "showYLabels"); + w.chartShowYLabels = cJSON_IsBool(showYLabels) ? cJSON_IsTrue(showYLabels) : true; + cJSON* showBg = cJSON_GetObjectItem(chart, "showBg"); + w.chartShowBg = cJSON_IsBool(showBg) ? cJSON_IsTrue(showBg) : true; + cJSON* showGrid = cJSON_GetObjectItem(chart, "showGrid"); + w.chartShowGrid = cJSON_IsBool(showGrid) ? cJSON_IsTrue(showGrid) : true; + cJSON* lineWidth = cJSON_GetObjectItem(chart, "lineWidth"); + w.chartLineWidth = cJSON_IsNumber(lineWidth) ? lineWidth->valueint : 2; + if (w.chartLineWidth == 0) w.chartLineWidth = 2; + cJSON* pointCount = cJSON_GetObjectItem(chart, "pointCount"); + w.chartPointCount = cJSON_IsNumber(pointCount) ? pointCount->valueint : 120; + if (w.chartPointCount < 10) w.chartPointCount = 10; + if (w.chartPointCount > 120) w.chartPointCount = 120; + cJSON* showPoints = cJSON_GetObjectItem(chart, "showPoints"); + w.chartShowPoints = cJSON_IsBool(showPoints) ? cJSON_IsTrue(showPoints) : false; + cJSON* pointSize = cJSON_GetObjectItem(chart, "pointSize"); + w.chartPointSize = cJSON_IsNumber(pointSize) ? pointSize->valueint : 4; + cJSON* series = cJSON_GetObjectItem(chart, "series"); if (cJSON_IsArray(series)) { uint8_t idx = 0; diff --git a/main/widgets/ChartWidget.cpp b/main/widgets/ChartWidget.cpp index 14daabf..b639a75 100644 --- a/main/widgets/ChartWidget.cpp +++ b/main/widgets/ChartWidget.cpp @@ -18,43 +18,55 @@ lv_obj_t* ChartWidget::create(lv_obj_t* parent) { 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; + 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; + int32_t chartHeight = height - xAxisHeight - topPad; 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); + 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); + } } - 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); + 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, 0); + 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); - lv_chart_set_point_count(chart_, HistoryStore::CHART_POINT_COUNT); - lv_chart_set_div_line_count(chart_, 4, 6); + 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; @@ -84,8 +96,23 @@ void ChartWidget::applyStyle() { 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); + 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( @@ -96,12 +123,20 @@ void ChartWidget::applyStyle() { 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); + } } } @@ -127,9 +162,9 @@ void ChartWidget::refreshData() { config_.chartTextSource[i], static_cast(config_.chartPeriod), seriesData_[i].data(), - seriesData_[i].size()); + pointCount_); - for (size_t j = 0; j < seriesData_[i].size(); ++j) { + for (size_t j = 0; j < pointCount_; ++j) { int32_t value = seriesData_[i][j]; if (value == HistoryStore::NO_POINT) continue; if (!hasAny) { diff --git a/main/widgets/ChartWidget.hpp b/main/widgets/ChartWidget.hpp index 072f2bb..89bf17a 100644 --- a/main/widgets/ChartWidget.hpp +++ b/main/widgets/ChartWidget.hpp @@ -22,4 +22,5 @@ private: lv_obj_t* xScale_ = nullptr; lv_chart_series_t* series_[CHART_MAX_SERIES] = {}; std::array, CHART_MAX_SERIES> seriesData_ = {}; + uint8_t pointCount_ = HistoryStore::CHART_POINT_COUNT; }; diff --git a/web-interface/src/components/widgets/elements/ChartElement.vue b/web-interface/src/components/widgets/elements/ChartElement.vue index 369b427..38b0e3d 100644 --- a/web-interface/src/components/widgets/elements/ChartElement.vue +++ b/web-interface/src/components/widgets/elements/ChartElement.vue @@ -7,16 +7,42 @@ @touchstart.stop="$emit('drag-start', { id: widget.id, event: $event })" @click.stop="$emit('select')" > -
-
- {{ widget.text || 'Chart' }} +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + +
+
+
+
-
-
- - - - + +
+
+ {{ label }} +
@@ -45,10 +71,89 @@ const props = defineProps({ defineEmits(['select', 'drag-start', 'resize-start']); +const showXLine = computed(() => props.widget?.chart?.showXLine !== false); +const showYLine = computed(() => props.widget?.chart?.showYLine !== false); +const showXLabels = computed(() => props.widget?.chart?.showXLabels !== false); +const showYLabels = computed(() => props.widget?.chart?.showYLabels !== false); +const showBg = computed(() => props.widget?.chart?.showBg !== false); +const showGrid = computed(() => props.widget?.chart?.showGrid !== false); + +const showPoints = computed(() => props.widget?.chart?.showPoints === true); +const hasYAxis = computed(() => showYLine.value || showYLabels.value); +const hasXAxis = computed(() => showXLine.value || showXLabels.value); +const yAxisWidth = computed(() => hasYAxis.value ? Math.round(36 * props.scale) : 0); +const xAxisHeight = computed(() => hasXAxis.value ? Math.round(24 * props.scale) : 0); +const topPad = computed(() => Math.round(8 * props.scale)); +const axisFontSize = computed(() => Math.round(9 * props.scale)); +const lineWidth = computed(() => Math.max(1, props.widget?.chart?.lineWidth ?? 2)); +const pointDiameter = computed(() => Math.max(2, (props.widget?.chart?.pointSize ?? 4) * props.scale)); +const numPoints = computed(() => { + const pc = props.widget?.chart?.pointCount ?? 120; + // Scale down for preview (don't render 120 dots) + return Math.max(5, Math.min(pc, 20)); +}); + +const periodXLabels = { + 0: ['-60m', '-45m', '-30m', '-15m', '0'], + 1: ['-3h', '-2h', '-1h', '-30m', '0'], + 2: ['-5h', '-4h', '-3h', '-2h', '-1h', '0'], + 3: ['-12h', '-9h', '-6h', '-3h', '0'], + 4: ['-24h', '-18h', '-12h', '-6h', '0'], + 5: ['-30d', '-21d', '-14d', '-7d', '0'] +}; + +const xLabels = computed(() => { + const period = props.widget?.chart?.period ?? 0; + return periodXLabels[period] || periodXLabels[0]; +}); + +const yLabels = computed(() => ['100', '75', '50', '25', '0']); + +const defaultColors = ['#EF6351', '#7DD3B0', '#5EA2EF']; + +const seriesColors = computed(() => { + const series = props.widget?.chart?.series ?? []; + return series.map((s, i) => s.color || defaultColors[i] || defaultColors[0]); +}); + +// Generate demo wave data per series +const seriesPaths = computed(() => { + const series = props.widget?.chart?.series ?? []; + const count = Math.max(1, Math.min(series.length, 3)); + const steps = numPoints.value; + const result = []; + for (let s = 0; s < count; s++) { + const linePoints = []; + const dots = []; + const phase = s * 2.2; + const amp = 12 + s * 4; + for (let i = 0; i <= steps; i++) { + const x = (i / steps) * 100; + const y = 25 + amp * Math.sin((i / steps) * Math.PI * 2.5 + phase) * 0.4; + const cx = clamp(x, 0, 100); + const cy = clamp(y, 2, 48); + linePoints.push(`${cx.toFixed(1)},${cy.toFixed(1)}`); + dots.push({ x: cx, y: (cy / 50) * 100 }); // map 0-50 viewBox to 0-100% + } + result.push({ line: linePoints.join(' '), dots }); + } + return result; +}); + +const allPoints = computed(() => { + const colors = seriesColors.value; + const pts = []; + seriesPaths.value.forEach((sp, si) => { + sp.dots.forEach(d => { + pts.push({ x: d.x, y: d.y, color: colors[si] || '#EF6351' }); + }); + }); + return pts; +}); + const computedStyle = computed(() => { const w = props.widget; const s = props.scale; - const style = getBaseStyle(w, s); if (w.bgOpacity > 0) { @@ -61,8 +166,7 @@ const computedStyle = computed(() => { } Object.assign(style, getBorderStyle(w, s)); - - style.padding = `${12 * s}px`; + style.padding = `${4 * s}px`; return style; }); diff --git a/web-interface/src/components/widgets/settings/ChartSettings.vue b/web-interface/src/components/widgets/settings/ChartSettings.vue index 6ddeb04..a59c223 100644 --- a/web-interface/src/components/widgets/settings/ChartSettings.vue +++ b/web-interface/src/components/widgets/settings/ChartSettings.vue @@ -15,6 +15,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/web-interface/src/constants.js b/web-interface/src/constants.js index 19f986e..41b18c5 100644 --- a/web-interface/src/constants.js +++ b/web-interface/src/constants.js @@ -434,6 +434,16 @@ export const WIDGET_DEFAULTS = { themeFixed: false, chart: { period: 0, + showXLine: true, + showYLine: true, + showXLabels: true, + showYLabels: true, + showBg: true, + showGrid: true, + lineWidth: 2, + pointCount: 120, + showPoints: false, + pointSize: 4, series: [ { knxAddr: 0, textSrc: 1, color: '#EF6351' } ] diff --git a/web-interface/src/utils.js b/web-interface/src/utils.js index 13669b0..28303a3 100644 --- a/web-interface/src/utils.js +++ b/web-interface/src/utils.js @@ -157,6 +157,16 @@ export function normalizeWidget(w, nextWidgetIdRef) { if (w.chart.period === undefined || w.chart.period === null) { w.chart.period = defaults.chart.period ?? 0; } + if (w.chart.showXLine === undefined) w.chart.showXLine = true; + if (w.chart.showYLine === undefined) w.chart.showYLine = true; + if (w.chart.showXLabels === undefined) w.chart.showXLabels = true; + if (w.chart.showYLabels === undefined) w.chart.showYLabels = true; + if (w.chart.showBg === undefined) w.chart.showBg = true; + if (w.chart.showGrid === undefined) w.chart.showGrid = true; + if (w.chart.lineWidth === undefined) w.chart.lineWidth = 2; + if (w.chart.pointCount === undefined) w.chart.pointCount = 120; + if (w.chart.showPoints === undefined) w.chart.showPoints = false; + if (w.chart.pointSize === undefined) w.chart.pointSize = 4; if (!Array.isArray(w.chart.series)) { w.chart.series = []; }