refactor, new features
This commit is contained in:
parent
376d0a17e8
commit
14e22cc636
@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(nvme-monitor)
|
||||
project(temp-monitor)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
||||
@ -44,19 +44,19 @@ set(HEADERS
|
||||
include/config_manager.h
|
||||
)
|
||||
|
||||
add_executable(nvme-monitor ${SOURCES} ${HEADERS})
|
||||
add_executable(temp-monitor ${SOURCES} ${HEADERS})
|
||||
|
||||
target_link_libraries(nvme-monitor
|
||||
target_link_libraries(temp-monitor
|
||||
${GTK_LIBRARIES}
|
||||
${CAIRO_LIBRARIES}
|
||||
m
|
||||
)
|
||||
|
||||
target_compile_options(nvme-monitor PRIVATE ${GTK_CFLAGS_OTHER})
|
||||
target_compile_options(nvme-monitor PRIVATE ${CAIRO_CFLAGS_OTHER})
|
||||
target_compile_options(temp-monitor PRIVATE ${GTK_CFLAGS_OTHER})
|
||||
target_compile_options(temp-monitor PRIVATE ${CAIRO_CFLAGS_OTHER})
|
||||
|
||||
# Install executable
|
||||
install(TARGETS nvme-monitor
|
||||
install(TARGETS temp-monitor
|
||||
DESTINATION bin)
|
||||
|
||||
# Install desktop file for system integration
|
||||
|
||||
2
build.sh
2
build.sh
@ -8,4 +8,4 @@ cd build
|
||||
cmake ..
|
||||
make
|
||||
|
||||
echo "Build complete! Run with: ./nvme-monitor"
|
||||
echo "Build complete! Run with: ./temp-monitor"
|
||||
|
||||
@ -18,16 +18,20 @@ public:
|
||||
int getWindowWidth() const { return windowWidth; }
|
||||
int getWindowHeight() const { return windowHeight; }
|
||||
int getPollingTime() const { return pollingTime; }
|
||||
int getHistoryLength() const { return historyLength; }
|
||||
|
||||
void setWindowWidth(int w) { windowWidth = w; }
|
||||
void setWindowHeight(int h) { windowHeight = h; }
|
||||
void setPollingTime(int t) { pollingTime = t; }
|
||||
void setHistoryLength(int m) { historyLength = m; }
|
||||
|
||||
std::map<std::string, std::string> getSensorColors() const { return sensorColors; }
|
||||
std::map<std::string, std::string> getSensorNames() const { return sensorNames; }
|
||||
std::map<std::string, bool> getSensorEnabled() const { return sensorEnabled; }
|
||||
|
||||
void setSensorColor(const std::string &id, const std::string &color) { sensorColors[id] = color; }
|
||||
void setSensorName(const std::string &id, const std::string &name) { sensorNames[id] = name; }
|
||||
void setSensorEnabled(const std::string &id, bool enabled) { sensorEnabled[id] = enabled; }
|
||||
|
||||
private:
|
||||
std::string getConfigFilePath() const;
|
||||
@ -36,9 +40,11 @@ private:
|
||||
int windowWidth;
|
||||
int windowHeight;
|
||||
int pollingTime;
|
||||
int historyLength;
|
||||
|
||||
std::map<std::string, std::string> sensorColors;
|
||||
std::map<std::string, std::string> sensorNames;
|
||||
std::map<std::string, bool> sensorEnabled;
|
||||
};
|
||||
|
||||
#endif // CONFIG_MANAGER_H
|
||||
|
||||
@ -24,14 +24,17 @@ private:
|
||||
static gboolean onDeleteWindow(GtkWidget *widget, gpointer userData);
|
||||
static gboolean onUpdateTimer(gpointer userData);
|
||||
static void onRefreshRateChanged(GtkSpinButton *spinButton, gpointer userData);
|
||||
static void onHistoryLengthChanged(GtkSpinButton *spinButton, gpointer userData);
|
||||
static void onClearButtonClicked(GtkButton *button, gpointer userData);
|
||||
static void onQuitButtonClicked(GtkButton *button, gpointer userData);
|
||||
static void onColorSet(GObject *object, GParamSpec *pspec, gpointer userData);
|
||||
static void onNameChanged(GtkEditable *editable, gpointer userData);
|
||||
static void onVisibilityToggled(GtkCheckButton *checkButton, gpointer userData);
|
||||
|
||||
GtkWidget *window;
|
||||
GtkWidget *statusLabel;
|
||||
GtkSpinButton *refreshRateSpinBox;
|
||||
GtkSpinButton *historyLengthSpinBox;
|
||||
std::unique_ptr<TemperatureChart> chart;
|
||||
std::unique_ptr<TempMonitor> monitor;
|
||||
std::unique_ptr<ConfigManager> config;
|
||||
|
||||
@ -18,6 +18,7 @@ struct SeriesData {
|
||||
GdkRGBA color;
|
||||
std::string id; // Unique internal ID (device + sensor)
|
||||
std::string name; // User-friendly display name
|
||||
bool visible = true;
|
||||
};
|
||||
|
||||
class TemperatureChart {
|
||||
@ -40,6 +41,12 @@ public:
|
||||
std::string getSeriesName(const std::string &seriesId) const;
|
||||
void setSeriesName(const std::string &seriesId, const std::string &name);
|
||||
|
||||
// Visibility management
|
||||
bool isSeriesVisible(const std::string &seriesId) const;
|
||||
void setSeriesVisible(const std::string &seriesId, bool visible);
|
||||
|
||||
void setHistoryLength(int minutes);
|
||||
|
||||
void drawChart(GtkDrawingArea *area, cairo_t *cr, int width, int height);
|
||||
|
||||
private:
|
||||
@ -75,7 +82,7 @@ private:
|
||||
// Cairo drawing state
|
||||
double minTemp, maxTemp;
|
||||
int64_t minTime, maxTime;
|
||||
static constexpr int MAX_TIME_MS = 600000; // 10 minutes in milliseconds
|
||||
int historyLengthMinutes;
|
||||
static constexpr double MIN_TEMP = 10.0;
|
||||
static constexpr double MAX_TEMP = 110.0;
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
ConfigManager::ConfigManager()
|
||||
: windowWidth(1200), windowHeight(700), pollingTime(1)
|
||||
: windowWidth(1200), windowHeight(700), pollingTime(1), historyLength(10)
|
||||
{
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ std::string ConfigManager::getConfigFilePath() const
|
||||
|
||||
if (len == -1) {
|
||||
// Fallback to current directory
|
||||
cachedConfigPath = "./nvme-monitor.conf";
|
||||
cachedConfigPath = "./temp-monitor.conf";
|
||||
return cachedConfigPath;
|
||||
}
|
||||
|
||||
@ -34,11 +34,11 @@ std::string ConfigManager::getConfigFilePath() const
|
||||
size_t lastSlash = fullPath.find_last_of('/');
|
||||
if (lastSlash != std::string::npos) {
|
||||
std::string exeDir = fullPath.substr(0, lastSlash);
|
||||
cachedConfigPath = exeDir + "/nvme-monitor.conf";
|
||||
cachedConfigPath = exeDir + "/temp-monitor.conf";
|
||||
return cachedConfigPath;
|
||||
}
|
||||
|
||||
cachedConfigPath = "./nvme-monitor.conf";
|
||||
cachedConfigPath = "./temp-monitor.conf";
|
||||
return cachedConfigPath;
|
||||
}
|
||||
|
||||
@ -75,10 +75,14 @@ void ConfigManager::load()
|
||||
windowHeight = std::stoi(value);
|
||||
} else if (key == "polling_time") {
|
||||
pollingTime = std::stoi(value);
|
||||
} else if (key == "history_length") {
|
||||
historyLength = std::stoi(value);
|
||||
} else if (key.find("color_") == 0) {
|
||||
sensorColors[key.substr(6)] = value;
|
||||
} else if (key.find("name_") == 0) {
|
||||
sensorNames[key.substr(5)] = value;
|
||||
} else if (key.find("enabled_") == 0) {
|
||||
sensorEnabled[key.substr(8)] = (value == "true" || value == "1");
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "Config error: invalid value for '" << key << "': " << value << " (" << e.what() << ")" << std::endl;
|
||||
@ -104,6 +108,7 @@ void ConfigManager::save()
|
||||
file << "window_width = " << windowWidth << "\n";
|
||||
file << "window_height = " << windowHeight << "\n";
|
||||
file << "polling_time = " << pollingTime << "\n";
|
||||
file << "history_length = " << historyLength << "\n";
|
||||
|
||||
for (auto const& [id, color] : sensorColors) {
|
||||
file << "color_" << id << " = " << color << "\n";
|
||||
@ -112,6 +117,10 @@ void ConfigManager::save()
|
||||
for (auto const& [id, name] : sensorNames) {
|
||||
file << "name_" << id << " = " << name << "\n";
|
||||
}
|
||||
|
||||
for (auto const& [id, enabled] : sensorEnabled) {
|
||||
file << "enabled_" << id << " = " << (enabled ? "true" : "false") << "\n";
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
@ -8,7 +8,8 @@
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
|
||||
MainWindow::MainWindow()
|
||||
: window(nullptr), statusLabel(nullptr), refreshRateSpinBox(nullptr),
|
||||
: window(nullptr), statusLabel(nullptr),
|
||||
refreshRateSpinBox(nullptr), historyLengthSpinBox(nullptr),
|
||||
timerID(0), refreshRateSec(3)
|
||||
{
|
||||
monitor = std::make_unique<TempMonitor>();
|
||||
@ -19,6 +20,12 @@ MainWindow::MainWindow()
|
||||
|
||||
setupUI();
|
||||
|
||||
int historyLen = config->getHistoryLength();
|
||||
chart->setHistoryLength(historyLen);
|
||||
if (historyLengthSpinBox) {
|
||||
gtk_spin_button_set_value(historyLengthSpinBox, historyLen);
|
||||
}
|
||||
|
||||
// Set window size from config
|
||||
gtk_window_set_default_size(GTK_WINDOW(window), config->getWindowWidth(), config->getWindowHeight());
|
||||
|
||||
@ -50,7 +57,7 @@ void MainWindow::setupUI()
|
||||
g_object_unref(provider);
|
||||
|
||||
window = gtk_window_new();
|
||||
gtk_window_set_title(GTK_WINDOW(window), "NVMe Temperature Monitor");
|
||||
gtk_window_set_title(GTK_WINDOW(window), "Temperature Monitor");
|
||||
|
||||
// Set window icon - in GTK4 we load pixbuf and use it for the window display
|
||||
GError *error = nullptr;
|
||||
@ -92,6 +99,17 @@ void MainWindow::setupUI()
|
||||
g_signal_connect(refreshRateSpinBox, "value-changed", G_CALLBACK(onRefreshRateChanged), this);
|
||||
gtk_box_append(GTK_BOX(controlBox), GTK_WIDGET(refreshRateSpinBox));
|
||||
|
||||
// History length label
|
||||
GtkWidget *historyLabel = gtk_label_new("History (min):");
|
||||
gtk_box_append(GTK_BOX(controlBox), historyLabel);
|
||||
|
||||
// History length spinner (5-60 min, step 5)
|
||||
GtkAdjustment *histAdjustment = gtk_adjustment_new(10, 5, 60, 5, 10, 0);
|
||||
historyLengthSpinBox = GTK_SPIN_BUTTON(gtk_spin_button_new(histAdjustment, 5, 0));
|
||||
gtk_widget_set_size_request(GTK_WIDGET(historyLengthSpinBox), 80, -1);
|
||||
g_signal_connect(historyLengthSpinBox, "value-changed", G_CALLBACK(onHistoryLengthChanged), this);
|
||||
gtk_box_append(GTK_BOX(controlBox), GTK_WIDGET(historyLengthSpinBox));
|
||||
|
||||
// Clear button
|
||||
GtkWidget *clearButton = gtk_button_new_with_label("Clear Data");
|
||||
g_signal_connect(clearButton, "clicked", G_CALLBACK(onClearButtonClicked), this);
|
||||
@ -161,6 +179,14 @@ void MainWindow::onRefreshRateChanged(GtkSpinButton *spinButton, gpointer userDa
|
||||
self->timerID = g_timeout_add(self->refreshRateSec * 1000, onUpdateTimer, self);
|
||||
}
|
||||
|
||||
void MainWindow::onHistoryLengthChanged(GtkSpinButton *spinButton, gpointer userData)
|
||||
{
|
||||
MainWindow *self = static_cast<MainWindow*>(userData);
|
||||
int minutes = gtk_spin_button_get_value_as_int(spinButton);
|
||||
self->chart->setHistoryLength(minutes);
|
||||
self->config->setHistoryLength(minutes);
|
||||
}
|
||||
|
||||
void MainWindow::onClearButtonClicked(GtkButton *button, gpointer userData)
|
||||
{
|
||||
MainWindow *self = static_cast<MainWindow*>(userData);
|
||||
@ -273,15 +299,30 @@ void MainWindow::updateLegend()
|
||||
auto seriesColors = chart->getSeriesColors();
|
||||
|
||||
// Add legend items
|
||||
auto sensorEnabledMap = config->getSensorEnabled();
|
||||
|
||||
for (const auto &pair : seriesColors) {
|
||||
const std::string &seriesId = pair.first;
|
||||
const GdkRGBA &color = pair.second;
|
||||
std::string displayName = chart->getSeriesName(seriesId);
|
||||
|
||||
bool isVisible = true;
|
||||
if (sensorEnabledMap.find(seriesId) != sensorEnabledMap.end()) {
|
||||
isVisible = sensorEnabledMap[seriesId];
|
||||
}
|
||||
chart->setSeriesVisible(seriesId, isVisible);
|
||||
|
||||
// Create container for legend item
|
||||
GtkWidget *itemBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
|
||||
gtk_widget_add_css_class(itemBox, "legend-item");
|
||||
|
||||
// Checkbox for visibility
|
||||
GtkWidget *checkButton = gtk_check_button_new();
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(checkButton), isVisible);
|
||||
g_object_set_data_full(G_OBJECT(checkButton), "series-name", g_strdup(seriesId.c_str()), g_free);
|
||||
g_signal_connect(checkButton, "toggled", G_CALLBACK(onVisibilityToggled), this);
|
||||
gtk_box_append(GTK_BOX(itemBox), checkButton);
|
||||
|
||||
// Create color dialog and button
|
||||
GtkColorDialog *dialog = gtk_color_dialog_new();
|
||||
GtkWidget *colorButton = gtk_color_dialog_button_new(dialog);
|
||||
@ -354,3 +395,16 @@ void MainWindow::onNameChanged(GtkEditable *editable, gpointer userData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onVisibilityToggled(GtkCheckButton *checkButton, gpointer userData)
|
||||
{
|
||||
MainWindow *self = static_cast<MainWindow*>(userData);
|
||||
const char *seriesId = static_cast<const char*>(g_object_get_data(G_OBJECT(checkButton), "series-name"));
|
||||
|
||||
if (seriesId) {
|
||||
bool visible = gtk_check_button_get_active(checkButton);
|
||||
self->chart->setSeriesVisible(seriesId, visible);
|
||||
self->config->setSensorEnabled(seriesId, visible);
|
||||
self->config->save();
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ TemperatureChart::TemperatureChart()
|
||||
: drawingArea(nullptr), tooltipWindow(nullptr), tooltipLabel(nullptr),
|
||||
maxDataPoints(600), tickHandler(0),
|
||||
minTemp(MIN_TEMP), maxTemp(MAX_TEMP), minTime(0), maxTime(0),
|
||||
lastMouseX(-1), lastMouseY(-1)
|
||||
lastMouseX(-1), lastMouseY(-1), historyLengthMinutes(10)
|
||||
{
|
||||
drawingArea = gtk_drawing_area_new();
|
||||
gtk_widget_set_size_request(drawingArea, 800, 400);
|
||||
@ -200,8 +200,8 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std::
|
||||
|
||||
seriesMap[seriesKey].points.push_back(point);
|
||||
|
||||
// Keep only last 10 minutes of data
|
||||
int64_t cutoffTime = timestamp - MAX_TIME_MS;
|
||||
// Keep only historyLengthMinutes of data
|
||||
int64_t cutoffTime = timestamp - (static_cast<int64_t>(historyLengthMinutes) * 60 * 1000);
|
||||
for (auto &entry : seriesMap) {
|
||||
auto &points = entry.second.points;
|
||||
auto it = points.begin();
|
||||
@ -216,6 +216,7 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std::
|
||||
bool hasData = false;
|
||||
|
||||
for (const auto &entry : seriesMap) {
|
||||
if (!entry.second.visible) continue;
|
||||
for (const auto &p : entry.second.points) {
|
||||
if (p.temperature < currentMin) currentMin = p.temperature;
|
||||
if (p.temperature > currentMax) currentMax = p.temperature;
|
||||
@ -240,9 +241,9 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std::
|
||||
maxTemp = MAX_TEMP;
|
||||
}
|
||||
|
||||
// Update time range - keep 10 minute window
|
||||
// Update time range - keep dynamic window
|
||||
maxTime = timestamp;
|
||||
minTime = timestamp - MAX_TIME_MS;
|
||||
minTime = timestamp - (static_cast<int64_t>(historyLengthMinutes) * 60 * 1000);
|
||||
|
||||
// Trigger redraw
|
||||
gtk_widget_queue_draw(drawingArea);
|
||||
@ -283,6 +284,67 @@ void TemperatureChart::setSeriesColor(const std::string &seriesName, const GdkRG
|
||||
gtk_widget_queue_draw(drawingArea);
|
||||
}
|
||||
|
||||
void TemperatureChart::setHistoryLength(int minutes)
|
||||
{
|
||||
historyLengthMinutes = minutes;
|
||||
|
||||
// Immediately recalculate ranges based on new history length
|
||||
if (!seriesMap.empty()) {
|
||||
int64_t latestTimestamp = 0;
|
||||
for (const auto &entry : seriesMap) {
|
||||
if (!entry.second.points.empty()) {
|
||||
latestTimestamp = std::max(latestTimestamp, entry.second.points.back().timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
if (latestTimestamp > 0) {
|
||||
int64_t cutoffTime = latestTimestamp - (static_cast<int64_t>(historyLengthMinutes) * 60 * 1000);
|
||||
|
||||
double currentMin = 1000.0;
|
||||
double currentMax = -1000.0;
|
||||
bool hasData = false;
|
||||
|
||||
for (auto &entry : seriesMap) {
|
||||
auto &points = entry.second.points;
|
||||
|
||||
if (!entry.second.visible) continue;
|
||||
|
||||
// Prune points that are now outside the new history window
|
||||
auto it = points.begin();
|
||||
while (it != points.end() && it->timestamp < cutoffTime) {
|
||||
it = points.erase(it);
|
||||
}
|
||||
|
||||
for (const auto &p : points) {
|
||||
if (p.temperature < currentMin) currentMin = p.temperature;
|
||||
if (p.temperature > currentMax) currentMax = p.temperature;
|
||||
hasData = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasData) {
|
||||
minTemp = std::floor(currentMin / 10.0) * 10.0;
|
||||
maxTemp = std::ceil(currentMax / 10.0) * 10.0;
|
||||
|
||||
// Ensure at least 20 degrees range
|
||||
if (maxTemp - minTemp < 20.0) {
|
||||
maxTemp = minTemp + 20.0;
|
||||
}
|
||||
|
||||
if (minTemp > 30.0) minTemp = 30.0;
|
||||
} else {
|
||||
minTemp = MIN_TEMP;
|
||||
maxTemp = MAX_TEMP;
|
||||
}
|
||||
|
||||
maxTime = latestTimestamp;
|
||||
minTime = cutoffTime;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_queue_draw(drawingArea);
|
||||
}
|
||||
|
||||
std::string TemperatureChart::getSeriesName(const std::string &seriesId) const
|
||||
{
|
||||
auto it = seriesMap.find(seriesId);
|
||||
@ -292,6 +354,45 @@ std::string TemperatureChart::getSeriesName(const std::string &seriesId) const
|
||||
return seriesId;
|
||||
}
|
||||
|
||||
bool TemperatureChart::isSeriesVisible(const std::string &seriesId) const
|
||||
{
|
||||
auto it = seriesMap.find(seriesId);
|
||||
if (it != seriesMap.end()) {
|
||||
return it->second.visible;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TemperatureChart::setSeriesVisible(const std::string &seriesId, bool visible)
|
||||
{
|
||||
if (seriesMap.find(seriesId) != seriesMap.end()) {
|
||||
seriesMap[seriesId].visible = visible;
|
||||
|
||||
// Recalculate temperature range
|
||||
double currentMin = 1000.0;
|
||||
double currentMax = -1000.0;
|
||||
bool hasVisibleData = false;
|
||||
|
||||
for (const auto &entry : seriesMap) {
|
||||
if (!entry.second.visible) continue;
|
||||
for (const auto &p : entry.second.points) {
|
||||
if (p.temperature < currentMin) currentMin = p.temperature;
|
||||
if (p.temperature > currentMax) currentMax = p.temperature;
|
||||
hasVisibleData = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasVisibleData) {
|
||||
minTemp = std::floor(currentMin / 10.0) * 10.0;
|
||||
maxTemp = std::ceil(currentMax / 10.0) * 10.0;
|
||||
if (maxTemp - minTemp < 20.0) maxTemp = minTemp + 20.0;
|
||||
if (minTemp > 30.0) minTemp = 30.0;
|
||||
}
|
||||
|
||||
gtk_widget_queue_draw(drawingArea);
|
||||
}
|
||||
}
|
||||
|
||||
void TemperatureChart::setSeriesName(const std::string &seriesId, const std::string &name)
|
||||
{
|
||||
if (seriesMap.find(seriesId) != seriesMap.end()) {
|
||||
@ -371,13 +472,12 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
||||
cairo_show_text(cr, tempStr);
|
||||
}
|
||||
|
||||
// Time labels on X axis (5 points for 10-minute window)
|
||||
// Time labels on X axis (5 points for history window)
|
||||
for (int i = 0; i <= 5; i++) {
|
||||
double x = marginLeft + (plotWidth * i / 5.0);
|
||||
|
||||
// Calculate time offset in seconds
|
||||
int secondsOffset = (5 - i) * 120; // 10 minutes / 5 = 2 minutes between points
|
||||
int minutes = secondsOffset / 60;
|
||||
// Calculate time offset in minutes
|
||||
int minutes = (5 - i) * historyLengthMinutes / 5;
|
||||
|
||||
char timeStr[16];
|
||||
snprintf(timeStr, sizeof(timeStr), "-%d m", minutes);
|
||||
@ -386,11 +486,11 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
||||
cairo_show_text(cr, timeStr);
|
||||
}
|
||||
|
||||
// Draw data series (right to left, with 10-minute window)
|
||||
// Draw data series (right to left, with history window)
|
||||
for (auto &seriesEntry : seriesMap) {
|
||||
SeriesData &series = seriesEntry.second;
|
||||
|
||||
if (series.points.empty()) continue;
|
||||
if (!series.visible || series.points.empty()) continue;
|
||||
|
||||
// Set series color
|
||||
cairo_set_source_rgba(cr, series.color.red, series.color.green,
|
||||
@ -400,7 +500,8 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
||||
bool firstPoint = true;
|
||||
for (const DataPoint &point : series.points) {
|
||||
// Reverse X axis - newer data on the right, older on the left
|
||||
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / MAX_TIME_MS);
|
||||
int64_t historyMs = static_cast<int64_t>(historyLengthMinutes) * 60 * 1000;
|
||||
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / historyMs);
|
||||
double y = marginTop + plotHeight * (1.0 - (point.temperature - minTemp) / (maxTemp - minTemp));
|
||||
|
||||
if (firstPoint) {
|
||||
@ -444,8 +545,11 @@ TemperatureChart::NearestPoint TemperatureChart::findNearestDataPoint(double mou
|
||||
for (auto &seriesEntry : seriesMap) {
|
||||
SeriesData &series = seriesEntry.second;
|
||||
|
||||
if (!series.visible) continue;
|
||||
|
||||
for (const DataPoint &point : series.points) {
|
||||
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / MAX_TIME_MS);
|
||||
int64_t historyMs = static_cast<int64_t>(historyLengthMinutes) * 60 * 1000;
|
||||
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / historyMs);
|
||||
double y = marginTop + plotHeight * (1.0 - (point.temperature - minTemp) / (maxTemp - minTemp));
|
||||
|
||||
double distance = std::hypot(mouseX - x, mouseY - y);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user