我正在做一些图表绘制,其中横轴有时间,纵轴是价格。
价格可能介于0.23487至0.8746或20.47至45.48或1.4578至1.6859或9000至12000 ......您明白了,任何范围都可能存在。数字的精度也可能不同(但通常是2位小数或4位小数)。
现在在纵轴上,我需要显示价格,但不是所有价格只显示一些重要的水平。我需要显示尽可能多的重要级别,但这些级别不应该比30像素更接近(。
因此,如果我的图表的数据范围从1.4567到1.6789,图表高度为500,我可以显示最多16个重要级别。可见价格范围是1.6789-1.4567 = 0.2222。 0.2222 / 16 = 0.0138所以我可以显示1.4716,1.4854等等。但是我想把这个等级调整到一些有效数字,例如1.4600,1.4700,1.4800 ......或1.4580,1.4590,1.4600 ......或1.4580,1.4585 ......等等我希望始终显示尽可能多的符号级别,具体取决于我有多少空间但始终只显示级别在一些重要的值(我不是说舍入值也是20.25是显着的),它们是1,2,2.5,5和10或它们的乘数(10,20,25 ......或100,200,250 ... )或他们的分裂(0.1,0.2,0.25 ......或0.0001,0.0002,0.00025 ......)
我实际上有这个工作,但我根本不喜欢我的算法,它太长而且不优雅。我希望有人可以建议一些更优雅和通用的方式。我正在寻找算法,我可以实现不必要的代码。以下是我目前在objective-c中的算法。感谢。
-(float) getPriceLineDenominator
{
NSArray *possVal = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:1.0],
[NSNumber numberWithFloat:2.0],
[NSNumber numberWithFloat:2.5],
[NSNumber numberWithFloat:5.0],
[NSNumber numberWithFloat:10.0],
[NSNumber numberWithFloat:20.0],
[NSNumber numberWithFloat:25.0],
[NSNumber numberWithFloat:50.0],
[NSNumber numberWithFloat:100.0],
nil];
float diff = highestPrice-lowestPrice;//range of shown values
double multiplier = 1;
if(diff<10)
{
while (diff<10)
{
multiplier/=10;
diff = diff*10;
}
}
else
{
while (diff>100)
{
multiplier*=10;
diff = diff/10;
}
}
float result = 0;
for(NSNumber *n in possVal)
{
float f = [n floatValue]*multiplier;
float x = [self priceY:highestPrice];
float y = [self priceY:highestPrice-f];
if((y-x)>=30)//30 is minimum distance between price levels shown
{
result = f;
break;
}
}
return result;
}
答案 0 :(得分:3)
您可以使用对数来识别每个子范围的大小。
假设您知道数据中的最小值和最大值。你也知道你想要多少级别。
最大值和最小值之间的差值除以级别数量(稍微)小于每个子范围的大小
double diff = highestPrice - lowestPrice; // range of shown values
double range = diff / levels; // size of range
double logrange = log10(range); // log10
int lograngeint = (int)logrange; // integer part
double lograngerest = logrange - lograngeint; // fractional part
if (lograngerest < 0) { // adjust if negative
lograngerest += 1;
lograngeint -= 1;
}
/* now you can increase lograngerest to the boundaries you like */
if (lograngerest < log10(2)) lograngerest = log10(2);
else if (lograngerest < log10(2.5)) lograngerest = log10(2.5);
else if (lograngerest < log10(5)) lograngerest = log10(5);
else lograngerest = /* log10(10) */ 1;
/* and the size of each range is therefore */
return pow(10, lograngeint + lograngerest);
第一个范围在数据中的最小值之前开始。使用fmod
确切地查找之前的数量。
答案 1 :(得分:1)
正如您所说,可用高度决定了最大分割数。为了争论,我们可以避免使用幻数,并说你有height
像素,最小间距为closest
:
int maxDivs = height / closest;
将范围划分为多个部分。你很可能会得到一些丑陋的价值,但它提供了一个起点:
double minTickSpacing = diff/maxDivs;
您需要从此间距升级,直到达到适当数量级的“重要”值之一。您可以使用一些数学函数来查找顺序,而不是循环和除法/乘法:
double multiplier = pow(10, -floor(log10(minTickSpacing)));
从{2, 2.5, 5, 10}
范围中选择下一个间距 - 为了简单起见,我将使用常量和if
- else
执行此操作:
double scaledSpacing = multiplier * minTickSpacing;
if ( scaledSpacing < 2 ) result = 2;
else if ( scaledSpacing < 2.5 ) result = 2.5;
else if ( scaledSpacing < 5 ) result = 5;
else result = 10;
return result/multiplier;
或类似的东西。完全未经测试,因此您需要检查标志和范围等。并且肯定会有一些有趣的边缘情况。但我认为它应该在正确的球场......