27

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

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

智能算法将优先考虑 10、5 和 2 倍数的刻度线。此外,智能算法将在零附近对称。

4

4 回答 4

12

由于我不喜欢迄今为止找到的任何解决方案,因此我实施了自己的解决方案。它在 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;
}
于 2018-04-19T01:09:49.963 回答
2

取关于零的最长线段(或整个图表,如果零不在范围内) - 例如,如果您在 [-5, 1] 范围内有一些东西,则取 [-5,0]。

计算出该段的大致长度,以刻度为单位。这只是将长度除以刻度的宽度。所以假设该方法说我们可以在 -5 到 0 之间放置 11 个刻度。这是我们的上限。对于较短的一面,我们将只在较长的一面反映结果。

现在尝试输入尽可能多(最多 11 个)的记号,这样每个记号的标记形式为 i*10*10^n、i*5*10^n、i*2*10^n,其中n 是整数,i 是刻度的索引。现在这是一个优化问题——我们想要最大化我们可以放入的刻度数,同时最小化最后一个刻度和结果结束之间的距离。因此,为获得尽可能多的滴答声分配一个分数,小于我们的上限,并为使最后一个滴答声接近 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 再次执行此操作,并选择得分最高的那个。

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

于 2008-10-27T04:09:01.600 回答
1

我一直在使用 jQuery flot图形库。它是开源的,并且可以很好地生成轴/刻度线。我建议查看它的代码并从中汲取一些想法。

于 2008-10-27T05:43:05.780 回答
1

这个简单的算法产生的间隔是 1、2 或 5 乘以 10 的幂的倍数。并且轴范围被划分为至少 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;
}
于 2017-03-04T08:19:51.793 回答