Skip to content

Rewrite C# Compatibility Layer as bindings in C# #285

Closed
@kMutagene

Description

@kMutagene

Plotly.NET CSharp layer

Update 30/05/22: C# high-level Chart API layer design notes

The C# API layer for Plotly.NET aims to remove the friction created by language specific issues when using the F# API from C#.
It uses the following methods to reach that goal:

  • Use 'native' C# optional parameters This gets rid of the Microsoft.FSharp.Core.FSharpOption<T> signatures for optional parameters and significantly reduces visual noise in intellisense
  • Use named generics in the C# methods this makes it easier to handle the generic type annotations necessary for many methods. Here is an example:
     public static GenericChart.GenericChart Scatter<XType,YType,TextType>(...)
    This makes it obvious what the generics are used for. This is especially helpful when the generics must be set for optional parameters, but are not actually used.

It is currently only aimed at wrapping the high-level Chart creation and styling APIs.

  • The Chart creation APIs (e.g. Chart.Scatter, Chart.Histogram) get equivalents as static methods of the Plotly.NET.CSharp.Chart class.

  • The Chart styling APIs, which were previously GenericChart extension methods on the F# layer will be re-implemented as C# extension methods. Fluent interface style APIs are best at home in the C# API and i think once it is 100% ported we might remove it from the F# API alltogether to reduce duplicate code.

The C# API layer functions should always call the respective F# functions and match the parameter count and names 1:1. XML docs should also be the same. Optional parameters on the C# side are used with null as default values and are subsequently wrapped as FSharpOptions ( null -> None, rest -> Some rest) for calling the F# methods. There are helper functions implemented for this.

The work for this is done on the csharp-layer branch

Here is an example of a full binding for the Chart.Point method (see also here):

public static GenericChart.GenericChart Point<XType, YType, TextType>(
            IEnumerable<XType> x,
            IEnumerable<YType> y,
            string? Name = null,
            bool? ShowLegend = null,
            double? Opacity = null,
            IEnumerable<double>? MultiOpacity = null,
            TextType? Text = null,
            IEnumerable<TextType>? MultiText = null,
            StyleParam.TextPosition? TextPosition = null,
            IEnumerable<StyleParam.TextPosition>? MultiTextPosition = null,
            Color? MarkerColor = null,
            StyleParam.Colorscale? MarkerColorScale = null,
            Line? MarkerOutline = null,
            StyleParam.MarkerSymbol? MarkerSymbol = null,
            IEnumerable<StyleParam.MarkerSymbol>? MultiMarkerSymbol = null,
            Marker? Marker = null,
            string? StackGroup = null,
            StyleParam.Orientation? Orientation = null,
            StyleParam.GroupNorm? GroupNorm = null,
            bool? UseWebGL = null,
            bool? UseDefaults = null
        )
            where XType : IConvertible
            where YType : IConvertible
            where TextType : class, IConvertible
        =>
            Plotly.NET.Chart2D.Chart.Point(
                x: x,
                y: y,
                Name: Helpers.ToOption(Name),
                ShowLegend: Helpers.ToOptionV(ShowLegend),
                Opacity: Helpers.ToOptionV(Opacity),
                MultiOpacity: Helpers.ToOption(MultiOpacity),
                Text: Helpers.ToOption(Text),
                MultiText: Helpers.ToOption(MultiText),
                TextPosition: Helpers.ToOption(TextPosition),
                MultiTextPosition: Helpers.ToOption(MultiTextPosition),
                MarkerColor: Helpers.ToOption(MarkerColor),
                MarkerColorScale: Helpers.ToOption(MarkerColorScale),
                MarkerOutline: Helpers.ToOption(MarkerOutline),
                MarkerSymbol: Helpers.ToOption(MarkerSymbol),
                MultiMarkerSymbol: Helpers.ToOption(MultiMarkerSymbol),
                Marker: Helpers.ToOption(Marker),
                StackGroup: Helpers.ToOption(StackGroup),
                Orientation: Helpers.ToOption(Orientation),
                GroupNorm: Helpers.ToOption(GroupNorm),
                UseWebGL: Helpers.ToOptionV(UseWebGL),
                UseDefaults: Helpers.ToOptionV(UseDefaults)
            );

Example usage:

Chart.Point<int,int,string>(
    x: new int [] { 5, 6 },
    y: new int [] { 7, 8 }
)

Intellisense(without annotating the generics):

image

Thanks @WhiteBlackGoose for helping me with this.

Original issue description

This is the issue where progress/decisions about the new C# layer Plotly.NET.CSharp will be tracked.

While big efforts have gone into being as C# friendly as possible in the F# source code (by for example providing static type extensions for a fluent interface style API), some things are still not very usable from C#:

Optional parameter bloat

optional parameters in F# methods are displayed to have type FSharpOption in intellisense (minor issue without impact on compatibility, but makes method signatures quite unreadable)

Explicit type annotation bloat

#IConvertible in F# signatures makes explicit type annotations necessary when calling from C#. The more usage of #IConvertible, the less usable the method gets, as there have to be type annotations for optional parameter that are not used. Here is an example that is very bad:
image

FSharpFunc as return value from F# API

There are Styling APIs that return functions - a completely normal F# thing to do. Those have to be used in C# by using the Invoke method (see #187 for an example where this caused confusion) and are overall not very csharpy.

But there are also some good news. This does not have to be a complete C# rewrite of the library. Things we most likely do not have to touch:

  • Internal logic of methods, Json construction, etc. We just need C# bindings for F# methods/functions
  • the whole StyleParam module is quite usable from C# - here, the added methods for compatibility seem to be sufficient

Additionally, i think we might only provide bindings for the high level API. Whether to add low level bindings as well depends on community needs and amount of people helping to write these bindings.

Guidelines for contributors

I will post guidelines on how to help here once we decided on the way on how to implement this.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions