Skip to main content Skip to footer

Creating Bound Markers and Labels

The ComponentOne Chart control for WPF and WinRT XAML has special support for displaying bound and interactive markers and labels. There is no single approach to creating or displaying markers in a chart, so our strategy is to provide an extensible object model for the C1Chart control to help you create the exact setup you need. For static labels and tooltips, you should look at the PointTooltipTemplate and PointLabelTemplate properties. These have been documented and demonstrated in many samples and previous articles. In this post I will cover the ChartPanelObject and the ChartView.Layers collection and how you can use these to provide a customized assortment of markers and labels for your chart. For the sake of brevity, further on I will only refer to these items as markers. Most of the code snippets are for WPF but i've included a full WinRT sample at the end as well.

ChartPanel Introduction

Before we create some markers let me explain how C1Chart supports them. Through the Layers collection (ChartView.Layers) you can add any number of panels (ChartPanel). Each panel can have any number of objects (ChartPanelObject), which are basically UI elements that define our markers. The ChartPanelObject has a few key properties you need to understand in order to work with them successfully.

  • Attach – sets whether the object is attached or “snapped” to the data points. You can attach to X, Y, both or none.
  • Action – sets the interaction behavior such as on mouse move, mouse drag or none.
  • DataPoint – explicitly sets the initial data point, or for creating a static marker

The Attach property saves you some work from having to convert between screen coordinates and data points and vice versa. The Action property makes it easy to provide interactive markers that either move with the mouse or can be dragged by the mouse. These properties are very simple and save you a ton of time in customizing your chart. For static markers, you can set the DataPoint property explicitly and set the Action property to none. Static markers are usually not set by the user but used to call the user’s attention to a specific point in your UI. The content of a ChartPanelObject can be any UI elements so this is where you define the look of your marker as well as provide binding to the data point. In addition to the three properties described above, you should also play around with the alignment properties to get the right display of your marker. For instance, to create a centered marker you would set the HorizontalAlignment property to Center. To create a vertical line marker that stretches from top to bottom, you would set the VerticalContentAlignment property to Stretch. Here is a starting point to define a ChartPanel and a ChartPanelObject in XAML:


<c1:C1Chart x:Name="c1Chart1" ChartType="Area" Palette="Flow">  
    <c1:C1Chart.Data>  
        <c1:ChartData>  
            <c1:DataSeries Label="Series 1" RenderMode="Default" Values="20 22 19 24 25 24 18 11 15 14 16 18 21" Opacity="0.8"/>  
        </c1:ChartData>  
    </c1:C1Chart.Data>  
    <c1:C1Chart.View>  
        <c1:ChartView>  
            <!-- Markers layer -->  
            <c1:ChartView.Layers>  
                <c1:ChartPanel>  
                    <c1:ChartPanelObject Attach="DataX"  
                                         Action="MouseMove"  
                                         DataPoint="0,0">  
                        <!-- Your content here -->  
                    </c1:ChartPanelObject>  
                </c1:ChartPanel>  
            </c1:ChartView.Layers>  
        </c1:ChartView>  
    </c1:C1Chart.View>  
</c1:C1Chart>  

Next, let’s look at some basic examples to get you started. You can use the XAML above along with the snippets below, just replace the collection of markers.

Example: Simple Bound Marker

SimpleBoundMarker In this scenario we want to show a single marker on the data point under the mouse. Here we set the DataPoint property to -1,-1 so that it starts off-screen until the user moves over a plot element. The other key properties to set here on the ChartPanelObject are:

  • Attach = DataX
  • Action = MouseMove
  • DataPoint = -1,-1
  • HorizontalAlignment = Center
  • VerticalAlignment = Top

Even though we are showing the Y value in the marker’s template, the Attach property is set to DataX. This means that the marker will attach to the point closest to the mouse along the X axis. Here is the complete markup for the marker (no code required).


<!-- simple bound marker -->  
<c1:ChartPanelObject x:Name="obj" Attach="DataX"  
                     Action="MouseMove"  
                     DataPoint="-1,-1"  
                     HorizontalAlignment="Center"  
                     VerticalAlignment="Top"  
                     Width="60" Height="50">  
    <c1:ChartPanelObject.RenderTransform>  
        <TranslateTransform Y="-50"/>  
    </c1:ChartPanelObject.RenderTransform>  
    <Grid DataContext="{Binding RelativeSource={x:Static RelativeSource.Self},Path=Parent}" Opacity="0.8">  
        <Path Data="M0.5,0.5 L23,0.5 23,23 11.61165,29.286408 0.5,23 z" Stretch="Fill" Fill="#FFF1F1F1" Stroke="DarkGray" StrokeThickness="1"/>  
        <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">  
            <TextBlock Text="Value" Margin="2 0"/>  
            <TextBlock x:Name="label" Text="{Binding DataPoint.Y, StringFormat=c2}" FontWeight="Bold" Margin="2"/>  
        </StackPanel>  
    </Grid>  
</c1:ChartPanelObject>  

You'll notice that we can display bound information about the data point by binding to the DataPoint.Y or DataPoint.X property on the ChartPanelObject itself.

Example: Line and Dot Marker

LineDotMarker In this scenario we want to display a vertical line that moves with the mouse and shows the Y value in a label. This type of marker is commonly seen in financial charts. The key property we set here is VerticalContentAlignment to Stretch. This stretches the marker to the entire plot height, thus giving us the vertical line we want. The other key properties to set are:

  • Attach = DataX
  • Action = MouseMove
  • DataPoint = -1, NaN
  • HorizontalAlignment = Center
  • VerticalContentAlignment = Stretch

