Excel VBA 응용

Plot Digitizer

kwangpal 2023. 12. 2. 19:46
반응형

Plot Digitizer

논문이나 책에서

다른 사람이 만든 그래프를 따라 그리거나

값을 추출하고 싶을 때 사용하는 프로그램을

Plot Digitizer 라고 한다.

 

이를 전문적으로 하는 프로그램이 있긴 하지만

엑셀 VBA를 이용해서

비슷하게 따라할 수 있다.

 

우선 시트에 다음과 같이 입력하고

 

 

검은색 버튼의 이름은 Plot_Digitizer

 

 

우측에는 직선 및 표식이 있는 분산형 차트를 넣고

 

 

차트의 이름은 Chart1 이라고 정했다.

 

 

그리고 E4 와 E7 Cell 에는

[데이터] 탭 - [데이터 도구] 그룹 - [데이터 유효성 검사] 의 [데이터 유효성 검사] 메뉴에서

 

 

[설정] 탭의 [유효성 조건] - [제한 대상] 에서

목록 을 선택하고

원본에 No,Yes 를 입력한 후 확인을 누른다.

 

 

그러면 다음과 같이 해당 Cell에 드롭다운 메뉴가 생긴다.

 

 

드롭다운 메뉴 2개를 각각 No 라고 선택하고

나머지 4개의 빈 칸에 숫자를 입력하자.

 

 

각각은 x축 최소값과 최대값, y축 최소값과 최대값

그리고 x와 y축의 스케일이 Log인지 아닌지 선택하는 것이다.

 

이제 시트 좌측에 직사각형과 자유형: 도형을 하나씩 그리자.

크기, 모양은 신경쓰지 않아도 된다.

 

 

단, 직사각형은 채우기 없음으로 하자.

 

 

검은 버튼에 다음 코드를 연결해준다.

 

Sub Plot_Digitizer_Click()

    Dim Plot_Line As Shape
    Dim i As Integer
    Dim shp As Shape
    Dim X_Min As Double, X_Max As Double, Y_Min As Double, Y_Max As Double
    
    Application.ScreenUpdating = False
    
    For Each shp In ActiveSheet.Shapes
        If Left(shp.Name, 9) = "Rectangle" Then
            shp.Name = "Plot_Range"
        ElseIf Left(shp.Name, 8) = "Freeform" Then
            shp.Name = "Plot_Line"
        End If
    Next shp
    
    [D12] = ""
    [G:I] = ""
    [H2] = "X"
    [I2] = "Y"
    
    X_Min = [E2].Value
    X_Max = [E3].Value
    X_Log = [E4].Value
    Y_Min = [E5].Value
    Y_Max = [E6].Value
    Y_Log = [E7].Value
    
    If (X_Log = "Yes" And X_Min <= 0) Or (Y_Log = "Yes" And Y_Min <= 0) Then
        [D12] = "Log Scale은 0보다 커야함"
        Exit Sub
    End If
    
    For Each shp In ActiveSheet.Shapes
        If shp.Name = "Plot_Range" Then
            shp.Line.Weight = 2.25
            shp.Line.ForeColor.RGB = RGB(192, 0, 0)
            shp.Fill.Visible = msoFalse
            X_0 = shp.Left
            X_width = shp.Width
            X_range = X_Max - X_Min
            
            If X_Log = "No" Then
                X_scale = X_range / X_width
            Else
                X_scale = Log(X_Max / X_Min) / X_width
            End If
            
            Y_height = shp.Height
            Y_range = Y_Max - Y_Min
            Y_0 = shp.Top + Y_height
            
            If Y_Log = "No" Then
                Y_scale = -Y_range / Y_height
            Else
                Y_scale = -Log(Y_Max / Y_Min) / Y_height
            End If
        End If
        
        If shp.Name = "Plot_Line" Then
            shp.Line.Weight = 2.25
            shp.Line.ForeColor.RGB = RGB(0, 176, 80)
            For Each nd In shp.Nodes
                xy = nd.Points
                i = i + 1
                Cells(i + 2, 7) = i
                
                If X_Log = "No" Then
                    Cells(i + 2, 8) = (xy(1, 1) - X_0) * X_scale + X_Min
                Else
                    Cells(i + 2, 8) = X_Min * Exp((xy(1, 1) - X_0) * X_scale)
                End If
                
                If Y_Log = "No" Then
                    Cells(i + 2, 9) = (xy(1, 2) - Y_0) * Y_scale + Y_Min
                Else
                    Cells(i + 2, 9) = Y_Min * Exp((xy(1, 2) - Y_0) * Y_scale)
                End If
            Next nd
        End If
    Next shp
    
    Data_Count = [H2].End(xlDown).Row
    
    ActiveSheet.ChartObjects("Chart1").Activate
    With ActiveChart
        .SetSourceData Source:=Range("H2:I" & Data_Count)
        .ChartType = xlXYScatterLines
        .Axes(xlCategory).MinimumScale = X_Min
        .Axes(xlCategory).MaximumScale = X_Max
        If X_Log = "No" Then
            .Axes(xlCategory).ScaleType = xlLinear
        Else
            .Axes(xlCategory).ScaleType = xlLogarithmic
        End If
        .Axes(xlValue).MinimumScale = Y_Min
        .Axes(xlValue).MaximumScale = Y_Max
        If Y_Log = "No" Then
            .Axes(xlValue).ScaleType = xlLinear
        Else
            .Axes(xlValue).ScaleType = xlLogarithmic
        End If
    End With
    
    [A1].Select
    
    Application.ScreenUpdating = True
    
