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