利用VC绘制对数坐标系


利用VC绘制对数坐标系

目标:绘制以任意频率为起点、任意频率为终点的对数坐标,并能对坐标进行任意缩放。

实现方法:

======【Update 2013-12-18】:=========================================================

  • 今天发现以前的方法其实是很笨的方法。其实只要知道任意两个点的X坐标及其对应的频率f, 就可以根据第三点的频率确定第三点的坐标,或者根据第三点的坐标确定该点的频率。个人感觉对数坐标的本质就是,对于频率取对数之后,该坐标映射成为线性坐标,即屏幕坐标x可以看做log(f)的线性函数。

  • 给定三点(x0,f0), (x1,f1),(x2,f2), 其中x是X坐标,f是对应的频率,有对应关系 
  • (x2-x0)/(x1-x0) = (log(f2)-log(f0))/(log(f1)-log(f0)) = log(f2/f0)/log(f1/f0), 
  • 于是,可以求得
  •  x1=log(f1/f0)/log(f2/f0)*(x2-x0)+x0
  • 和 
  • log(f1) = 1/C* log(f2/f0)+log(f0), where C=(x2-x0)/(X1-x0)。 

  • 根据这两个公式,写成两个函数,一个是根据频率计算x坐标,一个根据x坐标计算频率,然后指定坐标轴的位置,就可以很容易绘制出对数坐标系了。
=====================================================================================

以下内容已经过时,仅供参考。

1、定义

标准频率:频率值为10N的相应的频率点,其中N=…-2,-1,0,1,2,3…,单位MHz。如0.01MHZ0.1MHz1MHz10MHz100MHz等都是标准频率。

2、思路

         首先找出标准频率,画出标准频率线,然后根据标准频率画出其他频率线。

3、实现

         1)对数坐标系数学基础

         如图所示的对数坐标系中:

由此,若知道上图中的三个点的坐标,可以求出另一点的坐标。

         2)找出起始|终止标准频率的代码:

double stdStartFreq,stdStopFreq;//  标准频率起始点、终止点

     // 格式化标准起始频率

     for(int i=0;i<7;++i)

     {

         if(StartFreq==pow((double)10,i-2))

         {

              stdStartFreq=StartFreq;

              break;

         }

         else if(StartFreq>pow((double)10,i-2) && StartFreq<pow((double)10,i-1))

         {

              stdStartFreq=pow((double)10,i-1);

              break;

         }

     }

     // 格式化标准终止频率

     for(int i=0;i<7;++i)

     {

         if(StopFreq==pow((double)10,i-2))

         {

              stdStopFreq=StopFreq;

              break;

         }

         else if(StopFreq>pow((double)10,i-2) && StopFreq<pow((double)10,i-1))

         {

              stdStopFreq=pow((double)10,i-2);

              break;

         }

     }

         起始标准频率就是第一个大于起始频率的标准频率;终止标准频率就是最后一个小于终止频率的标准频率。

举例:如果起始频率为1.9MHz,终止频率为201MHz,那么起始标准频率为10MHz,终止标准频率为100MHz;如果起始频率为1.9MHz,终止频率为20.1MHz,那么起始标准频率为10MHz,终止标准频率为10MHz;如果起始频率为1.9MHz,终止频率为2.01MHz,那么起始标准频率为10MHz,终止标准频率为1MHz

3)三种情况

起始频率≤起始标准频率<终止标准频率≤终止频率

  在这种情况下,首先画出起始|终止标准频率线。对于起始频率和起始标准频率之间的部分,由起始标准频率递减向起始频率画出虚线;对于终止标准频率和终止频率之间的部分,由终止标准频率向终止频率递增画出虚线;对于起始标准频率和终止标准频率之间的部分,首先找到二者之间的标准频率,然后根据标准频率(包括起始|终止标准频率)画出其间的虚线。

起始频率<起始标准频率=终止标准频率<终止频率

这种情况是第一种情况的特例。因为起始标准频率=终止标准频率,所以第一种情况里只有两种情况来画虚线。

终止标准频率<起始频率<终止频率<起始标准频率

在这种情况下,先找出终止标准频率,然后依据终止标准频率递增画出起始频率和终止频率之间的虚线。

4、其他问题

1)细化间隔

由于对数坐标相同频率段的间隔不同,如1020MHz的间隔与8090MHz的间隔不同,前者大于后者;另外,考虑到坐标系放大时,如200MHz300MHz频率段,整个坐标系中无虚线,只有横线,很难对测试曲线中某个点的频率进行粗略判断。因此,对于比较大的间隔,应该进行细化。

方法就是利用相邻两虚线的间隔所占坐标横轴的比例进行判断,若大于某个数值则有必要进行细化。由于间隔有大有小,考虑到细化的间隔不能影响整体对数坐标的效果,因此细化的竖线用灰色虚线表示,且将间隔分成不同等级,不同等级画不同条灰色虚线。

2)坐标标注

         坐标标注同样利用相邻两虚线的间隔所占坐标横轴的比例来进行判断,大于某个设定值后才进行标注,否则不进行标注。

 【源代码如下:】

// 绘制对数坐标
void CEMCView::DrawLogCoords(CDC* pDC, double StartFreq, double StopFreq)
...

目标:绘制以任意频率为起点、任意频率为终点的对数坐标,并能对坐标进行任意缩放。

 

附录:

1 读者来信问道:

您好,我是在校的学生,看了您关于“利用VC绘制对数坐标系”http://blog.csdn.net/bonny95/archive/2008/02/21/2111368.aspx 的博文,于是自己写了一个在不同坐标系(直角坐标系和对数坐标系)上描点并选择一些点进行线性拟合的C++程序,结果发现我的点不能很好的在对数坐标坐标上显示出来。
因此我想问您关于实际点到对数坐标系上的页面坐标点转换的过程,您的想法是什么样的呢?
比如点(1998,200)落在1000-10000的标准频率段内,并且是在1000-2000之间,这其中的如何正确地把该点在对数坐标上绘制出来呢。如果您有时间能给我这个解答的话不胜感激。


【答】首先我觉得有必要明确一下直角坐标系和对数坐标系的区别:直角坐标系的X轴、Y轴全部为线性坐标轴,也即1、2、3、……10、……100,1和2,2和3,99和100之间的距离是相等的;而对数坐标系(确切而言是半对数坐标系)的X轴(横坐标)是对数坐标,是非线性的,而Y轴是线性变化的,X轴上1、10、100之间的距离是相等的,而1和2,2和3,99和100之间的距离是不等的。

对于对数坐标系的横坐标,两个点之间的相对位置与两个点代表的频率值的对数呈正比,即:如果屏幕上某个像素点X1在屏幕上的坐标(计算机屏幕上的坐标系为线性坐标系)为(x1,y),X2的坐标为(x2,y),X3的坐标为(x3,y),X1、X2、X3分别为对数坐标系横轴上的三点,对应的频率值分别为f1, f2, f3,则有:
     屏幕上的像素点位置比例  (x2-x1)/(x2-x3) = [lg(f2)-lg(f1)]/[lg(f2)-lg(f3)]  对数坐标系的频率值取对数之后的比例
根据上述公式,设对数坐标横轴上频率1000对应的屏幕坐标为x1, 1998对应的屏幕坐标为x2, 10000对应的屏幕坐标为x3,带入上述公式就可以了。当然,对数坐标的原点对应的屏幕像素值你要自己设定。
因此,其实把测量仪器测得的直角坐标系的曲线(或像素点)转换成计算机屏幕上显示的对数坐标系的曲线(或像素点)的关键在于计算机屏幕直角坐标系和对数坐标系的转换:屏幕上对数坐标系在屏幕上显示的位置要你自己来设定(比如原点为(200,600),横轴(200~800,600),纵轴(200, 600~150)),你设定了这个坐标系的位置之后,就要进行对数坐标(横轴为频率值;也可以理解为频率值的对数,这样频率值的对数也是线性的)和直角坐标系(横轴为屏幕像素点的位置)的转换。
另外,对于纵轴,尽管都是线性坐标,但也要进行一些等比例变换,不然绘制出来的也有可能不对。
祝你好运!
PS: 你可以参见http://p.blog.csdn.net/images/p_blog_csdn_net/bonny95/工程信息提示栏.JPG,这是我自己绘制的对数坐标系。

关于属性页和属性单的应用总结

                                                         关于属性页和属性单的应用总结

1、属性页(CPropertyPage)中获取
 (1) 程序主框架指针:CMainFrame* pMainFrame=(CMainFrame*)AfxGetMainWnd();
 (2) 程序子框架指针:CChildFrame* pframe=(CChildFrame*)((CMainFrame*)AfxGetMainWnd())->GetActiveFrame();
 (3) 程序活动文档指针:CEMCDoc* m_pDoc=(CEMCDoc*)pframe->GetActiveDocument(); //获取活动的文档
 (4) 属性单(CPropertySheet)指针:CMyPropertySheet* pSheet=(CMyPropertySheet*)this->GetParent(); // 获取CMyPropertySheet指针
 (5) 程序活动视图指针:CEMCView* pView=(CEMCView*)pframe->GetActiveView(); // 获取活动视图

2、在属性单(CPropertySheet)单击OK按钮后,程序框架将逐个遍历加载的CPropertyPage(从GetPage(0)~GetPage(N-1)),查询是否
有CPropertyPage重载了OnOK(),若有则执行重载的代码,否则执行默认的CPropertyPage::OnOK()。

3、CPropertySheet和加载的CPropertyPage通信,可以在CPropertySheet中定义
 CPPageContentMode* pCM=(CPPageContentMode*)this->GetPage(0);
 然后利用pCM操作CPPageContentMode中的成员变量。
 也可以在CPropertyPage中获取CPropertySheet的指针。

4、两个或多个加载的CPropertyPage之间通信,可以借助于CPropertySheet实现。

5、对CPropertySheet标准按钮的操作
 (1)更改标准按钮的标题:
 CWnd* pWnd=GetDlgItem(IDOK);
 pWnd->SetWindowTextW(_T("开始"));
 (2)去掉“应用”按钮:
 pWnd=GetDlgItem(ID_APPLY_NOW);
 pWnd->ShowWindow(FALSE);
 (3)移动标准按钮至对话框中间

CPropertyPage* pPage;
 pPage
=this->GetPage(0);
// CRect rectPage;
 pPage->GetWindowRect(&rectPage);

 CWnd
* pWndOK=GetDlgItem(IDOK);
 CWnd
* pWndCancel=GetDlgItem(IDCANCEL);
 CRect rectOK,rectCancel;
 pWndOK
->GetWindowRect(&rectOK);
 pWndCancel
->GetWindowRect(&rectCancel);

 
int BtnCenter=(rectCancel.right-rectOK.left)/2// OK和CANCEL按钮中轴线
 int PageCenter=(rectPage.right-rectPage.left)/2//属性单的中轴线
 int dist=PageCenter-BtnCenter;
 rectOK.left
+=dist;
 rectOK.right
+=dist;
 rectCancel.left
+=dist;
 rectCancel.right
+=dist; 
 ScreenToClient(
&rectOK);
 ScreenToClient(
&rectCancel);
 pWndOK
->MoveWindow(&rectOK);
 pWndCancel
->MoveWindow(&rectCancel);