自制绘图之坐标轴

news/2024/7/7 21:37:32

写代码之前得先了解坐标轴的一些属性,坐标轴有范围,每隔多少显示一条数值信息。然而间隔信息有时并不确定,一旦设置不准确,图形会乱掉。最好的方法是使用另一个参数:分隔符总数。这样可以利用坐标范围计算出间隔。

首先需要定义范围:

 1 template <class T>
 2 class DataRange
 3 {
 4 public:
 5     DataRange(const T& minValue, const T& maxValue): itsMinValue(minValue), itsMaxValue(maxValue){}
 6     DataRange(){}
 7     T itsMinValue;
 8     T itsMaxValue;
 9     T Length() const { return itsMaxValue - itsMinValue ;}
10 };

先写测试代码:

1 void TestPicture::TestCoordSimple()
2 {
3     CoordinateAttribute<double> ca;
4     ca.itsNumSeparate = 10;
5     ca.itsGivenDataRange = DataRange<double>(0.0,2.4);
6     ca.CalcCoordinate();
7     assert(0.24==ca.itsSeparateLength);
8 }

通过这一测试很简单只需一个除法就搞定了。代码如下:

 1 template <class T>
 2 class CoordinateAttribute
 3 {
 4 public:
 5     CoordinateAttribute(){}
 6     //attribute
 7     int itsNumSeparate;
 8     DataRange<T> itsGivenDataRange;
 9     T itsSeparateLength;
10     //operators
11     void CalcCoordinate()
12     {
13         //calculate separate length
14         itsSeparateLength = itsGivenDataRange.Length() / itsNumSeparate;
15     }
16 };

有个问题就是如果坐标轴的范围不能被分隔符整除,显示出来的坐标将带有很多小数位,往往不是我们所想要的。于是还需要另一个参数给坐标轴,那就是最小刻度。比如有些情况下需要显示的都是整数,有时需要保留一位小数。

1 void TestPicture::TestCoordSimple2()
2 {
3     CoordinateAttribute<double> ca;
4     ca.itsNumSeparate = 10;
5     ca.itsGivenDataRange = DataRange<double>(0.0,2.4);
6     ca.itsMinScale = 0.1;
7     ca.CalcCoordinate();
8     assert(0.3==ca.itsSeparateLength);
9 }

修改代码为:

 1 template <class T>
 2 class CoordinateAttribute
 3 {
 4 public:
 5     CoordinateAttribute(){}
 6     //attribute
 7     int itsNumSeparate;
 8     DataRange<T> itsGivenDataRange;
 9     T itsMinScale;
10     T itsSeparateLength;
11     //operators
12     void CalcCoordinate()
13     {
14         //calculate separate length
15         T temp = itsGivenDataRange.Length() / itsNumSeparate;
16         itsSeparateLength = itsMinScale * ceil((double)temp / itsMinScale);
17     }
18 };

这个坐标轴的计算方法先做到这,下面来测试其效果。坐标轴的信息需要给图形,最简洁的方法是告诉图形在坐标轴的什么位置显示多少坐标值,由于坐标轴对图形是未知的,其位置可以使用百分比来表示,在C++中可以使用std::map<double,std::string>来传递坐标轴的信息。

在CoordinateAttribute加入一个FillToAxisMap函数

 1 void FillToAxisMap(std::map<double,std::string>& sm)
 2 {
 3     sm.clear();
 4     T iT = itsGivenDataRange.itsMinValue;
 5     for (int i = 0; i <= itsNumSeparate; i++,iT += itsSeparateLength)
 6     {
 7         double dIndex = iT - itsGivenDataRange.itsMinValue ;
 8         dIndex /= itsCalculateDataRange.Length();
 9             sm.insert(std::make_pair(dIndex,ConvertToString<T>(iT)));
10     }
11 }

其中ConvertToString函数如下:

1 template <typename T>
2 std::string ConvectToString(const T& v)
3 {
4     std::strstream str;
5     str<<v<<'\0';
6     return str.str();
7 }

写个输出函数来看我们的结果。

测试函数:

1     map<double,string> CoordInfo;
2     ca.FillToAxisMap(CoordInfo);
3     for (map<double,string>::iterator it = CoordInfo.begin();it != CoordInfo.end(); ++it)
4     {
5         cout << endl << it->first << " ---- " << it->second ;
6     }

运行结果:

0 ---- 0
0.1 ---- 0.3
0.2 ---- 0.6
0.3 ---- 0.9
0.4 ---- 1.2
0.5 ---- 1.5
0.6 ---- 1.8
0.7 ---- 2.1
0.8 ---- 2.4
0.9 ---- 2.7
1 ---- 3

  

可以看到,其结果还是在我们的意料之中的,有一点不好,我们给的范围是0~2.4,而现在的范围变成了0~3了。先不做改动,再做下一次测试。

1     CoordinateAttribute<double> ca;
2     ca.itsNumSeparate = 10;
3     ca.itsGivenDataRange = DataRange<double>(0.0,2.4);
4     ca.itsMinScale = 0.5;
5     ca.CalcCoordinate();

其输出结果为:

0 ---- 0
0.1 ---- 0.5
0.2 ---- 1
0.3 ---- 1.5
0.4 ---- 2
0.5 ---- 2.5
0.6 ---- 3
0.7 ---- 3.5
0.8 ---- 4
0.9 ---- 4.5
1 ---- 5

问题更严重了,计算出的范围比之前我们给的大很多。其实这是由于所给参数不合理导致的。既然itsMinScale设了0.5,那么itsNumSeparate设为10显然不正确,这时最好由程序自己来调整。

理想的输出结果是:

0 ---- 0
0.2 ---- 0.5
0.4 ---- 1
0.6 ---- 1.5
0.8 ---- 2
1 ---- 2.5

修改代码如下:

 1 template <class T>
 2 class CoordinateAttribute
 3 {
 4 public:
 5     CoordinateAttribute(){}
 6     //the param user give
 7     int itsNumSeparate;
 8     DataRange<T> itsGivenDataRange;
 9     T itsMinScale;
10     //the param calculate
11     T itsSeparateLength;
12     DataRange<T> itsCalculateDataRange;
13 
14     //operators
15     void CalcCoordinate()
16     {
17         //adjust its min value
18         itsCalculateDataRange.itsMinValue = itsMinScale * floor((double)itsGivenDataRange.itsMinValue / itsMinScale);
19         //calculate separate length
20         itsCalculateDataRange.itsMaxValue = itsGivenDataRange.itsMaxValue;
21         T temp = itsCalculateDataRange.Length() / itsNumSeparate;
22         itsSeparateLength =  itsMinScale * ceil((double)temp / itsMinScale);
23         //calculate its max value
24         itsCalculateDataRange.itsMaxValue = itsCalculateDataRange.itsMinValue + itsSeparateLength * itsNumSeparate;
25         //delete unused value
26         while( itsCalculateDataRange.itsMaxValue - itsSeparateLength > itsGivenDataRange.itsMaxValue)
27         {
28             itsCalculateDataRange.itsMaxValue -= itsSeparateLength;
29             itsNumSeparate--;
30         }
31     }
32 };

这里加了itsCalculateDataRange,用来记录计算后的数据范围,对max和min的范围进行了调整。

测试如下:

 1     ca.itsNumSeparate = 7;
 2     ca.itsMinScale = 0.1;
 3     ca.itsGivenDataRange = DataRange<double>(0.0,2.34);
 4     ca.CalcCoordinate();
 5     CPPUNIT_ASSERT_DOUBLES_EQUAL(0.4,ca.itsSeparateLength,0.01);
 6     CPPUNIT_ASSERT_DOUBLES_EQUAL(2.4,ca.itsCalculateDataRange.itsMaxValue,0.01);
 7     ca.itsNumSeparate = 5;
 8     ca.CalcCoordinate();
 9     CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5,ca.itsSeparateLength,0.01);
10     CPPUNIT_ASSERT_DOUBLES_EQUAL(2.5,ca.itsCalculateDataRange.itsMaxValue,0.01);
11     ca.itsNumSeparate = 10;
12     ca.itsMinScale = 0.1;
13     ca.CalcCoordinate();
14     CPPUNIT_ASSERT_DOUBLES_EQUAL(0.3,ca.itsSeparateLength,0.001);
15     CPPUNIT_ASSERT_DOUBLES_EQUAL(2.4,ca.itsCalculateDataRange.itsMaxValue,0.001);
16     ca.itsGivenDataRange.itsMinValue = 0.14;
17     ca.itsMinScale = 0.01;
18     ca.itsNumSeparate = 10;
19     ca.CalcCoordinate();
20     CPPUNIT_ASSERT_DOUBLES_EQUAL(0.22,ca.itsSeparateLength,0.001);
21     CPPUNIT_ASSERT_DOUBLES_EQUAL(0.14,ca.itsCalculateDataRange.itsMinValue,0.001);
22     CPPUNIT_ASSERT_DOUBLES_EQUAL(2.34,ca.itsCalculateDataRange.itsMaxValue,0.001);

转载于:https://www.cnblogs.com/zhangyonghugo/archive/2012/05/04/2482613.html


http://lihuaxi.xjx100.cn/news/243525.html

相关文章

Java培训好不好?零基础可以学吗?

5g时代的来临&#xff0c;越来越多的人开启智能时代&#xff0c;互联网行业的发展速度越来越快&#xff0c;高薪行业一直受到很多人的关注&#xff0c;尤其是java这一块&#xff0c;很多人都想学习&#xff0c;那么参加Java培训好不好?零基础可以学吗? Java培训好不好?零基础…

史上最全的“大数据”学习资源

2019独角兽企业重金招聘Python工程师标准>>> 资源列表&#xff1a; 关系数据库管理系统&#xff08;RDBMS&#xff09; 框架 分布式编程 分布式文件系统 文件数据模型 Key -Map 数据模型 键-值数据模型 图形数据模型 NewSQL数据库 列式数据库 时间序列数据…

《Linux内核设计与实现》读书笔记 第三章 进程管理

第三章进程管理 进程是Unix操作系统抽象概念中最基本的一种。我们拥有操作系统就是为了运行用户程序&#xff0c;因此&#xff0c;进程管理就是所有操作系统的心脏所在。 3.1进程 概念&#xff1a; 进程&#xff1a;处于执行期的程序。但不仅局限于程序&#xff0c;还包含其他资…

SDK开发日积月累(二)

WM_NOTIFY消息和WM_COMMAND消息在一个对话框中&#xff0c;子控件可以有两种方式与父对话框通信。1.向父对话框发送WM_COMMAND消息&#xff0c;但这种消息传递的信息量比较少。2.向父对话框发送WM_NOTIFY消息&#xff0c;信息量比较大。idCtrl (int) wParam; pnmh (LPNMHDR) …

零基础学习Java培训有什么攻略

零基础学习Java培训有什么攻略?java是主流编程语言之一&#xff0c;我们在学习Java的时候需要制定Java学习路线图&#xff0c;Java涉及到的知识点非常的多&#xff0c;我们该从何学起呢?怎么系统的学习呢?来看看下面的详细介绍。 一、Java学习阶段 将Java学习过程分为3个阶段…

笔试算法题(58):二分查找树性能分析(Binary Search Tree Performance Analysis)

议题&#xff1a;二分查找树性能分析&#xff08;Binary Search Tree Performance Analysis&#xff09; 分析&#xff1a; 二叉搜索树&#xff08;Binary Search Tree&#xff0c;BST&#xff09;是一颗典型的二叉树&#xff0c;同时任何节点的键值大于等于该节点左子树中的所…

深入理解Java内存模型(四)——volatile

2019独角兽企业重金招聘Python工程师标准>>> volatile的特性 当我们声明共享变量为volatile后&#xff0c;对这个变量的读/写将会很特别。理解volatile特性的一个好方法是&#xff1a;把对volatile变量的单个读/写&#xff0c;看成是使用同一个锁对这些单个读/写操作…

UI设计要做什么,UI设计培训都要学什么

UI设计要做什么&#xff0c;UI设计培训都要学什么?相信有很多人都对这个问题比较感兴趣&#xff0c;近几年&#xff0c;UI设计被越来越多的人关注&#xff0c;行业薪资水平也是一路飙升&#xff0c;很多人都在准备学习UI设计&#xff0c;那么具体的内容&#xff0c;下面我们来…