Algoritmo: rangos con intervalos regulares

Existen algunas situaciones en las que es probable que necesites un rango de números con intervalos regulares a partir de un valor mínimo y máximo, así como una cantidad máxima de intervalos o “marcas” (por ejemplo 10). Por ejemplo, esto resulta útil si estás diseñando un control de UI que muestre marcas para los ejes de una gráfica en la que se muestren valores cartesianos. Continúa leyendo para ver una técnica que podrías utilizar para ello.

Esta técnica está basada en el algoritmo NiceLabels que puedes encontrar en StackOverflow portado a varios lenguajes de programación; así que… ¿por qué no también a Xojo? De hecho ha sido pan comido convertirlo desde JavaScript a Xojo gracias a las similitudes entre ambos lenguajes de programación.

Por supuesto, si simplemente quieres usar la clase sin más, puedes descargar el proyecto de ejemplo desde este enlace.

Comencemos creando la clase propiamente dicha con el nombre NiceScale y todas las propiedades requeridas; por tanto, añade una nueva Clase a tu proyecto Xojo. Luego, con la clase seleccionada en el Navegador,. añade las siguientes propiedades:

  • MaxPoint As Double, definiendo el Scope a Protected
  • MinPoint As Double, definiendo el Scope a Protected
  • Spacing As Double, definiendo el Scope a Public
  • NiceMaximum As Double, definiendo el Scope a Public
  • NiceMinimum As Double, definiendo el Scope a Public

A continuación, es el momento de añadir los método comenzando por el Constructor.

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

Y escribiendo el siguiente fragmento de código en el Editor de Código asociado:

If Min = Max Then Max = Min + 1

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

Como puedes ver, el Constructor llama al método Calculate; de modo que vamos a añadirlo:

  • Method Name: Calculate
  • Scope: Protected

Escribiendo el siguiente código en el Editor de código asociado:

Var range As Double

range = niceNum(MaxPoint - MinPoint, False)

Spacing = niceNum(range / 9, True)

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

Nuevamente, este método está llamando al método NiceNum… de modo que añadamos este también a la clase NiceScale:

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

Escribiendo el siguiente código en el Editor de Código asociado:

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)

Por último, añadamos otro par de métodos a la clase: GetValues y SetminMaxPoints. El primero nos proporcionará un array con los valores de tipo doble ya convenientemente calculados, mientras que el segundo nos permitirá ajustar un nuevo par de valores mínimo y máximo sin necesidad de tener que crear una nueva instancia de la clase:

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

Escribe a continuación el siguiente código en el Editor de Código asociado:

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

Y teclea estas líneas de código en el Editor de Código asociado:

If Min = Max Then Max = Min + 1

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

NiceScale en la Práctica

Vamos a crear ahora la interfaz de usuario para un proyecto Desktop, de modo que podamos ver en funcionamiento la clase NiceScale; y dado que vamos a necesitar algo de dibujado adicional, arrastra un Canvas desde la Librería al Navegador del proyecto. Esto creará una subclase del Canvas. Con la subclase de Canvas recién añadida seleccionada en el Navegador, utiliza el Panel Inspector asociado para definir los siguientes valores:

  • Name: ScaleDrawing

Luego, añádele la siguiente propiedad:

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

Y un nuevo método a nuestra subclase ScaleDrawing usando los siguientes valores:

  • Method Name: Constructor

Escribiendo a continuación estas líneas de código en el Editor de Código asociado:

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

Añade luego un segundo método usando los siguientes valores:

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

Y escribiendo a continuación estas líneas de código en el Editor de Código asociado:

range.SetMinMaxPoints( minValue, maxValue )

Self.Refresh

Después de esto, es momento de dibujar algo en la superficie del canvas. Para ello necesitamos añadir el evento Paint a nuestra subclase ScaleDrawing. Cuando lo hayas hecho, escribe el siguiente código en el Editor de Código asociado:

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 )

Creando la UI del Ejemplo NiceScale

Selecciona la ventana Window1 en el Navegador de modo que se muestre en el Editor de Diseño. A continuación, arrastra la clase ScaleDrawing desde el Navegador y suéltala sobre el Editor de Diseño de modo que tenga un aspecto similar al siguiente (cierra los cuatro candados para ScaleDrawing1 en el Panel Inspector):

A continuación, añade un par de Label, un par de TextField y un Button bajo ScaleDrawing1, de modo que el diseño sea similar al mostrado en la siguiente imagen:

Renombra Label1 como MinimumLabel, Label2 como MaximumLabel, TextField1 como MinimumTF, TextField2 como MaximumTF, y Button1 como DrawBT. Luego, añade el evento Pressed a DrawBT y escribe el siguiente código en el Editor de Código asociado:

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

¡Eso es todo!

Ejecutando la App

Ya está todo listo, de modo que puedes ejecutar la app y escribir algunos valores en los campos de texto Minimum y Maximum, haciendo clic a continuación en el botón para refrescar el dibujado. Estos son algunos ejemplos de lo que obtendrás con los siguientes rangos de valores:

  • Por defecto:

  • Mínimo: 12, Máximo: 112

  • Mínimo: -345, Máximo: 835

  • Mínimo: -816, Máximo: 25

Como puedes ver, es bueno contar con estas clases en tu bolsa de desarrollador en el caso de que necesites dibujar este tipo de escalas. Por supuesto, probablemente querrás utilizar la clase ScaleDrawing como punto de partida para que se adapte mejor a tus necesidades.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *