Defining a Y-Based custom datatip with a non-auto format

3 Ansichten (letzte 30 Tage)
I am trying to create a custom datatip, where the value is a 2-input function, and that the format is not 'auto'. The following code shows the main part of the problem:
p = plot((1:10).^2); % Define a line
% X-Based Datatip, with `auto` formatting
p.DataTipTemplate.DataTipRows = [p.DataTipTemplate.DataTipRows; dataTipTextRow("A", @(x) 2*x, 'auto')];
% X-Based Datatip, with floating point formatting
p.DataTipTemplate.DataTipRows = [p.DataTipTemplate.DataTipRows; dataTipTextRow("B", @(x) 2*x, '%.3f')];
% Y-Based Datatip, with `auto` formatting
p.DataTipTemplate.DataTipRows = [p.DataTipTemplate.DataTipRows; dataTipTextRow("C", @(~,y) 2*y, 'auto')];
% Y-Based Datatip, with floating point formatting
p.DataTipTemplate.DataTipRows = [p.DataTipTemplate.DataTipRows; dataTipTextRow("D", @(~,y) 2*y, '%.3f')];
I get an error on the last line:
Error using matlab.graphics.datatip.DataTipTemplate/set.DataTipRows
Not enough input arguments.
I am not sure if I am using this feature incorrectly, or if this is a bug

Akzeptierte Antwort

Milan Bansal
Milan Bansal am 2 Mai 2024
Hi Yoad Pilosof,
I see that you are trying to add a Y-based dataTipTextRow to your plot but are facing an error when using a 2-input lambda function and setting the custom format to display 3 digits after the decimal.
A workaround to resolve this error is to use the sprintf function within the lambda function provided to dataTipTextRow. The sprintf function allows for precise control over the format of the output string. Please change the last line in your code as shown in the code snippet below:
p = plot((1:10).^2); % Define a line
% X-Based Datatip, with `auto` formatting
p.DataTipTemplate.DataTipRows = [p.DataTipTemplate.DataTipRows; dataTipTextRow("A", @(x) 2*x, 'auto')];
% X-Based Datatip, with floating point formatting
p.DataTipTemplate.DataTipRows = [p.DataTipTemplate.DataTipRows; dataTipTextRow("B", @(x) 2*x, '%.3f')];
% Y-Based Datatip, with `auto` formatting
p.DataTipTemplate.DataTipRows = [p.DataTipTemplate.DataTipRows; dataTipTextRow("C", @(~, y) 2*y, 'auto')];
% Y-Based Datatip, with floating point formatting
%% Using sprintf in the lambda function.
p.DataTipTemplate.DataTipRows = [p.DataTipTemplate.DataTipRows; dataTipTextRow("D", @(~,y) sprintf('%.3f', 2*y))];
% Verify the result
datatip(p,8,64);
Please refer to the documentation link to learn more about sprintf function.
Hope this helps!
  1 Kommentar
Thomas Carpenter
Thomas Carpenter am 15 Jul. 2025
Your suggestion of using sprintf doesn't seem to work properly for surface plots. The callback passes in the entire data array and not a scalar value, so the call to sprintf results in a very long string.
Instead it appears you have to wrap the sprintf within arrayfun and return a cell array of values, e.g.:
@(~,y)arrayfun(@(v)sprintf('%0.3f', v),y,'UniformOutput',false);
Hilareously wasteful processing a million values through sprintf to display a single data tip, but there we are.

Melden Sie sich an, um zu kommentieren.

Weitere Antworten (1)

