Announcement

Collapse
No announcement yet.

Partner 728x90

Collapse

Custom Data Box example?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Custom Data Box example?

    Is there an example, if at all possible, to display text or graphics that follows the cursor?

    #2
    It seems that drawing in on-render is cpu intensive. Here's what I came up with, is this the right approach? Perhaps change the cursor itself?

    PHP Code:
    #region Using declarations
    using System;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Threading;
    using NinjaTrader.Gui;
    using NinjaTrader.Gui.Chart;
    using NinjaTrader.NinjaScript;
    // Be explicit about which namespaces to use for resolving ambiguous references
    using SharpDX;
    using SharpDX.Direct2D1;
    using SharpDX.DirectWrite;
    // Use alias for System.Windows.Media to prevent ambiguous references
    using WPFBrush = System.Windows.Media.Brush;
    using WPFColor = System.Windows.Media.Color;
    using WPFSolidColorBrush = System.Windows.Media.SolidColorBrush;
    #endregion
    
    namespace NinjaTrader.NinjaScript.Indicators
    {
        public class MouseFollowingTextIndicator : Indicator
        {
            private System.Windows.Point lastPosition = new System.Windows.Point(0, 0);
            private System.Windows.Point lastValidPosition = new System.Windows.Point(0, 0);
            private DateTime lastCursorMoveTime = DateTime.MinValue;
            private DispatcherTimer updateTimer;
            private bool ctrlPressed = false;
            private bool isTextVisible = false;
            private ChartControl chartControl;
            private System.Windows.Point lastCursorPosition;
            private TextFormat textFormat;
            private byte currentAlpha = 255; // Full opacity by default
            private int currentAlphaLevel = 3; // Starting at max level
            private const int ALPHA_LEVELS = 4; // Number of alpha levels (0, 1, 2, 3)
            
            protected override void OnStateChange()
            {
                if (State == State.SetDefaults)
                {
                    Description = "Displays text that follows the mouse cursor on the chart when pressing CTRL";
                    Name = "Mouse Following Text";
                    Calculate = Calculate.OnPriceChange;
                    IsOverlay = true;
                    DisplayInDataBox = false;
                    PaintPriceMarkers = false;
                    IsSuspendedWhileInactive = true;
                    
                    // Default parameters
                    UpdateFrequencyMs = 150;
                    DisplayText = "BUY MSFT 100";
                    MaxCursorSpeedForDisplay = 500; // pixels per second
                    MaxFadeDistance = 300; // pixels
                    
                    // Set drawing style
                    TextColor = System.Windows.Media.Brushes.Blue;
                    TextSize = 14;
                    TextOffsetX = 15;
                    TextOffsetY = 15;
                }
                else if (State == State.Historical)
                {
                    // We don't need to do anything during a historical calculation
                }
                else if (State == State.Realtime)
                {
                    if (ChartControl != null)
                    {
                        // Store reference to chart control
                        chartControl = ChartControl;
                        
                        // Create TextFormat once for reuse
                        textFormat = new TextFormat(Core.Globals.DirectWriteFactory, "Arial",
                            SharpDX.DirectWrite.FontWeight.Normal, SharpDX.DirectWrite.FontStyle.Normal,
                            (float)TextSize);
                        
                        // Set up the update timer on the UI thread
                        Dispatcher.InvokeAsync(() =>
                        {
                            // Set up the update timer
                            updateTimer = new DispatcherTimer
                            {
                                Interval = TimeSpan.FromMilliseconds(UpdateFrequencyMs)
                            };
                            updateTimer.Tick += UpdateTimerTick;
                            
                            // Set up event handlers
                            chartControl.PreviewKeyDown += ChartControl_PreviewKeyDown;
                            chartControl.PreviewKeyUp += ChartControl_PreviewKeyUp;
                            chartControl.MouseMove += ChartControl_MouseMove;
                            
                            // Initialize time tracking
                            lastCursorMoveTime = DateTime.Now;
                            lastCursorPosition = new System.Windows.Point(0, 0);
                            
                            // Start the timer
                            updateTimer.Start();
                        });
                    }
                }
                else if (State == State.Terminated)
                {
                    // Clean up resources and event handlers
                    if (ChartControl != null)
                    {
                        Dispatcher.InvokeAsync(() =>
                        {
                            if (updateTimer != null)
                            {
                                updateTimer.Stop();
                                updateTimer.Tick -= UpdateTimerTick;
                            }
                            
                            if (chartControl != null)
                            {
                                chartControl.PreviewKeyDown -= ChartControl_PreviewKeyDown;
                                chartControl.PreviewKeyUp -= ChartControl_PreviewKeyUp;
                                chartControl.MouseMove -= ChartControl_MouseMove;
                            }
                        });
                    }
                    
                    // Dispose of DirectWrite resources
                    if (textFormat != null)
                    {
                        textFormat.Dispose();
                        textFormat = null;
                    }
                }
            }
            
            protected override void OnRender(ChartControl chartControl, ChartScale chartScale)
            {
                // Only draw the text if it's supposed to be visible
                if (isTextVisible && ctrlPressed)
                {
                    // Convert brush color to SharpDX color with current alpha
                    SharpDX.Color textColor = ToSharpDXColor(TextColor, currentAlpha);
                    
                    // Create brush
                    using (var brush = new SolidColorBrush(RenderTarget, textColor))
                    {
                        // Define the layout rectangle - make it large enough for the text
                        var layoutRect = new SharpDX.RectangleF(
                            (float)(lastValidPosition.X + TextOffsetX),
                            (float)(lastValidPosition.Y + TextOffsetY),
                            1000, // Large enough to accommodate the text
                            100);
                        
                        // Draw the text
                        RenderTarget.DrawText(
                            DisplayText,
                            textFormat,
                            layoutRect,
                            brush,
                            DrawTextOptions.None,
                            MeasuringMode.Natural);
                    }
                }
            }
            
            private SharpDX.Color ToSharpDXColor(WPFBrush brush, byte alpha = 255)
            {
                if (brush is WPFSolidColorBrush solidBrush)
                {
                    WPFColor color = solidBrush.Color;
                    // Explicitly cast to byte to avoid ambiguity between constructor overloads
                    return new SharpDX.Color((byte)color.R, (byte)color.G, (byte)color.B, alpha);
                }
                
                // Default to black if brush is not a solid color
                return new SharpDX.Color((byte)0, (byte)0, (byte)0, alpha);
            }
            
            private void ChartControl_PreviewKeyDown(object sender, KeyEventArgs e)
            {
                if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)
                {
                    ctrlPressed = true;
                    // The text will become visible on the next timer tick if cursor speed is below threshold
                    ChartControl.InvalidateVisual();
                }
            }
            
            private void ChartControl_PreviewKeyUp(object sender, KeyEventArgs e)
            {
                if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl)
                {
                    ctrlPressed = false;
                    isTextVisible = false;
                    ChartControl.InvalidateVisual();
                }
            }
            
            private void ChartControl_MouseMove(object sender, MouseEventArgs e)
            {
                if (chartControl == null) return;
                
                // Get current cursor position
                lastCursorPosition = e.GetPosition(chartControl);
                
                // Track cursor movement for speed calculation
                lastCursorMoveTime = DateTime.Now;
            }
            
            private void UpdateTimerTick(object sender, EventArgs e)
            {
                if (!ctrlPressed || chartControl == null)
                {
                    if (isTextVisible)
                    {
                        isTextVisible = false;
                        ChartControl.InvalidateVisual();
                    }
                    return;
                }
                
                DateTime now = DateTime.Now;
                
                // Calculate cursor speed (pixels per second)
                double timeDeltaSeconds = (now - lastCursorMoveTime).TotalSeconds;
                if (timeDeltaSeconds <= 0) timeDeltaSeconds = 0.001; // Prevent division by zero
                
                double cursorSpeed = 0;
                if (lastPosition.X != 0 || lastPosition.Y != 0) // Only if we have a previous position
                {
                    double distance = Math.Sqrt(
                        Math.Pow(lastCursorPosition.X - lastPosition.X, 2) +
                        Math.Pow(lastCursorPosition.Y - lastPosition.Y, 2));
                    cursorSpeed = distance / timeDeltaSeconds;
                }
                
                // Update position
                lastPosition = lastCursorPosition;
                
                // Only update the text position if cursor speed is below threshold
                bool shouldUpdateTextPosition = ctrlPressed && cursorSpeed < MaxCursorSpeedForDisplay;
                
                if (shouldUpdateTextPosition)
                {
                    // Update the valid position where text should be shown
                    lastValidPosition = lastCursorPosition;
                }
                
                // Calculate distance from cursor to text for alpha
                double dx = Math.Abs(lastCursorPosition.X - lastValidPosition.X);
                double dy = Math.Abs(lastCursorPosition.Y - lastValidPosition.Y);
                double maxDist = Math.Max(dx, dy);
                
                // Calculate alpha level based on distance (simplified approach)
                int newAlphaLevel = ALPHA_LEVELS - 1; // Start with max level
                
                if (maxDist > 0)
                {
                    // Convert to alpha level (0-3)
                    double distanceFactor = Math.Min(1.0, maxDist / MaxFadeDistance);
                    newAlphaLevel = ALPHA_LEVELS - 1 - (int)(distanceFactor * ALPHA_LEVELS);
                    
                    // Clamp to valid range
                    if (newAlphaLevel < 0) newAlphaLevel = 0;
                    if (newAlphaLevel >= ALPHA_LEVELS) newAlphaLevel = ALPHA_LEVELS - 1;
                }
                
                // Only update alpha and invalidate if level changed
                if (newAlphaLevel != currentAlphaLevel)
                {
                    currentAlphaLevel = newAlphaLevel;
                    
                    // Convert level to actual alpha value (0-255)
                    currentAlpha = (byte)(currentAlphaLevel * (255 / (ALPHA_LEVELS - 1)));
                    
                    // Ensure text is visible
                    isTextVisible = true;
                    
                    // Invalidate chart to update display
                    ChartControl.InvalidateVisual();
                }
                else if (!isTextVisible)
                {
                    // Make sure text is visible if it should be
                    isTextVisible = true;
                    ChartControl.InvalidateVisual();
                }
                else if (shouldUpdateTextPosition)
                {
                    // Position changed, need to redraw
                    ChartControl.InvalidateVisual();
                }
            }
            
            protected override void OnBarUpdate() { }
            
            #region Properties
            [NinjaScriptProperty]
            [Range(50, 1000)]
            [Display(Name="Update Frequency (ms)", Description="Frequency at which text position updates (milliseconds)", Order=1, GroupName="Parameters")]
            public int UpdateFrequencyMs { get; set; }
            
            [NinjaScriptProperty]
            [Display(Name="Display Text", Description="Text to display next to cursor", Order=2, GroupName="Parameters")]
            public string DisplayText { get; set; }
            
            [NinjaScriptProperty]
            [Range(50, 2000)]
            [Display(Name="Max Cursor Speed", Description="Maximum cursor speed for text display (pixels/sec)", Order=3, GroupName="Parameters")]
            public double MaxCursorSpeedForDisplay { get; set; }
            
            [NinjaScriptProperty]
            [Range(50, 1000)]
            [Display(Name="Max Fade Distance", Description="Distance at which text becomes fully transparent (pixels)", Order=4, GroupName="Parameters")]
            public double MaxFadeDistance { get; set; }
            
            [NinjaScriptProperty]
            [Display(Name="Text Color", Description="Color of the displayed text", Order=5, GroupName="Appearance")]
            public WPFBrush TextColor { get; set; }
            
            [NinjaScriptProperty]
            [Range(8, 72)]
            [Display(Name="Text Size", Description="Size of the displayed text", Order=6, GroupName="Appearance")]
            public double TextSize { get; set; }
            
            [NinjaScriptProperty]
            [Range(-100, 100)]
            [Display(Name="X Offset", Description="Horizontal offset from cursor", Order=7, GroupName="Appearance")]
            public double TextOffsetX { get; set; }
            
            [NinjaScriptProperty]
            [Range(-100, 100)]
            [Display(Name="Y Offset", Description="Vertical offset from cursor", Order=8, GroupName="Appearance")]
            public double TextOffsetY { get; set; }
            #endregion
        }
    }&#8203; 
    
    Last edited by MiCe1999; 03-15-2025, 09:11 AM.

    Comment


      #3
      Hello MiCe1999,

      Below is a link to an example on the forum of capturing the mouse points and converting these to x and y rendering coordinates.


      I can also provide an example that sends information from an addon window to an indicator, which you could reverse to send information from the indicator to the addon window and run a method that updates text in the window.
      Chelsea B.NinjaTrader Customer Service

      Comment

      Latest Posts

      Collapse

      Topics Statistics Last Post
      Started by SiebertCowen, Yesterday, 11:58 PM
      0 responses
      3 views
      0 likes
      Last Post SiebertCowen  
      Started by hunghnguyen2016, Yesterday, 08:00 PM
      0 responses
      8 views
      0 likes
      Last Post hunghnguyen2016  
      Started by cbentrikin, Yesterday, 03:49 PM
      0 responses
      11 views
      0 likes
      Last Post cbentrikin  
      Started by MiCe1999, 04-14-2025, 06:54 PM
      7 responses
      77 views
      0 likes
      Last Post b.j.d
      by b.j.d
       
      Started by NISNOS69, Yesterday, 02:20 PM
      0 responses
      18 views
      0 likes
      Last Post NISNOS69  
      Working...
      X