给定两个数值如(3001,5020),如何做到均匀地标注刻度?
研究matlab图形刻度会发现,在matlab中,图像无论如何缩放,坐标刻度间隔都是以1,2,5为基数,按照10倍或者0.1倍的幂进行放大或缩小也即,刻度间隔为:
…0.1 0.2 0.5 ; 12 5; 1020 50; 100200 500; 10002000 5000….
负刻度也类似:
…-0.1 -0.2 -0.5 ; -1-2 -5; -10 -20 -50; -100 -200 -500; -1000 -2000 -5000….
在matlab图像放大中,还会发现,坐标轴刻度个数都在4个到10个之间。当数值超过1000或小于0.001,则会采用科学计数法。
从上面的刻度看,无论正刻度还是负刻度,相邻刻度最大倍数为2.5。如果把绘制的刻度最小个数控制为4,则最大个数为4*2.5=10;这即是为什么matlab绘制的刻度个数为什么不会超出4个到10个这个范围。
根据上面的分析,回到最初的问题,如何计算(3001,5020)均匀刻度,让刻度符合matlab的刻度规律。
首先确定寻找的刻度范围,限制最小刻度数4个,则最大刻度数为10个:
(5020-3001)/4=504.75
(5020-3001)/10=201.9
因而,201.9-504.75之间只有500这个刻度值符合matlab规律。下面是C++编程实现的刻度生成器CLabelGenerator类
LabelGenerator.h:
#ifndef _LABELGENERATOR_H
#define _LABELGENERATOR_H
#pragma once
class CLabelGenerator
{
public:
CLabelGenerator(void);
~CLabelGenerator(void);
bool GenerateLabel(float x1,float x2,int minlabelnum,int &reallabelnum,float *&label,CStringArray &labelArr,int &order,int _interal=0,int limitorder=3); // 产生坐标刻度
CString StringCutZeros(CString str); // 对标注字符串进行去零处理
};
#endif
LabelGenerator.cpp:
//**************************** LabelGenerator.cpp *****************************
// 包含功能:坐标刻度生成
//
// 作者: jiangjp2812 1034378054@qq.com
// 单位: 中国地质大学(武汉)
// 日期: 2016/10/01
//*************************************************************************************
#include "StdAfx.h"
#include "LabelGenerator.h"
#include "math.h"
CLabelGenerator::CLabelGenerator(void)
{
}
CLabelGenerator::~CLabelGenerator(void)
{
}
bool CLabelGenerator::GenerateLabel(float x1,float x2,int minlabelnum,int &reallabelnum,
float *&label,CStringArray &labelArr,int &order,int _interal,int limitorder)
// 生成坐标刻度
// x1,x2 :需要计算刻度的数值范围,minlabelnum:最小刻度数目。reallabelnum:得到的刻度个数。
// label:刻度的数值位置。labelArr:刻度字符串。order:刻度数值采用科学计数法的阶次
// _interal:指定刻度间隔,默认采用计算值。limitorder:超过此阶次采用科学计数,默认为3
{
labelArr.RemoveAll();
float labelInterval=0; // 标注间隔
float leftx=(x2-x1)/((float)minlabelnum*2.5); // 求标注间隔范围
if(leftx<0.000001) // 刻度产生失败
return false;
float rightx=leftx*2.5;
int kx=0,ky=0;
float a=0;
float DOne=0; // 1,2,5 不断乘以10判断是否在标注间隔范围内,如果在则终止循环,
float DTwo=0;
float DFive=0;
//----------------- 计算x方向大于1刻度 ----------------------------------------------------------------------
while(a<=(x2-x1)) // 如果没有找到合适的标度,当超出最大数也终止
{
DOne=1*pow((float)10,(float)kx); // 1,2,5 不断乘以10判断是否在标注间隔范围内,如果在则终止循环,
DTwo=2*pow((float)10,(float)kx);
DFive=5*pow((float)10,(float)kx);
if( DOne>=leftx && DOne<=rightx)
{
labelInterval=DOne;
break;
}
if(DTwo>=leftx && DTwo<=rightx)
{
labelInterval=DTwo;
break;
}
if(DFive>=leftx && DFive<=rightx)
{
labelInterval=DFive;
break;
}
kx++;
a=DFive; // 每循环一次是1,2,5同时按倍数扩大,即判断的数扩大到a=DOne
}
//----------- x方向如果没有大于1刻度则计算小于1刻度 --------------------------------------
kx=0;
a=1;
if(labelInterval==0)
{
while(a>0.000001) // 如果没有找到合适的标度,当小于最小数也终止
{
DOne=1*pow((float)0.1,(float)kx); // 1,2,5 不断乘以0.1判断是否在标注间隔范围内,如果在则终止循环,
DTwo=2*pow((float)0.1,(float)kx);
DFive=5*pow((float)0.1,(float)kx);
if( DOne>=leftx && DOne<=rightx)
{
labelInterval=DOne;
break;
}
if(DTwo>=leftx && DTwo<=rightx)
{
labelInterval=DTwo;
break;
}
if(DFive>=leftx && DFive<=rightx)
{
labelInterval=DFive;
break;
}
kx++;
a=DOne; // 每循环一次是1,2,5同时按倍数缩小,即判断的数扩大到a=DOne
}
}
//-----------------------------------------------------------------------------------
if(labelInterval==0 ) return false;
if(_interal!=0) labelInterval=_interal; // 如果指定间隔,则使用指定的间隔
float startx,endx;
float temstartx/*,temendx*/;
temstartx=x1/labelInterval-(int)(x1/labelInterval);
if(temstartx==0) // 如果x1正好位于刻度上,则其实点从x1开始
{
startx=labelInterval*(int)(x1/labelInterval);
}
else // 如果x1不位于刻度上,则起始点从x1之后某点开始
{
if(x1>=0)
startx=labelInterval*((int)(x1/labelInterval)+1);
else
startx=labelInterval*((int)(x1/labelInterval)-1+1); // 当出现负数时,取整向0靠拢,正半轴需加1,负半轴不需要
}
if(x2>=0)
endx=labelInterval*(int)(x2/labelInterval);
else
endx=labelInterval*((int)(x2/labelInterval)-1); // 当出现负数时,取整向0靠拢,正半轴需加1,负半轴不需要
reallabelnum=(endx-startx)/labelInterval+0.5+1; // 真实需要绘制的坐标刻度个数
// 此处(endx-startx)/labelInterval计算得到的数值应为整数,但浮点型计算得不到精确值
// 会略小于理论整数,因此需要加上0.5后再取整
label=new float[reallabelnum]; // 开辟坐标位置容器
int valueorder=0,intervalorder=0; // 采用科学计数法进行刻度标注,绝对值最大值阶数,标注间隔值阶数
float loop1=labelInterval,loop2=abs(endx);
if(labelInterval>=1) // 求取标注间隔阶次,间隔大于1,正阶
{
while(loop1>=10)
{
loop1=loop1/10;
intervalorder++;
}
}else if(labelInterval<1) // 标注小于1,负阶
{
while(loop1<0.1)
{
loop1=loop1*10;
intervalorder++;
}
}
float valueabs=abs(startx)>abs(endx) abs(startx) : abs(endx) ; // 获取最大值,求取阶数
int limitvmax=pow(10.0,limitorder);
float limitvmin=pow(10.0,limitorder*-1)