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 |