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.
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
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
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
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
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!!
Lina,
if you can send a quick sample, I can take a look
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;
}
}
if you are using ColumnSeries, then
LineSeries ls = this.lsChart.Series[0] as LineSeries;
is not right, you have to cast it to ColumnSeries
If I do so, then how should I managed this one: Point p = ls.Points[orders.IndexOf(o)];
Now I can’t use Points…
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
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.
ok, thanks a lot!!
Lina,
That is a nice workaround
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!!
you should be able to clear the chidren, by setting itemsource to null and from canvas using Children.clear
Thanks a lot, It works!!
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.
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.
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.