图形轴的Tickmark算法

时间:2008-10-25 23:54:36

标签: algorithm optimization paint

我正在寻找一种在轴上放置刻度线的算法,给定要显示的范围,显示它的宽度,以及测量刻度线的字符串宽度的函数。

例如,假设我需要在1e-6和5e-6之间显示以及以像素显示的宽度,算法将确定我应该将标记(例如)放在1e-6,2e-6, 3e-6,4e-6和5e-6。给定较小的宽度,可能会确定最佳位置仅在偶数位置,即2e-6和4e-6(因为放置更多的标记会导致它们重叠)。

智能算法会优先选择10,5和2的倍数。此外,智能算法将在零附近对称。

5 个答案:

答案 0 :(得分:6)

由于我不喜欢到目前为止找到的任何解决方案,我实施了自己的解决方案。它在C#中,但可以很容易地翻译成任何其他语言。

它基本上从可能的步骤列表中选择显示所有值的最小步骤而不会在边缘完全保留任何值,让您轻松选择要使用的步骤(没有编辑丑陋的if-else if块),并支持任何范围的值。我使用C#Tuple返回三个值,只是为了快速简单的演示。

private static Tuple<decimal, decimal, decimal> GetScaleDetails(decimal min, decimal max)
{
    // Minimal increment to avoid round extreme values to be on the edge of the chart
    decimal epsilon = (max - min) / 1e6m;
    max += epsilon;
    min -= epsilon;
    decimal range = max - min;

    // Target number of values to be displayed on the Y axis (it may be less)
    int stepCount = 20;
    // First approximation
    decimal roughStep = range / (stepCount - 1);

    // Set best step for the range
    decimal[] goodNormalizedSteps = { 1, 1.5m, 2, 2.5m, 5, 7.5m, 10 }; // keep the 10 at the end
    // Or use these if you prefer:  { 1, 2, 5, 10 };

    // Normalize rough step to find the normalized one that fits best
    decimal stepPower = (decimal)Math.Pow(10, -Math.Floor(Math.Log10((double)Math.Abs(roughStep))));
    var normalizedStep = roughStep * stepPower;
    var goodNormalizedStep = goodNormalizedSteps.First(n => n >= normalizedStep);
    decimal step = goodNormalizedStep / stepPower;

    // Determine the scale limits based on the chosen step.
    decimal scaleMax = Math.Ceiling(max / step) * step;
    decimal scaleMin = Math.Floor(min / step) * step;

    return new Tuple<decimal, decimal, decimal>(scaleMin, scaleMax, step);
}

static void Main()
{
    // Dummy code to show a usage example.
    var minimumValue = data.Min();
    var maximumValue = data.Max();
    var results = GetScaleDetails(minimumValue, maximumValue);
    chart.YAxis.MinValue = results.Item1;
    chart.YAxis.MaxValue = results.Item2;
    chart.YAxis.Step = results.Item3;
}

答案 1 :(得分:2)

将最长的段视为零(或整个图形,如果零不在范围内) - 例如,如果您在范围[-5,1]上有某些内容,则取[-5,0]

计算出该段的长度大约为多长。这只是将长度除以刻度的宽度。因此假设该方法表示我们可以将从11到0的11个刻度输入到0.这是我们的上限。对于较短的一面,我们只是在较长的一面反映结果。

现在尝试输入尽可能多(最多11个)的刻度,使得每个刻度的标记形式为i * 10 * 10 ^ n,i * 5 * 10 ^ n,i * 2 * 10 ^ n,其中n是整数,i是tick的索引。现在这是一个优化问题 - 我们希望最大化我们可以放入的滴答数,同时最小化最后一个滴答和结果结束之间的距离。因此,指定一个得分尽可能多的得分,小于我们的上限,并指定一个得分,让最后一个得分接近n - 你必须在这里进行试验。

在上面的例子中,尝试n = 1.我们得到1个滴答(在i = 0)。 n = 2给我们1个滴答,我们离下界更远,所以我们知道我们必须走另一条路。 n = 0在每个整数点上给出6个刻度。 n = -1给出12个刻度(0,-0.5,...,-5.0)。 n = -2给出了24个刻度,依此类推。评分算法会给每个人一个得分 - 更高意味着更好的方法。

对于i * 5 * 10 ^ n和i * 2 * 10 ^ n再次执行此操作,并选择得分最高的那个。

(作为一个评分算法的例子,假设得分是到最后一个滴答的距离乘以最大滴答数减去所需的数量。这可能是坏的,但它将作为一个不错的起点)。

答案 2 :(得分:0)

我一直在使用jQuery flot图库。它是开源的,可以很好地生成轴/蜱。我建议查看它的代码并从那里捏一些想法。

答案 3 :(得分:0)

这个简单的算法产生的间隔是10的幂的1,2或5倍的倍数。并且轴范围被划分为至少5个间隔。代码示例使用java语言:

protected double calculateInterval(double range) {
    double x = Math.pow(10.0, Math.floor(Math.log10(range)));
    if (range / x >= 5)
        return x;
    else if (range / (x / 2.0) >= 5)
        return x / 2.0;
    else
        return x / 5.0;
}

这是一种替代方案,至少10个时间间隔:

protected double calculateInterval(double range) {
    double x = Math.pow(10.0, Math.floor(Math.log10(range)));
    if (range / (x / 2.0) >= 10)
        return x / 2.0;
    else if (range / (x / 5.0) >= 10)
        return x / 5.0;
    else
        return x / 10.0;
}

答案 4 :(得分:-4)

你的开发语言是什么?我在C ++中使用图形控件,可以使用对数,单元等组合轻松解决这个问题。如果您愿意,可以为您解释代码。