Interfaces gráficas IX: Elementos "Indicador de progreso" y "Gráfico lineal" (Capítulo 2)

Anatoli Kazharski | 26 agosto, 2016


Índice

 

Introducción

El primer artículo de la serie nos cuenta con más detalles para qué sirve esta librería: Interfaces gráficas I: Preparación de la estructura de la librería (Capítulo 1). Al final de cada artículo de la serie se muestra la lista completa de los capítulos con los enlaces. Además, se puede descargar la versión completa de la librería en la fase actual del desarrollo del proyecto. Es necesario colocar los ficheros en los mismos directorios, tal como están ubicados en el archivo.

En el artículo anterior hemos analizado dos controles interralacionados: «Paleta para seleccionar el color» y «Botón para abrir la paleta de colores». El segundo capítulo estará dedicado a dos elementos de la interfaz: hablaremos del «Indicador de progreso» y «Gráfico lineal». Como siempre mostraremos los ejemplos detallados de cómo puede usar estos elementos en sus aplicaciones MQL.

 


Elemento «Indicador de progreso»

Cuando se ejecuta un proceso prolongado en el tiempo, será necesario un elemento de la interfaz que pueda mostrar al usuario en qué fase de ejecución se encuentra, así como ayudar a comprender cuánto tiempo queda aproximadamente hasta su finalización. Normalmente, esta posibilidad se obtiene a través del elemento de la interfaz gráfica «Indicador de progreso». En la implementación más simple, este elemento se compone de dos rectángulos uno de los cuales muestra la duración total del proceso mostrado y el segundo muestra la parte ejecutada de este proceso. En diferentes implementaciones del indicador de progreso, se puede ver con mucha frecuencia el índice porcentual, lo que añade un grado de información aún mayor a este elemento.

Vamos a nombrar todos los componentes que van a formar parte del elemento «Indicador de progreso» en nuestra librería.

  1. Fondo
  2. Descripción
  3. Barra del indicador
  4. Fondo del indicador
  5. Índice porcentual



Fig. 1. Partes integrantes del control “Indicador de progreso”.


A continuación, vamos a ver cómo está organizada la clase para la creación de este elemento.

 


Desarrollo de la clase CProgressBar

Creamos el archivo ProgressBar.mqh con la clase CProgressBar con los métodos estándar para todos los controles, y lo incluimos en el motor de la librería (archivo WndContainer.mqh). Aquí tenemos la lista de las propiedades del elemento que están disponibles para el ajuste por el usuario.

  • Color del fondo general del elemento
  • Texto de la descripción
  • Color del texto de la descripción
  • Desplazamiento de la descripción por dos ejes (x, y)
  • Color del fondo y del borde del área general del indicador
  • Tamaños del fondo del área del indicador
  • Grosor del borde del área del indicador
  • Color de la barra del indicador
  • Desplazamiento de la etiqueta de texto del índice porcentual del proceso en ejecución
  • Número de dígitos después de la coma para el índice porcentual
class CProgressBar : public CElement
  {
private:
   //--- Color del fondo del elemento
   color             m_area_color;
   //--- Descripción del proceso mostrado
   string            m_label_text;
   //--- Color del texto
   color             m_label_color;
   //--- Desplazamiento de la etiqueta de texto por dos ejes
   int               m_label_x_offset;
   int               m_label_y_offset;
   //--- Color del fondo de la barra del progreso y del borde del fondo
   color             m_bar_area_color;
   color             m_bar_border_color;
   //--- Tamaños de la barra de progreso
   int               m_bar_x_size;
   int               m_bar_y_size;
   //--- Desplazamiento de la barra de progreso por dos ejes
   int               m_bar_x_offset;
   int               m_bar_y_offset;
   //--- Grosor del borde de la barra de progreso
   int               m_bar_border_width;
   //--- Color del indicador
   color             m_indicator_color;
   //--- Desplazamiento de la etiqueta del índice de los por cientos
   int               m_percent_x_offset;
   int               m_percent_y_offset;
   //--- Número de dígitos después de la coma
   int               m_digits;
   //---
public:
   //--- Número de dígitos después de la coma
   void              SetDigits(const int digits)        { m_digits=::fabs(digits);         }
   //--- (1) Color del fondo, (2) nombre del proceso y (3) color del texto
   void              AreaColor(const color clr)         { m_area_color=clr;                }
   void              LabelText(const string text)       { m_label_text=text;               }
   void              LabelColor(const color clr)        { m_label_color=clr;               }
   //--- Desplazamiento de la etiqueta de texto (nombre del proceso)
   void              LabelXOffset(const int x_offset)   { m_label_x_offset=x_offset;       }
   void              LabelYOffset(const int y_offset)   { m_label_y_offset=y_offset;       }
   //--- Color del (1) fondo y (2) del borde de proceso, (3) color del indicador
   void              BarAreaColor(const color clr)      { m_bar_area_color=clr;            }
   void              BarBorderColor(const color clr)    { m_bar_border_color=clr;          }
   void              IndicatorColor(const color clr)    { m_indicator_color=clr;           }
   //--- (1) Grosor del borde, (2) tamaños del área del indicador
   void              BarBorderWidth(const int width)    { m_bar_border_width=width;        }
   void              BarXSize(const int x_size)         { m_bar_x_size=x_size;             }
   void              BarYSize(const int y_size)         { m_bar_y_size=y_size;             }
   //--- (1) Desplazamiento de la barra de progreso por dos ejes, (2) desplazamiento de la etiqueta del índice de los por cientos
   void              BarXOffset(const int x_offset)     { m_bar_x_offset=x_offset;         }
   void              BarYOffset(const int y_offset)     { m_bar_y_offset=y_offset;         }
   //--- Desplazamiento de la etiqueta de texto (por ciento del proceso)
   void              PercentXOffset(const int x_offset) { m_percent_x_offset=x_offset;     }
   void              PercentYOffset(const int y_offset) { m_percent_y_offset=y_offset;     }
  };

Para crear el elemento “Indicador de progreso”, vamos a necesitar cinco métodos privados (private) y un método público (public):

class CProgressBar : public CElement
  {
private:
   //--- Objetos para crear el elemento
   CRectLabel        m_area;
   CLabel            m_label;
   CRectLabel        m_bar_bg;
   CRectLabel        m_indicator;
   CLabel            m_percent;
   //---
public:
   //--- Métodos para crear el elemento
   bool              CreateProgressBar(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateArea(void);
   bool              CreateLabel(void);
   bool              CreateBarArea(void);
   bool              CreateIndicator(void);
   bool              CreatePercent(void);
  };

Para que el indicador de progreso trabaje tal como se espera de él, es necesario indicar el número total de pasos (iteraciones) del proceso al que este indicador «está vinculado» y el índice actual de iteraciones. Para eso vamos a necesitar dos métodos adicionales: CProgressBar::StepsTotal() y CProgressBar::CurrentIndex(). Estos métodos están destinados para el uso interno (private). Ambos métodos reciben sólo un argumento cuyo valor se corrige si es necesario, para prevenir la salida fuera del rango permitido. 

class CProgressBar : public CElement
  {
private:
   //--- Número de pasos del rango
   double            m_steps_total;
   //--- Posición actual del indicador
   double            m_current_index;
   //---
private:
   //--- Establecimiento de nuevos valores para el indicador
   void              CurrentIndex(const int index);
   void              StepsTotal(const int total);
  };
//+------------------------------------------------------------------+
//| Número de pasos de la barra de progreso                                   |
//+------------------------------------------------------------------+
void CProgressBar::StepsTotal(const int total)
  {
//--- Corregir si es menos de 0
   m_steps_total=(total<1)? 1 : total;
//--- Corregimos el índice si salimos fuera del rango
   if(m_current_index>m_steps_total)
      m_current_index=m_steps_total;
  }
//+------------------------------------------------------------------+
//| Posición actual del indicador                                       |
//+------------------------------------------------------------------+
void CProgressBar::CurrentIndex(const int index)
  {
//--- Corregir si es menos de 0
   if(index<0)
      m_current_index=1;
//--- Corregimos el índice si salimos fuera del rango
   else
      m_current_index=(index>m_steps_total)? m_steps_total : index;
  }

Los métodos CProgressBar::StepsTotal() y CProgressBar::CurrentIndex() se llaman en el método principal para la interracción con el elemento desde fuera: CProgressBar::Update(). Precisamente a este método se le pasan los parámetros para el indicador de progreso respecto a los cuales se realizan los cálculos necesarios y su redibujo. El primer argumento será el índice de iteración del proceso (index), y el segundo será el número total de iteraciones (total). Una vez comprobados estos valores, se calcula nuevo ancho para la barra del indicador. Luego, si hace falta, estos valores se corrigen. A continuación, (1) se establece nuevo ancho para la barra del indicador, (2) se calcula el valor para el índice porcentual y se forma su cadena, y al final del método (3) se establece nuevo valor para la etiqueta de texto del índice porcentual. 

class CProgressBar : public CElement
  {
public:
   //--- Actualización del indicador según los valores especificados
   void              Update(const int index,const int total);
  };
//+------------------------------------------------------------------+
//| Actualiza la barra de progreso                                           |
//+------------------------------------------------------------------+
void CProgressBar::Update(const int index,const int total)
  {
//--- Establecer nuevo índice
   CurrentIndex(index);
//--- Establecer nuevo rango
   StepsTotal(total);
//--- Calculamos el ancho del indicador
   double new_width=(m_current_index/m_steps_total)*m_bar_bg.XSize();
//--- Corregir si es menos de 1
   if((int)new_width<1)
      new_width=1;
   else
     {
      //--- Corregir tomando en cuenta el ancho del borde
      int x_size=m_bar_bg.XSize()-(m_bar_border_width*2);
      //--- Corregir si salimos fuera del límite
      if((int)new_width>=x_size)
         new_width=x_size;
     }
//--- Establecemos nuevo ancho para el indicador
   m_indicator.X_Size((int)new_width);
//--- Calculamos el por ciento y formamos la cadena
   double percent =m_current_index/m_steps_total*100;
   string desc    =::DoubleToString((percent>100)? 100 : percent,m_digits)+"%";
//--- Establecemos valor nuevo
   m_percent.Description(desc);
  }

Todos los métodos para la creación y el control del indicador están preparados. Ahora vamos a probarlo y veremos cómo se comporta en la interfaz gráfica de la aplicación MQL. 


Prueba del «Indicador de progreso»

Se puede coger el EA del artículo anterior para la prueba y usarlo como plantilla. Eliminamos de ahí todo, salvo el menú principal y la barra de estado. Vamos a añadir a la interfaz gráfica de este EA ocho indicadores de progreso. Para que la tarea sea más interesante, hagamos que el número de iteraciones se controle a través del control «Slider». 

Declaramos en el cuerpo de la clase personalizada CProgram las instancias de los tipos necesarios de elementos y los métodos para su creación con los márgenes desde el punto extremo del formulario:

class CProgram : public CWndEvents
  {
private:
  //--- Sliders
   CSlider           m_slider1;
   //--- Indicadores de progreso
   CProgressBar      m_progress_bar1;
   CProgressBar      m_progress_bar2;
   CProgressBar      m_progress_bar3;
   CProgressBar      m_progress_bar4;
   CProgressBar      m_progress_bar5;
   CProgressBar      m_progress_bar6;
   CProgressBar      m_progress_bar7;
   CProgressBar      m_progress_bar8;
   //---
private:
  //--- Sliders
#define SLIDER1_GAP_X         (7)
#define SLIDER1_GAP_Y         (50)
   bool              CreateSlider1(const string text);
//---
#define PROGRESSBAR1_GAP_X    (7)
#define PROGRESSBAR1_GAP_Y    (100)
   bool              CreateProgressBar1(void);
//---
#define PROGRESSBAR2_GAP_X    (7)
#define PROGRESSBAR2_GAP_Y    (125)
   bool              CreateProgressBar2(void);
//---
#define PROGRESSBAR3_GAP_X    (7)
#define PROGRESSBAR3_GAP_Y    (150)
   bool              CreateProgressBar3(void);
//---
#define PROGRESSBAR4_GAP_X    (7)
#define PROGRESSBAR4_GAP_Y    (175)
   bool              CreateProgressBar4(void);
//---
#define PROGRESSBAR5_GAP_X    (7)
#define PROGRESSBAR5_GAP_Y    (200)
   bool              CreateProgressBar5(void);
//---
#define PROGRESSBAR6_GAP_X    (7)
#define PROGRESSBAR6_GAP_Y    (225)
   bool              CreateProgressBar6(void);
//---
#define PROGRESSBAR7_GAP_X    (7)
#define PROGRESSBAR7_GAP_Y    (250)
   bool              CreateProgressBar7(void);
//---
#define PROGRESSBAR8_GAP_X    (7)
#define PROGRESSBAR8_GAP_Y    (275)
   bool              CreateProgressBar8(void);
  };

Ya hemos considerado la creación de todos los controles en los artículos anteriores, por eso aquí de ejemplo se puede mostrar el código del método sólo de uno de los indicadores de progreso (véase el código de abajo). Como se puede observar, no tiene nada que pueda suponer dificultades para la comprensión. Todo es bastante transparente y claro. 

//+------------------------------------------------------------------+
//| Crea la barra de progreso 1                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateProgressBar1(void)
  {
//--- Guardamos el puntero al formulario
   m_progress_bar1.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+PROGRESSBAR1_GAP_X;
   int y=m_window1.Y()+PROGRESSBAR1_GAP_Y;
//--- Establecemos las propiedades antes de la creación
   m_progress_bar1.XSize(220);
   m_progress_bar1.YSize(15);
   m_progress_bar1.BarXSize(123);
   m_progress_bar1.BarYSize(11);
   m_progress_bar1.BarXOffset(65);
   m_progress_bar1.BarYOffset(2);
   m_progress_bar1.LabelText("Progress 01:");
//--- Creación del elemento
   if(!m_progress_bar1.CreateProgressBar(m_chart_id,m_subwin,x,y))
      return(false);
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,m_progress_bar1);
   return(true);
  }

La llamada a los método debe realizarse en el método principal de la creación de la interfaz gráfica. En el código de abajo se muestra la versión reducida de este método que contiene sólo lo que es necesario añadir ahí: 

//+------------------------------------------------------------------+
//| Crea el panel del EA                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateExpertPanel(void)
  {
//--- Creación del formulario para los controles
//--- Creación de controles:
//    Menú principal
//--- Menús contextuales

//--- Sliders
   if(!CreateSlider1("Iterations total:"))
      return(false);
//--- Indicadores de progreso
   if(!CreateProgressBar1())
      return(false);
   if(!CreateProgressBar2())
      return(false);
   if(!CreateProgressBar3())
      return(false);
   if(!CreateProgressBar4())
      return(false);
   if(!CreateProgressBar5())
      return(false);
   if(!CreateProgressBar6())
      return(false);
   if(!CreateProgressBar7())
      return(false);
   if(!CreateProgressBar8())
      return(false);
      
//--- Redibujar el gráfico
   m_chart.Redraw();
   return(true);
  }

Ahora hablaremos de cómo va a funcionar eso. Imitamos el proceso en el temporizador de la clase personalizada CProgram::OnTimerEvent() para todos los indicadores de progreso. Se puede controlar el número de iteraciones manualmente a través del slider, y en cada evento del temporizador hay posibilidad de obtener el valor actual en su campo de edición. Cada indicador de progreso tendrá su contador estático cuyo valor vamos a aumentar con diferentes valores de incremento. De esta manera, imitaremos que los indicadores de progreso trabajen en diferentes procesos de forma asincrónica. 

//+------------------------------------------------------------------+
//| Temporizador                                                           |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();
//--- Número de iteraciones
   int total=(int)m_slider1.GetValue();
//--- Ocho barras de progreso
   static int count1=0;
   count1=(count1>=total) ? 0 : count1+=8;
   m_progress_bar1.Update(count1,total);
//---
   static int count2=0;
   count2=(count2>=total) ? 0 : count2+=3;
   m_progress_bar2.Update(count2,total);
//---
   static int count3=0;
   count3=(count3>=total) ? 0 : count3+=12;
   m_progress_bar3.Update(count3,total);
//---
   static int count4=0;
   count4=(count4>=total) ? 0 : count4+=6;
   m_progress_bar4.Update(count4,total);
//---
   static int count5=0;
   count5=(count5>=total) ? 0 : count5+=18;
   m_progress_bar5.Update(count5,total);
//---
   static int count6=0;
   count6=(count6>=total) ? 0 : count6+=10;
   m_progress_bar6.Update(count6,total);
//---
   static int count7=0;
   count7=(count7>=total) ? 0 : count7+=1;
   m_progress_bar7.Update(count7,total);
//---
   static int count8=0;
   count8=(count8>=total) ? 0 : count8+=15;
   m_progress_bar8.Update(count8,total);
   
//--- Temporizador para la barra de estado
   static int count9=0;
   if(count9<TIMER_STEP_MSC*10)
     {
      count9+=TIMER_STEP_MSC;
      return;
     }
   count9=0;
   m_status_bar.ValueToItem(1,::TimeToString(::TimeLocal(),TIME_DATE|TIME_SECONDS));
  }

Por favor, compile el programa y cárguelo en el gráfico. El resultado será el siguiente:

 Fig. 2. Prueba del control “Indicador de progreso”.

Fig. 2. Prueba del control “Indicador de progreso”.

¡No está mal! Más abajo, en la aplicación de prueba para el gráfico lineal, probaremos otra vez el indicador de progreso en un ejemplo más concreto.

 


Elemento «Gráfico lineal»

El gráfico lineal permite visualizar los arrays de datos en el área rectangular destinada para ello con la escala vertical y horizontal, donde los puntos se unen consecutivamente con una línea. Es difícil menospreciar la utilidad de esta herramienta. Por ejemplo, se puede crear el gráfico donde se puede mostrar el balance y la equidad en la cuenta del trader. Según mi opinión, no es muy conveniente usar para eso el tipo estándar de los programas del terminal MetaTrader, como «Indicador», puesto que no hay posibilidad de demostrar ahí el array de datos completo en la parte visible de la ventana. 

Para los gráficos del terminal se puede establecer la escala sólo de 0 (el mayor grado de compresión) hasta 5. En el mayor grado de compresión, se supone que a un elemento del array se le asigna un píxel, y si el array completo de datos no cabe en el área asignada, hay posibilidad de usar el desplazamiento del gráfico. La segunda opción es meter más datos: pasar al período de tiempo mayor, y si tenemos puesto el tipo «Velas» o «Barras», por lo menos se puede ver el diapasón entero de valores para el período seleccionado. 

Nos gustaría obtener la posibilidad cuando la escalabilidad del gráfico no se limitaría en nada, y se podría meter el array con datos de cualquier tamaño en el área con el ancho empezando incluso de un píxel. Además, es necesario que los datos siempre se ubiquen justamente desde el principio del área hasta su final. Es decir, el primer punto de datos (elemento del array) debe estar magnetizado hacia el lado izquierdo del área, y el último punto, hacia el lado derecho. Por ejemplo, precisamente con esta calidad se hacen los gráficos en Excel. Ahí se puede meter en el gráfico el array de cualquier tamaño sin perder los mínimos y los máximos.

Como ejemplo, en la captura de pantalla de abajo se muestra el gráfico lineal de Excel, en el que ha sido metido el array de 50000 elementos sin perder los mínimos y los máximos.

 Fig. 3. Gráfico lineal en Excel. El tamaño del array de datos es de 50000 elementos.

Fig. 3. Gráfico lineal en Excel. El tamaño del array de datos es de 50000 elementos.


En la librería estándar, los desarrolladores del terminal ofrecen las clases para crear varios tipos de gráficos. Se ubican en el directorio <carpeta del terminal>\MQLX\Include\Canvas\Charts. Vamos a nombrar estas clases en la lista:

  • CChartCanvas – clase base para la creación de uno de tres tipos de gráficos.
  • CLineChart – clase derivada de CChartCanvas para crear el gráfico lineal.
  • CHistogramChart – clase derivada de CChartCanvas para crear el histograma.
  • CPieChart – clase derivada de CChartCanvas para crear el diagrama circular.

Para que podamos usar ahora mismo los gráficos en las interfaces gráficas de nuestras aplicaciones MQL, como solución intermedia usaremos lo que ofrece la librería estándar. Algunos métodos de estas clases serán modificados, algunos serán sustituidos por completo. Además, añadiremos las posibilidades adicionales. Posteriormente, en el proceso de la escritura de los futuros artículos, intentaremos crear la librería adicional para dibujar los gráficos de mayor calidad.



Mejora de las clases de la biblioteca estándar

Como ya hemos mencionado antes, los archivos con las clases necesarias se encuentran en el directorio: <carpeta del terminal>\MQLX\Include\Canvas\Charts. Vamos a crear en el directorio de nuestra librería (<carpeta del terminal>\MQLX\Include\EasyAndFastGUI\Canvas ) la carpeta Charts y copiaremos ahí los archivos ChartCanvas.mqh y LineChart.mqh desde la librería estándar. 

