I am using Oxyplot to draw heatmap for a WPF project and needed to draw a crosshair that moves according to user clicks.
Since I couldn't find any good example or documentation on the topic, I made a small hack by using LineAnnotations and by over-riding Oxyplot mouse click event.
Here is the sample code using WPF with MVVM pattern-
MainWindow.xaml
<Grid>
<oxy:Plot x:Name="CrossHairPlot">
<oxy:Plot.Axes>
<oxy:LinearAxis Key="MyXAxis" Position="Bottom" IsZoomEnabled="False"/>
<oxy:LinearAxis Key="MyYAxis" Position="Left" IsZoomEnabled="False"/>
<oxy:LinearColorAxis Key="ZAxis" Position="Top" LowColor="Black"
HighColor="White" PaletteSize="300">
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="Red" Offset="0.2" />
<GradientStop Color="DarkOrange" Offset="0.4" />
<GradientStop Color="Yellow" Offset="0.6" />
<GradientStop Color="White" Offset="0.8" />
</oxy:LinearColorAxis>
</oxy:Plot.Axes>
<oxy:Plot.Series>
<oxy:HeatMapSeries Name="MyHeatMapSeries" ColorAxisKey="Z"
XAxisKey="MyXAxis" YAxisKey="MyYAxis"
Data="{Binding Path=MyData, UpdateSourceTrigger=PropertyChanged}"
X0="{Binding Path=MinX}"
X1="{Binding Path=MaxX}"
Y0="{Binding Path=MinY}"
Y1="{Binding Path=MaxY}" />
</oxy:Plot.Series>
</oxy:Plot>
</Grid>
And here is my code behind file
public partial class MainWindow : Window
{
// View Model Instance
private readonly MainWindowViewModel _viewModel;
private OxyPlot.Wpf.LineAnnotation crosshair_v;
private OxyPlot.Wpf.LineAnnotation crosshair_h;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainWindowViewModel();
this.DataContext = _viewModel;
InitializeCrossHair();
BindMouseClick();
}
private void InitializeCrossHair()
{
// Add Cross hair to plot
crosshair_v = new OxyPlot.Wpf.LineAnnotation
{
Type = LineAnnotationType.Vertical,
Color = Color.FromRgb(135, 206, 250),
StrokeThickness = 1.05,
LineStyle = LineStyle.Solid,
X = 0.5,
Y = 0
};
crosshair_h = new OxyPlot.Wpf.LineAnnotation
{
Type = LineAnnotationType.Horizontal,
Color = Color.FromRgb(135, 206, 250),
StrokeThickness = 1.05,
LineStyle = LineStyle.Solid,
X = 0,
Y = 0.5
};
CrossHairPlot.Annotations.Add(crosshair_v);
CrossHairPlot.Annotations.Add(crosshair_h);
}
private void BindMouseClick()
{
var controller = CrossHairPlot.ActualController;
var handleClick = new DelegatePlotCommand<OxyMouseDownEventArgs>(
(v, c, e) =>
{
var args = new HitTestArguments(e.Position, 0);
Axis yAxis = CrossHairPlot.ActualModel.Axes[1];
DataPoint dataPoint = CrossHairPlot.ActualModel.DefaultXAxis.InverseTransform(args.Point.X, args.Point.Y, yAxis);
// Update cross hair position according to new mouse click
crosshair_v.X = dataPoint.X;
crosshair_v.Y = 0;
crosshair_h.X = 0;
crosshair_h.Y = dataPoint.Y;
CrossHairPlot.InvalidatePlot(false);
e.Handled = true;
});
controller.Bind(new OxyMouseDownGesture(OxyMouseButton.Left), handleClick);
}
}
The ViewModel class contains properties needed data binding
class MainWindowViewModel
{
public MainWindowViewModel()
{
MinX = 0;
MinY = 0;
MaxX = 1;
MaxY = 1;
MyData = GenerateHeatMap();
}
public double[,] MyData { get; set; }
public int MinX { get; set; }
public int MinY { get; set; }
public int MaxX { get; set; }
public int MaxY { get; set; }
// Generates heat map data.
private double[,] GenerateHeatMap()
{
const int rows = 11;
const int cols = 11;
var result = new double[rows, cols];
for (var i = 0; i < rows; i++)
{
for (var j = 0; j < cols; j++)
{
result[i, j] = Math.Sin(4 * Math.PI * i / rows) * Math.Sqrt(200 * Math.PI * j / cols);
}
}
return result;
}
}
And the end result 😎
Since I couldn't find any good example or documentation on the topic, I made a small hack by using LineAnnotations and by over-riding Oxyplot mouse click event.
Here is the sample code using WPF with MVVM pattern-
MainWindow.xaml
<Grid>
<oxy:Plot x:Name="CrossHairPlot">
<oxy:Plot.Axes>
<oxy:LinearAxis Key="MyXAxis" Position="Bottom" IsZoomEnabled="False"/>
<oxy:LinearAxis Key="MyYAxis" Position="Left" IsZoomEnabled="False"/>
<oxy:LinearColorAxis Key="ZAxis" Position="Top" LowColor="Black"
HighColor="White" PaletteSize="300">
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="Red" Offset="0.2" />
<GradientStop Color="DarkOrange" Offset="0.4" />
<GradientStop Color="Yellow" Offset="0.6" />
<GradientStop Color="White" Offset="0.8" />
</oxy:LinearColorAxis>
</oxy:Plot.Axes>
<oxy:Plot.Series>
<oxy:HeatMapSeries Name="MyHeatMapSeries" ColorAxisKey="Z"
XAxisKey="MyXAxis" YAxisKey="MyYAxis"
Data="{Binding Path=MyData, UpdateSourceTrigger=PropertyChanged}"
X0="{Binding Path=MinX}"
X1="{Binding Path=MaxX}"
Y0="{Binding Path=MinY}"
Y1="{Binding Path=MaxY}" />
</oxy:Plot.Series>
</oxy:Plot>
</Grid>
And here is my code behind file
public partial class MainWindow : Window
{
// View Model Instance
private readonly MainWindowViewModel _viewModel;
private OxyPlot.Wpf.LineAnnotation crosshair_v;
private OxyPlot.Wpf.LineAnnotation crosshair_h;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainWindowViewModel();
this.DataContext = _viewModel;
InitializeCrossHair();
BindMouseClick();
}
private void InitializeCrossHair()
{
// Add Cross hair to plot
crosshair_v = new OxyPlot.Wpf.LineAnnotation
{
Type = LineAnnotationType.Vertical,
Color = Color.FromRgb(135, 206, 250),
StrokeThickness = 1.05,
LineStyle = LineStyle.Solid,
X = 0.5,
Y = 0
};
crosshair_h = new OxyPlot.Wpf.LineAnnotation
{
Type = LineAnnotationType.Horizontal,
Color = Color.FromRgb(135, 206, 250),
StrokeThickness = 1.05,
LineStyle = LineStyle.Solid,
X = 0,
Y = 0.5
};
CrossHairPlot.Annotations.Add(crosshair_v);
CrossHairPlot.Annotations.Add(crosshair_h);
}
private void BindMouseClick()
{
var controller = CrossHairPlot.ActualController;
var handleClick = new DelegatePlotCommand<OxyMouseDownEventArgs>(
(v, c, e) =>
{
var args = new HitTestArguments(e.Position, 0);
Axis yAxis = CrossHairPlot.ActualModel.Axes[1];
DataPoint dataPoint = CrossHairPlot.ActualModel.DefaultXAxis.InverseTransform(args.Point.X, args.Point.Y, yAxis);
// Update cross hair position according to new mouse click
crosshair_v.X = dataPoint.X;
crosshair_v.Y = 0;
crosshair_h.X = 0;
crosshair_h.Y = dataPoint.Y;
CrossHairPlot.InvalidatePlot(false);
e.Handled = true;
});
controller.Bind(new OxyMouseDownGesture(OxyMouseButton.Left), handleClick);
}
}
The ViewModel class contains properties needed data binding
class MainWindowViewModel
{
public MainWindowViewModel()
{
MinX = 0;
MinY = 0;
MaxX = 1;
MaxY = 1;
MyData = GenerateHeatMap();
}
public double[,] MyData { get; set; }
public int MinX { get; set; }
public int MinY { get; set; }
public int MaxX { get; set; }
public int MaxY { get; set; }
// Generates heat map data.
private double[,] GenerateHeatMap()
{
const int rows = 11;
const int cols = 11;
var result = new double[rows, cols];
for (var i = 0; i < rows; i++)
{
for (var j = 0; j < cols; j++)
{
result[i, j] = Math.Sin(4 * Math.PI * i / rows) * Math.Sqrt(200 * Math.PI * j / cols);
}
}
return result;
}
}
And the end result 😎
Comments
Hi, I'm new to C # and I needed to use OxyPlot for scientific work. I wanted to see how your code works. And why does not it start for me. Can I contact you somehow? my mail is -roman.kripton@gmail.com