For the DataPoint, setting the Y part to NaN also helps give us the full vertical line. It means that the marker will never position itself to any specific coordinate along the Y axis. Otherwise, if we don't use NaN here, the line would span from the bottom to the Y value of the plot element, giving us a very different result. You can try that and see for yourself. The circular label, however, is a separate ChartPanelObject that we do want to position on the plot element. So its DataPoint property should be set to something other than NaN. Here is the complete markup for the marker (no code required).


<!-- vertical line and dot markers -->  
<c1:ChartPanelObject x:Name="vline"  
                     Attach="DataX"  
                     Action="MouseMove"  
                     DataPoint="-1, NaN"  
                     VerticalContentAlignment="Stretch"  
                     HorizontalAlignment="Center">  
        <Border Background="Black" BorderBrush="Black" Padding="1" BorderThickness="1 0 0 0" />  
</c1:ChartPanelObject>  
<c1:ChartPanelObject x:Name="dot"  
                     Attach="DataX"  
                     Action="MouseMove"  
                     DataPoint="0.5,0.5"  
                     HorizontalAlignment="Center"  
                     VerticalAlignment="Center">  
    <Grid DataContext="{Binding RelativeSource={x:Static RelativeSource.Self},Path=Parent}">  
        <Ellipse Fill="White" Stroke="Black" StrokeThickness="1" Width="30" Height="30" />  
        <TextBlock x:Name="label" Text="{Binding DataPoint.Y, StringFormat=n0}" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center"/>  
    </Grid>  
</c1:ChartPanelObject>  

Example: Crosshairs

CrosshairsMarker With crosshairs we simply show a horizontal and vertical line. It’s the same as the previous example but with an added horizontal line. This time we use NaN again for the DataPoint to help achieve the desired result. This time, for fun, I’ve set the Attach to none. This makes the crosshairs free flowing and they do not snap to existing data points. (If you prefer to have the crosshairs snap to the closest data point, then you should just set the Attach property for each ChartPanelObject).


<!-- crosshairs -->  
<c1:ChartPanelObject x:Name="vline"  
                     Attach="None"  
                     Action="MouseMove"  
                     DataPoint="-1, NaN"  
                     VerticalContentAlignment="Stretch"  
                     HorizontalAlignment="Center">  
        <Border Background="Red" BorderBrush="Red" Padding="1" BorderThickness="1 0 0 0" />  
</c1:ChartPanelObject>  
<c1:ChartPanelObject x:Name="hline"  
                     Attach="None"  
                     Action="MouseMove"  
                     DataPoint="NaN, -1"  
                     HorizontalContentAlignment="Stretch"  
                     VerticalAlignment="Center">  
    <Border Background="Red" BorderBrush="Red" Padding="1" BorderThickness="0 1 0 0" />  
</c1:ChartPanelObject>  
<c1:ChartPanelObject x:Name="dot"  
                     Attach="None"  
                     Action="MouseMove"  
                     DataPoint="0.5,0.5"  
                     HorizontalAlignment="Center"  
                     VerticalAlignment="Center">  
    <Grid DataContext="{Binding RelativeSource={x:Static RelativeSource.Self},Path=Parent}">  
        <Ellipse Fill="White" Stroke="Red" StrokeThickness="1" Width="30" Height="30" />  
        <TextBlock x:Name="label" Text="{Binding DataPoint.Y, StringFormat=n0}" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center"/>  
    </Grid>  
</c1:ChartPanelObject>  

Custom Marker in Code

The examples above are completely created in XAML. Of course, you can completely create the ChartPanelObject and its content in code if you choose. A common scenario is that you may have a label outside the chart that you wish to update to reflect the data point value from a marker. To achieve this you can listen to the DataPointChanged event on the marker. Here is a code snippet that shows how to get the marker’s data point value and set it to a TextBlock on your form.


private void ChartPanelObject_DataPointChanged(object sender, EventArgs e)  
{  
    // update label in code from marker  
    var obj = (ChartPanelObject)sender;  
    if (obj != null)  
    {  
        lbl.Text = obj.DataPoint.Y.ToString("c2");  
    }  
}  

Displaying Bound Labels in WinRT

If you are using C1Chart for WinRT XAML or Windows Phone, then the same examples above can be used with slight modification. The only difference is in the way be perform the binding in XAML. In non-WPF, XAML platforms you can bind your label directly to the ChartPanelObject element using ElementName.


<!-- simple bound marker in WinRT-->  
<c1:ChartPanelObject x:Name="obj" Attach="DataX"  
                     Action="MouseMove"  
                     DataPoint="-1,-1"  
                     HorizontalAlignment="Center"  
                     VerticalAlignment="Top"  
                     Width="60" Height="50">  
    <TextBlock Text="{Binding DataPoint.Y, ElementName=obj}" TextAlignment="Center"/>  
</c1:ChartPanelObject>  

Conclusion

In this article I’ve covered the basics for creating markers and labels using the ChartPanelObject. As you can see from the examples above, it’s a flexible object model that can be used to create a variety of interactive markers without needing to write much code. Of course, we provide more samples in our Chart Samples collection, including code-only examples. I’m including a WPF and WinRT XAML sample that has all the examples above that you can try for yourself. Download ChartMarker_WPF Sample Download ChartMarkers_WinRT Sample Update: I've created another sample below that shows how to show labels for multiple data series using the DataPointChanged event. Download ChartMarker_MultiSeries_WPF Sample MultiSeriesMarker

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus