From c95327c5bd9d3b321bd5979f511a7d39397b213c Mon Sep 17 00:00:00 2001 From: Kevin Schneider Date: Thu, 30 Sep 2021 10:04:05 +0200 Subject: [PATCH 1/4] Drasticly improve chart.grid to use TraceID --- src/Plotly.NET/ChartAPI/Chart.fs | 201 ++++++++++++++----- src/Plotly.NET/Playground.fsx | 281 ++++++++------------------- src/Plotly.NET/Traces/Trace.fs | 11 +- src/Plotly.NET/Traces/TraceCarpet.fs | 14 ++ src/Plotly.NET/Traces/TraceID.fs | 11 +- 5 files changed, 258 insertions(+), 260 deletions(-) diff --git a/src/Plotly.NET/ChartAPI/Chart.fs b/src/Plotly.NET/ChartAPI/Chart.fs index 43b5feccb..a34584509 100644 --- a/src/Plotly.NET/ChartAPI/Chart.fs +++ b/src/Plotly.NET/ChartAPI/Chart.fs @@ -83,7 +83,7 @@ type Chart = |> TraceStyle.TraceInfo(?Name=(naming i Name),?ShowLegend=ShowLegend,?LegendGroup=LegendGroup,?Visible=Visible) ) - /// Set the axis anchor id the trace is belonging to + /// Set the axis anchor id the trace is belonging to [] static member withAxisAnchor ( @@ -96,9 +96,11 @@ type Chart = fun (ch:GenericChart) -> ch |> mapTrace (fun trace -> match trace with - | :? Trace2D as trace -> trace |> Trace2DStyle.SetAxisAnchor(?X=idx,?Y=idy) :> Trace + | :? Trace2D as trace -> trace |> Trace2DStyle.SetAxisAnchor(?X=idx,?Y=idy) :> Trace + | :? TraceCarpet as trace when trace.``type`` = "carpet" -> + trace |> TraceCarpetStyle.SetAxisAnchor(?X=idx,?Y=idy) :> Trace | _ -> - printfn "the input was not a 2D cartesian trace. no axis anchors set." + printfn "the input was not a 2D cartesian or carpet trace. no axis anchors set." trace ) [] @@ -110,6 +112,14 @@ type Chart = ) = ch |> Chart.withAxisAnchor(?X=X,?Y=Y) + /// Set the axis anchor id the trace is belonging to + [] + static member withColorAxisAnchor + ( + [] ?Id: int + ) = + fun (ch:GenericChart) -> ch |> mapTrace (TraceStyle.setColorAxisAnchor(?ColorAxisId = Id)) + /// Apply styling to the Marker(s) of the chart as Object. [] static member withMarker(marker:Marker) = @@ -1051,73 +1061,156 @@ type Chart = []?XSide : StyleParam.LayoutGridXSide, []?YSide : StyleParam.LayoutGridYSide ) = - fun (gCharts:#seq) -> - + fun (gCharts:#seq) -> + let pattern = defaultArg Pattern StyleParam.LayoutGridPattern.Independent let hasSharedAxes = pattern = StyleParam.LayoutGridPattern.Coupled // rows x cols coordinate grid let gridCoordinates = - Array.init nRows (fun i -> - Array.init nCols (fun j -> - i+1,j+1 + Array.init nRows (fun rowIndex -> + Array.init nCols (fun colIndex -> + rowIndex+1,colIndex+1 ) ) |> Array.concat - // extract all axes from the plots to later add them with an updated axis anchor - // TODO: currently only gets the default (first) x and y axis. There might be charts with multiple axes which might cause havoc downstream. - // those should either be removed or accounted for - let axes = - gCharts - |> Seq.map (fun gChart -> - gChart - |> GenericChart.getLayout - |> fun l -> - let xAxis = l.TryGetTypedValue "xaxis" |> Option.defaultValue (LinearAxis.init()) - let yAxis = l.TryGetTypedValue "yaxis" |> Option.defaultValue (LinearAxis.init()) - xAxis,yAxis - ) - gCharts |> Seq.zip gridCoordinates - |> Seq.zip axes - |> Seq.mapi (fun i ((xAxis,yAxis), ((y,x), gChart)) -> - - let xAnchor, yAnchor = - if hasSharedAxes then - x, y //set axis anchors according to grid coordinates - else - i+1, i+1 //set individual axis anchors for each subplot - - gChart - |> Chart.withAxisAnchor(xAnchor,yAnchor) // set adapted axis anchors - |> Chart.withXAxis(xAxis,(StyleParam.SubPlotId.XAxis (i+1))) // set previous axis with adapted id (one individual axis for each subplot, wether or not they will be used later) - |> Chart.withYAxis(yAxis,(StyleParam.SubPlotId.YAxis (i+1))) // set previous axis with adapted id (one individual axis for each subplot, wether or not they will be used later) - |> GenericChart.mapLayout (fun l -> - if i > 0 then - // remove default axes from consecutive charts, otherwise they will override the first one - l.Remove("xaxis") |> ignore - l.Remove("yaxis") |> ignore - l - ) + |> Seq.mapi (fun i ((rowIndex, colIndex), gChart) -> + + let layout = gChart |> GenericChart.getLayout + + match TraceID.ofTraces (gChart |> GenericChart.getTraces) with + | TraceID.Multi -> failwith $"the trace for ({rowIndex},{colIndex}) contains multiple different subplot types. this is not supported." + | TraceID.Cartesian2D | TraceID.Carpet -> + + let xAxis = layout.TryGetTypedValue "xaxis" |> Option.defaultValue (LinearAxis.init()) + let yAxis = layout.TryGetTypedValue "yaxis" |> Option.defaultValue (LinearAxis.init()) + + let xAnchor, yAnchor = + if hasSharedAxes then + colIndex, rowIndex //set axis anchors according to grid coordinates + else + i+1, i+1 + + gChart + |> Chart.withAxisAnchor(xAnchor,yAnchor) // set adapted axis anchors + |> Chart.withXAxis(xAxis,(StyleParam.SubPlotId.XAxis (i+1))) // set previous axis with adapted id (one individual axis for each subplot, wether or not they will be used later) + |> Chart.withYAxis(yAxis,(StyleParam.SubPlotId.YAxis (i+1))) // set previous axis with adapted id (one individual axis for each subplot, wether or not they will be used later) + |> GenericChart.mapLayout (fun l -> + if i > 0 then + // remove default axes from consecutive charts, otherwise they will override the first one + l.Remove("xaxis") |> ignore + l.Remove("yaxis") |> ignore + l + ) + | TraceID.Cartesian3D -> + + let scene = + layout.TryGetTypedValue "scene" |> Option.defaultValue (Scene.init()) + |> Scene.style(Domain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1)) + + let sceneAnchor = StyleParam.SubPlotId.Scene (i+1) + + gChart + |> GenericChart.mapTrace(fun t -> + t + :?> Trace3D + |> Trace3DStyle.SetScene sceneAnchor + :> Trace + ) + |> Chart.withScene(scene,sceneAnchor) + | TraceID.Polar -> + + let polar = + layout.TryGetTypedValue "polar" |> Option.defaultValue (Polar.init()) + |> Polar.style(Domain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1)) + + let polarAnchor = StyleParam.SubPlotId.Polar (i+1) + + gChart + |> GenericChart.mapTrace(fun t -> + t + :?> TracePolar + |> TracePolarStyle.SetPolar polarAnchor + :> Trace + ) + |> Chart.withPolar(polar,polarAnchor) + | TraceID.Geo -> + let geo = + layout.TryGetTypedValue "geo" |> Option.defaultValue (Geo.init()) + |> Geo.style(Domain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1)) + + let geoAnchor = StyleParam.SubPlotId.Geo (i+1) + + gChart + |> GenericChart.mapTrace(fun t -> + t + :?> TraceGeo + |> TraceGeoStyle.SetGeo geoAnchor + :> Trace + ) + |> Chart.withGeo(geo,geoAnchor) + | TraceID.Mapbox -> + let mapbox = + layout.TryGetTypedValue "mapbox" |> Option.defaultValue (Mapbox.init()) + |> Mapbox.style(Domain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1)) + + let mapboxAnchor = StyleParam.SubPlotId.Mapbox (i+1) + + gChart + |> GenericChart.mapTrace(fun t -> + t + :?> TraceMapbox + |> TraceMapboxStyle.SetMapbox mapboxAnchor + :> Trace + ) + |> Chart.withMapbox(mapbox,mapboxAnchor) + | TraceID.Domain -> + let newDomain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1) + + gChart + |> GenericChart.mapTrace(fun t -> + t + :?> TraceDomain + |> TraceDomainStyle.SetDomain newDomain + :> Trace + ) + + | TraceID.Ternary -> + + let ternary = + layout.TryGetTypedValue "ternary" |> Option.defaultValue (Ternary.init()) + |> Ternary.style(Domain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1)) + + let ternaryAnchor = StyleParam.SubPlotId.Ternary (i+1) + + gChart + |> GenericChart.mapTrace(fun t -> + t + :?> TraceTernary + |> TraceTernaryStyle.SetTernary ternaryAnchor + :> Trace + ) + |> Chart.withTernary(ternary,ternaryAnchor) ) |> Chart.combine |> Chart.withLayoutGrid ( LayoutGrid.init( - Rows = nRows, - Columns = nCols, - Pattern = pattern, - ?SubPlots = SubPlots, - ?XAxes = XAxes, - ?YAxes = YAxes, - ?RowOrder = RowOrder, - ?XGap = XGap, - ?YGap = YGap, - ?Domain = Domain, - ?XSide = XSide, - ?YSide = YSide + Rows = nRows , + Columns = nCols , + Pattern = pattern , + ?SubPlots = SubPlots , + ?XAxes = XAxes , + ?YAxes = YAxes , + ?RowOrder = RowOrder , + ?XGap = XGap , + ?YGap = YGap , + ?Domain = Domain , + ?XSide = XSide , + ?YSide = YSide ) ) diff --git a/src/Plotly.NET/Playground.fsx b/src/Plotly.NET/Playground.fsx index f9ad1e05f..cf92dd876 100644 --- a/src/Plotly.NET/Playground.fsx +++ b/src/Plotly.NET/Playground.fsx @@ -520,183 +520,7 @@ Chart.Invisible() ) |> Chart.show -type TraceIDLocal = - | Cartesian2D - | Cartesian3D - | Polar - | Geo - | Mapbox - | Ternary - | Carpet - | Domain - | Multi - - static member ofTrace (t:Trace) : TraceIDLocal = - match t with - | :? Trace2D -> TraceIDLocal.Cartesian2D - | :? Trace3D -> TraceIDLocal.Cartesian3D - | :? TracePolar -> TraceIDLocal.Polar - | :? TraceGeo -> TraceIDLocal.Geo - | :? TraceMapbox -> TraceIDLocal.Mapbox - | :? TraceTernary -> TraceIDLocal.Ternary - | :? TraceCarpet -> TraceIDLocal.Carpet - | :? TraceDomain -> TraceIDLocal.Domain - | _ as unknownTraceType -> failwith $"cannot get trace id for type {unknownTraceType.GetType()}" - - static member ofTraces (t:seq) : TraceIDLocal = - let traceIds = t |> Seq.map TraceIDLocal.ofTrace |> Seq.distinct |> Array.ofSeq - match traceIds with - | [|sameTraceID|] -> sameTraceID - | [||] -> TraceIDLocal.Domain - | _ -> TraceIDLocal.Multi - -let gridNew (nRows: int) (nCols: int) = - - fun (gCharts:#seq) -> - - let pattern = StyleParam.LayoutGridPattern.Independent - - let hasSharedAxes = false - - // rows x cols coordinate grid - let gridCoordinates = - Array.init nRows (fun rowIndex -> - Array.init nCols (fun colIndex -> - rowIndex+1,colIndex+1 - ) - ) - |> Array.concat - - gCharts - |> Seq.zip gridCoordinates - |> Seq.mapi (fun i ((rowIndex, colIndex), gChart) -> - - let layout = gChart |> GenericChart.getLayout - - match TraceIDLocal.ofTraces (gChart |> GenericChart.getTraces) with - | TraceIDLocal.Multi -> failwith $"the trace for ({rowIndex},{colIndex}) contains multiple different subplot types. this is not supported." - | TraceIDLocal.Cartesian2D -> - - let xAxis = layout.TryGetTypedValue "xaxis" |> Option.defaultValue (LinearAxis.init()) - let yAxis = layout.TryGetTypedValue "yaxis" |> Option.defaultValue (LinearAxis.init()) - - let xAnchor, yAnchor = - if hasSharedAxes then - colIndex, rowIndex //set axis anchors according to grid coordinates - else - i+1, i+1 - - gChart - |> Chart.withAxisAnchor(xAnchor,yAnchor) // set adapted axis anchors - |> Chart.withXAxis(xAxis,(StyleParam.SubPlotId.XAxis (i+1))) // set previous axis with adapted id (one individual axis for each subplot, wether or not they will be used later) - |> Chart.withYAxis(yAxis,(StyleParam.SubPlotId.YAxis (i+1))) // set previous axis with adapted id (one individual axis for each subplot, wether or not they will be used later) - |> GenericChart.mapLayout (fun l -> - if i > 0 then - // remove default axes from consecutive charts, otherwise they will override the first one - l.Remove("xaxis") |> ignore - l.Remove("yaxis") |> ignore - l - ) - | TraceIDLocal.Cartesian3D -> - - let scene = - layout.TryGetTypedValue "scene" |> Option.defaultValue (Scene.init()) - |> Scene.style(Domain = Domain.init(Row = rowIndex - 1, Column = colIndex - 1)) - - let sceneAnchor = StyleParam.SubPlotId.Scene (i+1) - - gChart - |> GenericChart.mapTrace(fun t -> - t - :?> Trace3D - |> Trace3DStyle.SetScene sceneAnchor - :> Trace - ) - |> Chart.withScene(scene,sceneAnchor) - | TraceIDLocal.Polar -> - - let polar = - layout.TryGetTypedValue "polar" |> Option.defaultValue (Polar.init()) - |> Polar.style(Domain = Domain.init(Row = rowIndex - 1, Column = colIndex - 1)) - - let polarAnchor = StyleParam.SubPlotId.Polar (i+1) - - gChart - |> GenericChart.mapTrace(fun t -> - t - :?> TracePolar - |> TracePolarStyle.SetPolar polarAnchor - :> Trace - ) - |> Chart.withPolar(polar,polarAnchor) - | TraceIDLocal.Geo -> - let geo = - layout.TryGetTypedValue "geo" |> Option.defaultValue (Geo.init()) - |> Geo.style(Domain = Domain.init(Row = rowIndex - 1, Column = colIndex - 1)) - - let geoAnchor = StyleParam.SubPlotId.Geo (i+1) - - gChart - |> GenericChart.mapTrace(fun t -> - t - :?> TraceGeo - |> TraceGeoStyle.SetGeo geoAnchor - :> Trace - ) - |> Chart.withGeo(geo,geoAnchor) - | TraceIDLocal.Mapbox -> - let mapbox = - layout.TryGetTypedValue "mapbox" |> Option.defaultValue (Mapbox.init()) - |> Mapbox.style(Domain = Domain.init(Row = rowIndex - 1, Column = colIndex - 1)) - - let mapboxAnchor = StyleParam.SubPlotId.Mapbox (i+1) - - gChart - |> GenericChart.mapTrace(fun t -> - t - :?> TraceMapbox - |> TraceMapboxStyle.SetMapbox mapboxAnchor - :> Trace - ) - |> Chart.withMapbox(mapbox,mapboxAnchor) - | TraceIDLocal.Domain -> - let newDomain = Domain.init(Row = rowIndex - 1, Column = colIndex - 1) - - gChart - |> GenericChart.mapTrace(fun t -> - t - :?> TraceDomain - |> TraceDomainStyle.SetDomain newDomain - :> Trace - ) - - | TraceIDLocal.Ternary -> - - let ternary = - layout.TryGetTypedValue "ternary" |> Option.defaultValue (Ternary.init()) - |> Ternary.style(Domain = Domain.init(Row = rowIndex - 1, Column = colIndex - 1)) - - let ternaryAnchor = StyleParam.SubPlotId.Ternary (i+1) - - gChart - |> GenericChart.mapTrace(fun t -> - t - :?> TraceTernary - |> TraceTernaryStyle.SetTernary ternaryAnchor - :> Trace - ) - |> Chart.withTernary(ternary,ternaryAnchor) - ) - |> Chart.combine - |> Chart.withLayoutGrid ( - LayoutGrid.init( - Rows = nRows, - Columns = nCols, - Pattern = pattern - ) - ) - -gridNew 3 3 [ +[ let header = ["RowIndex";"A";"simple";"table"] let rows = @@ -725,35 +549,90 @@ gridNew 3 3 [ f x.[j] y.[i] ) ) - - - - Chart.Contour(z, Showscale = false) - Chart.Table(header, rows) - Chart.Point3d([1,3,2]) [ - Chart.Line([1,2; 3,4; 5,6]) - Chart.Spline([1,2; 3,4; 5,7]) + Chart.Contour(z, Showscale = false) + [ + Chart.Carpet( + "contour", + A = [0.; 1.; 2.; 3.; 0.; 1.; 2.; 3.; 0.; 1.; 2.; 3.], + B = [4.; 4.; 4.; 4.; 5.; 5.; 5.; 5.; 6.; 6.; 6.; 6.], + X = [2.; 3.; 4.; 5.; 2.2; 3.1; 4.1; 5.1; 1.5; 2.5; 3.5; 4.5], + Y = [1.; 1.4; 1.6; 1.75; 2.; 2.5; 2.7; 2.75; 3.; 3.5; 3.7; 3.75], + AAxis = LinearAxis.initCarpet( + TickPrefix = "a = ", + Smoothing = 0., + MinorGridCount = 9, + AxisType = StyleParam.AxisType.Linear + ), + BAxis = LinearAxis.initCarpet( + TickPrefix = "b = ", + Smoothing = 0., + MinorGridCount = 9, + AxisType = StyleParam.AxisType.Linear + ) + ) + Chart.ContourCarpet( + "contour", + [1.; 1.96; 2.56; 3.0625; 4.; 5.0625; 1.; 7.5625; 9.; 12.25; 15.21; 14.0625], + A = [0; 1; 2; 3; 0; 1; 2; 3; 0; 1; 2; 3], + B = [4; 4; 4; 4; 5; 5; 5; 5; 6; 6; 6; 6], + ShowScale = false + ) + ] + |> Chart.combine + Chart.Point3d([1,3,2]) ] - |> Chart.combine - Chart.PointPolar([1,2]) - Chart.PointTernary([1,2,3]) - Chart.PointMapbox([1,2]) |> Chart.withMapbox(Mapbox.init(Style = StyleParam.MapboxStyle.OpenStreetMap)) - Chart.Sunburst( - ["A";"B";"C";"D";"E"], - ["";"";"B";"B";""], - Values=[5.;0.;3.;2.;3.], - Text=["At";"Bt";"Ct";"Dt";"Et"] - ) - let x = ["bin1";"bin2";"bin1";"bin2";"bin1";"bin2";"bin1";"bin1";"bin2";"bin1"] - let y' = [2.; 1.5; 5.; 1.5; 2.; 2.5; 2.1; 2.5; 1.5; 1.;2.; 1.5; 5.; 1.5; 3.; 2.5; 2.5; 1.5; 3.5; 1.] + [ + [ + Chart.Line([1,2; 3,4; 5,6]) + Chart.Spline([1,2; 3,4; 5,7]) + ] + |> Chart.combine + Chart.PointPolar([1,2]) + Chart.PointTernary([1,2,3]) + ] + [ + Chart.PointMapbox([1,2]) |> Chart.withMapbox(Mapbox.init(Style = StyleParam.MapboxStyle.OpenStreetMap)) + Chart.Sunburst( + ["A";"B";"C";"D";"E"], + ["";"";"B";"B";""], + Values=[5.;0.;3.;2.;3.], + Text=["At";"Bt";"Ct";"Dt";"Et"] + ) + let x = ["bin1";"bin2";"bin1";"bin2";"bin1";"bin2";"bin1";"bin1";"bin2";"bin1"] + let y' = [2.; 1.5; 5.; 1.5; 2.; 2.5; 2.1; 2.5; 1.5; 1.;2.; 1.5; 5.; 1.5; 3.; 2.5; 2.5; 1.5; 3.5; 1.] - [ - Chart.BoxPlot("y" ,y,Name="bin1",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); - Chart.BoxPlot("y'",y',Name="bin2",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + [ + Chart.BoxPlot("y" ,y,Name="bin1",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + Chart.BoxPlot("y'",y',Name="bin2",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + ] + |> Chart.combine + ] - |> Chart.combine + ] +|> Chart.Grid() |> Chart.withSize(1500,1500) |> Chart.show + +let z0=[|1; 2; 3; 4;4;-3;-1;1;|]; +let z1=[|10; 2; 1; 0;4;3;5;6;|]; +let seq1 = [1 ..4 ] |> Seq.map(fun _-> z0) |> Seq.toArray +let seq2 = [1 ..4 ] |> Seq.map(fun _-> z1) |> Seq.toArray +let marker = Marker.init(Size= 25,Colorscale=StyleParam.Colorscale.Viridis, ShowScale=false); + +let heatmap1= + Chart.Heatmap(data=seq1) + |> Chart.withMarker(marker) + |> Chart.withColorAxisAnchor(1) + +let heatmap2= + Chart.Heatmap(seq2) + |> Chart.withMarker(marker) + |> Chart.withColorAxisAnchor(1) + +[|heatmap1;heatmap2|] +|> Chart.Grid(1,2) +|> Chart.withColorAxis(ColorAxis.init(AutoColorScale=true)) +|> Chart.show \ No newline at end of file diff --git a/src/Plotly.NET/Traces/Trace.fs b/src/Plotly.NET/Traces/Trace.fs index ea51d2de2..eb5fce9db 100644 --- a/src/Plotly.NET/Traces/Trace.fs +++ b/src/Plotly.NET/Traces/Trace.fs @@ -7,7 +7,7 @@ open System open System.Runtime.InteropServices /// Trace type inherits from dynamic object -type Trace (traceTypeName) = +type Trace (traceTypeName:string) = inherit DynamicObj () //interface ITrace with // Implictit ITrace @@ -202,8 +202,13 @@ type TraceStyle() = ) - - + /// Sets the given color axis anchor on a Trace object. (determines which colorscale it uses) + static member setColorAxisAnchor (?ColorAxisId: int) = + let id = ColorAxisId |> Option.map StyleParam.SubPlotId.ColorAxis + (fun (trace:('T :> Trace)) -> + id |> DynObj.setValueOptBy trace "coloraxis" StyleParam.SubPlotId.convert + trace + ) /// Sets the given domain on a Trace object. static member SetDomain diff --git a/src/Plotly.NET/Traces/TraceCarpet.fs b/src/Plotly.NET/Traces/TraceCarpet.fs index 9061910be..71d3a8bdc 100644 --- a/src/Plotly.NET/Traces/TraceCarpet.fs +++ b/src/Plotly.NET/Traces/TraceCarpet.fs @@ -24,6 +24,20 @@ type TraceCarpet(traceTypeName) = type TraceCarpetStyle() = + /// Sets the given axis anchor id(s) on a Trace object. + static member SetAxisAnchor + ( + [] ?X:StyleParam.LinearAxisId, + [] ?Y:StyleParam.LinearAxisId + ) = + (fun (trace:TraceCarpet) -> + + X |> DynObj.setValueOptBy trace "xaxis" StyleParam.LinearAxisId.toString + Y |> DynObj.setValueOptBy trace "yaxis" StyleParam.LinearAxisId.toString + + trace + ) + static member SetCarpet ( [] ?CarpetId:StyleParam.SubPlotId diff --git a/src/Plotly.NET/Traces/TraceID.fs b/src/Plotly.NET/Traces/TraceID.fs index 2c2361719..4a50e052d 100644 --- a/src/Plotly.NET/Traces/TraceID.fs +++ b/src/Plotly.NET/Traces/TraceID.fs @@ -12,7 +12,7 @@ type TraceID = | Cartesian3D | Polar | Geo - | Mapbox + | Mapbox | Ternary | Carpet | Domain @@ -28,4 +28,11 @@ type TraceID = | :? TraceTernary -> TraceID.Ternary | :? TraceCarpet -> TraceID.Carpet | :? TraceDomain -> TraceID.Domain - | _ as unknownTraceType -> failwith $"unknown trace type {unknownTraceType.GetType()}" + | _ as unknownTraceType -> failwith $"cannot get trace id for type {unknownTraceType.GetType()}" + + static member ofTraces (t:seq) : TraceID = + let traceIds = t |> Seq.map TraceID.ofTrace |> Seq.distinct |> Array.ofSeq + match traceIds with + | [|sameTraceID|] -> sameTraceID + | [||] -> TraceID.Domain + | _ -> TraceID.Multi From 1bb361376d5cc36f6c7bd7cc17416ecf476f4af7 Mon Sep 17 00:00:00 2001 From: Kevin Schneider Date: Thu, 30 Sep 2021 10:28:52 +0200 Subject: [PATCH 2/4] Add functions to get TraceIDs of charts --- src/Plotly.NET/ChartAPI/GenericChart.fs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Plotly.NET/ChartAPI/GenericChart.fs b/src/Plotly.NET/ChartAPI/GenericChart.fs index 382fb057f..2e8fa1b83 100644 --- a/src/Plotly.NET/ChartAPI/GenericChart.fs +++ b/src/Plotly.NET/ChartAPI/GenericChart.fs @@ -409,4 +409,14 @@ module GenericChart = | Chart (trace, layout, config, displayOpts) -> Chart (trace, layout, config, f displayOpts) | MultiChart (traces, layout, config, displayOpts) -> MultiChart (traces,layout,config, f displayOpts) - + /// returns a single TraceID (when all traces of the charts are of the same type), or traceID.Multi if the chart contains traces of multiple different types + let getTraceID gChart = + match gChart with + | Chart (trace, _, _, _) -> TraceID.ofTrace trace + | MultiChart (traces, layout, config, displayOpts) -> TraceID.ofTraces traces + + /// returns a list of TraceIDs representing the types of all traces contained in the chart. + let getTraceIDs gChart = + match gChart with + | Chart (trace, _, _, _) -> [TraceID.ofTrace trace] + | MultiChart (traces, _, _, _) -> traces |> List.map TraceID.ofTrace From 21867165e3fc61b8218bd8143514f3dd89379d08 Mon Sep 17 00:00:00 2001 From: Kevin Schneider Date: Thu, 30 Sep 2021 10:28:58 +0200 Subject: [PATCH 3/4] Adapt grid docs --- docs/01_2_multiple-charts.fsx | 62 +++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/docs/01_2_multiple-charts.fsx b/docs/01_2_multiple-charts.fsx index ba640e30b..960c393a4 100644 --- a/docs/01_2_multiple-charts.fsx +++ b/docs/01_2_multiple-charts.fsx @@ -237,3 +237,65 @@ singleStack (***hide***) singleStack |> GenericChart.toChartHTML (***include-it-raw***) + +(** +### Using subplots of different trace types in a grid + +Chart.Grid does some internal magic to make sure that all trace types get their grid cell according to plotly.js's inner logic. + +The only thing you have to consider is, that when you are using nested combined charts, that these have to have the same trace type. + +Otherwise, you can freely combine all charts with Chart.Grid: + +*) +open Plotly.NET.LayoutObjects + +let multipleTraceTypesGrid = + [ + Chart.Point([1,2; 2,3]) + Chart.PointTernary([1,2,3; 2,3,4]) + Chart.Heatmap([[1; 2];[3; 4]], Showscale=false) + Chart.Point3d([1,3,2]) + Chart.PointMapbox([1,2]) |> Chart.withMapbox(Mapbox.init(Style = StyleParam.MapboxStyle.OpenStreetMap)) + [ + // you can use nested combined charts, but they have to have the same trace type (Cartesian2D in this case) + let y = [2.; 1.5; 5.; 1.5; 2.; 2.5; 2.1; 2.5; 1.5; 1.;2.; 1.5; 5.; 1.5; 3.; 2.5; 2.5; 1.5; 3.5; 1.] + Chart.BoxPlot("y" ,y,Name="bin1",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + Chart.BoxPlot("y'",y,Name="bin2",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + ] + |> Chart.combine + ] + |> Chart.Grid(2,3) + |> Chart.withSize(1000,1000) + +(*** condition: ipynb ***) +#if IPYNB +multipleTraceTypesGrid +#endif // IPYNB + +(***hide***) +multipleTraceTypesGrid |> GenericChart.toChartHTML +(***include-it-raw***) + +(** +If you are not sure if traceTypes are compatible, look at the `TraceIDs`: +*) + +let pointType = Chart.Point([1,2]) |> GenericChart.getTraceID +(***include-it***) + +[ + Chart.Point([1,2]) + Chart.PointTernary([1,2,3]) +] +|> Chart.combine +|> GenericChart.getTraceID +(***include-it***) + +[ + Chart.Point([1,2]) + Chart.PointTernary([1,2,3]) +] +|> Chart.combine +|> GenericChart.getTraceIDs +(***include-it***) \ No newline at end of file From 2d8cc81eeb3482efa1b76ce2dc0c18882b273046 Mon Sep 17 00:00:00 2001 From: Kevin Schneider Date: Thu, 30 Sep 2021 10:57:04 +0200 Subject: [PATCH 4/4] Add tests for TraceID and Chart.Grid/SingleStack --- src/Plotly.NET/Playground.fsx | 17 ++++++ .../HtmlCodegen/ChartLayout.fs | 56 ++++++++++++++++++- .../Plotly.NET.Tests/Plotly.NET.Tests.fsproj | 1 + tests/Plotly.NET.Tests/Traces/TraceID.fs | 53 ++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 tests/Plotly.NET.Tests/Traces/TraceID.fs diff --git a/src/Plotly.NET/Playground.fsx b/src/Plotly.NET/Playground.fsx index cf92dd876..dbc2f5a8a 100644 --- a/src/Plotly.NET/Playground.fsx +++ b/src/Plotly.NET/Playground.fsx @@ -635,4 +635,21 @@ let heatmap2= [|heatmap1;heatmap2|] |> Chart.Grid(1,2) |> Chart.withColorAxis(ColorAxis.init(AutoColorScale=true)) +|> Chart.show + +[ + Chart.Point([1,2; 2,3]) + Chart.PointTernary([1,2,3; 2,3,4]) + Chart.Heatmap([[1; 2];[3; 4]], Showscale=false) + Chart.Point3d([1,3,2]) + Chart.PointMapbox([1,2]) |> Chart.withMapbox(Mapbox.init(Style = StyleParam.MapboxStyle.OpenStreetMap)) + [ + let y = [2.; 1.5; 5.; 1.5; 2.; 2.5; 2.1; 2.5; 1.5; 1.;2.; 1.5; 5.; 1.5; 3.; 2.5; 2.5; 1.5; 3.5; 1.] + Chart.BoxPlot("y" ,y,Name="bin1",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + Chart.BoxPlot("y'",y,Name="bin2",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + ] + |> Chart.combine +] +|> Chart.Grid(2,3) +|> Chart.withSize(1000,1000) |> Chart.show \ No newline at end of file diff --git a/tests/Plotly.NET.Tests/HtmlCodegen/ChartLayout.fs b/tests/Plotly.NET.Tests/HtmlCodegen/ChartLayout.fs index 801dfcdce..fc0876ecc 100644 --- a/tests/Plotly.NET.Tests/HtmlCodegen/ChartLayout.fs +++ b/tests/Plotly.NET.Tests/HtmlCodegen/ChartLayout.fs @@ -157,7 +157,44 @@ let singleStackChart = |> Chart.withLayoutGridStyle(XSide=StyleParam.LayoutGridXSide.Bottom,YGap= 0.1) |> Chart.withTitle("Hi i am the new SingleStackChart") |> Chart.withXAxisStyle("im the shared xAxis") - + +let multiTraceGrid = + [ + Chart.Point([1,2; 2,3]) + Chart.PointTernary([1,2,3; 2,3,4]) + Chart.Heatmap([[1; 2];[3; 4]], Showscale=false) + Chart.Point3d([1,3,2]) + Chart.PointMapbox([1,2]) |> Chart.withMapbox(Mapbox.init(Style = StyleParam.MapboxStyle.OpenStreetMap)) + [ + // you can use nested combined charts, but they have to have the same trace type (Cartesian2D in this case) + let y = [2.; 1.5; 5.; 1.5; 2.; 2.5; 2.1; 2.5; 1.5; 1.;2.; 1.5; 5.; 1.5; 3.; 2.5; 2.5; 1.5; 3.5; 1.] + Chart.BoxPlot("y" ,y,Name="bin1",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + Chart.BoxPlot("y'",y,Name="bin2",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + ] + |> Chart.combine + ] + |> Chart.Grid(2,3) + |> Chart.withSize(1000,1000) + + +let multiTraceSingleStack = + [ + Chart.Point([1,2; 2,3]) + Chart.PointTernary([1,2,3; 2,3,4]) + Chart.Heatmap([[1; 2];[3; 4]], Showscale=false) + Chart.Point3d([1,3,2]) + Chart.PointMapbox([1,2]) |> Chart.withMapbox(Mapbox.init(Style = StyleParam.MapboxStyle.OpenStreetMap)) + [ + // you can use nested combined charts, but they have to have the same trace type (Cartesian2D in this case) + let y = [2.; 1.5; 5.; 1.5; 2.; 2.5; 2.1; 2.5; 1.5; 1.;2.; 1.5; 5.; 1.5; 3.; 2.5; 2.5; 1.5; 3.5; 1.] + Chart.BoxPlot("y" ,y,Name="bin1",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + Chart.BoxPlot("y'",y,Name="bin2",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All); + ] + |> Chart.combine + ] + |> Chart.SingleStack() + |> Chart.withSize(1000,1000) + [] let ``Multicharts and subplots`` = testList "ChartLayout.Multicharts and subplots" [ @@ -172,6 +209,14 @@ let ``Multicharts and subplots`` = testCase "Subplot grids layout" ( fun () -> "var layout = {\"xaxis\":{\"title\":{\"text\":\"x1\"}},\"yaxis\":{\"title\":{\"text\":\"y1\"}},\"xaxis2\":{\"title\":{\"text\":\"x2\"}},\"yaxis2\":{\"title\":{\"text\":\"y2\"}},\"xaxis3\":{\"title\":{\"text\":\"x3\"}},\"yaxis3\":{\"title\":{\"text\":\"y3\"}},\"xaxis4\":{\"title\":{\"text\":\"x4\"}},\"yaxis4\":{\"title\":{\"text\":\"y4\"}},\"grid\":{\"rows\":2,\"columns\":2,\"pattern\":\"independent\"}};" |> chartGeneratedContains subPlotChart + ); + testCase "MultiTrace Subplot grid data" ( fun () -> + """var data = [{"type":"scatter","mode":"markers","x":[1,2],"y":[2,3],"marker":{},"xaxis":"x","yaxis":"y"},{"type":"scatterternary","mode":"markers","a":[1,2],"b":[2,3],"c":[3,4],"marker":{},"subplot":"ternary2"},{"type":"heatmap","z":[[1,2],[3,4]],"showscale":false,"xaxis":"x3","yaxis":"y3"},{"type":"scatter3d","mode":"markers","x":[1],"y":[3],"z":[2],"line":{},"marker":{},"scene":"scene4"},{"type":"scattermapbox","mode":"markers","lon":[1],"lat":[2],"line":{},"marker":{},"subplot":"mapbox5"},{"type":"box","y":[2.0,1.5,5.0,1.5,2.0,2.5,2.1,2.5,1.5,1.0,2.0,1.5,5.0,1.5,3.0,2.5,2.5,1.5,3.5,1.0],"x":"y","boxpoints":"all","jitter":0.1,"name":"bin1","marker":{},"xaxis":"x6","yaxis":"y6"},{"type":"box","y":[2.0,1.5,5.0,1.5,2.0,2.5,2.1,2.5,1.5,1.0,2.0,1.5,5.0,1.5,3.0,2.5,2.5,1.5,3.5,1.0],"x":"y'","boxpoints":"all","jitter":0.1,"name":"bin2","marker":{},"xaxis":"x6","yaxis":"y6"}];""" + |> chartGeneratedContains multiTraceGrid + ); + testCase "MultiTrace Subplot grid layout" ( fun () -> + """var layout = {"xaxis":{},"yaxis":{},"ternary2":{"domain":{"row":0,"column":1}},"xaxis3":{},"yaxis3":{},"scene4":{"domain":{"row":1,"column":0}},"mapbox":{"style":"open-street-map","domain":{"row":1,"column":1}},"mapbox5":{"style":"open-street-map","domain":{"row":1,"column":1}},"xaxis6":{},"yaxis6":{},"grid":{"rows":2,"columns":3,"pattern":"independent"},"width":1000,"height":1000};""" + |> chartGeneratedContains multiTraceGrid ); testCase "Single Stack data" ( fun () -> """var data = [{"type":"scatter","mode":"markers","x":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0],"y":[2.0,1.5,5.0,1.5,3.0,2.5,2.5,1.5,3.5,1.0],"marker":{},"xaxis":"x","yaxis":"y"},{"type":"scatter","mode":"lines","x":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0],"y":[2.0,1.5,5.0,1.5,3.0,2.5,2.5,1.5,3.5,1.0],"line":{},"marker":{},"xaxis":"x","yaxis":"y2"},{"type":"scatter","mode":"lines","x":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0],"y":[2.0,1.5,5.0,1.5,3.0,2.5,2.5,1.5,3.5,1.0],"line":{"shape":"spline"},"marker":{},"xaxis":"x","yaxis":"y3"}];""" @@ -180,6 +225,15 @@ let ``Multicharts and subplots`` = testCase "Single Stack layout" ( fun () -> "var layout = {\"yaxis\":{\"title\":{\"text\":\"This title must\"}},\"xaxis\":{\"title\":{\"text\":\"im the shared xAxis\"}},\"xaxis2\":{},\"yaxis2\":{\"title\":{\"text\":\"be set on the\"},\"zeroline\":false},\"xaxis3\":{},\"yaxis3\":{\"title\":{\"text\":\"respective subplots\"},\"zeroline\":false},\"grid\":{\"rows\":3,\"columns\":1,\"pattern\":\"coupled\",\"ygap\":0.1,\"xside\":\"bottom\"},\"title\":{\"text\":\"Hi i am the new SingleStackChart\"}};" |> chartGeneratedContains singleStackChart + ); + + testCase "MultiTrace Single Stack data" ( fun () -> + """var data = [{"type":"scatter","mode":"markers","x":[1,2],"y":[2,3],"marker":{},"xaxis":"x","yaxis":"y"},{"type":"scatterternary","mode":"markers","a":[1,2],"b":[2,3],"c":[3,4],"marker":{},"subplot":"ternary2"},{"type":"heatmap","z":[[1,2],[3,4]],"showscale":false,"xaxis":"x3","yaxis":"y3"},{"type":"scatter3d","mode":"markers","x":[1],"y":[3],"z":[2],"line":{},"marker":{},"scene":"scene4"},{"type":"scattermapbox","mode":"markers","lon":[1],"lat":[2],"line":{},"marker":{},"subplot":"mapbox5"},{"type":"box","y":[2.0,1.5,5.0,1.5,2.0,2.5,2.1,2.5,1.5,1.0,2.0,1.5,5.0,1.5,3.0,2.5,2.5,1.5,3.5,1.0],"x":"y","boxpoints":"all","jitter":0.1,"name":"bin1","marker":{},"xaxis":"x6","yaxis":"y6"},{"type":"box","y":[2.0,1.5,5.0,1.5,2.0,2.5,2.1,2.5,1.5,1.0,2.0,1.5,5.0,1.5,3.0,2.5,2.5,1.5,3.5,1.0],"x":"y'","boxpoints":"all","jitter":0.1,"name":"bin2","marker":{},"xaxis":"x6","yaxis":"y6"}];""" + |> chartGeneratedContains multiTraceSingleStack + ); + testCase "MultiTrace Single Stack layout" ( fun () -> + """var layout = {"xaxis":{},"yaxis":{},"ternary2":{"domain":{"row":1,"column":0}},"xaxis3":{},"yaxis3":{},"scene4":{"domain":{"row":3,"column":0}},"mapbox":{"style":"open-street-map","domain":{"row":4,"column":0}},"mapbox5":{"style":"open-street-map","domain":{"row":4,"column":0}},"xaxis6":{},"yaxis6":{},"grid":{"rows":6,"columns":1,"pattern":"independent"},"width":1000,"height":1000};""" + |> chartGeneratedContains multiTraceSingleStack ); ] diff --git a/tests/Plotly.NET.Tests/Plotly.NET.Tests.fsproj b/tests/Plotly.NET.Tests/Plotly.NET.Tests.fsproj index 60aed8743..2216fd5b0 100644 --- a/tests/Plotly.NET.Tests/Plotly.NET.Tests.fsproj +++ b/tests/Plotly.NET.Tests/Plotly.NET.Tests.fsproj @@ -10,6 +10,7 @@ + diff --git a/tests/Plotly.NET.Tests/Traces/TraceID.fs b/tests/Plotly.NET.Tests/Traces/TraceID.fs new file mode 100644 index 000000000..b8c55f6e7 --- /dev/null +++ b/tests/Plotly.NET.Tests/Traces/TraceID.fs @@ -0,0 +1,53 @@ +module Tests.LayoutObjects.TraceID + +open Expecto +open Plotly.NET +open Plotly.NET.LayoutObjects +open Plotly.NET.TraceObjects +open Plotly.NET.GenericChart + +let allTraceTypesChart = + [ + Chart.Point([]) + Chart.Point3d([]) + Chart.PointPolar([]) + Chart.ChoroplethMap([],[]) + Chart.ChoroplethMapbox([],[],obj) + Chart.PointTernary([1,2,3]) + Chart.PointCarpet([], "") + Chart.Pie([2]) + [ + Chart.PointCarpet([], "") + Chart.Pie([2]) + ] + |> Chart.combine + ] + |> Chart.combine + +[] +let ``TraceID tests`` = + testList "Traces.TraceID" [ + testCase "extract single TraceID from Chart" (fun _ -> + Expect.equal + (allTraceTypesChart |> GenericChart.getTraceID) + TraceID.Multi + "GenericChart.getTraceID did not return the correct TraceID for all traces" + ) + testCase "extract all TraceIDS from Chart" (fun _ -> + Expect.equal + (allTraceTypesChart |> GenericChart.getTraceIDs) + [ + TraceID.Cartesian2D + TraceID.Cartesian3D + TraceID.Polar + TraceID.Geo + TraceID.Mapbox + TraceID.Ternary + TraceID.Carpet + TraceID.Domain + TraceID.Carpet + TraceID.Domain + ] + "GenericChart.getTraceIDs did not return the correct TraceIDs for all traces." + ) + ] \ No newline at end of file