Skip to content

Instantly share code, notes, and snippets.

@VisualMelon
Last active September 5, 2020 11:47
Show Gist options
  • Save VisualMelon/3d35da1a7fc8ee97a4b4c5cd8862e282 to your computer and use it in GitHub Desktop.
Save VisualMelon/3d35da1a7fc8ee97a4b4c5cd8862e282 to your computer and use it in GitHub Desktop.
OxyPlot Cursors
using OxyPlot;
using OxyPlot.Annotations;
using OxyPlot.Axes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Utilities
{
public class CursorPair
{
public CursorPair(CursorAnnotation low, CursorAnnotation high)
{
Low = low ?? throw new ArgumentNullException(nameof(low));
High = high ?? throw new ArgumentNullException(nameof(high));
}
public CursorAnnotation Low { get; }
public CursorAnnotation High { get; }
public bool AnyOn => Low.IsOn || High.IsOn;
public void GetInfo(out double low, out double high, out double delta)
{
low = Low.Position;
high = High.Position;
delta = high - low;
}
}
public class CursorInfoAnnotation : Annotation
{
private List<CursorPair> Pairs { get; } = new List<CursorPair>();
public ScreenPoint Position { get; set; }
public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Left;
public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Top;
public double Width { get; } = 200;
public void AddPair(CursorAnnotation low, CursorAnnotation high)
{
Pairs.Add(new CursorPair(low, high));
}
private int OnCount => Pairs.Count(p => p.AnyOn);
private OxyRect Bounds { get; set; }
public CursorInfoAnnotation()
{
this.MouseDown += CursorInfoAnnotation_MouseDown;
this.MouseUp += CursorInfoAnnotation_MouseUp;
this.MouseMove += CursorInfoAnnotation_MouseMove;
}
public override void Render(IRenderContext rc)
{
int on = OnCount;
if (on == 0)
return;
var subHeight = rc.MeasureText("0123456789").Height + 2;
var subWidth = Width / 3;
var height = subHeight * (on + 1);
var x = Position.X + (Sign(HorizontalAlignment) + 1) * Width / 2;
var y = Position.Y + (Sign(VerticalAlignment) + 1) * height / 2;
var topLeft = new ScreenPoint(x, y);
Bounds = new OxyRect(topLeft, new OxySize(Width, height));
rc.DrawRectangle(Bounds, OxyColor.FromAColor(128, OxyColors.LightSteelBlue), OxyColors.Black, 1);
rc.DrawText(new ScreenPoint(x + subWidth * 0.5, y + subHeight * 0.5), "Low", OxyColors.Black, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
rc.DrawText(new ScreenPoint(x + subWidth * 1.5, y + subHeight * 0.5), "High", OxyColors.Black, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
rc.DrawText(new ScreenPoint(x + subWidth * 2.5, y + subHeight * 0.5), "Delta", OxyColors.Black, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
foreach (var p in Pairs.Where(p => p.AnyOn))
{
y += subHeight;
p.GetInfo(out double low, out double high, out double delta);
if (!double.IsNaN(low))
rc.DrawText(new ScreenPoint(x + subWidth * 0.5, y + subHeight * 0.5), low.ToString("G5"), p.Low.Color, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
if (!double.IsNaN(high))
rc.DrawText(new ScreenPoint(x + subWidth * 1.5, y + subHeight * 0.5), high.ToString("G5"), p.High.Color, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
if (!double.IsNaN(delta))
rc.DrawText(new ScreenPoint(x + subWidth * 2.5, y + subHeight * 0.5), delta.ToString("G5"), p.Low.Color, horizontalAlignment: HorizontalAlignment.Center, verticalAlignment: VerticalAlignment.Middle);
}
}
protected override HitTestResult HitTestOverride(HitTestArguments args)
{
if (Bounds.Contains(args.Point))
{
return new HitTestResult(this, args.Point);
}
return null;
}
private bool moused = false;
private ScreenVector mouseOffset;
private void CursorInfoAnnotation_MouseMove(object sender, OxyMouseEventArgs e)
{
if (moused && e.Position.X >= 0 && e.Position.Y >= 0 && e.Position.X < PlotModel.Width && e.Position.Y < PlotModel.Height)
{
Position = e.Position - mouseOffset;
PlotModel.InvalidatePlot(false);
}
}
private void CursorInfoAnnotation_MouseUp(object sender, OxyMouseEventArgs e)
{
moused = false;
}
private void CursorInfoAnnotation_MouseDown(object sender, OxyMouseDownEventArgs e)
{
if (e.ChangedButton == OxyMouseButton.Left)
{
moused = true;
mouseOffset = e.Position - Position;
e.Handled = true;
}
}
private int Sign(HorizontalAlignment horizontalAlignment)
{
switch (horizontalAlignment)
{
case HorizontalAlignment.Left:
return -1;
case HorizontalAlignment.Center:
return 0;
case HorizontalAlignment.Right:
return +1;
default:
throw new ArgumentException(nameof(horizontalAlignment));
}
}
private int Sign(VerticalAlignment verticalAlignment)
{
switch (verticalAlignment)
{
case VerticalAlignment.Top:
return -1;
case VerticalAlignment.Middle:
return 0;
case VerticalAlignment.Bottom:
return +1;
default:
throw new ArgumentException(nameof(verticalAlignment));
}
}
}
public class CursorAnnotation : Annotation
{
public double Position { get; set; } = double.NaN;
public OxyColor Color { get; set; } = OxyColors.LightGray;
public bool ReverseX { get; set; } = false;
public bool ReverseY { get; set; } = false;
public double BoxSize { get; set; } = 8;
public double LineThickness { get; set; } = 1;
public bool IsEnabled { get; set; } = true;
public CursorAnnotation(string xAxisKey, string yAxisKey)
{
XAxisKey = xAxisKey;
YAxisKey = yAxisKey;
Layer = AnnotationLayer.BelowAxes;
this.MouseDown += CursorAnnotation_MouseDown;
this.MouseUp += CursorAnnotation_MouseUp;
this.MouseMove += CursorAnnotation_MouseMove;
}
public bool IsOn => IsEnabled && XAxis.IsAxisVisible && !double.IsNaN(Position);
public void GetPoints(out ScreenPoint near, out ScreenPoint far, out ScreenPoint off)
{
var p = Position;
if (double.IsNaN(p))
p = ReverseX ? XAxis.ActualMaximum : XAxis.ActualMinimum;
if (this.YAxisKey == null)
{
var xp = XAxis.Transform(p);
near = new ScreenPoint(xp, XAxis.IsHorizontal() ? XAxis.PlotModel.PlotArea.Bottom : XAxis.PlotModel.PlotArea.Left);
far = new ScreenPoint(xp, XAxis.IsHorizontal() ? XAxis.PlotModel.PlotArea.Top : XAxis.PlotModel.PlotArea.Right);
}
else
{
near = XAxis.Transform(p, YAxis.ActualMinimum, YAxis);
far = XAxis.Transform(p, YAxis.ActualMaximum, YAxis);
}
if (ReverseY)
{
var t = near;
near = far;
far = t;
}
if (XAxis.IsVertical())
{
near = new ScreenPoint(near.Y, near.X);
far = new ScreenPoint(far.Y, far.X);
off = new ScreenPoint(near.X + (ReverseY ? BoxSize / 2 : -BoxSize / 2), far.Y);
}
else
{
off = new ScreenPoint(near.X, far.Y + (ReverseY ? BoxSize / 2 : -BoxSize / 2));
}
}
public double TranslateScreenPoint(ScreenPoint sp)
{
if (XAxis.IsVertical())
sp = new ScreenPoint(sp.Y, sp.X);
var newPosition = XAxis.InverseTransform(sp.X, sp.Y, YAxis).X;
if (XAxis.ActualMinimum > newPosition || XAxis.ActualMaximum < newPosition)
newPosition = double.NaN;
return newPosition;
}
public override void Render(IRenderContext rc)
{
if (!IsEnabled || !XAxis.IsAxisVisible || XAxis.ActualMinimum > Position || XAxis.ActualMaximum < Position)
return;
GetPoints(out ScreenPoint near, out ScreenPoint far, out ScreenPoint off);
rc.DrawLine(new[] { near, far }, Color, LineThickness);
var offRect = new OxyRect(off.X - BoxSize / 2, off.Y - BoxSize / 2, BoxSize, BoxSize);
rc.DrawRectangle(offRect, Color, OxyColors.Transparent, 0);
}
protected override HitTestResult HitTestOverride(HitTestArguments args)
{
if (!IsEnabled || !XAxis.IsAxisVisible || XAxis.ActualMinimum > Position || XAxis.ActualMaximum < Position)
return null;
GetPoints(out ScreenPoint near, out ScreenPoint far, out ScreenPoint off);
var lineHitRect = new OxyRect(near, far).Inflate(new OxyThickness(LineThickness + 1));
var offRect = new OxyRect(off.X - BoxSize / 2, off.Y - BoxSize / 2, BoxSize, BoxSize);
if (lineHitRect.Contains(args.Point) || offRect.Contains(args.Point))
{
return new HitTestResult(this, near);
}
return null;
}
private bool moused = false;
private void CursorAnnotation_MouseMove(object sender, OxyMouseEventArgs e)
{
if (moused)
{
Position = TranslateScreenPoint(e.Position);
PlotModel.InvalidatePlot(false);
}
}
private void CursorAnnotation_MouseUp(object sender, OxyMouseEventArgs e)
{
moused = false;
}
private void CursorAnnotation_MouseDown(object sender, OxyMouseDownEventArgs e)
{
if (e.ChangedButton == OxyMouseButton.Left)
{
moused = true;
e.Handled = true;
}
}
}
}
var cusorInfo = new CursorInfoAnnotation();
plot.Annotations.Add(cusorInfo);
AddCursorPair(cusorInfo, "mv", "t", false, true, OxyColors.Red);
AddCursorPair(cusorInfo, "t", null, true, true, OxyColors.Blue);
private void AddCursorPair(CursorInfoAnnotation cursorInfo, string xaxisKey, string yaxisKey, bool reverseX, bool reverseY, OxyColor color)
{
var low = AddCursor(cursorInfo.PlotModel, xaxisKey, yaxisKey, reverseX, reverseY, color);
var high = AddCursor(cursorInfo.PlotModel, xaxisKey, yaxisKey, reverseX, reverseY, color);
cursorInfo.AddPair(low, high);
}
private CursorAnnotation AddCursor(PlotModel plot, string xaxisKey, string yaxisKey, bool reverseX, bool reverseY, OxyColor color)
{
var res = new CursorAnnotation(xaxisKey, yaxisKey)
{
Color = color,
ReverseX = reverseX,
ReverseY = reverseY,
};
plot.Annotations.Add(res);
return res;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment