Challenge: make a multiline chart that works

Jul 15, 2013 at 9:59 PM
While I was able to figure out useful pie and bar charts in Win 8 with charting, I've given up on multi-line charts (e.g., the first chart of http://blog.thekieners.com/2010/02/07/databinding-multi-series-charts/ ). My reading of people's experiences is that you could do this with Silverlight (as Kierners blog indicates), but something broke with WPF, and the breakage persists in winRTXAMLtoolkit.

One of the problems: each series gets its own independent X-axis and labels, so if there are 5 series, you get 5 sets of labels stacked up. (And if you don't define the axis for a particular series, then you get an auto-generated one with not the scale you want.)

I'm giving up, but prove me wrong by posting an example. (I'd like datetimes on the X axis, counts on the y. I was mostly trying 5 {X,Y} series, but if {X, Y1, Y2, Y3, Y4, Y5} could work, that would be great too.)

Bonus karma points: If polylines didn't have to all have the same start date, and the same end date, but could instead start or end in the middle of the chart, without a line segment dropping down to the X axis.

BTW: My preference would be to suppress the data point circles... I can do that successfully with a single-line graph.

Thanks to anyone who gives it a try.
Jul 17, 2013 at 12:59 PM
Hi, I need to develop similar kind of thing, though I didn't find any better solution, so I co the code-behind way. I hope you will like that.

XAML
<charting:Chart x:Name="LineChart" Title="Multiple Line Chart" Margin="70,0"/>
C#
public sealed partial class Test2 : Page
{
    Random _random = new Random();

    public Test2()
    {
        this.InitializeComponent();
    }

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        LineSeries line;
        for (int i = 0; i < 5; i++)
        {
            line = new LineSeries();
            line.Title = string.Format("Line [{0}]", i.ToString());
            line.IndependentValueBinding = GetBinding("Date", true);
            line.DependentValueBinding = GetBinding("Value", false);
            line.ItemsSource = GetItems();
            this.LineChart.Series.Add(line);
        }
    }

    private Binding GetBinding(string PropName, bool IsDateTime)
    {
        Binding bind = new Binding();
        bind.Path = new PropertyPath(PropName);

        if (IsDateTime)
        {
            bind.Converter = new DateToStringConverter();
        }
        return bind;
    }

    private List<DateValueItem> GetItems()
    {
        List<DateValueItem> items = new List<DateValueItem>();
        items.Add(new DateValueItem { Date = DateTime.Now.AddDays(-5), Value = _random.Next(10, 100) });
        items.Add(new DateValueItem { Date = DateTime.Now.AddDays(-4), Value = _random.Next(10, 100) });
        items.Add(new DateValueItem { Date = DateTime.Now.AddDays(-3), Value = _random.Next(10, 100) });
        items.Add(new DateValueItem { Date = DateTime.Now.AddDays(-2), Value = _random.Next(10, 100) });
        items.Add(new DateValueItem { Date = DateTime.Now.AddDays(-1), Value = _random.Next(10, 100) });

        return items;
    }
}

public class DateValueItem
{
    public DateTime Date { get; set; }
    public int Value { get; set; }
}

public class DateToStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string Language)
    {
        return ((DateTime)value).ToString("MM/dd/yyyy", System.Globalization.CultureInfo.InvariantCulture);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}
Jul 17, 2013 at 9:16 PM
Thanks, Xyroid. Your post was similar to one of the variations I tried earlier, but it was very helpful to clarify my thinking, to realize much of my problems lay elsewhere. The larger problem was that I wasn't happy with the automatically-chosen Y axis intervals, so was introducing Y axes with defined Intervals (and defined X axes too). That really wasn't working for me. But after getting your response, I was encouraged to beat on it further. In terms of your code (except I'm using a native DateTimeAxis, not converting to string), I would add the following inside the for loop, just before ...Series.Add(line):
if(i == 0)
{
line.IndependentAxis = new DataTimeAxis() {
  Miniminum = minDT, Maximum = maxDT,
  Orientation = AxisOrientation.X, Location = AxisLocation.Bottom,
  FontSize = 10};
line.DependentAxis = new LinearAxis() 
{
  Miniminum = 0, Maximum = maxCount,
  Orientation = AxisOrientation.Y, Location = AxisLocation.Left,
  FontSize = 11, Interval = 1.0, ShowGridLines = true};
}
else
{
line.IndependentAxis = new DataTimeAxis() {
  Minimum = minDT, Maximum = maxDT,
  Orientation = AxisOrientation.X, Location = AxisLocation.Bottom,
  FontSize = 0.1};
line.DependentAxis = new LinearAxis() 
{
  Minimum = 0, Maximum = maxCount,
  Orientation = AxisOrientation.Y, Location = AxisLocation.Left,
  FontSize = 0.1, Interval = 1.0, ShowGridLines = true};
}
The important things I found are -
  • the minimum and maximum values of X and Y calculated across ALL series must be explicitly set, to avoid autoscaling and its problems.
  • redundant axis labels are suppressed by setting the fontsize to 0.1.
Another "DUH" problem I had is that if one of my series has only a single data point, and you show only the lines and not the datapoints, then there's nothing to see. Solved by deciding to show the datapoints, and (to catch the eye) adding an additional adjoining zero-value datapoint to force draw of a line segment.
Jul 18, 2013 at 4:42 AM
Ok, can you please see my issue, may be you have some solution ?