Adjusting x-axis to reflect psuedo-time using XTickLabel

8 views (last 30 days)
I apologize in advance for the lengthy post.
I currently am writing a function to read in IMU data 'real time' and plot the data. The IMU data is generated at 2460Hz but is stored on internal IMU memory and output at 10Hz. In other words, I read 246 samples of data every 100msec through Matlab.
As soon as I read the data from the IMU, I timestamp it using the 'now' function. I then plot the data with time (in datenum format) on the x-axis and the IMU data on the y-axis. The problem I encountered was that I could not zoom in to the level of detail I wanted (say ~10msec on x-axis). I found this strange and did some googling and noticed that others had similar issues and they suggested using a 'psuedo time' scale.
In order to create this psuedo time, I basically use the sample number instead of a datenum. In other words, the first 246 samples I collect are 'samples' 1-246, the next are 247 - 492 and so on. So now I plot this psuedo-time on the x-axis and IMU data on the y-axis and I get the zoom resolution I want so life is good.
Since I'm reading in data real-time, I want the last minute of data to be on the plot, and have the plot gradually shift right as time progresses (like a scope or any other stripchart recorder would do). I implemented that without issue by changing the xlims.
Now the fun part. I need to 'mask' the x-axis so it appears to be time. I use XTickLabel and I get labels, but the times/values they use aren't what I want them to be. I would expect the date ticks to be -1 minute of the current time at the left and +1 minute at the right but instead it looks like it's roughly +/- 2 seconds. I've made a 'mock up' code of what's happening below by just using rand to 'generate' the IMU data and plot it. Can someone help me figure out why my x-labels aren't working out? Please don't tell me to use datenum as the x-axis as I've tried that and it didn't work (due to resolution).
An aside, but so you understand why I'm doing what I am in the code. I originally was plotting the data as soon as it came in, but Matlab was getting 'behind' such that if I moved the IMU, it wouldn't show up until several seconds later. To alleviate that problem, I still read in the data at 10Hz but only plot it every 500msec. Matlab seems to like this just fine :)
% Initialize
count = 0;
Offset = 0;
BufferData = [];
% Setup Time Constants (found manually in command window)
onehundredmsec = 1.157401129603386e-06;
onesec = 1.157401129603386e-05;
onemin = 6.944444030523300e-04;
% Create figure and axes
handles.fig = figure;
handles.axis = axes('Tag','X_Axes','Position',[0.07,0.1,0.9,0.80],'ylim',[-1,1]);
hold(handles.axis,'on')
for i = 1:120
% Add to our counter for how many msec of data we have
count = count + 1;
% "Read in" 246 samples (0.1 seconds worth of data)
StreamedData = rand(246,1);
% Grab Time of data read in (as close as possible)
% and Interpolate time of each sample
Time(1:246,1) = linspace(now - onehundredmsec, now,246);
% Add to buffer to hold 500msec of data
BufferData = [BufferData; Time, StreamedData];
% Once 500msec of data reached, plot it
if count > 4
% Used for adjusting plot so we can use psuedo-time on axis
Offset = Offset + 1;
%Initialize x, aka psuedo time
x = (Offset-1)*1230+1:1230*Offset;
% Grab first time entry since that's where we start
% Note: This is datenum time aka actual time
CurrentTime = BufferData(1,1);
% Setup Time arrays for the axes
TimeLabels = linspace(CurrentTime-onemin,CurrentTime+onemin,1230);
% Set axis so that it is +/- 1 minute (i.e. data always plots in
% center)
set(handles.axis,'XLim',[-1230*(120-Offset),1230*(120+Offset)])
% Only update axis time labels at the start of the plot and then
% every minute thereafter
if Offset == 1 || rem(Offset, 120) == 0
set(handles.axis,'XTickLabel',datestr(TimeLabels,'HH:MM:SS'))
end %if
plot(handles.axis,x,BufferData(:, 2))
% Reset buffer/counters
BufferData = [];
count = 0;
end %if
% Pause to simulate the 100msec, plus it's needed to update plot 'real
% time'
pause(0.1)
end %for
Thanks, Mike
  4 Comments
dpb
dpb on 14 Nov 2013
Edited: dpb on 14 Nov 2013
Excepting afaik the only way the time formatting routines work is with the Matlab datenum range and afaict you're trying to format an "ordinary" linear variable as if it were -- but it isn't.
You could compute the h,m,s from some arbitrary point and create those labels w/ sprintf() and use those but I don't see any way to make datestr work on anything except a "real" datenum.
ADDENDUM:
You say you want an x-axis limits of [-146370,148830] in "pseudo time". For my elucidation and potential playing around, what would you ideally have the axes tick labels be? And how does that relate to the above value range?
Mike
Mike on 14 Nov 2013
Ideally, I'd like 5 tick marks. From left to right they'd read (in datestr HH:MM:SS):
CurrentTime - 1 min, CurrentTime - 30 sec, Current Time, CurrentTime + 30 sec, CurrentTime + 1 min

Sign in to comment.

Accepted Answer

