1 (изменено: Alectric, 2023-05-10 19:41:46)

Тема: AHK: Class Рисование графиков из массива данных

Класс позволяет рассматривать собранные данные в графическом виде.
Поддерживает следующие типы массивов:

  • простой массив с данными; [ 1, 2, 3, ...]

  • массив данных с меткой времени в формате AHK (YYYYMMDDHH24MISS); [ [1,20230510200000], [2,20230510200001], ...]

  • массив с меткой времени с миллисекундами;  [ [1,20230510200000,123], [2,20230510200001,432], ...]

  • массив массивов выше перечисленных типов.

Возможности:

  • автомасштаб;

  • режим отображения по точкам/по времени;

  • пауза перемещения;

  • переход на точку/время;

  • перемещение графика левой кнопкой мыши;

  • выделение и увеличение выделенного правой кнопкой мыши;

  • возврат к предыдущим параметрам после увеличения кликом правой кн. мыши;

  • отображение значения при наведении мыши на точку;

  • сглаживание "ступенек" линий;

  • регулировка толщины линии;

  • изменения размера шрифта;

  • маркеры диапазона значений с расчетом времени, среднего значения, среднеквадратичного отклонения;

  • маркеры и настройки работают только для выбранного графика;

  • выбор цвета графика.

Совместимость: Windows XP, 7, 10.

Важно!
Для удаления экземпляра класса обязательно необходимо вызвать метод "Delete" до удаления объекта.

Тема для обсуждения.

Пример:

SetBatchLines,-1

c:=1,d:=0,e:=1

arr1:=[]
arr2:=[1]
arr3:={}
arr3[1]:={}
arr3[2]:={}
arr3[3]:={}
arr3[4]:={}
arr3[5]:={}
arr3[6]:={}

gosub,ddd
settimer,ddd,100
gosub,add2
gosub,add3

gra1:=new Graph()
gra2:=new Graph()
gra3:=new Graph()

gra1.show(700,500)
gra2.show(800,600)
gra3.show(900,700)

gra1.DataArray:=arr1
gra2.DataArray:=arr2
gra3.DataArray:=arr3
return

f1::
gra1.show()
gra2.show()
return

f2::
gra3.show()
return

ddd:
{
  a+=0.1
  if (b<-1 or b>1)
    t:=!t
  if t
    b+=0.005
  else
    b-=0.005
  if (b>1)
  {
    random,d,-1000000000,1000000000
    random,e,-1000000000,1000000000
    random,c,0.5,1.5
  }
  random,var,50.5,100.5
  shift:=0
;var:=11
  Val:=(sin(a)*var*b+shift)

  arr1.Push([Val,A_Now])
  arr3[3].Push([-Val,A_Now,A_MSec])
  arr3[4].Push([Val,A_Now,A_MSec])
  arr3[5].Push([cos(-a*2)*10*b+10,A_Now,A_MSec])
  arr3[6].Push([-(cos(a*2)*10*b+10),A_Now,A_MSec])
}
return

add2:
  random,g,0,1
  if (g!=prevg)
  {
    arr2.Push(g)
    arr3[1].Push(g)
  }
  prevg:=g

  random,v,0,5000
  settimer,add2,% v
return

add3:
  random,g,0,1
  if (g!=prevg3)
  {
    arr3[2].Push(g)
  }
  prevg3:=g

  random,v,0,5000
  settimer,add3,% v
return

esc::
  gra1:=gra1.Delete()
  gra2:=gra2.Delete()
  gra3:=gra3.Delete()
  exitapp

Class:

class Graph
{
  static hModule:=DllCall("LoadLibrary","str","gdiplus","Ptr")
  static pToken, InstanceCount=0
  static WinText:="Special text to identify the ownership of the Graph window."
  ;gdiplus\
  static hGdipCreateFont:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipCreateFont","Ptr")
  static hGdipCreateFontFamilyFromName:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipCreateFontFamilyFromName","Ptr")
  static hGdipCreateFromHDC:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipCreateFromHDC","Ptr")
  static hGdipCreatePen1:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipCreatePen1","Ptr")
  static hGdipCreateSolidFill:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipCreateSolidFill","Ptr")
  static hGdipCreateStringFormat:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipCreateStringFormat","Ptr")
  static hGdipDeleteBrush:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDeleteBrush","Ptr")
  static hGdipDeleteFont:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDeleteFont","Ptr")
  static hGdipDeleteFontFamily:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDeleteFontFamily","Ptr")
  static hGdipDeletePen:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDeletePen","Ptr")
  static hGdipDeleteStringFormat:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDeleteStringFormat","Ptr")
  static hGdipDrawEllipse:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDrawEllipse","Ptr")
  static hGdipDrawLine:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDrawLine","Ptr")
  static hGdipDrawLines:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDrawLines","Ptr")
  static hGdipDrawString:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDrawString","Ptr")
  static hGdipFillRectangle:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipFillRectangle","Ptr")
  static hGdiplusShutdown:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdiplusShutdown","Ptr")
  static hGdiplusStartup:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdiplusStartup","Ptr")
  static hGdipMeasureString:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipMeasureString","Ptr")
  static hGdipSetSmoothingMode:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipSetSmoothingMode","Ptr")
  static hGdipSetStringFormatAlign:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipSetStringFormatAlign","Ptr")
  static hGdipSetTextRenderingHint:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipSetTextRenderingHint","Ptr")
  static hGdipReleaseDC:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipReleaseDC","Ptr")
  static hGdipDeleteGraphics:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDeleteGraphics","Ptr")
  static hGdipCreateBitmapFromHBITMAP:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipCreateBitmapFromHBITMAP","Ptr")
  static hGdipCreateTexture2:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipCreateTexture2","Ptr")
  static hGdipDisposeImage:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDisposeImage","Ptr")
  static hGdipDrawRectangle:=DllCall("GetProcAddress","Ptr",Graph.hModule,"AStr","GdipDrawRectangle","Ptr")

  static min:=-9223372036854775808
  static max:= 9223372036854775807
  static MaxTime:=30*24*3600000 ; миллисекунд

  ChooseColor(pRGB:=0,hOwner:=0,DlgX:=0,DlgY:=0,Palette*)
  {
    F:="ChooseColor"
    if isFunc(F)
    {
      tmp1:=%F%(pRGB,hOwner,DlgX,DlgY,Palette)
      return tmp1
    }
  }

  __Delete()
  {
    Graph.InstanceCount--
    if (Graph.InstanceCount=0)
    {
      DllCall(Graph.hGdiplusShutdown,"Ptr",Graph.pToken)
      Graph.pToken:=""
    }
;    tooltip,% "Деструктор вызван.`n" Graph.InstanceCount
;    sleep,1000
;    tooltip
  }

  Delete()
  {
    obm:=this.UpdateTimer
    settimer,% obm,Delete
    this.UpdateTimer:=""
    Number:=this.Number
    menu,GraphMenu%Number%settings,DeleteAll
    menu,GraphMenu%Number%,DeleteAll
    DllCall(Graph.hGdipDeleteGraphics,"UPtr",this.pGraphics)
    DllCall("SelectObject","Ptr",this.hDC,"Ptr",this.obm)
    DllCall("DeleteObject","UPtr",this.hBitmap)
    DllCall("ReleaseDC","Ptr",this.hGuiG,"Ptr",this.hDC)
    Gui,% this.hGuiHelp . ":Destroy"
    Gui,% this.hGuiTT . ":Destroy"
    Gui,% this.hGuiG . ":Destroy"
    Gui,% this.hGuiB . ":Destroy"
  }

