티스토리 뷰

matlab

ButtonDownFcn과 SelectionType의 활용

게으른 the lazy 2022. 12. 25. 21:43

 

 

매트랩 그래픽 객체의 ButtonDownFcn과 Figure 객체의 SelectionType을 이용하면 재밌는 걸 할 수 있습니다. 본 글의 목표는 아래의 코드를 구현하는 것입니다.

- Line 또는 FunctionLine을 클릭하여 linestyle을 바꿈

- 좌클릭은 dashed (--)

- 우클릭은 dotted (:)

- 중클릭은 dash-dotted(-.)

- 더블클릭은 solid line(-)

- Figure에 만들어지는 모든 Line과 FunctionLine에 적용 (새로 추가되는 객체에도 적용)

 


 

1. ButtonDownFcn

우선 클릭했을 때 어떤 동작이 일어나도록 만들어야 합니다. 이와 같이 어떤 이벤트에 의해서 실행되는 함수를 일반적으로 '콜백(callback) 함수'라고 부릅니다. 매트랩의 대부분(아마 모든) 그래픽 객체에는 ButtonDownFcn이라는 속성이 있습니다. 이름 그대로 Button(마우스 버튼)이 눌렸을 때(Down) 동작시킬 함수(Fcn)를 지칭합니다. ButtonDownFcn에 간단한 함수를 하나 넣어보죠.

 
1
2
3
4
5
figure,
= linspace(01);
= plot(x, x.^2);
 
h.ButtonDownFcn = @(~, ~) disp('test');
cs

 

 

Line을 클릭할 때마다 test 함수가 실행되는 걸 볼 수 있습니다. (~,~)의 의미는 뒤에서 설명하겠습니다.

 


 

2. SelectionType

Figure 객체에는 SelectionType이라는 속성이 있습니다. Figure 객체가 클릭되는 방식에 따라 값이 달라집니다.

● 좌클릭을 하면 'normal'이 됩니다.

● 우클릭을 하면 'alt'가 됩니다.

● 가운데 클릭을 하면 'extend'가 됩니다.

● 더블클릭을 하면 'open'이 됩니다.

 

Line이 Axes에 속해 있고 Axes는 Figure에 속해 있으므로, Line을 클릭해도 SelectionType이 바뀝니다.

 

1
2
3
4
5
6
7
8
9
figure,
= linspace(01);
= plot(x, x.^2);
 
h.ButtonDownFcn = @(~, ~) test;
 
function test(~, ~)
disp(get(gcf, 'SelectionType'))
end
cs

 

 

더블클릭을 할 때, 첫 클릭 때는 SelectionType이 normal이 되었다가 두 번째 클릭 때 open으로 바뀌는게 보이는군요.

 

이 코드의 (아마도) 유일한 단점이 하나 있습니다. 원래는 마우스 클릭만으로 Line의 Data tip을 찍을 수 있어야 합니다. 하지만 이제 Line의 클릭 이벤트는 다른 함수가 선점하고 있으므로, 그냥 클릭해서는 Data tip을 찍을 수 없습니다. Data tip을 찍으려면 axes toolbar를 이용해야 합니다.

 


 

3. 합치기

이제 필요한 재료는 다 모였습니다. Line이 클릭될 때마다 ButtonDownFcn에 지정한 함수로 들어가서, Figure의 SelectionType에 따라 LineStyle을 바꾸면 됩니다. 그런데 LineStyle을 바꾸려면 Line의 핸들이 있어야 하는데... 어떻게 가져오죠? 사실 이미 가져오고 있습니다. ButtonDownFcn에 전달되는 첫 번째 인자가 콜백함수를 호출한 객체의 핸들입니다.

 

1
2
3
4
5
6
7
8
9
10
figure,
= linspace(01);
= plot(x, x.^2);
 
h.ButtonDownFcn = @test;
 
function test(src, hit)
disp(src)
disp(hit)
end
cs

 

 

ButtonDownFcn에는 인자 2개가 자동으로 전달됩니다. 첫 번째는 이벤트가 발생한 객체입니다. 우리가 발생시킨 이벤트는 "Line이 클릭됨"이므로, 클릭된 Line 객체가 src로 들어옵니다. 두 번째 인자는 Hit라는 객체인데, 중요하지 않으므로 넘어가겠습니다. 사실 저도 뭔지 잘 모릅니다.

 

앞의 테스트 코드에서는 src와 hit을 둘 다 사용하지 않았으므로 함수를 정의할 때 (~, ~)와 같이 썼습니다. 이제 Line의 속성을 바꿔야 하므로 src는 필요합니다. hit은 필요 없습니다. 함수를 (src, ~)로 정의하면 되겠군요. src가 Line 객체이니, src.LineStyle을 바꾸면 되겠습니다. 전체 코드는 아래와 같습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
figure;
= linspace(01);
= plot(x, x.^2);
 
h.ButtonDownFcn = @change_line_style;
 
function change_line_style(src, ~)
switch get(gcf, 'SelectionType')
    case 'normal'
        src.LineStyle = '--';
    case 'alt'
        src.LineStyle = ':';
    case 'extend'
        src.LineStyle = '-.';
    case 'open'
        src.LineStyle = '-';
end
end
cs

 

 


 

4. 확장하기