Thomas Carpenter
Thomas Carpenter am 18 Jul. 2025
The accepted answer unfornately doesn't work with image or surface axes, because the call to the data formatting function is given the whole XData and YData arrays which being non-scalar values cause the sprintf method to fail.
Even if you were to wrap the sprintf in an arrayfun and convert all of the values, this is incredibly inefficient for large surfaces - for example if you had a 1000 x 1000 data point surface, it would make a million calls to sprintf every time you update the data tip, which is painfully slow.
With that in mind, some sluething is in order. It seems that the issue stems from a bug in the MATLAB DataTipTextRow internal class which as part of its setting validation calls the data function handle presumably as a dry run to check if it works. However regardless of the expected number of arguments for the handle, it always only passes in a single value. This means your function gets called with missing argments and so raises the exception we are seeing.
Trying to solve this by using varargin doesn't work because the nargin check that is performed returns the wrong number of arguments and leads to a different validation error. Bummer.
The real solution until Mathworks fix the bug in their validation logic is that you must use a function handle that checks nargin when called and doesn't use the missing arguments on the dry run. Of course this means you can't use anonymous functions, but frankly that is a small price to pay for the performance gain of not calling sprintf so many times.
A working example:
% Let's create a nice surface
figure;
x=1:100;
y=1:800;
% With surfaces, we must use the full meshgrid otherwise the behaviour of
% the MATLAB data tip code results in indexing errors when processing the
% custom data tip.
[x,y]=meshgrid(x,y);
% Make the surface
s = surface(x,y,y);
% Need to create and delete a fake data tip to enable setting tip templates
% (another bug in MATLAB).
delete(datatip(s,'Visible','off'));
% Set our formatter
s.DataTipTemplate.DataTipRows(2) = dataTipTextRow('MyYLabel',@myDataTipFcn,'%0.3g')
% Example 2 argument function that checks nargin
function val = myDataTipFcn(x, y)
if nargin < 2
% On dry-run, bug in MATLAB internals means y is missing, so let's just
% use x.
y = x;
end
val = y * 1000;
end
Notice how the data tip function handles the dry run case gracefully. This is what prevents the error when assigning the data tip row to the handle.
If you do want to use anonymous functions, the following is another working example which uses inline functions and virtual workspaces to allow the anonymous function to be called correctly. The helper function makeSafeDataTipFcn can be saved anywhere on the path and can be called as many times as needed for each data tip format.
% Let's create a nice surface
figure;
x=1:100;
y=1:800;
% With surfaces, we must use the full meshgrid otherwise the behaviour of
% the MATLAB data tip code results in indexing errors when processing the
% custom data tip.
[x,y]=meshgrid(x,y);
% Make the surface
s = surface(x,y,y);
% Need to create and delete a fake data tip to enable setting tip templates
% (another bug in MATLAB).
delete(datatip(s,'Visible','off'));
% Set our formatter. We pass the anonymous function into the make safe
% helper to allow it to work with the data tip subsystem. The wrapper is
% only needed for 2 and 3 argument formatters.
s.DataTipTemplate.DataTipRows(2) = dataTipTextRow('MyYLabel',makeSafeDataTipFcn(@(x,y)y.*1000, 2),'%0.3g')
function fcnWrap = makeSafeDataTipFcn(fcn, nArgs)
% Wrap the user supplied function in a virtual workspace function wrapper
% than ensures if positional arguments are missing, it doesn't cause an
% error. This is soley intended to work around a bug in the MATLAB class
% DataTipTextRow which performs a test call of the handle with a single
% argument regardless of how many are needed.
if nArgs == 2
fcnWrap = @dataTipWrapper2;
else
fcnWrap = @dataTipWrapper3;
end
% Wrapper for 2 arguments
function val = dataTipWrapper2(x, y)
if nargin < 2
y = x;
end
val = fcn(x,y);
end
% Wrapper for 3 arguments
function val = dataTipWrapper3(x, y, z)
if nargin < 2
y = x;
end
if nargin < 3
z = y;
end
val = fcn(x,y,z);
end
end

Kategorien

Mehr zu MATLAB finden Sie in Help Center und File Exchange

Tags

Produkte


Version

R2022b

Community Treasure Hunt

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

Start Hunting!

Translated by