Skip to content

Instantly share code, notes, and snippets.

@MareMare
Last active April 4, 2022 18:39
Show Gist options
  • Save MareMare/be4b3976c27448ec1ec411301041659f to your computer and use it in GitHub Desktop.
Save MareMare/be4b3976c27448ec1ec411301041659f to your computer and use it in GitHub Desktop.
ScottPlot: I want to display the value of the data point closest to the cursor from data containing gaps.

ScottPlot/ScottPlot#1762 (comment)

ScottPlot: I want to display the value of the data point closest to the cursor from data containing gaps.

ScottPlot: I want to display the value of the data point closest to the cursor from data containing gaps.

Hi, I would like to mark the highlights of the two locations indicated in the open circle.

The main code to do this is as follows:

private readonly List<ScottPlot.Plottable.SignalPlotXY> _sigsOfSeries0;
private readonly List<ScottPlot.Plottable.SignalPlotXY> _sigsOfSeries1;
private readonly ScottPlot.Plottable.MarkerPlot _highlightedPoint0;
private readonly ScottPlot.Plottable.MarkerPlot _highlightedPoint1;

private void FormsPlot1_MouseMove(object sender, MouseEventArgs e)
{
    var (mousePixelX, mousePixelY) = formsPlot1.GetMousePixel();

    // Search from data series 0's SignalPlotXYs.
    var (x0, y0) = ChooseNearestXY(mousePixelX, mousePixelY, _sigsOfSeries0);
    if (x0.HasValue && y0.HasValue)
    {
        _highlightedPoint0.X = x0.Value;
        _highlightedPoint0.Y = y0.Value;
        _highlightedPoint0.IsVisible = true;
    }

    // Search from data series 1's SignalPlotXYs.
    var (x1, y1) = ChooseNearestXY(mousePixelX, mousePixelY, _sigsOfSeries1);
    if (x1.HasValue && y1.HasValue)
    {
        _highlightedPoint1.X = x1.Value;
        _highlightedPoint1.Y = y1.Value;
        _highlightedPoint1.IsVisible = true;
    }

    formsPlot1.Refresh();
}

