Description
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:
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.
public static GenericChart.GenericChart Scatter<XType,YType,TextType>(...)
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 thePlotly.NET.CSharp.Chart
class. -
The
Chart styling APIs
, which were previouslyGenericChart
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 FSharpOption
s ( 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):
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:
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.