Skip to content

Commit 66496cc

Browse files
committed
fix #413:
- remove all existing axes/subplot layouts before adding a single new one each per respective grid cell - add tests
1 parent ab7a9e8 commit 66496cc

File tree

8 files changed

+182
-37
lines changed

8 files changed

+182
-37
lines changed

src/Plotly.NET/ChartAPI/Chart.fs

+93-17
Original file line numberDiff line numberDiff line change
@@ -3072,15 +3072,26 @@ type Chart =
30723072

30733073
ch |> Chart.withConfig config)
30743074

3075-
3076-
30773075
//==============================================================================================================
30783076
//================================= More complicated composite methods =========================================
30793077
//==============================================================================================================
30803078

30813079

30823080
/// <summary>
3083-
/// Creates a subplot grid with the given dimensions (nRows x nCols) for the input charts.
3081+
/// Creates a subplot grid with the given dimensions (nRows x nCols) for the input charts. The default row order is from top to bottom.
3082+
///
3083+
/// For each input chart, a corresponding subplot cell is created in the grid. The following limitations apply to the individual grid cells:
3084+
///
3085+
/// - only one pair of 2D cartesian axes is allowed per cell. If there are multiple x or y axes on an input chart, the first one is used, and the rest is discarded (meaning, it is removed from the combined layout).
3086+
/// if you need multiple axes per grid cell, create a custom grid by manually creating axes with custom domains instead.
3087+
/// The new id of the axes corresponds to the number of the grid cell, e.g. the third grid cell will contain xaxis3 and yaxis3
3088+
///
3089+
/// - For other subplot layouts (Cartesian3D, Polar, Ternary, Geo, Mapbox, Smith), the same rule applies: only one subplot per grid cell, the first one is used, the rest is discarded.
3090+
/// The new id of the subplot layout corresponds to the number of the grid cell, e.g. the third grid cell will contain scene3 etc.
3091+
///
3092+
/// - The Domain of traces that calculate their position by domain only (e.g. Pie traces) are replaced by a domain pointing to the new grid position.
3093+
///
3094+
/// - If SubPlotTitles are provided, they are used as the titles of the individual cells in ascending order. If the number of titles is less than the number of subplots, the remaining subplots are left without a title.
30843095
/// </summary>
30853096
/// <param name ="nRows">The number of rows in the grid. If you provide a 2D `subplots` array or a `yaxes` array, its length is used as the default. But it's also possible to have a different length, if you want to leave a row at the end for non-cartesian subplots.</param>
30863097
/// <param name ="nCols">The number of columns in the grid. If you provide a 2D `subplots` array, the length of its longest row is used as the default. If you give an `xaxes` array, its length is used as the default. But it's also possible to have a different length, if you want to leave a row at the end for non-cartesian subplots.</param>
@@ -3213,6 +3224,13 @@ type Chart =
32133224
let yAxis =
32143225
layout.TryGetTypedValue<LinearAxis> "yaxis" |> Option.defaultValue (LinearAxis.init ())
32153226

3227+
let allXAxes = Layout.getXAxes layout |> Seq.map fst
3228+
let allYAxes = Layout.getYAxes layout |> Seq.map fst
3229+
3230+
// remove all axes from layout. Only cartesian axis in each dimension is supported per grid cell, and leaving anything else on this layout may lead to property name clashes on combine.
3231+
allXAxes |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
3232+
allYAxes |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
3233+
32163234
let xAnchor, yAnchor =
32173235
if hasSharedAxes then
32183236
colIndex, rowIndex //set axis anchors according to grid coordinates
@@ -3223,20 +3241,19 @@ type Chart =
32233241
|> Chart.withAxisAnchor (xAnchor, yAnchor) // set adapted axis anchors
32243242
|> Chart.withXAxis (xAxis, (StyleParam.SubPlotId.XAxis(i + 1))) // set previous axis with adapted id (one individual axis for each subplot, whether or not they will be used later)
32253243
|> Chart.withYAxis (yAxis, (StyleParam.SubPlotId.YAxis(i + 1))) // set previous axis with adapted id (one individual axis for each subplot, whether or not they will be used later)
3226-
|> GenericChart.mapLayout (fun l ->
3227-
if i > 0 then
3228-
// remove default axes from consecutive charts, otherwise they will override the first one
3229-
l.Remove("xaxis") |> ignore
3230-
l.Remove("yaxis") |> ignore
32313244

3232-
l)
32333245
| TraceID.Cartesian3D ->
32343246

32353247
let scene =
32363248
layout.TryGetTypedValue<Scene> "scene"
32373249
|> Option.defaultValue (Scene.init ())
32383250
|> Scene.style (Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1))
32393251

