Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion plotjuggler_app/curve_tracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,13 +285,18 @@ std::optional<QPointF> curvePointAt(const QwtPlotCurve* curve, double x)
{
int index = qwtUpperSampleIndex<QPointF>(*curve->data(), x, compareX());

if (index > 0)
if (index > 0 && index < curve->dataSize())
{
auto p1 = (curve->sample(index - 1));
auto p2 = (curve->sample(index));
double middle_X = (p1.x() + p2.x()) / 2.0;
return (x < middle_X) ? p1 : p2;
}
else if (index >= curve->dataSize())
{
// Target is at or beyond the last point - return the last point
return curve->sample(curve->dataSize() - 1);
}
Comment on lines +295 to +299
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose? also, do not include in the PR changes that are not related

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've included two small fixes as reported in the PR description (Fix tooltip display at end of time series data)

}
return std::nullopt;
}
4 changes: 2 additions & 2 deletions plotjuggler_app/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1881,7 +1881,7 @@ std::tuple<double, double, int> MainWindow::calculateVisibleRangeX()
const double t1 = data.back().x;
min_time = std::min(min_time, t0);
max_time = std::max(max_time, t1);
max_steps = std::max(max_steps, (int)data.size());
max_steps = std::max(max_steps, (int)data.size() - 1);
}
}
});
Expand All @@ -1898,7 +1898,7 @@ std::tuple<double, double, int> MainWindow::calculateVisibleRangeX()
const double t1 = data.back().x;
min_time = std::min(min_time, t0);
max_time = std::max(max_time, t1);
max_steps = std::max(max_steps, (int)data.size());
max_steps = std::max(max_steps, (int)data.size() - 1);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here the other fix (Timeline slider fix)

}
}
}
Expand Down
178 changes: 168 additions & 10 deletions plotjuggler_plugins/DataLoadCSV/dataload_csv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,75 @@ void DataLoadCSV::parseHeader(QFile& file, std::vector<std::string>& column_name
_ui->rawText->setPlainText(preview_lines);
_ui->tableView->resizeColumnsToContents();

// Auto-detect DATE_ONLY and TIME_ONLY column pairs and create combined virtual columns
_combined_columns.clear();

if (lines.size() > 0)
{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually prefer early returns

Suggested change
if (lines.size() > 0)
{
if (lines.empty())
{
return;
}

// Detect column types from the first data row
std::vector<PJ::CSV::ColumnTypeInfo> column_types(column_names.size());
QStringList first_data_line;
SplitLine(lines[0], _delimiter, first_data_line);

for (size_t i = 0; i < column_types.size() && i < first_data_line.size(); i++)
{
if (!first_data_line[i].isEmpty())
{
column_types[i] = PJ::CSV::DetectColumnType(first_data_line[i].toStdString());
}
}

// Find DATE_ONLY and TIME_ONLY column pairs
for (size_t date_idx = 0; date_idx < column_types.size(); date_idx++)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my suggestion is to limit to consecutive columns only, therefore change this to

Suggested change
for (size_t date_idx = 0; date_idx < column_types.size(); date_idx++)
using PJ::CSV::ColumnType;
for (size_t index = 0; (index + 1) < column_types.size(); index++)
{
const auto date_idx = index;
const auto tie_idx = index + 1;
if((column_types[date_idx].type!= ColumnType::DATE_ONLY) ||
(column_types[time_idx].type!= ColumnType::TIME_ONLY))
{
continue; // early return is easier to read
}
// is_adjacent is implicitly true

{
if (column_types[date_idx].type == PJ::CSV::ColumnType::DATE_ONLY)
{
// Look for a matching TIME_ONLY column
for (size_t time_idx = 0; time_idx < column_types.size(); time_idx++)
{
if (column_types[time_idx].type == PJ::CSV::ColumnType::TIME_ONLY)
{
// Prefer adjacent columns
bool is_adjacent = (time_idx == date_idx + 1 || time_idx == date_idx - 1);

if (is_adjacent)
{
// Check if this pair is not already used
bool already_used = false;
for (const auto& existing : _combined_columns)
{
if (existing.date_column_index == date_idx ||
existing.time_column_index == time_idx)
{
already_used = true;
break;
}
}

if (!already_used)
{
// Create combined virtual column
std::string virtual_name = column_names[date_idx] + " + " + column_names[time_idx];

CombinedColumn combined;
combined.date_column_index = date_idx;
combined.time_column_index = time_idx;
combined.virtual_name = virtual_name;
_combined_columns.push_back(combined);

// Add to the UI list widget ONLY (not to column_names, which is used for CSV parsing)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that I want to add a column in the UI. At most a not in a QLabel?

auto qname = QString::fromStdString(virtual_name);
_ui->listWidgetSeries->addItem(qname);

break; // Found a pair for this date column
}
}
}
}
}
}
}

file.close();
}

Expand Down Expand Up @@ -540,6 +609,7 @@ bool DataLoadCSV::readDataFromFile(FileLoadInfo* info, PlotDataMapRef& plot_data
}
else
{
// First check regular columns
for (size_t i = 0; i < column_names.size(); i++)
{
if (column_names[i] == _default_time_axis)
Expand All @@ -548,6 +618,19 @@ bool DataLoadCSV::readDataFromFile(FileLoadInfo* info, PlotDataMapRef& plot_data
break;
}
}

// If not found, check virtual combined columns
if (time_index == TIME_INDEX_NOT_DEFINED)
{
for (size_t i = 0; i < _combined_columns.size(); i++)
{
if (_combined_columns[i].virtual_name == _default_time_axis)
{
time_index = column_names.size() + i;
break;
}
}
}
}
}

Expand Down Expand Up @@ -671,33 +754,78 @@ bool DataLoadCSV::readDataFromFile(FileLoadInfo* info, PlotDataMapRef& plot_data

if (time_index >= 0)
{
t_str = string_items[time_index];
const auto time_trimm = t_str.trimmed();
// Check if this is a combined virtual column
bool is_combined = false;
int date_col_idx = -1;
int time_col_idx = -1;

// Virtual columns have indices >= original column count
if (time_index >= static_cast<int>(column_types.size()))
{
// This is a virtual combined column
int virtual_idx = time_index - column_types.size();
if (virtual_idx < static_cast<int>(_combined_columns.size()))
{
is_combined = true;
date_col_idx = _combined_columns[virtual_idx].date_column_index;
time_col_idx = _combined_columns[virtual_idx].time_column_index;
}
}

bool is_number = false;

if (parse_date_format)
if (is_combined && date_col_idx >= 0 && time_col_idx >= 0)
{
if (auto ts = FormatParseTimestamp(time_trimm, format_string))
// Parse combined date+time columns
const QString& date_str = string_items[date_col_idx];
const QString& time_str = string_items[time_col_idx];

if (auto ts = PJ::CSV::ParseCombinedDateTime(
date_str.trimmed().toStdString(), time_str.trimmed().toStdString(),
column_types[date_col_idx], column_types[time_col_idx]))
{
is_number = true;
timestamp = *ts;
t_str = date_str + " " + time_str; // For error messages
}
}
else
{
// Use the detected column type for the time column
const auto& time_type = column_types[time_index];
if (time_type.type != PJ::CSV::ColumnType::STRING)
// Regular single-column timestamp parsing
t_str = string_items[time_index];
const auto time_trimm = t_str.trimmed();

if (parse_date_format)
{
if (auto ts = PJ::CSV::ParseWithType(time_trimm.toStdString(), time_type))
if (auto ts = FormatParseTimestamp(time_trimm, format_string))
{
is_number = true;
timestamp = *ts;
}
}
else
{
// Use the detected column type for the time column
const auto& time_type = column_types[time_index];
if (time_type.type != PJ::CSV::ColumnType::STRING)
{
if (auto ts = PJ::CSV::ParseWithType(time_trimm.toStdString(), time_type))
{
is_number = true;
timestamp = *ts;
}
}
}
}

time_header_str = header_string_items[time_index];
if (is_combined)
{
time_header_str = QString::fromStdString(_combined_columns[time_index - column_types.size()].virtual_name);
}
else
{
time_header_str = header_string_items[time_index];
}

if (!is_number)
{
Expand Down Expand Up @@ -776,6 +904,22 @@ bool DataLoadCSV::readDataFromFile(FileLoadInfo* info, PlotDataMapRef& plot_data
continue;
}

// Skip Date/Time columns that are part of a combined column
// (they're only used to generate the timestamp, not stored as data)
bool skip_combined_component = false;
for (const auto& combined : _combined_columns)
{
if (i == combined.date_column_index || i == combined.time_column_index)
{
skip_combined_component = true;
break;
}
}
if (skip_combined_component)
{
continue;
}

// Use the detected column type to parse the value
if (col_type.type != PJ::CSV::ColumnType::STRING)
{
Expand Down Expand Up @@ -817,7 +961,21 @@ bool DataLoadCSV::readDataFromFile(FileLoadInfo* info, PlotDataMapRef& plot_data

if (time_index >= 0)
{
_default_time_axis = column_names[time_index];
// Check if this is a combined virtual column
if (time_index >= static_cast<int>(column_names.size()))
{
// Virtual combined column
int virtual_idx = time_index - column_names.size();
if (virtual_idx < static_cast<int>(_combined_columns.size()))
{
_default_time_axis = _combined_columns[virtual_idx].virtual_name;
}
}
else
{
// Regular column
_default_time_axis = column_names[time_index];
}
}
else if (time_index == TIME_INDEX_GENERATED)
{
Expand Down
8 changes: 8 additions & 0 deletions plotjuggler_plugins/DataLoadCSV/dataload_csv.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,12 @@ class DataLoadCSV : public DataLoader
QStandardItemModel* _model;

bool multiple_columns_warning_ = true;

// Structure to track combined date+time virtual columns
struct CombinedColumn {
int date_column_index;
int time_column_index;
std::string virtual_name;
};
std::vector<CombinedColumn> _combined_columns;
};
Loading
Loading