Solved

Math to calculate user friendly XY graph axis ticks?

Posted on 2011-04-25
Medium Priority
536 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

ID: 35459917
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

ID: 35459924
Linear is perfectly fine.
0

LVL 27

Expert Comment

ID: 35460091
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

ID: 35460127

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

ID: 35460131
... or published code I could crib from.
0

LVL 27

Expert Comment

ID: 35461124
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 27

Accepted Solution

d-glitch earned 2000 total points
ID: 35461157
0

LVL 37

Expert Comment

ID: 35468044
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

ID: 35713902
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

ID: 35723061

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

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

High user turnover can cause old/redundant user data to consume valuable space. UserResourceCleanup was developed to address this by automatically deleting user folders when the user account is deleted.
We take a look at the fast-evolving changes in Search Engine Optimization rules and algorithms by Google.
This Micro Tutorial will teach you how to add a cinematic look to any film or video out there. There are very few simple steps that you will follow to do so. This will be demonstrated using Adobe Premiere Pro CS6.
I've attached the XLSM Excel spreadsheet I used in the video and also text files containing the macros used below. https://filedb.experts-exchange.com/incoming/2017/03_w12/1151775/Permutations.txt https://filedb.experts-exchange.com/incoming/201…
Suggested Courses
Course of the Month12 days, 21 hours left to enroll