Торговые инструменты MQL5 (Часть 21): Добавление темы в стиле киберпанк в графики регрессии
Введение
Для парного анализа вы используете стандартные регрессионные графики, но они кажутся статичными и неинтересными во время длительных сессий. Это затрудняет концентрацию на корреляциях или выявление трендов среди монотонной визуализации, особенно в условиях низкой освещенности, где блики снижают читаемость. В базовых визуальны[ представлениях недостает динамических элементов для выделения ключевых точек данных или поддержания вовлеченности, что приводит к упущению информации на нестабильных рынках. Эта статья предназначена для разработчиков MetaQuotes Language 5 (MQL5) и алгоритмических трейдеров, стремящихся добавить иммерсивные темы в графические инструменты для улучшения визуализации.
В своей предыдущей статье (Часть 20) мы создали графический инструмент на основе Canvas в MQL5 для статистической корреляции и линейно-регрессионного анализа между двумя символами с возможностью перетаскивания и изменения размера. В Части 21 мы усовершенствуем этот инструмент, добавив режим темы в стиле киберпанк с неоновым свечением, анимацией, голографическими рамками и футуристическими элементами. Эта система с двумя темами включает в себя переключение с помощью кнопки, динамичные фоны со звездами и неоновые визуальные элементы для иммерсивного анализа пар. В статье рассмотрим следующие темы:
- Создание темы в стиле киберпанк для улучшенной визуализации регрессии
- Реализация средствами MQL5
- Тестирование на истории
- Заключение
В итоге у вас получится тематический график регрессии, сочетающий стандартный и киберпанковский стили. Перейдём к реализации!
Создание темы в стиле киберпанк для улучшенной визуализации регрессии
Тема киберпанк улучшает визуализацию регрессии за счет неонового свечения, анимированных импульсов, голографических рамок и динамических сеток, превращая стандартные графики в захватывающие футуристические экраны, которые привлекают внимание, сохраняя при этом точность анализа. В ней используется смешивание цветов, альфа-композиция и анимация на основе таймера для создания таких эффектов, как светящиеся линии, мерцающие фоны и панели в стиле glassmorphism, которые идеально подходят для торговли в ночном режиме или стилистических предпочтений.
Этот режим темы переключается с помощью кнопки, предлагая двойную функциональность со стандартными режимами отображения для универсального анализа рыночных пар. Мы добавим возможность переключения тем оформления с анимацией по таймеру, отображения элементов киберпанка, таких как неоновые точки и светящиеся контуры, с использованием смешения цветов. Кроме того, адаптируем стандартные функции для обеспечения совместимости двух режимов. Вкратце, ниже представлено наглядное представление наших целей.

Реализация средствами MQL5
Для улучшения программы на языке MQL5 нам потребуется добавить новые глобальные переменные и входные данные, чтобы адаптироваться к новым улучшениям и темам, которые мы будем внедрять.
Расширение входных данных и глобальных переменных для управления темами
Для поддержки новых характеристик киберпанка мы вводим входные параметры для управления свечением и глобальные переменные для цветов, флагов и отслеживания анимации.
//+------------------------------------------------------------------+ //| Canvas Graphing PART 2 - Statistical Regression (Cyberpunk).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 #property description "Dual Theme Regression: Standard & Cyberpunk Modes" //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "=== CYBERPUNK MODE SETTINGS ===" input double glowIntensity = 0.8; // Glow Intensity (0-1) //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ bool isCyberpunkThemeEnabled = false; // Initialize cyberpunk theme flag int themeToggleButtonX = 0; // Initialize theme button X int themeToggleButtonY = 0; // Initialize theme button Y const int THEME_TOGGLE_BUTTON_SIZE = 25; // Set theme toggle button size const color CYBER_PRIMARY_COLOR = C'0,191,255'; // Set cyber primary color const color CYBER_SECONDARY_COLOR = C'255,0,255'; // Set cyber secondary color const color CYBER_DATA_POINTS_COLOR = C'255,50,150'; // Set cyber data points color const color CYBER_REGRESSION_COLOR = C'0,150,255'; // Set cyber regression color int currentAnimationFrame = 0; // Initialize animation frame
Начнем с добавления новой группы ввода "=== CYBERPUNK MODE SETTINGS ===" со значением "glowIntensity" типа double, изменяющимся от 0 до 1, по умолчанию равным 0,8, для управления интенсивностью эффектов свечения в киберпанк-теме. Далее мы объявляем глобальные переменные для управления темой: "isCyberpunkThemeEnabled" инициализируется значением false в качестве флага для переключения режимов; "themeToggleButtonX" и "themeToggleButtonY" устанавливаются в 0 для позиционирования кнопки; константа "THEME_TOGGLE_BUTTON_SIZE" имеет размер 25 пикселей. Мы определяем специфические для киберпанка цвета, используя обозначение C'...': "CYBER_PRIMARY_COLOR" в ярко-голубом цвете C'0,191,255', "CYBER_SECONDARY_COLOR" в пурпурном цвете C'255,0,255', "CYBER_DATA_POINTS_COLOR" в розовом цвете C'255,50,150' и "CYBER_REGRESSION_COLOR" в синем цвете C'0,150,255', создавая яркие тематические палитры.
Вы можете выбрать любой другой цветовой оттенок, который вам нравится; мы просто посчитали это произвольным вариантом для демонстрации. Наконец, мы инициализируем "currentAnimationFrame" значением 0 для отслеживания прогресса анимации в динамических эффектах. Далее мы создадим вспомогательную функцию для плавного смешивания цветов в режиме киберпанка, чтобы избежать получения однородного цвета.
Реализация утилиты смешивания цветов
Для реализации динамических эффектов, таких как свечение и градиенты, мы определяем функцию интерполяции между двумя цветами.
//+------------------------------------------------------------------+ //| Blend colors | //+------------------------------------------------------------------+ color BlendColors(color color1, color color2, double ratio) { //--- Extract color1 red uchar r1 = (uchar)((color1 >> 16) & 0xFF); //--- Extract color1 green uchar g1 = (uchar)((color1 >> 8) & 0xFF); //--- Extract color1 blue uchar b1 = (uchar)(color1 & 0xFF); //--- Extract color2 red uchar r2 = (uchar)((color2 >> 16) & 0xFF); //--- Extract color2 green uchar g2 = (uchar)((color2 >> 8) & 0xFF); //--- Extract color2 blue uchar b2 = (uchar)(color2 & 0xFF); //--- Blend red uchar r = (uchar)(r1 * (1.0 - ratio) + r2 * ratio); //--- Blend green uchar g = (uchar)(g1 * (1.0 - ratio) + g2 * ratio); //--- Blend blue uchar b = (uchar)(b1 * (1.0 - ratio) + b2 * ratio); //--- Return blended color return (r << 16) | (g << 8) | b; }
Здесь мы реализуем функцию "BlendColors" для создания плавных цветовых переходов для получения киберпанк-эффектов, извлекая компоненты RGB из двух входных цветов с помощью побитовых сдвигов, затем смешивая каждый канал с помощью (1 - коэффициент) для первого и коэффициента для второго и повторно объединяя в новое значение цвета для динамических градиентов или анимации. Представьте это себе как волшебный миксер для смешивания цветов. Вы даете ему два цвета и говорите: "Смешай их в таком соотношении" (соотношение примерно 0,5 на половину). Получается новый цвет. В киберрежиме мы будем использовать его для создания плавных светящихся эффектов, например, смешивания синего и розового цветов для получения пурпурных огней. Без него цвета будут казаться малоконтрастными; с ним же всё будет выглядеть более гармонично и профессионально. Далее мы добавим кнопку переключения тем в заголовок, чтобы она позволяла нам легко переключаться между темами.
Добавление кнопки переключения темы
Для обеспечения возможности переключения режимов мы отображаем кнопку в заголовке и центрируем линию регрессии для обеспечения баланса.
//--- Adjust line drawing for centering in drawRegressionPlot //--- Draw line mainCanvas.LineAA(lineStartScreenX, lineStartScreenY + w - regressionLineWidth/2, lineEndScreenX, lineEndScreenY + w - regressionLineWidth/2, argbLine); //+------------------------------------------------------------------+ //| Draw theme toggle button | //+------------------------------------------------------------------+ void drawThemeToggleButton() { //--- Compute button X themeToggleButtonX = currentWidthPixels - THEME_TOGGLE_BUTTON_SIZE - 8; //--- Compute button Y themeToggleButtonY = (HEADER_BAR_HEIGHT - THEME_TOGGLE_BUTTON_SIZE) / 2; //--- Declare button color color buttonColor; //--- Check cyber mode if (isCyberpunkThemeEnabled) { //--- Set cyber color buttonColor = isHoveringThemeButton ? LightenColor(CYBER_PRIMARY_COLOR, 0.3) : CYBER_PRIMARY_COLOR; } else { //--- Set standard color buttonColor = isHoveringThemeButton ? LightenColor(themeColor, 0.3) : themeColor; } //--- Get button ARGB uint argbButton = ColorToARGB(buttonColor, 200); //--- Get border ARGB uint argbBorder = ColorToARGB(isCyberpunkThemeEnabled ? CYBER_SECONDARY_COLOR : themeColor, 255); //--- Fill button mainCanvas.FillRectangle(themeToggleButtonX, themeToggleButtonY, themeToggleButtonX + THEME_TOGGLE_BUTTON_SIZE, themeToggleButtonY + THEME_TOGGLE_BUTTON_SIZE, argbButton); //--- Draw border mainCanvas.Rectangle(themeToggleButtonX, themeToggleButtonY, themeToggleButtonX + THEME_TOGGLE_BUTTON_SIZE, themeToggleButtonY + THEME_TOGGLE_BUTTON_SIZE, argbBorder); //--- Set icon font mainCanvas.FontSet("Arial Black", 16); //--- Get text ARGB uint argbText = ColorToARGB(clrWhite, 255); //--- Set icon text string iconText = isCyberpunkThemeEnabled ? "◆" : "●"; //--- Draw icon mainCanvas.TextOut(themeToggleButtonX + THEME_TOGGLE_BUTTON_SIZE/2, themeToggleButtonY + 3, iconText, argbText, TA_CENTER); }
Сначала мы изменим функцию 'drawRegressionPlot' таким образом, чтобы линия регрессии была центрирована по вертикали. Это делается путем регулировки положения по оси Y в цикле по ширине. Вычтем половину ширины линии 'regression LineWidth/2' из начальной и конечной координат по оси Y на экране. Это обеспечивает сбалансированную толщину по всей рассчитанной траектории при рисовании с помощью функции 'LineAA'. Раньше толстая линия могла выглядеть смещенной от центра, как кривобокая дорога. Вычитание половины ширины идеально центрирует её. Это небольшое исправление, но оно придает линии аккуратный и профессиональный вид, как будто вы поправляете картину на стене.
Далее нам предстоит реализовать функцию 'drawThemeToggleButton'. Эта функция отображает кнопку переключения в заголовке для переключения режимов. Она вычисляет положение кнопки переключения как нижнее правое с отступом и выбирает цвет в зависимости от темы и состояния наведения курсора. Для придания динамичности используется 'LightenColor.' Функция заполняет прямоугольник полупрозрачным цветом ARGB, рисует границу цветом, соответствующим теме, и устанавливает жирный шрифт "Arial Black". Затем используя метод TextOut можно центрировать квадратную иконку для киберпанка или круглую иконку для стандартного стиля белым цветом ARGB. Как и раньше, можно использовать иконки из символов Wingdings. Однако мы выбрали его из-за простоты и технологичности оформления. Далее мы создаем визуализацию темы киберпанк. Давайте начнем с наложения основного слоя Canvas.
Рендеринг фона и границ темы киберпанк
Чтобы создать основу для этой темы, мы создаем звездные градиенты и анимированные голографические рамки.
//+------------------------------------------------------------------+ //| Draw cyberpunk background | //+------------------------------------------------------------------+ void drawCyberpunkBackground() { //--- Set top color color topColor = C'10,10,25'; //--- Set bottom color color bottomColor = C'20,5,30'; //--- Compute alpha uchar alphaChannel = (uchar)(255 * backgroundOpacityLevel); //--- Loop over rows for (int y = HEADER_BAR_HEIGHT; y < currentHeightPixels; y++) { //--- Compute factor double factor = (double)(y - HEADER_BAR_HEIGHT) / (currentHeightPixels - HEADER_BAR_HEIGHT); //--- Blend row color color rowColor = BlendColors(topColor, bottomColor, factor); //--- Get ARGB uint argbColor = ColorToARGB(rowColor, alphaChannel); //--- Loop over columns for (int x = 0; x < currentWidthPixels; x++) { //--- Check for star if (MathRand() % 800 == 0) { //--- Compute brightness uchar brightness = (uchar)(100 + MathRand() % 155); //--- Get star color uint starColor = ColorToARGB(C'255,255,255', brightness); //--- Blend star blendPixelSet(mainCanvas, x, y, starColor); } else { //--- Blend normal blendPixelSet(mainCanvas, x, y, argbColor); } } } } //+------------------------------------------------------------------+ //| Draw holographic border | //+------------------------------------------------------------------+ void drawHolographicBorder() { //--- Compute phase double phase = (currentAnimationFrame % 360) * M_PI / 180.0; //--- Compute glow factor double glowFactor = (MathSin(phase) + 1.0) * 0.5; //--- Blend border color color borderColor = BlendColors(CYBER_PRIMARY_COLOR, CYBER_SECONDARY_COLOR, glowFactor); //--- Loop for glow layers for (int i = 0; i < 3; i++) { //--- Compute alpha uchar alpha = (uchar)(80 * glowIntensity * (3 - i) / 3.0); //--- Get glow color uint glowColor = ColorToARGB(borderColor, alpha); //--- Draw top glow for (int x = i; x < currentWidthPixels - i; x++) { //--- Blend pixel blendPixelSet(mainCanvas, x, i, glowColor); } //--- Draw bottom glow for (int x = i; x < currentWidthPixels - i; x++) { //--- Blend pixel blendPixelSet(mainCanvas, x, currentHeightPixels - 1 - i, glowColor); } //--- Draw left glow for (int y = i; y < currentHeightPixels - i; y++) { //--- Blend pixel blendPixelSet(mainCanvas, i, y, glowColor); } //--- Draw right glow for (int y = i; y < currentHeightPixels - i; y++) { //--- Blend pixel blendPixelSet(mainCanvas, currentWidthPixels - 1 - i, y, glowColor); } } //--- Get border ARGB uint argbBorder = ColorToARGB(borderColor, 255); //--- Draw outer border mainCanvas.Rectangle(0, 0, currentWidthPixels - 1, currentHeightPixels - 1, argbBorder); //--- Draw inner border mainCanvas.Rectangle(1, 1, currentWidthPixels - 2, currentHeightPixels - 2, argbBorder); //--- Draw corner accents drawCornerAccents(borderColor); } //+------------------------------------------------------------------+ //| Draw corner accents | //+------------------------------------------------------------------+ void drawCornerAccents(color accentColor) { //--- Get accent ARGB uint argbAccent = ColorToARGB(accentColor, 255); //--- Set accent size int accentSize = 15; //--- Loop for top-left for (int i = 0; i < 3; i++) { //--- Draw horizontal mainCanvas.Line(5, 5 + i, accentSize, 5 + i, argbAccent); //--- Draw vertical mainCanvas.Line(5 + i, 5, 5 + i, accentSize, argbAccent); } //--- Loop for top-right for (int i = 0; i < 3; i++) { //--- Draw horizontal mainCanvas.Line(currentWidthPixels - accentSize - 1, 5 + i, currentWidthPixels - 6, 5 + i, argbAccent); //--- Draw vertical mainCanvas.Line(currentWidthPixels - 6 - i, 5, currentWidthPixels - 6 - i, accentSize, argbAccent); } //--- Loop for bottom-left for (int i = 0; i < 3; i++) { //--- Draw horizontal mainCanvas.Line(5, currentHeightPixels - 6 - i, accentSize, currentHeightPixels - 6 - i, argbAccent); //--- Draw vertical mainCanvas.Line(5 + i, currentHeightPixels - accentSize - 1, 5 + i, currentHeightPixels - 6, argbAccent); } //--- Loop for bottom-right for (int i = 0; i < 3; i++) { //--- Draw horizontal mainCanvas.Line(currentWidthPixels - accentSize - 1, currentHeightPixels - 6 - i, currentWidthPixels - 6, currentHeightPixels - 6 - i, argbAccent); //--- Draw vertical mainCanvas.Line(currentWidthPixels - 6 - i, currentHeightPixels - accentSize - 1, currentWidthPixels - 6 - i, currentHeightPixels - 6, argbAccent); } }
Для фрейма в стиле киберпанк мы реализуем функцию "drawCyberpunkBackground", чтобы создать темный звездный градиент для темы киберпанк, установив вверху темно-сине-черный цвет C'10,10,25', а внизу - пурпурно-черный C'20,5,30', вычисляя альфа по непрозрачности, затем циклом перебирая ряды, чтобы смешать цвета с помощью "BlendColors", и случайным образом добавляем белые звезды с помощью MathRand % 800 == 0 с различной яркостью, смешивая все с помощью "blendPixelSet" для создания космического эффекта.
Затем мы создаем "drawHolographicBorder" для анимированных светящихся краев, вычисляем фазу на основе синуса из "currentAnimationFrame" для пульсации, смешиваем основные и дополнительные киберцвета для динамической границы, затем накладываем слои свечения с уменьшением интенсивности альфа-сигнала в циклах для верхнего / нижнего / левого / правого края, рисуем основные границы с помощью Rectangle и вызываем "drawCornerAccents" для получения улучшений. В функции "drawCornerAccents" мы преобразуем акцентный цвет в ARGB, устанавливаем размер равным 15 и трижды повторяем цикл, чтобы нарисовать короткие горизонтальные и вертикальные линии в каждом углу (верхний левый, верхний правый, нижний левый, нижний правый) с помощью Line, формируя акценты в виде скобок для технологичной рамки. Теперь нам нужно будет нарисовать футуристический заголовок.
Рисование футуристического заголовка
В довершение всего мы создаем градиентный заголовок со светящимся текстом и анимационными эффектами.
//+------------------------------------------------------------------+ //| Draw futuristic header | //+------------------------------------------------------------------+ void drawFuturisticHeader() { //--- Compute phase double phase = (currentAnimationFrame % 360) * M_PI / 180.0; //--- Compute anim factor double animFactor = (MathSin(phase) + 1.0) * 0.5; //--- Loop over header rows for (int y = 0; y < HEADER_BAR_HEIGHT; y++) { //--- Compute grad factor double gradFactor = (double)y / HEADER_BAR_HEIGHT; //--- Declare header color color headerColor; //--- Check dragging if (isDraggingCanvas) { //--- Blend dragged headerColor = BlendColors(DarkenColor(CYBER_PRIMARY_COLOR, 0.7), DarkenColor(CYBER_SECONDARY_COLOR, 0.7), gradFactor); } //--- Check hovering else if (isHoveringHeader) { //--- Blend hovered headerColor = BlendColors(DarkenColor(CYBER_PRIMARY_COLOR, 0.5), DarkenColor(CYBER_SECONDARY_COLOR, 0.5), gradFactor); } else { //--- Blend normal headerColor = BlendColors(DarkenColor(CYBER_PRIMARY_COLOR, 0.8), DarkenColor(CYBER_SECONDARY_COLOR, 0.8), gradFactor); } //--- Get header ARGB uint argbHeader = ColorToARGB(headerColor, 255); //--- Loop over columns for (int x = 0; x < currentWidthPixels; x++) { //--- Set pixel mainCanvas.PixelSet(x, y, argbHeader); } } //--- Blend border color borderColor = BlendColors(CYBER_PRIMARY_COLOR, CYBER_SECONDARY_COLOR, animFactor); //--- Get border ARGB uint argbBorder = ColorToARGB(borderColor, 255); //--- Draw outer mainCanvas.Rectangle(0, 0, currentWidthPixels - 1, HEADER_BAR_HEIGHT, argbBorder); //--- Draw inner mainCanvas.Rectangle(1, 1, currentWidthPixels - 2, HEADER_BAR_HEIGHT - 1, argbBorder); //--- Set title font mainCanvas.FontSet("Arial Black", titleFontSize); //--- Format title string titleText = StringFormat("◆ %s vs %s - Linear Regression ◆", secondarySymbol, primarySymbol); //--- Get glow ARGB uint glowColor = ColorToARGB(CYBER_PRIMARY_COLOR, (uchar)(120 * glowIntensity)); //--- Loop for glow Y for (int offsetY = -1; offsetY <= 1; offsetY++) { //--- Loop for glow X for (int offsetX = -1; offsetX <= 1; offsetX++) { //--- Skip center if (offsetX == 0 && offsetY == 0) continue; //--- Draw glow text mainCanvas.TextOut(currentWidthPixels / 2 + offsetX, (HEADER_BAR_HEIGHT - titleFontSize) / 2 + offsetY, titleText, glowColor, TA_CENTER); } } //--- Set text color uint textColor = ColorToARGB(C'255,105,180', 255); //--- Draw title mainCanvas.TextOut(currentWidthPixels / 2, (HEADER_BAR_HEIGHT - titleFontSize) / 2, titleText, textColor, TA_CENTER); }
Здесь мы реализуем функцию "drawFuturisticHeader" для отрисовки анимированного градиентного заголовка в стиле киберпанк, начиная с вычисления фазы от "currentAnimationFrame" с использованием синуса для пульсирующих эффектов и коэффициента анимации, нормализованного в диапазоне от 0 до 1. Далее мы перебираем в цикле строки заголовка, чтобы вычислить коэффициент вертикального градиента, смешиваем затемненные основные и второстепенные цвета кибер-темы на основе состояния (перетаскивание, наведение курсора или значение по умолчанию) с помощью функций "BlendColors" и "DarkenColor", преобразуем в ARGB и устанавливаем значение для каждого пикселя построчно с помощью PixelSet для динамического затенения.
Для добавления границ мы смешиваем основные и второстепенные цвета с коэффициентом анимации, рисуем внешние и внутренние прямоугольники, используя Rectangle в смешанном ARGB. Мы используем жирный шрифт "Arial Black" с помощью FontSet, форматируем заголовок с помощью символов и названий пар, используя StringFormat, затем создаем эффект свечения, рисуя смещенный текст в сетке 3x3 (пропуская центр) с низкой прозрачностью в цвете CYBER PRIMARY COLOR в формате ARGB, и накладываем основной заголовок ярко-розовым цветом ARGB, центрированным с помощью TextOut метода. Далее мы нарисуем график регрессии в стиле киберпанка, но сначала нам понадобятся некоторые вспомогательные функции для рендеринга сетки и точек данных.
Рисование киберпанковской сетки и неоновых точек
Для наложения графиков добавляем анимированные сетки и светящиеся маркеры данных.
//+------------------------------------------------------------------+ //| Draw cyberpunk grid | //+------------------------------------------------------------------+ void drawCyberpunkGrid(int startX, int startY, int width, int height) { //--- Set grid spacing int gridSpacing = 40; //--- Compute phase double phase = (currentAnimationFrame % 120) * M_PI / 60.0; //--- Compute alpha uchar gridAlpha = (uchar)(30 + 15 * MathSin(phase)); //--- Set grid color color gridColor = CYBER_PRIMARY_COLOR; //--- Get grid ARGB uint argbGrid = ColorToARGB(gridColor, gridAlpha); //--- Loop vertical lines for (int x = startX; x < startX + width; x += gridSpacing) { //--- Loop over height for (int y = startY; y < startY + height; y++) { //--- Blend grid pixel blendPixelSet(mainCanvas, x, y, argbGrid); } } //--- Loop horizontal lines for (int y = startY; y < startY + height; y += gridSpacing) { //--- Loop over width for (int x = startX; x < startX + width; x++) { //--- Blend grid pixel blendPixelSet(mainCanvas, x, y, argbGrid); } } } //+------------------------------------------------------------------+ //| Draw neon point | //+------------------------------------------------------------------+ void drawNeonPoint(int centerX, int centerY, int radius, color pointColor) { //--- Loop for glow for (int glowRadius = radius + 4; glowRadius > radius; glowRadius--) { //--- Compute alpha uchar glowAlpha = (uchar)(80 * glowIntensity * (radius + 4 - glowRadius) / 4.0); //--- Get glow color uint glowColor = ColorToARGB(pointColor, glowAlpha); //--- Draw glow circle drawCirclePoint(centerX, centerY, glowRadius, glowColor); } //--- Get point ARGB uint argbPoint = ColorToARGB(pointColor, 255); //--- Draw point circle drawCirclePoint(centerX, centerY, radius, argbPoint); //--- Get center ARGB uint argbCenter = ColorToARGB(C'255,255,255', 200); //--- Draw center circle drawCirclePoint(centerX, centerY, radius - 1, argbCenter); }
Мы реализуем функцию "drawCyberpunkGrid" для создания анимированной сетки, накладываемой на область построения в режиме киберпанка, устанавливая интервал в 40 пикселей, вычисляя фазу из "currentAnimationFrame" для синусоидальной пульсации и варьируя альфа-канал от 30 до 45 для получения едва заметного свечения. Далее мы используем основной цвет кибер-темы, преобразуем его в ARGB с динамическим альфа-каналом, затем циклически рисуем вертикальные линии с заданным интервалом, смешивая пиксели по всей высоте с помощью "blendPixelSet", а также горизонтальные линии аналогично по ширине, добавляя футуристический фон в стиле "Матрицы", не перегружая при этом данные. Эта сетка поможет усилить тематическое погружение за счет смешивания с низкой степенью прозрачности, гарантируя, что она легко интегрируется с другими элементами. Впрочем, при желании можно оставить всё как есть.
Для добавления неоновых эффектов мы создаём функцию "drawNeonPoint" для точек данных, выполняя цикл от радиуса +4 до радиуса +1, чтобы нарисовать концентрические светящиеся круги с уменьшающейся альфа-интенсивностью, масштабируемой с помощью "glowIntensity", используя функцию "drawCirclePoint" (предполагая, что это базовый круг) в цвете точек ARGB. Затем мы рисуем центральную точку с максимальным радиусом и прозрачностью, за которой следует меньший внутренний круг полупрозрачным белым цветом C'255,255,255' с альфа-каналом 200 для выделения, создавая пульсирующую, научно-фантастическую визуализацию, которая выделяется на фоне темных тонов. Опять же, выбор цвета можно изменить. Мы просто использовали произвольные значения для целей демонстрации. Теперь мы можем использовать эти вспомогательные функции для создания графика в стиле киберпанк.
Создание графика регрессии в стиле киберпанк
Для наглядного представления основного анализа мы адаптируем график, используя сетки, оси, деления, линии и точки в стиле киберпанка.
//+------------------------------------------------------------------+ //| Draw regression plot cyberpunk | //+------------------------------------------------------------------+ void drawRegressionPlotCyberpunk() { //--- Return if no data if (!dataLoadedSuccessfully) return; //--- Set plot left int plotAreaLeft = 60; //--- Set plot right int plotAreaRight = currentWidthPixels - 40; //--- Set plot top int plotAreaTop = HEADER_BAR_HEIGHT + 10; //--- Set plot bottom int plotAreaBottom = currentHeightPixels - 50; //--- Set draw left int drawAreaLeft = plotAreaLeft + plotPadding; //--- Set draw right int drawAreaRight = plotAreaRight - plotPadding; //--- Set draw top int drawAreaTop = plotAreaTop + plotPadding; //--- Set draw bottom int drawAreaBottom = plotAreaBottom - plotPadding; //--- Compute plot width int plotWidth = drawAreaRight - drawAreaLeft; //--- Compute plot height int plotHeight = drawAreaBottom - drawAreaTop; //--- Return if invalid if (plotWidth <= 0 || plotHeight <= 0) return; //--- Init min X double minX = primaryClosePrices[0]; //--- Init max X double maxX = primaryClosePrices[0]; //--- Init min Y double minY = secondaryClosePrices[0]; //--- Init max Y double maxY = secondaryClosePrices[0]; //--- Get data points int dataPoints = ArraySize(primaryClosePrices); //--- Loop over points for (int i = 1; i < dataPoints; i++) { //--- Update min X if (primaryClosePrices[i] < minX) minX = primaryClosePrices[i]; //--- Update max X if (primaryClosePrices[i] > maxX) maxX = primaryClosePrices[i]; //--- Update min Y if (secondaryClosePrices[i] < minY) minY = secondaryClosePrices[i]; //--- Update max Y if (secondaryClosePrices[i] > maxY) maxY = secondaryClosePrices[i]; } //--- Compute range X double rangeX = maxX - minX; //--- Compute range Y double rangeY = maxY - minY; //--- Set min range X if (rangeX == 0) rangeX = 1; //--- Set min range Y if (rangeY == 0) rangeY = 1; //--- Draw grid drawCyberpunkGrid(drawAreaLeft, drawAreaTop, plotWidth, plotHeight); //--- Set axis color color axisColor = CYBER_PRIMARY_COLOR; //--- Get axis ARGB uint argbAxis = ColorToARGB(axisColor, 255); //--- Get glow ARGB uint glowColor = ColorToARGB(axisColor, (uchar)(60 * glowIntensity)); //--- Loop for glow for (int g = 1; g <= 2; g++) { //--- Draw Y glow for (int y = plotAreaTop; y <= plotAreaBottom; y++) { //--- Blend left glow blendPixelSet(mainCanvas, plotAreaLeft - g - 1, y, glowColor); //--- Blend right glow blendPixelSet(mainCanvas, plotAreaLeft + g + 1, y, glowColor); } //--- Draw X glow for (int x = plotAreaLeft; x <= plotAreaRight; x++) { //--- Blend bottom glow blendPixelSet(mainCanvas, x, plotAreaBottom + g + 1, glowColor); //--- Blend top glow blendPixelSet(mainCanvas, x, plotAreaBottom - g - 1, glowColor); } } //--- Loop for thick Y-axis for (int thick = 0; thick < 2; thick++) { //--- Draw Y-axis line mainCanvas.Line(plotAreaLeft - thick, plotAreaTop, plotAreaLeft - thick, plotAreaBottom, argbAxis); } //--- Loop for thick X-axis for (int thick = 0; thick < 2; thick++) { //--- Draw X-axis line mainCanvas.Line(plotAreaLeft, plotAreaBottom + thick, plotAreaRight, plotAreaBottom + thick, argbAxis); } //--- Set tick font mainCanvas.FontSet("Consolas", axisLabelFontSize); //--- Get tick label ARGB uint argbTickLabel = ColorToARGB(CYBER_PRIMARY_COLOR, 255); //--- Declare Y ticks double yTickValues[]; //--- Compute Y ticks int numYTicks = calculateOptimalTicks(minY, maxY, plotHeight, yTickValues); //--- Loop over Y ticks for (int i = 0; i < numYTicks; i++) { //--- Get Y value double yValue = yTickValues[i]; //--- Skip out of range if (yValue < minY || yValue > maxY) continue; //--- Compute Y pos int yPos = drawAreaBottom - (int)((yValue - minY) / rangeY * plotHeight); //--- Draw tick mainCanvas.Line(plotAreaLeft - 5, yPos, plotAreaLeft, yPos, argbAxis); //--- Format label string yLabel = formatTickLabel(yValue, rangeY); //--- Draw label mainCanvas.TextOut(plotAreaLeft - 8, yPos - axisLabelFontSize/2, yLabel, argbTickLabel, TA_RIGHT); } //--- Declare X ticks double xTickValues[]; //--- Compute X ticks int numXTicks = calculateOptimalTicks(minX, maxX, plotWidth, xTickValues); //--- Loop over X ticks for (int i = 0; i < numXTicks; i++) { //--- Get X value double xValue = xTickValues[i]; //--- Skip out of range if (xValue < minX || xValue > maxX) continue; //--- Compute X pos int xPos = drawAreaLeft + (int)((xValue - minX) / rangeX * plotWidth); //--- Draw tick mainCanvas.Line(xPos, plotAreaBottom, xPos, plotAreaBottom + 5, argbAxis); //--- Format label string xLabel = formatTickLabel(xValue, rangeX); //--- Draw label mainCanvas.TextOut(xPos, plotAreaBottom + 7, xLabel, argbTickLabel, TA_CENTER); } //--- Compute start Y double lineStartY = regressionIntercept + regressionSlope * minX; //--- Compute end Y double lineEndY = regressionIntercept + regressionSlope * maxX; //--- Set start screen X int lineStartScreenX = drawAreaLeft; //--- Compute start screen Y int lineStartScreenY = drawAreaBottom - (int)((lineStartY - minY) / rangeY * plotHeight); //--- Set end screen X int lineEndScreenX = drawAreaRight; //--- Compute end screen Y int lineEndScreenY = drawAreaBottom - (int)((lineEndY - minY) / rangeY * plotHeight); //--- Get line ARGB uint argbLine = ColorToARGB(CYBER_REGRESSION_COLOR, 255); //--- Loop for width for (int w = 0; w < regressionLineWidth; w++) { //--- Draw line mainCanvas.LineAA(lineStartScreenX, lineStartScreenY + w - regressionLineWidth/2, lineEndScreenX, lineEndScreenY + w - regressionLineWidth/2, argbLine); } //--- Compute pulse phase double pulsePhase = (currentAnimationFrame % 60) * M_PI / 30.0; //--- Compute pulse factor double pulseFactor = (MathSin(pulsePhase) + 1.0) * 0.3 + 0.7; //--- Compute point size int effectivePointSize = (int)(dataPointSize * pulseFactor); //--- Loop over points for (int i = 0; i < dataPoints; i++) { //--- Compute screen X int screenX = drawAreaLeft + (int)((primaryClosePrices[i] - minX) / rangeX * plotWidth); //--- Compute screen Y int screenY = drawAreaBottom - (int)((secondaryClosePrices[i] - minY) / rangeY * plotHeight); //--- Draw neon point drawNeonPoint(screenX, screenY, effectivePointSize, CYBER_DATA_POINTS_COLOR); } //--- Set axis label font mainCanvas.FontSet("Arial Bold", labelFontSize); //--- Get axis label ARGB uint argbAxisLabel = ColorToARGB(CYBER_SECONDARY_COLOR, 255); //--- Set X label string xAxisLabel = "► " + primarySymbol + " (X-AXIS)"; //--- Draw X label mainCanvas.TextOut(currentWidthPixels / 2, currentHeightPixels - 20, xAxisLabel, argbAxisLabel, TA_CENTER); //--- Set Y label string yAxisLabel = "► " + secondarySymbol + " (Y-AXIS)"; //--- Set vertical angle mainCanvas.FontAngleSet(900); //--- Draw Y label mainCanvas.TextOut(12, currentHeightPixels / 2, yAxisLabel, argbAxisLabel, TA_CENTER); //--- Reset angle mainCanvas.FontAngleSet(0); }
Здесь мы реализуем функцию "drawRegressionPlotCyberpunk" для визуализации регрессии в режиме киберпанка, сначала возвращая значение, если данные отсутствуют, затем устанавливая области построения графика и отрисовки с отступами, вычисляя ширину/высоту и завершая работу, если значения недопустимы. Далее определяем минимум/максимум для оси X (основной) и оси Y (вторичный), перебирая цены, устанавливаем нулевые диапазоны равными 1, вызываем функцию "drawCyberpunkGrid" для фоновой сетки, устанавливаем ось в значение CYBER PRIMARY COLOR в формате ARGB, добавляем слои свечения с помощью функции "blendPixelSet" вокруг осей Y/X для создания глубины и рисуем утолщенные линии с помощью функции Line в циклах. Для создания меток мы используем шрифт "Consolas" с цветом CYBER PRIMARY COLOR в формате ARGB, вычисляем тики по оси Y, создаем цикл для рисования коротких линий и выравнивания отформатированных меток по правому краю с помощью функции "formatTickLabel"; аналогично для оси X, где нижние метки центрированы.
Для построения линии мы вычисляем начальную/конечную точку по оси Y, используя параметры регрессии, сопоставляем их с позициями на экране, используем киберрегрессию ARGB и рисуем сглаженные сегменты с помощью LineAA, центрированные с помощью корректировки в половину ширины в цикле. Для усиления эффекта свечения мы вычисляем фазу пульсации из "currentAnimationFrame" с синусом, коэффициент масштабирования 0,7-1,0, зацикливаем данные для позиционирования экранов и выполняем рендеринг с помощью "drawNeonPoint" в цвете кибер-точек для создания анимированного свечения. Наконец, мы устанавливаем жирный шрифт "Arial Bold" с cyber secondary ARGB для меток, добавляем жестко заданные префиксы стрелок для эффектности. Опять же, мы использовали это для простоты, отцентрировали по оси X внизу, повернули по оси Y на 900 с помощью FontAngleSet для вертикали и сбросили значение угла наклона, завершая тематический график. При компиляции это даст нам что-то вроде следующего результата.

Завершив этот процесс мы можем добавить панели и легенды для этой киберпанк-темы.
//+------------------------------------------------------------------+ //| Draw glass morphism stats panel | //+------------------------------------------------------------------+ void drawGlassMorphismStatsPanel() { //--- Set panel X int panelX = statsPanelX; //--- Set panel Y int panelY = HEADER_BAR_HEIGHT + statsPanelY; //--- Set panel width int panelWidth = statsPanelWidth; //--- Set panel height int panelHeight = statsPanelHeight; //--- Compute bg color color panelBgColor = DarkenColor(CYBER_PRIMARY_COLOR, 0.85); //--- Set alpha uchar bgAlpha = 120; //--- Get panel bg ARGB uint argbPanelBg = ColorToARGB(panelBgColor, bgAlpha); //--- Loop over rows for (int y = panelY; y <= panelY + panelHeight; y++) { //--- Loop over columns for (int x = panelX; x <= panelX + panelWidth; x++) { //--- Blend bg pixel blendPixelSet(mainCanvas, x, y, argbPanelBg); } } //--- Blend border color borderColor = BlendColors(CYBER_PRIMARY_COLOR, CYBER_SECONDARY_COLOR, 0.5); //--- Get border ARGB uint argbBorder = ColorToARGB(borderColor, 255); //--- Get glow ARGB uint glowColor = ColorToARGB(borderColor, (uchar)(60 * glowIntensity)); //--- Loop for glow for (int g = 1; g <= 2; g++) { //--- Draw top glow for (int x = panelX - g; x <= panelX + panelWidth + g; x++) { //--- Blend pixel blendPixelSet(mainCanvas, x, panelY - g, glowColor); } //--- Draw bottom glow for (int x = panelX - g; x <= panelX + panelWidth + g; x++) { //--- Blend pixel blendPixelSet(mainCanvas, x, panelY + panelHeight + g, glowColor); } //--- Draw left glow for (int y = panelY - g; y <= panelY + panelHeight + g; y++) { //--- Blend pixel blendPixelSet(mainCanvas, panelX - g, y, glowColor); } //--- Draw right glow for (int y = panelY - g; y <= panelY + panelHeight + g; y++) { //--- Blend pixel blendPixelSet(mainCanvas, panelX + panelWidth + g, y, glowColor); } } //--- Draw top border for (int x = panelX; x <= panelX + panelWidth; x++) { //--- Blend border pixel blendPixelSet(mainCanvas, x, panelY, argbBorder); //--- Blend bottom border blendPixelSet(mainCanvas, x, panelY + panelHeight, argbBorder); } //--- Draw left and right borders for (int y = panelY; y <= panelY + panelHeight; y++) { //--- Blend left blendPixelSet(mainCanvas, panelX, y, argbBorder); //--- Blend right blendPixelSet(mainCanvas, panelX + panelWidth, y, argbBorder); } //--- Set stats font mainCanvas.FontSet("Consolas", panelFontSize); //--- Get text ARGB uint argbText = ColorToARGB(CYBER_PRIMARY_COLOR, 255); //--- Set text Y int textY = panelY + 8; //--- Set line spacing int lineSpacing = panelFontSize; //--- Format equation string equationText = StringFormat("Y = %.3f + %.3f * X", regressionIntercept, regressionSlope); //--- Draw equation mainCanvas.TextOut(panelX + 8, textY, equationText, argbText, TA_LEFT); //--- Update Y textY += lineSpacing; //--- Format correlation string correlationText = StringFormat("Correlation: %.4f", correlationCoefficient); //--- Draw correlation mainCanvas.TextOut(panelX + 8, textY, correlationText, argbText, TA_LEFT); //--- Update Y textY += lineSpacing; //--- Format R-squared string rSquaredText = StringFormat("R-Squared: %.4f", rSquared); //--- Draw R-squared mainCanvas.TextOut(panelX + 8, textY, rSquaredText, argbText, TA_LEFT); //--- Update Y textY += lineSpacing; //--- Format points string dataPointsText = StringFormat("Points: %d", ArraySize(primaryClosePrices)); //--- Draw points mainCanvas.TextOut(panelX + 8, textY, dataPointsText, argbText, TA_LEFT); } //+------------------------------------------------------------------+ //| Draw glass morphism legend | //+------------------------------------------------------------------+ void drawGlassMorphismLegend() { //--- Set legend X int legendX = statsPanelX; //--- Set legend Y int legendY = HEADER_BAR_HEIGHT + statsPanelY + statsPanelHeight; //--- Set legend width int legendWidth = statsPanelWidth; //--- Set legend height int legendHeightThis = legendHeight; //--- Compute bg color color legendBgColor = DarkenColor(CYBER_SECONDARY_COLOR, 0.85); //--- Set alpha uchar bgAlpha = 120; //--- Get legend bg ARGB uint argbLegendBg = ColorToARGB(legendBgColor, bgAlpha); //--- Loop over rows for (int y = legendY; y <= legendY + legendHeightThis; y++) { //--- Loop over columns for (int x = legendX; x <= legendX + legendWidth; x++) { //--- Blend bg pixel blendPixelSet(mainCanvas, x, y, argbLegendBg); } } //--- Blend border color borderColor = BlendColors(CYBER_SECONDARY_COLOR, CYBER_PRIMARY_COLOR, 0.5); //--- Get border ARGB uint argbBorder = ColorToARGB(borderColor, 255); //--- Get glow ARGB uint glowColor = ColorToARGB(borderColor, (uchar)(60 * glowIntensity)); //--- Loop for glow for (int g = 1; g <= 2; g++) { //--- Draw top and bottom glow for (int x = legendX - g; x <= legendX + legendWidth + g; x++) { //--- Blend top blendPixelSet(mainCanvas, x, legendY - g, glowColor); //--- Blend bottom blendPixelSet(mainCanvas, x, legendY + legendHeightThis + g, glowColor); } //--- Draw left and right glow for (int y = legendY - g; y <= legendY + legendHeightThis + g; y++) { //--- Blend left blendPixelSet(mainCanvas, legendX - g, y, glowColor); //--- Blend right blendPixelSet(mainCanvas, legendX + legendWidth + g, y, glowColor); } } //--- Draw top and bottom borders for (int x = legendX; x <= legendX + legendWidth; x++) { //--- Blend top blendPixelSet(mainCanvas, x, legendY, argbBorder); //--- Blend bottom blendPixelSet(mainCanvas, x, legendY + legendHeightThis, argbBorder); } //--- Draw left and right borders for (int y = legendY; y <= legendY + legendHeightThis; y++) { //--- Blend left blendPixelSet(mainCanvas, legendX, y, argbBorder); //--- Blend right blendPixelSet(mainCanvas, legendX + legendWidth, y, argbBorder); } //--- Set legend font mainCanvas.FontSet("Consolas", panelFontSize); //--- Get text ARGB uint argbText = ColorToARGB(CYBER_SECONDARY_COLOR, 255); //--- Set item Y int itemY = legendY + 10; //--- Set line spacing int lineSpacing = panelFontSize; //--- Draw neon point drawNeonPoint(legendX + 12, itemY, dataPointSize, CYBER_DATA_POINTS_COLOR); //--- Draw data label mainCanvas.TextOut(legendX + 22, itemY - 4, "Data Points", argbText, TA_LEFT); //--- Update Y itemY += lineSpacing; //--- Get line color uint argbLineColor = ColorToARGB(CYBER_REGRESSION_COLOR, 255); //--- Loop to draw line for (int i = 0; i < 15; i++) { //--- Blend line pixel blendPixelSet(mainCanvas, legendX + 7 + i, itemY, argbLineColor); //--- Blend below pixel blendPixelSet(mainCanvas, legendX + 7 + i, itemY + 1, argbLineColor); } //--- Draw line label mainCanvas.TextOut(legendX + 27, itemY - 4, "Regression Line", argbText, TA_LEFT); }
Сначала мы реализуем функцию "drawGlassMorphismStatsPanel" для создания полупрозрачного светящегося наложения статистики в стиле темы киберпанк, позиционируя его на основе входных параметров со смещением от заголовка, затемняя основной цвет киберпанка с помощью функции "DarkenColor" для фона, устанавливая низкое значение альфа-канала 120 и смешивая область пиксель за пикселем с помощью функции "blendPixelSet" для эффекта матового стекла. Далее, смешиваем цвет границы из основных и дополнительных цветов киберпространства с помощью функции «BlendColors», подготавливаем ARGB, добавляем внешние слои свечения, расширяя циклы вокруг краев панели с уменьшающейся интенсивностью, затем рисуем верхнюю/нижнюю/левую/правую границы, смешивая линии. Мы устанавливаем шрифт "Consolas" с помощью FontSet, используем цвет CYBER PRIMARY COLOR в формате ARGB для текста, инициализируем Y отступами и межстрочным интервалом, заданными размером шрифта, форматируем и отображаем уравнение, корреляцию, коэффициент детерминации R-квадрат и количество точек с помощью StringFormat и "TextOut" с выравниванием по левому краю, обновляя значение Y для каждой строки, размещая строки друг под другом.
Аналогично, для "drawGlassMorphismLegend" мы размещаем ниже панель статистики соответствующей ширины, затемняем дополнительный цвет киберпространства для фона, смешиваем заливку, применяем идентичное смешивание границ и расширяем свечение. Мы рисуем верхнюю/нижнюю/левую/правую границы, устанавливаем шрифт Consolas с текстом дополнительного цвета ARGB темы киберпанк, рисуем неоновую точечную иконку в скорректированном положении с помощью функции "drawNeonPoint" цветом точек темы киберпанк, добавляем метку "Data Points", обновляем значение по оси Y; затем смешиваем короткий горизонтальный отрезок линии (ширина 15 пикселей, высота 2 пикселя) цветом регрессии темы киберпанки добавляем метку "Regression Line" для обеспечения видимости. Для неонового индикатора изменения размера мы используем следующий подход.
Отрисовка неонового индикатора изменения размера
Для удобства изменения размера мы отображаем выделенные элементы управления с помощью свечения.
//+------------------------------------------------------------------+ //| Draw neon resize indicator | //+------------------------------------------------------------------+ void drawNeonResizeIndicator() { //--- Blend indicator color color indicatorColor = BlendColors(CYBER_PRIMARY_COLOR, CYBER_SECONDARY_COLOR, 0.5); //--- Get indicator ARGB uint argbIndicator = ColorToARGB(indicatorColor, 200); //--- Get glow ARGB uint glowColor = ColorToARGB(indicatorColor, (uchar)(100 * glowIntensity)); //--- Check corner if (hoverResizeMode == RESIZE_CORNER || activeResizeMode == RESIZE_CORNER) { //--- Compute corner X int cornerX = currentWidthPixels - resizeGripSize; //--- Compute corner Y int cornerY = currentHeightPixels - resizeGripSize; //--- Loop for glow for (int g = 1; g <= 3; g++) { //--- Loop Y glow for (int y = cornerY - g; y < currentHeightPixels + g; y++) { //--- Loop X glow for (int x = cornerX - g; x < currentWidthPixels + g; x++) { //--- Blend glow blendPixelSet(mainCanvas, x, y, glowColor); } } } //--- Fill corner mainCanvas.FillRectangle(cornerX, cornerY, currentWidthPixels - 1, currentHeightPixels - 1, argbIndicator); //--- Set line color uint lineColor = ColorToARGB(C'255,255,255', 255); //--- Loop for lines for (int i = 0; i < 4; i++) { //--- Compute offset int offset = i * 3; //--- Draw diagonal mainCanvas.Line(cornerX + offset, currentHeightPixels - 1, currentWidthPixels - 1, cornerY + offset, lineColor); } } //--- Check right if (hoverResizeMode == RESIZE_RIGHT_EDGE || activeResizeMode == RESIZE_RIGHT_EDGE) { //--- Compute indicator Y int indicatorY = currentHeightPixels / 2 - 15; //--- Loop for glow for (int g = 1; g <= 3; g++) { //--- Fill glow mainCanvas.FillRectangle(currentWidthPixels - 3 - g, indicatorY - g, currentWidthPixels - 1 + g, indicatorY + 30 + g, glowColor); } //--- Fill indicator mainCanvas.FillRectangle(currentWidthPixels - 3, indicatorY, currentWidthPixels - 1, indicatorY + 30, argbIndicator); } //--- Check bottom if (hoverResizeMode == RESIZE_BOTTOM_EDGE || activeResizeMode == RESIZE_BOTTOM_EDGE) { //--- Compute indicator X int indicatorX = currentWidthPixels / 2 - 15; //--- Loop for glow for (int g = 1; g <= 3; g++) { //--- Fill glow mainCanvas.FillRectangle(indicatorX - g, currentHeightPixels - 3 - g, indicatorX + 30 + g, currentHeightPixels - 1 + g, glowColor); } //--- Fill indicator mainCanvas.FillRectangle(indicatorX, currentHeightPixels - 3, indicatorX + 30, currentHeightPixels - 1, argbIndicator); } }
Мы реализуем функцию "drawNeonResizeIndicator" для визуального отображения изменений размера в режиме киберпанка, начиная со смешивания основных и дополнительных цветов киберпанка с помощью функции "BlendColors" для индикатора, преобразования в ARGB с альфа-каналом 200 и подготавливаем вариант цвета свечения с alpha, зависящим от glowIntensity. Для изменения размера угла (при наведении курсора или активации) мы вычисляем положение нижнего правого угла на основе размера элемента управления, добавляем многослойное свечение путем выполнения цикла, заполняя расширенные области вокруг угла с помощью функции "blendPixelSet", затем заполняем квадрат элемента управления с помощью функции FillRectangle и рисуем четыре смещенные диагональные линии в полном белом цвете ARGB с помощью "Line" для создания эффекта полос.
Далее, для правого края, мы центрируем вертикальный индикатор Y, применяем свечение, заполняя слегка расширенные и смещенные прямоугольники циклами, а затем основной заливкой. Аналогично, для нижнего края, по центру разместим горизонтальный индикатор X, добавим заливку свечением и завершим оформление основным прямоугольником-индикатором. Этот неоновый стиль с эффектом свечения повысит интерактивность, позволяя выделяться изменяемым по размеру областям в темном киберпанк-стиле за счет многослойного смешивания для создания глубины. Теперь мы можем последовательно вызывать и создавать графики, используя следующую логику.
//+------------------------------------------------------------------+ //| Render visualization | //+------------------------------------------------------------------+ void renderVisualization() { //--- Check cyberpunk mode if (isCyberpunkThemeEnabled) { //--- Render cyberpunk renderCyberpunkTheme(); } else { //--- Render standard renderStandardTheme(); } } //+------------------------------------------------------------------+ //| Render standard theme | //+------------------------------------------------------------------+ void renderStandardTheme() { //--- Erase canvas mainCanvas.Erase(0); //--- Check background fill if (enableBackgroundFill) { //--- Draw gradient drawGradientBackground(); } //--- Draw border drawCanvasBorder(); //--- Draw header drawHeaderBar(); //--- Draw toggle button drawThemeToggleButton(); //--- Draw plot drawRegressionPlot(); //--- Check show stats if (showStatistics) { //--- Draw stats panel drawStatisticsPanel(); //--- Draw legend drawLegend(); } //--- Check resize hover if (isHoveringResizeZone && enableResizing) { //--- Draw indicator drawResizeIndicator(); } //--- Update canvas mainCanvas.Update(); } //+------------------------------------------------------------------+ //| Render cyberpunk theme | //+------------------------------------------------------------------+ void renderCyberpunkTheme() { //--- Erase canvas mainCanvas.Erase(0); //--- Draw cyber background drawCyberpunkBackground(); //--- Draw holographic border drawHolographicBorder(); //--- Draw futuristic header drawFuturisticHeader(); //--- Draw toggle button drawThemeToggleButton(); //--- Draw cyber plot drawRegressionPlotCyberpunk(); //--- Check show stats if (showStatistics) { //--- Draw glass stats drawGlassMorphismStatsPanel(); //--- Draw glass legend drawGlassMorphismLegend(); } //--- Check resize hover if (isHoveringResizeZone && enableResizing) { //--- Draw neon indicator drawNeonResizeIndicator(); } //--- Update canvas mainCanvas.Update(); }
Здесь мы реализуем функцию "renderVisualization" для условного отображения графика на основе выбранного режима темы, проверяя значение "isCyberpunkThemeEnabled", чтобы вызвать "renderCyberpunkTheme" для футуристических эффектов или "renderStandardTheme" для классического отображения, позволяя плавно переключаться между режимами без избыточного кода. В "renderStandardTheme" мы очищаем данные с объекта Canvas с помощью функции Erase, при необходимости рисуем градиентный фон (если эта функция включена), добавляем границы, поле заголовка, кнопку переключения темы, основной график регрессии, и, если "showStatistics" имеет значение true, включаем панель статистики и легенду; при наведении курсора изменяем размер и включаем, добавляем индикатор, а затем обновляем данные с помощью Update метода.
Аналогично, "renderCyberpunkTheme" очищает старый фон, рисует кибернетический фон, голографические рамки, футуристический заголовок, кнопку переключения, киберпанковский график, панель статистики и легенду в стиле глассморфизма (если отображается), а также неоновый индикатор изменения размера (если применимо), после чего выполняет обновление, обеспечивая тематическую согласованность за счет специализированных функций. Это условно реализует визуализацию на основе выбранной темы, но нам потребуется установить таймер для анимации. Анимация основана на настройках таймера, который периодически обновляет рамки, обеспечивая плавность эффектов в режиме киберпанка.
Настройка таймера для анимации
//+------------------------------------------------------------------+ //| Initialize expert | //+------------------------------------------------------------------+ int OnInit() { //--- Render visualization renderVisualization(); //--- Enable mouse events ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Redraw chart ChartRedraw(); //--- Set timer for animations EventSetMillisecondTimer(50); //--- Return success return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Deinitialize expert | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Kill timer EventKillTimer(); //--- Destroy canvas mainCanvas.Destroy(); //--- Redraw chart ChartRedraw(); } //+------------------------------------------------------------------+ //| Handle timer | //+------------------------------------------------------------------+ void OnTimer() { //--- Check cyberpunk enabled if (isCyberpunkThemeEnabled) { //--- Increment frame currentAnimationFrame++; //--- Reset frame if (currentAnimationFrame > 360) currentAnimationFrame = 0; //--- Render renderVisualization(); //--- Redraw chart ChartRedraw(); } }
Начинаем с обработчика OnInit, расширяя предыдущую логику, чтобы включить поддержку анимации, вызываем функцию "renderVisualization" для первоначального отображения, включаем события перемещения мыши, перерисовываем график и устанавливаем 50-миллисекундный таймер с помощью EventSetMillisecondTimer для периодического обновления, после чего возвращаем INIT_SUCCEEDED. Далее, в OnDeinit мы останавливаем таймер с помощью EventKillTimer, чтобы прекратить анимацию, удалить объект Canvas и перерисовываем график для очистки.
Для обработки анимации мы используем OnTimer, проверяем, включен ли режим киберпанка, увеличиваем значение параметра "currentAnimationFrame" и сбрасывая его до 360 для зацикливания. Затем перерисовываем визуализацию с помощью "renderVisualization" и перерисовываем график для применения изменений на основе фреймов, таких как пульсации или свечение. Вот и всё. Наконец, напомним, что мы добавили дополнительную кнопку в заголовок, которую нужно распознавать при наведении курсора. Мы расширим её логику, включив ее в обработчик событий графика.
Обработка переключения темы в событиях
Для обработки кликов мы расширяем обработчик событий, предназначенный для обнаружения нажатий кнопок.
//+------------------------------------------------------------------+ //| Handle chart event | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Check mouse move if (id == CHARTEVENT_MOUSE_MOVE) { //--- Set mouse X int mouseX = (int)lparam; //--- Set mouse Y int mouseY = (int)dparam; //--- Set mouse state int mouseState = (int)sparam; //--- Store prev canvas hover bool previousHoverState = isHoveringCanvas; //--- Store prev header hover bool previousHeaderHoverState = isHoveringHeader; //--- Store prev resize hover bool previousResizeHoverState = isHoveringResizeZone; //--- Store prev theme hover bool previousThemeButtonHover = isHoveringThemeButton; //--- Update canvas hover isHoveringCanvas = (mouseX >= currentPositionX && mouseX <= currentPositionX + currentWidthPixels && mouseY >= currentPositionY && mouseY <= currentPositionY + currentHeightPixels); //--- Update header hover isHoveringHeader = isMouseOverHeaderBar(mouseX, mouseY); //--- Update theme hover isHoveringThemeButton = isMouseOverThemeButton(mouseX, mouseY); //--- Update resize hover isHoveringResizeZone = isMouseInResizeZone(mouseX, mouseY, hoverResizeMode); //--- Check if redraw needed bool needRedraw = (previousHoverState != isHoveringCanvas || previousHeaderHoverState != isHoveringHeader || previousResizeHoverState != isHoveringResizeZone || previousThemeButtonHover != isHoveringThemeButton); //--- Check button press if (mouseState == 1 && previousMouseButtonState == 0) { //--- Check theme button if (isHoveringThemeButton) { //--- Toggle theme isCyberpunkThemeEnabled = !isCyberpunkThemeEnabled; //--- Render renderVisualization(); //--- Redraw chart ChartRedraw(); } //--- Check drag start else if (enableDragging && isHoveringHeader && !isHoveringResizeZone) { //--- Set dragging isDraggingCanvas = true; //--- Set start X dragStartX = mouseX; //--- Set start Y dragStartY = mouseY; //--- Set canvas X canvasStartX = currentPositionX; //--- Set canvas Y canvasStartY = currentPositionY; //--- Disable scroll ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Set redraw needRedraw = true; } //--- Check resize start else if (isHoveringResizeZone) { //--- Set resizing isResizingCanvas = true; //--- Set active mode activeResizeMode = hoverResizeMode; //--- Set start X resizeStartX = mouseX; //--- Set start Y resizeStartY = mouseY; //--- Set initial width resizeInitialWidth = currentWidthPixels; //--- Set initial height resizeInitialHeight = currentHeightPixels; //--- Disable scroll ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Set redraw needRedraw = true; } } //--- Check drag else if (mouseState == 1 && previousMouseButtonState == 1) { //--- Handle drag if (isDraggingCanvas) { //--- Handle drag handleCanvasDrag(mouseX, mouseY); } //--- Handle resize else if (isResizingCanvas) { //--- Handle resize handleCanvasResize(mouseX, mouseY); } } //--- Check button release else if (mouseState == 0 && previousMouseButtonState == 1) { //--- Check active if (isDraggingCanvas || isResizingCanvas) { //--- Reset dragging isDraggingCanvas = false; //--- Reset resizing isResizingCanvas = false; //--- Reset mode activeResizeMode = NO_RESIZE; //--- Enable scroll ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Set redraw needRedraw = true; } } //--- Check redraw if (needRedraw) { //--- Render renderVisualization(); //--- Redraw chart ChartRedraw(); } //--- Update last X lastMouseX = mouseX; //--- Update last Y lastMouseY = mouseY; //--- Update prev state previousMouseButtonState = mouseState; } }
Для обработки переключения кнопки темы мы расширяем обработчик OnChartEvent, чтобы интегрировать его с существующими взаимодействиями, начиная с проверки на наличие CHARTEVENT_MOUSE_MOVE, извлечения координат и состояния мыши, сохранения предыдущих наведений курсора, включая новую кнопку темы, и обновления флагов для объекта Canvas, заголовка с помощью "isMouseOverHeaderBar", кнопки темы с помощью "isMouseOverThemeButton" и зоны изменения размера с помощью "isMouseInResizeZone", а затем определяем, требуется ли перерисовка при каком-либо изменении наведения курсора.
При нажатии кнопки мыши (состояние 1, prev 0) сначала проверяется, находится ли курсор на кнопке темы, и переключается параметр "isCyberpunkThemeEnabled", выполняется перерисовка с помощью "renderVisualization", и график перерисовывается. В противном случае, если параметр включен, обрабатывается перетаскивание, а заголовок находится в режиме наведения без изменения размера путем установки флагов, захвата начальных точек, отключения прокрутки и установки флага для перерисовки; или инициируется изменение размера, если элемент находится в зоне, путем установки режима, инициалов и отключения прокрутки. В удерживаемом состоянии (состояние 1, prev 1) вызывается "handleCanvasDrag" для перетаскивания или "handleCanvasResize" для изменения размера. После отпускания (состояние 0, prev 1) сбрасываем флаги и режим, включаем прокрутку, перерисовку флага. При необходимости перерисовки вызываем "renderVisualization" и ChartRedraw. Наконец, для обеспечения непрерывности обновляем последние положения курсора мыши и предыдущее состояние. Для большей ясности мы выделили конкретные изменения. После компиляции получаем следующий результат.

На изображении видно, что мы улучшили график регрессии, добавив тему киберпанка, тем самым достигнув наших целей. Теперь остаётся проверить работоспособность системы, что и рассматривается в следующем разделе.
Тестирование на истории
Мы провели тестирование, а ниже показан итоговый результат в формате Graphics Interchange Format (GIF).

В ходе тестирования кнопки переключения тем переключали режимы мгновенно и без задержек, неоновая подсветка выделяла точки данных для более четкого обнаружения выбросов, а анимации пульсировали синхронно с обновлениями фреймов, обеспечивая плавную работу.
Заключение
В заключение, мы усовершенствовали инструмент построения графиков регрессии в MQL5, добавив режим в стиле киберпанк с неоновым свечением, анимацией и голографическими эффектами для обеспечения более иммерсивной визуализации. Мы интегрировали переключение тем, динамические фоны со звездами, светящимися контурами и неоновыми точками / линиями, сохраняя при этом совместимость со стандартным режимом. Эта двухтематическая система придает парному анализу футуристическую эстетику, поддерживая обновления и взаимодействия в режиме реального времени для получения полезных торговых выводов. После ознакомления со статьей вы сможете:
- Переключать темы на графиках для лучшей видимости при различном освещении
- Использовать неоновые точки для быстрого определения отклонений в корреляции
- Использовать пульсирующие линии в качестве визуальных индикаторов силы наклона при совершении парных сделок
В следующих частях мы рассмотрим построение графиков статистических моделей распределения и их графическое представление в виде столбчатых диаграмм. Следите за обновлениями!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21306
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Статистический арбитраж с использованием коинтегрированных акций (Часть 4): Обновление параметров модели в реальном времени
Разработка инструментария для анализа Price Action (Часть 30): Советник CCI Zero Line
От начального до среднего уровня: Указатель на функцию
Создание самооптимизирующихся советников на MQL5 (Часть 14): Преобразования данных как параметры настройки регулятора с обратной связью
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования