Plotly.Net - Table Creation in C#

76 Views Asked by At

I've started working with Plotly.NET which is written in F# and has a C# adapter. The API between C# & F# seems a bit inconsistent. What I'm trying to accomplish as given as an example in F# in the Plotly.NET documentation.

Value dependent cell coloring: https://plotly.net/simple-charts/table.html

Here is the code as written in F#.

let table3 =
    let header2 = [ "Identifier"; "T0"; "T1"; "T2"; "T3" ]

    let rowvalues =
        [ [ 10001.; 0.2; 2.0; 4.0; 5.0 ]
          [ 10002.; 2.1; 2.0; 1.8; 2.1 ]
          [ 10003.; 4.5; 3.0; 2.0; 2.5 ]
          [ 10004.; 0.0; 0.1; 0.3; 0.2 ]
          [ 10005.; 1.0; 1.6; 1.8; 2.2 ]
          [ 10006.; 1.0; 0.8; 1.5; 0.7 ]
          [ 10007.; 2.0; 2.0; 2.1; 1.9 ] ]
        |> Seq.sortBy (fun x -> x.[1])

    //map color from value to hex representation
    let mapColor min max value =
        let proportion = (255. * (value - min) / (max - min)) |> int
        Color.fromRGB 255 (255 - proportion) proportion

    //Assign a color to every cell seperately. Matrix must be transposed for correct orientation.
    let cellcolor =
        rowvalues
        |> Seq.map (fun row ->
            row
            |> Seq.mapi (fun index value ->
                if index = 0 then
                    Color.fromString "white"
                else
                    mapColor 0. 5. value))
        |> Seq.transpose
        |> Seq.map Color.fromColors
        |> Color.fromColors

    Chart.Table(headerValues = header2, cellsValues = rowvalues, CellsFillColor = cellcolor)

I've tried to reproduce this in C# a few times. There are Namespace issues with Plotly.NET. If you are using the Plotly.NET.CSharp Nuget Package, you'll find you need to explicitly reference anything in the Plotly.NET namespace, as there are many conflicts.

Plotly.NET.TraceDomain.initTable() is one way of creating a table, and the other is through the .CSharp namespace. Here is some working code that will create a Plotly Table through the CSharp namespace which is higher level code.

public class PlotlyTablePlotter
{
    public static void CreateTable(IEnumerable<IEnumerable<string>> headerData, IEnumerable<IEnumerable<string>> rowData)
    {

        var transposedHeader = Transpose(headerData);

        var header = TableCells.init<IEnumerable<string>, string>(
            Align: FSharpOption<Plotly.NET.StyleParam.HorizontalAlign>.Some(Plotly.NET.StyleParam.HorizontalAlign.Center),
            Font: FSharpOption<Plotly.NET.Font>.Some(Plotly.NET.Font.init(Size: 14)),
            Values: FSharpOption<IEnumerable<IEnumerable<string>>>.Some(transposedHeader)
        );

        var transposedRows = Transpose(rowData);
        var rows = TableCells.init<IEnumerable<string>, string>(
            Fill: FSharpOption<Plotly.NET.TraceObjects.TableFill>.Some(TableFill.init(Color : Plotly.NET.Color.fromString("red"))),
            Values: FSharpOption<IEnumerable<IEnumerable<string>>>.Some(transposedRows)
        );

        var table = Plotly.NET.CSharp.Chart.Table(
             header,
             rows,
             Name: "Test"
        );

        table.Show();
    }

    /// <summary>
    /// Plotly is column major.
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    private static IEnumerable<IEnumerable<string>> Transpose(IEnumerable<IEnumerable<string>> data)
    {
        var columns = new List<IEnumerable<string>>();
        for (int i = 0; i < data.First().Count(); i++)
        {
            var column = new List<string>();
            foreach (var row in data)
            {
                column.Add(row.ElementAt(i));
            }
            columns.Add(column);
        }
        return columns;
    }
}

What I can't do through this interface is set a cells fill color per cell. The C# call to init a row provides the method parameter Fill:

Fill: FSharpOption<Plotly.NET.TraceObjects.TableFill>.Some(TableFill.init(Color : Plotly.NET.Color.fromString("red"))),

This fills the entire table with the same background color. I've looked at Table.style() function that's provided in the CSharp namepace, and I can't figure out how to work that to give what I want either.

I'd appreciate seeing how to do the above demo in C# if possible. And if not possible, I'd like that confirmation.

1

There are 1 best solutions below

1
On BEST ANSWER

There are multiple ways to solve this issue. Just for context, i currently do not work on the CSharp API because i want to auto-generate these bindings based on the F# library eventually, as keeping it in sync with the core F# API manually is a huge pain. When that is done, there should be no need to reference anything else in C#.

First, this might be solvable just via using the Color abstraction correctly. I cannot get your example to work in my notebook, but in this line:

Fill: FSharpOption<Plotly.NET.TraceObjects.TableFill>.Some(TableFill.init(Color : Plotly.NET.Color.fromString("red"))),

try creating a color colection via Color.fromColors. You can learn more about color abstractions here: https://plotly.net/general/working-with-colors.html

That being said, Plotly.NET is designed in such a way that there are always fallback mechanisms in lower level APIs when the high level abstractions are not sufficient. In this case, there would be the following options:

  1. Since there is no high level abstraction in Plotly.NET.CSharp, just use the exact same Method used in the F# example (note that i simply showcase how to set colors, not implement the full logic for value based coloring - this is something you'll also have to implement based on your data):
using Plotly.NET; // no `.CSharp` here!

var colors = 
    new Color [] { 
        Color.fromColors(new Color [] {Color.fromKeyword(ColorKeyword.Red), Color.fromKeyword(ColorKeyword.Red)}),
        Color.fromColors(new Color [] {Color.fromKeyword(ColorKeyword.Yellow), Color.fromKeyword(ColorKeyword.Blue)}),
        Color.fromColors(new Color [] {Color.fromKeyword(ColorKeyword.Purple), Color.fromKeyword(ColorKeyword.DarkGoldenRod)})
    };
    

ChartDomain.Chart.Table<string[],string,string[],string>(
    headerValues: new string[] [] {
        new string [] {"A"},
        new string [] {"B"},
        new string [] {"C"},
    }, 
    cellsValues: 
        new string [] [] {
            new string [] {"1", "2", "3"},
            new string [] {"4", "5", "6"}
        }
    ,
    CellsFillColor: Color.fromColors(colors)
)

enter image description here

  1. Use dynamic object style. This low-level API layer allows you to do absolutely everything that is possible in plotly.js as long as you know how to do it in plotly.js (e.g. know the correct property names, expected color formats, etc.).