Hello!
I have a xamDataChart where I add dynamically series depending on what the user selects as information to be shown.
The X Axis shows the date as categoryAxis. My problem comes when the user chooses 2 different data to be shown, one from the 2011 year and the other from the 2012 year.
In the X axis is only shown the 2012 year, which is the last item the user has chosen, so the 2011 is never shown.
On the other side, if the user chooses to show something that starts at 2000 and finishes at 2015 as third option, the chart displays the three items correctly.
How can I make XAxis to display all the dates if I only choose the first and second option?
Thank you.
In other words, how can I stablish an Start/end for the X axis? Y axis has it's own Min value and max value
Hi,
The category series, if sharing an x axis, expect that the data be aligned. So that the category values for each series are the same, and any missing values are filled out with null or 0. They are essentially expecting tabular data so that they can corellate the data visually, as if you were looking down the rows of a table.
The chart will not attempt to corellate your data for you. If multiple series are sharing an x axis. It will use whatever you assign as the itemssource for the x axis to define the category labels, and assume that each series assigned to the axis has a value for each category on the axis, and in the same order as the categories appear on the axis.
If you don't want to plot each series on the same axis then you can create a seperate x axis for each series and each axis will have its own set of category labels and will scale its content to fill the entire chart.
But if you want to share the same category axis between series, you need to make sure your data has a value for each category and that the data for each series appears in the same order. This is easily achieved if you are using the same collection as the itemssource for every series and the axis and each series is just getting its values from a different property on the data source, but if you have a seperate collection that you are using for each series, you need to make sure that they are properly formatted.
Properly ordering and aligning the data, if there is a lot of volume, is best done in the data tier as you have various indexes to enable you to quickly relate the data and fill in missing values with null or 0 through the use of outer joins, etc. But failing that, or if there isn't significant volume, there is always an option to align the data on the client.
Here is an example of how you could build a data adapter to adjust two or more disparate collections to be aligned properly for the chart:
Xaml:
<Grid x:Name="LayoutRoot" Background="White"> <igChart:XamDataChart x:Name="theChart"> <igChart:XamDataChart.Axes> <igChart:NumericYAxis x:Name="yAxis" /> <igChart:CategoryXAxis x:Name="xAxis" ItemsSource="{Binding Outputs[0]}" Label="{}{Label}" /> </igChart:XamDataChart.Axes> <igChart:XamDataChart.Series> <igChart:ColumnSeries x:Name="c1" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ItemsSource="{Binding Outputs[0]}" ValueMemberPath="Value" /> <igChart:ColumnSeries x:Name="c2" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ItemsSource="{Binding Outputs[1]}" ValueMemberPath="Value" /> </igChart:XamDataChart.Series> </igChart:XamDataChart> </Grid>
And code behind:
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); var td1 = new TestData1(); var td2 = new TestData2(); var da = new CategoryDataAdapter( (o1, o2) => { var s1 = (string)o1; var s2 = (string)o2; DateTime date1 = DateTime.ParseExact(s1, "MMM", CultureInfo.CurrentCulture); DateTime date2 = DateTime.ParseExact(s2, "MMM", CultureInfo.CurrentCulture); return date1.Month.CompareTo(date2.Month); }, new CategoryInfo( td1, (o) => ((TestDataItem)o).Label, (o) => new TestDataItem() { Label = (string)o, Value = 0 }), new CategoryInfo( td2, (o) => ((TestDataItem)o).Label, (o) => new TestDataItem() { Label = (string)o, Value = 0 })); DataContext = da; } } public class TestData1 : ObservableCollection<TestDataItem> { public TestData1() { Add(new TestDataItem() { Label = "Jan", Value = 2 }); Add(new TestDataItem() { Label = "Mar", Value = 3 }); Add(new TestDataItem() { Label = "Apr", Value = 6 }); } } public class TestData2 : ObservableCollection<TestDataItem> { public TestData2() { Add(new TestDataItem() { Label = "Feb", Value = 4 }); Add(new TestDataItem() { Label = "Mar", Value = 1 }); Add(new TestDataItem() { Label = "Apr", Value = 8 }); } } public class TestDataItem { public string Label { get; set; } public double Value { get; set; } } public class CategoryDataAdapter { private List<CategoryInfo> _inputs; private Comparison<object> _compareCategories; public CategoryDataAdapter( Comparison<object> compareCategories, params CategoryInfo[] inputs) { _inputs = new List<CategoryInfo>(inputs); _compareCategories = compareCategories; foreach (var input in _inputs) { if (input.CategoryData is INotifyCollectionChanged) { ((INotifyCollectionChanged)input.CategoryData).CollectionChanged += CategoryDataAdapter_CollectionChanged; } } RefreshOutput(); } void CategoryDataAdapter_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { //Reader Note: Something more efficient but complex could be done here. RefreshOutput(); } private List<ObservableCollection<object>> _outputs; public List<ObservableCollection<object>> Outputs { get { return _outputs; } } private void RefreshOutput() { if (_outputs == null || _outputs.Count != _inputs.Count) { _outputs = new List<ObservableCollection<object>>(); foreach (var input in _inputs) { _outputs.Add(new ObservableCollection<object>()); } } foreach (var output in _outputs) { output.Clear(); } var categories = GetUniqueCategories(); categories.Sort(_compareCategories); List<List<object>> inputs = new List<List<object>>(); var indexes = new List<int>(); foreach (var input in _inputs) { List<object> inputValues = new List<object>(input.CategoryData.OfType<object>()); inputValues.Sort((v1, v2) => { return _compareCategories( input.GetCategory(v1), input.GetCategory(v2)); }); inputs.Add(inputValues); indexes.Add(0); } for (var i = 0; i < categories.Count; i++) { for (var j = 0; j < inputs.Count; j++) { if (indexes[j] < inputs[j].Count) { var currentCategory = _inputs[j].GetCategory(inputs[j][indexes[j]]); if (_compareCategories(currentCategory, categories[i]) == 0) { _outputs[j].Add(inputs[j][indexes[j]]); indexes[j]++; continue; } } _outputs[j].Add(_inputs[j].GetEmptyValueForCategory(categories[i])); } } } private List<object> GetUniqueCategories() { IEnumerable<object> allCats = null; foreach (var input in _inputs) { var cats = from item in input.CategoryData.OfType<object>() select input.GetCategory(item); if (allCats == null) { allCats = cats; } else { allCats = allCats.Concat(cats); } } var uniqueCats = from cat in allCats group cat by cat into g select g.Key; return uniqueCats.ToList(); } } public class CategoryInfo { public CategoryInfo( IList categoryData, Func<object, object> getCategory, Func<object, object> getEmptyValueForCategory) { _categoryData = categoryData; _getCategory = getCategory; _getEmptyValueForCategory = getEmptyValueForCategory; } private IList _categoryData; private Func<object, object> _getCategory; private Func<object, object> _getEmptyValueForCategory; public IList CategoryData { get { return _categoryData; } } public Func<object, object> GetCategory { get { return _getCategory; } } public Func<object, object> GetEmptyValueForCategory { get { return _getEmptyValueForCategory; } } }
Another option, if you have date data is to use the CategoryDateTimeXAxis which can display values at the exact date on which they occurred.But its other semantics may not be appropriate to your scenario.Hope this helps!-Graham