//+------------------------------------------------------------------+ //| Vertical.mqh | //| Copyright 2025, Niquel Mendoza. | //| https://www.mql5.com/es/users/nique_372/news | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Niquel Mendoza." #property link "https://www.mql5.com/es/users/nique_372/news" #property strict #include "HistogramaBases.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #define HISTOGRAM_CALCULE_ANCHO(v,m) int((v)*(m)) //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CHistogramVertical : public CHistogram { private: void RecalculeCordinatesBarsAndText() override final; void OnNeweCordinatesBarsAndSize() override final; void OnBarraAdd(int index) override final; void OnBarrDelete(double extra_value) override final; void OnSetLienzo() override final; void OnMaxOrMinValueSuperate() override final; void InitCordinates() override final; void ConjuntoBarWidth(int index, double new_value) override final; public: CHistogramVertical() { m_type = HISTOGRAM_VERTICAL; } int AddConjuntoBar(const string &label, uint label_clr, int label_fontsize, const string &label_font, ENUM_HISTOGRAM_TEXT_MODE label_mode, int barras_de_reserva_, int eje_gap, ENUM_HIST_DRAW_RECT_STYLE initial_style, double width = 0.000000) override final; using CHistogram::AddConjuntoBar; // Desocultamos la funcion soibrecatgada de AddCounjtoBar con clr aleatorio void MinConjuntoAncho(int new_value) override; void CreateEjeLine(int mode_position_line_sections, uint clr_line_Sections, uint clr_line_fixed, int initial_sections, int section_gap_value, int sections_size_px, int8_t section_decimals, int section_fontsize = 0, string section_font = NULL) override; void NumConjuntoBars(int num_bars, int reserve_bars = 0) override; using CHistogram::NumConjuntoBars; // Descoultamos el meotodo getter }; //+------------------------------------------------------------------+ //| Resize al numero de barras | //+------------------------------------------------------------------+ // Unicamente resize el tamaño de berras // Para poder setear las barras use SetBar con redraw false (todavia no se le asigna cordenadas) // O tambien SetBarFast void CHistogramVertical::NumConjuntoBars(int num_bars, int reserve_bars = 0) { m_bars_size = num_bars; ArrayResize(m_bars, m_bars_size); for(int i = 0; i < m_bars_size; i++) { m_bars[i] = new CHistogramConjuntoVertical(); m_bars[i].ReserveBarras(reserve_bars); m_bars[i].CleanColor(m_back_color); } m_curr_index_bar_p = 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CHistogramVertical::CreateEjeLine(int mode_position_line_sections, uint clr_line_Sections, uint clr_line_fixed, int initial_sections, int section_gap_value, int sections_size_px, int8_t section_decimals, int section_fontsize = 0, string section_font = NULL) { // Apunta a algo, es seguro comprar con NULL, dado que al eliminar o iniciar el puntero tendra un valor de NULL (no hay objeto invalido, pero direccion seigue apuntado a algo) if(m_eje != NULL) return; m_eje = new CHistogramEjeVertical(); int x2 = m_barras_x1 + m_barras_espacio_dibujable_width; int y2 = m_barras_y1 + m_barras_espacio_dibujable_height; m_eje.Init(m_canvas, m_barras_x1, x2, m_barras_y1, y2, mode_position_line_sections, clr_line_Sections, clr_line_fixed, m_back_color); const bool use_change_val = (m_fr_change_value == m_fr_min_value) ? false : true; m_eje.HistogramLinePointerGet().CreateSections(initial_sections, m_fr_max_value, m_fr_min_value, use_change_val, m_fr_change_value, section_gap_value, sections_size_px, section_decimals, section_fontsize, section_font); // Solo crea seccion el titulo lo hace el usuario si es que desea } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CHistogramVertical::MinConjuntoAncho(int new_value) { //--- m_barras_min_width = new_value; //--- Verificamos si las barras cumple, si no la marcamos para su posterior eliminacion int idx_to_Remove[]; double extra = 0.00; for(int i = 0; i < m_bars_size; i++) { m_bars[i].CleanBarras(false); m_bars[i].TextClean(); const double v = m_bars[i].ValueForAncho(); // Calcular con max_height (vertical) const int a = HISTOGRAM_CALCULE_ANCHO(v, m_barras_max_height); if(a < m_barras_min_width) { extra += v; idx_to_Remove.Push(i); } } //--- Eliminar conjuntos muy pequeños const int size_remove = ArraySize(idx_to_Remove); if(size_remove > 0) { for(int i = 0; i < size_remove; i++) { const int k = idx_to_Remove[i]; m_bars[k].Destroy(); delete m_bars[k]; } RemoveMultipleIndexes(m_bars, idx_to_Remove, 0); m_bars_size -= size_remove; } else { return; // Todas las barras cumple, nada que hacer } //--- Redistribuir espacio extra const double rest = extra / double(m_bars_size); // Iterar en Y (vertical) int y = m_barras_cordinate_init; // gap_corte es distancia entre x1 y condernada de corte const int gap_corte = m_barras_cordenanda_eje_corte - m_barras_x1; for(int i = 0; i < m_bars_size; i++) { m_bars[i].ValueForAnchoAument(rest); // Calcular alto con max_height const int a = HISTOGRAM_CALCULE_ANCHO(m_bars[i].ValueForAncho(), m_barras_max_height); // Parámetros correctos para vertical m_bars[i].SetNewsCordenadasAndSize(m_barras_x1, y, a); // Avanzar en Y y += a + m_barras_espacio_entre; } } //+------------------------------------------------------------------+ //| Añade un nuevo conjunto de barras | //+------------------------------------------------------------------+ int CHistogramVertical::AddConjuntoBar(const string &label, uint label_clr, int label_fontsize, const string &label_font, ENUM_HISTOGRAM_TEXT_MODE label_mode, int barras_de_reserva_, int eje_gap, ENUM_HIST_DRAW_RECT_STYLE initial_style, double width = 0.000000) { m_curr_index_bar_p = m_bars_size; ArrayResize(m_bars, m_bars_size + 1); m_bars[m_bars_size] = new CHistogramConjuntoVertical(); // Cambiar esto, talvez com template.. <> m_bars[m_bars_size].ReserveBarras(barras_de_reserva_); m_bars[m_bars_size].Set(m_canvas, label, label_clr, label_mode, label_fontsize, label_font, eje_gap, initial_style); m_bars[m_bars_size].ValueForAncho(width); m_bars[m_bars_size].CleanColor(m_back_color); // Color de limpeiza que sea el fondo del lienzo m_bars_size++; return m_curr_index_bar_p; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CHistogramVertical::InitCordinates(void) { // Posición Y inicial (ya incluye gap_init) int y = m_barras_cordinate_init; // gap_corte es distancia entre x1 y condernada de corte const int gap_corte = m_barras_cordenanda_eje_corte - m_barras_x1; // Iterar sobre cada conjunto de barras for(int i = 0; i < m_bars_size; i++) { // Calcular alto del conjunto basado en su proporción // ValueForAncho() retorna proporción (0.0-1.0) // m_barras_max_height es el espacio vertical total disponible const int a = HISTOGRAM_CALCULE_ANCHO(m_bars[i].ValueForAncho(), m_barras_max_height); // Inicializar conjunto con sus coordenadas m_bars[i].Init( m_function_value_to_pixel, // Función conversión valor→pixel m_barras_x1, // X1: borde izquierdo del área y, // Y: posición vertical actual gap_corte, //Gap del corte (pos↔neg) a, // Alto del conjunto en pixeles m_factor_conversion_value_to_pixel_pos, // Factor para valores positivos m_factor_conversion_value_to_pixel_neg, // Factor para valores negativos m_fr_change_value // Valor donde ocurre el corte ); // Avanzar a la siguiente posición Y // = posición actual + alto del conjunto + espacio entre conjuntos y += a + m_barras_espacio_entre; } } //+------------------------------------------------------------------+ //| Configura el lienzo (VERTICAL) | //+------------------------------------------------------------------+ void CHistogramVertical::OnSetLienzo(void) { // En vertical, los conjuntos se distribuyen en ALTURA (eje Y) m_barras_max_height = m_barras_espacio_dibujable_height; m_barras_max_height -= (m_bars_size - 1) * m_barras_espacio_entre; // Restamos gaps entre conjuntos m_barras_max_height -= (m_barras_gap_init + m_barras_gap_end); // Restamos gaps inicial y final // Las barras dentro de cada conjunto crecen en ANCHO (eje X) m_barras_max_width = m_barras_espacio_dibujable_width; //--- Calcular proporciones default para conjuntos sin ancho especificado int idx_no_tiene[]; double sum = 0.00; for(int i = 0; i < m_bars_size; i++) { const double v = m_bars[i].ValueForAncho(); if(v == 0.00) idx_no_tiene.Push(i); else sum += v; } //--- Tamaño del array const int s = ArraySize(idx_no_tiene); //--- No hay defaults, todos tienen proporción definida if(s < 1) return; //--- Calcular y asignar proporción default const double def_factor = (1.0 - sum) / double(s); for(int i = 0; i < s; i++) m_bars[idx_no_tiene[i]].ValueForAncho(def_factor); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CHistogramVertical::RecalculeCordinatesBarsAndText(void) { for(int i = 0; i < m_bars_size; i++) m_bars[i].RecalculeCordinates(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CHistogramVertical::OnMaxOrMinValueSuperate(void) { // gap_corte es distancia entre x1 y condernada de corte const int gap_corte = m_barras_cordenanda_eje_corte - m_barras_x1; for(int i = 0; i < m_bars_size; i++) { m_bars[i].SetNewsValues(gap_corte, m_factor_conversion_value_to_pixel_pos, m_factor_conversion_value_to_pixel_neg, m_fr_change_value); } } //+------------------------------------------------------------------+ //| Recalcula coordenadas y tamaños (VERTICAL) | //+------------------------------------------------------------------+ void CHistogramVertical::OnNeweCordinatesBarsAndSize(void) { // Iterar en Y (vertical), no en X int y = m_barras_cordinate_init; for(int i = 0; i < m_bars_size; i++) { // Calcular ALTO usando max_height const int a = HISTOGRAM_CALCULE_ANCHO(m_bars[i].ValueForAncho(), m_barras_max_height); // Parámetros correctos (x_fijo, y_variable, alto) m_bars[i].SetNewsCordenadasAndSize(m_barras_x1, y, a); // Avanzar en Y y += a + m_barras_espacio_entre; } } //+------------------------------------------------------------------+ //| Se ejecuta al añadir un conjunto (VERTICAL) | //+------------------------------------------------------------------+ void CHistogramVertical::OnBarraAdd(int index) { // Restar espacio de HEIGHT (no width) m_barras_max_height -= m_barras_espacio_entre; double extra = m_bars[index].ValueForAncho(); if(extra == 0.00) { extra = 1.0 / double(m_bars_size); } double rest = extra / double(m_bars_size - 1); //--- Reducir proporción de otros conjuntos int idx_to_Remove[]; extra = 0.00; for(int i = 0; i < m_bars_size; i++) { m_bars[i].CleanBarras(false); m_bars[i].TextClean(); if(i == index) continue; m_bars[i].ValueForAnchoReduce(rest); const double v = m_bars[i].ValueForAncho(); // Calcular con max_height const int a = HISTOGRAM_CALCULE_ANCHO(v, m_barras_max_height); if(a < m_barras_min_width) { extra += v; idx_to_Remove.Push(i); } } //--- Eliminar conjuntos muy pequeños const int size_remove = ArraySize(idx_to_Remove); if(size_remove > 0) { for(int i = 0; i < size_remove; i++) { const int k = idx_to_Remove[i]; m_bars[k].Destroy(); delete m_bars[k]; } RemoveMultipleIndexes(m_bars, idx_to_Remove, 0); m_bars_size -= size_remove; } //--- Redistribuir espacio extra rest = extra / double(m_bars_size); // Iterar en Y int y = m_barras_cordinate_init; // gap_corte es X absoluto const int gap_corte = m_barras_cordenanda_eje_corte - m_barras_x1; for(int i = 0; i < m_bars_size; i++) { m_bars[i].ValueForAnchoAument(rest); // Calcular alto con max_height const int a = HISTOGRAM_CALCULE_ANCHO(m_bars[i].ValueForAncho(), m_barras_max_height); if(i == index) { // Init con parámetros correctos m_bars[i].Init(m_function_value_to_pixel, m_barras_x1, // X fijo y, // Y variable gap_corte, // X de corte a, // Alto m_factor_conversion_value_to_pixel_pos, m_factor_conversion_value_to_pixel_neg, m_fr_change_value); } else { // SetNewsCordenadasAndSize con parámetros correctos m_bars[i].SetNewsCordenadasAndSize(m_barras_x1, y, a); } // Avanzar en Y y += a + m_barras_espacio_entre; } } //+------------------------------------------------------------------+ //| Maneja eliminación de conjunto (VERTICAL) | //+------------------------------------------------------------------+ void CHistogramVertical::OnBarrDelete(double extra_value) { // La limpieza ya se hace en la clase base // Distribuir el espacio liberado entre conjuntos restantes const double rest = extra_value / double(m_bars_size); // Iterar en Y (vertical), no en X int y = m_barras_cordinate_init; // Posición Y inicial // gap_corte es distancia entre x1 y condernada de corte const int gap_corte = m_barras_cordenanda_eje_corte - m_barras_x1; for(int i = 0; i < m_bars_size; i++) { // Aumentar proporción del conjunto m_bars[i].ValueForAnchoAument(rest); // Calcular ALTO (no ancho) usando max_height const int a = HISTOGRAM_CALCULE_ANCHO(m_bars[i].ValueForAncho(), m_barras_max_height); // SetNewsCordenadasAndSize con parámetros correctos // Para vertical: (x_fijo, y_variable, alto) m_bars[i].SetNewsCordenadasAndSize(m_barras_x1, y, a); // Avanzar en Y (no en X) y += a + m_barras_espacio_entre; } } //+------------------------------------------------------------------+ //| Cambia el ancho/alto de un conjunto (VERTICAL) | //+------------------------------------------------------------------+ void CHistogramVertical::ConjuntoBarWidth(int index, double new_value) { double extra = new_value - m_bars[index].ValueForAncho(); m_bars[index].ValueForAncho(new_value); if(m_bars_size <= 1) { LogWarning("No se puede cambiar ancho con solo 1 grupo", FUNCION_ACTUAL); return; } //--- Si extra > 0: Se aumentó el tamaño del conjunto 'index' if(extra > 0.00) { double rest = extra / double(m_bars_size - 1); //--- Reducir proporción de los demás conjuntos int idx_to_Remove[]; extra = 0.00; for(int i = 0; i < m_bars_size; i++) { m_bars[i].CleanBarras(false); m_bars[i].TextClean(); if(i != index) m_bars[i].ValueForAnchoReduce(rest); const double v = m_bars[i].ValueForAncho(); // Calcular con max_height (vertical) const int a = HISTOGRAM_CALCULE_ANCHO(v, m_barras_max_height); if(a < m_barras_min_width) { extra += v; idx_to_Remove.Push(i); } } //--- Eliminar conjuntos muy pequeños const int size_remove = ArraySize(idx_to_Remove); if(size_remove > 0) { for(int i = 0; i < size_remove; i++) { const int k = idx_to_Remove[i]; m_bars[k].Destroy(); delete m_bars[k]; } RemoveMultipleIndexes(m_bars, idx_to_Remove, 0); m_bars_size -= size_remove; } //--- Redistribuir espacio extra rest = extra / double(m_bars_size); // Iterar en Y (vertical) int y = m_barras_cordinate_init; // gap_corte es distancia entre x1 y condernada de corte const int gap_corte = m_barras_cordenanda_eje_corte - m_barras_x1; for(int i = 0; i < m_bars_size; i++) { m_bars[i].ValueForAnchoAument(rest); // Calcular alto con max_height const int a = HISTOGRAM_CALCULE_ANCHO(m_bars[i].ValueForAncho(), m_barras_max_height); // Parámetros correctos para vertical m_bars[i].SetNewsCordenadasAndSize(m_barras_x1, y, a); // Avanzar en Y y += a + m_barras_espacio_entre; } } else { //--- Si extra < 0: Se redujo el tamaño del conjunto 'index' extra *= -1.0; const double rest = extra / double(m_bars_size - 1); // Iterar en Y int y = m_barras_cordinate_init; // gap_corte es distancia entre x1 y condernada de corte const int gap_corte = m_barras_cordenanda_eje_corte - m_barras_x1; //--- Limpiar todos los conjuntos for(int i = 0; i < m_bars_size; i++) { m_bars[i].CleanBarras(false); m_bars[i].TextClean(); } //--- Redibujar con nuevas proporciones for(int i = 0; i < m_bars_size; i++) { if(i != index) m_bars[i].ValueForAnchoAument(rest); // Calcular alto con max_height const int a = HISTOGRAM_CALCULE_ANCHO(m_bars[i].ValueForAncho(), m_barras_max_height); // Parámetros correctos m_bars[i].SetNewsCordenadasAndSize(m_barras_x1, y, a); // Avanzar en Y y += a + m_barras_espacio_entre; } } } //+------------------------------------------------------------------+ #undef HISTOGRAM_CALCULE_ANCHO //+------------------------------------------------------------------+