Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically adapt number of self defined ticks

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).

enter image description here

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_))
like image 726
embert Avatar asked Oct 30 '14 06:10

embert


1 Answers

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

animation

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

animation2

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.

like image 170
Amro Avatar answered Oct 23 '22 18:10

Amro