1

I am building a WPF application and rendering a chart using OxyPlot.

In order to maximize the performance of the chart, I switched to OxyPlot.WindowsForms and embedded the chart using WindowsFormsHost. Source: https://oxyplot.userecho.com/de/communities/1/topics/35-wpf-performance

Zooming is a very important part of the chart I'm rendering, but OxyPlot.WindowsForms seems to not have a great-looking tooltip.

WindowsForms: enter image description here

WPF: enter image description here

Is there any way to customize the WindowsForms tooltip to look anything like the WPF one? The most important things are the vertical and horizontal line of the point.

5
  • Looking into the source code of the component, it's not really a tooltip, it's a Label. An option could be getting the private trackingLabel and apply changes on the region of the control (or ideally replace it with a custom label which supports that shape). Commented Apr 4, 2022 at 14:27
  • That post is nine years old, I'm just curious, did you test WPF performance for your case and find it lacking? Commented Apr 4, 2022 at 14:46
  • @JimFoye I can see the UI differences, WPF has some trouble rendering many points and it glitches a bit sometimes and leaves blank spaces, which is not my desired outcome. I don't appear to have these issues with WinForms. Do you know how I can test the performance any other way? I haven't done any performance testing before. Commented Apr 4, 2022 at 14:55
  • @RezaAghaei Thanks! I've looked into it but I can't find any way to modify this trackerLabel, there's no public property that exposes it. I'll keep looking and hopefully something comes up! Commented Apr 4, 2022 at 14:56
  • If you're seeing the problems, then that's good enough :) Commented Apr 4, 2022 at 14:57

1 Answer 1

3

Looking into the source code of the component, it's not really a tooltip, it's a Label. An option could be getting the private trackingLabel and apply changes on the region of the control (or ideally replace it with a custom label which supports that shape).

Also to draw the crosshair tracker, you can handle Paint event of the plot, and draw the horizontal and vertical line based on the center of label.

well, what I mentioned above is just a quick workaround which results in the following:

enter image description here

Using the following code:

private Label trackerLabel;
private void Form1_Load(object sender, EventArgs e)
{
    var trackerLabelField = plotView1.GetType().GetField("trackerLabel",
        System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    trackerLabel = new BalloonLabel()
    {
        Visible = false,
        Parent = plotView1
    };
    trackerLabelField.SetValue(plotView1, trackerLabel);

    var myModel = new PlotModel { Title = "Example 1" };
    myModel.Series.Add(new FunctionSeries(Math.Cos, 0, 10, 0.1, "cos(x)"));
    this.plotView1.Model = myModel;
    this.plotView1.Paint += PlotView1_Paint;
    trackerLabel.VisibleChanged += TrackerLabel_VisibleChanged;
}
private void TrackerLabel_VisibleChanged(object sender, EventArgs e)
{
    plotView1.Invalidate();
}
private void PlotView1_Paint(object sender, PaintEventArgs e)
{
    if (trackerLabel.Visible)
    {
        var r = plotView1.Model.PlotArea;
        e.Graphics.DrawLine(Pens.Blue, trackerLabel.Left + trackerLabel.Width / 2, (int)r.Top,
            trackerLabel.Left + trackerLabel.Width / 2, (int)r.Bottom);
        e.Graphics.DrawLine(Pens.Blue, (int)r.Left, trackerLabel.Bottom, (int)r.Right, trackerLabel.Bottom);
    }
}

And this balloon label, which I assume is a good-enough start point:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class BalloonLabel : Label
{
    public BalloonLabel()
    {
        BackColor = SystemColors.Info;
        Padding = new Padding(5, 5, 5, 20);
        ArrowSize = new Size(10, 20);
        AutoSize = true;
    }
    private Size arrowSize;
    public Size ArrowSize
    {
        get { return arrowSize; }
        set
        {
            arrowSize = value;
            this.RecreateRegion();
        }
    }

    private GraphicsPath GetBalloon(Rectangle bounds)
    {
        GraphicsPath path = new GraphicsPath();
        path.StartFigure();

        if (arrowSize.Width > 0 && arrowSize.Height > 0)
        {
            path.AddLine(bounds.Left, bounds.Bottom - arrowSize.Height / 2, bounds.Left, bounds.Top);
            path.AddLine(bounds.Left, bounds.Top, bounds.Right, bounds.Top);
            path.AddLine(bounds.Right, bounds.Top, bounds.Right, bounds.Bottom - arrowSize.Height / 2);
            path.AddLine(bounds.Right, bounds.Bottom - arrowSize.Height / 2, bounds.Left + bounds.Width / 2 + arrowSize.Width / 2, bounds.Bottom - arrowSize.Height / 2);
            path.AddLine(bounds.Left + bounds.Width / 2 + arrowSize.Width / 2, bounds.Bottom - arrowSize.Height / 2,
                bounds.Left + bounds.Width / 2, bounds.Bottom);
            path.AddLine(bounds.Left + bounds.Width / 2, bounds.Bottom,
                bounds.Left + bounds.Width / 2 - arrowSize.Width / 2, bounds.Bottom - arrowSize.Height / 2);
            path.AddLine(bounds.Left + bounds.Width / 2 - arrowSize.Width / 2, bounds.Bottom - arrowSize.Height / 2,
                bounds.Left, bounds.Bottom - arrowSize.Height / 2);
        }
        else
        {
            path.AddLine(bounds.Left, bounds.Bottom, bounds.Left, bounds.Top);
            path.AddLine(bounds.Left, bounds.Top, bounds.Right, bounds.Top);
            path.AddLine(bounds.Right, bounds.Top, bounds.Right, bounds.Bottom);
            path.AddLine(bounds.Right, bounds.Bottom, bounds.Left, bounds.Bottom);
        }
        path.CloseFigure();
        return path;
    }

    private void RecreateRegion()
    {
        var r = ClientRectangle;
        using (var path = GetBalloon(r))
            this.Region = new Region(path);
        this.Invalidate();
    }
    protected override void OnSizeChanged(EventArgs e)
    {
        base.OnSizeChanged(e);
        this.RecreateRegion();
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        var r = ClientRectangle;
        r.Inflate(-1, -1);
        using (var path = GetBalloon(r))
        {
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            using (var pen = new Pen(Color.Gray, 1) { Alignment = PenAlignment.Inset })
                e.Graphics.DrawPath(pen, path);
            if (Parent != null)
                Parent.Invalidate();
        }
    }
}

And here is the animated result:

enter image description here

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.