Code covered by the BSD License  

Highlights from
Date Odometer class

image thumbnail

Date Odometer class

by

 

12 Apr 2011 (Updated )

Add an odometer-like object to a plot to show the progression of time.

odometer_digit
classdef odometer_digit < handle
% classdef odometer_digit < handle
%
% handle = odometer_digit(axes_handle, position, value)
%
% Example:
% h = odometer_digit(axes_handle, [0.8, 0.1, 0.03, 0.05], 1)

% Kevin J. Delaney, BMT Scientific Marine Services
% April 12, 2011

properties (Access = private, Dependent = false, Transient = true)
    being_incremented       % boolean
    current_digit_handle    % handle to textbox uicontrol
    lower_digit_handle      % handle to textbox uicontrol
    parent                  % Axes object
    position                % [x, y, width, height] in normalized units
    step_fraction           % Typically 1, but can be a fraction for animation
                            %  Example: setting = 0.25 rolls the odometer
                            %  one quarter of the way
	total_rotation_fraction % Used to keep track of when enough partial
                            %  rotations have been accomplished.
    upper_digit_handle      % handle to textbox uicontrol
    value                   % digit value
end


methods
    %
    %   OBJECT CONSTRUCTOR
    %
    function handle = odometer_digit(varargin)
        % handle = odometer_digit(axes_handle, position, value)
        %
        % Instantiates an 'odometer_digit' object.
        %
        % Inputs:
        %   axes_handle     Parent object
        %   position        [x, y, width, height] in normalized units
        %   value           Initial value
        
        switch nargin

            case 3
                handle.parent   = varargin{1};
                handle.position = varargin{2};
                handle.value    = varargin{3};
                digit_width     = handle.position(3);
                digit_height    = handle.position(4);

                %   The displayed digit.
                image_data = retrieve_odometer_digit_image(handle.value);
                
                num_cols = size(image_data, 2);
                x_vector = linspace(handle.position(1), ...
                                    handle.position(1) + digit_width, ...
                                    num_cols);
                num_rows = size(image_data, 1);
                y_vector = linspace(handle.position(2) + digit_height, ...
                                    handle.position(2), ...
                                    num_rows);
                handle.current_digit_handle = image('Parent', handle.parent, ...
                                                    'CData',  image_data, ...
                                                    'XData',  x_vector, ...
                                                    'YData',  y_vector);

                %   The next lower digit.
                lower_digit = mod(handle.value - 1, 10);
                image_data = retrieve_odometer_digit_image(lower_digit);
                y_vector = linspace(handle.position(2) + (2*digit_height), ...
                                    handle.position(2) + (1*digit_height), ...
                                    num_rows);
                handle.upper_digit_handle   = image('Parent', handle.parent, ...
                                                    'CData',  image_data, ...
                                                    'XData',  x_vector, ...
                                                    'YData',  y_vector);

                %   The next higher digit (hidden beneath "lower_frame")
                higher_digit = mod(handle.value + 1, 10);
                image_data = retrieve_odometer_digit_image(higher_digit);
                y_vector = linspace(handle.position(2), ...
                                    handle.position(2) - digit_height, ...
                                    num_rows);
                handle.lower_digit_handle   = image('Parent', handle.parent, ...
                                                    'CData',  image_data, ...
                                                    'XData',  x_vector, ...
                                                    'YData',  y_vector);
                %   Defaults
                handle.step_fraction = 0.25;
                handle.being_incremented = false;
                handle.total_rotation_fraction = 0;
                
            otherwise
                errordlg(['Unexpected number of inputs in "odometer_digit" constructor: ', ...
                    num2str(nargin)], mfilename);
                return
        end
           
    end %object constructor
    
    function set.value(obj, value)
        if ~isempty(value) && isnumeric(value) && iswhole(value) && isbetween(value, 0, 9)
            obj.value = value;
        else
            errordlg('Input "value" is not valid".', mfilename);
            return
        end
    end
        
    function value = get.value(obj)
        value = obj.value;
    end
        
    function handle = get.current_digit_handle(obj)
        handle = obj.current_digit_handle;
    end

    function handle = get.upper_digit_handle(obj)
        handle = obj.upper_digit_handle;
    end
        
    function handle = get.lower_digit_handle(obj)
        handle = obj.lower_digit_handle;
    end
        
    function fraction = get.step_fraction(obj)
        fraction = obj.step_fraction;
    end
        
    function boolean = get.being_incremented(obj)
        boolean = obj.being_incremented;
    end
        
    function fraction = get.total_rotation_fraction(obj)
        fraction = obj.total_rotation_fraction;
    end
        
    function set.parent(obj, parent_axes)
        if isempty(parent_axes) || ...
           (ishandle(parent_axes) && strcmpi(get(parent_axes, 'Type'), 'axes'))
            obj.parent = parent_axes;
        else
            errordlg('Input "parent_axes" is not an "axes" object.', ...
                mfilename);
            return
        end
    end
        
    function set.position(obj, position_vector)
        if isempty(position_vector) || ...
           (isnumeric(position_vector) && length(position_vector) == 4)
            obj.position = position_vector;
        else
            errordlg('Input "position_vector" is not a [1 X 4] numeric vector.', ...
                mfilename);
            return
        end
    end
        
    function increment(obj, step_fraction)
        
        %   Was this digit already in the middle of being incremented?
        if obj.being_incremented
            %   Then you can't change step sizes in the middle of increment
            step_fraction = obj.step_fraction;
            num_steps = 1;
        else
            %   OK, I'll listen to user input.
            if ~exist('step_fraction', 'var') || isempty(step_fraction) || ~isnumeric(step_fraction)
                %   Default: Animate in four steps.
                step_fraction = 0.25;
                num_steps     = 4;
            else
                if (step_fraction < -1) || (step_fraction > 1)
                    errordlg(['Invalid step size: ', num2str(step_fraction), '.'], ...
                        mfilename);
                    return
                end
                
                %   Make sure it all results in exactly one whole rotation.
                total_num_steps = round(1 / abs(step_fraction));
                step_fraction = sign(step_fraction) / total_num_steps;
                obj.step_fraction = step_fraction;
                
                %   Perform ONE step, then return to calling routine.
                num_steps = 1;
                obj.being_incremented = true;
            end
        end
        
        current_box_y_vector = get(obj.current_digit_handle, 'YData');
        upper_box_y_vector   = get(obj.upper_digit_handle,   'YData');
        vertical_increment = (upper_box_y_vector(1) - current_box_y_vector(1)) * step_fraction;   

        for step_index = 1:num_steps
            %   Rollup the boxes.
            box_y_vector = get(obj.current_digit_handle, 'YData');
            set(obj.current_digit_handle, 'YData', box_y_vector + vertical_increment);
            box_y_vector = get(obj.upper_digit_handle,   'YData');
            set(obj.upper_digit_handle, 'YData',   box_y_vector + vertical_increment);
            box_y_vector = get(obj.lower_digit_handle, 'YData');
            set(obj.lower_digit_handle, 'YData', box_y_vector   + vertical_increment);
            
            %   Remember how far we've rotated.
            obj.total_rotation_fraction = obj.total_rotation_fraction + step_fraction;
        end
        
        %   If the rotation is finished, move the boxes back to start.
        if abs(obj.total_rotation_fraction) >= 1
            upper_box_y_vector  = get(obj.upper_digit_handle,   'YData');
            middle_box_y_vector = get(obj.current_digit_handle, 'YData');
            lower_box_y_vector  = get(obj.lower_digit_handle,   'YData');
            
            %   Make the non-displayed digits invisible before moving them.
            if obj.total_rotation_fraction > 0
                %   Then it was a POSITIVE increment.
                obj.value = mod(obj.value + 1, 10);
                set(obj.upper_digit_handle, 'Visible', 'off');
                delta_y = 3 * (middle_box_y_vector(1) - lower_box_y_vector(1));
                set(obj.upper_digit_handle, 'YData', upper_box_y_vector - delta_y);
                set(obj.upper_digit_handle, 'Visible', 'on');
                
                %   Swap the names back.
                temp_handle              = obj.lower_digit_handle;
                obj.lower_digit_handle   = obj.upper_digit_handle;
                obj.upper_digit_handle   = obj.current_digit_handle;
                obj.current_digit_handle = temp_handle;
            else
                %   Then it was a NEGATIVE increment.
                obj.value = mod(obj.value - 1, 10);
                set(obj.lower_digit_handle, 'Visible', 'off');
                delta_y = 3 * (upper_box_y_vector(1) - middle_box_y_vector(1));
                set(obj.lower_digit_handle, 'YData', lower_box_y_vector + delta_y);
                set(obj.lower_digit_handle, 'Visible', 'on');                
                
                %   Swap the names back.
                temp_handle              = obj.upper_digit_handle;
                obj.upper_digit_handle   = obj.lower_digit_handle;
                obj.lower_digit_handle   = obj.current_digit_handle;
                obj.current_digit_handle = temp_handle;
            end

            %   Reset their values.
            image_data = retrieve_odometer_digit_image(mod(obj.value + 1, 10));
            set(obj.lower_digit_handle, 'CData', image_data);
            image_data = retrieve_odometer_digit_image(mod(obj.value - 1, 10));
            set(obj.upper_digit_handle, 'CData', image_data);
                        
            obj.being_incremented = false;
            obj.total_rotation_fraction = 0;
        end        
    end
    
    function delete(obj)
        if ishandle(obj.current_digit_handle)
            delete(obj.current_digit_handle);
        end
        
        if ishandle(obj.lower_digit_handle)
            delete(obj.lower_digit_handle);
        end
        
        if ishandle(obj.upper_digit_handle)
            delete(obj.upper_digit_handle);
        end
    end
    
end

end %classdef

Contact us