-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from bonsai-rx/feature-dev
Add support for composing bar and rolling graphs in a single panel
- Loading branch information
Showing
34 changed files
with
4,706 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
using System.Collections.Generic; | ||
using System.Drawing; | ||
using ZedGraph; | ||
|
||
namespace Bonsai.Gui.Visualizers | ||
{ | ||
class BarGraph : RollingGraph | ||
{ | ||
public BarBase BaseAxis | ||
{ | ||
get { return GraphPane.BarSettings.Base; } | ||
set { GraphPane.BarSettings.Base = value; } | ||
} | ||
|
||
public BarType BarType | ||
{ | ||
get { return GraphPane.BarSettings.Type; } | ||
set { GraphPane.BarSettings.Type = value; } | ||
} | ||
|
||
internal override CurveItem CreateSeries(string label, IPointListEdit points, Color color) | ||
{ | ||
var curve = new BarItem(label, points, color); | ||
curve.Label.IsVisible = !string.IsNullOrEmpty(label); | ||
curve.Bar.Fill.Type = FillType.Solid; | ||
curve.Bar.Border.IsVisible = false; | ||
return curve; | ||
} | ||
|
||
static int FindIndex(IPointListEdit series, string tag) | ||
{ | ||
if (!string.IsNullOrEmpty(tag)) | ||
{ | ||
for (int i = 0; i < series.Count; i++) | ||
{ | ||
if (EqualityComparer<string>.Default.Equals(tag, (string)series[i].Tag)) | ||
{ | ||
return i; | ||
} | ||
} | ||
} | ||
|
||
return -1; | ||
} | ||
|
||
public new void AddValues(double index, string label, double[] values) | ||
{ | ||
if (values.Length > 0) | ||
{ | ||
var updateIndex = FindIndex(Series[0], label); | ||
if (updateIndex >= 0 && BaseAxis <= BarBase.X2) UpdateLastBaseX(); | ||
else if (updateIndex >= 0) UpdateLastBaseY(); | ||
else if (BaseAxis <= BarBase.X2) AddBaseX(); | ||
else AddBaseY(); | ||
|
||
void UpdateLastBaseX() | ||
{ | ||
for (int i = 0; i < Series.Length; i++) | ||
Series[i][updateIndex].Y = values[i]; | ||
} | ||
|
||
void UpdateLastBaseY() | ||
{ | ||
for (int i = 0; i < Series.Length; i++) | ||
Series[i][updateIndex].X = values[i]; | ||
} | ||
|
||
void AddBaseX() | ||
{ | ||
for (int i = 0; i < Series.Length; i++) | ||
Series[i].Add(index, values[i], label); | ||
} | ||
|
||
void AddBaseY() | ||
{ | ||
for (int i = 0; i < Series.Length; i++) | ||
Series[i].Add(values[i], index, label); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
using Bonsai.Expressions; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.ObjectModel; | ||
using System.ComponentModel; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using ZedGraph; | ||
|
||
namespace Bonsai.Gui.Visualizers | ||
{ | ||
/// <summary> | ||
/// Represents an operator that configures a visualizer to plot each element | ||
/// of the sequence as a bar graph. | ||
/// </summary> | ||
[DefaultProperty(nameof(ValueSelector))] | ||
[TypeVisualizer(typeof(BarGraphVisualizer))] | ||
[Description("A visualizer that plots each element of the sequence as a bar graph.")] | ||
public class BarGraphBuilder : SingleArgumentExpressionBuilder | ||
{ | ||
/// <summary> | ||
/// Gets or sets the name of the property that will be used as index for the graph. | ||
/// </summary> | ||
[Editor("Bonsai.Design.MemberSelectorEditor, Bonsai.Design", DesignTypes.UITypeEditor)] | ||
[Description("The name of the property that will be used as index for the graph.")] | ||
public string IndexSelector { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the names of the properties that will be displayed in the graph. | ||
/// </summary> | ||
[Editor("Bonsai.Design.MultiMemberSelectorEditor, Bonsai.Design", DesignTypes.UITypeEditor)] | ||
[Description("The names of the properties that will be displayed in the graph.")] | ||
public string ValueSelector { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a value specifying the axis on which the bars in the graph will be displayed. | ||
/// </summary> | ||
[TypeConverter(typeof(BaseAxisConverter))] | ||
[Category(nameof(CategoryAttribute.Appearance))] | ||
[Description("Specifies the axis on which the bars in the graph will be displayed.")] | ||
public BarBase BaseAxis { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a value specifying how the different bars in the graph will be visually arranged. | ||
/// </summary> | ||
[Category(nameof(CategoryAttribute.Appearance))] | ||
[Description("Specifies how the different bars in the graph will be visually arranged.")] | ||
public BarType BarType { get; set; } | ||
|
||
/// <summary> | ||
/// Gets the optional settings for each bar added to the graph. | ||
/// </summary> | ||
[Category(nameof(CategoryAttribute.Appearance))] | ||
[Description("Specifies optional settings for each bar added to the graph.")] | ||
public Collection<CurveConfiguration> CurveSettings { get; } = new(); | ||
|
||
/// <summary> | ||
/// Gets or sets the optional capacity used for rolling bar graphs. If no capacity is specified, | ||
/// all data points will be displayed. | ||
/// </summary> | ||
[Category("Range")] | ||
[Description("The optional capacity used for rolling bar graphs. If no capacity is specified, all data points will be displayed.")] | ||
public int? Capacity { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a value specifying a fixed lower limit for the bar range axis. | ||
/// If no fixed range is specified, the graph limits can be edited online. | ||
/// </summary> | ||
[Category("Range")] | ||
[Description("Specifies the optional fixed lower limit for the bar range axis.")] | ||
public double? Min { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a value specifying a fixed upper limit for the bar range axis. | ||
/// If no fixed range is specified, the graph limits can be edited online. | ||
/// </summary> | ||
[Category("Range")] | ||
[Description("Specifies the optional fixed upper limit for the bar range axis.")] | ||
public double? Max { get; set; } | ||
|
||
internal VisualizerController Controller { get; set; } | ||
|
||
internal class VisualizerController | ||
{ | ||
internal int? Capacity; | ||
internal double? Min; | ||
internal double? Max; | ||
internal Type IndexType; | ||
internal string IndexLabel; | ||
internal string[] ValueLabels; | ||
internal CurveConfiguration[] CurveSettings; | ||
internal Action<object, IBarGraphVisualizer> AddValues; | ||
internal BarBase BaseAxis; | ||
internal BarType BarType; | ||
} | ||
|
||
/// <summary> | ||
/// Builds the expression tree for configuring and calling the | ||
/// bar graph visualizer on the specified input argument. | ||
/// </summary> | ||
/// <inheritdoc/> | ||
public override Expression Build(IEnumerable<Expression> arguments) | ||
{ | ||
var source = arguments.First(); | ||
var parameterType = source.Type.GetGenericArguments()[0]; | ||
var valueParameter = Expression.Parameter(typeof(object)); | ||
var viewParameter = Expression.Parameter(typeof(IBarGraphVisualizer)); | ||
var elementVariable = Expression.Variable(parameterType); | ||
Controller = new VisualizerController | ||
{ | ||
Capacity = Capacity, | ||
Min = Min, | ||
Max = Max, | ||
BaseAxis = BaseAxis, | ||
BarType = BarType, | ||
CurveSettings = CurveSettings.ToArray() | ||
}; | ||
|
||
var selectedIndex = GraphHelper.SelectIndexMember(elementVariable, IndexSelector, out Controller.IndexLabel); | ||
Controller.IndexType = selectedIndex.Type; | ||
if (selectedIndex.Type != typeof(double) && selectedIndex.Type != typeof(string)) | ||
{ | ||
selectedIndex = Expression.Convert(selectedIndex, typeof(double)); | ||
} | ||
|
||
var selectedValues = GraphHelper.SelectDataValues(elementVariable, ValueSelector, out Controller.ValueLabels); | ||
var addValuesBody = Expression.Block(new[] { elementVariable }, | ||
Expression.Assign(elementVariable, Expression.Convert(valueParameter, parameterType)), | ||
Expression.Call(viewParameter, nameof(IBarGraphVisualizer.AddValues), null, selectedIndex, selectedValues)); | ||
Controller.AddValues = Expression.Lambda<Action<object, IBarGraphVisualizer>>(addValuesBody, valueParameter, viewParameter).Compile(); | ||
return Expression.Call(typeof(BarGraphBuilder), nameof(Process), new[] { parameterType }, source); | ||
} | ||
|
||
static IObservable<TSource> Process<TSource>(IObservable<TSource> source) | ||
{ | ||
return source; | ||
} | ||
} | ||
|
||
class BaseAxisConverter : EnumConverter | ||
{ | ||
public BaseAxisConverter(Type type) | ||
: base(type) | ||
{ | ||
} | ||
|
||
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) | ||
{ | ||
return new StandardValuesCollection(new[] { BarBase.X, BarBase.Y }); | ||
} | ||
} | ||
|
||
interface IBarGraphVisualizer | ||
{ | ||
void AddValues(string index, params double[] values); | ||
|
||
void AddValues(double index, params double[] values); | ||
|
||
void AddValues(double index, string tag, params double[] values); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Drawing; | ||
using System.Reactive; | ||
using Bonsai; | ||
using Bonsai.Design; | ||
using Bonsai.Gui.Visualizers; | ||
using Bonsai.Expressions; | ||
using ZedGraph; | ||
|
||
[assembly: TypeVisualizer(typeof(BarGraphOverlay), Target = typeof(MashupSource<GraphPanelVisualizer, BarGraphVisualizer>))] | ||
|
||
|
||
namespace Bonsai.Gui.Visualizers | ||
{ | ||
/// <summary> | ||
/// Provides a type visualizer used to overlay a sequence of values as a bar graph. | ||
/// </summary> | ||
public class BarGraphOverlay : BufferedVisualizer, IBarGraphVisualizer | ||
{ | ||
GraphPanelVisualizer visualizer; | ||
BarGraphBuilder.VisualizerController controller; | ||
BoundedPointPairList[] series; | ||
|
||
void IBarGraphVisualizer.AddValues(string index, params double[] values) => AddValues(0, index, values); | ||
|
||
void IBarGraphVisualizer.AddValues(double index, params double[] values) => AddValues(index, null, values); | ||
|
||
void IBarGraphVisualizer.AddValues(double index, string tag, params double[] values) => AddValues(index, tag, values); | ||
|
||
static int FindIndex(IPointListEdit series, string tag) | ||
{ | ||
if (!string.IsNullOrEmpty(tag)) | ||
{ | ||
for (int i = 0; i < series.Count; i++) | ||
{ | ||
if (EqualityComparer<string>.Default.Equals(tag, (string)series[i].Tag)) | ||
{ | ||
return i; | ||
} | ||
} | ||
} | ||
|
||
return -1; | ||
} | ||
|
||
internal void AddValues(double index, string tag, params double[] values) | ||
{ | ||
if (values.Length > 0) | ||
{ | ||
var updateIndex = FindIndex(series[0], tag); | ||
if (updateIndex >= 0 && controller.BaseAxis <= BarBase.X2) UpdateLastBaseX(); | ||
else if (updateIndex >= 0) UpdateLastBaseY(); | ||
else if (controller.BaseAxis <= BarBase.X2) AddBaseX(); | ||
else AddBaseY(); | ||
|
||
void UpdateLastBaseX() | ||
{ | ||
for (int i = 0; i < series.Length; i++) | ||
series[i][updateIndex].Y = values[i]; | ||
} | ||
|
||
void UpdateLastBaseY() | ||
{ | ||
for (int i = 0; i < series.Length; i++) | ||
series[i][updateIndex].X = values[i]; | ||
} | ||
|
||
void AddBaseX() | ||
{ | ||
for (int i = 0; i < series.Length; i++) | ||
series[i].Add(index, values[i], index, tag); | ||
} | ||
|
||
void AddBaseY() | ||
{ | ||
for (int i = 0; i < series.Length; i++) | ||
series[i].Add(values[i], index, index, tag); | ||
} | ||
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void Load(IServiceProvider provider) | ||
{ | ||
visualizer = (GraphPanelVisualizer)provider.GetService(typeof(MashupVisualizer)); | ||
var context = (ITypeVisualizerContext)provider.GetService(typeof(ITypeVisualizerContext)); | ||
var barGraphBuilder = (BarGraphBuilder)ExpressionBuilder.GetVisualizerElement(context.Source).Builder; | ||
controller = barGraphBuilder.Controller; | ||
visualizer.EnsureBarSettings(new BarSettings(visualizer.Control.GraphPane) | ||
{ | ||
Base = controller.BaseAxis, | ||
Type = controller.BarType | ||
}); | ||
visualizer.EnsureIndex(controller.IndexType); | ||
|
||
var hasLabels = controller.ValueLabels != null; | ||
if (hasLabels) | ||
{ | ||
series = new BoundedPointPairList[controller.ValueLabels.Length]; | ||
for (int i = 0; i < series.Length; i++) | ||
{ | ||
series[i] = new BoundedPointPairList(); | ||
var curveSettings = controller.CurveSettings.Length > 0 | ||
? controller.CurveSettings[i % controller.CurveSettings.Length] | ||
: null; | ||
var color = curveSettings?.Color.IsEmpty == false | ||
? curveSettings.Color | ||
: visualizer.Control.GetNextColor(); | ||
var curve = CreateSeries(curveSettings?.Label ?? controller.ValueLabels[i], series[i], color); | ||
visualizer.Control.GraphPane.CurveList.Add(curve); | ||
} | ||
} | ||
} | ||
|
||
private CurveItem CreateSeries(string label, IPointListEdit points, Color color) | ||
{ | ||
var curve = new BarItem(label, points, color); | ||
curve.Label.IsVisible = !string.IsNullOrEmpty(label); | ||
curve.Bar.Fill.Type = FillType.Solid; | ||
curve.Bar.Border.IsVisible = false; | ||
return curve; | ||
} | ||
|
||
/// <inheritdoc/> | ||
protected override void ShowBuffer(IList<Timestamped<object>> values) | ||
{ | ||
base.ShowBuffer(values); | ||
if (values.Count > 0) | ||
{ | ||
visualizer.Control.Invalidate(); | ||
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void Show(object value) | ||
{ | ||
Show(DateTime.Now, value); | ||
} | ||
|
||
/// <inheritdoc/> | ||
protected override void Show(DateTime time, object value) | ||
{ | ||
controller.AddValues(value, this); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void Unload() | ||
{ | ||
visualizer = null; | ||
} | ||
} | ||
} |
Oops, something went wrong.