题
我需要制作一个经过优化的图表 y 轴最大值。
我目前制作图表的方法只是使用所有图表的最大值,然后将其除以十,并将其用作网格线。我没写。
更新说明: 这些图表已被更改。一旦我修复了代码,我的动态图就开始工作,这使得这个问题变得毫无意义(因为示例中不再有任何错误)。我已经用静态图像更新了这些内容,但有些答案引用了不同的值。记住这一点。2月份到目前为止,共有12003至14003个来电。信息丰富,但丑陋。
我想避免看起来像猴子想出的图表 y- 轴编号。
使用 Google 图表 API 有一点帮助,但它仍然不是我想要的。数字很干净,但 y 值的顶部始终与图表上的最大值相同。该图表的范围从 0 到 1357。我需要计算出 1400 的正确值, 有问题地.
我投入 罗比在这里对“好”数字的定义是因为它解释得很好。
- “好”数字是指具有 3 个或更少非零数字的数字(例如:1230000)
- 一个“好”数字具有与零数字相同或更少的非零数字(例如,1230 不好,1200 好)
- 最好的数字是 3 个零的倍数(例如“1,000”、“1,000,000”)
- 第二好的数字是 3 个零加 2 个零的倍数(例如“1,500,000”、“1,200”)
解决方案
我找到了使用马克·兰塞姆想法的修改版本来获得我想要的结果的方法。
首先,当给定刻度数时,Mark Ransom 的代码确定了刻度之间的最佳间距。有时,这个数字最终会超过图表上最高值的两倍,具体取决于您想要的网格线数量。
我正在做的是用 5、6、7、8、9 和 10 条网格线(刻度线)运行 Mark 的代码,以找出其中哪一条是最低的。如果值为 23,图表的高度将变为 25,网格线位于 5、10、15、20 和 25 处。如果值为 26,则图表的高度为 30,网格线位于 5、10、15、20、25 和 30 处。它的网格线间距相同,但数量更多。
下面是复制 Excel 的操作步骤,使图表变得更加精美。
- 暂时将图表的最高值提高约 5%(以便图表最高点和图表区域顶部之间始终存在一些空间。我们希望 99.9 向上舍入为 120)
- 找到5、6、7、8、9和10个网格线的最佳网格线位置。
- 选出这些数字中最小的一个。记住获得该值所需的网格线数。
- 现在您已获得最佳图表高度。线条/条形永远不会与图表顶部对接,并且您拥有最佳的刻度数。
PHP:
function roundUp($maxValue){
$optiMax = $maxValue * 2;
for ($i = 5; $i <= 10; $i++){
$tmpMaxValue = bestTick($maxValue,$i);
if (($optiMax > $tmpMaxValue) and ($tmpMaxValue > ($maxValue + $maxValue * 0.05))){
$optiMax = $tmpMaxValue;
$optiTicks = $i;
}
}
return $optiMax;
}
function bestTick($maxValue, $mostTicks){
$minimum = $maxValue / $mostTicks;
$magnitude = pow(10,floor(log($minimum) / log(10)));
$residual = $minimum / $magnitude;
if ($residual > 5){
$tick = 10 * $magnitude;
} elseif ($residual > 2) {
$tick = 5 * $magnitude;
} elseif ($residual > 1){
$tick = 2 * $magnitude;
} else {
$tick = $magnitude;
}
return ($tick * $mostTicks);
}
Python:
import math
def BestTick(largest, mostticks):
minimum = largest / mostticks
magnitude = 10 ** math.floor(math.log(minimum) / math.log(10))
residual = minimum / magnitude
if residual > 5:
tick = 10 * magnitude
elif residual > 2:
tick = 5 * magnitude
elif residual > 1:
tick = 2 * magnitude
else:
tick = magnitude
return tick
value = int(input(""))
optMax = value * 2
for i in range(5,11):
maxValue = BestTick(value,i) * i
print maxValue
if (optMax > maxValue) and (maxValue > value + (value*.05)):
optMax = maxValue
optTicks = i
print "\nTest Value: " + str(value + (value * .05)) + "\n\nChart Height: " + str(optMax) + " Ticks: " + str(optTicks)
解决方案
这是来自之前的类似问题:
我已经用一种蛮力的方法完成了此操作。首先,找出您可以适合空间的刻度标记数量的最大数量。除以总数 值范围以 蜱;这是 最低限度刻度线的间距。现在计算 底数以对数为底 10 到 获取即时报价的大小,以及 除以此值。你应该结束 在 1 到 1 的范围内 10.只需选择大于或等于值的整数,即可 将其乘以对数 较早计算。这是你的 最终刻度间距。
Python 中的示例:
import math
def BestTick(largest, mostticks):
minimum = largest / mostticks
magnitude = 10 ** math.floor(math.log(minimum) / math.log(10))
residual = minimum / magnitude
if residual > 5:
tick = 10 * magnitude
elif residual > 2:
tick = 5 * magnitude
elif residual > 1:
tick = 2 * magnitude
else:
tick = magnitude
return tick
其他提示
过去我以一种蛮力的方式完成了这件事。这是一段运行良好的 C++ 代码......但对于硬编码的下限和上限(0 和 5000):
int PickYUnits()
{
int MinSize[8] = {20, 20, 20, 20, 20, 20, 20, 20};
int ItemsPerUnit[8] = {5, 10, 20, 25, 50, 100, 250, 500};
int ItemLimits[8] = {20, 50, 100, 250, 500, 1000, 2500, 5000};
int MaxNumUnits = 8;
double PixelsPerY;
int PixelsPerAxis;
int Units;
//
// Figure out the max from the dataset
// - Min is always 0 for a bar chart
//
m_MinY = 0;
m_MaxY = -9999999;
m_TotalY = 0;
for (int j = 0; j < m_DataPoints.GetSize(); j++) {
if (m_DataPoints[j].m_y > m_MaxY) {
m_MaxY = m_DataPoints[j].m_y;
}
m_TotalY += m_DataPoints[j].m_y;
}
//
// Give some space at the top
//
m_MaxY = m_MaxY + 1;
//
// Figure out the size of the range
//
double yRange = (m_MaxY - m_MinY);
//
// Pick the initial size
//
Units = MaxNumUnits;
for (int k = 0; k < MaxNumUnits; k++)
{
if (yRange < ItemLimits[k])
{
Units = k;
break;
}
}
//
// Adjust it upwards based on the space available
//
PixelsPerY = m_rcGraph.Height() / yRange;
PixelsPerAxis = (int)(PixelsPerY * ItemsPerUnit[Units]);
while (PixelsPerAxis < MinSize[Units]){
Units += 1;
PixelsPerAxis = (int)(PixelsPerY * ItemsPerUnit[Units]);
if (Units == 5)
break;
}
return ItemsPerUnit[Units];
}
然而,你所说的一些话让我有些不舒服。要选择好的轴编号,“好编号”的定义会有所帮助:
- “好”数字是指具有 3 个或更少非零数字的数字(例如:1230000)
- 一个“好”数字具有与零数字相同或更少的非零数字(例如,1230 不好,1200 好)
- 最好的数字是 3 个零的倍数(例如“1,000”、“1,000,000”)
- 第二好的数字是 3 个零加 2 个零的倍数(例如“1,500,000”、“1,200”)
不确定上述定义是否“正确”或实际上有帮助(但有了定义,设计算法就变得更简单)。
您可以四舍五入到两位有效数字。以下伪代码应该可以工作:
// maxValue is the largest value in your chart
magnitude = floor(log10(maxValue))
base = 10^(magnitude - 1)
chartHeight = ceiling(maxValue / base) * base
例如,如果 maxValue
是 1357,则震级为 3,基数为 100。除以 100、向上舍入并乘以 100 的结果是向上舍入到下一个 100 的倍数,即四舍五入至两位有效数字。在本例中,结果为 1400 (1357 ⇒ 13.57 ⇒ 14 ⇒ 1400)。
稍微改进并测试...(适用于单位分数,而不仅仅是整数)
public void testNumbers() {
double test = 0.20000;
double multiple = 1;
int scale = 0;
String[] prefix = new String[]{"", "m", "u", "n"};
while (Math.log10(test) < 0) {
multiple = multiple * 1000;
test = test * 1000;
scale++;
}
double tick;
double minimum = test / 10;
double magnitude = 100000000;
while (minimum <= magnitude){
magnitude = magnitude / 10;
}
double residual = test / (magnitude * 10);
if (residual > 5) {
tick = 10 * magnitude;
} else if (residual > 2) {
tick = 5 * magnitude;
} else if (residual > 1) {
tick = 2 * magnitude;
} else {
tick = magnitude;
}
double curAmt = 0;
int ticks = (int) Math.ceil(test / tick);
for (int ix = 0; ix < ticks; ix++) {
curAmt += tick;
BigDecimal bigDecimal = new BigDecimal(curAmt);
bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println(bigDecimal.stripTrailingZeros().toPlainString() + prefix[scale] + "s");
}
System.out.println("Value = " + test + prefix[scale] + "s");
System.out.println("Tick = " + tick + prefix[scale] + "s");
System.out.println("Ticks = " + ticks);
System.out.println("Scale = " + multiple + " : " + scale);
}
如果您希望顶部为 1400,请将最后两个参数调整为 1400 而不是 1357:
您可以使用 div 和 mod。例如。
假设您希望图表以 20 为增量向上舍入(只是为了使其比典型的“10”值更加任意)。
所以我假设 1、11、18 都会四舍五入到 20。但 21、33、38 将四舍五入为 40。
要得出正确的值,请执行以下操作:
Where divisor = your rounding increment.
divisor = 20
multiple = maxValue / divisor; // Do an integer divide here.
if (maxValue modulus divisor > 0)
multiple++;
graphMax = multiple * maxValue;
现在让我们插入实数:
divisor = 20; multiple = 33 / 20; (integer divide) so multiple = 1 if (33 modulus 20 > 0) (it is.. it equals 13) multiple++; so multiple = 2; graphMax = multiple (2) * maxValue (20); graphMax = 40;