﻿//+------------------------------------------------------------------+
//|           Canvas Graphing PART 3 - Statistical Distributions.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict

#include <Canvas\Canvas.mqh>
#include <Math\Stat\Binomial.mqh>
#include <Math\Stat\Math.mqh>

//+------------------------------------------------------------------+
//| Resize direction enumeration                                     |
//+------------------------------------------------------------------+
enum ResizeDirection
  {
   NO_RESIZE,           // No resize action
   RESIZE_BOTTOM_EDGE,  // Resize using bottom edge
   RESIZE_RIGHT_EDGE,   // Resize using right edge
   RESIZE_CORNER        // Resize using bottom-right corner
  };

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "=== DISTRIBUTION SETTINGS ==="
input int    numTrials = 40;                    // Number of trials (n) for binomial distribution
input double successProbability = 0.75;         // Success probability (p) per trial (0.0-1.0)
input int    sampleSize = 1000000;              // Sample size for generating histogram data
input int    histogramCells = 20;               // Number of cells (bins) in the histogram
input int    histogramGapPixels = 2;            // Gap in pixels between adjacent histogram bars
input ENUM_TIMEFRAMES chartTimeframe = PERIOD_CURRENT; // Timeframe used for new-bar detection

input group "=== CANVAS DISPLAY SETTINGS ==="
input int    initialCanvasX = 20;               // Initial X position of canvas on chart (pixels)
input int    initialCanvasY = 30;               // Initial Y position of canvas on chart (pixels)
input int    initialCanvasWidth = 600;          // Initial width of canvas in pixels
input int    initialCanvasHeight = 400;         // Initial height of canvas in pixels
input int    plotPadding = 10;                  // Internal padding around plot area in pixels

input group "=== THEME COLOR (SINGLE CONTROL!) ==="
input color  themeColor = clrDodgerBlue;        // Master theme color controlling all UI accents
input bool   showBorderFrame = true;            // Display decorative border frame around canvas

input group "=== HISTOGRAM AND CURVE SETTINGS ==="
input color  histogramColor = clrRed;           // Fill color for histogram bars
input color  theoreticalCurveColor = clrBlue;   // Color of theoretical probability mass function curve
input int    curveLineWidth = 2;                // Thickness in pixels of the theoretical curve

input group "=== BACKGROUND SETTINGS ==="
input bool   enableBackgroundFill = true;       // Enable gradient background fill inside canvas
input color  backgroundTopColor = clrWhite;     // Top color of the gradient background
input double backgroundOpacityLevel = 0.95;     // Background opacity level (0.0 fully transparent - 1.0 opaque)

input group "=== TEXT AND LABELS ==="
input int    titleFontSize = 14;                // Font size for main window title
input color  titleTextColor = clrBlack;         // Color of the main title text
input int    labelFontSize = 11;                // Font size for general labels and legend
input color  labelTextColor = clrBlack;         // Color of general label text
input int    axisLabelFontSize = 12;            // Font size for axis tick labels
input bool   showStatistics = true;             // Show statistics panel and legend

input group "=== STATS & LEGEND PANEL SETTINGS ==="
input int    statsPanelX = 70;                  // X position of statistics panel inside canvas
input int    statsPanelY = 10;                  // Y offset of statistics panel from header
input int    statsPanelWidth = 130;             // Width of statistics panel in pixels
input int    statsPanelHeight = 175;            // Height of statistics panel in pixels
input int    panelFontSize = 13;                // Font size used in stats and legend panels
input int    legendHeight = 35;                 // Height of legend panel in pixels

input group "=== INTERACTION SETTINGS ==="
input bool   enableDragging = true;             // Allow dragging canvas by clicking header
input bool   enableResizing = true;             // Allow resizing canvas with mouse grips
input int    resizeGripSize = 8;                // Size of resize grip detection zones in pixels

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
CCanvas mainCanvas;                                     // Main canvas object for all drawing
string canvasObjectName = "DistributionCanvas_Main";    // Name of the graphical object on chart
int currentPositionX = initialCanvasX;                  // Current X coordinate of canvas
int currentPositionY = initialCanvasY;                  // Current Y coordinate of canvas
int currentWidthPixels = initialCanvasWidth;            // Current canvas width in pixels
int currentHeightPixels = initialCanvasHeight;          // Current canvas height in pixels
bool isDraggingCanvas = false;                          // True while canvas is being dragged
bool isResizingCanvas = false;                          // True while canvas is being resized
int dragStartX = 0;                                     // Mouse X when drag started
int dragStartY = 0;                                     // Mouse Y when drag started
int canvasStartX = 0;                                   // Canvas X when drag started
int canvasStartY = 0;                                   // Canvas Y when drag started
int resizeStartX = 0;                                   // Mouse X when resize started
int resizeStartY = 0;                                   // Mouse Y when resize started
int resizeInitialWidth = 0;                             // Width when resize started
int resizeInitialHeight = 0;                            // Height when resize started
ResizeDirection activeResizeMode = NO_RESIZE;           // Currently active resize direction
ResizeDirection hoverResizeMode = NO_RESIZE;            // Hover resize direction
bool isHoveringCanvas = false;                          // Mouse is over canvas area
bool isHoveringHeader = false;                          // Mouse is over header bar
bool isHoveringResizeZone = false;                      // Mouse is over a resize grip
int lastMouseX = 0;                                     // Last recorded mouse X
int lastMouseY = 0;                                     // Last recorded mouse Y
int previousMouseButtonState = 0;                       // Previous mouse button state
const int MIN_CANVAS_WIDTH = 300;                       // Minimum allowed canvas width
const int MIN_CANVAS_HEIGHT = 200;                      // Minimum allowed canvas height
const int HEADER_BAR_HEIGHT = 35;                       // Fixed height of header bar
double sampleData[];                                    // Generated binomial sample values
double histogramIntervals[];                            // Center positions of histogram bins
double histogramFrequencies[];                          // Frequency count per histogram bin
double theoreticalXValues[];                            // X values for theoretical PMF
double theoreticalYValues[];                            // Probability values for theoretical PMF
double minDataValue = 0.0;                              // Minimum value in sample data
double maxDataValue = 0.0;                              // Maximum value in sample data
double maxFrequency = 0.0;                              // Highest histogram frequency
double maxTheoreticalValue = 0.0;                       // Highest theoretical probability
bool dataLoadedSuccessfully = false;                    // True after successful data load
double sampleMean = 0.0;                                // Sample mean
double sampleStandardDeviation = 0.0;                   // Sample standard deviation
double sampleSkewness = 0.0;                            // Sample skewness
double sampleKurtosis = 0.0;                            // Sample kurtosis
double percentile25 = 0.0;                              // 25th percentile
double percentile50 = 0.0;                              // 50th percentile (median)
double percentile75 = 0.0;                              // 75th percentile
double confidenceInterval95Lower = 0.0;                 // 95% CI lower bound
double confidenceInterval95Upper = 0.0;                 // 95% CI upper bound
double confidenceInterval99Lower = 0.0;                 // 99% CI lower bound
double confidenceInterval99Upper = 0.0;                 // 99% CI upper bound

