Skip to content

Conversation

@lucabonamini
Copy link

@lucabonamini lucabonamini commented Jan 7, 2026

Summary

Added automatic detection of CSV files with date and time stored in separate columns, with automatic combination into a single timestamp for plotting.

Motivation

Some data logging equipment stores date and time information in separated CSV columns:

Date,Time,Temperature
2026-01-01,10:30:25.000,23.5
2026-01-01,10:30:26.000,23.6

PlotJuggler previously required timestamps in a single column.

Solution

Implemented automatic detection and merging:

  1. New column types: Added DATE_ONLY and TIME_ONLY to ColumnType enum
  2. Detection logic: Automatically identifies date-only columns (e.g., "2026-01-01") and time-only columns (e.g., "10:30:25.000")
  3. Pairing algorithm: Finds adjacent date/time column pairs
  4. Virtual columns: Creates combined "Date + Time" virtual columns in the UI
  5. Timestamp generation: Parses and combines separate date/time values into Unix epoch timestamps with fractional second support

Timeline slider fix

The timeline slider was diving the time range by the number of data points instead of the number of intervals between them, causing navigation steps to display incorrect time values. This fix ensures the slider accurately represents the actual time spacing in the data.


Fix tooltip display at end of time series data

When the cursor is positioned at or beyond the last data point in a time series, the tooltip now correctly shows the final data point value instead of disappearing. This ensures consistent tooltip behavior across the entire data range, including at the boundaries.

@lucabonamini
Copy link
Author

Related to #1220

Copy link
Owner

@facontidavide facontidavide left a comment

Choose a reason for hiding this comment

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

first pass of suggested changes. also please remember to apply formatting with pre-commit before submitting a PR

Comment on lines +295 to +299
else if (index >= curve->dataSize())
{
// Target is at or beyond the last point - return the last point
return curve->sample(curve->dataSize() - 1);
}
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
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)

Comment on lines 407 to 419
for (const char* fmt : date_formats)
{
std::istringstream in{ base_str };
in.imbue(std::locale::classic());
date::year_month_day ymd;
in >> date::parse(fmt, ymd);
if (!in.fail())
{
info.type = ColumnType::DATE_ONLY;
info.format = fmt;
return info;
}
}
Copy link
Owner

Choose a reason for hiding this comment

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

can you please use a lambda or a small helper function to avoid code repetition?

}

// 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

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?

Comment on lines 397 to 398
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;
}

@facontidavide
Copy link
Owner

I wonder if we shouldn't add a radio option for this case instead of relying on magically finding the pair and adding it to the columns.

image

Comment on lines 428 to 431
"%H:%M:%S",
"%H:%M",
"%I:%M:%S %p", // 12-hour with AM/PM
"%I:%M %p"
Copy link
Owner

Choose a reason for hiding this comment

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

I don't think we should accept times that don't have seconds

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
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)

@lucabonamini
Copy link
Author

I wonder if we shouldn't add a radio option for this case instead of relying on magically finding the pair and adding it to the columns.

image

Done. I've replaced the automatic list widget item with a dedicated radio button option. When adjacent date+time column pairs are detected, the radio button becomes enabled and shows which columns will be combined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants