MATLAB.Exponenta
–Û·Ë͇ Matlab&Toolboxes

Приложения с GUI и дескрипторная графика

Создание приложений с GUI без среды GUIDE

Приложение для предварительного просмотра большого количества графиков

На практике часто возникает необходимость представления результатов вычислений достаточно большого объема в графическом виде, например до 100 графиков. При этом оказывается удобным вывести все результаты в графическое окно на оси небольшого размера для предварительного просмотра и увеличивать график щелчком мыши по нему для вывода его в отдельном графическом окне.

В этом разделе мы рассмотрим создание приложения, окно которого содержит заданное число пар осей, щелчок мышью по которым приводит к отображению отдельного окна с выбранным графиком (см. рисунок ниже).


Приложение для просмотра графиков

Сначала рассмотрим написание такого приложения в самом простом варианте, имеющем некоторые недостатки. Идея заключается в программировании события ButtonDownFcn осей, размещенных в основном (желтом на рисунке) окне приложения. Событие ButtonDownFcn осей возникает при щелчке мышью по ним. При возникновении этого события мы создадим новое графическое окно (на рисунке оно белого цвета) и скопируем на него выбранные оси, задав их размеры так, чтобы они занимали большую часть нового графического окна.

Приложение manyplots состоит из основной функции manyplots и подфункции обработки события ButtonDownFcn, одной для всех пар осей, которая называется mouse_click (эти функции следует сохранить в одном файле manyplots.m).

В основной функции manyplots создается графическое окно желтого цвета (свойство Color установлено в 'y'). Далее в этом графическом окне во вложенных циклах создаются оси. Для создания осей используется функция subplot (если осей достаточно много, то лучше отказаться от subplot и создавать их самостоятельно, об этом написано в конце этого раздела). Функция subplot возвращает указатели на созданные оси, которые записываются в двумерный массив h. Для простоты в этом примере на каждую пару осей выводится ломаная линия случайного цвета. Координаты вершин ломаной и цвет в формате RGB задаются при помощи функции rand, возвращающей массив случайных чисел от 0 до 1. После завершения циклов свойство ButtonDownFcn получает значение указателя на подфункцию mouse_click. Это делается при помощи функции set, в первом входном аргументе которой задается массив указателей на все оси. Таким образом, щелчок по каждой паре осей обрабатывается одной и той же подфункцией.

В подфункции mouse_click создается графическое окно белого цвета, его положение и размеры задаются в пикселях. Указатель на него сохраняется в переменной hNewF.

Примечание. Размеры и положение графического окна в пределах экрана монитора определяются значением свойства Position графического окна, которое является вектором из четырех элементов

[x y width height]

Здесь:

  • x, y — координаты левого нижнего угла графического окна;
  • width — ширина графического окна;
  • height — высота графического окна.

По умолчанию, координаты левого нижнего угла графического окна, его ширина и высота задаются в пикселях.

Далее, при помощи функции copyobj в это окно копируются оси, по которым был сделан щелчок мышью. Функция copyobj копирует объект вместе со своими потомками, в данном случае оси копируются вместе с линией графика. Интерфейс функции copyobj таков

NewObject = copyobj(Object, Parent)

Смысл входных и выходных аргументов функции copyobj следующий.

  • Object — указатель на копируемый объект. В нашем примере это входной аргумент src функции mouse_click, который содержит указатель на тот объект, событие ButtonDownFcn которого обрабатывается в данный момент времени, т.е. как раз указатель на те оси основного графического окна, по которым был сделан щелчок мышью.
  • Parent — указатель на родительский объект для копируемого. В нашем примере это hNewF — указатель на созданное графическое окно.
  • NewObject — указатель на новый графический объект, созданный после копирования. В нашем примере указатель на скопированные в новое окно оси записывается в переменную hNewA

После копирования осей в новое графическое окно задаются их размеры и положение

Примечание. Размеры и положение осей в пределах графического окна определяются значением свойства Position осей, которое является вектором из четырех элементов

[x y width height]

Здесь:

  • x, y — координаты левого нижнего угла осей;
  • width — ширина осей;
  • height — высота осей.

По умолчанию, координаты левого нижнего угла осей, их ширина и высота задаются в, так называемых, нормализованных единицах измерения. Т.е. считается, что высота и ширина графического окна равны единице, а x, y, width и height задаются в долях единицы.