//+------------------------------------------------------------------+
//| Lighten the base color                                           |
//+------------------------------------------------------------------+
color LightenColor(color baseColor, double factor)
  {
   //--- Extract red component from base color
   uchar r = (uchar)((baseColor >> 16) & 0xFF);
   //--- Extract green component from base color
   uchar g = (uchar)((baseColor >> 8) & 0xFF);
   //--- Extract blue component from base color
   uchar b = (uchar)(baseColor & 0xFF);
   
   //--- Lighten red component
   r = (uchar)MathMin(255, r + (255 - r) * factor);
   //--- Lighten green component
   g = (uchar)MathMin(255, g + (255 - g) * factor);
   //--- Lighten blue component
   b = (uchar)MathMin(255, b + (255 - b) * factor);
   
   //--- Reassemble ARGB and return lightened color
   return (r << 16) | (g << 8) | b;
  }

//+------------------------------------------------------------------+
//| Darken the base color                                            |
//+------------------------------------------------------------------+
color DarkenColor(color baseColor, double factor)
  {
   //--- Extract red component from base color
   uchar r = (uchar)((baseColor >> 16) & 0xFF);
   //--- Extract green component from base color
   uchar g = (uchar)((baseColor >> 8) & 0xFF);
   //--- Extract blue component from base color
   uchar b = (uchar)(baseColor & 0xFF);
   
   //--- Darken red component
   r = (uchar)(r * (1.0 - factor));
   //--- Darken green component
   g = (uchar)(g * (1.0 - factor));
   //--- Darken blue component
   b = (uchar)(b * (1.0 - factor));
   
   //--- Reassemble ARGB and return darkened color
   return (r << 16) | (g << 8) | b;
  }

//+------------------------------------------------------------------+
//| Calculate mean of array                                          |
//+------------------------------------------------------------------+
double CalculateMean(const double &data[])
  {
   //--- Get current array size
   int size = ArraySize(data);
   //--- Return zero for empty array
   if (size == 0) return 0.0;
   
   //--- Initialize running sum
   double sum = 0.0;
   //--- Accumulate every element
   for (int i = 0; i < size; i++)
     {
      sum += data[i];
     }
   //--- Return arithmetic mean
   return sum / size;
  }

//+------------------------------------------------------------------+
//| Calculate standard deviation of array                            |
//+------------------------------------------------------------------+
double CalculateStandardDeviation(const double &data[], double mean)
  {
   //--- Get current array size
   int size = ArraySize(data);
   //--- Return zero for insufficient data
   if (size <= 1) return 0.0;
   
   //--- Initialize sum of squared differences
   double sumSquaredDiff = 0.0;
   //--- Loop through all values
   for (int i = 0; i < size; i++)
     {
      //--- Compute deviation from mean
      double diff = data[i] - mean;
      //--- Accumulate squared deviation
      sumSquaredDiff += diff * diff;
     }
   //--- Return sample standard deviation
   return MathSqrt(sumSquaredDiff / (size - 1));
  }

//+------------------------------------------------------------------+
//| Calculate skewness of array                                      |
//+------------------------------------------------------------------+
double CalculateSkewness(const double &data[], double mean, double stdDev)
  {
   //--- Get current array size
   int size = ArraySize(data);
   //--- Return zero for insufficient data or zero std dev
   if (size < 3 || stdDev == 0.0) return 0.0;
   
   //--- Initialize sum of cubed standardized differences
   double sumCubedDiff = 0.0;
   //--- Loop through all values
   for (int i = 0; i < size; i++)
     {
      //--- Standardize the deviation
      double diff = (data[i] - mean) / stdDev;
      //--- Accumulate cubed term
      sumCubedDiff += diff * diff * diff;
     }
   
   //--- Cast size to double for formula
   double n = (double)size;
   //--- Apply bias-corrected skewness formula and return
   return (n / ((n - 1) * (n - 2))) * sumCubedDiff;
  }

//+------------------------------------------------------------------+
//| Calculate kurtosis of array                                      |
//+------------------------------------------------------------------+
double CalculateKurtosis(const double &data[], double mean, double stdDev)
  {
   //--- Get current array size
   int size = ArraySize(data);
   //--- Return zero for insufficient data or zero std dev
   if (size < 4 || stdDev == 0.0) return 0.0;
   
   //--- Initialize sum of fourth powers of standardized differences
   double sumFourthPower = 0.0;
   //--- Loop through all values
   for (int i = 0; i < size; i++)
     {
      //--- Standardize the deviation
      double diff = (data[i] - mean) / stdDev;
      //--- Square the standardized value
      double squared = diff * diff;
      //--- Accumulate fourth power
      sumFourthPower += squared * squared;
     }
   
   //--- Cast size to double
   double n = (double)size;
   //--- First part of excess kurtosis formula
   double kurtosis = (n * (n + 1) / ((n - 1) * (n - 2) * (n - 3))) * sumFourthPower;
   //--- Subtract bias correction term
   kurtosis -= (3 * (n - 1) * (n - 1)) / ((n - 2) * (n - 3));
   
   //--- Return kurtosis value
   return kurtosis;
  }

//+------------------------------------------------------------------+
//| Calculate percentile of array                                    |
//+------------------------------------------------------------------+
double CalculatePercentile(double &data[], double percentile)
  {
   //--- Get current array size
   int size = ArraySize(data);
   //--- Return zero for empty array
   if (size == 0) return 0.0;
   
   //--- Prepare sorted copy
   double sortedData[];
   //--- Resize to match original
   ArrayResize(sortedData, size);
   //--- Copy original data
   ArrayCopy(sortedData, data);
   //--- Sort in ascending order
   ArraySort(sortedData);
   
   //--- Compute rank for interpolation
   double rank = (percentile / 100.0) * (size - 1);
   //--- Lower index for interpolation
   int lowerIndex = (int)MathFloor(rank);
   //--- Upper index for interpolation
   int upperIndex = (int)MathCeil(rank);
   
   //--- Exact match case
   if (lowerIndex == upperIndex)
     {
      return sortedData[lowerIndex];
     }
   
   //--- Compute interpolation fraction
   double fraction = rank - lowerIndex;
   //--- Linear interpolation and return
   return sortedData[lowerIndex] + fraction * (sortedData[upperIndex] - sortedData[lowerIndex]);
  }

//+------------------------------------------------------------------+
//| Calculate confidence interval                                    |
//+------------------------------------------------------------------+
void CalculateConfidenceInterval(double mean, double stdDev, int in_sampleSize, double zScore, 
                                 double &lowerBound, double &upperBound)
  {
   //--- Compute margin of error
   double marginOfError = zScore * (stdDev / MathSqrt(in_sampleSize));
   //--- Set lower bound
   lowerBound = mean - marginOfError;
   //--- Set upper bound
   upperBound = mean + marginOfError;
  }

//+------------------------------------------------------------------+
//| Compute advanced statistics                                      |
//+------------------------------------------------------------------+
void ComputeAdvancedStatistics()
  {
   //--- Calculate sample mean
   sampleMean = CalculateMean(sampleData);
   //--- Calculate sample standard deviation
   sampleStandardDeviation = CalculateStandardDeviation(sampleData, sampleMean);
   
   //--- Calculate skewness
   sampleSkewness = CalculateSkewness(sampleData, sampleMean, sampleStandardDeviation);
   //--- Calculate kurtosis
   sampleKurtosis = CalculateKurtosis(sampleData, sampleMean, sampleStandardDeviation);
   
   //--- Calculate first quartile
   percentile25 = CalculatePercentile(sampleData, 25.0);
   //--- Calculate median
   percentile50 = CalculatePercentile(sampleData, 50.0);
   //--- Calculate third quartile
   percentile75 = CalculatePercentile(sampleData, 75.0);
   
   //--- Compute 95% confidence interval
   CalculateConfidenceInterval(sampleMean, sampleStandardDeviation, sampleSize, 1.96, 
                               confidenceInterval95Lower, confidenceInterval95Upper);
   //--- Compute 99% confidence interval
   CalculateConfidenceInterval(sampleMean, sampleStandardDeviation, sampleSize, 2.576, 
                               confidenceInterval99Lower, confidenceInterval99Upper);
  }

//+------------------------------------------------------------------+
//| Initialize the expert                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Copy initial X position from inputs
   currentPositionX = initialCanvasX;
   //--- Copy initial Y position from inputs
   currentPositionY = initialCanvasY;
   //--- Copy initial width from inputs
   currentWidthPixels = initialCanvasWidth;
   //--- Copy initial height from inputs
   currentHeightPixels = initialCanvasHeight;

   //--- Create canvas object
   if (!CreateCanvas())
     {
      //--- Log creation failure
      Print("ERROR: Failed to create distribution canvas");
      //--- Fail initialization
      return(INIT_FAILED);
     }

   //--- Load initial distribution data
   if (!LoadDistributionData())
     {
      //--- Log data load failure
      Print("ERROR: Failed to load distribution data");
      //--- Fail initialization
      return(INIT_FAILED);
     }

   //--- Render first visualization
   RenderVisualization();

   //--- Activate mouse move events
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
   //--- Force chart redraw
   ChartRedraw();

   //--- Successful initialization
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Deinitialize the expert                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   //--- Destroy canvas and free resources
   mainCanvas.Destroy();
   //--- Force chart redraw to clean up
   ChartRedraw();
  }

//+------------------------------------------------------------------+
//| Process new tick                                                 |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Remember last processed bar time
   static datetime lastBarTimestamp = 0;
   //--- Get time of newest bar on chosen timeframe
   datetime currentBarTimestamp = iTime(_Symbol, chartTimeframe, 0);

   //--- New bar detected
   if (currentBarTimestamp > lastBarTimestamp)
     {
      //--- Reload fresh data
      if (LoadDistributionData())
        {
         //--- Update visualization
         RenderVisualization();
         //--- Redraw chart
         ChartRedraw();
        }
      //--- Store new bar time
      lastBarTimestamp = currentBarTimestamp;
     }
  }

//+------------------------------------------------------------------+
//| Create distribution canvas                                       |
//+------------------------------------------------------------------+
bool CreateCanvas()
  {
   //--- Attempt to create bitmap label canvas
   if (!mainCanvas.CreateBitmapLabel(0, 0, canvasObjectName, 
       currentPositionX, currentPositionY, currentWidthPixels, currentHeightPixels, 
       COLOR_FORMAT_ARGB_NORMALIZE))
     {
      //--- Creation failed
      return false;
     }
   //--- Canvas created successfully
   return true;
  }

//+------------------------------------------------------------------+
//| Load distribution data                                           |
//+------------------------------------------------------------------+
bool LoadDistributionData()
  {
   //--- Seed random generator with current tick count
   MathSrand(GetTickCount());

   //--- Resize sample array to requested size
   ArrayResize(sampleData, sampleSize);
   //--- Generate binomial random samples
   MathRandomBinomial(numTrials, successProbability, sampleSize, sampleData);

   //--- Compute histogram from samples
   if (!ComputeHistogram(sampleData, histogramIntervals, histogramFrequencies, maxDataValue, minDataValue, histogramCells))
     {
      //--- Histogram calculation failed
      Print("ERROR: Failed to calculate histogram");
      //--- Return failure
      return false;
     }

   //--- Resize theoretical arrays
   ArrayResize(theoreticalXValues, numTrials + 1);
   ArrayResize(theoreticalYValues, numTrials + 1);
   //--- Fill X values from 0 to n
   MathSequence(0, numTrials, 1, theoreticalXValues);
   //--- Compute binomial PMF values
   MathProbabilityDensityBinomial(theoreticalXValues, numTrials, successProbability, false, theoreticalYValues);

   //--- Find maximum histogram frequency
   maxFrequency = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   //--- Find maximum theoretical probability
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   //--- Compute scaling factor to match histogram height
   double scaleFactor = maxFrequency / maxTheoreticalValue;
   //--- Scale theoretical frequencies to visual match
   for (int i = 0; i < histogramCells; i++)
     {
      histogramFrequencies[i] /= scaleFactor;
     }

   //--- Compute all advanced statistics
   ComputeAdvancedStatistics();

   //--- Mark data as ready
   dataLoadedSuccessfully = true;
   //--- Log success
   Print("SUCCESS: Loaded binomial distribution data with advanced statistics");
   //--- Return success
   return true;
  }

//+------------------------------------------------------------------+
//| Compute histogram array with forced range                        |
//+------------------------------------------------------------------+
bool ComputeHistogram(const double &data[], double &intervals[], double &frequency[], double &maxv, double &minv, const int cells = 10)
  {
   //--- Invalid cell count aborts
   if (cells <= 1) return false;
   //--- Get data length
   int size = ArraySize(data);
   //--- Empty data aborts
   if (size < 1) return false;
   
   //--- Force histogram range from 0 to n
   minv = 0;
   maxv = numTrials;
   //--- Compute total range
   double range = maxv - minv;
   //--- Compute bin width
   double width = range / cells;
   //--- Zero width aborts
   if (width == 0) return false;
   
   //--- Resize output arrays
   ArrayResize(intervals, cells);
   ArrayResize(frequency, cells);
   
   //--- Initialize interval centers and zero frequencies
   for (int i = 0; i < cells; i++)
     {
      intervals[i] = minv + (i + 0.5) * width;
      frequency[i] = 0;
     }
   
   //--- Bin each sample value
   for (int i = 0; i < size; i++)
     {
      //--- Current value
      double val = data[i];
      //--- Skip out-of-range values
      if (val < minv || val > maxv) continue;
      //--- Compute bin index
      int ind = (int)((val - minv) / width);
      //--- Clamp index to valid range
      if (ind >= cells) ind = cells - 1;
      if (ind < 0) ind = 0;
      //--- Increment frequency
      frequency[ind]++;
     }
   //--- Histogram computed successfully
   return true;
  }

//+------------------------------------------------------------------+
//| Render distribution visualization                                |
//+------------------------------------------------------------------+
void RenderVisualization()
  {
   //--- Clear entire canvas
   mainCanvas.Erase(0);

   //--- Draw gradient if enabled
   if (enableBackgroundFill)
     {
      DrawGradientBackground();
     }

   //--- Draw outer border
   DrawCanvasBorder();
   //--- Draw header bar
   DrawHeaderBar();
   //--- Draw main plot area
   DrawDistributionPlot();

   //--- Draw statistics if enabled
   if (showStatistics)
     {
      DrawStatisticsPanel();
      DrawLegend();
     }

   //--- Show resize indicator when hovering
   if (isHoveringResizeZone && enableResizing)
     {
      DrawResizeIndicator();
     }

   //--- Update canvas to screen
   mainCanvas.Update();
  }

//+------------------------------------------------------------------+
//| Draw gradient background                                         |
//+------------------------------------------------------------------+
void DrawGradientBackground()
  {
   //--- Compute bottom gradient color
   color bottomColor = LightenColor(themeColor, 0.85);
   
   //--- Loop every row starting after header
   for (int y = HEADER_BAR_HEIGHT; y < currentHeightPixels; y++)
     {
      //--- Compute vertical gradient factor
      double gradientFactor = (double)(y - HEADER_BAR_HEIGHT) / (currentHeightPixels - HEADER_BAR_HEIGHT);
      //--- Interpolate current row color
      color currentRowColor = InterpolateColors(backgroundTopColor, bottomColor, gradientFactor);
      //--- Compute alpha for row
      uchar alphaChannel = (uchar)(255 * backgroundOpacityLevel);
      //--- Convert to ARGB
      uint argbColor = ColorToARGB(currentRowColor, alphaChannel);

      //--- Fill entire row
      for (int x = 0; x < currentWidthPixels; x++)
        {
         mainCanvas.PixelSet(x, y, argbColor);
        }
     }
  }

//+------------------------------------------------------------------+
//| Draw canvas border                                               |
//+------------------------------------------------------------------+
void DrawCanvasBorder()
  {
   //--- Skip if border disabled
   if (!showBorderFrame) return;

   //--- Darken border when resizing
   color borderColor = isHoveringResizeZone ? DarkenColor(themeColor, 0.2) : themeColor;
   //--- Convert to ARGB
   uint argbBorder = ColorToARGB(borderColor, 255);

   //--- Draw outer rectangle
   mainCanvas.Rectangle(0, 0, currentWidthPixels - 1, currentHeightPixels - 1, argbBorder);
   //--- Draw inner rectangle
   mainCanvas.Rectangle(1, 1, currentWidthPixels - 2, currentHeightPixels - 2, argbBorder);
  }

//+------------------------------------------------------------------+
//| Draw header bar                                                  |
//+------------------------------------------------------------------+
void DrawHeaderBar()
  {
   //--- Choose header color based on interaction state
   color headerColor;
   if (isDraggingCanvas)
     {
      headerColor = DarkenColor(themeColor, 0.1);
     }
   else if (isHoveringHeader)
     {
      headerColor = LightenColor(themeColor, 0.4);
     }
   else
     {
      headerColor = LightenColor(themeColor, 0.7);
     }
   //--- Convert header color
   uint argbHeader = ColorToARGB(headerColor, 255);

   //--- Fill header rectangle
   mainCanvas.FillRectangle(0, 0, currentWidthPixels - 1, HEADER_BAR_HEIGHT, argbHeader);

   //--- Draw header borders if enabled
   if (showBorderFrame)
     {
      uint argbBorder = ColorToARGB(themeColor, 255);
      mainCanvas.Rectangle(0, 0, currentWidthPixels - 1, HEADER_BAR_HEIGHT, argbBorder);
      mainCanvas.Rectangle(1, 1, currentWidthPixels - 2, HEADER_BAR_HEIGHT - 1, argbBorder);
     }

   //--- Set title font
   mainCanvas.FontSet("Arial Bold", titleFontSize);
   //--- Title text color
   uint argbText = ColorToARGB(titleTextColor, 255);

   //--- Format dynamic title with parameters
   string titleText = StringFormat("Binomial Distribution (n=%d, p=%.2f)", numTrials, successProbability);
   //--- Draw centered title
   mainCanvas.TextOut(currentWidthPixels / 2, (HEADER_BAR_HEIGHT - titleFontSize) / 2, 
                      titleText, argbText, TA_CENTER);
  }

//+------------------------------------------------------------------+
//| Calculate optimal ticks with aggressive spacing                  |
//+------------------------------------------------------------------+
int CalculateOptimalTicks(double minValue, double maxValue, int pixelRange, double &tickValues[])
  {
   //--- Compute data range
   double range = maxValue - minValue;
   //--- Guard against zero range
   if (range == 0 || pixelRange <= 0)
     {
      ArrayResize(tickValues, 1);
      tickValues[0] = minValue;
      return 1;
     }
   
   //--- Target number of ticks based on pixels
   int targetTickCount = (int)(pixelRange / 50.0);
   //--- Enforce minimum ticks
   if (targetTickCount < 3) targetTickCount = 3;
   //--- Enforce maximum ticks
   if (targetTickCount > 20) targetTickCount = 20;
   
   //--- Rough step size
   double roughStep = range / (double)(targetTickCount - 1);
   
   //--- Determine magnitude for nice numbers
   double magnitude = MathPow(10.0, MathFloor(MathLog10(roughStep)));
   
   //--- Normalize step
   double normalized = roughStep / magnitude;
   
   //--- Choose nice step value
   double niceNormalized;
   if (normalized <= 1.0) niceNormalized = 1.0;
   else if (normalized <= 1.5) niceNormalized = 1.0;
   else if (normalized <= 2.0) niceNormalized = 2.0;
   else if (normalized <= 2.5) niceNormalized = 2.0;
   else if (normalized <= 3.0) niceNormalized = 2.5;
   else if (normalized <= 4.0) niceNormalized = 4.0;
   else if (normalized <= 5.0) niceNormalized = 5.0;
   else if (normalized <= 7.5) niceNormalized = 5.0;
   else niceNormalized = 10.0;
   
   //--- Final nice step
   double step = niceNormalized * magnitude;
   
   //--- Compute first tick below min
   double tickMin = MathFloor(minValue / step) * step;
   //--- Compute last tick above max
   double tickMax = MathCeil(maxValue / step) * step;
   
   //--- Number of ticks
   int numTicks = (int)MathRound((tickMax - tickMin) / step) + 1;
   
   //--- Reduce density if too many ticks
   if (numTicks > 25)
     {
      step *= 2.0;
      tickMin = MathFloor(minValue / step) * step;
      tickMax = MathCeil(maxValue / step) * step;
      numTicks = (int)MathRound((tickMax - tickMin) / step) + 1;
     }
   
   //--- Increase density if too few ticks
   if (numTicks < 3)
     {
      step /= 2.0;
      tickMin = MathFloor(minValue / step) * step;
      tickMax = MathCeil(maxValue / step) * step;
      numTicks = (int)MathRound((tickMax - tickMin) / step) + 1;
     }
   
   //--- Resize output array
   ArrayResize(tickValues, numTicks);
   //--- Fill tick values
   for (int i = 0; i < numTicks; i++)
     {
      tickValues[i] = tickMin + i * step;
     }
   
   //--- Return actual tick count
   return numTicks;
  }

