Solved

# Math to calculate user friendly XY graph axis ticks?

Posted on 2011-04-25
527 Views

Hello, this is kind of an oddball question.  I'm rolling my own plot widget in .NET (I know, I know, but the UI needs for this particular plot are very application-specific) and I can plot the data just fine (this was actually surprisingly easy) but the only part I'm having trouble with (also surprising) is the calculation of the ticks to plot on the axis.  All plot widgets calculate user friendly tick sizes based on two inputs

* data range (e.g., 0-10 or 0.0001 to 0.003 or 1e23 to 1e25)
* width of the plot (because of the purpose of the ticks are visual aids, you want a comfortable number of ticks in a given span of pixels)

And it will come up with good tick values (5,10,15; or 0.2, 0.4, 0.6, etc) at given locations

I'm having brain block and can't come up with the right equations to come up with user friendly tick values.  And this is just esoteric enough so I can't seem to find good equations on google.  This can't be too hard since every widget does it.

Does anyone have good equations, or have come across good equations in their travels?

Thanks for any help.

0
Question by:riceman0

LVL 12

Expert Comment

So are you ranges linear, exponential. logarithmic?  its hard to tell from your examples and the computation would be different based on the type of scale you want to represent

0

Author Comment

Linear is perfectly fine.
0

LVL 26

Expert Comment

This is certainly a worthy problem.  You probably have to do it for both axes.

Concentrate on the x-axis.

and look at the mantissa in scientific notation.

If it's 1.00 to 1.59 you might want 10 to 17 divisions   spacing 0.1
If it's 1.60 to 2.99 you might want   8 to 17 divisions   spacing 0.2
If it's 3.00 to 5.99 you might want   6 to 14 divisions   spacing 0.5
If it's 6.00 to 9.99 you might want   6 to 12 divisions   spacing 1.0

To find the plotting limits, you would have round you Xmax up and Xmin down to the next tic.
0

Author Comment

Exactly.   Although I think same algorithm will end up working for both X and Y.  I hit several snags between that formulation and practice.  I would feel bad asking someone to work it out -- I figured there *must* be some math published out there.

0

Author Comment

... or published code I could crib from.
0

LVL 26

Expert Comment

I've been looking at MatLab and gnuplot.  They both certainly do it.  But I'm not finding code or even search terms.

http://www.mathworks.com/help/techdoc/ref/axis.html
0

LVL 26

Accepted Solution

0

LVL 37

Expert Comment

I would choose a minimum distance between ticks (in pixels) then maximize the number of ticks rounding to the nearest 1, 2, or 5 (with leading or trailing 0s as needed).
So if you require at least 30 pixels for a tick, then if the graph is 200 pixels, you have room for at most floor(200/30) = 6 ticks (not counting the bottom as a tick since it helps the math). Then take (max - min)/ticks and round it up to the nearest 1, 2, or 5.
For example min = .97, max = 1.05 (max - min)/6 = .013333... rounds to .02 so we use .96, .98, etc.
0

Author Comment

By the way I thought the astrostatistics link referenced source code, so closed the question.  It doesn't, but I did end up working out some source code that seems to work, attached.  Comments welcome.
``````Private Class cFriendlyAxisTickCalculator

Public m_Input_MinLogical As Single
Public m_Input_MaxLogical As Single
Public m_Input_PixelSpan As Single
Public m_Input_IdealPixelDistance As Single = 100

Public m_Output_ResultsAreValid As Boolean
Public m_Output_NumberOfTicks As Integer
Public m_Output_FirstTickLogicalValue As Single
Public m_Output_TickLogicalInterval As Single
Public m_Output_FirstTickPixelPosition As Single
Public m_Output_TickPixelInterval As Single
Public m_Output_DecimalPlacesToShow As Integer

Public Sub Calculate()

m_Output_ResultsAreValid = False

If m_Input_PixelSpan = 0 Then Return
If m_Input_MaxLogical <= m_Input_MinLogical Then Return

Dim deltalogical As Single = m_Input_MaxLogical - m_Input_MinLogical
Dim log10_deltalogical As Single = Math.Log10(deltalogical)
Dim fixed_log10_deltalogical As Single = Int(log10_deltalogical)
Dim logical_per_pixel As Single = deltalogical / m_Input_PixelSpan
Dim logical_for_ideal_pixel_distance As Single = logical_per_pixel * m_Input_IdealPixelDistance
Dim LFIPD_normalized_to_fixed_log10 As Single = logical_for_ideal_pixel_distance / 10 ^ (fixed_log10_deltalogical)

' fit to 0.1, 0.2, 1.0 (i.e., 1/10, 1/5, or whole integers)

Dim LFPID_norm_friendly As Single

If LFIPD_normalized_to_fixed_log10 < 0.15 Then
m_Output_DecimalPlacesToShow = -fixed_log10_deltalogical + 1
LFPID_norm_friendly = 0.1
ElseIf LFIPD_normalized_to_fixed_log10 < 0.6 Then
m_Output_DecimalPlacesToShow = -fixed_log10_deltalogical + 1
LFPID_norm_friendly = 0.2
Else
m_Output_DecimalPlacesToShow = -fixed_log10_deltalogical
LFPID_norm_friendly = 1.0
End If

If m_Output_DecimalPlacesToShow < 0 Then m_Output_DecimalPlacesToShow = 0

m_Output_TickLogicalInterval = LFPID_norm_friendly * 10 ^ (fixed_log10_deltalogical)
m_Output_TickPixelInterval = m_Output_TickLogicalInterval / logical_per_pixel

If m_Output_TickPixelInterval = 0 Then Return

Dim z As Single = Fix(m_Input_MinLogical / m_Output_TickLogicalInterval)

m_Output_FirstTickLogicalValue = (z + 1) * m_Output_TickLogicalInterval
m_Output_FirstTickPixelPosition = (m_Output_FirstTickLogicalValue - m_Input_MinLogical) / logical_per_pixel
m_Output_NumberOfTicks = 1 + Int((m_Input_PixelSpan - m_Output_FirstTickPixelPosition) / m_Output_TickPixelInterval)

m_Output_ResultsAreValid = True

End Sub

End Class
``````
0

Author Comment

Small fix for when low end is negative:
``````Private Class cFriendlyAxisTickCalculator

Public m_Input_MinLogical As Single
Public m_Input_MaxLogical As Single
Public m_Input_PixelSpan As Single
Public m_Input_IdealPixelDistance As Single = 80

Public m_Output_ResultsAreValid As Boolean
Public m_Output_NumberOfTicks As Integer
Public m_Output_FirstTickLogicalValue As Single
Public m_Output_TickLogicalInterval As Single
Public m_Output_FirstTickPixelPosition As Single
Public m_Output_TickPixelInterval As Single
Public m_Output_DecimalPlacesToShow As Integer

Public m_Temp As Single

Public Sub Calculate()

m_Output_ResultsAreValid = False

If m_Input_PixelSpan = 0 Then Return
If m_Input_MaxLogical <= m_Input_MinLogical Then Return

Dim deltalogical As Single = m_Input_MaxLogical - m_Input_MinLogical
Dim log10_deltalogical As Single = Math.Log10(deltalogical)
Dim fixed_log10_deltalogical As Single = Int(log10_deltalogical)
Dim logical_per_pixel As Single = deltalogical / m_Input_PixelSpan
Dim logical_for_ideal_pixel_distance As Single = logical_per_pixel * m_Input_IdealPixelDistance
Dim LFIPD_normalized_to_fixed_log10 As Single = logical_for_ideal_pixel_distance / 10 ^ (fixed_log10_deltalogical)

' fit to 0.1, 0.2, 1.0 (i.e., 1/10, 1/5, or whole integers)

m_Temp = LFIPD_normalized_to_fixed_log10

Dim LFPID_norm_friendly As Single

If LFIPD_normalized_to_fixed_log10 < 0.15 Then
m_Output_DecimalPlacesToShow = -fixed_log10_deltalogical + 1
LFPID_norm_friendly = 0.1
ElseIf LFIPD_normalized_to_fixed_log10 < 0.35 Then
m_Output_DecimalPlacesToShow = -fixed_log10_deltalogical + 1
LFPID_norm_friendly = 0.2
ElseIf LFIPD_normalized_to_fixed_log10 < 0.6 Then
m_Output_DecimalPlacesToShow = -fixed_log10_deltalogical + 1
LFPID_norm_friendly = 0.5
Else
m_Output_DecimalPlacesToShow = -fixed_log10_deltalogical
LFPID_norm_friendly = 1.0
End If

If m_Output_DecimalPlacesToShow < 0 Then m_Output_DecimalPlacesToShow = 0

m_Output_TickLogicalInterval = LFPID_norm_friendly * 10 ^ (fixed_log10_deltalogical)
m_Output_TickPixelInterval = m_Output_TickLogicalInterval / logical_per_pixel

If m_Output_TickPixelInterval = 0 Then Return

Dim z As Single = Fix(m_Input_MinLogical / m_Output_TickLogicalInterval)

m_Output_FirstTickLogicalValue = z * m_Output_TickLogicalInterval
If m_Output_FirstTickLogicalValue < m_Input_MinLogical Then m_Output_FirstTickLogicalValue += m_Output_TickLogicalInterval
m_Output_FirstTickPixelPosition = (m_Output_FirstTickLogicalValue - m_Input_MinLogical) / logical_per_pixel
m_Output_NumberOfTicks = 1 + Int((m_Input_PixelSpan - m_Output_FirstTickPixelPosition) / m_Output_TickPixelInterval)

m_Output_ResultsAreValid = True

End Sub

End Class
``````
0

## Featured Post

### Suggested Solutions

One of Google's most recent algorithm changes affecting local searches is entitled "The Pigeon Update." This update has dramatically enhanced search inquires for the keyword "Yelp." Google searches with the word "Yelp" included will now yield Yelp a…
Exception Handling is in the core of any application that is able to dignify its name. In this article, I'll guide you through the process of writing a DRY (Don't Repeat Yourself) Exception Handling mechanism, using Aspect Oriented Programming.
Migrating to Microsoft Office 365 is becoming increasingly popular for organizations both large and small. If you have made the leap to Microsoft’s cloud platform, you know that you will need to create a corporate email signature for your Office 365…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…