3252+
let allScenes = Layout.getScenes layout |> Seq.map fst
3253+
3254+
// remove all scenes from layout. Only one scene is supported per grid cell, and leaving anything else on this layout may lead to property name clashes on combine.
3255+
allScenes |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
3256+
32403257
let sceneAnchor =
32413258
StyleParam.SubPlotId.Scene(i + 1)
32423259

@@ -3250,6 +3267,11 @@ type Chart =
32503267
|> Option.defaultValue (Polar.init ())
32513268
|> Polar.style (Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1))
32523269

3270+
let allPolars = Layout.getPolars layout |> Seq.map fst
3271+
3272+
// remove all polar subplots from layout. Only one polar subplot is supported per grid cell, and leaving anything else on this layout may lead to property name clashes on combine.
3273+
allPolars |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
3274+
32533275
let polarAnchor =
32543276
StyleParam.SubPlotId.Polar(i + 1)
32553277

@@ -3264,6 +3286,11 @@ type Chart =
32643286
layout.TryGetTypedValue<Smith> "smith"
32653287
|> Option.defaultValue (Smith.init ())
32663288
|> Smith.style (Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1))
3289+
3290+
let allSmiths = Layout.getSmiths layout |> Seq.map fst
3291+
3292+
// remove all smith subplots from layout. Only one smith subplot is supported per grid cell, and leaving anything else on this layout may lead to property name clashes on combine.
3293+
allSmiths |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
32673294

32683295
let polarAnchor =
32693296
StyleParam.SubPlotId.Smith(i + 1)
@@ -3279,12 +3306,18 @@ type Chart =
32793306
|> Option.defaultValue (Geo.init ())
32803307
|> Geo.style (Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1))
32813308

3309+
let allGeos = Layout.getGeos layout |> Seq.map fst
3310+
3311+
// remove all geo subplots from layout. Only one geo subplot is supported per grid cell, and leaving anything else on this layout may lead to property name clashes on combine.
3312+
allGeos |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
3313+
32823314
let geoAnchor =
32833315
StyleParam.SubPlotId.Geo(i + 1)
32843316

32853317
gChart
32863318
|> GenericChart.mapTrace (fun t -> t :?> TraceGeo |> TraceGeoStyle.SetGeo geoAnchor :> Trace)
32873319
|> Chart.withGeo (geo, (i + 1))
3320+
32883321
| TraceID.Mapbox ->
32893322
let mapbox =
32903323
layout.TryGetTypedValue<Mapbox> "mapbox"
@@ -3293,20 +3326,21 @@ type Chart =
32933326
Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1)
32943327
)
32953328

3329+
let allMapboxes = Layout.getMapboxes layout |> Seq.map fst
3330+
3331+
// remove all mapbox subplots from layout. Only one mapbox subplot is supported per grid cell, and leaving anything else on this layout may lead to property name clashes on combine.
3332+
allMapboxes |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
3333+
3334+
let geoAnchor =
3335+
StyleParam.SubPlotId.Geo(i + 1)
3336+
32963337
let mapboxAnchor =
32973338
StyleParam.SubPlotId.Mapbox(i + 1)
32983339

32993340
gChart
33003341
|> GenericChart.mapTrace (fun t ->
33013342
t :?> TraceMapbox |> TraceMapboxStyle.SetMapbox mapboxAnchor :> Trace)
33023343
|> Chart.withMapbox (mapbox, (i + 1))
3303-
| TraceID.Domain ->
3304-
let newDomain =
3305-
LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1)
3306-
3307-
gChart
3308-
|> GenericChart.mapTrace (fun t ->
3309-
t :?> TraceDomain |> TraceDomainStyle.SetDomain newDomain :> Trace)
33103344

33113345
| TraceID.Ternary ->
33123346

@@ -3317,13 +3351,29 @@ type Chart =
33173351
Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1)
33183352
)
33193353

3354+
let allTernaries = Layout.getTernaries layout |> Seq.map fst
3355+
3356+
// remove all ternary subplots from layout. Only one ternary subplot is supported per grid cell, and leaving anything else on this layout may lead to property name clashes on combine.
3357+
allTernaries |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
3358+
33203359
let ternaryAnchor =
33213360
StyleParam.SubPlotId.Ternary(i + 1)
33223361

