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