Функция manyplots с подфункцией mouse_click

function manyplots
% основная функция приложения

% создание основного графического окна желтого цвета без меню
% с заголовком manyplots
figure('Color', 'y',...
    'MenuBar', 'none',...
    'Name', 'manyplots',...
    'NumberTitle', 'off') 
% создание осей в циклах, запись указателей на них в массив h
% и вывод на них графиков
for i = 1 : 4
    for j = 1 : 5
        h(i, j) = subplot(4, 5, 5*(i-1)+j);
        plot(rand(5,1), rand(5,1), 'Color', rand(1,3));
    end
end
% связывание подфункции mouse_click с событием ButtonDownFcn осей основного окна
set(h, 'ButtonDownFcn', @mouse_click)
 
function mouse_click(src, evt)
% подфункция mouse_click обработки события ButtonDownFcn осей основного окна

% создание графического окна белого цвета без меню и заголовка 
% и задание его размеров и положения
hNewF = figure('Color', 'w',...
    'MenuBar', 'none',...
    'Position', [200   200   400   320],...
    'NumberTitle', 'off');
 % копирование выбранных осей в новое графическое окно 
% и сохранение указателя на них в hNewA
hNewA = copyobj(src, hNewF);
% изменение размеров и положения скопированных осей
set(hNewA, 'Position', [0.13 0.11 0.775 0.815])

Созданное приложение manyplots имеет два недостатка.

  1. При щелчке по линиям графиков на осях основного окна ничего не происходит.
  2. При щелчке по скопированным в отдельное окно осям создается новое графическое окно с теми же самыми осями и графиком.

Разберем, почему так происходит.

  1. Щелчок мышью по линии графика не приводит к возникновению события ButtonDownFcn, т.к. оно возникает только при щелчке по свободной части осей. Следовательно, необходимо при создании линии графика связать с ее событием ButtonDownFcn функцию обработки этого события.

  2. При копировании осей в новое графическое окно, значения свойств новых осей совпадают со значениями свойств оригинала. Следовательно, значением свойства ButtonDownFcn новых осей является указатель на подфункцию обработки события mouse_click и при щелчке по новым осям она выполняется, приводя к появлению нового графического окна, содержащего скопированные на него оси. Для избегания этого следует установить скопированным осям значение [] (т.е. пустой массив). Более того, свойству ButtonDownFcn скопированной линии графика следует так же установить в качестве значения пустой массив для того, чтобы при щелчке по скопированной линии не происходило повторное создание графического окна.

Вышеописанные улучшения реализованы в основной функции manyplots2 с подфункцией mouse_click. В функции manyplots2 при создании линий функцией plot сохраняются указатели на них в массиве hL. Далее с событием ButtonDownFcn связывается та же самая функция mouse_click. В функции mouse_click происходит проверка типа объекта, событие ButtonDownFcn которого выполняется в данный момент времени. Если объект не является осями (а значит, линией), то вызывается функция get для получения указателя на его родительские оси. Далее происходит копирование и установка значения [] свойству ButtonDownFcn новых осей и их потомков.

Функция manyplots2 с подфункцией mouse_click

function manyplots2
% основная функция приложения

% создание основного графического окна желтого цвета без меню
% с заголовком manyplots
figure('Color', 'y',...
    'MenuBar', 'none',...
    'Name', 'manyplots',...
    'NumberTitle', 'off')  
% создание осей в циклах, запись указателей на них в массив h и вывод на них графиков, 
% запись указателей на линии в массив hL
for i = 1 : 4
    for j = 1 : 5
        h(i, j) = subplot(4, 5, 5*(i-1)+j);
        hL(i, j) = plot(rand(5,1), rand(5,1), 'Color', rand(1,3));
    end
end
% связывание подфункции mouse_click с событием ButtonDownFcn осей основного окна
set(h, 'ButtonDownFcn', @mouse_click)
% связывание подфункции mouse_click  с событием ButtonDownFcn линий
set(hL, 'ButtonDownFcn', @mouse_click)
 
function mouse_click(src, evt)
% подфункция mouse_click обработки события ButtonDownFcn осей основного окна

% создание графического окна белого цвета без меню и заголовка 
% и задание его размеров и положения
hNewF = figure('Color', 'w',...
    'MenuBar', 'none',...
    'Position', [200   200   400   320],...
    'NumberTitle', 'off');
% получение типа объекта, по которому был сделан щелчок мышью
src_type = get(src, 'Type');
% проверка, является ли объект осями
if ~isequal(src_type, 'axes')
    % объект не является осями (следовательно это линия)
    % записываем в  src  указатель на оси (родительский объект линии)
    src = get(src, 'Parent');
end
% копирование выбранных осей в новое графическое окно 
% и сохранение указателя на них в hNewA
hNewA = copyobj(src, hNewF);
% изменение размеров и положения скопированных осей
% и задание значения [] их свойству ButtonDownFcn
set(hNewA, 'Position', [0.13 0.11 0.775 0.815], ...
    'ButtonDownFcn', [])
% получение указателей на потомков скопированных осей 
% и задание значения [] их свойству ButtonDownFcn
hC = get(hNewA, 'Children');
set(hC, 'ButtonDownFcn', [])

Еще одним недостатком нашего приложения, который проявляется в случае большого числа пар осей в основном окне, является использование функции subplot. Дело в том, что функция subplot работает достаточно медленно. Сравним, например, создание 100 пар осей без разметки при помощи функции subplot и при помощи функции axes (с вычислением положения и размеров каждой пары осей по очевидным формулам).

Следующий код вызывает subplot в цикле и замеряет время.

tic 
figure 
h = zeros(100,1);
for i = 1 : 100
    h(i) = subplot(10,10,i);
end 
set(h, 'XTickLabel', [],...
    'YTickLabel',[]) 
toc

Получается около 2,5 секунд.

Откажемся теперь от subplot и будем создавать оси при помощи функции axes, вычисляя их положение и размеры в двух вложенных циклах.

tic
figure
Delta = 0.005; 
n=10;
Width = (1 - (n+1)*Delta)/n; 
Height = Width; 
for i = 1 : n
    for j = 1 : n
        y = 1 -  (Height+Delta)*i; 
        x = Delta + (Width+Delta)*(j-1); 
        axes('Position', [x, y, Width, Height],... 
            'XTickLabel', [], 'YTickLabel',[],... 
            'Box', 'on'); 
    end
end 
toc

Время работы этого кода меньше 0.1 сек. При увеличении числа осей разница становится еще более существенной.

Таким образом, если предполагается предварительный просмотр достаточно большого количества графиков, то соответствующие оси лучше создавать при помощи axes, а не subplot.

Приложение manyplots2 легко переделать и для предварительного просмотра содержимого графических файлов текущего каталога. Пример такого приложения jpgshow с комментариями приведен ниже.


Приложение jpgshow для предварительного просмотра jpg файлов

Сначала при помощи функции dir определяется содержимое текущего каталога. Функция dir возвращает массив структур files, каждая из которых содержит информацию о файле, либо подкаталоге текущего каталога. При этом, если поле isdir структуры равно 1, то это подкаталог. В цикле перебирается содержимое текущего каталога и обрабатываются только имена файлов. Для получения расширения файла используется функция fileparts. Если расширение совпадает с .jpg (функция strcmpi сравнивает две строки без учета регистра), то имя файла заносятся в массив ячеек jpg_names.

Если в текущем каталоге был найден хоть один файл с расширением jpg, то происходит вычисление, на сколько осей по вертикали и горизонтали следует разбить графическое окно и во вложенных циклах происходит создание текущей пары осей и вывод на них изображения. Считывание содержимого графического файла осуществляется функцией imread, которая возвращает массив с графическими данными.

Далее, для вывода на оси изображения вызывается функция imshow. Она возвращает указатель на изображение, который сохраняется в векторе hIMG. Функция drawnow задействована для вывода считанного изображения на оси перед продолжением работы приложения. Без функции drawnow сначала бы завершилось чтение всех файлов jpg, а только затем произошел бы графический вывод, что может быть плохо в случае достаточно большого числа файлов jpg, или при достаточно большом их размере.

После вывода всех изображений с их событием ButtonDownFcn связывается функция mouse_click. В функции mouse_click создается новое графическое окно, на него копируются оси с изображением (указатель на оси является указателем на предка изображения). Для потомков новых осей свойству ButtonDownFcn устанавливается в качестве значения пустой массив для того, чтобы не происходил повторный вызов функции mouse_click при щелчке по новому изображению.

Функция jpgshow с подфункцией mouse_click

function jpgshow
% получаем список файлов и подкаталогов текущего каталога
files = dir;
% узнаем длину списка
file_num = length(dir);
jpg_num = 0;
% проходим в цикле по содержимому каталога
for k = 1 : file_num
    % определяем: files(k) это файл или каталог 
    if ~files(k).isdir
        % files(k) это файл, получаем его имя и расширение
        [pathstr, name, ext, versn] = fileparts(files(k).name);
        % проверяем, будет ли files(k) файлом jpg
        if strcmpi(ext, '.jpg')
            % files(k) является файлом *.jpg, увеличиваем счетчик на 1
            jpg_num = jpg_num + 1;
            % добавляем имя файла в массив ячеек имен jpg-файлов            
            jpg_names{jpg_num} = [name ext];
        end    
    end
end
% проверяем, есть ли файлы jpg
if jpg_num > 0
    % jpg-файлы есть, вычисляем разбиение графического окна на оси
    n = ceil(sqrt(jpg_num));
    % создаем нулевой массив для хранения указателей на рисунки
    hIMG = zeros(1, jpg_num);
    % создаем графическое окно
    figure('Color', 'y',...
        'MenuBar', 'none',...
        'Name', 'jpgshow',...
        'NumberTitle', 'off') 
    % задаем расстояние между осями (в нормализованных единицах)
    Delta = 0.005; 
    % вычисляем ширину и высоту каждой пары осей
    Width = (1 - (n+1)*Delta)/n; 
    Height = Width; 
    % в циклах создаем оси в графическом окне
    for i = 1 : n
        for j = 1 : n
            % вычисляем координаты левого нижнего угла осей
            y = 1 - (Height+Delta)*i; 
            x = Delta + (Width+Delta)*(j-1); 
            % находим номер текущего рисунка
            im_num = (i-1)*n+j;
            % проверяем, не превысили ли число рисунков
            if im_num <= jpg_num
                % создаем оси
                axes('Position', [x, y, Width, Height]) 
                % читаем файл jpg
                IMG = imread(jpg_names{im_num});
                % выводим рисунок на оси и сохраняем указатель 
                % на него в массиве hIMG
                hIMG(im_num) = imshow(IMG);
                % обновляем оси 
                drawnow
            end
        end 
    end
    % связываем с событием ButtonDownFcn рисунка 
    % функцию mouse_click
    set(hIMG, 'ButtonDownFcn', @mouse_click)
end

function mouse_click(src, evt)
% функция обработки события ButtonDownFcn изображений 

% создаем графическое окно
hNewF = figure('Color', 'w',...
    'MenuBar', 'none',...
    'Position', [200   200   400   320],...
    'NumberTitle', 'off');
% получаем указатель на оси, т.е. на предка изображения
hAxes = get(src, 'Parent');
% копируем оси в новое графическое окно
hNewA = copyobj(hAxes, hNewF);
% задаем размеры и положение новых осей
set(hNewA, 'Position', [0.13 0.11 0.775 0.815])
% получаем указатели на потомков осей
hC = get(hNewA, 'Children');
% задаем их свойству ButtonDownFcn значение пустой массив
set(hC, 'ButtonDownFcn', [])

Рисование кривых мышью

В MATLAB есть функция ginput, которая позволяет задавать точки на осях щелчком мыши и получить их координаты. В справочной системе в разделе Graphics: Creating Specialized Plots: Interactive Plotting приведен пример небольшого приложения, в котором заданные точки соединяются сплайном. В этом приложении предварительно требуется задать все точки и только затем построится сплайн. В этом разделе мы рассмотрим способ рисования кривых без использования функции ginput, при котором задание каждой новой точки сразу же приводит к перестроению проходящей через них кривой (см. рис. 1). Для добавления точек следует делать щелчок с удержанием Ctrl, а щелчок без Ctrl приводит к завершению соединения точек кривой.


Рис. 1. Рисование кривых мышью.

Алгоритм простой — при добавлении первой точки сплайн строить не нужно. Как только появляется вторая точка, следует построить сплайн, а при добавлении третьей, четвертой и т.д. точек нужно перестроить сплайн. Для сохранения данных (координат точек и указателя на линию) заведем структуру данных приложения Line, которую будем получать и сохранять при помощи функции guidata. Для обработки щелчка мыши запрограммируем возникающее при этом событие осей ButtonDownFcn в подфункции BtnDown. В ней проверим, что точка, в которой сделан щелчок, лежит в пределах осей. Координаты точки получим при помощи свойства CurrentPoint осей. Далее определим тип щелчка, для чего задействуем свойство SelectionType графического окна. При обычном щелчке мышью это свойство принимает значение ‘normal’, а при щелчке левой кнопкой с удержанием Ctrl — значение ‘alt’. Если был сделан обычный щелчок, то всем полям структуры Line присвоим пустые массивы, а если был сделан щелчок с удержанием Ctrl, то добавим в этой точке маркер, а координаты точки занесем в Line.X и Line.Y, после чего построим сплайн при помощи функции spline, удалим график предыдущего сплайна и построим график нового.

Иногда возможна ситуация, когда новая точка должна быть добавлена на существующей кривой. При щелчке по кривой событие ButtonDownFcn осей не возникает, поэтому после построения кривой в качестве обработки ее события ButtonDownFcn назначим ту же самую функцию BtnDown.

Ниже приведена функция plotcurve с подфункцией BtnDown.

function plotcurve
figure('Color', 'w') % создаем графическое окно 
axes('XLim', [-1 1], 'YLim', [-1 1], 'Box', 'on', ...
        'ButtonDownFcn', @BtnDown); % создаем оси  
hold on
% заполняем структуру данных приложения
Line.X = []; % абсциссы точек
Line.Y = []; % ординаты точек 
Line.h = []; % указатель на линию
guidata(gcf, Line) % сохраняем структуру данных

function BtnDown(src, eventdata) 
% Подфункция обработки события щелчка по осям

% получаем координаты текущей точки 
C = get(gca,'CurrentPoint');
x = C(1,1);  
y = C(1,2);
% узнаем пределы осей
xlim = get(gca, 'XLim');
ylim = get(gca, 'YLim');
% проверяем, был ли щелчок в пределах осей
inaxes = xlim(1)< x  & xlim(2) > x & ...
    ylim(1)< y  & ylim(2) > y;
if inaxes
    % узнаем тип щелчка мышью
    key = get(gcf, 'SelectionType'); 
    if isequal(key, 'normal')
        % обычный щелчок
        % присваиваем всем полям структуры данных пустые массивы 
        Line = guidata(gcf);
        Line.X = [];  
        Line.Y = []; 
        Line.h = [];
    else
        % добавляем координаты новой точки
        Line = guidata(gcf);
        Line.X = [Line.X x]; 
        Line.Y = [Line.Y y];
        % рисуем маркер
        line(x, y, 'Marker','o', 'MarkerSize', 10,... 
           'MarkerFaceColor', 'c', 'MarkerEdgeColor', 'm');               
        % если точек больше одной, то строим сплайн
        if length(Line.X) > 1
            t = 1 : length(Line.X); % узлы сплайна
            tt = 1: 0.1: t(end);  % промежуточные точки для вычисления в них значения сплайна
            sp = spline(t, [Line.X; Line.Y], tt);
            % если есть линия, то удаляем ее
            if ~isempty(Line.h)
                delete(Line.h)
            end
            % рисуем новую линию
            Line.h = plot(sp(1, :), sp(2, :), 'm');
            set(Line.h, 'LineWidth', 2)
            set(Line.h, 'ButtonDownFcn', @BtnDown)
        end
    end
    % сохраняем структуру данных
    guidata(gcf, Line)      
end

Перемещение объектов на осях мышью

Мы разберем простой способ организации передвижения объектов, нарисованных на осях, при помощи мыши. Ограничимся пока приложением movecrc, в котором на осях есть три кружка, для их перемещения используется мышь при нажатой левой кнопке (см. рис. 1). Далее немного модифицируем приложение, добавив в него возможность копирования кружков движением мыши при нажатой клавише Ctrl и удаление кружка двойным щелчком.


Рис. 1. Приложение movecrc для перемещения кружков мышью.

Приложение movecrc состоит из основной функции с подфункциями (их текст приведен ниже). В основной функции создаются:

  • графическое окно и оси с пределами по x и y от -1 до 1 и с рамкой вокруг;
  • три круглых маркера заданного размера и цвета при помощи функции line (в функции line указаны координаты одной точки, поэтому линии не будет, а будет только маркер), указатели на маркеры записываем в массив hM.

Далее с событием ButtonDownFcn каждого маркера (которое возникает при нажатии кнопки мыши, если курсор находится на маркере) в цикле связываем подфункцию MarkerButtonDownFcn.

Назначение подфункций приложения movecrc следующее.

  • MarkerButtonDownFcn(src, eventdata) — подфункция, вызываемая при нажатии кнопки мыши на маркере. В ней с событием графического окна WindowButtonMotionFcn (которое возникает при движении курсора мыши в пределах графического окна) связывается подфункция MouseMoving. В подфункцию MouseMoving передается дополнительный аргумент — указатель на маркер, который возвращается функцией gcbo (функция gcbo возвращает указатель на объект, событие которого выполняется в данный момент времени). Кроме этого, с событием графического окна WindowButtonUpFcn (которое возникает при отпускании кнопки мыши) связывается подфункция ButtonUp.

  • MouseMoving(src, eventdata, hM) — подфункция, выполняемая при движении курсора мыши. Ее третий аргумент hM содержит указатель на текущий (двигаемый) маркер. В этой подфункции при помощи свойства осей CurrentPoint узнаются текущие координаты курсора мыши, делается проверка на выход за пределы осей и изменяются координаты маркера.

  • ButtonUp(src, eventdata) — подфункция, выполняемая при отпускании кнопки мыши. В этой подфункции свойствам WindowButtonMotionFcn и WindowButtonUpFcn графического окна присваивается пустая строка для разрыва соответствующих событий с прежними функциями MouseMoving и ButtonUp.
function movecrc
% создаем графическое окно 
figure('Color', 'w') 
 % создаем оси
axes('XLim', [-1 1], 'YLim', [-1 1], 'Box', 'on'); 
% Рисуем три маркера (линии) 
hM(1)=line(0.5, 0.5, 'Marker','o', 'MarkerSize', 20,... 
     'MarkerFaceColor', 'r', 'MarkerEdgeColor', 'g'); 
hM(2)=line(-0.5, -0.5, 'Marker','o', 'MarkerSize', 20,... 
    'MarkerFaceColor', 'y', 'MarkerEdgeColor', 'b'); 
hM(3)=line(0, 0, 'Marker','o', 'MarkerSize', 20,... 
    'MarkerFaceColor', 'c', 'MarkerEdgeColor', 'm'); 
% определяем подфункцию обработки события ButtonDownFcn каждой линии 
for k = 1 : length(hM) 
    set(hM(k),'ButtonDownFcn',@MarkerButtonDownFcn) 
end

function MarkerButtonDownFcn(src,eventdata)
% Подфункция обработки события ButtonDownFcn линии 
% Определяем подфункции для событий WindowButtonMotionFcn 
% и WindowButtonUpFcn графического окна 
set(gcf,'WindowButtonMotionFcn',{@MouseMoving,gcbo}) 
set(gcf,'WindowButtonUpFcn',@ButtonUp)

function MouseMoving(src,eventdata,hM) 
% Подфункция для события WindowButtonMotionFcn 
% получаем координаты текущей точки осей
C = get(gca, 'CurrentPoint'); 
x = C(1,1);
y = C(1,2);
% получаем пределы осей
xlim = get(gca, 'XLim');
ylim = get(gca, 'YLim');
% в inaxes 1, если не вышли за оси, иначе - 0
inaxes = xlim(1)< x  & xlim(2) > x & ...
    ylim(1)< y  & ylim(2) > y;
if inaxes
    % если находимся в пределах осей,
    % то изменяем координаты маркера 
    set(hM,'XData', x, 'YData', y) 
end

function ButtonUp(src, eventdata) 
% Подфункция для события WindowButtonUpFcn 
% Когда отпустили кнопку мыши, графическое окно должно перестать 
% реагировать на движение мыши 
set(gcf,'WindowButtonMotionFcn', '') 
set(gcf,'WindowButtonUpFcn', '')

Небольшая модификация подфункции MarkerButtonDownFcn позволяет копировать маркеры (мышью при нажатой клавише Ctrl) и удалять их двойным щелчком. Для того, чтобы узнать тип щелчка используется свойство SelectionType графического окна. Это свойство принимает следующие значения.

  • normal — при обычном щелчке левой кнопкой;
  • extend — при щелчке левой кнопкой с удержанием Shift, или при щелчке одновременно двумя клавишами;
  • alt — при щелчке левой кнопкой с удержанием Ctrl, или при щелчке правой кнопкой
  • open — при двойном щелчке любой кнопкой.

После определения типа щелчка в подфункции MarkerButtonDownFcn происходит его обработка. Если был сделан щелчок с удержанием Ctrl, то маркер копируется при помощи функции copyobj. Ее первым входным аргументом является указатель на копируемый объект, вторым — указатель на объект, которому по иерархии принадлежит копируемый объект (оси), а в выходном аргументе возвращается указатель на новый объект. Далее с событием ButtonDownFcn нового маркера связывается функция MarkerButtonDownFcn. Если был сделан двойной щелчок, то удаляем маркер и завершаем работу подфункции при помощи return. Связывание с событиями WindowButtonMotionFcn и WindowButtonUpFcn соответствующих подфункций остается без изменений.

function MarkerButtonDownFcn(src,eventdata,hM) 
% Подфункция обработки события ButtonDownFcn линии 
% узнаем тип щелчка мышью
key = get(gcf, 'SelectionType');  
if isequal(key, 'alt')
    % если щелчок с Ctrl, то дублируем маркер
    hM = copyobj(hM, gca)
    % назначем подфункцию обработки его события ButtonDownFcn
    set(hM, 'ButtonDownFcn', {@MarkerButtonDownFcn, hM})
end
if isequal(key, 'open')
    % если двойной щелчок, то удаляем маркер
    delete(hM)
    % и завершаем работу подфункции
    return
end
% Определяем подфункции для событий WindowButtonMotionFcn 
% и WindowButtonUpFcn графического окна 
set(gcf,'WindowButtonMotionFcn',{@MouseMoving,hM}) 
set(gcf,'WindowButtonUpFcn',@ButtonUp)

Приложение с панелью переключателей для zoom и pan

В данном разделе демонстрируется создание простого приложения с графическим интерфейсом пользователя panzoom, содержащее оси с графиком и панель переключателей для увеличения мышью масштаба просмотра как отдельно по каждой из осей, так и по обеим осям сразу, а также для прокрутки содержимого осей как по отдельным осям, так и в произвольном направлении. Окно приложения приведено ниже.

Для увеличения масштаба просмотра в MATLAB имеется команда zoom, которая может быть вызвана с дополнительными опциями:

  • zoom on — включает интерактивное увеличение масштаба просмотра при помощи щелчка мыши или выделения увеличиваемой области;
  • zoom off — выключает интерактивное увеличение масштаба просмотра;
  • zoom out — возвращает график к исходному масштабу;
  • zoom reset — запоминает текущее состояние как исходное, так что последующее выполнение zoom out установит именно этот запомненный масштаб;
  • zoom xon — включает интерактивное увеличение масштаба просмотра по оси x;
  • zoom yon — включает интерактивное увеличение масштаба просмотра по оси y;
  • zoom(factor) — увеличение масштаба в factor раз (если factor от 0 до 1, то, соответственно происходит уменьшение масштаба).

Если zoom вызвана без дополнительных опций, то происходит переключение между zoom on и zoom off. По умолчанию, zoom работает для текущего графического окна, однако, если использовать функциональную форму вызова zoom, то появляется возможность задать указатель fig на требуемое графическое окно, например:

zoom(fig, ‘on’) 

Вместо ‘on’ можно указать перечисленные выше опции для командного способа вызова zoom.

Перемещение области просмотра производится при помощи команды pan, которая, так же как и zoom, может быть вызвана с дополнительными опциями.

pan on — включает интерактивное перемещение области просмотра захватом мышью;
pan xon — включает интерактивное перемещение области просмотра только по горизонтали;
pan yon — включает интерактивное перемещение области просмотра только по вертикали;
pan off — выключает интерактивное перемещение области просмотра.

Аналогично zoom команда pan, вызываемая без дополнительных опций, производит переключение между pan on и pan off. Функциональный способ вызова pan позволяет задать указатель на графическое окно, к осям которого должно быть применено интерактивное перемещение области просмотра, например:

pan(fig, ‘on’) 

Одновременно режимы pan и zoom не могут быть включены. Включение одного из них приводит к отмене другого.