33233362
gChart
33243363
|> GenericChart.mapTrace (fun t ->
33253364
t :?> TraceTernary |> TraceTernaryStyle.SetTernary ternaryAnchor :> Trace)
3326-
|> Chart.withTernary (ternary, (i + 1)))
3365+
|> Chart.withTernary (ternary, (i + 1))
3366+
3367+
| TraceID.Domain ->
3368+
3369+
// no need to remove existing domains, as only one domain can exist on the original layout. Just replace it.
3370+
let newDomain =
3371+
LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1)
3372+
3373+
gChart
3374+
|> GenericChart.mapTrace (fun t ->
3375+
t :?> TraceDomain |> TraceDomainStyle.SetDomain newDomain :> Trace)
3376+
)
33273377
|> Chart.combine
33283378
|> Chart.withAnnotations(subPlotTitleAnnotations, Append=true)
33293379
|> Chart.withLayoutGrid (
@@ -3349,6 +3399,19 @@ type Chart =
33493399
/// ATTENTION: when the individual rows do not have the same amount of charts, they will be filled with dummy charts TO THE RIGHT.
33503400
///
33513401
/// prevent this behaviour by using Chart.Invisible at the cells that should be empty.
3402+
///
3403+
/// For each input chart, a corresponding subplot cell is created in the grid. The following limitations apply to the individual grid cells:
3404+
///
3405+
/// - only one pair of 2D cartesian axes is allowed per cell. If there are multiple x or y axes on an input chart, the first one is used, and the rest is discarded (meaning, it is removed from the combined layout).
3406+
/// if you need multiple axes per grid cell, create a custom grid by manually creating axes with custom domains instead.
3407+
/// The new id of the axes corresponds to the number of the grid cell, e.g. the third grid cell will contain xaxis3 and yaxis3
3408+
///
3409+
/// - For other subplot layouts (Cartesian3D, Polar, Ternary, Geo, Mapbox, Smith), the same rule applies: only one subplot per grid cell, the first one is used, the rest is discarded.
3410+
/// The new id of the subplot layout corresponds to the number of the grid cell, e.g. the third grid cell will contain scene3 etc.
3411+
///
3412+
/// - The Domain of traces that calculate their position by domain only (e.g. Pie traces) are replaced by a domain pointing to the new grid position.
3413+
///
3414+
/// - If SubPlotTitles are provided, they are used as the titles of the individual cells in ascending order. If the number of titles is less than the number of subplots, the remaining subplots are left without a title.
33523415
/// </summary>
33533416
/// <param name ="SubPlotTitles">A collection of titles for the individual subplots.</param>
33543417
/// <param name ="SubPlotTitleFont">The font of the subplot titles</param>
@@ -3453,6 +3516,19 @@ type Chart =
34533516
)
34543517

34553518
/// Creates a chart stack (a subplot grid with one column) from the input charts.
3519+
///
3520+
/// For each input chart, a corresponding subplot cell is created in the column. The following limitations apply to the individual grid cells:
3521+
///
3522+
/// - only one pair of 2D cartesian axes is allowed per cell. If there are multiple x or y axes on an input chart, the first one is used, and the rest is discarded (meaning, it is removed from the combined layout).
3523+
/// if you need multiple axes per grid cell, create a custom grid by manually creating axes with custom domains instead.
3524+
/// The new id of the axes corresponds to the number of the grid cell, e.g. the third grid cell will contain xaxis3 and yaxis3
3525+
///
3526+
/// - For other subplot layouts (Cartesian3D, Polar, Ternary, Geo, Mapbox, Smith), the same rule applies: only one subplot per grid cell, the first one is used, the rest is discarded.
3527+
/// The new id of the subplot layout corresponds to the number of the grid cell, e.g. the third grid cell will contain scene3 etc.
3528+
///
3529+
/// - The Domain of traces that calculate their position by domain only (e.g. Pie traces) are replaced by a domain pointing to the new grid position.
3530+
///
3531+
/// - If SubPlotTitles are provided, they are used as the titles of the individual cells in ascending order. If the number of titles is less than the number of subplots, the remaining subplots are left without a title.
34563532
/// </summary>
34573533
/// <param name ="SubPlotTitles">A collection of titles for the individual subplots.</param>
34583534
/// <param name ="SubPlotTitleFont">The font of the subplot titles</param>

tests/Common/FSharpTestBase/FSharpTestBase.fsproj

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
</ItemGroup>
1313

1414
<ItemGroup>
15+
<Compile Include="TestCharts\FeatureAdditions\Fix_3d_GridPosition.fs" />
1516
<Compile Include="TestCharts\FeatureAdditions\Grid_SubPlotTitles.fs" />
1617
<Compile Include="TestCharts\FeatureAdditions\Fix_HoverInfo.fs" />
1718
<Compile Include="TestCharts\FeatureAdditions\UpdateMenuButton_Args.fs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module Fix_3d_GridPosition
2+
3+
open Plotly.NET
4+
open Plotly.NET.TraceObjects
5+
open Plotly.NET.LayoutObjects
6+
open DynamicObj
7+
8+
// https://github.com/plotly/Plotly.NET/issues/413
9+
10+
module ``Remove all existing subplots from individual charts on grid creation #413`` =
11+
12+
let ``2x2 grid with only 3D charts and correct scene positioning`` =
13+
[
14+
Chart.Point3D(xyz = [1,3,2], UseDefaults = false)
15+
Chart.Point3D(xyz = [1,3,2], UseDefaults = false)
16+
Chart.Point3D(xyz = [1,3,2], UseDefaults = false)
17+
Chart.Point3D(xyz = [1,3,2], UseDefaults = false)
18+
]
19+
|> Chart.Grid(2,2, SubPlotTitles = ["1";"2";"3";"4"])
20+
21+
let ``2x2 grid chart creation ignores other scenes`` =
22+
[
23+
Chart.Point3D(xyz = [1,3,2], UseDefaults = false)
24+
|> Chart.withScene(Scene.init(), Id = 2)
25+
Chart.Point3D(xyz = [1,3,2], UseDefaults = false)
26+
|> Chart.withScene(Scene.init(), Id = 420)
27+
Chart.Point3D(xyz = [1,3,2], UseDefaults = false)
28+
|> Chart.withScene(Scene.init(), Id = 69)
29+
Chart.Point3D(xyz = [1,3,2], UseDefaults = false)
30+
|> Chart.withScene(Scene.init(), Id = 1337)
31+
]
32+
|> Chart.Grid(2,2, SubPlotTitles = ["1";"2";"3";"4"])

tests/ConsoleApps/FSharpConsole/Program.fs

+15-14
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,22 @@ open Newtonsoft.Json
1111

1212
[<EntryPoint>]
1313
let main args =
14+
let x = [1.; 2.; 3.; 4.; 5.; 6.; 7.; 8.; 9.; 10.; ]
15+
let y = [2.; 1.5; 5.; 1.5; 3.; 2.5; 2.5; 1.5; 3.5; 1.]
1416
[
15-
Chart.Point(xy = [1,2; 2,3], UseDefaults = false)
16-
Chart.PointTernary(abc = [1,2,3; 2,3,4], UseDefaults = false)
17-
Chart.Heatmap(zData = [[1; 2];[3; 4]], ShowScale=false, UseDefaults = false)
18-
Chart.Point3D(xyz = [1,3,2], UseDefaults = false)
19-
Chart.PointMapbox(lonlat = [1,2], UseDefaults = false) |> Chart.withMapbox(Mapbox.init(Style = StyleParam.MapboxStyle.OpenStreetMap))
20-
[
21-
// you can use nested combined charts, but they have to have the same trace type (Cartesian2D in this case)
22-
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.]
23-
Chart.BoxPlot(X = "y" ,Y = y,Name="bin1",Jitter=0.1,BoxPoints=StyleParam.BoxPoints.All, UseDefaults = false);
24-
Chart.BoxPlot(X = "y'",Y = y,Name="bin2",Jitter=0.1,BoxPoints=StyleParam.BoxPoints.All, UseDefaults = false);
25-
]
26-
|> Chart.combine
17+
Chart.Point(x = x, y = y, UseDefaults = false)
18+
|> Chart.withYAxisStyle("This title must")
19+
20+
Chart.Line(x = x, y = y, UseDefaults = false)
21+
|> Chart.withYAxisStyle("be set on the",ZeroLine=false)
22+
23+
Chart.Spline(x = x, y = y, UseDefaults = false)
24+
|> Chart.withYAxisStyle("respective subplots",ZeroLine=false)
2725
]
28-
|> Chart.SingleStack()
29-
|> Chart.withSize(1000,1000)
26+
|> Chart.SingleStack(Pattern = StyleParam.LayoutGridPattern.Coupled)
27+
//move xAxis to bottom and increase spacing between plots by using the withLayoutGridStyle function
28+
|> Chart.withLayoutGridStyle(XSide=StyleParam.LayoutGridXSide.Bottom,YGap= 0.1)
29+
|> Chart.withTitle("Hi i am the new SingleStackChart")
30+
|> Chart.withXAxisStyle("im the shared xAxis")
3031
|> Chart.show
3132
0

tests/CoreTests/CoreTests/CoreTests.fsproj

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<Compile Include="UpstreamFeatures\2.21.fs" />
4747
<Compile Include="UpstreamFeatures\2.20.fs" />
4848
<Compile Include="UpstreamFeatures\2.19.fs" />
49+
<Compile Include="FeatureAdditions\Fix_3d_GridPosition.fs" />
4950
<Compile Include="FeatureAdditions\Grid_SubPlotTitles.fs" />
5051
<Compile Include="FeatureAdditions\Fix_HoverInfo.fs" />
5152
<Compile Include="FeatureAdditions\UpdateMenuButton_Args.fs" />

0 commit comments

Comments
 (0)