Nic*_*ick 24 algorithm optimization paint
我正在寻找一种在轴上放置刻度线的算法,给定要显示的范围,显示它的宽度,以及测量刻度线的字符串宽度的功能.
例如,假设我需要在1e-6和5e-6之间显示以及以像素显示的宽度,算法将确定我应该将标记(例如)放在1e-6,2e-6,3e-6处,4e-6和5e-6.给定较小的宽度,可以确定最佳放置仅在偶数位置,即2e-6和4e-6(因为放置更多的标记将导致它们重叠).
智能算法将优先考虑10,5和2的倍数的标记.此外,智能算法将在零附近对称.
因为我不喜欢到目前为止我找到的任何解决方案,所以我实现了自己的解决方案.它在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;
}
Run Code Online (Sandbox Code Playgroud)
取最长的线段大约为零(或整个图表,如果零不在范围内) - 例如,如果您有范围 [-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 再次执行此操作,并取得分最高的那个。
(作为评分算法的示例,假设分数是到最后一个刻度的距离乘以最大刻度数减去所需的数量。这可能很糟糕,但它将作为一个不错的起点)。