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_text
classdef odometer_text < handle
% classdef odometer_text < handle
%
% handle = odometer_text(axes_handle, position, text_cell, value)
%
% Example:
% h = odometer_text(axes_handle, [0.8, 0.1, 0.05, 0.05], ...
%                   {'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', ...
%                    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'}, 1);

% "odometer_text" objects contain three textboxes:
%       1) the current text (displayed)
%       2) one higher (hidden)
%       3) one lower (hidden)

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

properties (Access = private, Dependent = false, Transient = true)
    being_incremented       % boolean
    current_text_handle     % handle to textbox uicontrol
    lower_text_handle       % handle to textbox uicontrol
% 	lower_frame             % Handle to blank textbox used to hide text.
    parent                  % Figure 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
%     substrate_frame         % Handle to black frame underlying the text.
    text_cell               % Cell array of values, in order
	total_rotation_fraction % Used to keep track of when enough partial
                            %  rotations have been accomplished.
    upper_text_handle       % handle to textbox uicontrol
% 	upper_frame             % Handle to blank textbox used to hide text.
    value                   % numerical value of enumerated text
end

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

            case 4
                handle.parent    = varargin{1};
                handle.position  = varargin{2};
                handle.text_cell = varargin{3};
                
                %   Special handling for value.
                if isempty(varargin{4}) || ~isnumeric(varargin{4}) || ...
                   (length(varargin{4}) > 1) || ~iswhole(varargin{4}) 
                    errordlg('Invalid "value" for object.', mfilename);
                    return
                end
                
                % obj.value runs from 1-> length(text_cell)
                value_zero_scale = mod(varargin{4} - 1, length(handle.text_cell));
                handle.value     = value_zero_scale + 1;
                
                text_width       = handle.position(3);
                text_height      = handle.position(4);
                
                %   The displayed digit.
                image_data = retrieve_odometer_digit_image(handle.text_cell{1 + value_zero_scale});
                num_cols = size(image_data, 2);
                x_vector = linspace(handle.position(1), ...
                                    handle.position(1) + text_width, ...
                                    num_cols);
                num_rows = size(image_data, 1);
                y_vector = linspace(handle.position(2) + text_height, ...
                                    handle.position(2), ...
                                    num_rows);
                handle.current_text_handle = image('Parent', handle.parent, ...
                                                   'CData',  image_data, ...
                                                   'XData',  x_vector, ...
                                                   'YData',  y_vector);

                %   The next lower text (hidden beneath "upper_frame")
                next_lower_value_zero_scale = mod(value_zero_scale - 1, length(handle.text_cell));
                image_data = retrieve_odometer_digit_image(handle.text_cell{1 + next_lower_value_zero_scale});
                y_vector = linspace(handle.position(2) + (2*text_height), ...
                                    handle.position(2) + (1*text_height), ...
                                    num_rows);
                handle.upper_text_handle   = image('Parent', handle.parent, ...
                                                   'CData',  image_data, ...
                                                   'XData',  x_vector, ...
                                                   'YData',  y_vector);

                %   The next higher text (hidden beneath "lower_frame")
                next_higher_value_zero_scale = mod(value_zero_scale + 1, length(handle.text_cell));
                y_vector = linspace(handle.position(2), ...
                                    handle.position(2) - text_height, ...
                                    num_rows);
                image_data = retrieve_odometer_digit_image(handle.text_cell{1 + next_higher_value_zero_scale});
                handle.lower_text_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_text" constructor: ', ...
                    num2str(nargin)], mfilename);
                return
        end
           
    end %object constructor
            
    function value = get.value(obj)
        value = obj.value;
    end
        
    function handle = get.current_text_handle(obj)
        handle = obj.current_text_handle;
    end

    function handle = get.upper_text_handle(obj)
        handle = obj.upper_text_handle;
    end
        
    function handle = get.lower_text_handle(obj)
        handle = obj.lower_text_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 a "figure" object.', ...
                mfilename);
            return
        end
    end
        
    function set.text_cell(obj, text_cell)
        if iscell(text_cell)
            obj.text_cell = text_cell;
        else
            errordlg('Input "text_cell" is not a cell array.', 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 text 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_text_handle, 'YData');
        upper_box_y_vector   = get(obj.upper_text_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_text_handle, 'YData');
            set(obj.current_text_handle, 'YData', box_y_vector + vertical_increment);
            box_y_vector = get(obj.upper_text_handle,   'YData');
            set(obj.upper_text_handle, 'YData',   box_y_vector + vertical_increment);
            box_y_vector = get(obj.lower_text_handle, 'YData');
            set(obj.lower_text_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_text_handle,   'YData');
            middle_box_y_vector = get(obj.current_text_handle, 'YData');
            lower_box_y_vector  = get(obj.lower_text_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, length(obj.text_cell));
                set(obj.upper_text_handle, 'Visible', 'off');
                delta_y = 3 * (middle_box_y_vector(1) - lower_box_y_vector(1));
                set(obj.upper_text_handle, 'YData', upper_box_y_vector - delta_y);
                set(obj.upper_text_handle, 'Visible', 'on');
                
                %   Swap the names back.
                temp_handle              = obj.lower_text_handle;
                obj.lower_text_handle   = obj.upper_text_handle;
                obj.upper_text_handle   = obj.current_text_handle;
                obj.current_text_handle = temp_handle;
            else
                %   Then it was a NEGATIVE increment.
                obj.value = mod(obj.value - 1, length(obj.text_cell));
                set(obj.lower_text_handle, 'Visible', 'off');
                delta_y = 3 * (upper_box_y_vector(1) - middle_box_y_vector(1));
                set(obj.lower_text_handle, 'YData', lower_box_y_vector + delta_y);
                set(obj.lower_text_handle, 'Visible', 'on');                
                
                %   Swap the names back.
                temp_handle              = obj.upper_text_handle;
                obj.upper_text_handle   = obj.lower_text_handle;
                obj.lower_text_handle   = obj.current_text_handle;
                obj.current_text_handle = temp_handle;
            end

            %   Reset their values.
            new_value_zero_scale = obj.value - 1;
            next_text_index_zero_scale = mod(new_value_zero_scale + 1, length(obj.text_cell));
            image_data = retrieve_odometer_digit_image(obj.text_cell{1 + next_text_index_zero_scale});
            set(obj.lower_text_handle, 'CData', image_data);
            previous_text_index_zero_scale = mod(new_value_zero_scale - 1, length(obj.text_cell));
            image_data = retrieve_odometer_digit_image(obj.text_cell{1 + previous_text_index_zero_scale});
            set(obj.upper_text_handle, 'CData', image_data);
                        
            obj.being_incremented = false;
            obj.total_rotation_fraction = 0;
        end        
    end
    
    function delete(obj)
        if ishandle(obj.current_text_handle)
            delete(obj.current_text_handle);
        end
        
        if ishandle(obj.lower_text_handle)
            delete(obj.lower_text_handle);
        end
        
        if ishandle(obj.upper_text_handle)
            delete(obj.upper_text_handle);
        end
    end
    
end

end %classdef

Contact us