I'm having hard times to show some simple labels in my Cartesian Bar Charts, I'm reading a lot around but nothing seems to work for me. I'm using the MVVM pattern in my project, so this is the code I have so far..
VIEW
<lvc:CartesianChart Grid.Row="2" Series="{Binding ChartDataSets}">
<lvc:CartesianChart.AxisX>
<lvc:Axis LabelsRotation="20" Labels="{Binding ColumnLabels}" Position="RightTop" >
<lvc:Axis.Separator >
<lvc:Separator Step="1"></lvc:Separator>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis LabelFormatter="{Binding Formatter}" Position="RightTop"></lvc:Axis>
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
DataModel
class DataModel : INotifyPropertyChanged { private double value; public double Value { get => this.value; set { this.value = value; OnPropertyChanged(); } }
private string label;
public string Label
{
get => this.label;
set
{
this.label = value;
OnPropertyChanged("Label");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel
class BackupStatsViewModel : INotifyPropertyChanged
{
ChartValues<DataModel> values = new ChartValues<DataModel>();
public SeriesCollection ChartDataSets { get; set; }
public ObservableCollection<string> ColumnLabels { get; set; }
public class ErrorPrt
{
public ErrorPrt(){
prtName = string.Empty;
Count = -1;
}
public string prtName { get; set; }
public int Count { get; set; }
}
public BackupStatsViewModel()
{
InitializeBarChartData();
}
private void InitializeBarChartData()
{
this.ColumnLabels = new ObservableCollection<string>(values.Select(dataModel => dataModel.Label));
var dataMapper = new CartesianMapper<DataModel>()
.Y(dataModel => dataModel.Value)
.Fill(dataModel => dataModel.Value > 15.0 ? Brushes.Red : Brushes.Green);
this.ChartDataSets = new SeriesCollection
{
new ColumnSeries
{
Values = values,
Configuration = dataMapper,
DataLabels = true
}
};
}
public ErrorPrt[] PrtCount(List<DataRow> rows)
{
IEnumerable<IGrouping<string, DataRow>> grouped = rows.GroupBy(s => s.Field<string>(2));
ErrorPrt[] err = new ErrorPrt[grouped.Count()];
//Omitted code for sake of brevity
ErrorPrt[] arr = err.Where(c => c != null).ToArray();
for (int i = 0; i < arr.Count(); i++)
values.Add(new DataModel() { Label = $"PRT {arr[i].prtName}", Value = arr[i].Count });
return arr;
}
}
But as you can see no labels are shown on the X axis.. really don't know how to bypass this problem in order to go on with my work..please can anyone show me the right way?
Your flow looks broken:
you first initialize the chart data from the constructor by calling InitializeBarChartData()
, which also initializes the ColumnLabels
collection. Then you create the underlying ErrorPtr
items, which are the provider of the data for the column labels.
The result is that the ColumnLabels
property is empty => no labels will be displayed.
Because you add the new ErrorPtr
items to the values
field and this field is of type ChartValues
and this collection implements INotifyCollectionChanged
, the chart will reflect those changes. You were lucky here.
But because you never update the ColumnLabels
property after you have created the ErrorPtr
items, the initially (after calling InitializeBarChartData
from the constructor) empty ColumnLabels
collection remains empty.
Fix the flow of your data model initialization and call InitializeBarChartData
after PrtCount
:
public ErrorPrt[] PrtCount(List<DataRow> rows)
{
IEnumerable<IGrouping<string, DataRow>> grouped = rows.GroupBy(s => s.Field<string>(2));
ErrorPrt[] err = new ErrorPrt[grouped.Count()];
//Omitted code for sake of brevity
ErrorPrt[] arr = err.Where(c => c != null).ToArray();
for (int i = 0; i < arr.Count(); i++)
this.values.Add(new DataModel() { Label = $"PRT {arr[i].prtName}", Value = arr[i].Count });
// Initialize the chat models.
// NOW the label data (the ErrorPrt.prtName) is generated
// and ready to be extracted from the ErrorPrt instances
InitializeBarChartData();
return arr;
}
Since all involved collections implement INotifyCollectionChanged
you can dynamically update every collection when new data arrives. You don't need to initialize the complete chart data like the SeriesCollection
and the Mapper
or the label formatter over and over again (like in Solution 1 - in case PrtCount
will be called more than once).
You can continue to call InitializeBarChartData
once from the constructor, like you are currently doing it.
Simply don't only update the values
field, but also the ColumnLabels
property:
public ErrorPrt[] PrtCount(List<DataRow> rows)
{
IEnumerable<IGrouping<string, DataRow>> grouped = rows.GroupBy(s => s.Field<string>(2));
ErrorPrt[] err = new ErrorPrt[grouped.Count()];
//Omitted code for sake of brevity
ErrorPrt[] arr = err.Where(c => c != null).ToArray();
for (int i = 0; i < arr.Count(); i++)
{
var newDataModel = new DataModel() { Label = $"PRT {arr[i].prtName}", Value = arr[i].Count };
// Here you update the column values
// and add the new items to the existing items of previous calls
this.values.Add(newDataModel);
// Also update the labels whenever new column data has arrived
this.ColumnLabels.Add(newDataModel.Label);
}
return arr;
}