Primero modificaremos la clase base CChartCanvas. Ahora su clase base es CCanvas. Antes, la copia de la clase CCanvas de la librería estándar ha sido adaptada para nuestra librería recibiendo el nombre CCustomCanvas. Ahora hay que hacerla como la clase base para CChartCanvas. Abajo se muestran las líneas que han sido modificadas en el archivo ChartCanvas.mqh

//+------------------------------------------------------------------+
//|                                                  ChartCanvas.mqh |
//|                   Copyright 2009-2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "..\CustomCanvas.mqh"
...
//+------------------------------------------------------------------+
//| Class CChartCanvas                                               |
//| Usage: base class for graphical charts                           |
//+------------------------------------------------------------------+
class CChartCanvas : public CCustomCanvas
  {
...

Me gustaría que el fondo de los gráficos sea de gradiente. Vamos a añadir esta posibilidad. Para poder implementar esta opción, incluiremos el archivo con la clase para trabajar con el color (CColors) en el archivo CustomCanvas.mqh

//+------------------------------------------------------------------+
//|                                                 CustomCanvas.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "..\Colors.mqh"
...

Ahora se puede declarar la instancia de la clase CChartCanvas en esta clase y usarla en nuestro proyecto.

En esta fase del desarrollo, puesto que se plantea escribir desde cero la librería para crear los gráficos, no hay sentido de mostrar la descripción detallada de cómo están organizados todos los métodos de las clases que vamos a usar ahora. Indicaremos brevemente sólo los métodos que han sido modificados y los que han sido añadidos para que Usted pueda hacer comparación personalmente, en caso de necesidad.

Pues bien, vamos a nombrarlos. Para hacer un fondo de gradiente, hacen falta por lo menos dos colores. En la clase CChartCanvas ya existe el método para especificar el color del fondo: CChartCanvas::ColorBackground(). Añadimos el método parecido CChartCanvas::ColorBackground2() para establecer el segundo color. Necesitamos el método especial para inicializar el array con los colores de gradiente, igual que en la clase CElements (el método InitColorArray(). Para dibujar el gradiente, hay que cambiar el método CChartCanvas::DrawBackground(). Si antes era suficiente llenar el fondo con un color simplemente llamando a CCustomCanvas::Erase() y pasándole el color necesario como argumento, ahora primero hay que (1) declarar el array y establecerle el tamaño igual al alto del lienzo, (2) inicializarlo con los colores de gradiente, (3) dibujar en el ciclo cada línea usando los colores desde este array. Para resetar los arrays y eliminar los objetos, hay que añadir otro método más CChartCanvas::DeleteAll() a la clase. 

Todas las demás modificaciones en la clase CChartCanvas están relacionadas con los métodos donde se calculan y se establecen los márgenes, el fondo, tamaños de los marcadores en la descripción de las series y el estilo de la cuadrícula. Usted puede estudiar más detalladamente el código de todas estas adiciones y modificaciones en los archivos adjuntos al artículo, comparándolas con la versión inicial desde la librería estándar.

A continuación, veremos las modificaciones en la clase CLineChart. Su clase base es CChartCanvas. Aquí también es necesario crear el método para la eliminación de los objetos, CLineChart::DeleteAll(). Aquí las modificaciones principales están relacionadas con el dibujo de datos en el gráfico a través del método CLineChart::DrawData(). En primer lugar, ha recibido tres métodos adionales: CLineChart::CheckLimitWhile(), CLineChart::CalculateVariables() y CLineChart::CalculateArray(), en los que se realizan los cálculos. Y lo más importante es que ha sido corregido el algoritmo de compresión los datos mostrados en el gráfico. En la versión que se propone, la compresión se realiza de tal manera que si el ancho del área asignada del gráfico es superado por el ancho del array de los píxeles mostrados, este array se divide en el número de partes igual al número de píxeles que salen del lado derecho del gráfico. Luego, cada parte (salvo la primera) se solapa sobre la anterior. De esta manera, el saliente del lado derecho se nivela, el primer elemento del array siempre está anclado al lado derecho, así como no se pierden visualmente los datos respecto a los mínimos y los máximos. 

Ahora hay que crear la clase para la creación del elemento «Gráfico lineal» semejante a las clases de otros controles de la librería que han sido considerados en los artículos anteriores de la serie.



Desarrollo de la clase CLineGraph

En primer lugar, hay que crear el nuevo tipo del objeto en el archivo Objects.mqh. Será la clase CLineChartObject parecida a todas las clases en el archivo. Su clase base será CLineChart que es necesario incluir en el archivo Objects.mqh:

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "..\Canvas\Charts\LineChart.mqh"
...

Luego, creamos el archivo LineGraph.mqh con la clase CLineGraph y lo incluimos en el motor de la librería (WndContainer.mqh): La clase CLineGraph debe contener los métodos virtuales estándar, igual que todos los demás controles de la librería:

//+------------------------------------------------------------------+
//| Clase para crear el gráfico lineal                            |
//+------------------------------------------------------------------+
class CLineGraph : public CElement
  {
private:
   //--- Puntero al formulario al que está adjuntado el control
   CWindow          *m_wnd;
public:
   //--- Guarda el puntero del formulario
   void              WindowPointer(CWindow &object)    { m_wnd=::GetPointer(object);  }
   //---
public:
   //--- Manejador de eventos del gráfico
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) {}
   //--- Temporizador
   virtual void      OnEventTimer(void) {}
   //--- Desplazamiento del control
   virtual void      Moving(const int x,const int y);
   //--- (1) Mostrar, (2) ocultar, (3) resetear, (4) eliminar
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
  };

Las propiedades que estarán disponibles para el ajuste de la apariencia del gráfico lineal son las siguientes:

  • Colores del gradiente del fondo
  • Color del borde
  • Color de la cuadrícula
  • Color del texto
  • Número de dígitos después de la coma (para los valores en la escala vertical)
class CLineGraph : public CElement
  {
private:
   //--- Colores del gradiente
   color             m_bg_color;
   color             m_bg_color2;
   //--- Color del borde
   color             m_border_color;
  //--- Color de la cuadrícula
   color             m_grid_color;
   //--- Color del texto
   color             m_text_color;
   //--- Número de dígitos después de la coma
   int               m_digits;
   //---
public:
   //--- Número de dígitos después de la coma
   void              SetDigits(const int digits)       { m_digits=::fabs(digits);     }
   //--- Dos colores para el gradiente
   void              BackgroundColor(const color clr)  { m_bg_color=clr;              }
   void              BackgroundColor2(const color clr) { m_bg_color2=clr;             }
   //--- Colores del (1) borde, (2) cuadrícula (3) y texto
   void              BorderColor(const color clr)      { m_border_color=clr;          }
   void              GridColor(const color clr)        { m_grid_color=clr;            }
   void              TextColor(const color clr)        { m_text_color=clr;            }
  };

Para crear el gráfico lineal, vamos a necesitar un método privado (private) y uno público (public):

class CLineGraph : public CElement
  {
public:
   //--- Métodos para crear el elemento
   bool              CreateLineGraph(const long chart_id,const int subwin,const int x,const int y);
   //---
private:
   bool              CreateGraph(void);
  };

Y por último, para el trabajo con el elemento «Gráfico lineal» después de su creación, vamos a usar los métodos que no permitirán lo siguiente:

  • establecer el número máximo de las series en el gráfico;
  • establecer el mínimo/máximo de la escala vertical y el número de líneas de la cuadrícula;
  • agregar una línea de datos;
  • actualizar una línea de datos;
  • eliminar una línea de datos. 
class CLineGraph : public CElement
  {
public:
   //--- Número máximo de las líneas de datos
   void              MaxData(const int total)          { m_line_chart.MaxData(total); }
   //--- Establecer los parámetros de la escala vertical
   void              VScaleParams(const double max,const double min,const int num_grid);
   //--- Añadir una línea en el gráfico
   void              SeriesAdd(double &data[],const string descr,const color clr);
   //--- Actualizar línea en el gráfico
   void              SeriesUpdate(const uint pos,const double &data[],const string descr,const color clr);
   //--- Eliminar línea del gráfico
   void              SeriesDelete(const uint pos);
  };

Este mínimo es suficiente para poder usar los gráficos lineales en sus aplicaciones MQL. Ahora crearemos la aplicación que permitirá probar cómo funciona todo eso.

 

 

Escribimos la aplicación para la prueba del gráfico lineal

Para la prueba podemos hacer la copia del EA que hemos usado en este artículo para probar el «Indicador de progreso». Eliminamos de ahí todos los controles, salvo la barra de estado. Vamos a aclarar qué controles tenemos que crear en la interfaz gráfica para manejar el gráfico lineal. 

Implementamos el modo en el que va a realizarse la adición y la eliminación automática de datos en los arrays de la serie. Este proceso va a controlarse en el temporizador de la clase personalizada CProgram. Hagamos que la velocidad de este proceso también pueda controlarse indicando en el campo de edición el retardo en milisegundos (parámetro Delay). Introduciremos para este modo dos controles más en cuyos campos de edición podremos especificar el rango, es decir el número mínimo (parámetro Min. limit size) y máximo (parámetro Max. limit size) de elementos en los arrays. Si activamos este modo, los arrays van a aumentarse a un elemento con cada evento del temporizador y después de eso empezarán a reducirse hasta llegar al tamaño mínimo establecido. Después de eso, todo se repite. Además, en el campo de edición del control separado va a mostrarse el tamaño actual de los arrays (parámetro Size of series) que también puede manejarse manualmente.

El número de series (filas de datos) se puede ajustar seleccionando de la lista desplegable de 1 serie hasta 24 (parámetro Number of series). Los datos van a calcularse a través de las fórmulas trigonométricas usando los valores devueltos de las funciones matemáticas del seno y coseno. Añadimos los elementos que permitirán controlar los parámetros que participan en los cálculos. Aquí será el (1) ratio del incremento (parámetro Increment ratio) y (2) desplazamiento de cada serie respecto a la fila anterior de datos (parámetro Offset series). Cambiando el ratio del incremento, se puede obtener diferentes visualizaciones de las series. El parámetro Increment ratio tendrá su casilla de verificación (checkbox) mediante la cual se podrá habilitar el cambio automático al siguiente valor, cuando se finalice el ciclo del incremento/reducción del tamaño de la serie, si este modo está activado. Aquí el incremento del ratio va a realizarse hasta que no lleguemos a la limitación máxima en el propio control. Una vez alcanzada la limitación, el contador dará la vuelta para la reducción de los valores del parámetro Increment ratio. En otras palabras, el contador va a dar la vuelta en cuanto se alcance el valor mínimo o máximo.

Para que el experimento sea aún más interesante, vamos a implementar el modo que permitirá hacer la animación de las series «corrientes», así como añadiremos el parámetro adicional que controle el paso del desplazamiento de las series (Run speed). Aparte de eso, añadiremos a la interfaz gráfica el indicador de progreso. Si esta activado el modo de adición y reducción automática de los datos a los arrays de las series, este indicador va a mostrar qué es lo que queda hasta la finalización de este proceso.

Pues bien. Declaramos las instancias de las clases de los controles necesarios para la creación de la interfaz gráfica del programa, así como los métodos para su creación y los márgenes desde el punto extremo del formulario:

class CProgram : public CWndEvents
  {
private:
   //--- Controles
   CSpinEdit         m_delay_ms;
   CComboBox         m_series_total;
   CCheckBoxEdit     m_increment_ratio;
   CSpinEdit         m_offset_series;
   CSpinEdit         m_min_limit_size;
   CCheckBoxEdit     m_max_limit_size;
   CCheckBoxEdit     m_run_speed;
   CSpinEdit         m_series_size;
   CLineGraph        m_line_chart;
   CProgressBar      m_progress_bar;
   //---
private:
   //--- Controles para manejar el gráfico lineal
#define SPINEDIT1_GAP_X       (7)
#define SPINEDIT1_GAP_Y       (25)
   bool              CreateSpinEditDelay(const string text);
#define COMBOBOX1_GAP_X       (7)
#define COMBOBOX1_GAP_Y       (50)
   bool              CreateComboBoxSeriesTotal(const string text);
#define CHECKBOX_EDIT1_GAP_X  (161)
#define CHECKBOX_EDIT1_GAP_Y  (25)
   bool              CreateCheckBoxEditIncrementRatio(const string text);
#define SPINEDIT2_GAP_X       (161)
#define SPINEDIT2_GAP_Y       (50)
   bool              CreateSpinEditOffsetSeries(const string text);
#define SPINEDIT3_GAP_X       (330)
#define SPINEDIT3_GAP_Y       (25)
   bool              CreateSpinEditMinLimitSize(const string text);
#define CHECKBOX_EDIT2_GAP_X  (330)
#define CHECKBOX_EDIT2_GAP_Y  (50)
   bool              CreateCheckBoxEditMaxLimitSize(const string text);
#define CHECKBOX_EDIT3_GAP_X  (501)
#define CHECKBOX_EDIT3_GAP_Y  (25)
   bool              CreateCheckBoxEditRunSpeed(const string text);
#define SPINEDIT4_GAP_X       (501)
#define SPINEDIT4_GAP_Y       (50)
   bool              CreateSpinEditSeriesSize(const string text);
   //--- Gráfico lineal
#define LINECHART1_GAP_X      (5)
#define LINECHART1_GAP_Y      (75)
   bool              CreateLineChart(void);
   //--- Indicador de progreso
#define PROGRESSBAR1_GAP_X    (5)
#define PROGRESSBAR1_GAP_Y    (364)
   bool              CreateProgressBar(void);
  };

En los artículos anteriores, ya hemos considerado todos los métodos para la creación de los controles en la clase personalizada. Por eso aquí mostraremos sólo el código del método para la creación del gráfico lineal. Pero antes de eso, hay que crear los métodos para el trabajo con los arrays y para el cálculo de datos para ellos. Declaramos la estructura Series con el array para mostrar los datos data[] y con el array auxiliar data_temp[] para los cálculos previos. Además, necesitamos los arrays donde van a almacenarse los colores y descripciones de las series. 

class CProgram : public CWndEvents
  {
private:
   //--- Estructura de las series en el gráfico
   struct Series
     {
      double            data[];      // array de datos a mostrar
      double            data_temp[]; // array auxiliar para los cálculos
     };
   Series            m_series[];

   //--- (1) Nombres y (2) colores de las series
   string            m_series_name[];
   color             m_series_color[];
  };

Usando el método CProgram::ResizeDataArrays() se puede establecer nuevo tamaño para los arrays. El número actual de las series y sus tamaños se obtienen desde los controles: 

class CProgram : public CWndEvents
  {
private:
   //--- Establecer nuevo tamaño para las series
   void              ResizeDataArrays(void);
  };
//+------------------------------------------------------------------+
//| Establecer nuevo tamaño para los arrays                                  |
//+------------------------------------------------------------------+
void CProgram::ResizeDataArrays(void)
  {
   int total          =(int)m_series_total.ButtonText();
   int size_of_series =(int)m_series_size.GetValue();
//---
   for(int s=0; s<total; s++)
     {
      //--- Establecer nuevo tamaño para los arrays
      ::ArrayResize(m_series[s].data,size_of_series);
      ::ArrayResize(m_series[s].data_temp,size_of_series);
     }
  }

Después de establecer el tamaño, hay que inicializar el array para los cálculos con datos nuevos. Para eso aplicamos el método CProgram::InitArrays(). En los cálculos preliminares, vamos a utilizar los parámetros desde los campos de edición de los controles Offset series e Increment ratio. Para el modo de las series «corrientes» vamos a necesitar el contador m_run_speed_counter. Durante la llamada al método CProgram::ShiftLineChartSeries() en el temporizador, este contador va a aumentarse por el valor desde el campo de edición del elemento Run Speed, si el checkbox de este elemento está activado. 

class CProgram : public CWndEvents
  {
private:
   //--- Contador de la velocidad de la serie «corriente»
   double            m_run_speed_counter;

   //--- Inicialización de los arrays auxiliares para los cálculos
   void              InitArrays(void);

   //--- Desplazamiento de la serie del gráfico lineal (gráfico corriente)
   void              ShiftLineChartSeries(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_run_speed_counter(0.0)
  {
//--- ...
  }
//+------------------------------------------------------------------+
//| Desplazamiento de la serie del gráfico lineal                                 |
//+------------------------------------------------------------------+
void CProgram::ShiftLineChartSeries(void)
  {
   if(m_run_speed.CheckButtonState())
      m_run_speed_counter+=m_run_speed.GetValue();
  }
//+------------------------------------------------------------------+
//| Inicialización de los arrays auxiliares para los cálculos              |
//+------------------------------------------------------------------+
void CProgram::InitArrays(void)
  {
   int total=(int)m_series_total.ButtonText();
//---
   for(int s=0; s<total; s++)
     {
      int size_of_series=::ArraySize(m_series[s].data_temp);
      //---
      for(int i=0; i<size_of_series; i++)
        {
         if(i==0)
           {
            if(s>0)
               m_series[s].data_temp[i]=m_series[s-1].data_temp[i]+m_offset_series.GetValue();
            else
               m_series[s].data_temp[i]=m_run_speed_counter;
           }
         else
            m_series[s].data_temp[i]=m_series[s].data_temp[i-1]+(int)m_increment_ratio.GetValue();
        }
     }
  }

Como ejemplo, hagamos el cálculo de las series usando tres fórmulas que se puede eligir en los parámetros externos del EA. Para eso declaramos la enumeración ENUM_FORMULA. Pasaremos también el ajuste de los parámetros del color de la serie a los parámetros externos (véase el código de abajo).  

//--- Enumeración de las funciones
enum ENUM_FORMULA
  {
   FORMULA_1=0, // Formula 1
   FORMULA_2=1, // Formula 2
   FORMULA_3=2  // Formula 3
  };
//--- Parámetros externos
input ENUM_FORMULA Formula        =FORMULA_1;       // Formula
input color        ColorSeries_01 =clrRed;          // Color series 01
input color        ColorSeries_02 =clrDodgerBlue;   // Color series 02
input color        ColorSeries_03 =clrWhite;        // Color series 03
input color        ColorSeries_04 =clrYellow;       // Color series 04
input color        ColorSeries_05 =clrMediumPurple; // Color series 05
input color        ColorSeries_06 =clrMagenta;      // Color series 06

La puesta del tamaño inicial de los arrays de la serie, así como la inicialización de los arrays de las descripciones y del color de las series se realiza en el constructor de la clase CProgram

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_run_speed_counter(0.0)
  {
//--- Establecer el tamaño de los arrays de las series
   int number_of_series=24;
   ::ArrayResize(m_series,number_of_series);
   ::ArrayResize(m_series_name,number_of_series);
   ::ArrayResize(m_series_color,number_of_series);
//--- Inicialización del array de los nombres de las series
   for(int i=0; i<number_of_series; i++)
      m_series_name[i]="Series "+string(i+1);
//--- Inicialización de arrays del color de las series
   m_series_color[0] =m_series_color[6]  =m_series_color[12] =m_series_color[18] =ColorSeries_01;
   m_series_color[1] =m_series_color[7]  =m_series_color[13] =m_series_color[19] =ColorSeries_02;
   m_series_color[2] =m_series_color[8]  =m_series_color[14] =m_series_color[20] =ColorSeries_03;
   m_series_color[3] =m_series_color[9]  =m_series_color[15] =m_series_color[21] =ColorSeries_04;
   m_series_color[4] =m_series_color[10] =m_series_color[16] =m_series_color[22] =ColorSeries_05;
   m_series_color[5] =m_series_color[11] =m_series_color[17] =m_series_color[23] =ColorSeries_06;
  }

Para el cálculo de las series según la fórmula especificada en los parámetros externos, aplicaremos el método CProgram::CalculateSeries(): 

class CProgram : public CWndEvents
  {
private:
   //--- Calcular las series
   void              CalculateSeries(void);
  };
//+------------------------------------------------------------------+
//| Calcula las series                                               |
//+------------------------------------------------------------------+
void CProgram::CalculateSeries(void)
  {
   int total=(int)m_series_total.ButtonText();
//---
   for(int s=0; s<total; s++)
     {
      int size_of_series=::ArraySize(m_series[s].data_temp);
      //---
      for(int i=0; i<size_of_series; i++)
        {
         m_series[s].data_temp[i]+=m_offset_series.GetValue();
         //---
         switch(Formula)
           {
            case FORMULA_1 :
               m_series[s].data[i]=::sin(m_series[s].data_temp[i])-::cos(m_series[s].data_temp[i]);
               break;
            case FORMULA_2 :
               m_series[s].data[i]=::sin(m_series[s].data_temp[i]-::cos(m_series[s].data_temp[i]));
               break;
            case FORMULA_3 :
               m_series[s].data[i]=::sin(m_series[s].data_temp[i]*10)-::cos(m_series[s].data_temp[i]);
               break;
           }

        }
     }
  }

Después de que los datos calculados de las series hayan sido introducidos en los arrays, se puede (1) añadirlos al gráfico usando el método CProgram::AddSeries() o si han sido añadidos antes, se puede, (2) actualizarlos usando el método CProgram::UpdateSeries()

class CProgram : public CWndEvents
  {
private:
   //--- Añadir las series al gráfico
   void              AddSeries(void);
   //--- Actualizar las series en el gráfico
   void              UpdateSeries(void);
  };
//+------------------------------------------------------------------+
//| Calcula y coloca las series en el diagrama                  |
//+------------------------------------------------------------------+
void CProgram::AddSeries(void)
  {
   int total=(int)m_series_total.ButtonText();
   for(int s=0; s<total; s++)
      m_line_chart.SeriesAdd(m_series[s].data,m_series_name[s],m_series_color[s]);
  }
//+------------------------------------------------------------------+
//| Calcula y actualiza las series en el diagrama                      |
//+------------------------------------------------------------------+
void CProgram::UpdateSeries(void)
  {
   int total=(int)m_series_total.ButtonText();
   for(int s=0; s<total; s++)
      m_line_chart.SeriesUpdate(s,m_series[s].data,m_series_name[s],m_series_color[s]);
  }

Inmediatamente después de la creación del gráfico lineal, (1) se establece el tamaño para los arrayas de las series y (2) se realiza la inicialización de los arrays auxiliares. Después de eso, las series (3) se calculan y (4) se añaden al gráfico.

//+------------------------------------------------------------------+
//| Crea el gráfico lineal                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateLineChart(void)
  {
//--- Guardamos el puntero a la ventana
   m_line_chart.WindowPointer(m_window1);
//--- Coordenadas
   int x=m_window1.X()+LINECHART1_GAP_X;
   int y=m_window1.Y()+LINECHART1_GAP_Y;
//--- Establecemos las propiedades antes de la creación
   m_line_chart.XSize(630);
   m_line_chart.YSize(280);
   m_line_chart.BorderColor(clrSilver);
   m_line_chart.VScaleParams(2,-2,4);
   m_line_chart.MaxData(int(m_series_total.ButtonText()));
//--- Creamos el control
   if(!m_line_chart.CreateLineGraph(m_chart_id,m_subwin,x,y))
      return(false);
//--- (1) Establecemos el tamaño para los arrays y (2) los inicializamos
   ResizeDataArrays();
   InitArrays();
//--- (1) Calculamos y (2) añadimos las series al gráfico
   CalculateSeries();
   AddSeries();
//--- Añadimos el puntero al control a la base
   CWndContainer::AddToElementsArray(0,m_line_chart);
   return(true);
  }

Habrá que repetir muy a menudo la misma secuencia de acciones, pero con la actualización de la serie, con la interacción con los controles para el gráfico lineal. Por eso escribiremos el método adicional CProgram::RecalculatingSeries() que facilitará el código debido a la llamada sólo a un método en vez de cuatro: 

class CProgram : public CWndEvents
  {
private:
   //--- Recálculo de las series en el gráfico
   void              RecalculatingSeries(void);
  };
//+------------------------------------------------------------------+
//| Recálculo de las series en el gráfico                                      |
//+------------------------------------------------------------------+
void CProgram::RecalculatingSeries(void)
  {
//--- (1) Establecemos el tamaño para los arrays y (2) los inicializamos
   ResizeDataArrays();
   InitArrays();
//--- (1) Calculamos y (2) actualizamos las series
   CalculateSeries();
   UpdateSeries();
  }

En esta fase del desarrollo, si cargamos la aplicación en el gráfico, veremos el siguiente resultado:

Fig. 4. Prueba del control «Gráfico lineal».

Fig. 4. Prueba del control «Gráfico lineal».

Si el checkbox del parámetro Max. limit size está activado, se inicia el modo del cambio automático de los arrays de las series en el método CProgram::AutoResizeLineChartSeries(). Este algoritmo ya ha sido descrito al detalle al principio de esta sección, por eso para estudiar el código de este método (véase el código de abajo), bastará con comentarios detallados. 

class CProgram : public CWndEvents
  {
private:
   //--- Cambio automático del tamaño de las series del gráfico lineal
   void              AutoResizeLineChartSeries(void);
  };
//+------------------------------------------------------------------+
//| Cambio automático del tamaño de las series del gráfico lineal         |
//+------------------------------------------------------------------+
void CProgram::AutoResizeLineChartSeries(void)
  {
//--- Salir el aumento del array de las series por el temporizador está desactivado
   if(!m_max_limit_size.CheckButtonState())
      return;
//--- Para indicar la dirección del cambio del tamaño de los arrays
   static bool resize_direction=false;
//--- Si llegamos al mínimo del tamaño del array
   if((int)m_series_size.GetValue()<=m_min_limit_size.GetValue())
     {
      //--- Cambiamos la dirección hacia el aumento del array
      resize_direction=false;
      //--- Si hace falta cambiar el valor X
      if(m_increment_ratio.CheckButtonState())
        {
         //--- Para indicar la dirección del contador del ratio del incremento
         static bool increment_ratio_direction=true;
         //--- Si el contador está hacia el aumento
         if(increment_ratio_direction)
           {
            //--- Si hemos llegado a la limitación máxima, cambiamos la dirección del contador a la contraria
            if(m_increment_ratio.GetValue()>=m_increment_ratio.MaxValue()-1)
               increment_ratio_direction=false;
           }
         //--- Si el contador está hacia la reducción
         else
           {
            //--- Si hemos llegado a la limitación mínima, cambiamos la dirección del contador a la contraria
            if(m_increment_ratio.GetValue()<=m_increment_ratio.MinValue()+1)
               increment_ratio_direction=true;
           }
         //--- Obtenemos el valor actual del parámetro "Increment ratio" y lo cambiamos según la dirección especificada
         int increase_value=(int)m_increment_ratio.GetValue();
         m_increment_ratio.ChangeValue((increment_ratio_direction)? ++increase_value : --increase_value);
        }
     }
//--- Cambiamos la dirección hacia la disminución del array si hemos llegado hacia el máximo
   if((int)m_series_size.GetValue()>=m_max_limit_size.GetValue())
      resize_direction=true;

//--- Si el indicador de progreso está activado, mostramos el proceso
   if(m_progress_bar.IsVisible())
     {
      if(!resize_direction)
         m_progress_bar.Update((int)m_series_size.GetValue(),(int)m_max_limit_size.GetValue());
      else
         m_progress_bar.Update(int(m_max_limit_size.GetValue()-m_series_size.GetValue()),(int)m_max_limit_size.GetValue());
     }
//--- Cambiamos el tamaño del array según la dirección
   int size_of_series=(int)m_series_size.GetValue();
   m_series_size.ChangeValue((!resize_direction)? ++size_of_series : --size_of_series);
//--- Establecemos nuevo tamaño par los arrays
   ResizeDataArrays();
  }

Como ya hemos mencionado antes, la animación de los gráficos va a realizarse en el temporizador. Colocamos todas las acciones necesarias en el método CProgram::UpdateLineChartByTimer(). El programa abandona el método si (1) el formulario está plegado, si (2) los modos en lo que hace falta actualizar las series por el temporizador están desactivados. Además de eso, otro obstáculo puede ser el retardo en el campo de eición Delay. Si todas las comprobaciones han sido superadas, se realizan los cálculos necesarios para los modos activados y se actualizan las series en el gráfico lineal.  

class CProgram : public CWndEvents
  {
private:
   //--- Actualización del gráfico lineal por el temporizador
   void              UpdateLineChartByTimer(void);
  };
//+------------------------------------------------------------------+
//| Actualización por el temporizador                                            |
//+------------------------------------------------------------------+
void CProgram::UpdateLineChartByTimer(void)
  {
//--- Salir si el formulario está plegado o se encuentra en el proceso de desplazamiento
   if(m_window1.IsMinimized())
      return;
//--- Salir si el modo de animación está desactivado
   if(!m_max_limit_size.CheckButtonState() && !m_run_speed.CheckButtonState())
      return;
//--- Retardo
   static int count=0;
   if(count<m_delay_ms.GetValue())
     {
      count+=TIMER_STEP_MSC;
      return;
     }
   count=0;
//--- Si la opción «Series corrientes» está activada, vamos a desplazar el primer valor de la serie
   ShiftLineChartSeries();
//--- Si el control del tamaño de los arrays de las series está activado por el temporizador
   AutoResizeLineChartSeries();
//--- Inicializamos los arrays
   InitArrays();
//--- (1) Calculamos y (2) actualizamos las series
   CalculateSeries();
   UpdateSeries();
  }

Ahora veremos brevemente cómo está organizado el manejador de eventos CProgram::OnEvent() de la aplicación. Para la introducción momentánea de los cambios visuales en el gráfico lineal vamos a usar los siguientes controles.

  • Parámetro Number of series (lista combinada (combobox). Cada vez que se elige nuevo valor en este control, el número de series en el gráfico lineal va a cambiarse. Abajo se muestra el bloque del código para el procesamiento de este evento:
...
//--- Evento de la selección del elemento en el combobox
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      //--- Obtenemos nueva cantidad de las series
      m_line_chart.MaxData((int)m_series_total.ButtonText());
      //--- (1) Establecemos el tamaño para los arrays y (2) los inicializamos
      ResizeDataArrays();
      InitArrays();
      //--- (1) Calculamos, (2) añadimos al gráfico y (3) actualizamos las series
      CalculateSeries();
      AddSeries();
      UpdateSeries();
      return;
     }
...

Por ejemplo, por defecto, en el combobox se establece la visualización de seis series (valor 6). Vamos a cambiarlo por 3. El resultado se muestra en la captura de pantalla de abajo:

 Fig. 5. Prueba del cambio del número de las series en el gráfico lineal.

Fig. 5. Prueba del cambio del número de las series en el gráfico lineal.

  • Parámetros Max. limit size (checkbox con campo de edición) y Size of series (campo de edición). Al pinchar en estos controles se genera el evento con el identificador ON_CLICK_LABEL. Si se hace clic en el control Size of series, el valor en el campo de edición se reduce al mínimo. El clic en el control Max. limit size va a pasar el estado de su checkbox al contrario. Del estado del checkbox depende si se muestra o se oculta el indicador de progreso que se muestra cuando se activa el modo del cambio automático de los tamaños de las series en el gráfico lineal. 
...
//--- Evento del clic en la etiqueta de texto del control
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LABEL)
     {
      //--- Si es el mensaje del control 'Size of series'
      if(sparam==m_series_size.LabelText())
        {
          //--- Recálculo de las series en el gráfico
         RecalculatingSeries();
         return;
        }
      //--- Si es el mensaje del control 'Max. Limit Size'
      if(sparam==m_max_limit_size.LabelText())
        {
         //--- Mostrar u ocultar el indicador de progreso dependiendo del estado del checkbox del control 'Max. limit size'
         if(m_max_limit_size.CheckButtonState())
            m_progress_bar.Show();
         else
            m_progress_bar.Hide();
         //---
         return;
        }
     }
...

En la captura de pantalla de abajo se muestra cómo se ve eso en proceso:

 Fig. 6. Prueba del gráfico lineal en modo del cambio automático de los tamaños de las series.

Fig. 6. Prueba del gráfico lineal en modo del cambio automático de los tamaños de las series.

  • Al introducir los valores en los campos de edición de los controles Increment ratio, Offset series y Size of series se invoca el método CProgram::RecalculatingSeries() para el recálculo de las series (véase el código de abajo). 
...
//--- Evento de introducción del valor nuevo en el campo de edición
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      //--- Si es el mensaje del control 'Increment ratio' o 'Offset series' o 'Size of series'
      if(sparam==m_increment_ratio.LabelText() ||
         sparam==m_offset_series.LabelText() ||
         sparam==m_series_size.LabelText())
        {
          //--- Recálculo de las series en el gráfico
         RecalculatingSeries();
         return;
        }
      return;
     }
//--- Eventos del clic en los botones conmutadores del campo de edición
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_INC || id==CHARTEVENT_CUSTOM+ON_CLICK_DEC)
     {
      //--- Si es el mensaje del control 'Increment ratio' o 'Offset series' o 'Size of series'
      if(sparam==m_increment_ratio.LabelText() ||
         sparam==m_offset_series.LabelText() ||
         sparam==m_series_size.LabelText())
        {
          //--- Recálculo de las series en el gráfico
         RecalculatingSeries();
         return;
        }
      return;
     }
