?
Solved

Math to calculate user friendly XY graph axis ticks?

Posted on 2011-04-25
10
Medium Priority
?
536 Views
Last Modified: 2012-05-11

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
Comment
Question by:riceman0
10 Comments
 
LVL 12

Expert Comment

by:mwochnick
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

by:riceman0
ID: 35459924
Linear is perfectly fine.
0
 
LVL 27

Expert Comment

by:d-glitch
ID: 35460091
This is certainly a worthy problem.  You probably have to do it for both axes.

Concentrate on the x-axis.

Calculate your required range:  Xmax-Xmin
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
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:riceman0
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

by:riceman0
ID: 35460131
... or published code I could crib from.
0
 
LVL 27

Expert Comment

by:d-glitch
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

by:
d-glitch earned 2000 total points
ID: 35461157
0
 
LVL 37

Expert Comment

by:TommySzalapski
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

by:riceman0
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

Open in new window

0
 

Author Comment

by:riceman0
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

Open in new window

0

Featured Post

Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

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

580 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question