Rectangle Annotation With Rounded Corner in Oxyplot

It has been few months since I started playing around with Oxyplot, and it continues to impresses me. Having said that, there are times when certain challenges are thrown showing light on certain limitation of the tool. One of such limitation and a way to overcome is being discussed in this blog post.

Oxyplot supports a wide range of Annotations including Rectangle Annotation, Text Annotation, Image Annotation, Ellipse Annotation and Line Annotation among others. These by itself are extremely powerful and easy to use. However, recently I came across a business need where I would need to draw a Rectangle Annotation with Rounded Corner.

The Oxyplot library doesn’t allow us an in-build mechanism to do it. However, the good part is, that is not end of the road. We can create our own custom annotation. Before we hit code, it would be a good idea to discuss how we plan to create a RoundedCornerRectangleAnnotation.

Rounded Rectangle OxyPlot

The crux of the idea is to generate the rounded effect by drawing a set of two rectangle and 4 circle/ellipse. The Radius of the circles would be the intended corner radius of the final rectangle.

Okay, that’s enough for idea, let’s go ahead and write some code.

Part of Custom Annotation Class

 private void DrawRoundedRectangle(IRenderContext rc)
        {
            var xMin = double.IsNaN(this.MinimumX) || this.MinimumX.Equals(double.MinValue)
                            ? this.ClipByXAxis
                                ? this.XAxis.ActualMinimum
                                : this.XAxis.InverseTransform(this.PlotModel.PlotArea.Left)
                            : this.MinimumX;
            var xMax = double.IsNaN(this.MaximumX) || this.MaximumX.Equals(double.MaxValue)
                            ? this.ClipByXAxis
                                ? this.XAxis.ActualMaximum
                                : this.XAxis.InverseTransform(this.PlotModel.PlotArea.Right)
                            : this.MaximumX;
            var yMin = double.IsNaN(this.MinimumY) || this.MinimumY.Equals(double.MinValue)
                            ? this.ClipByYAxis
                                ? this.YAxis.ActualMinimum
                                : this.YAxis.InverseTransform(this.PlotModel.PlotArea.Bottom)
                            : this.MinimumY;
            var yMax = double.IsNaN(this.MaximumY) || this.MaximumY.Equals(double.MaxValue)
                            ? this.ClipByYAxis
                                ? this.YAxis.ActualMaximum
                                : this.YAxis.InverseTransform(this.PlotModel.PlotArea.Top)
                            : this.MaximumY;
            var xCornerRadius = (CornerRadius / (XAxis.Maximum - XAxis.Minimum)) * 100;
            var yCornerRadius = (CornerRadius / (YAxis.Maximum - YAxis.Minimum)) * 100;
            this.screenRectangleWithClippedXAxis = new OxyRect(this.Transform(xMin + xCornerRadius, yMin), this.Transform(xMax - xCornerRadius, yMax));
            this.screenRectangleWithClippedYAxis = new OxyRect(this.Transform(xMin, yMin + yCornerRadius), this.Transform(xMax, yMax - yCornerRadius));
            this.screenEllipseLeftBottom = new OxyRect(this.Transform(xMin, yMin), this.Transform(xMin + 2* xCornerRadius, yMin + 2* yCornerRadius));
            this.screenEllipseLeftTop = new OxyRect(this.Transform(xMin, yMax), this.Transform(xMin + 2 * xCornerRadius, yMax - 2 * yCornerRadius));
            this.screenEllipseRightBottom = new OxyRect(this.Transform(xMax, yMin), this.Transform(xMax - 2 * xCornerRadius, yMin + 2 * yCornerRadius));
            this.screenEllipseRightTop = new OxyRect(this.Transform(xMax, yMax), this.Transform(xMax - 2 * xCornerRadius, yMax - 2 * yCornerRadius));
            // clip to the area defined by the axes
            var clippingRectangle = OxyRect.Create(
                this.ClipByXAxis ? this.XAxis.ScreenMin.X : this.PlotModel.PlotArea.Left,
                this.ClipByYAxis ? this.YAxis.ScreenMin.Y : this.PlotModel.PlotArea.Top,
                this.ClipByXAxis ? this.XAxis.ScreenMax.X : this.PlotModel.PlotArea.Right,
                this.ClipByYAxis ? this.YAxis.ScreenMax.Y : this.PlotModel.PlotArea.Bottom);
            rc.DrawClippedRectangle(clippingRectangle,this.screenRectangleWithClippedXAxis,
                                            this.GetSelectableFillColor(this.Fill),
                                            this.GetSelectableColor(this.Stroke),
                                            this.StrokeThickness);
            rc.DrawClippedRectangle(clippingRectangle, this.screenRectangleWithClippedYAxis,
                                            this.GetSelectableFillColor(this.Fill),
                                            this.GetSelectableColor(this.Stroke),
                                            this.StrokeThickness);
            rc.DrawClippedEllipse(clippingRectangle, screenEllipseLeftBottom,
                                            this.GetSelectableFillColor(this.Fill),
                                            this.GetSelectableColor(this.Stroke),
                                            this.StrokeThickness);
            rc.DrawClippedEllipse(clippingRectangle, screenEllipseLeftTop,
                                this.GetSelectableFillColor(this.Fill),
                                this.GetSelectableColor(this.Stroke),
                                this.StrokeThickness);
            rc.DrawClippedEllipse(clippingRectangle, screenEllipseRightBottom,
                                this.GetSelectableFillColor(this.Fill),
                                this.GetSelectableColor(this.Stroke),
                                this.StrokeThickness);
            rc.DrawClippedEllipse(clippingRectangle, screenEllipseRightTop,
                                this.GetSelectableFillColor(this.Fill),
                                this.GetSelectableColor(this.Stroke),
                                this.StrokeThickness);
            if (!string.IsNullOrEmpty(this.Text))
            {
                var textPosition = this.GetActualTextPosition(() => this.screenRectangle.Center);
                rc.DrawClippedText(
                    clippingRectangle,
                    textPosition,
                    this.Text,
                    this.ActualTextColor,
                    this.ActualFont,
                    this.ActualFontSize,
                    this.ActualFontWeight,
                    this.TextRotation,
                    HorizontalAlignment.Center,
                    VerticalAlignment.Middle);
            }
        }

 