  __New(Name="",UpdatePeriod=50)
  {
; Сохранить дефолтное окно
    SaveDefaultGui:=A_DefaultGui
; Запустить библиотеку гдиплюс
    if !Graph.pToken
    {
      VarSetCapacity(si,A_PtrSize=8 ? 24 : 16,0), si:=Chr(1)
      DllCall(Graph.hGdiplusStartup,"UPtr*",pToken,"Ptr",&si,"Ptr",0)
      Graph.pToken:=pToken
    }
    Graph.InstanceCount++
    this.Number:=Number:=Graph.InstanceCount

    if !Name
      this.Name:="График " Graph.InstanceCount
    this.UpdatePeriod:=UpdatePeriod

; Добавление меню
    ; Меню настроек
    obm:=ObjBindMethod(this,"MenuAlwaysOnTop")
    menu,GraphMenu%Number%settings,add,Всегда поверх окон,% obm

    obm:=ObjBindMethod(this,"MenuAutoScale")
    menu,GraphMenu%Number%settings,add,Автомасштаб,% obm
    menu,GraphMenu%Number%settings,check,Автомасштаб

    obm:=ObjBindMethod(this,"MenuSmooth")
    menu,GraphMenu%Number%settings,add,Сглаживать ступеньки,% obm
    menu,GraphMenu%Number%settings,check,Сглаживать ступеньки

    menu,GraphMenu%Number%settings,add

    obm:=ObjBindMethod(this,"MenuScaleXByTime")
    menu,GraphMenu%Number%settings,add,Масштабировать X по времени,% obm,+Radio
    menu,GraphMenu%Number%settings,check,Масштабировать X по времени
    this.ScaleXByTime:=true

    obm:=ObjBindMethod(this,"MenuScaleXByDots")
    menu,GraphMenu%Number%settings,add,Масштабировать X по точкам,% obm,+Radio

    menu,GraphMenu%Number%settings,add

    obm:=ObjBindMethod(this,"MenuResetFont")
    menu,GraphMenu%Number%settings,add,Сбросить размер шрифта,% obm

    obm:=ObjBindMethod(this,"MenuGColor")
    menu,GraphMenu%Number%settings,add,Выбрать цвет графика,% obm

    ; основное меню
    obm:=ObjBindMethod(this,"MenuClose")
    menu,GraphMenu%Number%,add,Закрыть,% obm

    obm:=ObjBindMethod(this,"MenuPause")
    menu,GraphMenu%Number%,add,Пауза,% obm

    obm:=ObjBindMethod(this,"MenuForward")
    menu,GraphMenu%Number%,add,Вперёд,% obm

    obm:=ObjBindMethod(this,"MenuBackward")
    menu,GraphMenu%Number%,add,Назад,% obm

    obm:=ObjBindMethod(this,"MenuGoTo")
    menu,GraphMenu%Number%,add,Перейти,% obm

    obm:=ObjBindMethod(this,"MenuMarkers")
    menu,GraphMenu%Number%,add,Маркеры,% obm

    menu,GraphMenu%Number%,add,Настр.,:GraphMenu%Number%settings

    obm:=ObjBindMethod(this,"MenuHelp")
    menu,GraphMenu%Number%,add,Справка,% obm

; Создание основного окна
    Gui,Graph%Number%:+HwndhGuiB -sysmenu +MinSize400x100 +Resize
    this.hGuiB:=hGuiB
    Gui,%hGuiB%:Margin,0,0
; Создание окна отрисовки графики
    Gui,Graphics%Number%:+HwndhGuiG +ownerGraph%Number% -Caption +ToolWindow +E0x08080020 ; WS_EX_LAYERED := 0x80000
    this.hGuiG:=hGuiG
    Gui,%hGuiG%:Show, noactivate x0 y0 w1 h1,% "Graphics " Number
; Создание окна перехода по времени
    Gui,GraphGoToT%Number%:+HwndhGuiTT +ownerGraph%Number% -sysmenu -Resize
    this.hGuiTT:=hGuiTT
; Создание окна перехода к точке
    Gui,GraphGoToX%Number%:+HwndhGuiTX +ownerGraph%Number% -sysmenu -Resize
    this.hGuiTX:=hGuiTX
; Создание окна справки
    Gui,GraphHelp%Number%:+HwndhGuiHelp +ownerGraph%Number% -sysmenu -Resize
    this.hGuiHelp:=hGuiHelp

; Добавление элементов основного окна
    Gui,%hGuiB%:Menu,GraphMenu%Number%
    Gui,%hGuiB%:font,s10

    ; специальный текст
    Gui,%hGuiB%:add,text,w0 h0,% Graph.WinText

    ; Позиция графики
    Gui,Graph%Number%:Add,Edit,+hwndhGPos -HScroll -VScroll Disabled ReadOnly w400 h200
    this.hGPos:=hGPos

; Создание основного окна завершено
;    Gui,%hGuiB%:show,noactivate,% this.Name
;    Gui,%hGuiB%:hide

; Добавление элементов окна перехода по времени
    Gui,%hGuiTT%:add,DateTime,+hwndhTT xm+1 w160,HH:mm:ss        dd.MM.yyyy
    this.hTT:=hTT
    Gui,%hGuiTT%:add,button,+hwndhTTOkB xm+5 y+5 w70,Ok
    obm:=ObjBindMethod(this,"TTOk")
    GuiControl,+g,% hTTOkB,% obm
    Gui,%hGuiTT%:add,button,+hwndhTTCancel x+10 w70,Отмена
    obm:=ObjBindMethod(this,"TTCancel")
    GuiControl,+g,% hTTCancel,% obm

; Добавление элементов окна перехода к точке
    Gui,%hGuiTX%:add,Edit,+hwndhTX xm+1 w150,1
    this.hTX:=hTX
    Gui,%hGuiTX%:add,button,+hwndhTXOkB xm+5 y+5 w70,Ok
    obm:=ObjBindMethod(this,"TXOk")
    GuiControl,+g,% hTXOkB,% obm
    Gui,%hGuiTX%:add,button,+hwndhTXCancel x+10 w70,Отмена
    obm:=ObjBindMethod(this,"TXCancel")
    GuiControl,+g,% hTXCancel,% obm

; Добавление элементов окна справки
    HelpText=
    (LTrim
      Левая кнопка мыши - перемещение графика.
      Правая кнопка мыши - выделение и увеличение выделенного участка.
      Клик правой кнопкой - возврат к предыдущему масштабу.
    )
    Gui,%hGuiHelp%:add,text,+hwndhHelpText,% HelpText
    Gui,%hGuiHelp%:add,button,+hwndhHelpClose xm+10 y+5 w70,Закрыть
    obm:=ObjBindMethod(this,"HelpClose")
    GuiControl,+g,% hHelpClose,% obm

; Вернуть права дефолтному окну
    Gui,% SaveDefaultGui . ":Default"

; Создать графику
    this.Create()

; Настройки по умолчанию
    this.Font:="Lucida Console" ; шрифт
    this.FontSize:=12 ; размер шрифта
    this.SetFont(this.Font,this.FontSize)
    this.Font_Colour:=0xff000000 ; цвет шрифта
    this.Font_Back:=0xd0ffffff ; цвет фона шрифта
    this.WhiteBrush:=0xffffffff ; для общего фона
    this.GrayPen:=0xff808080 ; для оси
    this.GrayPen2:=0xffe0e0e0 ; для оси
    this.RedPen:=0xffff0000 ; для маркера
    this.LightGreenBrush:=0xffd5ffd5 ; для фона информации
    this.LightBlueBrush:=0xffd5d5ff ; для фона Меню
    this.BluePen:=0xff0000ff ; для графика
    this.Pen:=[]
    this.H:=[]

    this.ZoomX:=100
    this.ZoomT:=30000
    this.XTOffset:=0
    this.X_Offset:=0
    this.ZeroPos:=0
    this.ZeroPosU:=1
    this.YScale:=[]
    this.GPosH:=1

    this.AStartTime:=0
    this.StampOffset:=0
    this.MarkerOffset:=0
    this.StampsNum:=5
    this.XMarker:=[]
    this.MenuOffset:=0
    this.Old_XTOffset:=0

    this.Sel:=1
    this.Proc:=1
    this.prevArrSize:=[]
    this.StampT:={}
    this.SearchStop:=[]
    this.SearchSkip:=[]
    this.LineW:=[]
    this.Smooth:=[]
    this.AutoScale:=[]
    this.Savehi_lim:=[]
    this.Savelo_lim:=[]
    this.SaveAutoScale:=[]
    this.prev_ArrSize:=[]
    this.Val_UnderGraph_Drawed:=[]
    this.UnderGX:=[]
    this.UnderGY:=[]
    this.UnderGV:=[]
    this.Lock_hi_lim:=[]
    this.Lock_lo_lim:=[]
    this.GHide:=[]

; Запомнить метку для таймера
    this.UpdateTimer:=ObjBindMethod(this,"UpdateGraph")

/*
форматы обрабатывемых данных

Data:=[1,2,3,4,5,6,76,56,3,45,6,22,45,234]
 - простой массив значений

Data:=[[1,234],[2,345],[3,456],[4,567]]
 - массив с меткой времени [[значение,время],[значение,время]...]

Data:=[[1,234,56],[2,345,67],[3,456,78],[4,567,89]]
 - массив с меткой времени с миллисекундами [[значение,время,миллисекунды],[значение,время,миллисекунды]...]

Добавить:
Data:=[[[1,234,56],[2,345,67]],[[1,234,56],[2,345,67]],[[1,234,56],[2,345,67]]]
 - массив любых выше перечисленных массивов
*/
  }

  DataArray[]
  {
    set
    {
      this.StampT:={}
      return this.Data:=value
    }
    get
    {
      return this.Data
    }
  }

  HitBox(X,Y,Rect)
  {
    return (X>Rect.X and X<Rect.X+Rect.W and Y>Rect.Y and Y<Rect.Y+Rect.H)
  }