위와 같은 방식으로는 Line 하나에만 ButtonDownFcn을 적용시킬 수 있습니다. 이왕이면 Figure 내의 모든 Line이 똑같이 동작했으면 좋겠는데 말이죠. 특히 나중에 추가하는 Line도 동작했으면 좋겠습니다. FunctionLine도 되면 금상첨화겠군요.

 

방법이 있습니다. Line과 FunctionLine의 ButtonDownFcn의 기본값을 설정하면 됩니다.

 

1
2
figure;
set(gcf, 'DefaultLineButtonDownFcn', @change_line_style);
cs

 

Default와 Line과 ButtonDownFcn을 공백없이 붙여씁니다. Line의 ButtonDownFcn의 Default 값을 설정하겠다는 뜻입니다. gcf는 이 기본값이 적용되는 범위를 말합니다. figure 후에 set을 했으므로 방금 생성한 Figure에서만 적용됩니다. FunctionLine도 똑같이 동작했으면 좋겠다면 DefaultFunctionLineButtonDownFcn을 적용하면 됩니다. 현재 Figure가 아니라 모든 Figure에 적용시키려면 gcf 대신 groot를 쓰면됩니다.

 

 

전체 코드는 아래와 같습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
= linspace(-55);
figure;
set(gcf, 'DefaultLineButtonDownFcn', @change_line_style);
set(gcf, 'DefaultFunctionLineButtonDownFcn', @change_line_style);
 
hold on,
fplot(@sin, 'r')
fplot(@cos, 'g')
fplot(@tan, 'b')
plot(x, x, 'm')
 
function change_line_style(src, ~)
switch get(gcf, 'SelectionType')
    case 'normal'
        src.LineStyle = '--';
    case 'alt'
        src.LineStyle = ':';
    case 'extend'
        src.LineStyle = '-.';
    case 'open'
        src.LineStyle = '-';
end
end
cs

 

 

이 Figure에는 Line와 FunctionLine의 ButtonDownFcn의 기본값이 설정되어 있습니다. 따라서 나중에 추가되는 Line과 FunctionLine에도 change_line_style이 그대로 ButtonDownFcn으로 적용됩니다.

 

 


 

5. 활용 예시

5.1. 좌클릭=선 0.5 굵게, 우클릭=선 0.5 가늘게, 가운데클릭=0.5(기본값)로

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
figure;
= fplot(@sin, 'r');
h.ButtonDownFcn = @change_line_style;
 
function change_line_style(src, ~)
fprintf('LineWidth: %.1f', src.LineWidth)
switch get(gcf, 'SelectionType')
    case 'normal'
        src.LineWidth = src.LineWidth + 0.5;
    case 'alt'
        if src.LineWidth <= 0.5
            fprintf('(too thin to narrow)\n')
            return
        end
        src.LineWidth = src.LineWidth - 0.5;
    case 'extend'
        src.LineWidth = 0.5;
end
fprintf(' -> %.1f\n', src.LineWidth)
end
cs

 

 

 

 

5.2. 컬러풀 카메라맨

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
figure,
img = imread('cameraman.tif');
img = repmat(img, 113);
[imgR, imgG, imgB] = deal(img);
imgR(:,:,[23]) = imgR(:,:,[23])/1.2;
imgG(:,:,[13]) = imgG(:,:,[13])/1.2;
imgB(:,:,[12]) = imgB(:,:,[12])/1.2;
 
= imshow(img);
h.ButtonDownFcn = @change_color;
h.UserData = 'grayish';
 
function change_color(src, ~)
fprintf('Color: %s', src.UserData)
switch get(gcf, 'SelectionType')
    case 'normal'
        src.CData = evalin("base""imgR");
        src.UserData = 'redish';
    case 'extend'
        src.CData = evalin("base""imgG");
        src.UserData = 'greenish';
    case 'alt'
        src.CData = evalin("base""imgB");
        src.UserData = 'bluish';
    case 'open'
        src.CData = evalin("base""img");
        src.UserData = 'grayish';
end
fprintf(' -> %s\n', src.UserData)
end
cs

 

 

 

5.3. Single Image Viewer

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
figure,
set(gcf, 'DefaultImageButtonDownFcn', @show_single_image)
img = imread('cameraman.tif');
= gobjects(4);
subplot(2,2,1), h(1= imshow(img);
subplot(2,2,2), h(2= imshow(imresize(img, 1/2));
subplot(2,2,3), h(3= imshow(imresize(img, 1/4));
subplot(2,2,4), h(4= imshow(imresize(img, 1/8));
 
function show_single_image(src, ~)
ax = src.Parent;
if strcmpi(ax.Parent.SelectionType, 'open')
    hf = findobj('Name''Single Image Viewer');
    if isempty(hf)
        hf = figure('Name''Single Image Viewer');
        ax = axes('Parent', hf);
    else
        ax = hf.Children;
    end
    imshow(src.CData, 'parent', ax);
    ax.Position = [0 0 1 1];
end
end
cs

 

 


 

지금까지 ButtonDownFcn와 SelectionType을 활용하는 방법에 대해서 알아보았습니다.

 

- 게으른

 

'matlab' 카테고리의 다른 글

table 자료형 활용하기  (0) 2023.02.10
AYE, AYE3d  (0) 2023.01.26
티스토리에 매트랩 코드 쓰기  (0) 2022.12.13
e의 값을 찾아보자.  (0) 2022.11.26
수분부족이라고?  (2) 2022.10.13
댓글