Skip to content

Commit 8b4adc5

Browse files
committed
2 parents 05549df + ea2e452 commit 8b4adc5

File tree

10 files changed

+455
-260
lines changed

10 files changed

+455
-260
lines changed

docs/01_2_multiple-charts.fsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,65 @@ singleStack
237237
(***hide***)
238238
singleStack |> GenericChart.toChartHTML
239239
(***include-it-raw***)
240+
241+
(**
242+
### Using subplots of different trace types in a grid
243+
244+
Chart.Grid does some internal magic to make sure that all trace types get their grid cell according to plotly.js's inner logic.
245+
246+
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.
247+
248+
Otherwise, you can freely combine all charts with Chart.Grid:
249+
250+
*)
251+
open Plotly.NET.LayoutObjects
252+
253+
let multipleTraceTypesGrid =
254+
[
255+
Chart.Point([1,2; 2,3])
256+
Chart.PointTernary([1,2,3; 2,3,4])
257+
Chart.Heatmap([[1; 2];[3; 4]], Showscale=false)
258+
Chart.Point3d([1,3,2])
259+
Chart.PointMapbox([1,2]) |> Chart.withMapbox(Mapbox.init(Style = StyleParam.MapboxStyle.OpenStreetMap))
260+
[
261+
// you can use nested combined charts, but they have to have the same trace type (Cartesian2D in this case)
262+
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.]
263+
Chart.BoxPlot("y" ,y,Name="bin1",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All);
264+
Chart.BoxPlot("y'",y,Name="bin2",Jitter=0.1,Boxpoints=StyleParam.Boxpoints.All);
265+
]
266+
|> Chart.combine
267+
]
268+
|> Chart.Grid(2,3)
269+
|> Chart.withSize(1000,1000)
270+
271+
(*** condition: ipynb ***)
272+
#if IPYNB
273+
multipleTraceTypesGrid
274+
#endif // IPYNB
275+
276+
(***hide***)
277+
multipleTraceTypesGrid |> GenericChart.toChartHTML
278+
(***include-it-raw***)
279+
280+
(**
281+
If you are not sure if traceTypes are compatible, look at the `TraceIDs`:
282+
*)
283+
284+
let pointType = Chart.Point([1,2]) |> GenericChart.getTraceID
285+
(***include-it***)
286+
287+
[
288+
Chart.Point([1,2])
289+
Chart.PointTernary([1,2,3])
290+
]
291+
|> Chart.combine
292+
|> GenericChart.getTraceID
293+
(***include-it***)
294+
295+
[
296+
Chart.Point([1,2])
297+
Chart.PointTernary([1,2,3])
298+
]
299+
|> Chart.combine
300+
|> GenericChart.getTraceIDs
301+
(***include-it***)

src/Plotly.NET/ChartAPI/Chart.fs

Lines changed: 147 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ type Chart =
8383
|> TraceStyle.TraceInfo(?Name=(naming i Name),?ShowLegend=ShowLegend,?LegendGroup=LegendGroup,?Visible=Visible)
8484
)
8585

86-
/// Set the axis anchor id the trace is belonging to
86+
/// Set the axis anchor id the trace is belonging to
8787
[<CompiledName("WithAxisAnchor")>]
8888
static member withAxisAnchor
8989
(
@@ -96,9 +96,11 @@ type Chart =
9696
fun (ch:GenericChart) ->
9797
ch |> mapTrace (fun trace ->
9898
match trace with
99-
| :? Trace2D as trace -> trace |> Trace2DStyle.SetAxisAnchor(?X=idx,?Y=idy) :> Trace
99+
| :? Trace2D as trace -> trace |> Trace2DStyle.SetAxisAnchor(?X=idx,?Y=idy) :> Trace
100+
| :? TraceCarpet as trace when trace.``type`` = "carpet" ->
101+
trace |> TraceCarpetStyle.SetAxisAnchor(?X=idx,?Y=idy) :> Trace
100102
| _ ->
101-
printfn "the input was not a 2D cartesian trace. no axis anchors set."
103+
printfn "the input was not a 2D cartesian or carpet trace. no axis anchors set."
102104
trace
103105
)
104106
[<CompiledName("WithAxisAnchor")>]
@@ -110,6 +112,14 @@ type Chart =
110112
) =
111113
ch |> Chart.withAxisAnchor(?X=X,?Y=Y)
112114

