Skip to content

Algorithm: Equally Spaced Ranges

There are some situations where you may need a range of numbers that is equally spaced. For example, when given a minimum and a maximum value within which a set of numbers that need to be equally spaced. This is useful if you are designing an UI control displaying “ticks” or for the axis of a graphic chart displaying cartesian values. Continue reading to see a technique to do just this.

This technique is strongly based in the NiceLabels algorithm that can be found in StackOverflow ported to several programming languages, so… why not to Xojo too? In fact, it was a piece of cake porting it from JavaScript to Xojo due to the similarities of both languages,

Of course, if you just want to jump into the class itself, you can download the project from this link.

Let’s start creating the class itself. I’ve named it NiceScale with all the required properties. To begin, add a new Class to a Xojo project. Then, with the class selected in the Navigator, add the following properties:

  • MaxPoint As Double, setting the Scope to Protected
  • MinPoint As Double, setting the Scope to Protected
  • Spacing As Double, setting the Scope to Public
  • NiceMaximum As Double, setting the Scope to Public
  • NiceMinimum As Double

Next, add the methods starting with the Constructor.

  • Method Name: Constructor
  • Parameters: min As Double, max As Double
  • Scope: Public

And type the following snippet of code in the associated Code Editor:

If Min = Max Then Max = Min + 1

Self.MinPoint = Min(Min, Max)
Self.MaxPoint = Max(Max, Min)
Self.Calculate

As you can see here, the Constructor calls the Calculate method; so let’s add that one:

  • Method Name: Calculate
  • Scope: Protected

And type this code in the associated Code Editor:

Var range As Double

range = niceNum(MaxPoint - MinPoint, False)

Spacing = niceNum(range / 9, True)

NiceMinimum = Floor(MinPoint / Spacing) * Spacing
NiceMaximum = Ceiling(MaxPoint / Spacing) * Spacing

Once again, this method is calling the NiceNum method, so let’s add that one to the
NiceScale class:

  • Method Name: NiceNum
  • Parameters: range As Double, round As Boolean
  • Return Type: Double
  • Scope: Protected

And type the following snippet in the associated Code Editor:

Var exponent As Double
Var fraction As Double
Var niceFraction As Double

exponent = Floor(Log(range) / Log(10))
fraction = range / Pow(10, exponent)

If (round) Then
  If (fraction < 1.5) Then
    niceFraction = 1
  ElseIf (fraction < 3) Then
    niceFraction = 2
  ElseIf (fraction < 7) Then
    niceFraction = 5
  Else
    niceFraction = 10
  End If
Else
  If (fraction <= 1) Then
    niceFraction = 1
  ElseIf (fraction <= 2) Then
    niceFraction = 2
  ElseIf (fraction <= 5) Then
    niceFraction = 5
  Else
    niceFraction = 10
  End If
End If

Return niceFraction * Pow(10, exponent)

Lastly, let’s add a couple of useful methods for the class: GetValues and SetMinMaxPoints. The first one will let us get the Array of the already equally spaced Double values, while the second one will let us set a new pair of minimum and maximum values without needing to create a new instance of the class:

  • Method Name: GetValues
  • Return Type: Double()
  • Scope: Public

Typing the following snippet in the associated Code Editor:

Var values() As Double

For n As Double = Self._NiceMinimum To Self._NiceMaximum Step Self._mSpacing
  values.add n
Next

If values(0) > 0 Then
  While values(0) <> 0
    values.AddAt(0, values(0) - Self._mSpacing)
  Wend
End If

Return values
  • Method Name: SetMinMaxPoints
  • Parameters: min As Double, max As Double
  • Scope: Public

And type these lines of code in the associated Code Editor:

If Min = Max Then Max = Min + 1

MinPoint = Min(Min, Max)
MaxPoint = Max(Max, Min)
Calculate

 

NiceScale in Practice

Let’s create the UI for a Desktop project so we can put our NiceScale class in practice; and because we are going to need some custom drawing, drag a Canvas from the Library to the project Navigator. That will create a canvas subclass. With the just added Canvas subclass selected in the Navigator use the associated Inspector Panel to set the following values:

  • Name: ScaleDrawing

Then, add the following property to it:

  • Name: range
  • Type: NiceScale
  • Scope: Private

Add a new Method to our ScaleDrawing subclass using the following values:

  • Method Name: Constructor

And typing these lines of code in the associated Code Editor:

Super.Constructor
Self.range = New NiceScale(0, 1)

Then, add a second method using the following values:

  • Method Name: Redraw
  • Parameters: minValue As Double, maxValue As Double

And typing these two lines of code in the associated Code Editor:

range.SetMinMaxPoints(minValue, maxValue)

Self.Refresh

After that, let’s do the drawing itself! So we need to add the Paint event to our ScaleDrawing class. When done, add the following lines of code to the associated Code Editor:

g.DrawRectangle(0, 0, g.Width, g.Height)

Var values() As Double = range.GetValues
Var offset As Double = (( g.Width ) / values.LastIndex) - 10
Var x As Double = 20
Var y As Double = g.Height / 2 - 5
Var tx As Double
Var Val As Double

For n As Integer = 0 To values.LastIndex
  Val = values(n)

  g.DrawLine( x, y, x, y + 10 )

  tx = x - g.TextWidth(values(n).ToString) / 2

  g.DrawText(values(n).ToString, tx, y + 10 + g.FontAscent)

  x = x + offset
Next

g.DrawLine(20, y, x - offset, y)

 

Creating the NiceScale Example UI

Select the Window1 window in the Navigator so it is displayed in the Layout Editor. Next drag the ScaleDrawing class from the Navigator and drop it into the Layout Editor so it looks like like this (locking the four locks for ScaleDrawing1 in the Inspector Panel):

Next, add a couple of Labels, two TextFields and a Button below ScaleDrawing1 in the Layout Editor so our final UI looks like this:

Rename Label1 as MinimumLabel, Label2 as MaximumLabel, TextField1 as MinimumTF, TextField2 as MaximumTF, and Button1 as DrawBT. Then, add the Pressed event to DrawBT and type the following code in the associated Code Editor:

ScaleDrawing1.Redraw(MinimumTF.Text.ToDouble, maximumtf.Text.ToDouble)

That’s all!

Running the app!

Everything is in place, so run the app and put some values in the Minimum and Maximum textfields, clicking the button to refresh the drawing. These are some examples you will get for the following range values:

  • By Default:
  • Minimum: 12, Maximum: 112
  • Minimum: -345, Maximum: 835
  • Minimum: -816, Maximum: 25

As you can see, having these classes in your developer bag is a good thing if you need to draw this kind of scale! Use the ScaleDrawing class as a start point for your own needs.

Javier Menendez is an engineer at Xojo and has been using Xojo since 1998. He lives in Castellón, Spain and hosts regular Xojo hangouts en español. Ask Javier questions on Twitter at @XojoES or on the Xojo Forum.