Jackson Pope
Jackson Pope

Reputation: 14640

How can I get a tooltip over the whole of a WPFToolkit LineSeries?

I'm drawing a WPFToolkit LineSeries graph and it all works fine except the ToolTip. I want a tooltip to display the x and y values of the mouse for any point on the line. I've found this which works for the DataPoints (which are fairly sparse in my case): http://istacee.wordpress.com/2013/03/19/wpf-toolkit-chart-custom-tooltip-on-lineseries-charts/ and this for any point on the chart area: Show series value over any point on chart using tooltip c#.

This is my code so far:

<Grid.Resources>
  <ResourceDictionary>
      <ControlTemplate x:Key="CommonLineSeriesDataPointTemplate" TargetType="chartingToolkit:LineDataPoint">
        <Grid x:Name="Root" Opacity="1" />
      </ControlTemplate>

      <Style x:Key="CommonLineSeriesDataPoint" TargetType="chartingToolkit:LineDataPoint">
        <Setter Property="Template" Value="{StaticResource CommonLineSeriesDataPointTemplate}" />
      </Style>

      <Style x:Key="lineSeriesStyle" TargetType="{x:Type chartingToolkit:LineSeries}">
        <Setter Property="IsTabStop" Value="False" />
        <Setter Property="DataPointStyle" Value="{StaticResource CommonLineSeriesDataPoint}" />
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate TargetType="chartingToolkit:LineSeries">
              <Canvas x:Name="PlotArea">
                <Polyline Points="{TemplateBinding Points}" >
                  <Polyline.Stroke>
                    <SolidColorBrush Color="Red"/>
                  </Polyline.Stroke>
                </Polyline>
              </Canvas>
            </ControlTemplate>
          </Setter.Value>
        </Setter>
      </Style>

      <DataTemplate x:Key="chartTemplate" DataType="{x:Type Views:GraphCurve}">
        <chartingToolkit:LineSeries ItemsSource="{Binding}" 
                                  IndependentValuePath="X" 
                                  DependentValuePath="Y" 
                                  Style="{StaticResource lineSeriesStyle}" />
      </DataTemplate>
  </ResourceDictionary>
</Grid.Resources>

<chartingToolkit:Chart BorderBrush="DarkGray" 
                         SeriesSource="{Binding GraphItems}"
                         SeriesTemplate="{StaticResource chartTemplate}">

  <chartingToolkit:Chart.Axes>
    <chartingToolkit:LinearAxis Orientation="X" />
    <chartingToolkit:LinearAxis Orientation="Y" />
  </chartingToolkit:Chart.Axes>
</chartingToolkit:Chart>

Any ideas?

Upvotes: 0

Views: 1841

Answers (1)

whilefalse
whilefalse

Reputation: 38

You could try using a MouseMove event, and tracking the position of the mouse to display in the tooltip of the polyline. IRangeAxis provides a method GetValueAtPosition, which takes a pixel coordinate and converts it into a value in the axis coordinates.

First off, add the tooltip to your polyline:

<Polyline Points="{TemplateBinding Points}" >
    <Polyline.Stroke>
        <SolidColorBrush Color="Red"/>                                      
    </Polyline.Stroke>
    <Polyline.ToolTip>
        <ToolTip>
            <StackPanel>
                <TextBlock Text="{Binding HoverPoint.X}"/>
                <TextBlock Text="{Binding HoverPoint.Y}"/>
            </StackPanel>
        </ToolTip>
    </Polyline.ToolTip>
</Polyline>

Here I have bound to a property of the view model of this view called HoverPoint. Make sure to define this in your view model (I'm using ReactiveUI but you could use something else that implements INotifyPropertyChanged):

private Point hoverPoint;
public Point HoverPoint
{
    get { return hoverPoint; }
    set { this.RaiseAndSetIfChanged(me => me.HoverPoint, ref hoverPoint, value); }
}

Then, finally, we need to update this property when the mouse is moved around the chart area. The binding will then take care of updating the tooltip. If you add a handler for the MouseMove event on your chart:

<chartingToolkit:Chart MouseMove="Chart_MouseMove" ...

Then fill in the handler like so:

    private void Chart_MouseMove(object sender, MouseEventArgs e)
    {
        var chart = (Chart)sender;
        var xAxisRange = (IRangeAxis)xAxis;
        var yAxisRange = (IRangeAxis)yAxis;

        var plotArea = FindDescendantWithName(chart, "PlotArea");
        if (plotArea == null)
        {
            return;
        }
        var mousePositionInPixels = e.GetPosition(plotArea);
        var mouseXPositionInChartUnits = (double)xAxisRange.GetValueAtPosition(new UnitValue(mousePositionInPixels.X, Unit.Pixels));
        var mouseYPositionInChartUnits = (double)yAxisRange.GetValueAtPosition(new UnitValue(plotArea.Height - mousePositionInPixels.Y, Unit.Pixels));

        ((MainWindowViewModel)DataContext).HoverPoint = new Point(mouseXPositionInChartUnits, mouseYPositionInChartUnits);
    }

    public static FrameworkElement FindDescendantWithName(DependencyObject root, string name)
    {
        var numChildren = VisualTreeHelper.GetChildrenCount(root);

        for (var i = 0; i < numChildren; i++)
        {
            var child = (FrameworkElement) VisualTreeHelper.GetChild(root, i);
            if (child.Name == name)
            {
                return child;
            }

            var descendantOfChild = FindDescendantWithName(child, name);
            if (descendantOfChild != null)
            {
                return descendantOfChild;
            }
        }

        return null;
    }

First, we get references to the chart and axes (I've named my axes xAxis and yAxis in the XAML).

Then, we need to find the PlotArea part of the chart template, which is the bit where the chart is drawn (excluding axes etc.) There may be a better way to get at this, but I used a simple method called FindDescendantWithName to get to it.

Then, I use GetPosition to get the mouse position relative to this chart plot area. Finally, the GetValueAtPosition method on the axes gives us the value in chart co-ordinates of the current mouse position, and we set these co-ordindates in the HoverPoint property.

The only problem with this is that the graph line has a finite thickness, so you can actually move the mouse around and see different values of Y for the same value of X, or vice versa. However, it would not be hard to extend this to find the nearest plot point. You could then snap the tooltip to the nearest point. Alternatively, if you find the two nearest points (one to the left and one to the right of the mouse), you could do the interpolation yourself and work out what Y corresponds to the current value of X. I guess it depends how much you care about the thickness of the line if this is worth doing.

Upvotes: 1

Related Questions