Skip to content

Commit

Permalink
Merge pull request #1415 from glopesdev/buffered-visualizers
Browse files Browse the repository at this point in the history
Improve support for high-frequency visualizers
  • Loading branch information
glopesdev authored Jun 26, 2023
2 parents 38f6bce + 7e62da2 commit e886870
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 12 deletions.
2 changes: 1 addition & 1 deletion Bonsai.Design.Visualizers/BarGraphVisualizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Bonsai.Design.Visualizers
/// <summary>
/// Provides a type visualizer to display an object as a bar graph.
/// </summary>
public class BarGraphVisualizer : DialogTypeVisualizer
public class BarGraphVisualizer : BufferedVisualizer
{
BarGraphBuilder.VisualizerController controller;
BarGraphView view;
Expand Down
2 changes: 1 addition & 1 deletion Bonsai.Design.Visualizers/Bonsai.Design.Visualizers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<PackageTags>Bonsai Rx Visualizers</PackageTags>
<UseWindowsForms>true</UseWindowsForms>
<TargetFramework>net462</TargetFramework>
<VersionPrefix>2.7.0</VersionPrefix>
<VersionPrefix>2.8.0</VersionPrefix>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ZedGraph" Version="5.1.7" />
Expand Down
64 changes: 64 additions & 0 deletions Bonsai.Design.Visualizers/BufferedVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Reactive.Linq;
using System.Windows.Forms;

namespace Bonsai.Design.Visualizers
{
/// <summary>
/// Provides an abstract base class for type visualizers with an update
/// frequency potentially much higher than the screen refresh rate.
/// </summary>
public abstract class BufferedVisualizer : DialogTypeVisualizer
{
const int TargetInterval = 1000 / 50;

internal BufferedVisualizer()
{
}

/// <inheritdoc/>
public override IObservable<object> Visualize(IObservable<IObservable<object>> source, IServiceProvider provider)
{
if (provider.GetService(typeof(IDialogTypeVisualizerService)) is not Control visualizerControl)
{
return source;
}

return Observable.Using(
() => new Timer(),
timer =>
{
timer.Interval = TargetInterval;
var timerTick = Observable.FromEventPattern<EventHandler, EventArgs>(
handler => timer.Tick += handler,
handler => timer.Tick -= handler);
timer.Start();
var mergedSource = source.SelectMany(xs => xs.Do(
_ => { },
() => visualizerControl.BeginInvoke((Action)SequenceCompleted)));
return mergedSource
.Timestamp(HighResolutionScheduler.Default)
.Buffer(() => timerTick)
.Do(buffer =>
{
foreach (var timestamped in buffer)
{
var time = timestamped.Timestamp.LocalDateTime;
Show(time, timestamped.Value);
}
}).Finally(timer.Stop);
});
}

/// <summary>
/// Updates the type visualizer to display a buffered value object
/// received at the specified time.
/// </summary>
/// <param name="time">The time at which the value was received.</param>
/// <param name="value">The value to visualize.</param>
protected virtual void Show(DateTime time, object value)
{
Show(value);
}
}
}
9 changes: 7 additions & 2 deletions Bonsai.Design.Visualizers/GraphHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ static bool IsIntegralType(Type type)
}

internal static Expression SelectIndexMember(Expression expression, string indexSelector, out string indexLabel)
{
return SelectIndexMember(time: null, expression, indexSelector, out indexLabel);
}