В приложении panzoom предусмотрено шесть переключателей для включения pan, pan xon, pan yon, zoom, zoom xon и zoom yon. Переключатели размещены на панели переключателей, обладающей удобным свойством: она сама следит за тем, чтобы был включен только один переключатель, автоматически сбрасывая ранее включенный. При этом, для обработки переключения следует использовать событие панели SelectionChangeFcn (а не событие Callback переключателей) и в соответствующей функции обработки события отслеживать установленный переключатель, например при помощи if и значения Value каждого из них. Есть и второй способ, который состоит в обращении к свойству SelectedObject панели переключателей. Его значение содержит номер включенного переключателя, причем переключатели нумеруются в порядке создания.

Ниже приведен текст функции panzoom вместе с подфункцией btnGroupChange обработки события SelectionChangeFcn панели переключателей. В основной функции panzoom создаются графическое окно и оси, на которые выводится график функции sin1/x. Далее создается панель с указателем hbtngr, содержащая шесть переключателей. Для того, чтобы переключатели принадлежали панели переключателей, следует при их создании функцией uicontrol в качестве первого ее входного аргумента задать указатель на панель переключателей:

uicontrol(hbtngr, 'Style', 'radiobutton', ...

Для задания включенного переключателя используется свойство SelectedObject панели переключателей:

set(hbtngr, 'SelectedObject', rbtnZoom)

соответственно, сразу же включается режим интерактивного увеличения

zoom on

В подфункции btnGroupChange используется оператор if для включения нужного режима в зависимости от установленного переключателя

function panzoom
% создание окна приложения
figure('Resize', 'off', 'Menubar', 'none', ...
    'Name', 'panzoom', 'NumberTitle', 'off')
% создание осей приложения
axes('Position', [0.1 0.1 0.6 0.8])
% вывод графика
x = linspace(0.01,6,100000);
y = sin(1./x);
plot(x,y)
% создание панели переключателей
hbtngr = uibuttongroup('Position', [0.725 0.1 0.25 0.35],...
    'Title', 'Pan&Zoom','FontSize', 10, ...
    'SelectionChangeFcn',  @btnGroupChange);
% создание шести переключателей
uicontrol(hbtngr, 'Style', 'radiobutton', ...
    'Units', 'normalized', 'Position', [0.05 0.8 0.8 0.1],...
    'String', 'pan x and y', 'Tag', 'rbtnPanXY',...
    'FontSize', 10)
uicontrol(hbtngr, 'Style', 'radiobutton', ...
    'Units', 'normalized', 'Position', [0.05 0.7 0.8 0.1],...
    'String', 'pan x only', 'Tag', 'rbtnPanX',...
    'FontSize', 10)
uicontrol(hbtngr, 'Style', 'radiobutton', ...
    'Units', 'normalized', 'Position', [0.05 0.6 0.8 0.1],...
    'String', 'pan y only', 'Tag', 'rbtnPanY',...
    'FontSize', 10)
rbtnZoom =  uicontrol(hbtngr, 'Style', 'radiobutton', ...
    'Units', 'normalized', 'Position', [0.05 0.4 0.8 0.1],...
    'String', 'zoom x and y', 'Tag', 'rbtnZoomXY', ...
    'FontSize', 10);
uicontrol(hbtngr, 'Style', 'radiobutton', ...
    'Units', 'normalized', 'Position', [0.05 0.3 0.8 0.1],...
    'String', 'zoom x only', 'Tag', 'rbtnZoomX',...
    'FontSize', 10)
uicontrol(hbtngr, 'Style', 'radiobutton', ...
    'Units', 'normalized', 'Position', [0.05 0.2 0.8 0.1],...
    'String', 'zoom y only', 'Tag', 'rbtnZoomY',...
    'FontSize', 10)
% включение переключателя 'zoom x and y'
set(hbtngr, 'SelectedObject', rbtnZoom)
% включение режима увеличения
zoom on

function btnGroupChange(src, evt)
% функция обработки события SelectionChangeFcn панели

% получение структуры указателей на объекты приложения
handles = guihandles(src);
% проверка включенного переключателя
if isequal(get(handles.rbtnPanXY, 'Value'), 1)
    pan on
elseif isequal(get(handles.rbtnPanX, 'Value'), 1)
    pan xon
elseif isequal(get(handles.rbtnPanY, 'Value'), 1)
    pan yon
elseif isequal(get(handles.rbtnZoomXY, 'Value'), 1)
    zoom on
elseif isequal(get(handles.rbtnZoomX, 'Value'), 1)
    zoom xon
else 
    zoom yon
end


Поиск по сайту:

Система Orphus

Яндекс.Метрика