Dynamically adapt number of self defined ticks

假如想象 提交于 2019-12-07 05:08:13

问题


Taking an example from SO, I'd like to adapt the axis ticks dependent on the current view. This is the default behavior, unless one sets a self defined number of ticks,

The resulting behavior is demonstrated in the picture below. On the left side the default behavior, on the right side the figure with self defined ticks. When rotating the plot with the self defined Z ticks, their number won't be adapted to the currently available space (see right bottom figure).

Is there a simple, general solution for that without some fancy things like getting current angle by camva()? I'd wish to not scale the data itself, since it's a big dataset, but to use self defined ticks and tick-labels. It needs to work with MATLAB Version: 8.0.0.783 (R2012b).

Code

%# sample graph vertices and edges (similar to your data)
[adj,XYZ] = bucky;
[r, c] = find(adj);
edges = [r c];      %# M-by-2 matrix holding the vertex indices
points = XYZ';      %# 3-by-N matrix of points X/Y/Z coordinates

%# build a list of separated lines
e = edges';
e(end+1,:) = 1;
e = e(:);
p = points(:,e);
p(:,3:3:end) = NaN;

figure
line(p(1,:), p(2,:), p(3,:));
view(3)

% Now the same with self defined ticks
figure
line(p(1,:), p(2,:), p(3,:));
view(3)
z = points(3, :);
fac = 3.14159265359;
tickstep = (max(z)-min(z))/9;
ticklabels_ = min(z):tickstep:max(z);
set(gca, 'ZTick', ticklabels_)
set(gca, 'ZTickLabel', sprintf('%.3f|',ticklabels_))

回答1:


As indicated by @bdecaf in the comments, you could write an event handler for when the z-ticks change, in order to customize the tick labels accordingly. This is using undocumented functionality.

This is convenient because we still let MATLAB automatically decide on the best number of ticks to use depending on the screen space available for the axis, we just customize the format of the labels displayed.

Example:

function customize_ticks_example()
    % data
    [adj,XYZ] = bucky;
    [r,c] = find(adj);
    edges = [r c];
    points = XYZ';
    e = edges';
    e(end+1,:) = 1;
    e = e(:);
    p = points(:,e);
    p(:,3:3:end) = NaN;

    % plot
    hFig = figure;
    line(p(1,:), p(2,:), p(3,:), ...
        'LineWidth',2, 'Marker','.', 'MarkerSize',20);
    ax = handle(gca);
    view(3), grid on, box on
    xlabel x, ylabel y, zlabel z
    rotate3d on

    % listen to changes on ZTick property
    ev = handle.listener(ax, findprop(ax,'ZTick'), ...
        'PropertyPostSet', @onZTickChange);
    setappdata(hFig, 'my_ztick_listener', ev);
end

function onZTickChange(~,e)
    % get the new 'ZTick', and format them as needed
    labels = num2str(e.NewValue(:),'%g $');

    % update the 'ZTickLabel'
    set(double(e.AffectedObject), 'ZTickLabel',labels)
end

Of course the labels don't have to be numerical, you could use any custom labels as long as you have some kind of scale along which you can interpolate values to figure what label to use.


EDIT:

an easier way to set the event listener:

ev = addlistener(gca, 'ZTick', 'PostSet', @onZTickChange);

(in this case, there is no need to store ev inside the GUI with setappdata. This syntax bind the listener to the lifecycle of the GUI object).


EDIT 2:

In response to comments, here is another example:

function example_manual_ticks
    %% some 3d plot
    hFig = figure;
    sphere
    view(3), grid on, box on
    xlabel x, ylabel y, zlabel z

    %% create a hidden copy of the axis
    hax1 = gca;
    hax2 = copyobj(hax1, hFig);
    set(hax2, 'Visible','off', 'Color','none', 'HitTest','off', ...
        'XLimMode','manual', 'YLimMode','manual', 'ZLimMode','manual')
    delete(get(hax2, 'Children'))
    uistack(hax2, 'bottom')

    % sync axes on 3d rotation
    hlink = linkprop([hax1,hax2], {'CameraPosition','CameraUpVector'});
    setappdata(hax1, 'my_axes_linkprop', hlink);
    rotate3d on

    % respnd to changes in ZTick axis property
    ev = addlistener(hax2, 'ZTick', 'PostSet',@(o,e) onZTickChange(o,e,hax1));

    %% animation
    el = 90 .* sin(linspace(0,2*pi,100) + asin(1/3));
    for n=1:numel(el)
        view(-37.5, el(n))
        drawnow
    end
end
function onZTickChange(~,e,ax)
    % determine number of ticks
    num_ticks = numel(e.NewValue);
    new_num_ticks = num_ticks - 1;

    % interpolate new ticks along the axis limits
    limits = get(ax, 'ZLim');
    zticks = linspace(limits(1), limits(2), new_num_ticks);
    zticks_labels = num2str(zticks(:), '%.2f ($)');

    % update ticks
    set(ax, 'ZTick',zticks, 'ZTickLabel',zticks_labels)
    drawnow
end

The idea is to create a hidden copy of the axis, kept in sync with the original one on 3D rotation. This second axis will have automatic tick marks, while we customize the ticks of first axis as we wish (similar to before we register a callback on the hidden axis for when the ticks change).

Again I'm using this hidden axis as a way to let MATLAB deal with the task of determining the best number of ticks given the view (this is a lot easier than messing with camera angles and matrix transformations in order to find out the length of the projected axis in pixels).

In the example above, I'm simply setting the number of ticks one less than the automatic number ({2,4,10} instead of {3,5,11}), but you could use any kind of mapping you want.




回答2:


In 2015b the PostSet property is not supported anymore (https://de.mathworks.com/matlabcentral/answers/231377-problem-with-addlistener-to-xlim).

If the size is changed manually the WindowMouseRelease and SizeChanged properties can be used. I've adapted the code from https://stackoverflow.com/a/26705913/2295776, but the animation would not work in this example anymore:

% Create sphere
figure1 = figure;
axes1 = axes('Parent',figure1);
hold(axes1,'on');
sphere;
view(axes1, 3);

% create a hidden copy of the axis
axesCopy = copyobj(axes1, figure1);

% sync axes on 3d rotation
Link = linkprop([axes1, axesCopy],{'CameraUpVector', 'CameraPosition', 'CameraTarget', 'XLim', 'YLim', 'ZLim'});
setappdata(figure1, 'StoreTheLink', Link);
rotate3d on

set(axes1, 'Visible','off', 'Color','none', ...
        'XLimMode','auto', 'YLimMode','auto', 'ZLimMode','auto')
set(axesCopy, ...
        'XLimMode','manual', 'YLimMode','manual', 'ZLimMode','manual')
delete(get(axesCopy, 'Children'))
grid(axesCopy,'on');
uistack(axesCopy, 'bottom')

% respond to changes in XTick and YTick axis properties
addlistener(figure1,'WindowMouseRelease', @(src, evnt) onTickChange(axes1, axesCopy));
addlistener(figure1,'SizeChanged', @(src, evnt) onTickChange(axes1, axesCopy));
onTickChange(axes1, axesCopy);

%% functions
function onTickChange(axes, axesCopy)
    zticks_orig = zticks(axes);
    ZTickLabels = num2str(zticks_orig(:), '%.2f ($)');

    % update ticks
    set(axesCopy, 'ZTick', zticks(axes), 'ZTickLabel', ZTickLabels);
    drawnow;
end


来源:https://stackoverflow.com/questions/26646826/dynamically-adapt-number-of-self-defined-ticks

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!