dpb
dpb on 18 Nov 2013
Edited: dpb on 18 Nov 2013
OK, try this...looks like I think yours would given the aforementioned fact that the actual data range is so compacted into the limits that there's nothing but a blob going to show up...but it does handle the ticks and labels as I think you want...
Note that the full five tick marks/labels show up at the very beginning where they matchup precisely but after that since you're continually munging on the limits the number in range varies as they shift off to the left until refreshed. This may be okay for your purpose.
hax=axes('Position',[0.07,0.1,0.9,0.80],'ylim',[-1,1],'box','on');
set(hax,'xtick',[],'xticklabel',[])
set(gcf,'position',[579 344 560 420]) % get it out of the way...
hold(hax,'on')
% Initialization constants
onehundredmsec = 1/864000;
Fs=2460; % sample rate in case changes
Fs2=Fs/2;
xLen=Fs2-1; x1=0; x2=x1; % data buffer lengths, start
xLLen=Fs2*2*120; % xlim width
xLDel=Fs2; % xlim delta
xl1=-Fs2*120; % axis limit initialization
xl2=xl1+xLLen;
tix=xl1:xLLen/4:xl2; % 5 tick positions based on above
idx=0;
cntr=0;
B=[];
for i = 1:120
idx = idx + 1;
D = rand(246,1);
T = linspace(now - onehundredmsec, now,246)';
B = [B; [T D]];
if idx>4
cntr=cntr+1;
x1=x2+1; x2=x1+xLen; % increment x-axis value ranges (no mult needed)
xl1=xl1+xLDel; xl2=xl1+xLLen; % xlimit ranges
tix=tix+xLDel; % update ticks, too...
xlim(hax,[xl1,xl2])
x=[x1:x2]'; % new x range
plot(x,B(:,2))
if cntr==1 || rem(cntr,120)==0
CTime = B(1,1);
[y,mo,d,h,m,s]=datevec(CTime);
ds=datestr(datenum(y,mo,d,h,m-1,fix(s)+[0:30:120]'),'hh:MM:ss');
set(hax,'xtick',tix,'xticklab',ds)
end
B=[];
idx=0;
end
pause(0.1)
end
Also, note for overhead reduction you really don't need the T vector at all for the plotting, only the current time with the 100 msec subtracted is the only actual value used, at least for the plotting. If you need it for archiving purposes, that's the obvious reason to keep it anyway, of course.
ADDENDUM:
The real overhead in the above is the use of plot() repeatedly--this is creating a new line object every call and all the associated overhead of a full call and the associated handles, etc. I suspect it will eventually fail on memory and/or handles or somesuch.
Once you think the effect is at wanted, then can work on a much more efficient manner to present the actual data. Basically, you'll want to mung on the [x|y]data property directly after the initial data are drawn. There's an article in the 2D graphics section on animation that outlines the general ideas to use. But, before for that see if the above doesn't at least give the initial desired appearance.

More Answers (1)

dpb
dpb on 14 Nov 2013
Edited: dpb on 15 Nov 2013
OK, that can be done...
>> x=[-146370,148830]; % the ranges you mentioned
>> tix=x(1):diff(x)/4:x(2); % compute 5 tick positions based on above
>> dn=now; % present clock time
>> [y,mo,d,h,m,s]=datevec(dn); % get the pieces & make the 30-sec labels
>> ds=datestr(datenum(y,mo,d,h,[m-1:0.5:m+1]',fix(s)),'hh:MM:ss');
>> set(gca,'xtick',tix)
>> set(gca,'xticklab',ds)
ADDENDUM:
I'd recommend the following to build the datestr over the above as it uses the integer seconds field and is thus less susceptible to roundoff in general. Altho there won't be a problem w/ the short and even case above, using floating point deltas in building series of datenums can be troublesome--
ds=datestr(datenum(y,mo,d,h,m-1,fix(s)+[0:30:120]'),'hh:MM:ss');
Illustrates a very neat feature of datenum to propagate lower-level values to the higher accounting for rollover in the various fields properly while doing so as well...
  4 Comments
Mike
Mike on 18 Nov 2013
dpb, while I appreciate your help, I think you are trying to be too 'quick' in solving the problem and therefore not understanding the real problem.
I agree that simplifying code for the purpose of debugging is useful, and this code is simplified compared to the original. I'll take your advice and try to simplify it further.
The reason for the changing xlim is clearly commented in the code and again clarified above in a previous comment. I want the x-axis to reflect +/- 1 minute from the data being plotted. 1230 is equivalent to 500msec hence the multiplier of 120 to get 1 minute.
dpb
dpb on 18 Nov 2013
Edited: dpb on 18 Nov 2013
The point is that the tick mark values must reflect the x-lim values and if you put the code preceding that computes the tick times for them before the limits are set they won't be consistent.
Set a limit, use those limits as the [x1,x2] values as I did before and then the labels will show up over the range of the axes.
I did make an algebraic error, sorry, the delta is constant across...I'll try to find a few minutes more and build the demo code...

Sign in to comment.

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!