You can find the complete class code in Gist Link. The complete source code, including the WPF Wrapper can be found in the forked implementation at my GitHub.

PS:The implementation, of course has its own flaws. You cannot set Stroke for the Rounded Rectangle, hopefully, I would be able to workaround this issue soon.

IEnumerable.Shuffle Implementation

Following code is an implementation of Shuffle method on IEnumerable based on the Durstenfeld modification of Fisher Yates Shuffle algorithm, a simple and efficient method.

public static IEnumerable<TSource> Shuffle<TSource>(this IEnumerable<TSource> dataArray)
{
    var dataArrayList = dataArray.ToList();
    var returnValue = Enumerable.Empty<TSource>();
    for (int index = dataArrayList.Count()-1; index > 0; index--)
    {
        var randomKey = _randomGenerator.Next(1, index);
        var temp = dataArrayList[randomKey];
        dataArrayList[randomKey] = dataArrayList[dataArray.Count() - 1];
        dataArrayList[dataArray.Count() - 1] = temp;
    }
    return dataArrayList;
}

More information on the algorithm can be found in Wiki.

The complete code can be found in Github

VS Extension & HttpClient : FileNotFoundException

Recently I was working on a Visual Studio Extension which required HttpClient when I ran into the following exception. Frustratingly enough, I wasn’t able to resolve it myself, despite playing around with the versions and using Binding Redirect.

System.IO.FileNotFoundException: ‘Could not load file or assembly ‘System.Net.Http,Version=4.2.0.0, Culture=neutral, ….. The system cannot find the file specified.

The Binding Redirect didn’t quite as it is not possible to use the redirects in VS Extensions. However, the solution turned out to be choosing an alternative for HttpClient as pointed in SOF.

Instead of HttpClient, I had to depend on something which is more native to.Net, like HttpWebRequest. Hope this post is useful to anyone who stumbles on similar issues.

Linq Recipes : IsIncreasing/IsDecreasing & IsAlternating

LINQ, ever since its introduction has turned out to be one of the favorite tools for .Net Developers. The post focus on couple of quick recipes for common scenarios.

Scenario 001
You have a collection of objects and you would like verify if the collection is sorted in increasing/decreasing order.

Recipe

We could always sort the collection and compare with the original version, however, if you really want to stick to a LINQ based solution, following extension methods is one approach.

public static bool IsIncreasing<TSource>(this IEnumerable<TSource> data) 
{ 
   return data.Zip(data.Skip(1),(first, second) => Comparer.Default.Compare(first,second) < 0).All(b => b);
}


public static bool IsDecreasing<TSource>(this IEnumerable<TSource> data)
{
   return data.Zip(data.Skip(1),(first, second) => Comparer.Default.Compare(first,second) > 0).All(b => b);
}

Demo

void Main()
{
       var itemSet1 = new[] {1,3,4,5,6};
       var itemSet2 = new[] {5,4,3,2,1};
       Console.WriteLine($"Is {nameof(itemSet1)} increasing ? {itemSet1.IsIncreasing()}" );
       Console.WriteLine($"Is {nameof(itemSet1)} decreasing ? {itemSet1.IsDecreasing()}" );
       Console.WriteLine($"Is {nameof(itemSet2)} increasing ? {itemSet1.IsIncreasing()}" );
       Console.WriteLine($"Is {nameof(itemSet2)} decreasing ? {itemSet1.IsDecreasing()}" );
}

Output

Is itemSet1 increasing ? True
Is itemSet1 decreasing ? False
Is itemSet2 increasing ? True
Is itemSet2 decreasing ? False

Scenario 002:

Consider you have a Collection ‘A’. You want to ensure that each element in the series vary in an alternating way, that is, for each element in A, A[i] < A[i+1] > A[i+2] or A[i] > A[i+1] < A[+2]. For example, for the collection {1,3,2,4,3,5} follows the pattern.

Recipe
Once again we will write the solution as an extension method. Well, that’s easier to ‘reuse’ right.

public static bool IsAlternating<TSource>(this IEnumerable<TSource> dataList)
{
       return VerifyIfAlternating(dataList.ToList(),true) || VerifyIfAlternating(dataList.ToList(),false);
}

private static bool VerifyIfAlternating<TSource>(IList<TSource> data,bool toggle)
{
return data.Zip(data.Skip(1), (first, second) =>
                                            {
                                                toggle = !toggle;
                                                return Comparer.Default.Compare(first,second) > 0 == toggle;
                                            })
                                            .All(b=>b);
                                                              
}

Demo

void Main()
{
       var itemSet3 = new[] {1,4,1,4,1};
       var itemSet4 = new[] {2,4,3,2,1};

       Console.WriteLine($"Is {nameof(itemSet3)} alternating ? {itemSet3.IsAlternating()}" );
       Console.WriteLine($"Is {nameof(itemSet4)} alternating ? {itemSet4.IsAlternating()}" );

}

Output

Is itemSet3 alternating ? True
Is itemSet4 alternating ? False

That’s it for now. This is one series I would like to continue.