End Sub

 

이제 검은 버튼을 실행하면

다음 그림처럼 H와 I 컬럼에 데이터가 추출되고

좌측에 그린 그림과 같은 모양의 그래프가 우측에 생긴다.

차트 제목에 "Y" 라고 되어 있는 것은 거슬리니까 지우자.

 

 

이제 다른 그래프를 따라 그리는 작업을 해보자.

 

논문이나 책의 그래프를 하나 붙여넣고

(여기서는 직접 그린 것을 붙여넣었다.)

 

 

그 위에 직사각형을 그래프의 네 가장자리를 채우도록 그리고

(이전에 그렸던 직사각형을 그대로 써도 되고, 지우고 새로 그려도 된다.)

 

 

자유형: 도형도 기존 그래프의 선을 따라 그린다.

 

 

이제 검은 버튼을 눌러보면

다음 그림처럼 결과가 잘 나오는 것을 확인할 수 있다.

 

Log 스케일도 잘 그려준다.

 

 

Log 스케일을 선택했을 때

범위는 0보다 커야함을 유의하자.

 

 

선 굵기, 선 색깔, 직사각형 채우기 없음을

VBA 코드에서 바꾸도록 해두었기 때문에

실제 사용할 때는 선부터 그리고 직사각형을 그린 후

도형을 편집하지 않고

실행버튼을 누르면 더 편리하다.

 

 

코드 설명

    For Each shp In ActiveSheet.Shapes
        If Left(shp.Name, 9) = "Rectangle" Then
            shp.Name = "Plot_Range"
        ElseIf Left(shp.Name, 8) = "Freeform" Then
            shp.Name = "Plot_Line"
        End If
    Next shp

 

위 코드는 시트 내의 직사각형과 자유형: 도형을 찾아서

각각 이름을 Plot_Range와 Plot_Line으로 바꾸는 거다.

 

한글판 Excel에서 직사각형을 그리면

기본적으로 "직사각형 1", "직사각형 2" 처럼

직사각형 뒤에 숫자가 붙는다.

 

그런데 이게 보기에만 "직사각형 1" 이지

실제로는 "Rectangle 1" 이다.

 

자유형 도형도 보기에만 "자유형: 도형 1" 이지

실제로는 "Freeform 1" 이다.

 

그래서 Rectangle 이란 이름이 포함된 도형과

Freeform 이란 이름이 포함된 도형을 찾아서 이름을 바꾼 것이다.

 

실행버튼도 Rectangle 도형이지만

이름을 Plot_Digitizer 라고 바꿔두었기 때문에

아무 영향을 받지 않는다.

 

    If (X_Log = "Yes" And X_Min <= 0) Or (Y_Log = "Yes" And Y_Min <= 0) Then
        [D12] = "Log Scale은 0보다 커야함"
        Exit Sub
    End If

 

위 코드는 X 축이나 Y 축이 Log 스케일인데

축의 최소값을 0 이하로 입력할 경우

축의 최소값이 0보다는 커야한다고

D12 Cell에 출력하는 것이다.

 

10^(-n) 에서

n에 아무리 큰 수를 넣어도 0보다는 크니까

 

        If shp.Name = "Plot_Range" Then
            shp.Line.Weight = 2.25
            shp.Line.ForeColor.RGB = RGB(192, 0, 0)
            shp.Fill.Visible = msoFalse
            X_0 = shp.Left
            X_width = shp.Width
            X_range = X_Max - X_Min
            
            If X_Log = "No" Then
                X_scale = X_range / X_width
            Else
                X_scale = Log(X_Max / X_Min) / X_width
            End If
            
            Y_height = shp.Height
            Y_range = Y_Max - Y_Min
            Y_0 = shp.Top + Y_height
            
            If Y_Log = "No" Then
                Y_scale = -Y_range / Y_height
            Else
                Y_scale = -Log(Y_Max / Y_Min) / Y_height
            End If
        End If

 

위 코드는 직사각형 도형에 대해 계산 작업을 하는 것이다.

선 굵기를 2.25, 선 색깔을 빨간색, 채우기 없음으로 바꾸고

X_0 변수에 Excel Sheet 상에서 직사각형의 왼쪽 point 위치 를 입력하고

X_width 변수에 직사각형의 가로 길이 point를 입력하고

X_range 변수에는 X_Max 에서 X_Min 을 뺀 값을 입력했다.

 

그리고 X축이 Log 스케일인지 여부에 따라

X_scale을 계산하는 코드가 나온다.

 

그후 Y 축에 대해서도 같은 작업을 한다.

Y_height 변수에 직사각형의 높이 point를 입력하고

Y_range 변수에 Y_Max 에서 Y_Min 을 뺀 값을 입력했다.

그리고 Y_0 변수에는 직사각형의 위쪽 point 에서 Y_height 를 뺀 값을 입력했다.

 

Excel에서 point의 기준은 A1 Cell의 왼쪽 위이고, 거기가 (0, 0) 이다.

X축은 오른쪽으로 갈수록 숫자가 커지고

Y축은 아래쪽으로 갈수록 숫자가 커진다.

 

그래서 X_0 변수에는 직사각형의 왼쪽 point 위치를 입력했지만

Y_0 변수에는 직사각형의 위쪽 point 에서 Y_height 를 뺀 값을 입력했다.

우리가 일반적으로 사용하는 Y 축과 방향이 다르기 때문이다.

 

그 다음에는 Y축이 Log 스케일인지 여부에 따라

Y_scale을 계산하는 코드가 나온다.

 

여기서도 X_scale은 계산식 앞에 - 가 붙지 않는데

Y_scale은 계산식 앞에 - 가 각각 붙는다.

앞에서 설명한 Y축의 방향때문이다.

 

        If shp.Name = "Plot_Line" Then
            shp.Line.Weight = 2.25
            shp.Line.ForeColor.RGB = RGB(0, 192, 0)
            For Each nd In shp.Nodes
                xy = nd.Points
                i = i + 1
                Cells(i + 2, 7) = i
                
                If X_Log = "No" Then
                    Cells(i + 2, 8) = (xy(1, 1) - X_0) * X_scale + X_Min
                Else
                    Cells(i + 2, 8) = X_Min * Exp((xy(1, 1) - X_0) * X_scale)
                End If
                
                If Y_Log = "No" Then
                    Cells(i + 2, 9) = (xy(1, 2) - Y_0) * Y_scale + Y_Min
                Else
                    Cells(i + 2, 9) = Y_Min * Exp((xy(1, 2) - Y_0) * Y_scale)
                End If
            Next nd
        End If

 

위 코드는 자유형: 도형에 대해 계산 작업을 하는 것이다.

선 굵기를 2.25, 선 색깔을 녹색으로 바꾸고

 

자유형: 도형의 중간점(도형을 그릴 때 마우스로 클릭한 지점)마다

point 위치를 인식해서 그래프상의 좌표로 변환한다.

 

X, Y 축의 Log 스케일 여부에 따라 계산을 다르게 해준다.

 

    Data_Count = [H2].End(xlDown).Row
    
    ActiveSheet.ChartObjects("Chart1").Activate
    With ActiveChart
        .SetSourceData Source:=Range("H2:I" & Data_Count)
        .ChartType = xlXYScatterLines
        .Axes(xlCategory).MinimumScale = X_Min
        .Axes(xlCategory).MaximumScale = X_Max
        If X_Log = "No" Then
            .Axes(xlCategory).ScaleType = xlLinear
        Else
            .Axes(xlCategory).ScaleType = xlLogarithmic
        End If
        .Axes(xlValue).MinimumScale = Y_Min
        .Axes(xlValue).MaximumScale = Y_Max
        If Y_Log = "No" Then
            .Axes(xlValue).ScaleType = xlLinear
        Else
            .Axes(xlValue).ScaleType = xlLogarithmic
        End If
    End With

 

위 코드는 앞서 추출한 좌표값들로 차트를 그리는 것이다.

Chart1 이라고 이름붙인 차트에 대해

소스 데이터로 H, I 컬럼의 값들을 사용하고

X축과 Y축의 최소값, 최대값을 지정하고

Log 스케일 여부에 따라 축의 스케일을 다르게 지정한다.

 

반응형

'Excel VBA 응용' 카테고리의 다른 글

Palindrome Number  (0) 2023.12.08
Factorial 계산하기  (0) 2023.12.03
TSP (Traveling Salesman Problem)  (0) 2023.11.30
Stop Watch  (0) 2023.11.29
소인수분해  (0) 2023.11.29