diff --git a/src/Plotly.NET.CSharp/ChartAPI/Chart.cs b/src/Plotly.NET.CSharp/ChartAPI/Chart.cs
index 0ce232c5..13db2dac 100644
--- a/src/Plotly.NET.CSharp/ChartAPI/Chart.cs
+++ b/src/Plotly.NET.CSharp/ChartAPI/Chart.cs
@@ -23,6 +23,9 @@ public static partial class Chart
/// The charts to display on the grid.
/// 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.
/// 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.
+ /// A collection of titles for the individual subplots.
+ /// The font of the subplot titles
+ /// A vertical offset applied to each subplot title, moving it upwards if positive and vice versa
/// Used for freeform grids, where some axes may be shared across subplots but others are not. Each entry should be a cartesian subplot id, like "xy" or "x3y2", or "" to leave that cell empty. You may reuse x axes within the same column, and y axes within the same row. Non-cartesian subplots and traces that support `domain` can place themselves in this grid separately using the `gridcell` attribute.
/// Used with `yaxes` when the x and y axes are shared across columns and rows. Each entry should be an y axis id like "y", "y2", etc., or "" to not put a y axis in that row. Entries other than "" must be unique. Ignored if `subplots` is present. If missing but `xaxes` is present, will generate consecutive IDs.
/// Used with `yaxes` when the x and y axes are shared across columns and rows. Each entry should be an x axis id like "x", "x2", etc., or "" to not put an x axis in that column. Entries other than "" must be unique. Ignored if `subplots` is present. If missing but `yaxes` is present, will generate consecutive IDs.
@@ -37,6 +40,9 @@ public static GenericChart Grid(
IEnumerable gCharts,
int nRows,
int nCols,
+ Optional> SubPlotTitles = default,
+ Optional SubPlotTitleFont = default,
+ Optional SubPlotTitleOffset = default,
Optional[][]> SubPlots = default,
Optional XAxes = default,
Optional YAxes = default,
@@ -48,9 +54,12 @@ public static GenericChart Grid(
Optional XSide = default,
Optional YSide = default
) =>
- Plotly.NET.Chart.Grid>(
+ Plotly.NET.Chart.Grid,IEnumerable>(
nRows: nRows,
nCols: nCols,
+ SubPlotTitles: SubPlotTitles.ToOption(),
+ SubPlotTitleFont: SubPlotTitleFont.ToOption(),
+ SubPlotTitleOffset: SubPlotTitleOffset.ToOption(),
SubPlots: SubPlots.ToOption(),
XAxes: XAxes.ToOption(),
YAxes: YAxes.ToOption(),
diff --git a/src/Plotly.NET/ChartAPI/Chart.fs b/src/Plotly.NET/ChartAPI/Chart.fs
index 5d302a7a..151c896e 100644
--- a/src/Plotly.NET/ChartAPI/Chart.fs
+++ b/src/Plotly.NET/ChartAPI/Chart.fs
@@ -1128,7 +1128,7 @@ type Chart =
match sceneAxisId with
| StyleParam.SubPlotId.XAxis _ -> scene |> Scene.getXAxis
| StyleParam.SubPlotId.YAxis _ -> scene |> Scene.getYAxis
- | StyleParam.SubPlotId.ZAxis _ -> scene |> Scene.getZAxis
+ | StyleParam.SubPlotId.ZAxis -> scene |> Scene.getZAxis
| _ -> failwith "invalid scene axis id"
let updatedAxis =
@@ -1140,7 +1140,7 @@ type Chart =
match sceneAxisId with
| StyleParam.SubPlotId.XAxis _ -> s |> Scene.setXAxis axis
| StyleParam.SubPlotId.YAxis _ -> s |> Scene.setYAxis axis
- | StyleParam.SubPlotId.ZAxis _ -> s |> Scene.setZAxis axis
+ | StyleParam.SubPlotId.ZAxis -> s |> Scene.setZAxis axis
| _ -> failwith "invalid scene axis id"
layout |> Layout.updateSceneById (id, updatedScene)
@@ -1153,7 +1153,7 @@ type Chart =
match sceneAxisId with
| StyleParam.SubPlotId.XAxis _ -> s |> Scene.setXAxis axis
| StyleParam.SubPlotId.YAxis _ -> s |> Scene.setYAxis axis
- | StyleParam.SubPlotId.ZAxis _ -> s |> Scene.setZAxis axis
+ | StyleParam.SubPlotId.ZAxis -> s |> Scene.setZAxis axis
| _ -> failwith "invalid scene axis id"
layout |> Layout.updateSceneById (id, updatedScene))
@@ -3072,18 +3072,32 @@ type Chart =
ch |> Chart.withConfig config)
-
-
//==============================================================================================================
//================================= More complicated composite methods =========================================
//==============================================================================================================
///
- /// Creates a subplot grid with the given dimensions (nRows x nCols) for the input charts.
+ /// Creates a subplot grid with the given dimensions (nRows x nCols) for the input charts. The default row order is from top to bottom.
+ ///
+ /// For each input chart, a corresponding subplot cell is created in the grid. The following limitations apply to the individual grid cells:
+ ///
+ /// - 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).
+ /// if you need multiple axes per grid cell, create a custom grid by manually creating axes with custom domains instead.
+ /// 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
+ ///
+ /// - 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.
+ /// 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.
+ ///
+ /// - 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.
+ ///
+ /// - 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.
///
/// 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.
/// 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.
+ /// A collection of titles for the individual subplots.
+ /// The font of the subplot titles
+ /// A vertical offset applied to each subplot title, moving it upwards if positive and vice versa
/// Used for freeform grids, where some axes may be shared across subplots but others are not. Each entry should be a cartesian subplot id, like "xy" or "x3y2", or "" to leave that cell empty. You may reuse x axes within the same column, and y axes within the same row. Non-cartesian subplots and traces that support `domain` can place themselves in this grid separately using the `gridcell` attribute.
/// Used with `yaxes` when the x and y axes are shared across columns and rows. Each entry should be an y axis id like "y", "y2", etc., or "" to not put a y axis in that row. Entries other than "" must be unique. Ignored if `subplots` is present. If missing but `xaxes` is present, will generate consecutive IDs.
/// Used with `yaxes` when the x and y axes are shared across columns and rows. Each entry should be an x axis id like "x", "x2", etc., or "" to not put an x axis in that column. Entries other than "" must be unique. Ignored if `subplots` is present. If missing but `yaxes` is present, will generate consecutive IDs.
@@ -3099,6 +3113,9 @@ type Chart =
(
nRows: int,
nCols: int,
+ [] ?SubPlotTitles: #seq,
+ [] ?SubPlotTitleFont: Font,
+ [] ?SubPlotTitleOffset: float,
[] ?SubPlots: (StyleParam.LinearAxisId * StyleParam.LinearAxisId)[][],
[] ?XAxes: StyleParam.LinearAxisId[],
[] ?YAxes: StyleParam.LinearAxisId[],
@@ -3112,12 +3129,76 @@ type Chart =
) =
fun (gCharts: #seq) ->
+ // calculates the grid cell dimensions (in fractions of paper size), that is, the start and end points of each cell in a row or column
+ let getGridCellDimensions (gridDimensionStart: float) (gridDimensionEnd: float) (gap: float) (length: int) (reversed: bool) =
+ // adapted from grid cell layout logic directly in plotly.js source code: https://github.com/plotly/plotly.js/blob/5d6d45758f485ca309691bc7f33e799ef80f2cd5/src/components/grid/index.js#L224-L238
+
+ let step = (gridDimensionEnd - gridDimensionStart) / (float length - gap)
+ let cellDomain = step * (1. - gap)
+
+ Array.init length (fun i ->
+ let cellStart = gridDimensionStart + (step * float i)
+ (cellStart, cellStart + cellDomain)
+ )
+ |> fun p -> if reversed then p else Array.rev p
+
+ // calculates the positions of the subplot titles
+ // titles are placed in the middle of the top edge of each cell in a layout grid as annotations with paper copordinates.
+ let calculateSubplotTitlePositions (gridDimensionStart: float) (gridDimensionEnd: float) (xgap: float) (ygap: float) (nrows: int) (ncols: int) (reversed:bool) =
+
+ let subPlotTitleOffset = defaultArg SubPlotTitleOffset 0.
+
+ let xDomains = getGridCellDimensions gridDimensionStart gridDimensionEnd xgap ncols true
+ let yDomains = getGridCellDimensions gridDimensionStart gridDimensionEnd ygap nrows reversed
+
+ Array.init nrows (fun r ->
+ Array.init ncols (fun c ->
+ let xStart = fst xDomains.[c]
+ let xEnd = snd xDomains.[c]
+ let yEnd = snd yDomains.[r]
+ (r,c), ((xStart + xEnd) / 2., yEnd + subPlotTitleOffset)
+ )
+ )
+ |> Array.concat
+
let pattern =
defaultArg Pattern StyleParam.LayoutGridPattern.Independent
+ let rowOrder = defaultArg RowOrder StyleParam.LayoutGridRowOrder.TopToBottom
+
+ let xGap = defaultArg XGap (if pattern = StyleParam.LayoutGridPattern.Coupled then 0.1 else 0.2)
+ let yGap = defaultArg YGap (if pattern = StyleParam.LayoutGridPattern.Coupled then 0.1 else 0.3)
+
+
let hasSharedAxes =
pattern = StyleParam.LayoutGridPattern.Coupled
+ let subPlotTitleAnnotations =
+ match SubPlotTitles with
+ | Some titles ->
+
+ let reversed = rowOrder = StyleParam.LayoutGridRowOrder.BottomToTop
+
+ let positions =
+ calculateSubplotTitlePositions 0. 1. xGap yGap nRows nCols reversed
+
+ titles
+ |> Seq.zip positions[0 .. (Seq.length titles) - 1]
+ |> Seq.map (fun (((rowIndex, colIndex), (x, y)), title) ->
+ Annotation.init(
+ X = x,
+ XRef = "paper",
+ XAnchor = StyleParam.XAnchorPosition.Center,
+ Y = y,
+ YRef = "paper",
+ YAnchor = StyleParam.YAnchorPosition.Bottom,
+ Text = title,
+ ShowArrow = false,
+ ?Font = SubPlotTitleFont
+ )
+ )
+ | None -> [||]
+
// rows x cols coordinate grid
let gridCoordinates =
Array.init nRows (fun rowIndex -> Array.init nCols (fun colIndex -> rowIndex + 1, colIndex + 1))
@@ -3143,6 +3224,13 @@ type Chart =
let yAxis =
layout.TryGetTypedValue "yaxis" |> Option.defaultValue (LinearAxis.init ())
+ let allXAxes = Layout.getXAxes layout |> Seq.map fst
+ let allYAxes = Layout.getYAxes layout |> Seq.map fst
+
+ // 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.
+ allXAxes |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
+ allYAxes |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
+
let xAnchor, yAnchor =
if hasSharedAxes then
colIndex, rowIndex //set axis anchors according to grid coordinates
@@ -3153,13 +3241,7 @@ type Chart =
|> 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, whether 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, whether 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 =
@@ -3167,6 +3249,11 @@ type Chart =
|> Option.defaultValue (Scene.init ())
|> Scene.style (Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1))
+ let allScenes = Layout.getScenes layout |> Seq.map fst
+
+ // 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.
+ allScenes |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
+
let sceneAnchor =
StyleParam.SubPlotId.Scene(i + 1)
@@ -3180,6 +3267,11 @@ type Chart =
|> Option.defaultValue (Polar.init ())
|> Polar.style (Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1))
+ let allPolars = Layout.getPolars layout |> Seq.map fst
+
+ // 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.
+ allPolars |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
+
let polarAnchor =
StyleParam.SubPlotId.Polar(i + 1)
@@ -3194,6 +3286,11 @@ type Chart =
layout.TryGetTypedValue "smith"
|> Option.defaultValue (Smith.init ())
|> Smith.style (Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1))
+
+ let allSmiths = Layout.getSmiths layout |> Seq.map fst
+
+ // 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.
+ allSmiths |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
let polarAnchor =
StyleParam.SubPlotId.Smith(i + 1)
@@ -3209,12 +3306,18 @@ type Chart =
|> Option.defaultValue (Geo.init ())
|> Geo.style (Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1))
+ let allGeos = Layout.getGeos layout |> Seq.map fst
+
+ // 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.
+ allGeos |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
+
let geoAnchor =
StyleParam.SubPlotId.Geo(i + 1)
gChart
|> GenericChart.mapTrace (fun t -> t :?> TraceGeo |> TraceGeoStyle.SetGeo geoAnchor :> Trace)
|> Chart.withGeo (geo, (i + 1))
+
| TraceID.Mapbox ->
let mapbox =
layout.TryGetTypedValue "mapbox"
@@ -3223,6 +3326,14 @@ type Chart =
Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1)
)
+ let allMapboxes = Layout.getMapboxes layout |> Seq.map fst
+
+ // 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.
+ allMapboxes |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
+
+ let geoAnchor =
+ StyleParam.SubPlotId.Geo(i + 1)
+
let mapboxAnchor =
StyleParam.SubPlotId.Mapbox(i + 1)
@@ -3230,13 +3341,6 @@ type Chart =
|> GenericChart.mapTrace (fun t ->
t :?> TraceMapbox |> TraceMapboxStyle.SetMapbox mapboxAnchor :> Trace)
|> Chart.withMapbox (mapbox, (i + 1))
- | 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 ->
@@ -3247,23 +3351,40 @@ type Chart =
Domain = LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1)
)
+ let allTernaries = Layout.getTernaries layout |> Seq.map fst
+
+ // 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.
+ allTernaries |> Seq.iter (fun propName -> layout.Remove(propName) |> ignore)
+
let ternaryAnchor =
StyleParam.SubPlotId.Ternary(i + 1)
gChart
|> GenericChart.mapTrace (fun t ->
t :?> TraceTernary |> TraceTernaryStyle.SetTernary ternaryAnchor :> Trace)
- |> Chart.withTernary (ternary, (i + 1)))
+ |> Chart.withTernary (ternary, (i + 1))
+
+ | TraceID.Domain ->
+
+ // no need to remove existing domains, as only one domain can exist on the original layout. Just replace it.
+ let newDomain =
+ LayoutObjects.Domain.init (Row = rowIndex - 1, Column = colIndex - 1)
+
+ gChart
+ |> GenericChart.mapTrace (fun t ->
+ t :?> TraceDomain |> TraceDomainStyle.SetDomain newDomain :> Trace)
+ )
|> Chart.combine
+ |> Chart.withAnnotations(subPlotTitleAnnotations, Append=true)
|> Chart.withLayoutGrid (
LayoutGrid.init (
Rows = nRows,
Columns = nCols,
Pattern = pattern,
+ RowOrder = rowOrder,
?SubPlots = SubPlots,
?XAxes = XAxes,
?YAxes = YAxes,
- ?RowOrder = RowOrder,
?XGap = XGap,
?YGap = YGap,
?Domain = Domain,
@@ -3278,7 +3399,23 @@ type Chart =
/// ATTENTION: when the individual rows do not have the same amount of charts, they will be filled with dummy charts TO THE RIGHT.
///
/// prevent this behaviour by using Chart.Invisible at the cells that should be empty.
+ ///
+ /// For each input chart, a corresponding subplot cell is created in the grid. The following limitations apply to the individual grid cells:
+ ///
+ /// - 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).
+ /// if you need multiple axes per grid cell, create a custom grid by manually creating axes with custom domains instead.
+ /// 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
+ ///
+ /// - 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.
+ /// 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.
+ ///
+ /// - 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.
+ ///
+ /// - 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.
///
+ /// A collection of titles for the individual subplots.
+ /// The font of the subplot titles
+ /// A vertical offset applied to each subplot title, moving it upwards if positive and vice versa
/// Used for freeform grids, where some axes may be shared across subplots but others are not. Each entry should be a cartesian subplot id, like "xy" or "x3y2", or "" to leave that cell empty. You may reuse x axes within the same column, and y axes within the same row. Non-cartesian subplots and traces that support `domain` can place themselves in this grid separately using the `gridcell` attribute.
/// Used with `yaxes` when the x and y axes are shared across columns and rows. Each entry should be an y axis id like "y", "y2", etc., or "" to not put a y axis in that row. Entries other than "" must be unique. Ignored if `subplots` is present. If missing but `xaxes` is present, will generate consecutive IDs.
/// Used with `yaxes` when the x and y axes are shared across columns and rows. Each entry should be an x axis id like "x", "x2", etc., or "" to not put an x axis in that column. Entries other than "" must be unique. Ignored if `subplots` is present. If missing but `yaxes` is present, will generate consecutive IDs.
@@ -3292,6 +3429,9 @@ type Chart =
[]
static member Grid
(
+ [] ?SubPlotTitles: #seq,
+ [] ?SubPlotTitleFont: Font,
+ [] ?SubPlotTitleOffset: float,
[] ?SubPlots: (StyleParam.LinearAxisId * StyleParam.LinearAxisId)[][],
[] ?XAxes: StyleParam.LinearAxisId[],
[] ?YAxes: StyleParam.LinearAxisId[],
@@ -3340,6 +3480,9 @@ type Chart =
|> Chart.Grid(
nRows,
nCols,
+ ?SubPlotTitles = SubPlotTitles,
+ ?SubPlotTitleFont = SubPlotTitleFont,
+ ?SubPlotTitleOffset = SubPlotTitleOffset,
?SubPlots = SubPlots,
?XAxes = XAxes,
?YAxes = YAxes,
@@ -3357,6 +3500,9 @@ type Chart =
|> Chart.Grid(
nRows,
nCols,
+ ?SubPlotTitles = SubPlotTitles,
+ ?SubPlotTitleFont = SubPlotTitleFont,
+ ?SubPlotTitleOffset = SubPlotTitleOffset,
?SubPlots = SubPlots,
?XAxes = XAxes,
?YAxes = YAxes,
@@ -3370,7 +3516,23 @@ type Chart =
)
/// Creates a chart stack (a subplot grid with one column) from the input charts.
+ ///
+ /// For each input chart, a corresponding subplot cell is created in the column. The following limitations apply to the individual grid cells:
+ ///
+ /// - 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).
+ /// if you need multiple axes per grid cell, create a custom grid by manually creating axes with custom domains instead.
+ /// 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
+ ///
+ /// - 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.
+ /// 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.
+ ///
+ /// - 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.
+ ///
+ /// - 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.
///
+ /// A collection of titles for the individual subplots.
+ /// The font of the subplot titles
+ /// A vertical offset applied to each subplot title, moving it upwards if positive and vice versa
/// Used for freeform grids, where some axes may be shared across subplots but others are not. Each entry should be a cartesian subplot id, like "xy" or "x3y2", or "" to leave that cell empty. You may reuse x axes within the same column, and y axes within the same row. Non-cartesian subplots and traces that support `domain` can place themselves in this grid separately using the `gridcell` attribute.
/// Used with `yaxes` when the x and y axes are shared across columns and rows. Each entry should be an y axis id like "y", "y2", etc., or "" to not put a y axis in that row. Entries other than "" must be unique. Ignored if `subplots` is present. If missing but `xaxes` is present, will generate consecutive IDs.
/// Used with `yaxes` when the x and y axes are shared across columns and rows. Each entry should be an x axis id like "x", "x2", etc., or "" to not put an x axis in that column. Entries other than "" must be unique. Ignored if `subplots` is present. If missing but `yaxes` is present, will generate consecutive IDs.
@@ -3384,6 +3546,9 @@ type Chart =
[]
static member SingleStack
(
+ [] ?SubPlotTitles: #seq,
+ [] ?SubPlotTitleFont: Font,
+ [] ?SubPlotTitleOffset: float,
[] ?SubPlots: (StyleParam.LinearAxisId * StyleParam.LinearAxisId)[][],
[] ?XAxes: StyleParam.LinearAxisId[],
[] ?YAxes: StyleParam.LinearAxisId[],
@@ -3402,6 +3567,9 @@ type Chart =
|> Chart.Grid(
nRows = Seq.length gCharts,
nCols = 1,
+ ?SubPlotTitles = SubPlotTitles,
+ ?SubPlotTitleFont = SubPlotTitleFont,
+ ?SubPlotTitleOffset = SubPlotTitleOffset,
?SubPlots = SubPlots,
?XAxes = XAxes,
?YAxes = YAxes,
diff --git a/src/Plotly.NET/CommonAbstractions/StyleParams.fs b/src/Plotly.NET/CommonAbstractions/StyleParams.fs
index 82fa791a..0afd9be9 100644
--- a/src/Plotly.NET/CommonAbstractions/StyleParams.fs
+++ b/src/Plotly.NET/CommonAbstractions/StyleParams.fs
@@ -1,6 +1,8 @@
namespace Plotly.NET
open System
+open System.Text
+open System.Text.RegularExpressions
// https://plot.ly/javascript/reference/
// https://plot.ly/javascript-graphing-library/reference/
@@ -284,10 +286,24 @@ module StyleParam =
else
sprintf "legend%i" id
+ static member isValidXAxisId (id: string) = Regex.IsMatch(id, "xaxis[0-9]*")
+ static member isValidYAxisId (id: string) = Regex.IsMatch(id, "yaxis[0-9]*")
+ static member isValidZAxisId (id: string) = Regex.IsMatch(id, "zaxis")
+ static member isValidColorAxisId (id: string) = Regex.IsMatch(id, "coloraxis[0-9]*")
+ static member isValidGeoId (id: string) = Regex.IsMatch(id, "geo[0-9]*")
+ static member isValidMapboxId (id: string) = Regex.IsMatch(id, "mapbox[0-9]*")
+ static member isValidPolarId (id: string) = Regex.IsMatch(id, "polar[0-9]*")
+ static member isValidTernaryId (id: string) = Regex.IsMatch(id, "ternary[0-9]*")
+ static member isValidSceneId (id: string) = Regex.IsMatch(id, "scene[0-9]*")
+ static member isValidSmithId (id: string) = Regex.IsMatch(id, "smith[0-9]*")
+ static member isValidLegendId (id: string) = Regex.IsMatch(id, "legend[0-9]*")
+
static member convert = SubPlotId.toString >> box
override this.ToString() = this |> SubPlotId.toString
member this.Convert() = this |> SubPlotId.convert
+
+
[]
type AutoTypeNumbers =
| ConvertTypes
diff --git a/src/Plotly.NET/Layout/Layout.fs b/src/Plotly.NET/Layout/Layout.fs
index 943b66a7..2270be1f 100644
--- a/src/Plotly.NET/Layout/Layout.fs
+++ b/src/Plotly.NET/Layout/Layout.fs
@@ -458,7 +458,7 @@ type Layout() =
FunnelGap |> DynObj.setValueOpt layout "funnelgap"
FunnelGroupGap |> DynObj.setValueOpt layout "funnelgroupgap"
FunnelMode |> DynObj.setValueOptBy layout "funnelmode" StyleParam.FunnelMode.convert
- ExtendFunnelAreaColors |> DynObj.setValueOpt layout "extendfunnelareacolors "
+ ExtendFunnelAreaColors |> DynObj.setValueOpt layout "extendfunnelareacolors"
FunnelAreaColorWay |> DynObj.setValueOpt layout "funnelareacolorway"
ExtendSunBurstColors |> DynObj.setValueOpt layout "extendsunburstcolors"
SunBurstColorWay |> DynObj.setValueOpt layout "sunburstcolorway"
@@ -584,6 +584,34 @@ type Layout() =
static member getLinearAxisById(id: StyleParam.SubPlotId) =
(fun (layout: Layout) -> layout |> Layout.tryGetLinearAxisById id |> Option.defaultValue (LinearAxis.init ()))
+ ///
+ /// Returns a sequence of key-value pairs of the layout's dynamic members that are valid x axes (if the key matches and object can be cast to the correct type).
+ ///
+ /// The layout to get the x axes from
+ static member getXAxes (layout: Layout) =
+ layout.GetProperties(includeInstanceProperties = false)
+ |> Seq.choose (fun kv ->
+ if StyleParam.SubPlotId.isValidXAxisId kv.Key then
+ match layout.TryGetTypedValue(kv.Key) with
+ | Some axis -> Some (kv.Key, axis)
+ | None -> None
+ else None
+ )
+
+ ///
+ /// Returns a sequence of key-value pairs of the layout's dynamic members that are valid y axes (if the key matches and object can be cast to the correct type).
+ ///
+ /// The layout to get the y axes from
+ static member getYAxes (layout: Layout) =
+ layout.GetProperties(includeInstanceProperties = false)
+ |> Seq.choose (fun kv ->
+ if StyleParam.SubPlotId.isValidYAxisId kv.Key then
+ match layout.TryGetTypedValue(kv.Key) with
+ | Some axis -> Some (kv.Key, axis)
+ | None -> None
+ else None
+ )
+
///
/// Sets a linear axis object on the layout as a dynamic property with the given axis id.
///
@@ -633,6 +661,21 @@ type Layout() =
static member getSceneById(id: StyleParam.SubPlotId) =
(fun (layout: Layout) -> layout |> Layout.tryGetSceneById id |> Option.defaultValue (Scene.init ()))
+
+ ///
+ /// Returns a sequence of key-value pairs of the layout's dynamic members that are valid scenes (if the key matches and object can be cast to the correct type).
+ ///
+ /// The layout to get the scenes from
+ static member getScenes (layout: Layout) =
+ layout.GetProperties(includeInstanceProperties = false)
+ |> Seq.choose (fun kv ->
+ if StyleParam.SubPlotId.isValidSceneId kv.Key then
+ match layout.TryGetTypedValue(kv.Key) with
+ | Some scene -> Some (kv.Key, scene)
+ | None -> None
+ else None
+ )
+
///
/// Sets a scene object on the layout as a dynamic property with the given scene id.
///
@@ -674,6 +717,20 @@ type Layout() =
static member getGeoById(id: StyleParam.SubPlotId) =
(fun (layout: Layout) -> layout |> Layout.tryGetGeoById id |> Option.defaultValue (Geo.init ()))
+ ///
+ /// Returns a sequence of key-value pairs of the layout's dynamic members that are valid geo subplots (if the key matches and object can be cast to the correct type).
+ ///
+ /// The layout to get the geos from
+ static member getGeos (layout: Layout) =
+ layout.GetProperties(includeInstanceProperties = false)
+ |> Seq.choose (fun kv ->
+ if StyleParam.SubPlotId.isValidGeoId kv.Key then
+ match layout.TryGetTypedValue(kv.Key) with
+ | Some geo -> Some (kv.Key, geo)
+ | None -> None
+ else None
+ )
+
///
/// Sets a geo object on the layout as a dynamic property with the given geo id.
///
@@ -717,6 +774,20 @@ type Layout() =
static member getMapboxById(id: StyleParam.SubPlotId) =
(fun (layout: Layout) -> layout |> Layout.tryGetMapboxById id |> Option.defaultValue (Mapbox.init ()))
+ ///
+ /// Returns a sequence of key-value pairs of the layout's dynamic members that are valid mapbox subplots (if the key matches and object can be cast to the correct type).
+ ///
+ /// The layout to get the mapboxes from
+ static member getMapboxes (layout: Layout) =
+ layout.GetProperties(includeInstanceProperties = false)
+ |> Seq.choose (fun kv ->
+ if StyleParam.SubPlotId.isValidMapboxId kv.Key then
+ match layout.TryGetTypedValue(kv.Key) with
+ | Some mapbox -> Some (kv.Key, mapbox)
+ | None -> None
+ else None
+ )
+
///
/// Sets a mapbox object on the layout as a dynamic property with the given mapbox id.
///
@@ -762,6 +833,20 @@ type Layout() =
static member getPolarById(id: StyleParam.SubPlotId) =
(fun (layout: Layout) -> layout |> Layout.tryGetPolarById id |> Option.defaultValue (Polar.init ()))
+ ///
+ /// Returns a sequence of key-value pairs of the layout's dynamic members that are valid polar subplots (if the key matches and object can be cast to the correct type).
+ ///
+ /// The layout to get the polars from
+ static member getPolars (layout: Layout) =
+ layout.GetProperties(includeInstanceProperties = false)
+ |> Seq.choose (fun kv ->
+ if StyleParam.SubPlotId.isValidPolarId kv.Key then
+ match layout.TryGetTypedValue(kv.Key) with
+ | Some polar -> Some (kv.Key, polar)
+ | None -> None
+ else None
+ )
+
///
/// Sets a polar object on the layout as a dynamic property with the given polar id.
///
@@ -807,6 +892,20 @@ type Layout() =
static member getSmithById(id: StyleParam.SubPlotId) =
(fun (layout: Layout) -> layout |> Layout.tryGetSmithById id |> Option.defaultValue (Smith.init ()))
+ ///
+ /// Returns a sequence of key-value pairs of the layout's dynamic members that are valid smith subplots (if the key matches and object can be cast to the correct type).
+ ///
+ /// The layout to get the smiths from
+ static member getSmiths (layout: Layout) =
+ layout.GetProperties(includeInstanceProperties = false)
+ |> Seq.choose (fun kv ->
+ if StyleParam.SubPlotId.isValidSmithId kv.Key then
+ match layout.TryGetTypedValue(kv.Key) with
+ | Some smith -> Some (kv.Key, smith)
+ | None -> None
+ else None
+ )
+
///
/// Sets a smith object on the layout as a dynamic property with the given smith id.
///
@@ -852,6 +951,20 @@ type Layout() =
static member getColorAxisById(id: StyleParam.SubPlotId) =
(fun (layout: Layout) -> layout |> Layout.tryGetColorAxisById id |> Option.defaultValue (ColorAxis.init ()))
+ ///
+ /// Returns a sequence of key-value pairs of the layout's dynamic members that are valid color axes (if the key matches and object can be cast to the correct type).
+ ///
+ /// The layout to get the color axes from
+ static member getColorAxes (layout: Layout) =
+ layout.GetProperties(includeInstanceProperties = false)
+ |> Seq.choose (fun kv ->
+ if StyleParam.SubPlotId.isValidColorAxisId kv.Key then
+ match layout.TryGetTypedValue(kv.Key) with
+ | Some colorAxis -> Some (kv.Key, colorAxis)
+ | None -> None
+ else None
+ )
+
///
/// Sets a ColorAxis object on the layout as a dynamic property with the given ColorAxis id.
///
@@ -897,6 +1010,20 @@ type Layout() =
static member getTernaryById(id: StyleParam.SubPlotId) =
(fun (layout: Layout) -> layout |> Layout.tryGetTernaryById id |> Option.defaultValue (Ternary.init ()))
+ ///
+ /// Returns a sequence of key-value pairs of the layout's dynamic members that are valid ternary subplots (if the key matches and object can be cast to the correct type).
+ ///
+ /// The layout to get the ternaries from
+ static member getTernaries (layout: Layout) =
+ layout.GetProperties(includeInstanceProperties = false)
+ |> Seq.choose (fun kv ->
+ if StyleParam.SubPlotId.isValidTernaryId kv.Key then
+ match layout.TryGetTypedValue(kv.Key) with
+ | Some ternary -> Some (kv.Key, ternary)
+ | None -> None
+ else None
+ )
+
///
/// Sets a Ternary object on the layout as a dynamic property with the given Ternary id.
///
@@ -945,6 +1072,20 @@ type Layout() =
static member tryGetLegendById(id: StyleParam.SubPlotId) =
(fun (layout: Layout) -> layout.TryGetTypedValue