LineChart with Markers

I came across a forum post  in silverlight forums looking for a way to get markers in a linechart. I wanted to try it myself.

This is the end result. As the points are random, you might have to hit refresh, in case you dont see the lines.

charting

 

I changed the template to include a canvas

 <ControlTemplate TargetType=”charting:Chart”>
                        <Border Background=”{TemplateBinding Background}”
                                BorderBrush=”{TemplateBinding BorderBrush}”
                                BorderThickness=”{TemplateBinding BorderThickness}”
                                Padding=”10″>
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height=”Auto” />
                                    <RowDefinition Height=”*” />
                                </Grid.RowDefinitions>

                                <datavis:Title Content=”{TemplateBinding Title}”
                                               Style=”{TemplateBinding TitleStyle}” />

                                <!– Use a nested Grid to avoid possible clipping behavior resulting from ColumnSpan+Width=Auto –>
                                <Grid Grid.Row=”1″
                                      Margin=”0,15,0,15″>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width=”*” />
                                        <ColumnDefinition Width=”Auto” />
                                    </Grid.ColumnDefinitions>

                                    <datavis:Legend x:Name=”Legend”
                                                    Title=”{TemplateBinding LegendTitle}”
                                                    Style=”{TemplateBinding LegendStyle}”
                                                    Grid.Column=”1″ />

                                    <Grid x:Name=”ChartArea”
                                          Style=”{TemplateBinding ChartAreaStyle}”>
                                        <Grid x:Name=”PlotArea”
                                              Style=”{TemplateBinding PlotAreaStyle}”>
                                            <Grid x:Name=”GridLinesContainer” />
                                            <Grid x:Name=”SeriesContainer”>
                                              
                                            </Grid>
                                                <Border BorderBrush=”#FF919191″
                                                    BorderThickness=”1″ />
                                             <Canvas  Background=”Transparent”                                                      
                                                         x:Name=”c1″></Canvas>
                                        </Grid>
                                      
                                    </Grid>
                                   
                                </Grid>
                            </Grid>
                        </Border>
                    </ControlTemplate>

and in the code behind once we have the chart rendered . In the Layout updated method of the chart. I call the following function. which adds the markers

  void Highlight(List<Order> orderList)
        {
             LineSeries ls = this.chart1.Series[0] as LineSeries;
             Grid plotArea = chart1.GetChildrenByType<Grid>().Where(g => g.Name == “PlotArea”).SingleOrDefault<Grid>();
             Canvas c1 = chart1.GetChildrenByType<Canvas>().Where(c => c.Name == “c1”).SingleOrDefault<Canvas>();
            foreach (Order o in orderList)
            {
                Point p = ls.Points[orders.IndexOf(o)];
                Line ln = new Line();
                ln.Stroke = new SolidColorBrush(Colors.Black);
                ln.StrokeThickness = 1.0;
                DoubleCollection dashCollection = new DoubleCollection();
                dashCollection.Add(2);
                dashCollection.Add(4);
                ln.StrokeDashArray = dashCollection;
                ln.X1 = p.X;
                ln.X2 = p.X;
                ln.Y1 = -10.0;
                ln.Y2 = plotArea.ActualHeight;
                TextBlock txt = new TextBlock();
                txt.Text = o.OrderDate.ToShortDateString();
                txt.Foreground = new SolidColorBrush(Colors.Blue);
                Grid chartArea = plotArea.Parent as Grid;
                txt.SetValue(Canvas.LeftProperty, ln.X1 + 5.0);
                txt.SetValue(Canvas.TopProperty, ln.Y1 – 10);               
                c1.Children.Add(txt);
                c1.Children.Add(ln);
            }
        }

you can download the sample here

18 thoughts on “LineChart with Markers

  1. Lee,

    This is absolutely great work. Well Done! I am new to WPF and I require a chart control with markers; however, the markers should be moveable by the user so that s/he can place them anywhere on the line chart and just read off the x, y values. A single marker would do it; however, it will be a plus to have 2 such markers which user can place/fix at any chosen point(s) on the curve. Would you please give me some ideas as how to go about extending your line charts to include moveable markers/cursors instead of static/random markers?

    Thanks,
    Faisal

    1. Hi,
      one idea, might be to create event handlers for the line and respond to those events to move the line, or you could place a thumb and change the template so that it looks like a line or something and handle DragDelta or DragCompleted events

  2. Thanks Lee. It is a good idea. I will try it out. It will be a good WPF learning exercise for me :).
    Thanks again,
    Faisal

  3. Hi Lee, great work. Thanks a lot for this blog.
    I am trying to do the same but with Column Series. But when I come to this line: Point p = ls.Points[orders.IndexOf(o)]; I get an error message, saying I am trying to convert data of different type.
    Can you help me with it?

    Thanks a lot!!

  4. Thanks Lee for your time and knowledge.
    This is my Code:

    __________________________________________________________________

    __________________________________________________________________

    bool add = true;
    void Highlight(List orderList)
    {
    LineSeries ls = this.lsChart.Series[0] as LineSeries;
    Grid plotArea = lsChart.GetChildrenByType().Where(g => g.Name == “PlotArea”).SingleOrDefault();
    Canvas c1 = lsChart.GetChildrenByType().Where(c => c.Name == “c1”).SingleOrDefault();
    foreach (Order o in orderList)
    {
    Point p = ls.Points[orders.IndexOf(o)];
    Line ln = new Line();
    ln.Stroke = new SolidColorBrush(Colors.Black);
    ln.StrokeThickness = 1.0;
    DoubleCollection dashCollection = new DoubleCollection();
    dashCollection.Add(2);
    dashCollection.Add(4);
    ln.StrokeDashArray = dashCollection;
    ln.X1 = o.VALOR; //p.X;
    ln.X2 = o.VALOR; //p.X;
    ln.Y1 =10.0;
    ln.Y2 = plotArea.ActualHeight;
    TextBlock txt = new TextBlock();
    txt.Text = o.VALOR.ToString();
    txt.Foreground = new SolidColorBrush(Colors.Blue);
    Grid chartArea = plotArea.Parent as Grid;
    txt.SetValue(Canvas.LeftProperty, ln.X1);
    txt.SetValue(Canvas.TopProperty, ln.Y1);
    c1.Children.Add(txt);
    c1.Children.Add(ln);
    }
    }

    private void lsChart_LayoutUpdated(object sender, EventArgs e)
    {
    if (this.add)
    {
    Highlight(orders.Where(o => o.VALOR >= Convert.ToDouble(txtMaximo.Text)).ToList());
    Highlight(orders.Where(o => o.VALOR <= Convert.ToDouble(txtMinimo.Text)).ToList());
    this.add = false;
    }
    }

  5. if you are using ColumnSeries, then
    LineSeries ls = this.lsChart.Series[0] as LineSeries;
    is not right, you have to cast it to ColumnSeries

  6. If I do so, then how should I managed this one: Point p = ls.Points[orders.IndexOf(o)];
    Now I can’t use Points…

  7. What I was doing is to get the location(x,y) of the particular point. looks like ColumnSeries doesn’t expose the location of the column(atleast in the version of toolkit used in the sample. may be there is another way to get the location when it is columnseries, have to investigate

    1. Hi Lee,
      What I did, was a chart with ColumnSeries and LineSeries.
      The LineSeries is hidden.
      So I can use its data, but the user only sees the ColumnSeries.
      Thanks a lot for your help.
      If you happend to know something easier, please let me know.

  8. Hi Lee,
    I have another question that you may know.
    How can I clear values from the point chart and from the c1 canvas?
    Thanks a lot for your help!!

  9. Hi Lee,

    Thanks for your article. I use it in a WPF environment and have some approvements.

    1. Add a size changed event.
    void mcChart_SizeChanged(object sender, SizeChangedEventArgs e)
    {
    if (!add)
    {
    Highlight(orders);
    }
    }
    And make sure to add to following command before the foreach in Highlight:
    c1.Children.Clear();

    2. To get the X coordinate, you use Point p = ls.Points[orders.IndexOf(o)];
    Why don’t you do something like:
    DateTimeAxis xaxis = mcChart.Axes[0] as DateTimeAxis;
    System.Windows.Controls.DataVisualization.UnitValue value = xaxis.GetPlotAreaCoordinate(o.Date);

    ln.X1 = value.Value;
    In that case you can have an indepent list of markers

    Anyway thanks for putting me in the Canvas-direction.

  10. I’ll right away snatch your rss as I can’t in finding your email subscription hyperlink or e-newsletter service. Do you have any? Kindly let me understand in order that I could subscribe. Thanks.

  11. anyone got it works on visual studio 2010 with WPF Framework .Net 4.0? Mine doesn’t work i think due WPF version i believe. Kindly help.

Leave a comment