private (double? nearestXorNull, double? nearestYorNull) ChooseNearestXY(
    float mousePixelX,
    float mousePixelY,
    IEnumerable<ScottPlot.Plottable.SignalPlotXY> sigsToSerach)
{
    var mousePixelCoordinate = new ScottPlot.Coordinate(mousePixelX, mousePixelY);

    double? minDistance = null;
    (double? nearestX, double? nearestY) = (null, null);
    foreach (var sig in sigsToSerach)
    {
        (var mouseUnitX, _) = formsPlot1.GetMouseCoordinates(sig.XAxisIndex, sig.YAxisIndex);
        (var x, var y, _) = sig.GetPointNearestX(mouseUnitX);

        (var nearestPixelX, var nearestPixelY) = formsPlot1.Plot.GetPixel(x, y, sig.XAxisIndex, sig.YAxisIndex);
        var nearestPixelCoordinate = new ScottPlot.Coordinate(nearestPixelX, nearestPixelY);

        var nowDistance = mousePixelCoordinate.Distance(nearestPixelCoordinate);
        if (minDistance == null || minDistance > nowDistance)
        {
            (nearestX, nearestY) = (x, y);
            minDistance = nowDistance;
        }
    }

    return (nearestX, nearestY);
}
The sample data is generated as follows:
public DemoForm6()
{
    _sigsOfSeries0 = new();
    _sigsOfSeries1 = new();

    InitializeComponent();

    // create data for series0.
    var series0_segment0 = new (double x, double y)[]
    {
        (1, 5), (2, 4), (3, 8),
    };
    var series0_segment1 = new (double x, double y)[]
    {
        (5, 6), (6, 9), (7, 4), (8, 7),
    };
    var series0_segment2 = new (double x, double y)[]
    {
        (10, 8), (11, 3), (12, 7),
    };

    // create data for series1.
    var series1_segment0 = new (double x, double y)[]
    {
        (1, 40), (2, 30), (3, 60),
    };
    var series1_segment1 = new (double x, double y)[]
    {
        (6, 70), (7, 90), (8, 40), (9, 70),
    };

    // set up axises.
    var plot = formsPlot1.Plot;
    plot.SetAxisLimitsX(xMin: 0, xMax: 13);

    var labelOfDataSeries0 = "Data Series 0";
    plot.YAxis.Label(labelOfDataSeries0);
    plot.YAxis.Ticks(enable: true);
    plot.SetAxisLimits(yMin: 0, yMax: 10, yAxisIndex: 0);

    var labelOfDataSeries1 = "Data Series 1";
    plot.YAxis2.Label(labelOfDataSeries1);
    plot.YAxis2.Ticks(enable: true);
    plot.SetAxisLimits(yMin: 0, yMax: 100, yAxisIndex: 1);

    // set up for descriptions on plot.(marker, hspan and tooltip)
    SetupForDescription(plot);

    // series0.
    foreach (var segments in new[] { series0_segment0, series0_segment1, series0_segment2, })
    {
        var xs0 = segments.Select(pair => pair.x).ToArray();
        var ys0 = segments.Select(pair => pair.y).ToArray();
        var sig = plot.AddSignalXY(xs0, ys0, label: labelOfDataSeries0, color: ScottPlot.Palette.Category10.GetColor(0));
        sig.YAxisIndex = 0;
        _sigsOfSeries0.Add(sig);

        labelOfDataSeries0 = null; // legend label shows only first segment.
    }

    // series1.
    foreach (var segments in new[] { series1_segment0, series1_segment1, })
    {
        var xs0 = segments.Select(pair => pair.x).ToArray();
        var ys0 = segments.Select(pair => pair.y).ToArray();
        var sig = plot.AddSignalXY(xs0, ys0, label: labelOfDataSeries1, color: ScottPlot.Palette.Category10.GetColor(1));
        sig.YAxisIndex = 1;
        _sigsOfSeries1.Add(sig);

        labelOfDataSeries1 = null; // legend label shows only first segment.
    }

    // set up for highlighted point
    _highlightedPoint0 = plot.AddPoint(0, 0);
    _highlightedPoint0.YAxisIndex = 0;
    _highlightedPoint0.Color = ScottPlot.Palette.Category10.GetColor(0);
    _highlightedPoint0.MarkerSize = 15;
    _highlightedPoint0.MarkerShape = ScottPlot.MarkerShape.filledDiamond;
    _highlightedPoint0.IsVisible = false;

    _highlightedPoint1 = plot.AddPoint(0, 0);
    _highlightedPoint1.YAxisIndex = 1;
    _highlightedPoint1.Color = ScottPlot.Palette.Category10.GetColor(1);
    _highlightedPoint1.MarkerSize = 15;
    _highlightedPoint1.MarkerShape = ScottPlot.MarkerShape.filledDiamond;
    _highlightedPoint1.IsVisible = false;

    plot.Legend();
    formsPlot1.Refresh();

    formsPlot1.MouseMove += FormsPlot1_MouseMove;
    formsPlot1.MouseLeave += FormsPlot1_MouseLeave;
}

The complete code is here.

This code is working as I expect, but Is there a better way to do this than the way I am doing it with my ChooseNearestXY method?

I would be grateful if you could give me some advice.

