Fixes
This commit is contained in:
parent
05d50c5ad7
commit
556e72311f
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.cache/clangd/index/NetworkConfig.cpp.28689B151DE5324A.idx
Normal file
BIN
.cache/clangd/index/NetworkConfig.cpp.28689B151DE5324A.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/NetworkConfig.hpp.3283725D56F161D1.idx
Normal file
BIN
.cache/clangd/index/NetworkConfig.hpp.3283725D56F161D1.idx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<uint8_t>(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<uint8_t>(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;
|
||||
|
||||
@ -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<ChartPeriod>(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) {
|
||||
|
||||
@ -22,4 +22,5 @@ private:
|
||||
lv_obj_t* xScale_ = nullptr;
|
||||
lv_chart_series_t* series_[CHART_MAX_SERIES] = {};
|
||||
std::array<std::array<int32_t, HistoryStore::CHART_POINT_COUNT>, CHART_MAX_SERIES> seriesData_ = {};
|
||||
uint8_t pointCount_ = HistoryStore::CHART_POINT_COUNT;
|
||||
};
|
||||
|
||||
@ -7,16 +7,42 @@
|
||||
@touchstart.stop="$emit('drag-start', { id: widget.id, event: $event })"
|
||||
@click.stop="$emit('select')"
|
||||
>
|
||||
<div class="w-full h-full flex flex-col gap-2">
|
||||
<div class="text-[11px] uppercase tracking-[0.12em] opacity-80">
|
||||
{{ widget.text || 'Chart' }}
|
||||
<div class="w-full h-full flex flex-col" style="overflow: hidden;">
|
||||
<!-- Chart area -->
|
||||
<div class="flex-1 flex min-h-0">
|
||||
<!-- Y axis -->
|
||||
<div v-if="hasYAxis" class="flex flex-col justify-between items-end shrink-0 pr-[3px]" :style="{ width: yAxisWidth + 'px', paddingTop: topPad + 'px' }">
|
||||
<template v-if="showYLabels">
|
||||
<span v-for="(label, i) in yLabels" :key="i" :style="{ fontSize: axisFontSize + 'px', color: widget.textColor, opacity: 0.7, lineHeight: '1' }">{{ label }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span v-for="(label, i) in yLabels" :key="i" :style="{ fontSize: axisFontSize + 'px', opacity: 0 }">{{ label }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Chart plot area -->
|
||||
<div class="flex-1 relative" :style="{ marginTop: topPad + 'px' }">
|
||||
<!-- Background -->
|
||||
<div v-if="showBg" class="absolute inset-0 rounded-[4px]" :style="{ background: 'rgba(0,0,0,0.15)' }"></div>
|
||||
<!-- Grid -->
|
||||
<svg v-if="showGrid" class="absolute inset-0 w-full h-full" preserveAspectRatio="none">
|
||||
<line v-for="i in 4" :key="'h'+i" :x1="0" :y1="(i * 100 / 4) + '%'" :x2="'100%'" :y2="(i * 100 / 4) + '%'" stroke="currentColor" :stroke-opacity="0.12" stroke-width="1" />
|
||||
<line v-for="i in (xLabels.length - 1)" :key="'v'+i" :x1="(i * 100 / (xLabels.length - 1)) + '%'" :y1="0" :x2="(i * 100 / (xLabels.length - 1)) + '%'" :y2="'100%'" stroke="currentColor" :stroke-opacity="0.12" stroke-width="1" />
|
||||
</svg>
|
||||
<!-- Series lines -->
|
||||
<svg class="absolute inset-0 w-full h-full" viewBox="0 0 100 50" preserveAspectRatio="none">
|
||||
<polyline v-for="(path, i) in seriesPaths" :key="'s'+i" :points="path.line" fill="none" :stroke="seriesColors[i]" :stroke-width="lineWidth" vector-effect="non-scaling-stroke" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<!-- Point markers -->
|
||||
<div v-if="showPoints" class="absolute inset-0">
|
||||
<div v-for="(pt, pi) in allPoints" :key="pi" class="absolute rounded-full" :style="{ left: pt.x + '%', top: pt.y + '%', width: pointDiameter + 'px', height: pointDiameter + 'px', transform: 'translate(-50%, -50%)', backgroundColor: pt.color }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 rounded-[10px] bg-black/20 relative overflow-hidden">
|
||||
<div class="absolute inset-0 opacity-30" style="background-image: linear-gradient(to right, rgba(255,255,255,0.1) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,0.1) 1px, transparent 1px); background-size: 24px 24px;"></div>
|
||||
<svg class="absolute inset-0" viewBox="0 0 100 40" preserveAspectRatio="none">
|
||||
<path d="M0,30 L15,22 L30,26 L45,14 L60,18 L75,10 L100,16" fill="none" stroke="rgba(239,99,81,0.8)" stroke-width="2" />
|
||||
<path d="M0,34 L20,28 L40,32 L60,20 L80,24 L100,18" fill="none" stroke="rgba(125,211,176,0.8)" stroke-width="2" />
|
||||
</svg>
|
||||
<!-- X axis -->
|
||||
<div v-if="hasXAxis" class="flex shrink-0" :style="{ height: xAxisHeight + 'px', paddingLeft: yAxisWidth + 'px' }">
|
||||
<div class="flex-1 flex justify-between items-start">
|
||||
<span v-for="(label, i) in xLabels" :key="i" :style="{ fontSize: axisFontSize + 'px', color: widget.textColor, opacity: showXLabels ? 0.7 : 0, lineHeight: '1' }">{{ label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -15,6 +15,16 @@
|
||||
<option :value="3">3</option>
|
||||
</select>
|
||||
</div>
|
||||
<div :class="rowClass"><label :class="labelClass">X-Linie</label><input class="accent-[var(--accent)]" type="checkbox" v-model="widget.chart.showXLine"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Y-Linie</label><input class="accent-[var(--accent)]" type="checkbox" v-model="widget.chart.showYLine"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">X-Labels</label><input class="accent-[var(--accent)]" type="checkbox" v-model="widget.chart.showXLabels"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Y-Labels</label><input class="accent-[var(--accent)]" type="checkbox" v-model="widget.chart.showYLabels"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Hintergrund</label><input class="accent-[var(--accent)]" type="checkbox" v-model="widget.chart.showBg"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Gitterlinien</label><input class="accent-[var(--accent)]" type="checkbox" v-model="widget.chart.showGrid"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Liniendicke</label><input :class="inputClass" type="number" min="1" max="10" v-model.number="widget.chart.lineWidth"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Datenpunkte</label><input :class="inputClass" type="number" min="10" max="120" v-model.number="widget.chart.pointCount"></div>
|
||||
<div :class="rowClass"><label :class="labelClass">Punkte zeigen</label><input class="accent-[var(--accent)]" type="checkbox" v-model="widget.chart.showPoints"></div>
|
||||
<div v-if="widget.chart.showPoints" :class="rowClass"><label :class="labelClass">Punktgröße</label><input :class="inputClass" type="number" min="2" max="20" v-model.number="widget.chart.pointSize"></div>
|
||||
<div v-for="(series, idx) in chartSeries" :key="idx" class="border border-border rounded-lg px-2.5 py-2 mb-2 bg-panel-2">
|
||||
<div class="flex items-center gap-2 mb-2 text-[11px] text-muted">
|
||||
<label class="w-[70px] text-[11px] text-muted">Serie {{ idx + 1 }}</label>
|
||||
|
||||
@ -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' }
|
||||
]
|
||||
|
||||
@ -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 = [];
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user