115+
/// Set the axis anchor id the trace is belonging to
116+
[<CompiledName("WithColorAxisAnchor")>]
117+
static member withColorAxisAnchor
118+
(
119+
[<Optional;DefaultParameterValue(null)>] ?Id: int
120+
) =
121+
fun (ch:GenericChart) -> ch |> mapTrace (TraceStyle.setColorAxisAnchor(?ColorAxisId = Id))
122+
113123
/// Apply styling to the Marker(s) of the chart as Object.
114124
[<CompiledName("WithMarker")>]
115125
static member withMarker(marker:Marker) =
@@ -1051,73 +1061,156 @@ type Chart =
10511061
[<Optional;DefaultParameterValue(null)>]?XSide : StyleParam.LayoutGridXSide,
10521062
[<Optional;DefaultParameterValue(null)>]?YSide : StyleParam.LayoutGridYSide
10531063
) =
1054-
fun (gCharts:#seq<GenericChart>) ->
1055-
1064+
fun (gCharts:#seq<GenericChart.GenericChart>) ->
1065+
10561066
let pattern = defaultArg Pattern StyleParam.LayoutGridPattern.Independent
10571067

10581068
let hasSharedAxes = pattern = StyleParam.LayoutGridPattern.Coupled
10591069

10601070
// rows x cols coordinate grid
10611071
let gridCoordinates =
1062-
Array.init nRows (fun i ->
1063-
Array.init nCols (fun j ->
1064-
i+1,j+1
1072+
Array.init nRows (fun rowIndex ->
1073+
Array.init nCols (fun colIndex ->
1074+
rowIndex+1,colIndex+1
10651075
)
10661076
)
10671077
|> Array.concat
10681078

1069-
// extract all axes from the plots to later add them with an updated axis anchor
1070-
// TODO: currently only gets the default (first) x and y axis. There might be charts with multiple axes which might cause havoc downstream.
1071-
// those should either be removed or accounted for
1072-
let axes =
1073-
gCharts
1074-
|> Seq.map (fun gChart ->
1075-
gChart
1076-
|> GenericChart.getLayout
1077-
|> fun l ->
1078-
let xAxis = l.TryGetTypedValue<LinearAxis> "xaxis" |> Option.defaultValue (LinearAxis.init())
1079-
let yAxis = l.TryGetTypedValue<LinearAxis> "yaxis" |> Option.defaultValue (LinearAxis.init())
1080-
xAxis,yAxis
1081-
)
1082-
10831079
gCharts
10841080
|> Seq.zip gridCoordinates
1085-
|> Seq.zip axes
1086-
|> Seq.mapi (fun i ((xAxis,yAxis), ((y,x), gChart)) ->
1087-
1088-
let xAnchor, yAnchor =
1089-
if hasSharedAxes then
1090-
x, y //set axis anchors according to grid coordinates
1091-
else
1092-
i+1, i+1 //set individual axis anchors for each subplot
1093-
1094-
gChart
1095-
|> Chart.withAxisAnchor(xAnchor,yAnchor) // set adapted axis anchors
1096-
|> 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)
1097-
|> 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)
1098-
|> GenericChart.mapLayout (fun l ->
1099-
if i > 0 then
1100-
// remove default axes from consecutive charts, otherwise they will override the first one
1101-
l.Remove("xaxis") |> ignore
1102-
l.Remove("yaxis") |> ignore
1103-
l
1104-
)
1081+
|> Seq.mapi (fun i ((rowIndex, colIndex), gChart) ->
1082+
1083+
let layout = gChart |> GenericChart.getLayout
1084+
1085+
match TraceID.ofTraces (gChart |> GenericChart.getTraces) with
1086+
| TraceID.Multi -> failwith $"the trace for ({rowIndex},{colIndex}) contains multiple different subplot types. this is not supported."
1087+
| TraceID.Cartesian2D | TraceID.Carpet ->
1088+
1089+
let xAxis = layout.TryGetTypedValue<LinearAxis> "xaxis" |> Option.defaultValue (LinearAxis.init())
1090+
let yAxis = layout.TryGetTypedValue<LinearAxis> "yaxis" |> Option.defaultValue (LinearAxis.init())
1091+
1092+
let xAnchor, yAnchor =
1093+
if hasSharedAxes then
1094+
colIndex, rowIndex //set axis anchors according to grid coordinates
1095+
else
1096+
i+1, i+1
1097+
1098+
gChart
1099+
|> Chart.withAxisAnchor(xAnchor,yAnchor) // set adapted axis anchors
1100+
|> 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)
1101+
|> 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)
1102+
|> GenericChart.mapLayout (fun l ->
1103+
if i > 0 then
1104+
// remove default axes from consecutive charts, otherwise they will override the first one
1105+
l.Remove("xaxis") |> ignore
1106+
l.Remove("yaxis") |> ignore
1107+
l
1108+
)
1109+
| TraceID.Cartesian3D ->
1110+
1111+
let scene =
1112+
layout.TryGetTypedValue<Scene> "scene" |> Option.defaultValue (Scene.init())
1113+
|> Scene.style(Domain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1))
1114+
1115+
let sceneAnchor = StyleParam.SubPlotId.Scene (i+1)
1116+
1117+
gChart
1118+
|> GenericChart.mapTrace(fun t ->
1119+
t
1120+
:?> Trace3D
1121+
|> Trace3DStyle.SetScene sceneAnchor
1122+
:> Trace
1123+
)
1124+
|> Chart.withScene(scene,sceneAnchor)
1125+
| TraceID.Polar ->
1126+
1127+
let polar =
1128+
layout.TryGetTypedValue<Polar> "polar" |> Option.defaultValue (Polar.init())
1129+
|> Polar.style(Domain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1))
1130+
1131+
let polarAnchor = StyleParam.SubPlotId.Polar (i+1)
1132+
1133+
gChart
1134+
|> GenericChart.mapTrace(fun t ->
1135+
t
1136+
:?> TracePolar
1137+
|> TracePolarStyle.SetPolar polarAnchor
1138+
:> Trace
1139+
)
1140+
|> Chart.withPolar(polar,polarAnchor)
1141+
| TraceID.Geo ->
1142+
let geo =
1143+
layout.TryGetTypedValue<Geo> "geo" |> Option.defaultValue (Geo.init())
1144+
|> Geo.style(Domain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1))
1145+
1146+
let geoAnchor = StyleParam.SubPlotId.Geo (i+1)
1147+
1148+
gChart
1149+
|> GenericChart.mapTrace(fun t ->
1150+
t
1151+
:?> TraceGeo
1152+
|> TraceGeoStyle.SetGeo geoAnchor
1153+
:> Trace
1154+
)
1155+
|> Chart.withGeo(geo,geoAnchor)
1156+
| TraceID.Mapbox ->
1157+
let mapbox =
1158+
layout.TryGetTypedValue<Mapbox> "mapbox" |> Option.defaultValue (Mapbox.init())
1159+
|> Mapbox.style(Domain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1))
1160+
1161+
let mapboxAnchor = StyleParam.SubPlotId.Mapbox (i+1)
1162+
1163+
gChart
1164+
|> GenericChart.mapTrace(fun t ->
1165+
t
1166+
:?> TraceMapbox
1167+
|> TraceMapboxStyle.SetMapbox mapboxAnchor
1168+
:> Trace
1169+
)
1170+
|> Chart.withMapbox(mapbox,mapboxAnchor)
1171+
| TraceID.Domain ->
1172+
let newDomain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1)
1173+
1174+
gChart
1175+
|> GenericChart.mapTrace(fun t ->
1176+
t
1177+
:?> TraceDomain
1178+
|> TraceDomainStyle.SetDomain newDomain
1179+
:> Trace
1180+
)
1181+
1182+
| TraceID.Ternary ->
1183+
1184+
let ternary =
1185+
layout.TryGetTypedValue<Ternary> "ternary" |> Option.defaultValue (Ternary.init())
1186+
|> Ternary.style(Domain = LayoutObjects.Domain.init(Row = rowIndex - 1, Column = colIndex - 1))
1187+
1188+
let ternaryAnchor = StyleParam.SubPlotId.Ternary (i+1)
1189+
1190+
gChart
1191+
|> GenericChart.mapTrace(fun t ->
1192+
t
1193+
:?> TraceTernary
1194+
|> TraceTernaryStyle.SetTernary ternaryAnchor
1195+
:> Trace
1196+
)
1197+
|> Chart.withTernary(ternary,ternaryAnchor)
11051198
)
11061199
|> Chart.combine
11071200
|> Chart.withLayoutGrid (
11081201
LayoutGrid.init(
1109-
Rows = nRows,
1110-
Columns = nCols,
1111-
Pattern = pattern,
1112-
?SubPlots = SubPlots,
1113-
?XAxes = XAxes,
1114-
?YAxes = YAxes,
1115-
?RowOrder = RowOrder,
1116-
?XGap = XGap,
1117-
?YGap = YGap,
1118-
?Domain = Domain,
1119-
?XSide = XSide,
1120-
?YSide = YSide
1202+
Rows = nRows ,
1203+
Columns = nCols ,
1204+
Pattern = pattern ,
1205+
?SubPlots = SubPlots ,
1206+
?XAxes = XAxes ,
1207+
?YAxes = YAxes ,
1208+
?RowOrder = RowOrder ,
1209+
?XGap = XGap ,
1210+
?YGap = YGap ,
1211+
?Domain = Domain ,
1212+
?XSide = XSide ,
1213+
?YSide = YSide
11211214
)
11221215
)
11231216

src/Plotly.NET/ChartAPI/GenericChart.fs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,4 +409,14 @@ module GenericChart =
409409
| Chart (trace, layout, config, displayOpts) -> Chart (trace, layout, config, f displayOpts)
410410
| MultiChart (traces, layout, config, displayOpts) -> MultiChart (traces,layout,config, f displayOpts)
411411

412-
412+
/// 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
413+
let getTraceID gChart =
414+
match gChart with
415+
| Chart (trace, _, _, _) -> TraceID.ofTrace trace
416+
| MultiChart (traces, layout, config, displayOpts) -> TraceID.ofTraces traces
417+
418+
/// returns a list of TraceIDs representing the types of all traces contained in the chart.
419+
let getTraceIDs gChart =
420+
match gChart with
421+
| Chart (trace, _, _, _) -> [TraceID.ofTrace trace]
422+
| MultiChart (traces, _, _, _) -> traces |> List.map TraceID.ofTrace

0 commit comments

Comments
 (0)