using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using Keysight.OpenTap.ResultsViewer;
namespace OpenTap.Plugins.PluginDevelopment
{
///
/// Result XY Chart example
///
/// In this example we'll try to create a custom XY chart for use with the Keysight OpenTAP ResultsViewer application.
///
///
/// The result charting system is designed to be very high performance and very high flexibility.
/// Hence the API for creating custom plots is a bit cumbersome to use.
///
/// To help visualizing the concept used, imaging the data shown as a big table with each different type of
/// data being columns. (See the 'Data Table (Full)' plot chart for how this is shown.)
///
/// The data is split into "plot series". Each series represents a slice of the data.
/// The series contains indexes into each axis, which can be thought of as the columns of the data table.
/// For any given chart, a set of axis (columns) are selected and some rows (indexes) are taken from each column.
/// See getPoints for an example.
///
/// The filters are applied behind the scene and the result of the filtering is a list of indexes for each plot series.
/// The indexes are used to select the rows of the table, while the axis defined decides which column.
///
/// There is very little copying of the actual data. All filtering is represented by the index list.
///
///
///
[Display("XY Chart Example")]
public class ResultXYChartExample : Keysight.OpenTap.ResultsViewer.CustomResultsViewerPlugin
{
// Settings can be created like when developing other types of plugins,
// but you have to manually notify when properties has changed using PropertyNotify.
double offsetX = 0;
[Display("Offset X")]
public double OffsetX
{
get => offsetX;
set
{
offsetX = value;
PropertyNotify(nameof(OffsetX));
}
}
double offsetY;
[Display("Offset Y")]
public double OffsetY
{
get => offsetY;
set
{
offsetY = value;
this.PropertyNotify(nameof(OffsetY));
}
}
/// Called when the UI wants to clear the plot.
public override void Clear()
{
canvas.Children.Clear();
}
/// Called when the backing data has been changed.
public override void Invalidate()
{
}
readonly List axisGroups = new List
{
// The axis groups are very important. They define which dimensions are allowed in the plot.
// The tag is important for identifying the dimension within the date in the RedrawPlot method.
new AxisGroup
{
//Tag = 0: First dimension. This is important later when identifying the plot data.
Tag = 0,
// this one we call 'X': the X axis.
Name = "X",
// we only accept double number types for this plot
ValidTypes = AxisType.Double,
// A description
Description = "Our X/horizontal axis."
},
// Below is the definition of the Y axis.
new AxisGroup { Tag = 1, Name = "Y", ValidTypes = AxisType.Double, Description = "Our Y axis." }
};
public override List GetAxisGroups() => axisGroups;
/// False in most cases. if you need all data available, return true.
/// If this is set to return true, GetAxisGroups should return an empty list.
///
public override bool NeedAllAxes() => false;
public override bool HasLimits() => false;
List getPoints(List AxisData, List PlotSeries)
{
var result = new List();
foreach (var yAxis in AxisData.Where(x => x.Tag == 1))
{
foreach (var xAxis in AxisData.Where(x => x.Tag == 0))
{
// then we loop across each plot series
// the series can be seen as different lines in a x/y line chart.
foreach (var plot in PlotSeries)
{
foreach (var index in plot.Indices)
{
result.Add(new Point(xAxis.DoubleData[index], yAxis.DoubleData[index]));
}
}
}
}
return result;
}
/// rebuild the plot
/// The title of the plot. This can be shown inside the plot area.
/// The axises selected by the user. The axises used predefined tags to separate dimensions.
/// not included in example
/// Contains the series we are showing. These are labeled indexes into our plot data
public override void RedrawPlot(string Title, List AxisData, List LimitData, List PlotSeries)
{
if (canvas.IsLoaded == false) return;
var points = getPoints(AxisData, PlotSeries);
// lets normalize the data so its in the center of the screen.
// center the data vertically.
var canvasHeight = canvas.ActualHeight;
var canvasWidth = canvas.ActualWidth;
var minx = points.Select(pt => pt.X).Min();
var maxx = points.Select(pt => pt.X).Max();
var miny = points.Select(pt => pt.Y).Min();
var maxy = points.Select(pt => pt.Y).Max();
var centerX = (maxx + minx)/2;
var centerY = (maxy + miny)/2;
var spanx = minx - maxx;
if (spanx == 0) spanx = 1;
var spany = miny - maxy;
if (spany == 0) spany = 1;
// normalization values
var scaleX = canvasWidth /spanx;
var scaleY = canvasHeight /spany;
var startX = canvasWidth / 2 - centerX * scaleX;
var startY = canvasHeight / 2 - centerY* scaleY;
// now lets add the points at the right places.
foreach (var pt in points)
{
var point = new Rectangle { };
point.Width = 2;
point.Height = 2;
point.Fill = Brushes.White;
// extract axis data for this index.
Canvas.SetLeft(point, (pt.X + OffsetX)*scaleX * 0.95 + startX);
Canvas.SetTop(point, (pt.Y + OffsetY) *scaleY * 0.95 + startY);
this.canvas.Children.Add(point);
}
}
readonly Canvas canvas = new Canvas() {Background = Brushes.Black};
public ResultXYChartExample()
{
// when the canvas is loaded we need to do redraw
canvas.Loaded += (s, e) => PropertyNotify("");
}
/// our control, showing the plot in the user interface
public override FrameworkElement UserControl => canvas;
public override void DeserializeProperties(Dictionary Props)
{
if (Props.TryGetValue("OffsetX", out var offsetXStr))
double.TryParse(offsetXStr, out offsetX);
if (Props.TryGetValue(nameof(OffsetY), out var offsetYStr))
double.TryParse(offsetYStr, out offsetY);
}
public override Dictionary SerializeProperties()
{
return new Dictionary
{
{"OffsetX", OffsetX.ToString(CultureInfo.InvariantCulture)},
{"OffsetY", OffsetY.ToString(CultureInfo.InvariantCulture)}
};
}
}
}