internal static Expression SelectIndexMember(Expression time, Expression expression, string indexSelector, out string indexLabel)
{
Expression selectedIndex;
if (!string.IsNullOrEmpty(indexSelector))
Expand All @@ -88,14 +93,14 @@ internal static Expression SelectIndexMember(Expression expression, string index
}
else
{
selectedIndex = Expression.Property(null, typeof(DateTime), nameof(DateTime.Now));
selectedIndex = time ?? Expression.Property(null, typeof(DateTime), nameof(DateTime.Now));
indexLabel = "Time";
}

if (selectedIndex.Type == typeof(DateTimeOffset)) selectedIndex = Expression.Property(selectedIndex, nameof(DateTimeOffset.DateTime));
if (selectedIndex.Type == typeof(DateTime))
{
selectedIndex = Expression.Convert(selectedIndex, typeof(ZedGraph.XDate));
selectedIndex = Expression.Convert(selectedIndex, typeof(XDate));
}

return selectedIndex;
Expand Down
2 changes: 1 addition & 1 deletion Bonsai.Design.Visualizers/LineGraphVisualizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Bonsai.Design.Visualizers
/// <summary>
/// Provides a type visualizer to display an object as a line graph.
/// </summary>
public class LineGraphVisualizer : DialogTypeVisualizer
public class LineGraphVisualizer : BufferedVisualizer
{
LineGraphBuilder.VisualizerController controller;
LineGraphView view;
Expand Down
12 changes: 9 additions & 3 deletions Bonsai.Design.Visualizers/RollingGraphBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ internal class VisualizerController
internal string[] ValueLabels;
internal SymbolType SymbolType;
internal float LineWidth;
internal Action<object, RollingGraphVisualizer> AddValues;
internal Action<DateTime, object, RollingGraphVisualizer> AddValues;
}

/// <summary>
Expand All @@ -113,6 +113,7 @@ public override Expression Build(IEnumerable<Expression> arguments)
{
var source = arguments.First();
var parameterType = source.Type.GetGenericArguments()[0];
var timeParameter = Expression.Parameter(typeof(DateTime));
var valueParameter = Expression.Parameter(typeof(object));
var viewParameter = Expression.Parameter(typeof(RollingGraphVisualizer));
var elementVariable = Expression.Variable(parameterType);
Expand All @@ -125,7 +126,7 @@ public override Expression Build(IEnumerable<Expression> arguments)
LineWidth = LineWidth
};

var selectedIndex = GraphHelper.SelectIndexMember(elementVariable, IndexSelector, out Controller.IndexLabel);
var selectedIndex = GraphHelper.SelectIndexMember(timeParameter, elementVariable, IndexSelector, out Controller.IndexLabel);
Controller.IndexType = selectedIndex.Type;
if (selectedIndex.Type != typeof(double) && selectedIndex.Type != typeof(string))
{
Expand All @@ -136,7 +137,12 @@ public override Expression Build(IEnumerable<Expression> arguments)
var showBody = Expression.Block(new[] { elementVariable },
Expression.Assign(elementVariable, Expression.Convert(valueParameter, parameterType)),
Expression.Call(viewParameter, nameof(RollingGraphVisualizer.AddValues), null, selectedIndex, selectedValues));
Controller.AddValues = Expression.Lambda<Action<object, RollingGraphVisualizer>>(showBody, valueParameter, viewParameter).Compile();
Controller.AddValues = Expression.Lambda<Action<DateTime, object, RollingGraphVisualizer>>(
showBody,
timeParameter,
valueParameter,
viewParameter)
.Compile();
return Expression.Call(typeof(RollingGraphBuilder), nameof(Process), new[] { parameterType }, source);
}

Expand Down
11 changes: 8 additions & 3 deletions Bonsai.Design.Visualizers/RollingGraphVisualizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Bonsai.Design.Visualizers
/// <summary>
/// Provides a type visualizer to display an object as a rolling graph.
/// </summary>
public class RollingGraphVisualizer : DialogTypeVisualizer
public class RollingGraphVisualizer : BufferedVisualizer
{
static readonly TimeSpan TargetElapsedTime = TimeSpan.FromSeconds(1.0 / 30);
RollingGraphBuilder.VisualizerController controller;
Expand Down Expand Up @@ -127,8 +127,13 @@ public override void Load(IServiceProvider provider)
/// <inheritdoc/>
public override void Show(object value)
{
var time = DateTime.Now;
controller.AddValues(value, this);
Show(DateTime.Now, value);
}

/// <inheritdoc/>
protected override void Show(DateTime time, object value)
{
controller.AddValues(time, value, this);
if ((time - updateTime) > TargetElapsedTime)
{
view.Graph.Invalidate();
Expand Down
12 changes: 11 additions & 1 deletion Bonsai.Design.Visualizers/TimeSeriesVisualizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public override void Show(object value)
/// Provides a base class for rolling graph visualizers of multi-dimensional
/// time series data.
/// </summary>
public class TimeSeriesVisualizerBase : DialogTypeVisualizer
public class TimeSeriesVisualizerBase : BufferedVisualizer
{
static readonly TimeSpan TargetElapsedTime = TimeSpan.FromSeconds(1.0 / 30);
internal RollingGraphView view;
Expand Down Expand Up @@ -194,6 +194,16 @@ public override void Show(object value)
AddValue(DateTime.Now, Convert.ToDouble(value));
}

/// <inheritdoc/>
protected override void Show(DateTime time, object value)
{
if (value is IConvertible convertible)
{
AddValue(time, convertible.ToDouble(null));
}
else Show(value);
}

/// <inheritdoc/>
public override void Load(IServiceProvider provider)
{
Expand Down
6 changes: 6 additions & 0 deletions Bonsai.Design.Visualizers/TimestampedTimeSeriesVisualizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,11 @@ public override void Show(object value)
}
else AddValue(timestamped.Timestamp.DateTime, Convert.ToDouble(timestamped.Value));
}

/// <inheritdoc/>
protected override void Show(DateTime time, object value)
{
Show(value);
}
}
}

0 comments on commit e886870

Please sign in to comment.