From 617aeb2a0cc8a3618a5e7bc71b27005f46e36d72 Mon Sep 17 00:00:00 2001 From: Hannes Suhr Date: Sat, 7 Mar 2026 00:07:24 +0100 Subject: [PATCH 1/6] Add datetime support design doc Co-Authored-By: Claude Opus 4.6 --- docs/plans/2026-03-07-datetime-design.md | 72 ++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/plans/2026-03-07-datetime-design.md diff --git a/docs/plans/2026-03-07-datetime-design.md b/docs/plans/2026-03-07-datetime-design.md new file mode 100644 index 00000000..83143a54 --- /dev/null +++ b/docs/plans/2026-03-07-datetime-design.md @@ -0,0 +1,72 @@ +# FastPlot Datetime Support — Design + +## Goal + +Add datetime-aware X axis display to FastPlot. Users pass `datenum` values (or MATLAB `datetime` objects, auto-converted) and get human-readable date/time tick labels that adapt to zoom level. + +## Architecture + +### Input + +- `fp.addLine(x, y, 'XType', 'datenum')` — explicit opt-in, X is datenum doubles +- `fp.addLine(x, y)` where `x` is MATLAB `datetime` — auto-detected, converted to `datenum` via `datenum(x)`, `XType` set automatically + +### Internal Pipeline + +**No changes.** `datenum` values are regular doubles. Binary search, MinMax/LTTB downsampling, MEX accelerators, zoom/pan callbacks, pyramid levels — all work unchanged. + +### Storage + +- New field in `Lines` struct: `XType` — `'numeric'` (default) or `'datenum'` +- New property on `FastPlot`: `XType` — set from first line that declares `'datenum'`, enforced consistent across all lines on same axes + +### Tick Formatting + +Custom tick formatter installed on axes when `XType == 'datenum'`. Format auto-selected based on visible X range: + +| Visible range | Format | Example | +|---|---|---| +| > 1 day | `'mmm dd HH:MM'` | `Jan 15 10:00` | +| 1 hour – 1 day | `'HH:MM'` | `10:00` | +| 1 min – 1 hour | `'HH:MM'` | `10:30` | +| < 1 min | `'HH:MM:SS'` | `10:30:15` | + +Tick formatter re-runs on every zoom/pan (inside `onXLimChanged`). + +### Toolbar Display + +- **Crosshair text:** `datestr(xp, 'mmm dd HH:MM:SS')` instead of `sprintf('x=%.4g', xp)` +- **Data cursor label:** `datestr(sx, 'mmm dd HH:MM:SS')` instead of `sprintf('(%.4g, %.4g)', sx, sy)` +- Toolbar checks `fp.XType` (or first `FastPlots{i}.XType`) to decide formatting + +### FastPlotFigure + +Each tile can independently have `XType == 'datenum'` or `'numeric'`. No figure-level setting needed — it inherits from the FastPlot instances. + +## What Changes + +| File | Change | +|---|---| +| `FastPlot.m` | Add `XType` property; detect datetime input in `addLine`; install tick formatter in `render`; update ticks in `onXLimChanged` | +| `FastPlotToolbar.m` | Format crosshair/cursor display based on `XType` | +| `tests/test_toolbar.m` | Add datetime formatting tests | +| `tests/test_datetime.m` | New: datenum axes, tick formatting, datetime auto-conversion | +| `examples/example_toolbar.m` | Add datetime demo section | +| `README.md` | Document datetime usage | + +## What Does NOT Change + +- `binary_search.m` / MEX +- `minmax_downsample.m` / MEX +- `lttb_downsample.m` / MEX +- `compute_violations.m` +- Zoom/pan pipeline (internally) +- `FastPlotFigure.m` +- `FastPlotTheme.m` + +## Compatibility + +- Works in both GNU Octave and MATLAB +- Uses `datenum`/`datestr` (available in both) +- MATLAB `datetime` input auto-converted via `datenum()` +- `isdatetime()` check guarded with `exist('isdatetime')` for Octave compatibility From fb887bfaecf61b3613bc148040635da4c84d6118 Mon Sep 17 00:00:00 2001 From: Hannes Suhr Date: Sat, 7 Mar 2026 00:09:01 +0100 Subject: [PATCH 2/6] Add datetime implementation plan Co-Authored-By: Claude Opus 4.6 --- docs/plans/2026-03-07-datetime.md | 390 ++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 docs/plans/2026-03-07-datetime.md diff --git a/docs/plans/2026-03-07-datetime.md b/docs/plans/2026-03-07-datetime.md new file mode 100644 index 00000000..d3b8b7a2 --- /dev/null +++ b/docs/plans/2026-03-07-datetime.md @@ -0,0 +1,390 @@ +# Datetime X-Axis Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add datetime-aware X axis display so users can pass `datenum` values (or MATLAB `datetime` objects) and get auto-formatted date/time tick labels that adapt to zoom level. + +**Architecture:** Store `XType` (`'numeric'` or `'datenum'`) on each FastPlot instance. Internally everything stays as doubles — no changes to binary_search, downsampling, or MEX. On render and zoom, a tick formatter picks date format based on visible range. Toolbar crosshair/cursor uses `datestr()` for display. MATLAB `datetime` input is auto-converted to `datenum`. + +**Tech Stack:** MATLAB/Octave `datenum`/`datestr`, `XTick`/`XTickLabel` axis properties, `datetick` (reference only — we roll our own for zoom-aware formatting). + +--- + +### Task 1: XType property + datenum detection in addLine + +**Files:** +- Modify: `FastPlot.m` (properties, addLine method) +- Create: `tests/test_datetime.m` + +**Step 1: Write the failing test** + +In `tests/test_datetime.m`: + +```matlab +function test_datetime() +%TEST_DATETIME Tests for datetime X axis support. + + addpath(fullfile(fileparts(mfilename('fullpath')), '..')); + addpath(fullfile(fileparts(mfilename('fullpath')), '..', 'private')); + + close all force; + drawnow; + + % testXTypeDefaultIsNumeric + fp = FastPlot(); + fp.addLine(1:100, rand(1,100)); + assert(strcmp(fp.XType, 'numeric'), 'testXTypeDefault: should be numeric'); + + % testXTypeDatenum + fp = FastPlot(); + x = datenum(2024,1,1) + (0:99)/24; + fp.addLine(x, rand(1,100), 'XType', 'datenum'); + assert(strcmp(fp.XType, 'datenum'), 'testXTypeDatenum: should be datenum'); + + % testDatetimeAutoConvert + % Only run in MATLAB where datetime exists + if exist('datetime', 'class') + fp = FastPlot(); + dt = datetime(2024,1,1) + hours(0:99); + fp.addLine(dt, rand(1,100)); + assert(strcmp(fp.XType, 'datenum'), 'testDatetimeAutoConvert: should be datenum'); + assert(isnumeric(fp.Lines(1).X), 'testDatetimeAutoConvert: X should be numeric'); + end + + fprintf(' All datetime input tests passed.\n'); +end +``` + +**Step 2: Run test to verify it fails** + +Run: `octave --no-gui --eval "addpath('tests'); addpath('private'); test_datetime;"` +Expected: FAIL — `XType` property not defined + +**Step 3: Write minimal implementation** + +In `FastPlot.m`, add `XType` to public properties (after `Theme`): + +```matlab + XType = 'numeric' % 'numeric' or 'datenum' +``` + +In `FastPlot.m`, modify `addLine` method. After the row vector coercion (line 92-93) and before the size validation (line 96), add datetime detection and conversion: + +```matlab + % Detect and convert datetime input + if isobject(x) && exist('isdatetime', 'builtin') && isdatetime(x) + x = datenum(x); + % Auto-set XType + if strcmp(obj.XType, 'numeric') + obj.XType = 'datenum'; + end + end +``` + +In `addLine`, inside the varargin parsing loop (after the `DownsampleMethod` check), add an `XType` check: + +```matlab + elseif strcmpi(key, 'XType') + if strcmp(obj.XType, 'numeric') || strcmp(obj.XType, val) + obj.XType = val; + else + error('FastPlot:mixedXType', ... + 'All lines must use the same XType.'); + end +``` + +**Step 4: Run test to verify it passes** + +Run: `octave --no-gui --eval "addpath('tests'); addpath('private'); test_datetime;"` +Expected: PASS + +**Step 5: Commit** + +```bash +git add FastPlot.m tests/test_datetime.m +git commit -m "Add XType property and datenum/datetime detection in addLine" +``` + +--- + +### Task 2: Tick formatter — auto-format based on zoom level + +**Files:** +- Modify: `FastPlot.m` (render, onXLimChanged, new private method) +- Modify: `tests/test_datetime.m` + +**Step 1: Write the failing test** + +Append to `tests/test_datetime.m` before the final fprintf: + +```matlab + % testTickLabelsAreDateStrings + fp = FastPlot(); + x = datenum(2024,1,1) + (0:99)/24; % ~4 days of hourly data + fp.addLine(x, rand(1,100), 'XType', 'datenum'); + fp.render(); + labels = get(fp.hAxes, 'XTickLabel'); + % Labels should contain ':' (time formatting) not plain numbers + hasTime = false; + for i = 1:numel(labels) + if any(labels{i} == ':') + hasTime = true; + break; + end + end + assert(hasTime, 'testTickLabels: should have time-formatted labels'); + close(fp.hFigure); + + % testTickFormatChangesOnZoom + fp = FastPlot(); + x = datenum(2024,1,1) + (0:9999)/86400; % ~0.1s resolution + fp.addLine(x, rand(1,10000), 'XType', 'datenum'); + fp.render(); + % Zoom to 30 seconds + set(fp.hAxes, 'XLim', [x(1), x(1) + 30/86400]); + drawnow; + labels = get(fp.hAxes, 'XTickLabel'); + % Should show seconds (HH:MM:SS format) + hasSeconds = false; + for i = 1:numel(labels) + if sum(labels{i} == ':') >= 2 + hasSeconds = true; + break; + end + end + assert(hasSeconds, 'testTickFormatZoom: should show seconds when zoomed'); + close(fp.hFigure); +``` + +Update the fprintf count accordingly. + +**Step 2: Run test to verify it fails** + +Run: `octave --no-gui --eval "addpath('tests'); addpath('private'); test_datetime;"` +Expected: FAIL — tick labels are plain numbers + +**Step 3: Write minimal implementation** + +Add a new private method to `FastPlot.m`: + +```matlab + function updateDatetimeTicks(obj) + if ~strcmp(obj.XType, 'datenum'); return; end + xlims = get(obj.hAxes, 'XLim'); + xRange = xlims(2) - xlims(1); % in days + + if xRange > 1 + fmt = 'mmm dd HH:MM'; + elseif xRange > 1/60 % > 1 minute + fmt = 'HH:MM'; + else + fmt = 'HH:MM:SS'; + end + + ticks = get(obj.hAxes, 'XTick'); + labels = cell(size(ticks)); + for i = 1:numel(ticks) + labels{i} = datestr(ticks(i), fmt); + end + set(obj.hAxes, 'XTickLabel', labels); + end +``` + +In `render()`, after `set(obj.hAxes, 'XLim', ...)` (after line 586), add: + +```matlab + obj.updateDatetimeTicks(); +``` + +In `onXLimChanged()`, after `obj.drawnowLimitRate();` (after line 663), add: + +```matlab + obj.updateDatetimeTicks(); +``` + +**Step 4: Run test to verify it passes** + +Run: `octave --no-gui --eval "addpath('tests'); addpath('private'); test_datetime;"` +Expected: PASS + +**Step 5: Commit** + +```bash +git add FastPlot.m tests/test_datetime.m +git commit -m "Add auto-format datetime tick labels based on zoom level" +``` + +--- + +### Task 3: Toolbar datetime display — crosshair and cursor + +**Files:** +- Modify: `FastPlotToolbar.m` (onMouseMove, onMouseClick) +- Modify: `tests/test_datetime.m` + +**Step 1: Write the failing test** + +Append to `tests/test_datetime.m`: + +```matlab + % testToolbarFormatX + % Verify the static helper returns date string for datenum XType + xVal = datenum(2024, 3, 15, 10, 30, 45); + result = FastPlotToolbar.formatX(xVal, 'datenum'); + assert(any(result == ':'), 'testToolbarFormatX: should contain colon'); + resultNum = FastPlotToolbar.formatX(42.5, 'numeric'); + assert(~any(resultNum == ':'), 'testToolbarFormatXNum: should not contain colon'); +``` + +**Step 2: Run test to verify it fails** + +Expected: FAIL — `formatX` not defined + +**Step 3: Write minimal implementation** + +Add a new static method to `FastPlotToolbar.m` (in the `methods (Static)` block, after `makeIcon`): + +```matlab + function str = formatX(xVal, xtype) + %FORMATX Format an X value for display based on XType. + if strcmp(xtype, 'datenum') + str = datestr(xVal, 'mmm dd HH:MM:SS'); + else + str = sprintf('%.4g', xVal); + end + end +``` + +Add a new private helper to determine the active XType: + +```matlab + function xtype = getXType(obj, fp) + if ~isempty(fp) && isprop(fp, 'XType') + xtype = fp.XType; + else + xtype = 'numeric'; + end + end +``` + +In `onMouseMove`, replace the crosshair text line (line 228): + +```matlab + 'String', sprintf('x=%.4g y=%.4g', xp, yp)); +``` + +With: + +```matlab + 'String', sprintf('x=%s y=%.4g', ... + FastPlotToolbar.formatX(xp, obj.getXType([])), yp)); +``` + +But we need the active FastPlot to get XType. Modify `onMouseMove` to also get `fp`: + +Change line 197 from: +```matlab + [~, ax] = obj.getActiveTarget(); +``` +To: +```matlab + [fp, ax] = obj.getActiveTarget(); +``` + +And update both crosshair text lines to use: + +```matlab + 'String', sprintf('x=%s y=%.4g', ... + FastPlotToolbar.formatX(xp, obj.getXType(fp)), yp)); +``` + +In `onMouseClick`, replace the cursor label (line 250): + +```matlab + label = sprintf('(%.4g, %.4g)', sx, sy); +``` + +With: + +```matlab + label = sprintf('(%s, %.4g)', ... + FastPlotToolbar.formatX(sx, obj.getXType(fp)), sy); +``` + +**Step 4: Run test to verify it passes** + +Run: `octave --no-gui --eval "addpath('tests'); addpath('private'); test_datetime;"` +Expected: PASS + +**Step 5: Commit** + +```bash +git add FastPlotToolbar.m tests/test_datetime.m +git commit -m "Add datetime formatting to toolbar crosshair and cursor" +``` + +--- + +### Task 4: Example and README + +**Files:** +- Modify: `examples/example_toolbar.m` (add datetime section) +- Modify: `README.md` (add datetime section) + +**Step 1: Add datetime example** + +Append to `examples/example_toolbar.m`: + +```matlab +%% Datetime X-Axis +x = datenum(2024,1,1) + (0:99999)/86400; % ~1 day at 1-second resolution +y = sin((1:100000) * 2*pi/3600) + 0.2*randn(1,100000); + +fp3 = FastPlot('Theme', 'dark'); +fp3.addLine(x, y, 'DisplayName', 'Sensor', 'XType', 'datenum'); +fp3.render(); +title(fp3.hAxes, 'Datetime Axis — zoom to see format change'); + +tb3 = FastPlotToolbar(fp3); +fprintf('Datetime axis with toolbar ready. Zoom to see tick format adapt.\n'); +``` + +**Step 2: Update README** + +Add a `### Datetime Axes` section after the Toolbar section: + +```markdown +### Datetime Axes + +Pass `datenum` values as X data with `'XType', 'datenum'` to get auto-formatted date/time tick labels: + +\```matlab +x = datenum(2024,1,1) + (0:99999)/86400; % 1-second resolution +y = sin((1:100000) * 2*pi/3600); + +fp = FastPlot(); +fp.addLine(x, y, 'XType', 'datenum'); +fp.render(); +\``` + +Tick labels auto-adapt to zoom level: `Jan 15 10:00` when zoomed out, `10:30:15` when zoomed in. The toolbar crosshair and data cursor also display datetime values. + +In MATLAB, you can also pass `datetime` objects directly — they are auto-converted to `datenum`: + +\```matlab +dt = datetime(2024,1,1) + hours(0:999); +fp.addLine(dt, y); % XType set automatically +\``` +``` + +**Step 3: Run all tests** + +Run: `octave --no-gui --eval "addpath('tests'); addpath('private'); run_all_tests;"` +Expected: 20/20 passed (19 existing + test_datetime) + +**Step 4: Commit** + +```bash +git add examples/example_toolbar.m README.md +git commit -m "Add datetime example and update README" +``` From f40802c6b4bee24c4d0a4854f75b3b22034572b1 Mon Sep 17 00:00:00 2001 From: Hannes Suhr Date: Sat, 7 Mar 2026 00:12:27 +0100 Subject: [PATCH 3/6] Add XType property and datenum/datetime detection in addLine Co-Authored-By: Claude Opus 4.6 --- FastPlot.m | 16 ++++++++++++++++ tests/test_datetime.m | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tests/test_datetime.m diff --git a/FastPlot.m b/FastPlot.m index ac4c4072..a4f4b014 100644 --- a/FastPlot.m +++ b/FastPlot.m @@ -13,6 +13,7 @@ ParentAxes = [] % axes handle, empty = create new LinkGroup = '' % string ID for linked zoom/pan Theme = [] % theme struct (from FastPlotTheme) + XType = 'numeric' % 'numeric' or 'datenum' end properties (SetAccess = private) @@ -92,6 +93,14 @@ function addLine(obj, x, y, varargin) if ~isrow(x); x = x(:)'; end if ~isrow(y); y = y(:)'; end + % Detect and convert datetime input + if isa(x, 'datetime') + x = datenum(x); + if strcmp(obj.XType, 'numeric') + obj.XType = 'datenum'; + end + end + % Validate sizes match if numel(x) ~= numel(y) error('FastPlot:sizeMismatch', ... @@ -119,6 +128,13 @@ function addLine(obj, x, y, varargin) val = varargin{k+1}; if strcmpi(key, 'DownsampleMethod') dsMethod = val; + elseif strcmpi(key, 'XType') + if strcmp(obj.XType, 'numeric') || strcmp(obj.XType, val) + obj.XType = val; + else + error('FastPlot:mixedXType', ... + 'All lines must use the same XType.'); + end else opts.(key) = val; end diff --git a/tests/test_datetime.m b/tests/test_datetime.m new file mode 100644 index 00000000..e25903e8 --- /dev/null +++ b/tests/test_datetime.m @@ -0,0 +1,32 @@ +function test_datetime() +%TEST_DATETIME Tests for datetime X axis support. + + addpath(fullfile(fileparts(mfilename('fullpath')), '..')); + addpath(fullfile(fileparts(mfilename('fullpath')), '..', 'private')); + + close all force; + drawnow; + + % testXTypeDefaultIsNumeric + fp = FastPlot(); + fp.addLine(1:100, rand(1,100)); + assert(strcmp(fp.XType, 'numeric'), 'testXTypeDefault: should be numeric'); + + % testXTypeDatenum + fp = FastPlot(); + x = datenum(2024,1,1) + (0:99)/24; + fp.addLine(x, rand(1,100), 'XType', 'datenum'); + assert(strcmp(fp.XType, 'datenum'), 'testXTypeDatenum: should be datenum'); + + % testDatetimeAutoConvert + % Only run in MATLAB where datetime exists + if exist('datetime', 'class') + fp = FastPlot(); + dt = datetime(2024,1,1) + hours(0:99); + fp.addLine(dt, rand(1,100)); + assert(strcmp(fp.XType, 'datenum'), 'testDatetimeAutoConvert: should be datenum'); + assert(isnumeric(fp.Lines(1).X), 'testDatetimeAutoConvert: X should be numeric'); + end + + fprintf(' All datetime input tests passed.\n'); +end From 0953bd87f7cd495763f46dc7b9d374837280c9a8 Mon Sep 17 00:00:00 2001 From: Hannes Suhr Date: Sat, 7 Mar 2026 00:13:11 +0100 Subject: [PATCH 4/6] Add auto-format datetime tick labels based on zoom level Co-Authored-By: Claude Opus 4.6 --- FastPlot.m | 24 ++++++++++++++++++++++++ tests/test_datetime.m | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/FastPlot.m b/FastPlot.m index a4f4b014..5c430d73 100644 --- a/FastPlot.m +++ b/FastPlot.m @@ -601,6 +601,8 @@ function render(obj) set(obj.hAxes, 'XLimMode', 'manual'); set(obj.hAxes, 'YLimMode', 'manual'); + obj.updateDatetimeTicks(); + obj.CachedXLim = get(obj.hAxes, 'XLim'); % --- Install listeners --- @@ -677,6 +679,7 @@ function onXLimChanged(obj, ~, ~) end obj.drawnowLimitRate(); + obj.updateDatetimeTicks(); end function onResize(obj, ~, ~) @@ -889,6 +892,27 @@ function updateViolations(obj) end end + function updateDatetimeTicks(obj) + if ~strcmp(obj.XType, 'datenum'); return; end + xlims = get(obj.hAxes, 'XLim'); + xRange = xlims(2) - xlims(1); % in days + + if xRange > 1 + fmt = 'mmm dd HH:MM'; + elseif xRange > 1/60 % > 1 minute + fmt = 'HH:MM'; + else + fmt = 'HH:MM:SS'; + end + + ticks = get(obj.hAxes, 'XTick'); + labels = cell(size(ticks)); + for i = 1:numel(ticks) + labels{i} = datestr(ticks(i), fmt); + end + set(obj.hAxes, 'XTickLabel', labels); + end + function drawnowLimitRate(obj) if isempty(obj.HasLimitRate) obj.HasLimitRate = exist('OCTAVE_VERSION', 'builtin') == 0; diff --git a/tests/test_datetime.m b/tests/test_datetime.m index e25903e8..da52bd3c 100644 --- a/tests/test_datetime.m +++ b/tests/test_datetime.m @@ -28,5 +28,42 @@ function test_datetime() assert(isnumeric(fp.Lines(1).X), 'testDatetimeAutoConvert: X should be numeric'); end - fprintf(' All datetime input tests passed.\n'); + % testTickLabelsAreDateStrings + fp = FastPlot(); + x = datenum(2024,1,1) + (0:99)/24; % ~4 days of hourly data + fp.addLine(x, rand(1,100), 'XType', 'datenum'); + fp.render(); + labels = get(fp.hAxes, 'XTickLabel'); + % Labels should contain ':' (time formatting) not plain numbers + hasTime = false; + for i = 1:numel(labels) + if any(labels{i} == ':') + hasTime = true; + break; + end + end + assert(hasTime, 'testTickLabels: should have time-formatted labels'); + close(fp.hFigure); + + % testTickFormatChangesOnZoom + fp = FastPlot(); + x = datenum(2024,1,1) + (0:9999)/86400; % ~0.1s resolution + fp.addLine(x, rand(1,10000), 'XType', 'datenum'); + fp.render(); + % Zoom to 30 seconds + set(fp.hAxes, 'XLim', [x(1), x(1) + 30/86400]); + drawnow; + labels = get(fp.hAxes, 'XTickLabel'); + % Should show seconds (HH:MM:SS format) + hasSeconds = false; + for i = 1:numel(labels) + if sum(labels{i} == ':') >= 2 + hasSeconds = true; + break; + end + end + assert(hasSeconds, 'testTickFormatZoom: should show seconds when zoomed'); + close(fp.hFigure); + + fprintf(' All datetime tests passed.\n'); end From c4f75b69da76738e4a9a7d542bada36bf49998b3 Mon Sep 17 00:00:00 2001 From: Hannes Suhr Date: Sat, 7 Mar 2026 00:13:59 +0100 Subject: [PATCH 5/6] Add datetime formatting to toolbar crosshair and cursor Co-Authored-By: Claude Opus 4.6 --- FastPlotToolbar.m | 25 ++++++++++++++++++++++--- tests/test_datetime.m | 8 ++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/FastPlotToolbar.m b/FastPlotToolbar.m index d697ffd8..e72b7581 100644 --- a/FastPlotToolbar.m +++ b/FastPlotToolbar.m @@ -194,7 +194,7 @@ function onCrosshairOff(obj) function onMouseMove(obj) if ~strcmp(obj.Mode, 'crosshair'); return; end - [~, ax] = obj.getActiveTarget(); + [fp, ax] = obj.getActiveTarget(); if isempty(ax) obj.hideCrosshairLines(); return; @@ -225,7 +225,8 @@ function onMouseMove(obj) set(obj.hCrosshairV, 'Parent', ax, 'XData', [xp xp], 'YData', ylims); set(obj.hCrosshairTxt, 'Parent', ax, ... 'Position', [xlims(2), ylims(2), 0], ... - 'String', sprintf('x=%.4g y=%.4g', xp, yp)); + 'String', sprintf('x=%s y=%.4g', ... + FastPlotToolbar.formatX(xp, obj.getXType(fp)), yp)); end end @@ -247,7 +248,8 @@ function onMouseClick(obj) 'LineStyle', 'none', 'Marker', 'o', 'MarkerSize', 8, ... 'Color', lineColor, 'MarkerFaceColor', lineColor, ... 'HandleVisibility', 'off', 'HitTest', 'off'); - label = sprintf('(%.4g, %.4g)', sx, sy); + label = sprintf('(%s, %.4g)', ... + FastPlotToolbar.formatX(sx, obj.getXType(fp)), sy); obj.hCursorTxt = text(sx, sy, label, 'Parent', ax, ... 'FontSize', 8, 'VerticalAlignment', 'bottom', ... 'HorizontalAlignment', 'left', ... @@ -292,6 +294,14 @@ function cleanupCursor(obj) end methods (Access = private) + function xtype = getXType(~, fp) + if ~isempty(fp) && isprop(fp, 'XType') + xtype = fp.XType; + else + xtype = 'numeric'; + end + end + function onToggleGrid(obj) [~, ax] = obj.getActiveTarget(); if isempty(ax) @@ -399,6 +409,15 @@ function onExportPNG(obj) end methods (Static) + function str = formatX(xVal, xtype) + %FORMATX Format an X value for display based on XType. + if strcmp(xtype, 'datenum') + str = datestr(xVal, 'mmm dd HH:MM:SS'); + else + str = sprintf('%.4g', xVal); + end + end + function icon = makeIcon(name) %MAKEICON Generate a 16x16x3 RGB icon for toolbar buttons. icon = ones(16, 16, 3) * 0.94; % light gray background diff --git a/tests/test_datetime.m b/tests/test_datetime.m index da52bd3c..99305940 100644 --- a/tests/test_datetime.m +++ b/tests/test_datetime.m @@ -65,5 +65,13 @@ function test_datetime() assert(hasSeconds, 'testTickFormatZoom: should show seconds when zoomed'); close(fp.hFigure); + % testToolbarFormatX + % Verify the static helper returns date string for datenum XType + xVal = datenum(2024, 3, 15, 10, 30, 45); + result = FastPlotToolbar.formatX(xVal, 'datenum'); + assert(any(result == ':'), 'testToolbarFormatX: should contain colon'); + resultNum = FastPlotToolbar.formatX(42.5, 'numeric'); + assert(~any(resultNum == ':'), 'testToolbarFormatXNum: should not contain colon'); + fprintf(' All datetime tests passed.\n'); end From afc313848b75ca4aaef970a47fc0361ba14a54e4 Mon Sep 17 00:00:00 2001 From: Hannes Suhr Date: Sat, 7 Mar 2026 00:15:37 +0100 Subject: [PATCH 6/6] Add datetime example and update README Co-Authored-By: Claude Opus 4.6 --- README.md | 22 ++++++++++++++++++++++ examples/example_toolbar.m | 12 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/README.md b/README.md index 88b7f0a9..fe8428c0 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,28 @@ tb.setCrosshair(true); % enable crosshair mode tb.setCursor(true); % enable data cursor mode ``` +### Datetime Axes + +Pass `datenum` values as X data with `'XType', 'datenum'` to get auto-formatted date/time tick labels: + +```matlab +x = datenum(2024,1,1) + (0:99999)/86400; % 1-second resolution +y = sin((1:100000) * 2*pi/3600); + +fp = FastPlot(); +fp.addLine(x, y, 'XType', 'datenum'); +fp.render(); +``` + +Tick labels auto-adapt to zoom level: `Jan 15 10:00` when zoomed out, `10:30:15` when zoomed in. The toolbar crosshair and data cursor also display datetime values. + +In MATLAB, you can also pass `datetime` objects directly — they are auto-converted to `datenum`: + +```matlab +dt = datetime(2024,1,1) + hours(0:999); +fp.addLine(dt, y); % XType set automatically +``` + ## Installation ```bash diff --git a/examples/example_toolbar.m b/examples/example_toolbar.m index 96452913..522297ad 100644 --- a/examples/example_toolbar.m +++ b/examples/example_toolbar.m @@ -36,3 +36,15 @@ tb2 = FastPlotToolbar(fig); fprintf('Dashboard with toolbar ready.\n'); + +%% Datetime X-Axis +x = datenum(2024,1,1) + (0:99999)/86400; % ~1 day at 1-second resolution +y = sin((1:100000) * 2*pi/3600) + 0.2*randn(1,100000); + +fp3 = FastPlot('Theme', 'dark'); +fp3.addLine(x, y, 'DisplayName', 'Sensor', 'XType', 'datenum'); +fp3.render(); +title(fp3.hAxes, 'Datetime Axis — zoom to see format change'); + +tb3 = FastPlotToolbar(fp3); +fprintf('Datetime axis with toolbar ready. Zoom to see tick format adapt.\n');