I have a figure with two or more lines. These lines have additional, important information associated with them, like how many data points were averaged to create the line etc. I would like to access this information in my figure.
I thought a good solution for this would be to if you could hover over a line with your mouse and get that extended information.
However searching for tooltips/overlays/hover-over on figures seemed to not be fruitful.
Example:
figure; hold on;
plot(1:10,rand(10,1))
plot(1:10,rand(10,1))
% additional info
plot_1_info.name = 'Alice';
plot_2_info.name = 'Bob';
plot_1_info.age = 24;
plot_2_info.age = 12;
Any good solutions or better approaches for this?
You can change the data cursor behaviour, this option has good backwards compatability (I've tested the below in R2017b, used similar before in 15b).
See my comments for details:
% Create some data
x = (1:2:20).';
y = rand(10,1);
name = { 'Alice'; 'Alice'; 'Alice'; 'Alice'; 'Bob'; 'Bob'; 'Bob'; 'Chris'; 'Chris'; 'Chris' };
age = [ 24; 24; 24; 24; 12; 12; 12; 17; 17; 17 ];
% Put it in a table, so we have it all together for indexing as plot data
tbl = table( x, y, name, age );
% Create the plot, assign the UserData property to the plot object
f = figure;
plt = plot( x, y );
plt.UserData = tbl;
% Hijack the Data Cursor update callback so we can inject our own info
dcm = datacursormode( f );
set( dcm, 'UpdateFcn', @onDataCursor );
% Function which returns the text to be displayed on the data cursor
function txt = onDataCursor( ~, evt )
% Get containing figure
f = ancestor( evt.Target, 'figure' );
% Get the index within the original data
idx = getfield( getCursorInfo( datacursormode( f ) ), 'DataIndex' );
% The original data is stored in the UserData property
data = evt.Target.UserData;
% Each element of the cell array is a new line on the cursor
txt = { sprintf( 'X: %g', data.x(idx) ), ...
sprintf( 'Y: %g', data.y(idx) ), ...
sprintf( 'Name: %s', data.name{idx} ), ...
sprintf( 'Age: %g', data.age(idx) ) };
end
Output:
Note: I've not handled anywhere the case that there is more than one data cursor tip. You can easily implement a loop over idx
within the callback to handle this, I leave it as an exercise.
This approach is really flexible. For instance if we had 3 lines (one per 'person') then they can each have their own UserData
struct, and we don't need to repeat all the info in table rows.
A = struct( 'X', 1:4, 'Y', rand(1,4), 'Name', 'Alice', 'Age', 24 );
B = struct( 'X', 1:3, 'Y', rand(1,3), 'Name', 'Bob', 'Age', 12 );
C = struct( 'X', 1:3, 'Y', rand(1,3), 'Name', 'Chris', 'Age', 17 );
f = figure; hold on;
plt = plot( A.X, A.Y ); plt.UserData = A;
plt = plot( B.X, B.Y ); plt.UserData = B;
plt = plot( C.X, C.Y ); plt.UserData = C;
% ... Now the struct fields can be accessed from the callback
Using the new data tip customization system introduced in R2019a, we can do the following:
figure(); hP = plot(1:10,rand(10,1),1:10,rand(10,1));
nPts = cellfun(@numel, {hP.XData});
hP(1).DataTipTemplate.DataTipRows(end+1) = dataTipTextRow('Name', repmat("Alice",nPts(1),1) );
hP(1).DataTipTemplate.DataTipRows(end+1) = dataTipTextRow('Age', repmat(12, nPts(1),1) );
hP(2).DataTipTemplate.DataTipRows(end+1) = dataTipTextRow('Name', repmat("Bob", nPts(2),1) );
hP(2).DataTipTemplate.DataTipRows(end+1) = dataTipTextRow('Age', repmat(24, nPts(2),1) );
% (Of course the above can be organized in a nicer way using a function)
Which yields:
Notice that the "hover datatip" has black text, while the "click datatip" has blue text - this is default behavior.