Skip to main content

Customized Crosshair with OxyPlot charting library

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 😎



   

Comments

Unknown said…

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
sushmag said…
Hi Roman, do you still have problems with OxyPlot? Unfortunately they don't have enough documentation. I have created a public repository in GitHub with some small Oxyplot sample projects. I can share the repo link to your email id if you wish so.

Popular posts from this blog

Solving PyCharm bug: "Python helpers are not copied yet..."

I have been using PyCharm Professional to run and debug Python code from my Windows machine to a remote Linux device. There are some other tools available for the same purpose (e.g. Python Tool for Visual Studio 2017). But in my opinion, PyCharm Professional stands out among its counterparts as it comes with a "All Batteries Included" setup. Once you configure remote Python interpreter in PyCharm, then it works out of the box. However, today after upgrading to PyCharm Professional 2018.2.1, I could not run my Python script on remote device. The execution always failed with below error - "Error running 'hello': Python helpers are not copied yet to the remote host. Please wait until remote interpreter initialization finishes." To solve this issue, I had to remove the ".pycharm_helpers " folder from the remove device and then restart PyCharm so that the folder is re-created and files are copied again. Here are the steps with comma

Solving ‘Could not resolve host: github.com’ issue

Recently I ran into this issue while trying to clone one of my own repository from GitHub C:\GitHub\FS> git clone https://github.com/<username>/FS.git Cloning into 'FS'... fatal: unable to access 'https://github.com/<username>/FS.git/': Could not resolve host: github.com At first I thought that this could be due to some proxy setting. So I tried to unset it by executing below command - C:\GitHub\FS> git config --global --unset https.proxy But that didn’t fix the problem and I was still getting the same error. As it turned out later, the culprit was my VPN connection. Turning off the VPN fixed the issue. For the records, I was using ‘Surfshark’ on my Windows 10 laptop.