//+------------------------------------------------------------------+
//| Format tick label with appropriate precision                     |
//+------------------------------------------------------------------+
string FormatTickLabel(double value, double range)
  {
   //--- No decimals for large range
   if (range > 100) return DoubleToString(value, 0);
   //--- One decimal
   else if (range > 10) return DoubleToString(value, 1);
   //--- Two decimals
   else if (range > 1) return DoubleToString(value, 2);
   //--- Three decimals
   else if (range > 0.1) return DoubleToString(value, 3);
   //--- Four decimals for tiny values
   else return DoubleToString(value, 4);
  }

//+------------------------------------------------------------------+
//| Draw distribution plot                                           |
//+------------------------------------------------------------------+
void DrawDistributionPlot()
  {
   //--- Skip if data not ready
   if (!dataLoadedSuccessfully) return;

   //--- Define plot margins
   int plotAreaLeft = 60;
   int plotAreaRight = currentWidthPixels - 40;
   int plotAreaTop = HEADER_BAR_HEIGHT + 10;
   int plotAreaBottom = currentHeightPixels - 50;

   //--- Apply internal padding
   int drawAreaLeft = plotAreaLeft + plotPadding;
   int drawAreaRight = plotAreaRight - plotPadding;
   int drawAreaTop = plotAreaTop + plotPadding;
   int drawAreaBottom = plotAreaBottom - plotPadding;

   //--- Compute usable plot dimensions
   int plotWidth = drawAreaRight - drawAreaLeft;
   int plotHeight = drawAreaBottom - drawAreaTop;

   //--- Abort on zero size
   if (plotWidth <= 0 || plotHeight <= 0) return;

   //--- Data ranges for scaling
   double rangeX = maxDataValue - minDataValue;
   double rangeY = maxTheoreticalValue;

   //--- Prevent division by zero
   if (rangeX == 0) rangeX = 1;
   if (rangeY == 0) rangeY = 1;

   //--- Axis color
   uint argbAxisColor = ColorToARGB(clrBlack, 255);
   
   //--- Draw thick Y axis
   for (int thick = 0; thick < 2; thick++)
     {
      mainCanvas.Line(plotAreaLeft - thick, plotAreaTop, plotAreaLeft - thick, plotAreaBottom, argbAxisColor);
     }
   
   //--- Draw thick X axis
   for (int thick = 0; thick < 2; thick++)
     {
      mainCanvas.Line(plotAreaLeft, plotAreaBottom + thick, plotAreaRight, plotAreaBottom + thick, argbAxisColor);
     }

   //--- Set font for tick labels
   mainCanvas.FontSet("Arial", axisLabelFontSize);
   uint argbTickLabel = ColorToARGB(clrBlack, 255);
   
   //--- Y-axis ticks
   double yTickValues[];
   int numYTicks = CalculateOptimalTicks(0, rangeY, plotHeight, yTickValues);
   
   //--- Draw each Y tick
   for (int i = 0; i < numYTicks; i++)
     {
      double yValue = yTickValues[i];
      if (yValue < 0 || yValue > rangeY) continue;
      
      //--- Compute screen Y position
      int yPos = drawAreaBottom - (int)((yValue - 0) / rangeY * plotHeight);
      
      //--- Draw tick mark
      mainCanvas.Line(plotAreaLeft - 5, yPos, plotAreaLeft, yPos, argbAxisColor);
      
      //--- Format label
      string yLabel = FormatTickLabel(yValue, rangeY);
      //--- Draw right-aligned label
      mainCanvas.TextOut(plotAreaLeft - 8, yPos - axisLabelFontSize/2, yLabel, argbTickLabel, TA_RIGHT);
     }

   //--- X-axis ticks
   double xTickValues[];
   int numXTicks = CalculateOptimalTicks(minDataValue, maxDataValue, plotWidth, xTickValues);
   
   //--- Draw each X tick
   for (int i = 0; i < numXTicks; i++)
     {
      double xValue = xTickValues[i];
      if (xValue < minDataValue || xValue > maxDataValue) continue;
      
      //--- Compute screen X position
      int xPos = drawAreaLeft + (int)((xValue - minDataValue) / rangeX * plotWidth);
      
      //--- Draw tick mark
      mainCanvas.Line(xPos, plotAreaBottom, xPos, plotAreaBottom + 5, argbAxisColor);
      
      //--- Format label
      string xLabel = FormatTickLabel(xValue, rangeX);
      //--- Draw centered label below axis
      mainCanvas.TextOut(xPos, plotAreaBottom + 7, xLabel, argbTickLabel, TA_CENTER);
     }

   //--- Histogram bar color
   uint argbHist = ColorToARGB(histogramColor, 255);
   //--- Total gap space
   double totalGaps = (histogramCells - 1) * histogramGapPixels;
   //--- Width of each bar
   double barWidth = (plotWidth - totalGaps) / histogramCells;
   //--- Minimum visible width
   if (barWidth < 1) barWidth = 1;

   //--- Draw every histogram bar
   for (int i = 0; i < histogramCells; i++)
     {
      //--- Left edge of bar
      int barLeft = drawAreaLeft + (int)(i * (barWidth + histogramGapPixels));
      //--- Right edge of bar
      int barRight = barLeft + (int)barWidth - 1;
      //--- Height proportional to frequency
      int barHeight = (int)(histogramFrequencies[i] / rangeY * plotHeight);
      //--- Top of bar
      int barTop = drawAreaBottom - barHeight;

      //--- Draw filled bar if valid
      if (barRight >= barLeft)
        {
         mainCanvas.FillRectangle(barLeft, barTop, barRight, drawAreaBottom, argbHist);
        }
     }

   //--- Theoretical curve color
   uint argbCurve = ColorToARGB(theoreticalCurveColor, 255);

   //--- Draw continuous curve
   for (int i = 0; i < ArraySize(theoreticalXValues) - 1; i++)
     {
      //--- Screen X1
      int x1 = drawAreaLeft + (int)((theoreticalXValues[i] - minDataValue) / rangeX * plotWidth);
      //--- Screen Y1
      int y1 = drawAreaBottom - (int)(theoreticalYValues[i] / rangeY * plotHeight);
      //--- Screen X2
      int x2 = drawAreaLeft + (int)((theoreticalXValues[i+1] - minDataValue) / rangeX * plotWidth);
      //--- Screen Y2
      int y2 = drawAreaBottom - (int)(theoreticalYValues[i+1] / rangeY * plotHeight);

      //--- Draw anti-aliased line with thickness
      for (int w = 0; w < curveLineWidth; w++)
        {
         mainCanvas.LineAA(x1, y1 + w, x2, y2 + w, argbCurve);
        }
     }

   //--- Axis label font
   mainCanvas.FontSet("Arial Bold", labelFontSize);
   uint argbAxisLabel = ColorToARGB(clrBlack, 255);

   //--- X axis label
   string xAxisLabel = "Number of Successes (k)";
   mainCanvas.TextOut(currentWidthPixels / 2, currentHeightPixels - 20, xAxisLabel, argbAxisLabel, TA_CENTER);

   //--- Y axis label (vertical)
   string yAxisLabel = "Probability / Scaled Frequency";
   mainCanvas.FontAngleSet(900);
   mainCanvas.TextOut(12, currentHeightPixels / 2, yAxisLabel, argbAxisLabel, TA_CENTER);
   mainCanvas.FontAngleSet(0);
  }

//+------------------------------------------------------------------+
//| Draw statistics panel                                            |
//+------------------------------------------------------------------+
void DrawStatisticsPanel()
  {
   //--- Panel coordinates
   int panelX = statsPanelX;
   int panelY = HEADER_BAR_HEIGHT + statsPanelY;
   int panelWidth = statsPanelWidth;
   int panelHeight = statsPanelHeight;

   //--- Light background color
   color panelBgColor = LightenColor(themeColor, 0.9);
   //--- Semi-transparent alpha
   uchar bgAlpha = 153;
   uint argbPanelBg = ColorToARGB(panelBgColor, bgAlpha);
   uint argbBorder = ColorToARGB(themeColor, 255);
   uint argbText = ColorToARGB(clrBlack, 255);

   //--- Fill panel background with blending
   for (int y = panelY; y <= panelY + panelHeight; y++)
     {
      for (int x = panelX; x <= panelX + panelWidth; x++)
        {
         BlendPixelSet(mainCanvas, x, y, argbPanelBg);
        }
     }

   //--- Draw top border
   for (int x = panelX; x <= panelX + panelWidth; x++)
     {
      BlendPixelSet(mainCanvas, x, panelY, argbBorder);
     }
   //--- Draw right border
   for (int y = panelY; y <= panelY + panelHeight; y++)
     {
      BlendPixelSet(mainCanvas, panelX + panelWidth, y, argbBorder);
     }
   //--- Draw left border
   for (int y = panelY; y <= panelY + panelHeight; y++)
     {
      BlendPixelSet(mainCanvas, panelX, y, argbBorder);
     }

   //--- Set panel font
   mainCanvas.FontSet("Arial", panelFontSize);

   //--- Starting text position
   int textY = panelY + 6;
   int lineSpacing = panelFontSize + 1;

   //--- Display trials
   string trialsText = StringFormat("Trials (n): %d", numTrials);
   mainCanvas.TextOut(panelX + 8, textY, trialsText, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display probability
   string probText = StringFormat("Prob (p): %.2f", successProbability);
   mainCanvas.TextOut(panelX + 8, textY, probText, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display sample size
   string sampleText = StringFormat("Sample: %d", sampleSize);
   mainCanvas.TextOut(panelX + 8, textY, sampleText, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display mean
   string meanText = StringFormat("Mean: %.2f", sampleMean);
   mainCanvas.TextOut(panelX + 8, textY, meanText, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display standard deviation
   string stdDevText = StringFormat("StdDev: %.2f", sampleStandardDeviation);
   mainCanvas.TextOut(panelX + 8, textY, stdDevText, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display skewness
   string skewText = StringFormat("Skewness: %.3f", sampleSkewness);
   mainCanvas.TextOut(panelX + 8, textY, skewText, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display kurtosis
   string kurtText = StringFormat("Kurtosis: %.3f", sampleKurtosis);
   mainCanvas.TextOut(panelX + 8, textY, kurtText, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display Q1
   string p25Text = StringFormat("Q1 (25%%): %.1f", percentile25);
   mainCanvas.TextOut(panelX + 8, textY, p25Text, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display median
   string p50Text = StringFormat("Median (50%%): %.1f", percentile50);
   mainCanvas.TextOut(panelX + 8, textY, p50Text, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display Q3
   string p75Text = StringFormat("Q3 (75%%): %.1f", percentile75);
   mainCanvas.TextOut(panelX + 8, textY, p75Text, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display 95% CI
   string ci95Text = StringFormat("95%% CI: [%.2f, %.2f]", confidenceInterval95Lower, confidenceInterval95Upper);
   mainCanvas.TextOut(panelX + 8, textY, ci95Text, argbText, TA_LEFT);
   textY += lineSpacing;

   //--- Display 99% CI
   string ci99Text = StringFormat("99%% CI: [%.2f, %.2f]", confidenceInterval99Lower, confidenceInterval99Upper);
   mainCanvas.TextOut(panelX + 8, textY, ci99Text, argbText, TA_LEFT);
  }

//+------------------------------------------------------------------+
//| Draw legend                                                      |
//+------------------------------------------------------------------+
void DrawLegend()
  {
   //--- Legend coordinates
   int legendX = statsPanelX;
   int legendY = HEADER_BAR_HEIGHT + statsPanelY + statsPanelHeight;
   int legendWidth = statsPanelWidth;
   int legendHeightThis = legendHeight;

   //--- Light background
   color legendBgColor = LightenColor(themeColor, 0.9);
   uchar bgAlpha = 153;
   uint argbLegendBg = ColorToARGB(legendBgColor, bgAlpha);
   uint argbBorder = ColorToARGB(themeColor, 255);
   uint argbText = ColorToARGB(clrBlack, 255);

   //--- Fill legend background
   for (int y = legendY; y <= legendY + legendHeightThis; y++)
     {
      for (int x = legendX; x <= legendX + legendWidth; x++)
        {
         BlendPixelSet(mainCanvas, x, y, argbLegendBg);
        }
     }

   //--- Top border
   for (int x = legendX; x <= legendX + legendWidth; x++)
     {
      BlendPixelSet(mainCanvas, x, legendY, argbBorder);
     }
   //--- Right border
   for (int y = legendY; y <= legendY + legendHeightThis; y++)
     {
      BlendPixelSet(mainCanvas, legendX + legendWidth, y, argbBorder);
     }
   //--- Bottom border
   for (int x = legendX; x <= legendX + legendWidth; x++)
     {
      BlendPixelSet(mainCanvas, x, legendY + legendHeightThis, argbBorder);
     }
   //--- Left border
   for (int y = legendY; y <= legendY + legendHeightThis; y++)
     {
      BlendPixelSet(mainCanvas, legendX, y, argbBorder);
     }

   //--- Set legend font
   mainCanvas.FontSet("Arial", panelFontSize);

   //--- Legend item start
   int itemY = legendY + 10;
   int lineSpacing = panelFontSize;

   //--- Histogram sample
   uint argbHist = ColorToARGB(histogramColor, 255);
   mainCanvas.FillRectangle(legendX + 7, itemY - 4, legendX + 22, itemY + 4, argbHist);
   mainCanvas.TextOut(legendX + 27, itemY - 4, "Sample Histogram", argbText, TA_LEFT);
   itemY += lineSpacing;

   //--- Theoretical curve sample
   uint argbCurve = ColorToARGB(theoreticalCurveColor, 255);
   for (int i = 0; i < 15; i++)
     {
      BlendPixelSet(mainCanvas, legendX + 7 + i, itemY, argbCurve);
      BlendPixelSet(mainCanvas, legendX + 7 + i, itemY + 1, argbCurve);
     }
   mainCanvas.TextOut(legendX + 27, itemY - 4, "Theoretical PMF", argbText, TA_LEFT);
  }

//+------------------------------------------------------------------+
//| Draw resize indicator                                            |
//+------------------------------------------------------------------+
void DrawResizeIndicator()
  {
   //--- Indicator color
   uint argbIndicator = ColorToARGB(themeColor, 255);

   //--- Corner grip
   if (hoverResizeMode == RESIZE_CORNER || activeResizeMode == RESIZE_CORNER)
     {
      int cornerX = currentWidthPixels - resizeGripSize;
      int cornerY = currentHeightPixels - resizeGripSize;

      //--- Fill corner square
      mainCanvas.FillRectangle(cornerX, cornerY, currentWidthPixels - 1, currentHeightPixels - 1, argbIndicator);

      //--- Draw diagonal lines
      for (int i = 0; i < 3; i++)
        {
         int offset = i * 3;
         mainCanvas.Line(cornerX + offset, currentHeightPixels - 1, 
                         currentWidthPixels - 1, cornerY + offset, argbIndicator);
        }
     }

   //--- Right edge grip
   if (hoverResizeMode == RESIZE_RIGHT_EDGE || activeResizeMode == RESIZE_RIGHT_EDGE)
     {
      int indicatorY = currentHeightPixels / 2 - 15;
      mainCanvas.FillRectangle(currentWidthPixels - 3, indicatorY, 
                               currentWidthPixels - 1, indicatorY + 30, argbIndicator);
     }

   //--- Bottom edge grip
   if (hoverResizeMode == RESIZE_BOTTOM_EDGE || activeResizeMode == RESIZE_BOTTOM_EDGE)
     {
      int indicatorX = currentWidthPixels / 2 - 15;
      mainCanvas.FillRectangle(indicatorX, currentHeightPixels - 3, 
                               indicatorX + 30, currentHeightPixels - 1, argbIndicator);
     }
  }

//+------------------------------------------------------------------+
//| Check if mouse is over header                                    |
//+------------------------------------------------------------------+
bool IsMouseOverHeaderBar(int mouseX, int mouseY)
  {
   //--- Return true if coordinates inside header rectangle
   return (mouseX >= currentPositionX && mouseX <= currentPositionX + currentWidthPixels &&
           mouseY >= currentPositionY && mouseY <= currentPositionY + HEADER_BAR_HEIGHT);
  }

//+------------------------------------------------------------------+
//| Check if mouse is in resize zone                                 |
//+------------------------------------------------------------------+
bool IsMouseInResizeZone(int mouseX, int mouseY, ResizeDirection &resizeMode)
  {
   //--- Disabled resizing aborts
   if (!enableResizing) return false;

   //--- Mouse relative to canvas
   int relativeX = mouseX - currentPositionX;
   int relativeY = mouseY - currentPositionY;

   //--- Right edge detection
   bool nearRightEdge = (relativeX >= currentWidthPixels - resizeGripSize && 
                         relativeX <= currentWidthPixels &&
                         relativeY >= HEADER_BAR_HEIGHT && 
                         relativeY <= currentHeightPixels);

   //--- Bottom edge detection
   bool nearBottomEdge = (relativeY >= currentHeightPixels - resizeGripSize && 
                          relativeY <= currentHeightPixels &&
                          relativeX >= 0 && 
                          relativeX <= currentWidthPixels);

   //--- Corner detection
   bool nearCorner = (relativeX >= currentWidthPixels - resizeGripSize && 
                      relativeX <= currentWidthPixels &&
                      relativeY >= currentHeightPixels - resizeGripSize && 
                      relativeY <= currentHeightPixels);

   //--- Prioritize corner then edges
   if (nearCorner)
     {
      resizeMode = RESIZE_CORNER;
      return true;
     }
   else if (nearRightEdge)
     {
      resizeMode = RESIZE_RIGHT_EDGE;
      return true;
     }
   else if (nearBottomEdge)
     {
      resizeMode = RESIZE_BOTTOM_EDGE;
      return true;
     }

   //--- No resize zone
   resizeMode = NO_RESIZE;
   return false;
  }

//+------------------------------------------------------------------+
//| Handle canvas resizing                                           |
//+------------------------------------------------------------------+
void HandleCanvasResize(int mouseX, int mouseY)
  {
   //--- Mouse movement since start
   int deltaX = mouseX - resizeStartX;
   int deltaY = mouseY - resizeStartY;

   //--- Start with current dimensions
   int newWidth = currentWidthPixels;
   int newHeight = currentHeightPixels;

   //--- Apply horizontal resize if needed
   if (activeResizeMode == RESIZE_RIGHT_EDGE || activeResizeMode == RESIZE_CORNER)
     {
      newWidth = MathMax(MIN_CANVAS_WIDTH, resizeInitialWidth + deltaX);
     }

   //--- Apply vertical resize if needed
   if (activeResizeMode == RESIZE_BOTTOM_EDGE || activeResizeMode == RESIZE_CORNER)
     {
      newHeight = MathMax(MIN_CANVAS_HEIGHT, resizeInitialHeight + deltaY);
     }

   //--- Clamp to chart boundaries
   int chartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int chartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);

   newWidth = MathMin(newWidth, chartWidth - currentPositionX - 10);
   newHeight = MathMin(newHeight, chartHeight - currentPositionY - 10);

   //--- Update only if changed
   if (newWidth != currentWidthPixels || newHeight != currentHeightPixels)
     {
      currentWidthPixels = newWidth;
      currentHeightPixels = newHeight;

      //--- Resize canvas object
      mainCanvas.Resize(currentWidthPixels, currentHeightPixels);
      ObjectSetInteger(0, canvasObjectName, OBJPROP_XSIZE, currentWidthPixels);
      ObjectSetInteger(0, canvasObjectName, OBJPROP_YSIZE, currentHeightPixels);

      //--- Re-render everything
      RenderVisualization();
      ChartRedraw();
     }
  }

//+------------------------------------------------------------------+
//| Handle canvas dragging                                           |
//+------------------------------------------------------------------+
void HandleCanvasDrag(int mouseX, int mouseY)
  {
   //--- Mouse movement since start
   int deltaX = mouseX - dragStartX;
   int deltaY = mouseY - dragStartY;

   //--- New position
   int newX = canvasStartX + deltaX;
   int newY = canvasStartY + deltaY;

   //--- Get chart dimensions for clamping
   int chartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int chartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);

   //--- Keep canvas fully visible
   newX = MathMax(0, MathMin(chartWidth - currentWidthPixels, newX));
   newY = MathMax(0, MathMin(chartHeight - currentHeightPixels, newY));

   //--- Apply new position
   currentPositionX = newX;
   currentPositionY = newY;

   //--- Update object properties
   ObjectSetInteger(0, canvasObjectName, OBJPROP_XDISTANCE, currentPositionX);
   ObjectSetInteger(0, canvasObjectName, OBJPROP_YDISTANCE, currentPositionY);

   //--- Redraw chart
   ChartRedraw();
  }

//+------------------------------------------------------------------+
//| Interpolate between two colors                                   |
//+------------------------------------------------------------------+
color InterpolateColors(color startColor, color endColor, double factor)
  {
   //--- Extract start components
   uchar r1 = (uchar)((startColor >> 16) & 0xFF);
   uchar g1 = (uchar)((startColor >> 8) & 0xFF);
   uchar b1 = (uchar)(startColor & 0xFF);

   //--- Extract end components
   uchar r2 = (uchar)((endColor >> 16) & 0xFF);
   uchar g2 = (uchar)((endColor >> 8) & 0xFF);
   uchar b2 = (uchar)(endColor & 0xFF);

   //--- Linear interpolation for each channel
   uchar r = (uchar)(r1 + factor * (r2 - r1));
   uchar g = (uchar)(g1 + factor * (g2 - g1));
   uchar b = (uchar)(b1 + factor * (b2 - b1));

   //--- Return interpolated color
   return (r << 16) | (g << 8) | b;
  }

//+------------------------------------------------------------------+
//| Blend pixel with proper alpha blending                           |
//+------------------------------------------------------------------+
void BlendPixelSet(CCanvas &canvas, int x, int y, uint src)
  {
   //--- Skip out-of-bounds pixels
   if (x < 0 || x >= canvas.Width() || y < 0 || y >= canvas.Height()) return;
   
   //--- Get destination pixel
   uint dst = canvas.PixelGet(x, y);
   
   //--- Source alpha, RGB (0-1)
   double sa = ((src >> 24) & 0xFF) / 255.0;
   double sr = ((src >> 16) & 0xFF) / 255.0;
   double sg = ((src >> 8) & 0xFF) / 255.0;
   double sb = (src & 0xFF) / 255.0;
   
   //--- Destination alpha, RGB (0-1)
   double da = ((dst >> 24) & 0xFF) / 255.0;
   double dr = ((dst >> 16) & 0xFF) / 255.0;
   double dg = ((dst >> 8) & 0xFF) / 255.0;
   double db = (dst & 0xFF) / 255.0;
   
   //--- Output alpha
   double out_a = sa + da * (1 - sa);
   //--- Fully transparent result
   if (out_a == 0)
     {
      canvas.PixelSet(x, y, 0);
      return;
     }
   
   //--- Premultiplied output RGB
   double out_r = (sr * sa + dr * da * (1 - sa)) / out_a;
   double out_g = (sg * sa + dg * da * (1 - sa)) / out_a;
   double out_b = (sb * sa + db * da * (1 - sa)) / out_a;
   
   //--- Convert back to 0-255 with rounding
   uchar oa = (uchar)(out_a * 255 + 0.5);
   uchar or_ = (uchar)(out_r * 255 + 0.5);
   uchar og = (uchar)(out_g * 255 + 0.5);
   uchar ob = (uchar)(out_b * 255 + 0.5);
   
   //--- Assemble final ARGB
   uint out_col = ((uint)oa << 24) | ((uint)or_ << 16) | ((uint)og << 8) | (uint)ob;
   //--- Write blended pixel
   canvas.PixelSet(x, y, out_col);
  }

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   //--- Only process mouse move events
   if (id == CHARTEVENT_MOUSE_MOVE)
     {
      //--- Extract mouse coordinates and button state
      int mouseX = (int)lparam;
      int mouseY = (int)dparam;
      int mouseState = (int)sparam;

      //--- Store previous hover states for redraw decision
      bool previousHoverState = isHoveringCanvas;
      bool previousHeaderHoverState = isHoveringHeader;
      bool previousResizeHoverState = isHoveringResizeZone;

      //--- Update canvas hover flag
      isHoveringCanvas = (mouseX >= currentPositionX && mouseX <= currentPositionX + currentWidthPixels &&
                         mouseY >= currentPositionY && mouseY <= currentPositionY + currentHeightPixels);

      //--- Update header hover
      isHoveringHeader = IsMouseOverHeaderBar(mouseX, mouseY);

      //--- Update resize hover and mode
      isHoveringResizeZone = IsMouseInResizeZone(mouseX, mouseY, hoverResizeMode);

      //--- Redraw needed if any hover state changed
      bool needRedraw = (previousHoverState != isHoveringCanvas || 
                        previousHeaderHoverState != isHoveringHeader ||
                        previousResizeHoverState != isHoveringResizeZone);

      //--- Mouse button just pressed
      if (mouseState == 1 && previousMouseButtonState == 0)
        {
         //--- Start dragging if conditions met
         if (enableDragging && isHoveringHeader && !isHoveringResizeZone)
           {
            isDraggingCanvas = true;
            dragStartX = mouseX;
            dragStartY = mouseY;
            canvasStartX = currentPositionX;
            canvasStartY = currentPositionY;
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
            needRedraw = true;
           }
         //--- Start resizing if over grip
         else if (isHoveringResizeZone)
           {
            isResizingCanvas = true;
            activeResizeMode = hoverResizeMode;
            resizeStartX = mouseX;
            resizeStartY = mouseY;
            resizeInitialWidth = currentWidthPixels;
            resizeInitialHeight = currentHeightPixels;
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
            needRedraw = true;
           }
        } 
      //--- Mouse button still held
      else if (mouseState == 1 && previousMouseButtonState == 1)
        {
         //--- Continue drag
         if (isDraggingCanvas)
           {
            HandleCanvasDrag(mouseX, mouseY);
           }
         //--- Continue resize
         else if (isResizingCanvas)
           {
            HandleCanvasResize(mouseX, mouseY);
           }
        } 
      //--- Mouse button just released
      else if (mouseState == 0 && previousMouseButtonState == 1)
        {
         //--- End any active interaction
         if (isDraggingCanvas || isResizingCanvas)
           {
            isDraggingCanvas = false;
            isResizingCanvas = false;
            activeResizeMode = NO_RESIZE;
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
            needRedraw = true;
           }
        }

      //--- Redraw if state changed
      if (needRedraw)
        {
         RenderVisualization();
         ChartRedraw();
        }

      //--- Update last mouse position and button state
      lastMouseX = mouseX;
      lastMouseY = mouseY;
      previousMouseButtonState = mouseState;
     }
  }
//+------------------------------------------------------------------+