3

I'm working with an open source charting library that creates markers on a graph. (Dynamic Data Display) The markers that I'm creating are created with Rect objects. I noticed that all the Shapes in System.Windows.Shapes have a ToolTip property, but System.Windows.Rect does not. I want to have a tooltip pop up telling the user the price value of the marker when the mouse hovers over it. I was thinking about creating and popping up a tooltip when the mouse enters the area that the rect occupies, but I don't know how possible that is considering the rect will be zoomed/panned around by the chart. Any other suggestions?

Here is my code for the marker I am creating (From Felice Pollano Blog)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Shapes;
using System.Windows.Controls;
using System.Text;
using System.Windows;
using System.Windows.Media;

namespace Microsoft.Research.DynamicDataDisplay.PointMarkers
{
public class CandleStickPointMarker:ShapePointMarker,ITransformAware
{
    public double CandlestickWidth
    {
        get { return (double)GetValue(CandlestickWidthProperty); }
        set { SetValue(CandlestickWidthProperty, value); }
    }
    public static readonly DependencyProperty CandlestickWidthProperty =
        DependencyProperty.Register("CandlestickWidth", typeof(double), typeof(CandleStickPointMarker), new UIPropertyMetadata(4.0));
    public double CandlestickStrokeWidth
    {
        get { return (double)GetValue(CandlestickStrokeWidthProperty); }
        set { SetValue(CandlestickStrokeWidthProperty, value); }
    }

    public static readonly DependencyProperty CandlestickStrokeWidthProperty =
        DependencyProperty.Register("CandlestickStrokeWidth", typeof(double), typeof(CandleStickPointMarker), new UIPropertyMetadata(1.0));
    public Brush WhiteCandleFill
    {
        get { return (Brush)GetValue(WhiteCandleFillProperty); }
        set { SetValue(WhiteCandleFillProperty, value); }
    }

    public static readonly DependencyProperty WhiteCandleFillProperty =
        DependencyProperty.Register("WhiteCandleFill", typeof(Brush), typeof(CandleStickPointMarker), new UIPropertyMetadata(Brushes.White));



    public Brush WhiteCandleStroke
    {
        get { return (Brush)GetValue(WhiteCandleStrokeProperty); }
        set { SetValue(WhiteCandleStrokeProperty, value); }
    }

    public static readonly DependencyProperty WhiteCandleStrokeProperty =
        DependencyProperty.Register("WhiteCandleStroke", typeof(Brush), typeof(CandleStickPointMarker), new UIPropertyMetadata(Brushes.DarkGray));



    public Brush BlackCandleFill
    {
        get { return (Brush)GetValue(BlackCandleFillProperty); }
        set { SetValue(BlackCandleFillProperty, value); }
    }
    public static readonly DependencyProperty BlackCandleFillProperty =
        DependencyProperty.Register("BlackCandleFill", typeof(Brush), typeof(CandleStickPointMarker), new UIPropertyMetadata(Brushes.Black));
    public Brush BlackCandleStroke
    {
        get { return (Brush)GetValue(BlackCandleStrokeProperty); }
        set { SetValue(BlackCandleStrokeProperty, value); }
    }

    public static readonly DependencyProperty BlackCandleStrokeProperty =
        DependencyProperty.Register("BlackCandleStroke", typeof(Brush), typeof(CandleStickPointMarker), new UIPropertyMetadata(Brushes.Gray));
    public CoordinateTransform Transform { get; set; }
    public double High
    {
        get { return (double)GetValue(HighProperty); }
        set { SetValue(HighProperty, value); }
    }

    public static readonly DependencyProperty HighProperty =
        DependencyProperty.Register("High", typeof(double), typeof(CandleStickPointMarker), new UIPropertyMetadata(0.0));

    public double Low
    {
        get { return (double)GetValue(LowProperty); }
        set { SetValue(LowProperty, value); }
    }

    public static readonly DependencyProperty LowProperty =
        DependencyProperty.Register("Low", typeof(double), typeof(CandleStickPointMarker), new UIPropertyMetadata(0.0));


    public double Open
    {
        get { return (double)GetValue(OpenProperty); }
        set { SetValue(OpenProperty, value); }
    }

    public static readonly DependencyProperty OpenProperty =
        DependencyProperty.Register("Open", typeof(double), typeof(CandleStickPointMarker), new UIPropertyMetadata(0.0));


    public override void Render(System.Windows.Media.DrawingContext dc, Point screenPoint)
    {
        Point screenOpen = GetScreenPoint(Open,screenPoint.X);
        Point screenHigh = GetScreenPoint(High,screenPoint.X);
        Point screenLow = GetScreenPoint(Low, screenPoint.X);
        //screenPoint is the CLOSE by gentleman agreement.
        var close = screenPoint.ScreenToData(Transform).Y;
        Pen strokePen;
        if (Open >= close) // black
        {
            strokePen = new Pen(BlackCandleStroke, CandlestickStrokeWidth);
            var h = -screenOpen.Y + screenPoint.Y;
            Rect blkRect = new Rect(screenPoint.X - CandlestickWidth / 2, screenOpen.Y, CandlestickWidth, h);                       
            dc.DrawRectangle(BlackCandleFill,strokePen, blkRect);
            dc.DrawLine(strokePen, screenLow, screenPoint);
            dc.DrawLine(strokePen, screenHigh, screenOpen);
        }
        else // white
        {
            strokePen=new Pen(WhiteCandleStroke, CandlestickStrokeWidth);
            var h = screenOpen.Y - screenPoint.Y;
            Rect whtRect = new Rect(screenPoint.X - CandlestickWidth / 2, screenPoint.Y, CandlestickWidth, h);
            dc.DrawRectangle(WhiteCandleFill, strokePen, whtRect);
            dc.DrawLine(strokePen, screenLow, screenOpen);
            dc.DrawLine(strokePen, screenHigh, screenPoint);
        }
    }

    private Point GetScreenPoint(double Open,double screenX)
    {
        Point screen = new Point(0, Open);
        return new Point(screenX,screen.DataToScreen(Transform).Y);
    }
}

}

Jason Higgins
  • 1,516
  • 1
  • 17
  • 37

3 Answers3

6

I don't know if this can help you much, but I suppose the candlestick marker should be similar to a rectangle marker. The code below shows how I created a rectangle marker in D3 (actually a square), using the Polygon class, in a similar way to the CircleElementPointMarker, with the tooltip functionality working.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;

namespace Microsoft.Research.DynamicDataDisplay.PointMarkers
{
    /// <summary>Adds Rectangle element at every point of graph</summary>
    public class RectangleElementPointMarker : ShapeElementPointMarker
    {
        Polygon Rectangle;

        public override UIElement CreateMarker()
        {
            Rectangle = new Polygon();
            Rectangle.Stroke = (Pen != null) ? Pen.Brush : Brushes.White;
            Rectangle.StrokeThickness = (Pen != null) ? Pen.Thickness : 0;
            Rectangle.Fill = Fill;
            Rectangle.HorizontalAlignment = HorizontalAlignment.Center;
            Rectangle.VerticalAlignment = VerticalAlignment.Center;
            Rectangle.Width = Size;
            Rectangle.Height = Size;

            Point Point1 = new Point(-Rectangle.Width / 2, -Rectangle.Height / 2);
            Point Point2 = new Point(-Rectangle.Width / 2, Rectangle.Height / 2);
            Point Point3 = new Point(Rectangle.Width / 2, Rectangle.Height / 2);
            Point Point4 = new Point(Rectangle.Width / 2, -Rectangle.Height / 2);
            PointCollection myPointCollection = new PointCollection();
            myPointCollection.Add(Point1);
            myPointCollection.Add(Point2);
            myPointCollection.Add(Point3);
            myPointCollection.Add(Point4);
            Rectangle.Points = myPointCollection;

            if (!String.IsNullOrEmpty(ToolTipText))
            {
                ToolTip tt = new ToolTip();
                tt.Content = ToolTipText;
                Rectangle.ToolTip = tt;
            }

            return Rectangle;
        }

        public override void SetPosition(UIElement marker, Point screenPoint)
        {

            Canvas.SetLeft(marker, screenPoint.X - Size / 2);
            Canvas.SetTop(marker, screenPoint.Y - Size / 2);
        }
    }
}

The code was compiled it on .NET 4.0, VS2010

sɐunıɔןɐqɐp
  • 3,332
  • 15
  • 36
  • 40
2

One option is to use Rectangle instead of Rect.

Another option is to use Path and set the Data property to RectangleGeometry.

Here's the MSDN article for RectangleGeometry.

Josh C.
  • 4,303
  • 5
  • 30
  • 51
  • Could you explain the RectangleGeometry option in a little bit more detail? I was not able to see how to use a ToolTip with RectangleGeometry. Thanks! – Jason Higgins Oct 24 '12 at 18:41
2

The easiest way to achieve that is to follow these steps:

  1. create a Canvas (name it tooltipLayer) above your dc area (with same width and Height and boundaries as dc).
  2. For each rectangle in dc, add one canvas (name them tooltipContainers) to tooltipLayer.
  3. Each tooltipContainer has width and height equal to zero, Margin equal to "X,Y,0,0" where X and Y are its corresponding rectangle's X and Y, and also have one border as child element.
  4. each border must have the same width and height as its corresponding rectangle, and set its background to something barely visible (like "#01000000")
  5. add tooltip to each border
Bizhan
  • 16,157
  • 9
  • 63
  • 101