Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tickmark algorithm for a graph axis

I'm looking for an algorithm that places tick marks on an axis, given a range to display, a width to display it in, and a function to measure a string width for a tick mark.

For example, given that I need to display between 1e-6 and 5e-6 and a width to display in pixels, the algorithm would determine that I should put tickmarks (for example) at 1e-6, 2e-6, 3e-6, 4e-6, and 5e-6. Given a smaller width, it might decide that the optimal placement is only at the even positions, i.e. 2e-6 and 4e-6 (since putting more tickmarks would cause them to overlap).

A smart algorithm would give preference to tickmarks at multiples of 10, 5, and 2. Also, a smart algorithm would be symmetric around zero.

like image 724
Nick Avatar asked Oct 25 '08 23:10

Nick


People also ask

How do you add a tick mark in Axis?

Add tick marks on an axisClick Add Chart Element > Axes > More Axis Options. On the Format Axis pane, expand Tick Marks, and then click options for major and minor tick mark types.

What is a tick mark on an axis?

A tick is a short line on an axis. For category axes, ticks separate each category. For value axes, ticks mark the major divisions and show the exact point on an axis that the axis label defines. Ticks are always the same color and line style as the axis.

What do you call the ticks on a graph?

Hatch marks (also called hash marks or tick marks) are a form of mathematical notation. They are used in three ways as: Unit and value marks — as on a ruler or number line. Congruence notation in geometry — as on a geometric figure. Graphed points — as on a graph.

Are labeled tick marks required on a graph?

Tick marks are used to indicate a reference value at a given point in a chart. Tick marks function similar to the lines on a ruler – not all tick marks need to be labeled, but they do need to establish a continuous interval by ensuring the number of tick marks between each labeled tick mark is always the same.


2 Answers

As I didn't like any of the solutions I've found so far, I implemented my own. It's in C# but it can be easily translated into any other language.

It basically chooses from a list of possible steps the smallest one that displays all values, without leaving any value exactly in the edge, lets you easily select which possible steps you want to use (without having to edit ugly if-else if blocks), and supports any range of values. I used a C# Tuple to return three values just for a quick and simple demonstration.

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; } 
like image 62
Andrew Avatar answered Sep 28 '22 02:09

Andrew


Take the longest of the segments about zero (or the whole graph, if zero is not in the range) - for example, if you have something on the range [-5, 1], take [-5,0].

Figure out approximately how long this segment will be, in ticks. This is just dividing the length by the width of a tick. So suppose the method says that we can put 11 ticks in from -5 to 0. This is our upper bound. For the shorter side, we'll just mirror the result on the longer side.

Now try to put in as many (up to 11) ticks in, such that the marker for each tick in the form i*10*10^n, i*5*10^n, i*2*10^n, where n is an integer, and i is the index of the tick. Now it's an optimization problem - we want to maximize the number of ticks we can put in, while at the same time minimizing the distance between the last tick and the end of the result. So assign a score for getting as many ticks as we can, less than our upper bound, and assign a score to getting the last tick close to n - you'll have to experiment here.

In the above example, try n = 1. We get 1 tick (at i=0). n = 2 gives us 1 tick, and we're further from the lower bound, so we know that we have to go the other way. n = 0 gives us 6 ticks, at each integer point point. n = -1 gives us 12 ticks (0, -0.5, ..., -5.0). n = -2 gives us 24 ticks, and so on. The scoring algorithm will give them each a score - higher means a better method.

Do this again for the i * 5 * 10^n, and i*2*10^n, and take the one with the best score.

(as an example scoring algorithm, say that the score is the distance to the last tick times the maximum number of ticks minus the number needed. This will likely be bad, but it'll serve as a decent starting point).

like image 38
mindvirus Avatar answered Sep 28 '22 00:09

mindvirus