Best Regards,

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace ScottPlotSandbox.NET48
{
public partial class DemoForm6 : Form
{
private readonly List<ScottPlot.Plottable.SignalPlotXY> _sigsOfSeries0;
private readonly List<ScottPlot.Plottable.SignalPlotXY> _sigsOfSeries1;
private readonly ScottPlot.Plottable.MarkerPlot _highlightedPoint0;
private readonly ScottPlot.Plottable.MarkerPlot _highlightedPoint1;
public DemoForm6()
{
_sigsOfSeries0 = new();
_sigsOfSeries1 = new();
InitializeComponent();
// create data for series0.
var series0_segment0 = new (double x, double y)[]
{
(1, 5), (2, 4), (3, 8),
};
var series0_segment1 = new (double x, double y)[]
{
(5, 6), (6, 9), (7, 4), (8, 7),
};
var series0_segment2 = new (double x, double y)[]
{
(10, 8), (11, 3), (12, 7),
};
// create data for series1.
var series1_segment0 = new (double x, double y)[]
{
(1, 40), (2, 30), (3, 60),
};
var series1_segment1 = new (double x, double y)[]
{
(6, 70), (7, 90), (8, 40), (9, 70),
};
// set up axises.
var plot = formsPlot1.Plot;
plot.SetAxisLimitsX(xMin: 0, xMax: 13);
var labelOfDataSeries0 = "Data Series 0";
plot.YAxis.Label(labelOfDataSeries0);
plot.YAxis.Ticks(enable: true);
plot.SetAxisLimits(yMin: 0, yMax: 10, yAxisIndex: 0);
var labelOfDataSeries1 = "Data Series 1";
plot.YAxis2.Label(labelOfDataSeries1);
plot.YAxis2.Ticks(enable: true);
plot.SetAxisLimits(yMin: 0, yMax: 100, yAxisIndex: 1);
// set up for descriptions on plot.(marker, hspan and tooltip)
SetupForDescription(plot);
// series0.
foreach (var segments in new[] { series0_segment0, series0_segment1, series0_segment2, })
{
var xs0 = segments.Select(pair => pair.x).ToArray();
var ys0 = segments.Select(pair => pair.y).ToArray();
var sig = plot.AddSignalXY(xs0, ys0, label: labelOfDataSeries0, color: ScottPlot.Palette.Category10.GetColor(0));
sig.YAxisIndex = 0;
_sigsOfSeries0.Add(sig);
labelOfDataSeries0 = null; // legend label shows only first segment.
}
// series1.
foreach (var segments in new[] { series1_segment0, series1_segment1, })
{
var xs0 = segments.Select(pair => pair.x).ToArray();
var ys0 = segments.Select(pair => pair.y).ToArray();
var sig = plot.AddSignalXY(xs0, ys0, label: labelOfDataSeries1, color: ScottPlot.Palette.Category10.GetColor(1));
sig.YAxisIndex = 1;
_sigsOfSeries1.Add(sig);
labelOfDataSeries1 = null; // legend label shows only first segment.
}
// set up for highlighted point
_highlightedPoint0 = plot.AddPoint(0, 0);
_highlightedPoint0.YAxisIndex = 0;
_highlightedPoint0.Color = ScottPlot.Palette.Category10.GetColor(0);
_highlightedPoint0.MarkerSize = 15;
_highlightedPoint0.MarkerShape = ScottPlot.MarkerShape.filledDiamond;
_highlightedPoint0.IsVisible = false;
_highlightedPoint1 = plot.AddPoint(0, 0);
_highlightedPoint1.YAxisIndex = 1;
_highlightedPoint1.Color = ScottPlot.Palette.Category10.GetColor(1);
_highlightedPoint1.MarkerSize = 15;
_highlightedPoint1.MarkerShape = ScottPlot.MarkerShape.filledDiamond;
_highlightedPoint1.IsVisible = false;
plot.Legend();
formsPlot1.Refresh();
formsPlot1.MouseMove += FormsPlot1_MouseMove;
formsPlot1.MouseLeave += FormsPlot1_MouseLeave;
}
private static void SetupForDescription(ScottPlot.Plot plot)
{
var hSpanOfSegment0 = plot.AddHorizontalSpan(0.5, 3.9, color: Color.FromArgb(0x22, Color.Red));
hSpanOfSegment0.BorderColor = Color.FromArgb(0x55, Color.Red);
hSpanOfSegment0.BorderLineStyle = ScottPlot.LineStyle.Dash;
hSpanOfSegment0.BorderLineWidth = 1;
hSpanOfSegment0.DragEnabled = false;
hSpanOfSegment0.XAxisIndex = 0;
hSpanOfSegment0.Label = "Segment 0";
var hSpanOfSegment1 = plot.AddHorizontalSpan(4.1, 9.5, color: Color.FromArgb(0x22, Color.Green));
hSpanOfSegment1.BorderColor = Color.FromArgb(0x55, Color.Green);
hSpanOfSegment1.BorderLineStyle = ScottPlot.LineStyle.Dash;
hSpanOfSegment1.BorderLineWidth = 1;
hSpanOfSegment1.DragEnabled = false;
hSpanOfSegment1.XAxisIndex = 0;
hSpanOfSegment1.Label = "Segment 1";
var mp0 = plot.AddPoint(5, 6);
mp0.YAxisIndex = 0;
mp0.Color = Color.Red;
mp0.MarkerSize = 20;
mp0.MarkerShape = ScottPlot.MarkerShape.openCircle;
var mp1 = plot.AddPoint(3, 60);
mp1.YAxisIndex = 1;
mp1.Color = Color.Red;
mp1.MarkerSize = 20;
mp1.MarkerShape = ScottPlot.MarkerShape.openCircle;
var tt = plot.AddTooltip(label: "mouse cursor is hear!\nI would like to mark the highlights of the two locations indicated in the open circle.", x: 4d, y: 5d);
tt.FillColor = SystemColors.Info;
tt.XAxisIndex = 0;
tt.YAxisIndex = 0;
}
private void FormsPlot1_MouseLeave(object sender, EventArgs e)
{
_highlightedPoint0.IsVisible = false;
_highlightedPoint1.IsVisible = false;
formsPlot1.Refresh();
}
private void FormsPlot1_MouseMove(object sender, MouseEventArgs e)
{
var (mousePixelX, mousePixelY) = formsPlot1.GetMousePixel();
// Search from data series 0's SignalPlotXYs.
var (x0, y0) = ChooseNearestXY(mousePixelX, mousePixelY, _sigsOfSeries0);
if (x0.HasValue && y0.HasValue)
{
_highlightedPoint0.X = x0.Value;
_highlightedPoint0.Y = y0.Value;
_highlightedPoint0.IsVisible = true;
}
// Search from data series 1's SignalPlotXYs.
var (x1, y1) = ChooseNearestXY(mousePixelX, mousePixelY, _sigsOfSeries1);
if (x1.HasValue && y1.HasValue)
{
_highlightedPoint1.X = x1.Value;
_highlightedPoint1.Y = y1.Value;
_highlightedPoint1.IsVisible = true;
}
formsPlot1.Refresh();
}
private (double? nearestXorNull, double? nearestYorNull) ChooseNearestXY(float mousePixelX, float mousePixelY, IEnumerable<ScottPlot.Plottable.SignalPlotXY> sigsToSerach)
{
var mousePixelCoordinate = new ScottPlot.Coordinate(mousePixelX, mousePixelY);
double? minDistance = null;
(double? nearestX, double? nearestY) = (null, null);
foreach (var sig in sigsToSerach)
{
(var mouseUnitX, _) = formsPlot1.GetMouseCoordinates(sig.XAxisIndex, sig.YAxisIndex);
(var x, var y, _) = sig.GetPointNearestX(mouseUnitX);
(var nearestPixelX, var nearestPixelY) = formsPlot1.Plot.GetPixel(x, y, sig.XAxisIndex, sig.YAxisIndex);
var nearestPixelCoordinate = new ScottPlot.Coordinate(nearestPixelX, nearestPixelY);
var nowDistance = mousePixelCoordinate.Distance(nearestPixelCoordinate);
if (minDistance == null || minDistance > nowDistance)
{
(nearestX, nearestY) = (x, y);
minDistance = nowDistance;
}
}
return (nearestX, nearestY);
}
}
}
@MareMare
Copy link
Author

MareMare commented Apr 4, 2022

Animation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment