Numeric Compare display#
This tutorial demonstrates the use of Composite Data Requests for Compare Sets.
The existing Numeric display within ATLAS only displays values for the primary session, therefore a useful way to explore compare mode is to create a numeric like display to view the value of each display parameter for each session within a compare set.
Note
The code for this tutorial can be reviewed at Tutorials/NumericCompareDisplayPlugin
Start the tutorial by creating a new display from scratch named NumericCompareDisplayPlugin
.
The user interface should consist of a grid of display parameters vs sessions, where each cell is the display parameter value for that session.
- The first column should be the display parameter name.
Update the View class#
Configure the user interface as follows
- Use an
ItemsControl
to display a collection of cell values- Bind
ItemsSource
attribute to the View ModelCellValues
property
- Bind
- By default items are displayed as a vertical list, set the
ItemsPanel
element to aUniformGrid
instead- Bind the
Rows
attribute to the View ModelRowCount
property - Bind the
Columns
attribute to the View ModelColumnCount
property
- Bind the
- Set the
ItemTemplate
element to aDataTemplate
containing a uniformViewBox
containing a whiteTextBlock
displaying a cell value- Bind the
Text
attribute to the Cell View ModelValue
property
- Bind the
<ItemsControl ItemsSource="{Binding CellValues}" SnapsToDevicePixels="True" UseLayoutRounding="True">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding RowCount}" Columns="{Binding ColumnCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Viewbox Stretch="Uniform">
<TextBlock Padding="5" Foreground="White" Text="{Binding Value}" TextOptions.TextFormattingMode="Ideal" />
</Viewbox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Add Cell View Model class#
Add a simple View Model class to represent a generic cell value
public sealed class CellViewModel : BindableBase
{
private object obj;
public CellViewModel(object obj)
{
this.obj = obj;
}
public object Value
{
get => this.obj;
set => SetProperty(ref this.obj, value);
}
}
Note
BindableBase
provides an implementation of INotifyPropertyChanged
Since the Value
property setter calls the SetProperty()
method, the User Interface is automatically refreshed when a new value is set.
Update the View Model class#
Derive from TemplateDisplayViewModelBase and allow display parameters by specifying the DisplayPluginSettings
attribute
[DisplayPluginSettings(ParametersMaxCount = 100)]
public sealed class SampleDisplayViewModel : TemplateDisplayViewModelBase
Add the following backers
object
field namedparameterLock
to prevent multiple threads accessing the display parameters at the same timeList<CompositeSessionKey>
field namedcompositeSessionKeys
to hold the collection of configured composite session keysList<Guid>
field namedparameterIdentifiers
to hold the collection of configured parameter identifiersint
field namedrowCount
as backer for theRowCount
propertyint
field namedcolumnCount
as backer for theColumnCount
property
private readonly object parameterLock = new object();
private List<CompositeSessionKey> compositeSessionKeys = new List<CompositeSessionKey>();
private List<Guid> parameterIdentifiers = new List<Guid>();
private int rowCount;
private int columnCount;
Inject the ISignalBus
, IDataRequestSignalFactory
and ILogger
services into the View Model constructor and pass to the base constructor
public SampleDisplayViewModel(
ISignalBus signalBus,
IDataRequestSignalFactory dataRequestSignalFactory,
ILogger logger) :
base(signalBus, dataRequestSignalFactory, logger)
Add the following properties
int
property namedColumnCount
to specify the number of columns (composite sessions count + 1 for display parameter name)int
property namedRowCount
to specify the number of rows (display parameters count)ObservableCollection<CellViewModel>
property namedCellValues
to hold display parameter properties
[Browsable(false)]
public int ColumnCount
{
get => this.columnCount;
set => SetProperty(ref this.columnCount, value);
}
[Browsable(false)]
public int RowCount
{
get => this.rowCount;
set => SetProperty(ref this.rowCount, value);
}
[Browsable(false)]
public ObservableCollection<CellViewModel> CellValues { get; } = new ObservableCollection<CellViewModel>();
Subscribe to CompositeSampleResultSignal
in the View Model constructor to handle the result of a composite sample data request
this.Disposables.Add(
this.SignalBus.Subscribe<CompositeSampleResultSignal>
this.HandleCompositeSampleResultSignal,
r => r.SourceId == this.ScopeIdentity.Guid));
Note
The subscription instance is added to the Disposables
property for automatic cleanup on dispose.
Override the OnMakeCursorDataRequestsAsync()
method to issue a composite sample data request per display parameter when the cursor timestamp changes
protected override async Task OnMakeCursorDataRequestsAsync(ICompositeSession compositeSession)
{
await this.ExecuteOnUiAsync(this.SyncParameters);
foreach (var parameterContainer in DisplayParameterService.ParameterContainers)
{
var signal = this.DataRequestSignalFactory.CreateCompositeSampleRequestSignal(
this.ScopeIdentity.Guid,
this.ActiveCompositeSessionContainer.Key,
parameterContainer,
compositeSession.CursorPoint + 1,
1,
SampleDirection.Previous);
this.SignalBus.Send(signal);
}
}
Note
The CellValues
property is first updated by calling the SyncParameters()
method on the UI thread
Add the SyncParameters()
method to synchronize the CellValues
property with configured composite sessions and display parameters
private void SyncParameters()
{
lock (this.parameterLock)
{
var compositeSessions = this.ActiveCompositeSessionContainer?.CompositeSessions?.ToList();
var parameterContainers = this.DisplayParameterService.ParameterContainers.ToList();
if ((compositeSessions?.Count ?? 0) == 0 ||
parameterContainers.Count == 0)
{
this.compositeSessionKeys.Clear();
this.parameterIdentifiers.Clear();
this.RowCount = 0;
this.ColumnCount = 0;
this.CellValues.Clear();
return;
}
var newCompositeSessionKeys = compositeSessions.Select(cs => cs.Key).ToList();
var newParameterIdentifiers = new List<Guid>();
var newCellValues = new List<CellViewModel>();
foreach (var parameterContainer in parameterContainers)
{
newParameterIdentifiers.Add(parameterContainer.InstanceIdentifier);
newCellValues.Add(new CellViewModel(parameterContainer.Name));
var oldParameterIndex = this.parameterIdentifiers.IndexOf(parameterContainer.InstanceIdentifier);
foreach (var newCompositeSessionKey in newCompositeSessionKeys)
{
var oldCompositeSessionIndex = this.compositeSessionKeys.IndexOf(newCompositeSessionKey);
if (oldParameterIndex < 0 || oldCompositeSessionIndex < 0)
{
newCellValues.Add(new CellViewModel(string.Empty));
continue;
}
var parameterValueIndex = GetCellIndex(oldParameterIndex, oldCompositeSessionIndex);
var oldParameterValue = this.CellValues[parameterValueIndex];
newCellValues.Add(oldParameterValue);
}
}
this.compositeSessionKeys = newCompositeSessionKeys;
this.parameterIdentifiers = newParameterIdentifiers;
this.RowCount = this.parameterIdentifiers.Count;
this.ColumnCount = this.compositeSessionKeys.Count + 1;
this.CellValues.Clear();
foreach (var newCellValue in newCellValues)
{
this.CellValues.Add(newCellValue);
}
}
this.MakeDataRequests(true, false);
}
Add the HandleCompositeSampleResultSignal()
method to update the appropriate cell values with the results of the composite sample data request
private void HandleCompositeSampleResultSignal(CompositeSampleResultSignal signal)
{
lock (parameterLock)
{
var request = signal.Data.Request;
var parameterIndex = this.parameterIdentifiers.IndexOf(request.ParameterContainer.InstanceIdentifier);
if (parameterIndex < 0)
{
return;
}
var result = signal.Data;
foreach (var kvp in result.Results)
{
var compositeSessionIndex = this.compositeSessionKeys.IndexOf(kvp.Key);
if (compositeSessionIndex < 0)
{
continue;
}
var parameterValues = kvp.Value.ParameterValues;
if (parameterValues.SampleCount == 0)
{
continue;
}
parameterValues.Lock();
try
{
var cellIndex = this.GetCellIndex(parameterIndex, compositeSessionIndex);
this.CellValues[cellIndex].Value = parameterValues.Data[0];
}
finally
{
parameterValues.Unlock();
}
}
}
}
Add the GetCellIndex()
method to convert a display parameter and session grid coordinate into a cell index
private int GetCellIndex(int parameterIndex, int compositeSessionIndex)
{
var parameterValueIndex = parameterIndex * (this.compositeSessionKeys.Count + 1) + 1 + compositeSessionIndex;
return parameterValueIndex;
}
Testing the display#
To view the display parameter values
- Add at least two sessions via the Session Browser to the compare set associated with the display
- Add some display parameters to the display via the Parameter Browser
- Use a Waveform display to change the cursor