//+------------------------------------------------------------------+
//|                                             ColoredVolumesEn.mq5 |
//|                                                          Roman.  |
//|                                           romankiverin@yandex.ru |
//+------------------------------------------------------------------+
#property copyright   "Roman."
#property link        "romankiverin@yandex.ru"
#property version     "1.00"
#property description "If you like the indicator, you can thank the author"
#property description "Webmoney: Z330494414179, R222585888867"
#property description " "
#property description "Thank you very much."
#property indicator_chart_window
#property indicator_plots 0

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum ENUM_VOL_TYPE
  {
   VOL_TICK = 0, // Tick Volume
   VOL_REAL = 1  // Real Volume
  };

//+------------------------------------------------------------------+
//| Custom indicator input parameters                                |
//+------------------------------------------------------------------+
input bool              InpAutoAroundLine = false;         // Auto Draw Line Around Volume
input int               InpValueColor     = 4;             // Level Draw Line Around Volume (pixels)
input bool              InpAroundLine     = true;          // Draw Line Around Volume

input color             InpLo0            = C'0,139,139';  // Up Candle Color Top Gradient
input color             InpHi0            = clrWhiteSmoke; // Up Candle Color Bottom Gradient
input color             InpLn0            = clrLime;       // Up Candle Color Around Line
input int               InpOp0            = 160;           // Up Candle Start Opaque (0..255)
input int               InpOP0            = 48;            // Up Candle End Opaque (0..255)

input color             InpLo1            = C'236,0,130';  // Down Candle Color Top Gradient
input color             InpHi1            = clrWhiteSmoke; // Down Candle Color Bottom Gradient
input color             InpLn1            = clrLightCoral; // Down Candle Color Around Line
input int               InpOp1            = 144;           // Down Candle Start Opaque (0..255)
input int               InpOP1            = 64;            // Down Candle End Opaque (0..255)

input double            InpPersent        = 15.0;          // % of Screen
input ENUM_VOL_TYPE     InpVolumeType     = VOL_TICK;      // Applied Volume

//+------------------------------------------------------------------+
//| Internal structures and variables                                |
//+------------------------------------------------------------------+
struct Gradient
  {
   uint              R[2], G[2], B[2], O[2], Line;
  };

Gradient Storage[2];

int      Y               = 0;
int      X               = 0;
int      Scale           = 0;
double   g_volumeScale   = 0.0;
bool     g_initialized   = false;
long     g_chart_id      = 0;
string   g_prefix        = "";
int      g_max_drawn_bar = 0;

//+------------------------------------------------------------------+
//| Helper: Get chart first visible bar                              |
//+------------------------------------------------------------------+
int ChartFirstVisibleBar(const long chart_ID = 0)
  {
   long result = -1;
   ResetLastError();
   if(!ChartGetInteger(chart_ID, CHART_FIRST_VISIBLE_BAR, 0, result))
      Print(__FUNCTION__, ", Error Code = ", GetLastError());
   return((int)result);
  }

//+------------------------------------------------------------------+
//| Helper: Get chart visible bars count                             |
//+------------------------------------------------------------------+
int ChartVisibleBars(const long chart_ID = 0)
  {
   long result = -1;
   ResetLastError();
   if(!ChartGetInteger(chart_ID, CHART_VISIBLE_BARS, 0, result))
      Print(__FUNCTION__, ", Error Code = ", GetLastError());
   return((int)result);
  }

//+------------------------------------------------------------------+
//| Helper: Get chart scale                                          |
//+------------------------------------------------------------------+
int ChartScaleGet(const long chart_ID = 0)
  {
   long result = -1;
   ResetLastError();
   if(!ChartGetInteger(chart_ID, CHART_SCALE, 0, result))
      Print(__FUNCTION__, ", Error Code = ", GetLastError());
   return((int)result);
  }

//+------------------------------------------------------------------+
//| Update dimensions                                                |
//+------------------------------------------------------------------+
void UpdateDimensions()
  {
   Y = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
   X = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
  }

//+------------------------------------------------------------------+
//| Update Scale value based on chart zoom                           |
//+------------------------------------------------------------------+
void UpdateScale()
  {
   int chartScale = ChartScaleGet(0);
   switch(chartScale)
     {
      case 5:  Scale = 3; break;
      case 4:  Scale = 2; break;
      case 3:  Scale = 1; break;
      case 2:  Scale = 1; break;
      default: Scale = 0; break;
     }
  }

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_chart_id = ChartID();
   g_prefix   = "mapD_" + IntegerToString(g_chart_id) + "_";

   // Up candle gradient (index 0)
   Storage[0].R[0] = InpLo0 & 0xFF;
   Storage[0].G[0] = (InpLo0 >> 8) & 0xFF;
   Storage[0].B[0] = (InpLo0 >> 16) & 0xFF;
   Storage[0].O[0] = (uint)MathMin(MathMax(InpOp0, 0), 255);

   Storage[0].R[1] = InpHi0 & 0xFF;
   Storage[0].G[1] = (InpHi0 >> 8) & 0xFF;
   Storage[0].B[1] = (InpHi0 >> 16) & 0xFF;
   Storage[0].O[1] = (uint)MathMin(MathMax(InpOP0, 0), 255);

   Storage[0].Line = (uint)InpLn0;

   // Down candle gradient (index 1)
   Storage[1].R[0] = InpLo1 & 0xFF;
   Storage[1].G[0] = (InpLo1 >> 8) & 0xFF;
   Storage[1].B[0] = (InpLo1 >> 16) & 0xFF;
   Storage[1].O[0] = (uint)MathMin(MathMax(InpOp1, 0), 255);

   Storage[1].R[1] = InpHi1 & 0xFF;
   Storage[1].G[1] = (InpHi1 >> 8) & 0xFF;
   Storage[1].B[1] = (InpHi1 >> 16) & 0xFF;
   Storage[1].O[1] = (uint)MathMin(MathMax(InpOP1, 0), 255);

   Storage[1].Line = (uint)InpLn1;

   UpdateDimensions();
   UpdateScale();

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, g_prefix);
   for(int i = 0; i <= g_max_drawn_bar; i++)
     {
      string MasName = "::" + IntegerToString(g_chart_id) + "_Maps_" + IntegerToString(i);
      ResourceFree(MasName);
     }
   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Fill pixel memory buffer with gradient and optional outline      |
//+------------------------------------------------------------------+
void FillMap(uint &MapStorage[], const int iX, const int iY, const Gradient &gColor)
  {
   if(iX < 1 || iY < 1)
      return;

   ArrayResize(MapStorage, iX * iY);

   double dB = (gColor.B[1] != gColor.B[0] && iY > 1) ? ((double)gColor.B[1] - (double)gColor.B[0]) / (double)iY : 0.0;
   double dG = (gColor.G[1] != gColor.G[0] && iY > 1) ? ((double)gColor.G[1] - (double)gColor.G[0]) / (double)iY : 0.0;
   double dR = (gColor.R[1] != gColor.R[0] && iY > 1) ? ((double)gColor.R[1] - (double)gColor.R[0]) / (double)iY : 0.0;
   double dO = (gColor.O[1] != gColor.O[0] && iY > 1) ? ((double)gColor.O[1] - (double)gColor.O[0]) / (double)iY : 0.0;

   bool drawAroundLine = InpAroundLine;
   if(InpAutoAroundLine && iX < InpValueColor)
      drawAroundLine = false;

   if(drawAroundLine && iY >= 2 && iX >= 2)
     {
      for(int i = iY - 2; i > 0; i--)
        {
         uint  bVal = (uint)(gColor.B[0] + (dB != 0.0 ? (double)i * dB : 0.0));
         uint  gVal = (uint)(gColor.G[0] + (dG != 0.0 ? (double)i * dG : 0.0));
         uint  rVal = (uint)(gColor.R[0] + (dR != 0.0 ? (double)i * dR : 0.0));
         uchar oVal = (uchar)(gColor.O[0] + (dO != 0.0 ? (double)i * dO : 0.0));

         color bodyColor = (color)((bVal << 16) | (gVal << 8) | rVal);
         uint  bodyPixel = ColorToARGB(bodyColor, oVal);
         ArrayFill(MapStorage, iX * i, iX, bodyPixel);

         uint linePixel = ColorToARGB((color)gColor.Line, oVal);
         MapStorage[iX * i]          = linePixel;
         MapStorage[iX * i + iX - 1] = linePixel;
        }

      uint topLinePixel = ColorToARGB((color)gColor.Line, (uchar)gColor.O[0]);
      ArrayFill(MapStorage, 0, iX, topLinePixel);

      uint botLinePixel = ColorToARGB((color)gColor.Line, (uchar)gColor.O[1]);
      ArrayFill(MapStorage, iX * (iY - 1), iX, botLinePixel);
     }
   else
     {
      for(int i = 0; i < iY; i++)
        {
         uint  bVal = (uint)(gColor.B[0] + (dB != 0.0 ? (double)i * dB : 0.0));
         uint  gVal = (uint)(gColor.G[0] + (dG != 0.0 ? (double)i * dG : 0.0));
         uint  rVal = (uint)(gColor.R[0] + (dR != 0.0 ? (double)i * dR : 0.0));
         uchar oVal = (uchar)(gColor.O[0] + (dO != 0.0 ? (double)i * dO : 0.0));

         color bodyColor = (color)((bVal << 16) | (gVal << 8) | rVal);
         uint  bodyPixel = ColorToARGB(bodyColor, oVal);
         ArrayFill(MapStorage, iX * i, iX, bodyPixel);
        }
     }
  }

//+------------------------------------------------------------------+
//| Update the current forming bar (bar 0)                           |
//+------------------------------------------------------------------+
void DrawFirst(const datetime barTime, const double barLow, const datetime prevTime, const double prevLow, const double barOpen, const double barClose, const long barVolume)
  {
   if(g_volumeScale <= 0.0)
      return;

   int ShVolume = (int)((double)barVolume * g_volumeScale);
   if(ShVolume <= 0)
      return;

   UpdateDimensions();
   if(Y <= 0)
      return;

   if(ShVolume > Y - 4)
      ShVolume = Y - 4;

   double midPrice = 0.0;
   double pMax = ChartGetDouble(0, CHART_PRICE_MAX, 0);
   double pMin = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   if(pMax > pMin)
      midPrice = (pMax + pMin) * 0.5;
   else
      midPrice = barLow;

   int Cx = 0, Cy = 0, CxP = 0;
   ChartTimePriceToXY(0, 0, barTime, midPrice, Cx, Cy);
   ChartTimePriceToXY(0, 0, prevTime, midPrice, CxP, Cy);

   if(Cx <= 0 || CxP <= 0)
      return;

   int LengtOfBar = Cx - CxP;
   if(LengtOfBar <= 0)
      return;

   string D       = g_prefix + "0";
   string MasName = "::" + IntegerToString(g_chart_id) + "_Maps_0";

   ObjectDelete(0, D);
   ObjectCreate(0, D, OBJ_BITMAP_LABEL, 0, 0, 0);
   ObjectSetString(0, D, OBJPROP_BMPFILE, MasName);
   ObjectSetInteger(0, D, OBJPROP_XDISTANCE, Cx - LengtOfBar / 2);

   LengtOfBar = LengtOfBar - Scale;
   if(LengtOfBar < 1)
      LengtOfBar = 1;

   ObjectSetInteger(0, D, OBJPROP_YDISTANCE, Y - ShVolume - 2);
   ObjectSetInteger(0, D, OBJPROP_BACK, true);
   ObjectSetInteger(0, D, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, D, OBJPROP_SELECTED, false);
   ObjectSetInteger(0, D, OBJPROP_HIDDEN, true);

   uint MapStorage[];
   FillMap(MapStorage, LengtOfBar, ShVolume, barClose > barOpen ? Storage[0] : Storage[1]);

   ResourceFree(MasName);
   ResourceCreate(MasName, MapStorage, LengtOfBar, ShVolume, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE);

   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Draw all visible volume bars                                     |
//+------------------------------------------------------------------+
void DrawFull()
  {
   UpdateDimensions();
   UpdateScale();

   if(Y <= 0)
      return;

   int FVB = ChartFirstVisibleBar(0);
   int VB  = ChartVisibleBars(0);
   if(FVB < 0 || VB <= 0)
      return;

   int vB = FVB - VB;
   int barsNeeded = FVB + 6;

   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   int copied = CopyRates(_Symbol, _Period, 0, barsNeeded, rates);
   if(copied < 2)
      return;

   int startBar = (vB < 0) ? 0 : vB;
   int endBar   = FVB + 3;
   if(endBar >= copied - 1)
      endBar = copied - 2;
   if(startBar > endBar)
      return;

   // 1. Delete previous objects and free old resources
   ObjectsDeleteAll(0, g_prefix);
   for(int i = 0; i <= g_max_drawn_bar; i++)
     {
      string MasName = "::" + IntegerToString(g_chart_id) + "_Maps_" + IntegerToString(i);
      ResourceFree(MasName);
     }
   g_max_drawn_bar = 0;

   // 2. Find max volume among visible bars
   double maxVolume = 0.0;
   for(int i = startBar; i <= endBar; i++)
     {
      long vol = (InpVolumeType == VOL_REAL && rates[i].real_volume > 0) ? rates[i].real_volume : rates[i].tick_volume;
      if((double)vol > maxVolume)
         maxVolume = (double)vol;
     }

   if(maxVolume < 1.0)
      return;

   g_volumeScale = ((double)Y * (InpPersent / 100.0)) / maxVolume;

   double midPrice = 0.0;
   double pMax = ChartGetDouble(0, CHART_PRICE_MAX, 0);
   double pMin = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   if(pMax > pMin)
      midPrice = (pMax + pMin) * 0.5;
   else
      midPrice = rates[0].close;

   uint MapStorage[];
   int lastBarWidth = 0;

   // 3. Draw visible bars
   for(int i = startBar; i <= endBar; i++)
     {
      long vol = (InpVolumeType == VOL_REAL && rates[i].real_volume > 0) ? rates[i].real_volume : rates[i].tick_volume;
      int ShVolume = (int)((double)vol * g_volumeScale);
      if(ShVolume <= 0)
         continue;

      if(ShVolume > Y - 4)
         ShVolume = Y - 4;

      int Cx = 0, Cy = 0, CxP = 0;
      ChartTimePriceToXY(0, 0, rates[i].time, midPrice, Cx, Cy);
      ChartTimePriceToXY(0, 0, rates[i + 1].time, midPrice, CxP, Cy);

      int LengtOfBar = 0;
      if(Cx > 0 && CxP > 0)
        {
         LengtOfBar = Cx - CxP;
         if(LengtOfBar > 0)
            lastBarWidth = LengtOfBar;
        }
      else if(Cx > 0 && lastBarWidth > 0)
        {
         LengtOfBar = lastBarWidth;
        }
      else
         continue;

      if(LengtOfBar <= 0)
         continue;

      string D       = g_prefix + IntegerToString(i);
      string MasName = "::" + IntegerToString(g_chart_id) + "_Maps_" + IntegerToString(i);

      ObjectCreate(0, D, OBJ_BITMAP_LABEL, 0, 0, 0);
      ObjectSetString(0, D, OBJPROP_BMPFILE, MasName);
      ObjectSetInteger(0, D, OBJPROP_XDISTANCE, Cx - LengtOfBar / 2);

      LengtOfBar = LengtOfBar - Scale;
      if(LengtOfBar < 1)
         LengtOfBar = 1;

      ObjectSetInteger(0, D, OBJPROP_YDISTANCE, Y - ShVolume - 2);
      ObjectSetInteger(0, D, OBJPROP_BACK, true);
      ObjectSetInteger(0, D, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, D, OBJPROP_SELECTED, false);
      ObjectSetInteger(0, D, OBJPROP_HIDDEN, true);

      FillMap(MapStorage, LengtOfBar, ShVolume, rates[i].close > rates[i].open ? Storage[0] : Storage[1]);

      ResourceFree(MasName);
      ResourceCreate(MasName, MapStorage, LengtOfBar, ShVolume, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE);

      if(i > g_max_drawn_bar)
         g_max_drawn_bar = i;
     }

   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   if(rates_total < 2)
      return(0);

   ArraySetAsSeries(time, true);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(tick_volume, true);
   ArraySetAsSeries(volume, true);

   static datetime tmpTime = 0;
   g_initialized = true;

   if(tmpTime != time[0])
     {
      DrawFull();
      tmpTime = time[0];
     }
   else
     {
      long bar0Vol = (InpVolumeType == VOL_REAL && volume[0] > 0) ? volume[0] : tick_volume[0];
      DrawFirst(time[0], low[0], time[1], low[1], open[0], close[0], bar0Vol);
     }

   return(rates_total);
  }

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(id == CHARTEVENT_CHART_CHANGE && g_initialized)
     {
      DrawFull();
     }
  }
//+------------------------------------------------------------------+