...

En la captura de pantalla de abajo se muestra otro ejemplo. Intente establecer los mismos parámetros en su copia para ver cómo funciona eso en el modo de animación.

 Fig. 7. Prueba del modo «Series corrientes».

Fig. 7. Prueba del modo «Series corrientes».

Una vez introducidos los cambios en las copias de las clases de la librería estándar, los arrays multielementos caben correctamente en el área de las series del gráfico lineal. En la captura de pantalla de abajo se muestra el ejemplo cuando el tamaño de las series es igual a mil elementos (véase el parámetro Size of series).

 Fig. 8. Prueba de la serie con una gran cantidad de datos.

Fig. 8. Prueba de la serie con una gran cantidad de datos.

La aplicación para las pruebas está hecha. Usted puede descargar el archivo de este EA al final del artículo y probar todo por sí mismo. 

 


Conclusión

En este artículo han sido demostradas las clases del código para la creación de los controles de la interfaz como el «Indicador de progreso» y «Gráfico lineal». 

En esta fase del desarrollo de la librería para la creación de las interfaces gráficas, su esquema general tiene el siguiente aspecto. Algunos fragmentos de este esquema representan una solución temporal, y según vaya desarrollando la librería, en el esquema van a introducirse ciertos cambios.

 Fig. 9. Estructura de la librería en la fase actual del desarrollo.

Fig. 9. Estructura de la librería en la fase actual del desarrollo.

Con eso hemos terminado la parte principal de la serie de artículos sobre el desarrollo de la librería Easy And Fast para la creación de las interfaces gráficas en el entorno de los terminales de trading MetaTrader. En los siguientes artículos se explicará cómo trabajar con esta librería. Se mostrarán distintos ejemplos, adiciones y actualizaciones. Cualquiera que desee puede unirse al desarrollo de este proyecto. Si le ha gustado el resultado, por favor, pruebe la librería en sus proyectos, avise sobre los errores encontrados y haga sus preguntas.

Más abajo puede descargar el material de la novena parte de la serie para poder probar cómo funciona todo eso. Si le surgen algunas preguntas sobre el uso del material de estos archivos, puede dirigirse a la descripción detallada del proceso de desarrollo de la librería en uno de los artículos listados más abajo, o bien hacer su pregunta en los comentarios para el artículo.

Lista de artículos (capítulos) de la novena parte: