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
ItemsControlto display a collection of cell values- Bind
ItemsSourceattribute to the View ModelCellValuesproperty
- Bind
- By default items are displayed as a vertical list, set the
ItemsPanelelement to aUniformGridinstead- Bind the
Rowsattribute to the View ModelRowCountproperty - Bind the
Columnsattribute to the View ModelColumnCountproperty
- Bind the
- Set the
ItemTemplateelement to aDataTemplatecontaining a uniformViewBoxcontaining a whiteTextBlockdisplaying a cell value- Bind the
Textattribute to the Cell View ModelValueproperty
- 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
objectfield namedparameterLockto prevent multiple threads accessing the display parameters at the same timeList<CompositeSessionKey>field namedcompositeSessionKeysto hold the collection of configured composite session keysList<Guid>field namedparameterIdentifiersto hold the collection of configured parameter identifiersintfield namedrowCountas backer for theRowCountpropertyintfield namedcolumnCountas backer for theColumnCountproperty
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
intproperty namedColumnCountto specify the number of columns (composite sessions count + 1 for display parameter name)intproperty namedRowCountto specify the number of rows (display parameters count)ObservableCollection<CellViewModel>property namedCellValuesto 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