  FloatFormatFix()
  {
; Если в вашем коде где либо использован SetFormat,Float, то расскоментируйте следующие строки
/*
    if this.SWFormat:=!this.SWFormat
    {
      this.SaveFormatFloat:=A_FormatFloat
      SetFormat,Float,0.15
    }
    else
    {
      tmp1:=this.SaveFormatFloat
      SetFormat,Float,%tmp1%
    }
*/
; Тут какая-то чехорда, нужно подумать как сделать лучше...
  }

;/////////////////////////////////////////////////////////////////////////////////////////////////
;|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|
;||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////||
;|||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||
;||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/||||
  UpdateGraph()
  {
    if this.GuiHiden
      return

    this.FloatFormatFix()
    DllCall("QueryPerformanceFrequency", "Int64*", frequency)
    DllCall("QueryPerformanceCounter","Int64*",EndTime)
    CycleTime:=round((EndTime-this.StartTime)/frequency*1000,1)
    DllCall("QueryPerformanceCounter", "Int64*", StartTime)
    this.StartTime:=StartTime

; Взять координаты мыши
    SaveCoordMode:=A_CoordModeMouse
    CoordMode,Mouse,Client
    MouseGetPos,mX,mY,mW,mC,2
    CoordMode,Mouse,% SaveCoordMode
    MouseIn:=(this.hGPos=mC)
    DeltaX:=this.OldmX-mX
    DeltaY:=this.OldmY-mY

    this.Pos:=this.DrawString(0,0,0,,,,this.Font_Colour,this.Font_Back)
; очистить поле
    this.FillRectangle(this.WhiteBrush,0,0,this.GPosW,this.GPosH_)

; Добавление координаты маркеру
    if (MouseIn and getkeystate("RButton","P"))
      this.XMarker[2]:=mX

; Пауза скролинга для режима отображения по времени
    if this.Pause
    {
      this.XTOffset+=CycleTime
      this.OldXTOffset+=CycleTime
    }

; Определение массива массивов
    ArAr:=isObject(this.Data[1]) and isObject(this.Data[1,1])
       or isObject(this.Data[1]) and !isObject(this.Data[1,1]) and (this.Data[1,2]="" or strlen(this.Data[1,2])!=14)
    if ArAr
      Ars:=this.Data.MaxIndex()
    else
      Ars:=1

    loop,% Ars
    {
      this.Proc:=a_index
      if (this.prevArrSize[this.Proc]="")
      {
        this.prevArrSize[this.Proc]:=1
        this.SearchStop[this.Proc]:=true
        this.SearchSkip[this.Proc]:=0
        this.LineW[this.Proc]:=1
        this.Smooth[this.Proc]:=true
        this.AutoScale[this.Proc]:=true
        this.GHide[this.Proc]:=false
        this.UpdateMenu()
      }

      if (this.YScale[this.Proc]="")
        this.YScale[this.Proc]:=1

      if (this.Pen[this.Proc]="")
      {
        random,Hue,0,255
        if this.Hue[this.Proc-1]
        {
          loop,100
          {
            random,Hue,0,255
            if (abs(this.Hue[this.Proc-1]-Hue)>45)
              break
          }
        }
        this.Pen[this.Proc]:=Graph.AHSVToARGB(255,Hue,255,192)
        this.Hue[this.Proc]:=Hue
      }

      if (this.SearchSkip[this.Proc] and this.Old_XTOffset>this.XTOffset)
      {
        this.SearchStop[this.Proc]:=true
        this.SearchSkip[this.Proc]:=0
      }
    }
    this.Old_XTOffset:=this.XTOffset


; Обработка массивов данных
    this.ValTipPos:=""
    loop:
    loop,% Ars
    {
      this.Proc:=a_index
      if ArAr
        this.pData:=this.Data[this.Proc]
      else
        this.pData:=this.Data

      ArrSize:=this.pData.MaxIndex()
      this.ArrSize[this.Proc]:=ArrSize
      this.TimeStampExist:=strlen(this.pData[ArrSize,2])=14
      TimeStampMillis:=this.pData[ArrSize,3]!=""

      Period:=this.GPosW/this.ZoomX ; количество пикселей на точку по X
      PerioT:=this.GPosW/this.ZoomT ; количество пикселей на миллисекунду по X
      this.PerioT:=PerioT
      Pixel:=this.GPosH/this.YScale[this.Proc] ; сколько пикселей занимает 1 единица по Y
      ZeroPos:=this.ZeroPosU
; Пауза скролинга для режима отображения по точкам
      if (this.Pause and this.prev_ArrSize[this.Proc] and ArrSize!=this.prev_ArrSize[this.Proc])
      {
        tmp1:=ArrSize-this.prev_ArrSize[this.Proc]
        this.X_Offset+=tmp1
        this.OldX_Offset+=tmp1
      }
      this.prev_ArrSize[this.Proc]:=ArrSize

; Перемещение графика
      L_Button:
      if (MouseIn and getkeystate("LButton","P") and mY<this.GPosH and !this.Setting)
      {
        if !this.GetmCoordOnce
        {
          this.LButtonStart:=a_tickcount
          this.Old_mX:=mX
          this.Old_mY:=mY
          this.OldX_Offset:=this.X_Offset
          this.OldXTOffset:=this.XTOffset
          this.GetmCoordOnce:=true
          this.OffsetSetting:=true
          this.ShiftStrt:=true
        }
        this.X_Offset:=this.OldX_Offset-round((this.Old_mX-mX)/Period)
        this.XTOffset:=this.OldXTOffset-round((this.Old_mX-mX)/PerioT)
        this.ConstrainXOffset()

        DeltaY:=this.Old_mY-mY
        if (this.Sel=this.Proc)
          this.ZeroPos+=DeltaY
        if (this.Sel=this.Proc)
          this.Old_mY:=mY

        if (this.OldX_Offset-this.X_Offset!=0)
          this.MouseMoved:=true
      }
      else if (this.GetmCoordOnce and !getkeystate("LButton","P"))
      {
        this.GetmCoordOnce:=false
        this.OffsetSetting:=false
        ; Добавление координаты маркеру
        if !this.MouseMoved
          this.XMarker[1]:=mX
        this.MouseMoved:=false
      }
      if !getkeystate("LButton","P")
        this.SearchStop[this.Proc]:=false
; Выделение участка
      R_Button:
      if (MouseIn and getkeystate("RButton","P") and mY<this.GPosH and !this.Setting)
      {
        if !this.GetStartPosRectOnce
        {
          this.GetStartPosRectOnce:=true
          this.RectXs:=mX
          this.RectYs:=mY
        }
        if (this.RectXs>mX)
          this.RectX:=mX
        else
          this.RectX:=this.RectXs
        if (this.RectYs>mY)
          this.RectY:=mY
        else
          this.RectY:=this.RectYs
        this.RectW:=abs(this.RectXs-mX)
        this.RectH:=abs(this.RectYs-mY)
        if (this.RectW>10 and this.RectH>10)
          this.DrawRectangle(this.Font_Colour,this.RectX,this.RectY,this.RectW,this.RectH)
        else
          this.DrawRectangle(this.GrayPen2,this.RectX,this.RectY,this.RectW,this.RectH)
      }
      else if (this.GetStartPosRectOnce and !getkeystate("RButton","P"))
      {
        this.GetStartPosRectOnce:=false
        if (this.RectW>10 and this.RectH>10)
        {
          this.RectZoom:=true
          this.SaveZoomX:=this.ZoomX
          this.SaveZoomT:=this.ZoomT
          this.SaveX_Offset:=this.X_Offset
          this.SaveXTOffset:=this.XTOffset
          this.SavePause:=this.Pause
          this.Pause:=true
          this.ShiftStrt:=true
          loop,% Ars
          {
            this.SaveAutoScale[a_index]:=this.AutoScale[a_index]
            this.SaveZeroPos:=this.ZeroPos
            this.SaveYScale[a_index]:=this.YScale[a_index]
            if this.AutoScale[a_index]
            {
              this.AutoScale[a_index]:=false
            }
            P:=this.GPosH/this.YScale[a_index] ; сколько пикселей занимает 1 единица по Y
            this.YScale[a_index]:=this.RectH/P
          }
          this.UpdateMenu()
          U:=this.SaveYScale[this.Sel]/this.GPosH ; сколько единиц занимает 1 пиксель по Y
          P:=this.GPosH/this.YScale[this.Sel] ; сколько пикселей занимает 1 единица по Y
          RectY2:=(this.RectY+this.RectH)
          this.ZeroPos-=((this.ZeroPosU-RectY2)*U)*P+(this.GPosH-this.ZeroPosU)
          this.ZeroPos:=round(this.ZeroPos)
          if !this.ScaleXByTime
          {
            this.ZoomX:=round(this.RectW/Period)+1
            this.X_Offset+=round((this.GPosW-(this.RectX+this.RectW))/Period)
          }
          else
          {
            this.ZoomT:=round(this.RectW/PerioT)
            this.XTOffset+=round((this.GPosW-(this.RectX+this.RectW))/PerioT)
          }
        }
        else if this.RectZoom
        {
          this.RectZoom:=false
          this.ZoomX:=this.SaveZoomX
          this.ZoomT:=this.SaveZoomT
          this.X_Offset:=this.SaveX_Offset
          this.XTOffset:=this.SaveXTOffset
          this.Pause:=this.SavePause
          this.ShiftStrt:=true
          this.ZeroPos:=this.SaveZeroPos
          loop,% Ars
          {
            this.YScale[a_index]:=this.SaveYScale[a_index]
            if ((this.SaveAutoScale[a_index] and !this.AutoScale[a_index]) or (!this.SaveAutoScale[a_index] and this.AutoScale[a_index]))
            {
              this.AutoScale[a_index]:=true
              this.UpdateMenu()
            }
          }
        }
      }

; Обработать колесо мыши
      Whell:
      if ((Graph.WheelUp or Graph.WheelDown) and Graph.HotKeymC=this.hGPos)
      {
        this.ShiftStrt:=true
        CoefHi:=mY/this.GPosH
        CoefLo:=(this.GPosH-mY)/this.GPosH
        tmp1:=round(this.ZoomX/20)+1
        tmt1:=round(this.ZoomT/20)+1
        if (tmp2>tmp3)
          tmp3:=tmp2
        if Graph.WheelUp
        {
          this.ZoomX-=tmp1
          this.ZoomT-=tmt1
        }
        if Graph.WheelDown
        {
          this.ZoomX+=tmp1
          this.ZoomT+=tmt1
        }
        if (this.ZoomX<10)
          this.ZoomX:=10
        if (this.ZoomX>100000)
          this.ZoomX:=100000
        if (this.ZoomT<1000)
          this.ZoomT:=1000
        if (this.ZoomT>Graph.MaxTime)
          this.ZoomT:=Graph.MaxTime
        this.Pause:=true
        tmp1:=(this.GPosW-mX)/Period
        tmp2:=(this.GPosW-mX)/(this.GPosW/this.ZoomX)
        this.X_Offset+=round(tmp1-tmp2)
        tmp1:=(this.GPosW-mX)/PerioT
        tmp2:=(this.GPosW-mX)/(this.GPosW/this.ZoomT)
        this.XTOffset+=round(tmp1-tmp2)
        this.ConstrainXOffset()
      }

; Добавление временной метки массиву без неё
      if !this.TimeStampExist
      {
        if !isObject(this.StampT[this.Proc])
        {
          this.StampT[this.Proc]:=[[A_Now,A_MSec]]
          this.prevArrSize[this.Proc]:=1
        }
        if (ArrSize!=this.prevArrSize[this.Proc])
        {
          tmp2:=this.StampT[this.Proc,this.prevArrSize[this.Proc]]
          tmp3:=Graph.TimeSub([A_Now,A_MSec],tmp2)
          tmp1:=ArrSize-this.prevArrSize[this.Proc]
          tmp3:=tmp3//tmp1
          loop,% tmp1
          {
            add:=a_index*tmp3
            this.StampT[this.Proc].Push(Graph.TimeAddMs(tmp2,add))
          }
          this.prevArrSize[this.Proc]:=ArrSize
        }
      }

; отрисовать сетку
      if (this.Sel=this.Proc)
      {
        tmpc:=Graph.AHSVToARGB(200,this.Hue[this.Proc],this.Hue[this.Proc]>255?0:100,this.Hue[this.Proc]>255?(this.Hue[this.Proc]>>8):255)
        LineH:=this.Pos.H*2
        RoundTo:=1
        GoAgain:
        Drawed:=0
        if (ZeroPos<=this.GPosH+1 and ZeroPos>=0)
        {
          loop,% ZeroPos//LineH
          {
            tmp1:=a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            if (Val=0)
              continue
            CoordY:=ZeroPos-Val*Pixel
            if (CoordY<0)
              break
            this.DrawString(" " Val,0,CoordY,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
          loop,% (this.GPosH-ZeroPos)//LineH
          {
            tmp1:=a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            if (Val=0)
              continue
            CoordY:=ZeroPos+Val*Pixel
            if (CoordY>this.GPosH)
              break
            this.DrawString("-" Val,0,CoordY-this.Pos.H,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
        }
        else if (ZeroPos>this.GPosH)
        {
          loop,% this.GPosH//LineH
          {
            tmp1:=ZeroPos-a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            CoordY:=ZeroPos-Val*Pixel
            if (prevCoordY!=CoordY and CoordY<=this.GPosH-this.Pos.H and CoordY>=0)
            {
              this.DrawString(" " Val,0,CoordY,,,,this.Font_Colour,tmpc)
              this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
              Drawed++
            }
            prevCoordY:=CoordY
          }
          if (Drawed<1)
          {
            RoundTo++
            if (RoundTo<10)
              goto,GoAgain
            tmp1:=ZeroPos-this.GPosH/2
            tmp2:=tmp1/Pixel
            Val:=round(tmp2)
            CoordY:=ZeroPos-Val*Pixel
            this.DrawString(" " Val,0,CoordY,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
        }
        else if (ZeroPos<0)
        {
          loop,% this.GPosH//LineH
          {
            tmp1:=-ZeroPos+a_index*LineH
            tmp2:=tmp1/Pixel
            Val:=round(tmp2,-(strlen(round(tmp2))-RoundTo))
            CoordY:=ZeroPos+Val*Pixel
            if (prevCoordY!=CoordY and CoordY<=this.GPosH+1 and CoordY>=this.Pos.H)
            {
              this.DrawString("-" Val,0,CoordY-this.Pos.H,,,,this.Font_Colour,tmpc)
              this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
              Drawed++
            }
            prevCoordY:=CoordY
          }
          if (Drawed<1)
          {
            RoundTo++
            if (RoundTo<10)
              goto,GoAgain
            tmp1:=-ZeroPos+this.GPosH/2
            tmp2:=tmp1/Pixel
            Val:=round(tmp2)
            CoordY:=ZeroPos+Val*Pixel
            this.DrawString("-" Val,0,CoordY-this.Pos.H,,,,this.Font_Colour,tmpc)
            this.DrawLine(this.GrayPen2,0,CoordY,this.GPosW,CoordY)
          }
        }
; отрисовать линию и текст нуля
        if (ZeroPos<=this.GPosH+1 and ZeroPos>=0)
        {
          this.DrawLine(this.GrayPen,0,ZeroPos,this.GPosW,ZeroPos)
          tmp0:=ZeroPos+this.Pos.H//2
          tmp1:=(this.GPosH>tmp0) ? ZeroPos-this.Pos.H//2 : this.GPosH-this.Pos.H
          this.DrawString("0",0,tmp1,,,,this.Font_Colour,tmpc)
        }
      }
; сетка отрисована

      if !ArrSize
        this.MessagePos:=this.DrawString("Нет данных",this.GPosW//2-this.MessagePos.W//2,this.GPosH//2-this.MessagePos.H//2,,,,this.Font_Colour,this.Font_Back)
      if (this.SearchSkip[this.Proc]>0)
        this.SearchSkip[this.Proc]--

; Отрисовка графика ----------------------------------------------------------------
      hi_lim:=Graph.min
      lo_lim:=Graph.max
      Oldi:=0
      NotScaleXByTime:
      this.LockYP[this.Proc]:=0
      this.LockYM[this.Proc]:=0
      if isobject(this.StampPos)
        StampLine:=this.StampPos.W/2
      else
        StampLine:=0
      this.Val_UnderGraph_Drawed[this.Proc]:=false
      if !this.ScaleXByTime
      {
        Old_CoordY:=ZeroPos
        Old_CoordX:=this.GPosW+10
        GPoints:=[[Old_CoordX,Old_CoordY]]
        if !this.GHide[this.Proc]
        loop,% this.GPosW
        {
          iLast:=a_index=this.GPosW
          i:=round(ArrSize-(a_index/Period+1))-this.X_Offset
          if (i<1 or i>ArrSize) and !iLast
            continue
          if iLast
          {
            if (i-1>0)
              i--
            else
              i:=Oldi
          }
          Aindex++
          if (Aindex=1 and i+1<=ArrSize)
          {
            if !this.TimeStampExist
              Value:=this.pData[i]
            else
              Value:=this.pData[i,1]
            tmp1:=Value?Value:0
            Value:=Value!=""?Value:"Null"
            if (tmp1!="")
            {
              hi_lim:=tmp1>hi_lim ? tmp1 : hi_lim
              lo_lim:=tmp1<lo_lim ? tmp1 : lo_lim
            }
            CoordY:=ZeroPos-tmp1*Pixel
            if (CoordY<-1)
              CoordY:=0
            else if (CoordY>this.GPosH+1)
              CoordY:=this.GPosH
            GPoints.Push([this.GPosW,CoordY])
          }
          if !this.TimeStampExist
            Value:=this.pData[i]
          else
            Value:=this.pData[i,1]
          tmp1:=Value?Value:0
          Value:=Value!=""?Value:"Null"
          if (tmp1!="")
          {
            hi_lim:=tmp1>hi_lim ? tmp1 : hi_lim
            lo_lim:=tmp1<lo_lim ? tmp1 : lo_lim
          }
          CoordX:=this.GPosW-a_index
          CoordY:=ZeroPos-tmp1*Pixel
          SkipLine:=false
          if (CoordY<-1)
          {
            CoordY:=0
            SkipLine:=true
            this.LockYP[this.Proc]++
          }
          else if (CoordY>this.GPosH+1)
          {
            CoordY:=this.GPosH
            SkipLine:=true
            this.LockYM[this.Proc]++
          }
          NewStamp:=false
          NewXMarker:=false
          if (Oldi!=i)
          {
            ; линия графика
            if !this.Smooth[this.Proc]
            {
              GPoints.Push([Old_CoordX,CoordY])
              Old_CoordY:=CoordY
            }
            GPoints.Push([CoordX,CoordY])
            ; Отрисовка подробностей при малом увеличении
            if (Oldi-i>1)
            {
              max:=Graph.min
              min:=Graph.max
              loop,% Oldi-i+1
              {
                j:=i+a_index-1
                if (j<1 or j>ArrSize)
                  continue
                if !this.TimeStampExist
                  tmp1:=this.pData[j]
                else
                  tmp1:=this.pData[j,1]
                min:=min<tmp1?min:tmp1
                max:=max>tmp1?max:tmp1
                if (tmp1!="")
                {
                  hi_lim:=tmp1>hi_lim ? tmp1 : hi_lim
                  lo_lim:=tmp1<lo_lim ? tmp1 : lo_lim
                }
              }
              min:=ZeroPos-min*Pixel
              max:=ZeroPos-max*Pixel
              if (min<0)
                min:=1
              if (min>this.GPosH)
                min:=this.GPosH
              if (max<0)
                max:=0
              if (max>this.GPosH)
                max:=this.GPosH-1
              GPoints.Push([CoordX,min])
              GPoints.Push([CoordX,max])
              GPoints.Push([CoordX,CoordY])
            }
            ; Дорисовать точки при большом увеличении
            if (Old_CoordX-CoordX>3)
              this.DrawEllipse(this.Pen[this.Proc],CoordX-1,CoordY-1,2,2,this.LineW[this.Proc])

            ; текст значения под курсором
            if (!this.Val_UnderGraph_Drawed[this.Proc] and MouseIn)
            if (mX>=CoordX and mX<=CoordX+20)
            if (mY>CoordY and mY<CoordY+20)
            {
              this.Val_UnderGraph_Drawed[this.Proc]:=true
              this.UnderGX[this.Proc]:=CoordX
              this.UnderGY[this.Proc]:=CoordY
              this.UnderGV[this.Proc]:=Value
            }
            Old_CoordX:=CoordX
            Old_CoordY:=CoordY
            Oldi:=i
            NewStamp:=true
            if (this.Sel=this.Proc)
              NewXMarker:=true
          }
          ; линия и текст временой шкалы
          if (this.Sel=this.Proc)
          {
            if Period>=1
              StampLine++
            else
              StampLine+=1
            if !isobject(this.StampPos)
              tmp1:=0
            else
              tmp1:=this.StampPos.W+2
            if (NewStamp and StampLine>tmp1) or (!Stamp and a_index=this.GPosW)
            {
              StampLine:=0
              Stamp:=true
              this.DrawLine(this.GrayPen2,CoordX,0,CoordX,this.GPosH)
              if !this.TimeStampExist
              {
                FormatTime,tmp10,% this.StampT[this.Proc,i,1],HH:mm:ss
                tmp10.="`n" this.StampT[this.Proc,i,2] "ms"
              }
              else
              {
                FormatTime,tmp10,% this.pData[i,2],dd.MM.yyyy'`n'HH:mm:ss
                if (TimeStampMillis and this.pData[i,3]!="")
                  tmp10.="." this.pData[i,3]
              }
              StampPos:=this.DrawString(tmp10,CoordX-this.StampPos.W/2,this.GPosH,,,,this.Font_Colour,this.GrayPen2)
              if !GetSizeOnce
              {
                GetSizeOnce:=true
                this.StampPos:=StampPos
                this.StampOffset:=this.StampPos.H
              }
              if (prevStampPos.w)
              {
                if (this.StampPos.X+this.StampPos.w>prevStampPos.X)
                  this.StampsNum--
                if (this.StampPos.X+this.StampPos.w*2+5<prevStampPos.X)
                  this.StampsNum++
                if this.StampsNum<2
                  this.StampsNum:=2
              }
              prevStampPos:=this.StampPos
            }
            ; маркеры
            tmp1:=this.GPosW-a_index
            loop,2
            {
              if (NewXMarker and !XMarker%a_index% and tmp1<this.XMarker[a_index])
              {
                XMarker%a_index%:=true
                XMarker%a_index%i:=i
                XMarker%a_index%Coord:=tmp1
              }
            }
            NewXMarker:=false
          }
        }
        ; рисовать линию графика
        if !this.GHide[this.Proc]
          this.DrawLines(this.Pen[this.Proc],GPoints,this.LineW[this.Proc])
        ; блокировать перемещение по Y
        this.LockYP[this.Proc]:=(this.LockYP[this.Proc]>=Aindex-3)?1:0
        this.LockYM[this.Proc]:=(this.LockYM[this.Proc]>=Aindex-3)?1:0
      }

      ScaleXByTime:
      if this.ScaleXByTime
      {
        Strt:=Graph.TimeSubMs([A_Now,A_MSec],this.XTOffset)
        if (this.Pause and (!this.SaveStrt[1] or this.OffsetSetting or this.ShiftStrt))
        {
          this.SaveStrt:=Strt
          this.ShiftStrt:=false
        }
        else if (!this.Pause and this.SaveStrt[1])
          this.SaveStrt:=""
        if this.Pause
          Strt:=this.SaveStrt
        Endt:=Graph.TimeSubMs(Strt,this.ZoomT)
        ; линия и текст временой шкалы
        if (this.Sel=this.Proc)
        {
          loop,% this.GPosW
          {
            CoordX:=this.GPosW-(a_index-1)
            StampLine++
            if !isobject(this.StampPos)
              tmp1:=0
            else
              tmp1:=this.StampPos.W+2
            if (StampLine>tmp1) or (!Stamp and a_index=this.GPosW)
            {
              Stamp:=true
              StampLine:=0
              this.DrawLine(this.GrayPen2,CoordX,0,CoordX,this.GPosH)
              tmp2:=(this.ZoomT/this.GPosW)*(a_index-1)
              tmp1:=Graph.TimeSubMs(Strt,tmp2)
              FormatTime,tmp10,% tmp1[1],dd.MM.yyyy'`n'HH:mm:ss
              tmp10.="." tmp1[2]
              StampPos:=this.DrawString(tmp10,CoordX-this.StampPos.W/2,this.GPosH,,,,this.Font_Colour,this.GrayPen2)
              if !GetSizeOnce
              {
                GetSizeOnce:=true
                this.StampPos:=StampPos
                this.StampOffset:=this.StampPos.H
              }
              if (prevStampPos.w)
              {
                if (this.StampPos.X+this.StampPos.w>prevStampPos.X)
                  this.StampsNum--
                if (this.StampPos.X+this.StampPos.w*2+5<prevStampPos.X)
                  this.StampsNum++
                if this.StampsNum<2
                  this.StampsNum:=2
              }
              prevStampPos:=this.StampPos
            }
          }
        }
        ; найти первую точку
        if !this.GHide[this.Proc]
        loop,% ArrSize
        {
          i:=ArrSize-(a_index-1)-this.SearchSkip[this.Proc]
          if (i<1)
          {
            iStart:=1
            break
          }
          if !this.TimeStampExist
          {
            iTime:=this.StampT[this.Proc,i,1]
            iTimeMs:=this.StampT[this.Proc,i,2]
          }
          else
          {
            iTime:=this.pData[i,2]
            if TimeStampMillis
              iTimeMs:=this.pData[i,3]
            else
            {
              if !Same
              {
                iTimeMs:=0
                loop,1000
                {
                  if (iTime=this.pData[i-a_index,2])
                    Same++
                  else
                  {
                    SameT:=1000/(Same+1)
                    break
                  }
                }
              }
              if (Same>0)
              {
                iTimeMs:=round(SameT*Same)
                Same--
              }
            }
          }
          if (iTime>Strt[1] or (TimeStampMillis and iTime=Strt[1] and iTimeMs>=Strt[2]))
            continue
          else
          {
            iStart:=i=ArrSize?i:i+1
            break
          }
        }
        Old_CoordY:=ZeroPos
        Old_CoordX:=this.GPosW+10
        GPoints:=[[Old_CoordX,Old_CoordY]]
        SearchSkip:=this.SearchSkip[this.Proc]?this.SearchSkip[this.Proc]:0
        Same:=0
        Aindex:=0
        iLast:=false
        if !this.GHide[this.Proc]
        loop,% iStart
        {
          i:=iStart-(a_index-1)
          if !this.TimeStampExist
          {
            iTime:=this.StampT[this.Proc,i,1]
            iTimeMs:=this.StampT[this.Proc,i,2]
          }
          else
          {
            iTime:=this.pData[i,2]
            if TimeStampMillis
              iTimeMs:=this.pData[i,3]
            else
            {
              if !Same
              {
                iTimeMs:=0
                loop,1000
                {
                  tmp1:=this.pData[i-a_index,2]
                  if (iTime=tmp1)
                    Same++
                  else
                  {
                    SameT:=1000/(Same+1)
                    break
                  }
                }
              }
              if (Same>0)
              {
                iTimeMs:=round(SameT*Same)
                Same--
              }
            }
          }
          if (iTime<Endt[1])
            iLast:=true
          mms:=Strt[2]-iTimeMs
          if (mms<0)
          {
            mms+=1000
            iTime+=1,Seconds
          }
          ttt:=Strt[1]
          ttt-=iTime,Seconds
          CoordX:=this.GPosW-round(PerioT*(ttt*1000+mms))
          Aindex++
          if (Aindex=1 and !this.SearchStop[this.Proc])
          {
            tmp1:=ArrSize-i
            if (SearchSkip<tmp1)
              SearchSkip:=tmp1
          }
          if (CoordX!=Old_CoordX or iLast)
          {
            if !this.TimeStampExist
              Value:=this.pData[i]
            else
              Value:=this.pData[i,1]
            tmp1:=Value?Value:0
            if (Value!="" and Aindex)
            {
              hi_lim:=Value>hi_lim ? Value : hi_lim
              lo_lim:=Value<lo_lim ? Value : lo_lim
            }
            Value:=Value!=""?Value:"Null"
            CoordY:=ZeroPos-tmp1*Pixel
            SkipLine:=false
            if (CoordY<-1)
            {
              CoordY:=0
              SkipLine:=true
              this.LockYP[this.Proc]++
            }
            else if (CoordY>this.GPosH+1)
            {
              CoordY:=this.GPosH
              SkipLine:=true
              this.LockYM[this.Proc]++
            }
            ; линия графика
            if !this.Smooth[this.Proc]
            {
              GPoints.Push([Old_CoordX,CoordY])
              Old_CoordY:=CoordY
            }
            GPoints.Push([CoordX,CoordY])
            ; Отрисовка подробностей при малом увеличении
            if (Oldi-i>1)
            {
              max:=Graph.min
              min:=Graph.max
              loop,% Oldi-i
              {
                j:=i+a_index-1
                if (j<1 or j>ArrSize)
                  continue
                if !this.TimeStampExist
                  tmp1:=this.pData[j]
                else
                  tmp1:=this.pData[j,1]
                min:=min<tmp1?min:tmp1
                max:=max>tmp1?max:tmp1
                if (tmp1!="")
                {
                  hi_lim:=tmp1>hi_lim ? tmp1 : hi_lim
                  lo_lim:=tmp1<lo_lim ? tmp1 : lo_lim
                }
              }
              min:=ZeroPos-min*Pixel
              max:=ZeroPos-max*Pixel
              if (min<0)
                min:=1
              if (min>this.GPosH)
                min:=this.GPosH
              if (max<0)
                max:=0
              if (max>this.GPosH)
                max:=this.GPosH-1
              GPoints.Push([CoordX,min])
              GPoints.Push([CoordX,max])
              GPoints.Push([CoordX,CoordY])
            }
            ; Дорисовать точки при большом увеличении
            if (Old_CoordX-CoordX>3)
              this.DrawEllipse(this.Pen[this.Proc],CoordX-1,CoordY-1,2,2,this.LineW[this.Proc])

            ; текст значения под курсором
            if (!this.Val_UnderGraph_Drawed[this.Proc] and MouseIn)
            if (mX>=CoordX and mX<=CoordX+20)
            if (mY>CoordY and mY<CoordY+20)
            {
              this.Val_UnderGraph_Drawed[this.Proc]:=true
              this.UnderGX[this.Proc]:=CoordX
              this.UnderGY[this.Proc]:=CoordY
              this.UnderGV[this.Proc]:=Value
            }
            Old_CoordX:=CoordX
            Old_CoordY:=CoordY
            Oldi:=i
            if iLast
              break
            ; маркеры
            if (this.Sel=this.Proc)
            {
              loop,2
              {
                if (!XMarker%a_index% and CoordX<this.XMarker[a_index])
                {
                  XMarker%a_index%:=true
                  XMarker%a_index%i:=i
                  if !TimeStampMillis
                    MarkerTimeMs%a_index%:=format("{:03}",iTimeMs)
                  XMarker%a_index%Coord:=CoordX
                }
              }
            }
          }
        }
        ; рисовать линию графика
        if !this.GHide[this.Proc]
          this.DrawLines(this.Pen[this.Proc],GPoints,this.LineW[this.Proc])
        this.ZoomX:=Aindex
        this.SearchSkip[this.Proc]:=SearchSkip

        ; блокировать перемещение по Y
        this.LockYP[this.Proc]:=(this.LockYP[this.Proc]>=Aindex-3)?1:0
        this.LockYM[this.Proc]:=(this.LockYM[this.Proc]>=Aindex-3)?1:0
      }
      ; Автомасштаб
      if (this.AutoScale[this.Proc] and !this.GHide[this.Proc])
      {
        tmp1:=this.GPosH//2
        if (this.ZeroPosU>tmp1 and this.ZeroPosU<this.GPosH+1)
        {
          if (hi_lim>0)
          {
            tmp2:=this.GPosH-this.ZeroPosU
            this.YScale[this.Proc]:=abs(hi_lim)+tmp2/Pixel
          }
          else
            this.YScale[this.Proc]:=-lo_lim+this.ZeroPosU/Pixel
        }
        else if (this.ZeroPosU<=tmp1 and this.ZeroPosU>0)
        {
          if (lo_lim<0)
            this.YScale[this.Proc]:=-lo_lim+this.ZeroPosU/Pixel
          else
          {
            tmp2:=this.GPosH-this.ZeroPosU
            this.YScale[this.Proc]:=abs(hi_lim)+tmp2/Pixel
          }
        }
        if (this.YScale[this.Proc]=0)
          this.YScale[this.Proc]:=1
      }
      this.DrawLine(this.GrayPen2,0,this.GPosH,this.GPosW,this.GPosH)
      this.DrawLine(this.GrayPen2,0,this.GPosH+StampPos.H,this.GPosW,this.GPosH+StampPos.H)

      ; текст значения под курсором
      if this.Val_UnderGraph_Drawed[this.Proc]
      {
        tmpX:=this.UnderGX[this.Proc]+5<this.GPosW-this.ValTipPosW?this.UnderGX[this.Proc]+5:this.GPosW-this.ValTipPosW
        tmpY:=this.UnderGY[this.Proc]-this.Pos.H<0?0:(this.UnderGY[this.Proc]-this.Pos.H-5)
        this.DrawEllipse(0xffffffff,this.UnderGX[this.Proc]-3,this.UnderGY[this.Proc]-3,6,6,this.LineW[this.Proc])
        this.DrawEllipse(0xff000000,this.UnderGX[this.Proc]-2,this.UnderGY[this.Proc]-2,4,4,this.LineW[this.Proc])
        this.DrawEllipse(0xffffffff,this.UnderGX[this.Proc]-1,this.UnderGY[this.Proc]-1,2,2,this.LineW[this.Proc])
        tmpc:=Graph.AHSVToARGB(200,this.Hue[this.Proc],this.Hue[this.Proc]>255?0:100,this.Hue[this.Proc]>255?(this.Hue[this.Proc]>>8):255)
        this.ValTipPos:=this.DrawString(this.UnderGV[this.Proc],tmpX,tmpY,,,,this.Font_Colour,tmpc)
        this.ValTipPosW:=this.ValTipPos.W
      }
      if (this.Sel=this.Proc)
      {
        loop,2
        {
          if XMarker%a_index%Coord
          {
            if !this.TimeStampExist
            {
              MarkerTime%a_index%:=this.StampT[this.Proc,XMarker%a_index%i,1]
              FormatTime,tmp10,% MarkerTime%a_index%,HH:mm:ss
              MarkerTimeMs%a_index%:=this.StampT[this.Proc,XMarker%a_index%i,2]
              MarkerTimeFormated%a_index%:=tmp10 "." MarkerTimeMs%a_index%
              MarkerValue%a_index%:=this.pData[XMarker%a_index%i]
              MarkerText%a_index%.=tmp10 "." MarkerTimeMs%a_index% " " MarkerValue%a_index% ""
            }
            else
            {
              MarkerTime%a_index%:=this.pData[XMarker%a_index%i,2]
              FormatTime,tmp10,% MarkerTime%a_index%,dd.MM.yyyy HH:mm:ss
              if (TimeStampMillis and this.pData[XMarker%a_index%i,3]!="")
                MarkerTimeMs%a_index%:=this.pData[XMarker%a_index%i,3]
              tmp10.="." MarkerTimeMs%a_index%
              MarkerTimeFormated%a_index%:=tmp10
              MarkerValue%a_index%:=this.pData[XMarker%a_index%i,1]

              MarkerText%a_index%.=tmp10 " " this.pData[XMarker%a_index%i,1] ""
            }
          }
        }
      }
    } ; Конец цикла
    if (Graph.HotKeymC=this.hGPos)
    {
      Graph.WheelUp:=false
      Graph.WheelDown:=false
    }

; Отрисовать меню
    OnGraphMenu:
    MenuSPY:=StampPos.Y+StampPos.H
    SelPos:=[]
    tmpc:=Graph.AHSVToARGB(200,this.Hue[1],this.Hue[1]>255?0:100,this.Hue[1]>255?(this.Hue[1]>>8):255)
    SelPos[1]:=this.DrawString("1",0,MenuSPY,,,"Center vCenter",this.GHide[1]?tmpc:this.Font_Colour,tmpc)
    loop,% Ars-1
    {
      tmp1:=a_index+1
      tmpc:=Graph.AHSVToARGB(200,this.Hue[tmp1],this.Hue[tmp1]>255?0:100,this.Hue[tmp1]>255?(this.Hue[tmp1]>>8):255)
      SelPos[tmp1]:=this.DrawString(tmp1,SelPos[a_index].X+SelPos[a_index].W+5,MenuSPY,,,"Center vCenter",this.GHide[tmp1]?tmpc:this.Font_Colour,tmpc)
    }
    MenuSPY:=StampPos.Y+StampPos.H+SelPos[1].H
    if !this.ScaleXByTime
      this.ZoomPos:=this.DrawString("Zoom X = " this.ZoomX " точек",0,MenuSPY,,,"",this.Font_Colour,this.LightBlueBrush)
    else
    {
      tmp1:=Graph.MsToTime(this.ZoomT)
      tmp1[1]-=1000000
      FormatTime,tmp2,% tmp1[1],dd HH:mm:ss
      tmp2.="." tmp1[2]
      this.ZoomPos:=this.DrawString("Zoom X = " tmp2,0,MenuSPY,,,"",this.Font_Colour,this.LightBlueBrush)
    }
    this.SmoothPos:=this.DrawString("Сглаживание",this.ZoomPos.W+this.Pos.W,MenuSPY,,,"",this.Smooth[this.Sel]?this.Font_Colour:this.GrayPen,this.LightBlueBrush)
    this.LineWPos:=this.DrawString("Толщина " this.LineW[this.Sel],this.SmoothPos.X+this.SmoothPos.W+this.Pos.W,MenuSPY,,,"",this.Font_Colour,this.LightBlueBrush)
    this.FontSizePos:=this.DrawString("Шрифт " this.FontSize,this.LineWPos.X+this.LineWPos.W+this.Pos.W,MenuSPY,,,"",this.Font_Colour,this.LightBlueBrush)
    ; следующая строка
    this.AutoPos:=this.DrawString("Авто",0,this.ZoomPos.Y+this.ZoomPos.H,,,"",this.AutoScale[this.Sel]?this.Font_Colour:this.GrayPen,this.LightBlueBrush)
    this.ScalePos:=this.DrawString("Шкала " this.YScale[this.Sel],this.AutoPos.X+this.AutoPos.W,this.AutoPos.Y,,,"",this.Font_Colour,this.LightBlueBrush)
    ; Размер меню
    tmp1:=(this.AutoPos.H>this.ScalePos.H)?this.AutoPos.H:this.ScalePos.H
    this.MenuOffset:=this.ZoomPos.H+tmp1+SelPos[1].H

    ; хитбоксы
    loop,% Ars
    {
      SelHB%a_index%:=Graph.HitBox(mX,mY,SelPos[a_index])
      if (SelHB%a_index%)
      {
        SelHB:=true
        break
      }
    }
    LineWHB:=Graph.HitBox(mX,mY,this.LineWPos)
    FontSizeHB:=Graph.HitBox(mX,mY,this.FontSizePos)
    ZoomHB:=Graph.HitBox(mX,mY,this.ZoomPos)
    SmoothHB:=Graph.HitBox(mX,mY,this.SmoothPos)
    AutoHB:=Graph.HitBox(mX,mY,this.AutoPos)
    ScaleHB:=Graph.HitBox(mX,mY,this.ScalePos)
    HB:=AutoHB or ScaleHB or ZoomHB or SmoothHB or LineWHB or FontSizeHB or SelHB
    if (MouseIn and HB and !this.OffsetSetting and !this.Setting and A_Cursor="Arrow")
    {
      this.Setting:=true
      if AutoHB
      {
        TipText:="Автоматический масштаб`n`n Нажмите для включения/отключения"
        this.WhichSetting:=1
      }
      else if ScaleHB
      {
        TipText:="Масштаб шкалы`n`nЗажмите левую `nкн. мыши и потяните`n для изменения"
        this.WhichSetting:=2
      }
      else if ZoomHB
      {
        TipText:="Зажмите левую `nкн. мыши и потяните`n для изменения"
        this.WhichSetting:=3
      }
      else if LineWHB
      {
        TipText:="Толщина линии графика`n`nЗажмите левую `nкн. мыши и потяните`n для изменения"
        this.WhichSetting:=4
      }
      else if SelHB
      {
        TipText:="Нажмите левой кн. мыши чтобы`n выбрать график`n настройки которого`n будут изменяться`n`nПравой кн. мыши`n выключить график"
        this.WhichSetting:=5
      }
      else if FontSizeHB
      {
        TipText:="Размер шрифта`n`nЗажмите левую `nкн. мыши и потяните`n для изменения"
        this.WhichSetting:=6
      }
      else if SmoothHB
      {
        TipText:="Сглаживать ступеньки графика`n`n Нажмите для включения/отключения"
        this.WhichSetting:=7
      }
      this.TipPos:=this.DrawString(TipText,(this.GPosW-this.TipPos.W)//2,this.GPosH-this.TipPos.H,,,,this.Font_Colour,this.LightGreenBrush)
    }
    ; Изменение настроек
    if (getkeystate("LButton","P") and this.Setting)
    {
      if (this.WhichSetting=1)
      {
        this.AutoScale[this.Sel]:=!this.AutoScale[this.Sel]
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=2)
      {
        this.YScale[this.Sel]+=(round(abs(this.YScale[this.Sel])/20)+1)*DeltaY
        this.YScale[this.Sel]:=round(this.YScale[this.Sel])
        this.AutoScale[this.Sel]:=false
      }
      else if (this.WhichSetting=3)
      {
        this.ZoomX+=(round(abs(this.ZoomX)/20)+1)*DeltaY
        this.ZoomX:=(this.ZoomX>100000)?100000:(this.ZoomX<10)?10:round(this.ZoomX)
        this.ZoomT+=(round(abs(this.ZoomT)/20)+1)*DeltaY
        this.ZoomT:=(this.ZoomT>Graph.MaxTime)?Graph.MaxTime:(this.ZoomT<1000)?1000:round(this.ZoomT)
      }
      else if (this.WhichSetting=4)
      {
        this.LineW[this.Sel]+=DeltaY/10
        if (this.LineW[this.Sel]<1)
          this.LineW[this.Sel]:=1
        if (this.LineW[this.Sel]>5)
          this.LineW[this.Sel]:=5
        this.LineW[this.Sel]:=round(this.LineW[this.Sel],1)
      }
      else if (this.WhichSetting=5)
      {
        loop,% Ars
        {
          if (SelHB%a_index%)
          {
            this.Sel:=a_index
            this.UpdateMenu()
            break
          }
        }
        this.WhichSetting:=0
      }
      else if (this.WhichSetting=6)
      {
        this.FontSize+=DeltaY
        if (this.FontSize<8)
          this.FontSize:=8
        if (this.FontSize>70)
          this.FontSize:=70
        if (this.FontSize!=this.prevSize)
          this.SetFont(,this.FontSize)
      }
      else if (this.WhichSetting=7)
      {
        this.Smooth[this.Sel]:=!this.Smooth[this.Sel]
        this.WhichSetting:=0
      }
    }
    if (getkeystate("RButton","P") and this.Setting)
    {
      if (this.WhichSetting=5)
      {
        loop,% Ars
        {
          if (SelHB%a_index%)
          {
            this.GHide[a_index]:=!this.GHide[a_index]
            break
          }
        }
        this.WhichSetting:=0
      }
    }
    if (!getkeystate("LButton","P") and !getkeystate("RButton","P"))
    {
      this.Setting:=false
      this.WhichSetting:=0
    }

; Информация от маркеров
    Markers:
    max:=min:="`n"
    if this.ShowMarkers
    {
      loop,2
      {
        if XMarker%a_index%Coord
          this.DrawLine(this.RedPen,XMarker%a_index%Coord,0,XMarker%a_index%Coord,this.GPosH)
      }
      if !XMarker1Coord
        MarkerText1:="Нажмите левой кн. мыши на график"
      if !XMarker2Coord
        MarkerText2:="Нажмите правой кн. мыши на график"
      if (XMarker1Coord and XMarker2Coord)
      {
        i:=1, j:=2
        if (XMarker1Coord<XMarker2Coord)
          i:=2, j:=1
        tmp1:=Graph.TimeToMs(MarkerTime%i%)+MarkerTimeMs%i%
        tmp2:=Graph.TimeToMs(MarkerTime%j%)+MarkerTimeMs%j%
        ;YYYYMMDDHH24MISS
        MarkerTime%i%-=MarkerTime%j%,Seconds
        MarkerDeltaT:=1601
        MarkerDeltaT+=MarkerTime%i%,Seconds
        FormatTime,Y,% MarkerDeltaT,yyyy
        FormatTime,M,% MarkerDeltaT,MM
        FormatTime,D,% MarkerDeltaT,dd
        Y:=format("{:04}",Y-1601)
        M:=format("{:02}",M-1)
        D:=format("{:02}",D-1)
        if this.TimeStampExist
          MarkerDText.=D "." M "." Y " "

        tmp3:=Graph.MsToTime(tmp1-tmp2)
        tmp3:=tmp3[1]
        FormatTime,tmp10,% tmp3,HH:mm:ss
        MarkerDText.=tmp10

        if (MarkerTimeMs%i%!="")
        {
          StringRight,tmp1,% (tmp1-tmp2),3
          MarkerDText.="." tmp1
        }

        max:=Graph.min
        min:=Graph.max
        ValNum:=XMarker%i%i-XMarker%j%i+1
        loop,% ValNum
        {
          if !this.TimeStampExist
            Val:=this.pData[XMarker%j%i+A_Index-1]
          else
            Val:=this.pData[XMarker%j%i+A_Index-1,1]
          Summ+=Val
          if (Val>max)
            max:=Val
          if (Val<min)
            min:=Val
        }
        Mean:=Summ/ValNum
        loop,% ValNum
        {
          if !this.TimeStampExist
            Val:=this.pData[XMarker%j%i+A_Index-1]
          else
            Val:=this.pData[XMarker%j%i+A_Index-1,1]
          Deviation+=(Val-Mean)**2
        }
        StDeviation:=sqrt((Deviation/ValNum))
        StDeviation:="Среднекв. отклонение = " StDeviation
        Mean:="Среднее = " Mean
        min:="Min = " min
        max:="Max = " max
        ValNum:="Точек = " ValNum
      }
      String:=MarkerText1 "`n" MarkerText2 "`n" MarkerDText "`n" StDeviation "`n" Mean "`n" max "`n" min "`n" ValNum
      Pos:=this.DrawString(String,0,this.AutoPos.Y+this.AutoPos.H,this.GPosW,,"",this.Font_Colour,this.LightGreenBrush)
    }

; Подсказка о текущем увеличении
    if ((!this.ScaleXByTime and this.OldZoomX!=this.ZoomX) or this.OldZoomT!=this.ZoomT)
    {
      this.ShowZoomTip:=1000
      this.OldZoomX:=this.ZoomX
      this.OldZoomT:=this.ZoomT
    }
    if (this.ShowZoomTip>0)
    {
      this.ShowZoomTip-=CycleTime
      if !this.ScaleXByTime
        this.iZoomPos:=this.DrawString("Zoom`n" this.ZoomX "`nточек",(this.GPosW-this.iZoomPos.W)//2,0,,,,this.Font_Colour,this.LightGreenBrush)
      else
      {
        tmp1:=Graph.MsToTime(this.ZoomT)
        tmp1[1]+=-1000000
        FormatTime,tmp2,% tmp1[1],dd HH:mm:ss
        tmp2.="." tmp1[2]
        this.iZoomPos:=this.DrawString("Zoom`n" tmp2,(this.GPosW-this.iZoomPos.W)//2,0,,,,this.Font_Colour,this.LightGreenBrush)
      }
    }
; Подсказка о времени отрисовки
    DllCall("QueryPerformanceCounter","Int64*",EndTime)
    ElapsedTime:=round((EndTime-StartTime)/frequency*1000,1)
    this.ETPos:=this.DrawString(ElapsedTime "ms",this.GPosW-this.ETPos.W,this.ZoomPos.Y,,,,this.Font_Colour,this.Font_Back)
    if (Graph.HitBox(mX,mY,this.ETPos) and MouseIn)
      this.TipPos:=this.DrawString("Время отрисовки кадра",(this.GPosW-this.TipPos.W)//2,this.GPosH-this.TipPos.H,,,,this.Font_Colour,this.LightGreenBrush)

    if (ElapsedTime>1000)
    {
      if !this.ScaleXByTime
        this.ZoomX-=round(abs(this.ZoomX)/5)
      else
        this.ZoomT-=round(abs(this.ZoomT)/5)
    }

; Обновить изображение
    if !this.GuiHiden
      this.Update()
    this.OldmX:=mX
    this.OldmY:=mY
    this.FloatFormatFix()
  }
;||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/|||||||/||||
;|||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||||///|||
;||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////|||/////||
;|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|///////|
;/////////////////////////////////////////////////////////////////////////////////////////////////

  ConstrainXOffset()
  {
    if !this.ScaleXByTime
    {
      tmp1:=this.ZoomX//2
;      if (this.X_Offset>this.ArrSize[this.Proc]-tmp1)
;        this.X_Offset:=this.ArrSize[this.Proc]-tmp1
      if (this.X_Offset<-tmp1)
        this.X_Offset:=-tmp1
    }
    else
    {
      tmp1:=this.ZoomT//2
      if this.TimeStampExist
        tmp2:=Graph.TimeSub([A_Now,A_MSec],[this.pData[1,2],this.pData[1,3]])
      else
        tmp2:=Graph.TimeSub([A_Now,A_MSec],[this.StampT[this.Proc,1,1],this.StampT[this.Proc,1,2]])
;      if (this.XTOffset>tmp2-tmp1)
;        this.XTOffset:=tmp2-tmp1
      if (this.XTOffset<-tmp1)
        this.XTOffset:=-tmp1
    }
  }

  TimeSub(Time1,Time2)
  {
    T1:=Time1[1]
    T2:=Time2[1]
    ms:=Time1[2]-Time2[2]
    if (ms<0)
    {
      ms+=1000
      T2+=1,Seconds
    }
    T1-=T2,Seconds
    return T1*1000+ms
  }
  TimeSubMs(Time,ValueMs)
  {
    if (ValueMs=0)
      return Time
    m:=0
    if (ValueMs>0)
    {
      ms:=Time[2]-mod(ValueMs,1000)
      if (ms<0)
      {
        ms+=1000
        m:=1
      }
      t:=Time[1]
      t+=-(Floor(ValueMs/1000)+m),Seconds
    }
    else
    {
      ms:=mod(-ValueMs,1000)+Time[2]
      if (ms>999)
      {
        ms-=1000
        m:=1
      }
      t:=Time[1]
      t+=(Floor(-ValueMs/1000)+m),Seconds
    }
    return [t,format("{:03}",round(ms))]
  }
  TimeAddMs(Time,ValueMs)
  {
    T:=Time[1]
    T+=ValueMs//1000,Seconds
    ms:=Time[2]+mod(ValueMs,1000)
    if (ms>999)
    {
      ms-=1000
      T+=1,Seconds
    }
    return [T,format("{:03}",round(ms))]
  }
  TimeToMs(Time)
  {
    FormatTime,HH,% Time,HH
    FormatTime,MM,% Time,mm
    FormatTime,SS,% Time,ss
    return HH*3600000+MM*60000+SS*1000
  }
  MsToTime(Ms)
  {
    T:=1601
    T+=Ms//1000,Seconds
    Ms:=format("{:03}",mod(Ms,1000))
    return [T,Ms]
  }

  TTOk()
  {
    Gui,% this.hGuiTT . ":submit"
    GuiControlGet,ToTime,,% this.hTT
    tmp1:=A_Now
    tmp1-=ToTime,Seconds
    this.XTOffset:=tmp1*1000-round((this.GPosW/2)/this.PerioT)
    this.ConstrainXOffset()
    this.Pause:=true
    this.ShiftStrt:=true
  }
  TTCancel()
  {
    Gui,% this.hGuiTT . ":hide"
  }
  TXOk()
  {
    Gui,% this.hGuiTX . ":submit"
    GuiControlGet,ToX,,% this.hTX
    this.X_Offset:=this.ArrSize[this.Sel]-ToX
    this.ConstrainXOffset()
    this.Pause:=true
  }
  TXCancel()
  {
    Gui,% this.hGuiTX . ":hide"
  }

  HelpClose()
  {
    Gui,% this.hGuiHelp . ":hide"
  }

  MenuGColor()
  {
    tmp1:=Graph.ChooseColor(this.Pen[this.Sel],this.hGuiB)
    if (tmp1!="")
    {
      tmp2:=Graph.RGBToHSV(tmp1)
      if ((tmp2>>8)&0xff=0)
      {
        tmp3:=(tmp2&0xff)
        if (tmp3<127)
          tmp3:=127
        this.Hue[this.Sel]:=tmp3<<8
      }
      else
        this.Hue[this.Sel]:=tmp2>>16
      this.Pen[this.Sel]:=0xff000000 | tmp1
    }
  }
  MenuHelp()
  {
    hGuiHelp:=this.hGuiHelp
    Gui,%hGuiHelp%:show,,Справка
  }
  MenuGoTo()
  {
    if this.ScaleXByTime
    {
      hGuiTT:=this.hGuiTT
      Gui,%hGuiTT%:show,,Перейти на время
    }
    else
    {
      hGuiTX:=this.hGuiTX
      Gui,%hGuiTX%:show,,Перейти к точке
    }
  }
  MenuClose()
  {
    this.Hide()
  }
  MenuPause()
  {
    this.Pause:=!this.Pause
  }
  MenuForward()
  {
    this.Pause:=false
    this.XTOffset:=0
    this.X_Offset:=0
    this.ShiftStrt:=true
  }
  MenuBackward()
  {
    this.Pause:=true
    this.X_Offset:=this.ArrSize[this.Sel]-this.ZoomX//2
    if this.ScaleXByTime
    {
      tmp1:=A_Now
      if this.TimeStampExist
        tmp1-=this.pData[1,2],Seconds
      else
        tmp1-=this.StampT[this.Sel,1,1],Seconds
      this.XTOffset:=tmp1*1000-this.ZoomT//2
    }
    this.ShiftStrt:=true
  }
  MenuMarkers()
  {
    Add:=this.Pos.H*8
    if this.ShowMarkers:=!this.ShowMarkers
    {
      Size:=100+Add
      Number:=this.Number
      Gui,Graph%Number%:+MinSize300x%Size%
      WinGetPos,,,,BPosH,% "ahk_id" this.hGuiB
      WinMove,% "ahk_id" this.hGuiB,,,,,BPosH+Add
      this.MarkerOffset:=Add
    }
    else
    {
      Number:=this.Number
      Gui,Graph%Number%:+MinSize300x100
      WinGetPos,,,,BPosH,% "ahk_id" this.hGuiB
      WinMove,% "ahk_id" this.hGuiB,,,,,BPosH-Add
      this.MarkerOffset:=0
    }
  }
  MenuAutoScale()
  {
    if (this.Lock_hi_lim[this.Sel] or this.Lock_lo_lim[this.Sel])
      this.AutoScale[this.Sel]:=false
    Number:=this.Number
    if this.AutoScale[this.Sel]:=!this.AutoScale[this.Sel]
    {
      this.Lock_hi_lim[this.Sel]:=false
      this.Lock_lo_lim[this.Sel]:=false
      menu,GraphMenu%Number%settings,check,Автомасштаб
    }
    else
      menu,GraphMenu%Number%settings,uncheck,Автомасштаб
  }
  MenuAlwaysOnTop()
  {
    Number:=this.Number
    if this.AlwaysOnTop:=!this.AlwaysOnTop
    {
      Gui,Graph%Number%:+alwaysontop
      menu,GraphMenu%Number%settings,check,Всегда поверх окон
    }
    else
    {
      Gui,Graph%Number%:-alwaysontop
      menu,GraphMenu%Number%settings,uncheck,Всегда поверх окон
    }
  }
  MenuSmooth()
  {
    Number:=this.Number
    if this.Smooth[this.Sel]:=!this.Smooth[this.Sel]
      menu,GraphMenu%Number%settings,check,Сглаживать ступеньки
    else
      menu,GraphMenu%Number%settings,uncheck,Сглаживать ступеньки
  }
  MenuScaleXByTime()
  {
    Number:=this.Number
    this.ScaleXByTime:=true
    menu,GraphMenu%Number%settings,check,Масштабировать X по времени
    menu,GraphMenu%Number%settings,uncheck,Масштабировать X по точкам
  }
  MenuScaleXByDots()
  {
    Number:=this.Number
    this.ScaleXByTime:=false
    menu,GraphMenu%Number%settings,uncheck,Масштабировать X по времени
    menu,GraphMenu%Number%settings,check,Масштабировать X по точкам
  }
  MenuResetFont()
  {
    this.FontSize:=12
    this.SetFont(,this.FontSize)
  }
  UpdateMenu()
  {
    Number:=this.Number
    if this.AutoScale[this.Sel]
      menu,GraphMenu%Number%settings,check,Автомасштаб
    else
      menu,GraphMenu%Number%settings,uncheck,Автомасштаб
    if this.Smooth[this.Sel]
      menu,GraphMenu%Number%settings,check,Сглаживать ступеньки
    else
      menu,GraphMenu%Number%settings,uncheck,Сглаживать ступеньки
  }

  AHSVToARGB(a=255,h=0,s=0,v=0)
  {
    H:=h/255
    S:=s/255
    V:=v/255
    i:=Floor(H*6)
    f:=H*6-i
    p:=V*(1-S)
    q:=V*(1-f*S)
    t:=V*(1-(1-f)*S)
    sw:=mod(i,6)
    if (sw=0)
      r:=V, g:=t, b:=p
    if (sw=1)
      r:=q, g:=V, b:=p
    if (sw=2)
      r:=p, g:=V, b:=t
    if (sw=3)
      r:=p, g:=q, b:=V
    if (sw=4)
      r:=t, g:=p, b:=V
    if (sw=5)
      r:=V, g:=p, b:=q
    r:=(r*255)&0xff
    g:=(g*255)&0xff
    b:=(b*255)&0xff
    return (a<<24 | r<<16 | g<<8 | b)
  }
  RGBToHSV(RGB)
  {
    r:=(RGB>>16 & 0xff)/255
    g:=(RGB>>8 & 0xff)/255
    b:=(RGB & 0xff)/255
    min:=(r<g)?((r<b)?r:b):((g<b)?g:b)
    val:=(r>g)?((r>b)?r:b):((g>b)?g:b)
    delta:=val-min
    if (delta=0) ;pure blackness
      return (RGB & 0xff)
    if (r=val) ;between yellow and magenta
      hue:=(g-b)/delta
    else if (g=val) ;between cyan and yellow
      hue:=2+(b-r)/delta
    else ;between magenta and cyan
      hue:=4+(r-g)/delta
    if (hue<=0)
      hue+=6
    hue:=round(hue/6*255)
    sat:=round((Delta/val)*255)
    val:=round(val*255)
    return (hue<<16 | sat<<8 | val)
  }
  Show(x="",y="")
  {
    this.GuiHiden:=false
    hGuiB:=this.hGuiB
    if (x="")
      x:=a_screenwidth//2-400
    if (y="")
      y:=a_screenheight//2-200
    Gui,%hGuiB%:show,% "x" x " y" y,% this.Name
    obm:=this.UpdateTimer
    settimer,% obm,% this.UpdatePeriod
  }

  Hide()
  {
    this.GuiHiden:=true
    obm:=this.UpdateTimer
    settimer,% obm,off
    this.Update(0)
    Gui,% this.hGuiB . ":hide"
  }

  Create()
  {
    this.hDC:=DllCall("CreateCompatibleDC","UPtr",0)
    VarSetCapacity(bi,40,0)
    NumPut(40,bi,0,"uint")
    NumPut(A_ScreenWidth,bi,4,"uint")
    NumPut(A_ScreenHeight,bi,8,"uint")
    NumPut(1,bi,12,"ushort")
    NumPut(32,bi,14,"ushort")
    NumPut(0,bi,16,"uInt")
    this.hBitmap:=DllCall("CreateDIBSection"
                     ,"Ptr",this.hDC
                     ,"Ptr",&bi
                     ,"uint",0
                     ,"UPtr*",0
                     ,"Ptr",0
                     ,"uint",0,"Ptr")
    this.obm:=DllCall("SelectObject","Ptr",this.hDC,"Ptr",this.hBitmap)
    DllCall(Graph.hGdipCreateFromHDC,"Ptr",this.hDC,"UPtr*",pGraphics)
    this.pGraphics:=pGraphics
    DllCall(Graph.hGdipSetSmoothingMode,"UPtr",this.pGraphics,"Int",4)
  }

  Update(Alpha=255)
  {
    wingetpos,,,BPosW,BPosH,% "ahk_id" this.hGuiB
    if (this.PrevW!=BPosW or this.PrevH!=BPosH)
    {
      Pos:=Graph.GetClientSize(this.hGuiB)
      WinMove,% "ahk_id" this.hGPos,,0,0,Pos.W-1,Pos.H-1
      this.PrevW:=BPosW
      this.PrevH:=BPosH
    }
    wingetpos,GPosX,GPosY,GPosW,GPosH,% "ahk_id" this.hGPos
    this.GPosW:=GPosW
    this.GPosH:=GPosH-this.StampOffset-this.MarkerOffset-this.MenuOffset
    this.GPosH_:=GPosH
    this.ZeroPosU:=this.GPosH-this.ZeroPos
    VarSetCapacity(pt,8)
    NumPut(GPosX,pt,0,"UInt")
    NumPut(GPosY,pt,4,"UInt")
    DllCall("UpdateLayeredWindow"
            ,"Ptr",this.hGuiG
            ,"Ptr",0
            ,"Ptr",&pt
            ,"int64*",GPosW|GPosH<<32
            ,"Ptr",this.hDC
            ,"int64*",0
            ,"uint",0
            ,"UInt*",Alpha<<16|1<<24
            ,"uint",2)
  }

  GetClientSize(hwnd)
  {
    VarSetCapacity(rc,16)
    DllCall("GetClientRect","uint",hwnd,"uint",&rc)
    return {"X":NumGet(rc,0,"int"),"Y":NumGet(rc,4,"int"),"W":NumGet(rc,8,"int"),"H":NumGet(rc,12,"int")}
  }

  CreateSolidBrush(ARGB=0xff000000)
  {
    DllCall(Graph.hGdipCreateSolidFill,"UInt",ARGB,"UPtr*",pBrush)
    return pBrush
  }
  DeleteBrush(byref pBrush)
  {
    DllCall(Graph.hGdipDeleteBrush,"UPtr",pBrush)
    pBrush:=""
  }
  CreatePen(ARGB=0xff000000,w=1)
  {
    DllCall(Graph.hGdipCreatePen1,"UInt",ARGB,"float",w,"int",2,"UPtr*",pPen)
    return pPen
  }
  DeletePen(byref pPen)
  {
    DllCall(Graph.hGdipDeletePen,"UPtr",pPen)
    pPen:=""
  }

  FillRectangle(ARGB,x,y,w,h)
  {
    if (!this.pBrush or this.prevBrushColor!=ARGB)
    {
      if this.pBrush
        Graph.DeleteBrush(this.pBrush)
      this.pBrush:=Graph.CreateSolidBrush(ARGB)
    }
    this.prevBrushColor:=ARGB
    DllCall(Graph.hGdipFillRectangle
            ,"Ptr",this.pGraphics
            ,"Ptr",this.pBrush
            ,"float",x
            ,"float",y
            ,"float",w
            ,"float",h)
    return {"X":x,"Y":y,"W":w,"H":h}
  }
  DrawLine(ARGB,x1,y1,x2,y2,LineWidth=1)
  {
    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        Graph.DeletePen(this.pPen)
      this.pPen:=Graph.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    DllCall(Graph.hGdipDrawLine
            ,Ptr,this.pGraphics
            ,Ptr,this.pPen
            ,"float",x1
            ,"float",y1
            ,"float",x2
            ,"float",y2)
    return {"X1":x1,"Y1":y1,"X2":x2,"Y2":y2}
  }
  DrawLines(ARGB,Points,LineWidth=1)
  {
    tmp1:=Points.MaxIndex()
    if !tmp1
      return -1
    VarSetCapacity(PointF,8*tmp1)
    Loop,% tmp1
    {
      NumPut(Points[a_index,1],PointF,8*(A_Index-1),"float")
      NumPut(Points[a_index,2],PointF,(8*(A_Index-1))+4,"float")
    }

    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        Graph.DeletePen(this.pPen)
      this.pPen:=Graph.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    return DllCall(Graph.hGdipDrawLines
                   ,"Ptr",this.pGraphics
                   ,"Ptr",this.pPen
                   ,"Ptr",&PointF
                   ,"int",tmp1)
  }
  DrawEllipse(ARGB,x,y,w,h,LineWidth=1)
  {
    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        Graph.DeletePen(this.pPen)
      this.pPen:=Graph.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    DllCall(Graph.hGdipDrawEllipse
              ,"Ptr",this.pGraphics
              ,"Ptr",this.pPen
              ,"float",x
              ,"float",y
              ,"float",w
              ,"float",h)
    return {"X":x,"Y":y,"W":w,"H":h}
  }
  DrawRectangle(ARGB,x,y,w,h,LineWidth=1)
  {
    if (!this.pPen or this.prevPenColor!=ARGB or this.OldLineWidth!=LineWidth)
    {
      if this.pPen
        Graph.DeletePen(this.pPen)
      this.pPen:=Graph.CreatePen(ARGB,LineWidth)
    }
    this.OldLineWidth:=LineWidth
    this.prevPenColor:=ARGB
    DllCall(Graph.hGdipDrawRectangle
                ,"Ptr",this.pGraphics
                , "Ptr",this.pPen
                , "float",x
                , "float",y
                , "float",w
                , "float",h)
    return {"X":x,"Y":y,"W":w,"H":h}
  }

  SetFont(Font="",Size="",fStyle="",NoWrap="",RenderingHint="")
  {
    Style := 0, Styles := "Regular|Bold|Italic|BoldItalic|Underline|Strikeout"
    Loop, Parse, Styles, |
    {
      if RegExMatch(fStyle, "\b" A_loopField)
      Style |= (A_LoopField != "StrikeOut") ? (A_Index-1) : 8
    }

    if (!this.hFormat or (this.prevNoWrap!=NoWrap and NoWrap!=""))
    {
      if this.hFormat
      {
        DllCall(Graph.hGdipDeleteStringFormat,"UPtr",this.hFormat)
        this.hFormat:=""
      }
      DllCall(Graph.hGdipCreateStringFormat,"int",NoWrap ? 0x4000 | 0x1000 : 0x4000,"int",0,"UPtr*",hFormat)
      this.hFormat:=hFormat
      this.prevNoWrap:=NoWrap
    }

    if (!this.hFamily or (this.prevFont!=Font and Font!=""))
    {
      if this.hFamily
      {
        if this.hFont
        {
          DllCall(Graph.hGdipDeleteFont,"UPtr",this.hFont)
          this.hFont:=""
        }
        DllCall(Graph.hGdipDeleteFontFamily,"UPtr",this.hFamily)
        this.hFamily:=""
      }
      Font:=Font="" ? "Arial" : Font
      if !A_IsUnicode
      {
        nSize:=DllCall("MultiByteToWideChar","uint",0,"uint",0,"Ptr",&Font,"int",-1,"uint",0,"int",0)
        VarSetCapacity(wFont,nSize*2)
        DllCall("MultiByteToWideChar","uint",0,"uint",0,"Ptr",&Font,"int",-1,"Ptr",&wFont,"int",nSize)
      }
      DllCall(Graph.hGdipCreateFontFamilyFromName
              ,"Ptr",A_IsUnicode ? &Font : &wFont
              ,"uint",0
              ,"UPtr*",hFamily)
      this.hFamily:=hFamily
      this.prevFont:=Font
    }

    if (!this.hFont or (this.prevSize!=Size and Size!="") or (this.prevfStyle!=fStyle and fStyle!=""))
    {
      if this.hFont
      {
        DllCall(Graph.hGdipDeleteFont,"UPtr",this.hFont)
        this.hFont:=""
      }
      DllCall(Graph.hGdipCreateFont,"UPtr",this.hFamily,"float",(Size ? Size : 12),"int",fStyle="" ? 0 : Style,"int",0,"UPtr*",hFont)
      this.hFont:=hFont
      this.prevSize:=Size
      this.prevfStyle:=fStyle
    }

    RenderingHint:=(RenderingHint="") ? ((this.prevRenderingHint="") ? 0 : this.prevRenderingHint) : RenderingHint
    this.prevRenderingHint:=RenderingHint
    ; SystemDefault = 0
    ; SingleBitPerPixelGridFit = 1
    ; SingleBitPerPixel = 2
    ; AntiAliasGridFit = 3
    ; AntiAlias = 4
    DllCall(Graph.hGdipSetTextRenderingHint,"UPtr",this.pGraphics,"int",RenderingHint)
  }

  DrawString(Text,xpos=0,ypos=0,Width=0,Height=0,Options="Center",Colour=0xff000000,BackgroundColour=0)
  {
    RegExMatch(Options,"i)Top|Up|Bottom|Down|vCentre|vCenter",vPos)

    if !(this.hFamily and this.hFont and this.hFormat)
      this.SetFont()

    Align := 0, Alignments := "Near|Left|Centre|Center|Far|Right"
    Loop, Parse, Alignments, |
    {
      if RegExMatch(Options, "\b" A_loopField)
        Align |= A_Index//2.1      ; 0|0|1|1|2|2
    }
    DllCall(Graph.hGdipSetStringFormatAlign,"UPtr",this.hFormat,"int",Align)

    if (!this.FontpBrush or Colour!=this.prevColour)
    {
      if this.FontpBrush
        Graph.DeleteBrush(this.FontpBrush)
      this.FontpBrush:=Graph.CreateSolidBrush(Colour)
      this.prevColour:=Colour
    }

    Graph.CreateRectF(RC,xpos,ypos,Width,Height)
    ReturnRC:=this.MeasureString(Text,RC)
    if (!Width or !Height)
    {
      Width:=Width ? Width : ReturnRC.W
      Height:=Height ? Height : ReturnRC.H
      Graph.CreateRectF(RC,xpos,ypos,Width,Height)
      ReturnRC:=this.MeasureString(Text,RC)
    }
    if BackgroundColour
      this.FillRectangle(BackgroundColour,xpos,ypos,Width,Height)
    if vPos
    {
      if (vPos="vCentre" or vPos="vCenter")
        ypos+=(Height-ReturnRC.H)//2
      else if (vPos="Top" or vPos="Up")
        ypos:=ReturnRC.Y
      else if (vPos="Bottom" or vPos="Down")
        ypos+=Height-ReturnRC.H

      Graph.CreateRectF(RC,xpos,ypos,Width,ReturnRC.H)
      ReturnRC:=this.MeasureString(Text,RC)
    }

    sString:=Text
    if (!A_IsUnicode)
    {
      nSize:=DllCall("MultiByteToWideChar","uint",0,"uint",0,"Ptr",&sString,"int",-1,"Ptr",0,"int",0)
      VarSetCapacity(wString,nSize*2)
      DllCall("MultiByteToWideChar","uint",0,"uint",0,"Ptr",&sString,"int",-1,"Ptr",&wString,"int",nSize)
    }
    DllCall(Graph.hGdipDrawString
            ,"Ptr",this.pGraphics
            ,"Ptr",A_IsUnicode ? &sString : &wString
            ,"int",-1
            ,"Ptr",this.hFont
            ,"Ptr",&RC
            ,"Ptr",this.hFormat
            ,"Ptr",this.FontpBrush)

    return ReturnRC
  }
  CreateRectF(ByRef RectF, x, y, w, h)
  {
    VarSetCapacity(RectF, 16)
    NumPut(x, RectF, 0, "float"), NumPut(y, RectF, 4, "float"), NumPut(w, RectF, 8, "float"), NumPut(h, RectF, 12, "float")
  }

  MeasureString(sString,ByRef RectF)
  {
    VarSetCapacity(RC,16)
    if !A_IsUnicode
    {
      nSize:=DllCall("MultiByteToWideChar","uint",0,"uint",0,"Ptr",&sString,"int",-1,"uint",0,"int",0)
      VarSetCapacity(wString,nSize*2)
      DllCall("MultiByteToWideChar","uint",0,"uint",0,"Ptr",&sString,"int",-1,"Ptr",&wString,"int",nSize)
    }

    if !DllCall(Graph.hGdipMeasureString
            ,"Ptr",this.pGraphics
            ,"Ptr",A_IsUnicode ? &sString : &wString
            ,"int",-1
            ,"Ptr",this.hFont
            ,"Ptr",&RectF
            ,"Ptr",this.hFormat
            ,"Ptr",&RC
            ,"uint*",Chars
            ,"uint*",Lines)
    return {"X":NumGet(RC, 0, "float"),"Y":NumGet(RC, 4, "float"),"W":NumGet(RC, 8, "float"),"H":NumGet(RC, 12, "float"),"Chars":Chars,"Lines":Lines}
  }
}

#if WinActive("ahk_class AutoHotkeyGUI",Graph.WinText)
WheelUp::
MouseGetPos,,,,mC,2
Graph.WheelUp:=true
Graph.HotKeymC:=mC
return
WheelDown::
MouseGetPos,,,,mC,2
Graph.WheelDown:=true
Graph.HotKeymC:=mC
return
#if




/*  Windows Color Picker Plus (by rbrtryn)
    https-//autohotkey.com/board/topic/91229-windows-color-picker-plus/
    Edited by me lemasato-
        BGR2RGB()-
        Use Format() to convert the value to hex instead of SetFormat

    Function- ChooseColor([pRGB, hOwner, DlgX, DlgY, Palette])
        Displays a standard Windows dialog for choosing colors.
    Parameters-
        pRGB - The initial color to display in the dialog in RGB format.
               The default setting is Black.
        hOwner - The Window ID of the dialog's owner, if it has one. Defaults to
                0, i.e. no owner. If specified DlgX and DlgY are ignored.
        DlgX, DlgY - The X and Y coordinates of the upper left corner of the
                     dialog. Both default to 0.
        Palette - An array of up to 16 RGB color values. These become the
                  initial custom colors in the dialog.
    Remarks-
        The custom colors in the dialog are remembered between calls.

        If the user selects OK, the Palette array (if it exists) will be loaded
        with the custom colors from the dialog.
    Returns-
        If the user selects OK, the selected color is returned in RGB format
        and ErrorLevel is set to 0. Otherwise, the original pRGB value is
        returned and ErrorLevel is set to 1.
*/
ChooseColor(pRGB := 0, hOwner := 0, DlgX := 0, DlgY := 0, Palette*)
{
    static CustColors    ; Custom colors are remembered between calls
    static SizeOfCustColors := VarSetCapacity(CustColors, 64, 0)
    static StructSize := VarSetCapacity(ChooseColor, 9 * A_PtrSize, 0)

    CustData := (DlgX << 16) | DlgY    ; Store X in high word, Y in the low word

;___Load user's custom colors
    for Index, Value in Palette
        NumPut(BGR2RGB(Value), CustColors, (Index - 1) * 4, "UInt")

;___Set up a ChooseColor structure as described in the MSDN
    NumPut(StructSize, ChooseColor, 0, "UInt")
    NumPut(hOwner, ChooseColor, A_PtrSize, "UPtr")
    NumPut(BGR2RGB(pRGB), ChooseColor, 3 * A_PtrSize, "UInt")
    NumPut(&CustColors, ChooseColor, 4 * A_PtrSize, "UPtr")
    NumPut(0x113, ChooseColor, 5 * A_PtrSize, "UInt")
    NumPut(CustData, ChooseColor, 6 * A_PtrSize, "UInt")
    NumPut(RegisterCallback("ColorWindowProc"), ChooseColor, 7 * A_PtrSize, "UPtr")

;___Call the function
    ErrorLevel := ! DllCall("comdlg32\ChooseColor", "UPtr", &ChooseColor, "UInt")

;___Save the changes made to the custom colors
    if not ErrorLevel
        Loop 16
            Palette[A_Index] := BGR2RGB(NumGet(CustColors, (A_Index - 1) * 4, "UInt"))
    if !ErrorLevel ; modified by Alectric
      return BGR2RGB(NumGet(ChooseColor, 3 * A_PtrSize, "UINT"))
}

/*!
    Function- ColorWindowProc(hwnd, msg, wParam, lParam)
        Callback function used to modify the Color dialog before it is displayed
    Parameters-
        hwnd - Handle to the Color dialog window.
        msg - The message sent to the window.
        wParam - The handle to the control that has the keyboard focus.
        lParam - A pointer to the ChooseColor structure associated with the
                 Color dialog.
    Remarks-
        This is intended to be a private function, called only by ChooseColor.
        In response to a WM_INITDIALOG message, this function can be used to
        modify the Color dialog before it is displayed. Currently it just moves
        the window to a new X, Y location.
    Returns-
        If the hook procedure returns zero, the default dialog box procedure
        also processes the message. Otherwise, the default dialog box procedure
        ignores the message.
*/
ColorWindowProc(hwnd, msg, wParam, lParam)
{
    static WM_INITDIALOG := 0x0110

    if (msg <> WM_INITDIALOG)
        return 0

    hOwner := NumGet(lParam+0, A_PtrSize, "UPtr")
    if (hOwner)
        return 0

    DetectSetting := A_DetectHiddenWindows
    DetectHiddenWindows On
    CustData := NumGet(lParam+0, 6 * A_PtrSize, "UInt")
    DlgX := CustData >> 16, DlgY := CustData & 0xFFFF
    WinMove ahk_id %hwnd%, , %DlgX%, %DlgY%

    DetectHiddenWindows %DetectSetting%
    return 0
}

/*!
    Function- BGR2RGB(Color)
        Converts a BGR color value to a RGB one or vice versa.
    Parameters-
        Color - The BGR or RGB value to convert
    Returns-
        The converted value.
*/
BGR2RGB(Color)
{
    ret :=  (Color & 0xFF000000)
         | ((Color & 0xFF0000) >> 16)
         |  (Color & 0x00FF00)
         | ((Color & 0x0000FF) << 16)
    return ret
}
Win 10 x64
AHK v1.1.33.02
                       Справка тебе в помощь.