Skip to content

Commit 9ae74f5

Browse files
committed
fix #415:
- Add Layout API functions to retrieve all instances of a subplot (e.g. all x axes) - fix some typos in dynamic member setters - add various layout object/API tests
1 parent 290bfc6 commit 9ae74f5

File tree

8 files changed

+537
-36
lines changed

8 files changed

+537
-36
lines changed

src/Plotly.NET/ChartAPI/Chart.fs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,7 @@ type Chart =
11281128
match sceneAxisId with
11291129
| StyleParam.SubPlotId.XAxis _ -> scene |> Scene.getXAxis
11301130
| StyleParam.SubPlotId.YAxis _ -> scene |> Scene.getYAxis
1131-
| StyleParam.SubPlotId.ZAxis _ -> scene |> Scene.getZAxis
1131+
| StyleParam.SubPlotId.ZAxis -> scene |> Scene.getZAxis
11321132
| _ -> failwith "invalid scene axis id"
11331133

11341134
let updatedAxis =
@@ -1140,7 +1140,7 @@ type Chart =
11401140
match sceneAxisId with
11411141
| StyleParam.SubPlotId.XAxis _ -> s |> Scene.setXAxis axis
11421142
| StyleParam.SubPlotId.YAxis _ -> s |> Scene.setYAxis axis
1143-
| StyleParam.SubPlotId.ZAxis _ -> s |> Scene.setZAxis axis
1143+
| StyleParam.SubPlotId.ZAxis -> s |> Scene.setZAxis axis
11441144
| _ -> failwith "invalid scene axis id"
11451145

11461146
layout |> Layout.updateSceneById (id, updatedScene)
@@ -1153,7 +1153,7 @@ type Chart =
11531153
match sceneAxisId with
11541154
| StyleParam.SubPlotId.XAxis _ -> s |> Scene.setXAxis axis
11551155
| StyleParam.SubPlotId.YAxis _ -> s |> Scene.setYAxis axis
1156-
| StyleParam.SubPlotId.ZAxis _ -> s |> Scene.setZAxis axis
1156+
| StyleParam.SubPlotId.ZAxis -> s |> Scene.setZAxis axis
11571157
| _ -> failwith "invalid scene axis id"
11581158

11591159
layout |> Layout.updateSceneById (id, updatedScene))

src/Plotly.NET/CommonAbstractions/StyleParams.fs

+16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
namespace Plotly.NET
22

33
open System
4+
open System.Text
5+
open System.Text.RegularExpressions
46
// https://plot.ly/javascript/reference/
57
// https://plot.ly/javascript-graphing-library/reference/
68

@@ -284,10 +286,24 @@ module StyleParam =
284286
else
285287
sprintf "legend%i" id
286288

289+
static member isValidXAxisId (id: string) = Regex.IsMatch(id, "xaxis[0-9]*")
290+
static member isValidYAxisId (id: string) = Regex.IsMatch(id, "yaxis[0-9]*")
291+
static member isValidZAxisId (id: string) = Regex.IsMatch(id, "zaxis")
292+
static member isValidColorAxisId (id: string) = Regex.IsMatch(id, "coloraxis[0-9]*")
293+
static member isValidGeoId (id: string) = Regex.IsMatch(id, "geo[0-9]*")
294+
static member isValidMapboxId (id: string) = Regex.IsMatch(id, "mapbox[0-9]*")
295+
static member isValidPolarId (id: string) = Regex.IsMatch(id, "polar[0-9]*")
296+
static member isValidTernaryId (id: string) = Regex.IsMatch(id, "ternary[0-9]*")
297+
static member isValidSceneId (id: string) = Regex.IsMatch(id, "scene[0-9]*")
298+
static member isValidSmithId (id: string) = Regex.IsMatch(id, "smith[0-9]*")
299+
static member isValidLegendId (id: string) = Regex.IsMatch(id, "legend[0-9]*")
300+
287301
static member convert = SubPlotId.toString >> box
288302
override this.ToString() = this |> SubPlotId.toString
289303
member this.Convert() = this |> SubPlotId.convert
290304

305+
306+
291307
[<RequireQualifiedAccess>]
292308
type AutoTypeNumbers =
293309
| ConvertTypes

src/Plotly.NET/Layout/Layout.fs

+142-1
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ type Layout() =
458458
FunnelGap |> DynObj.setValueOpt layout "funnelgap"
459459
FunnelGroupGap |> DynObj.setValueOpt layout "funnelgroupgap"
460460
FunnelMode |> DynObj.setValueOptBy layout "funnelmode" StyleParam.FunnelMode.convert
461-
ExtendFunnelAreaColors |> DynObj.setValueOpt layout "extendfunnelareacolors "
461+
ExtendFunnelAreaColors |> DynObj.setValueOpt layout "extendfunnelareacolors"
462462
FunnelAreaColorWay |> DynObj.setValueOpt layout "funnelareacolorway"
463463
ExtendSunBurstColors |> DynObj.setValueOpt layout "extendsunburstcolors"
464464
SunBurstColorWay |> DynObj.setValueOpt layout "sunburstcolorway"
@@ -584,6 +584,34 @@ type Layout() =
584584
static member getLinearAxisById(id: StyleParam.SubPlotId) =
585585
(fun (layout: Layout) -> layout |> Layout.tryGetLinearAxisById id |> Option.defaultValue (LinearAxis.init ()))
586586

587+
/// <summary>
588+
/// 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).
589+
/// </summary>
590+
/// <param name="layout">The layout to get the x axes from</param>
591+
static member getXAxes (layout: Layout) =
592+
layout.GetProperties(includeInstanceProperties = false)
593+
|> Seq.choose (fun kv ->
594+
if StyleParam.SubPlotId.isValidXAxisId kv.Key then
595+
match layout.TryGetTypedValue<LinearAxis>(kv.Key) with
596+
| Some axis -> Some (kv.Key, axis)
597+
| None -> None
598+
else None
599+
)
600+
601+
/// <summary>
602+
/// 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).
603+
/// </summary>
604+
/// <param name="layout">The layout to get the y axes from</param>
605+
static member getYAxes (layout: Layout) =
606+
layout.GetProperties(includeInstanceProperties = false)
607+
|> Seq.choose (fun kv ->
608+
if StyleParam.SubPlotId.isValidYAxisId kv.Key then
609+
match layout.TryGetTypedValue<LinearAxis>(kv.Key) with
610+
| Some axis -> Some (kv.Key, axis)
611+
| None -> None
612+
else None
613+
)
614+
587615
/// <summary>
588616
/// Sets a linear axis object on the layout as a dynamic property with the given axis id.
589617
/// </summary>
@@ -633,6 +661,21 @@ type Layout() =
633661
static member getSceneById(id: StyleParam.SubPlotId) =
634662
(fun (layout: Layout) -> layout |> Layout.tryGetSceneById id |> Option.defaultValue (Scene.init ()))
635663

664+
665+
/// <summary>
666+
/// 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).
667+
/// </summary>
668+
/// <param name="layout">The layout to get the scenes from</param>
669+
static member getScenes (layout: Layout) =
670+
layout.GetProperties(includeInstanceProperties = false)
671+
|> Seq.choose (fun kv ->
672+
if StyleParam.SubPlotId.isValidSceneId kv.Key then
673+
match layout.TryGetTypedValue<Scene>(kv.Key) with
674+
| Some scene -> Some (kv.Key, scene)
675+
| None -> None
676+
else None
677+
)
678+
636679
/// <summary>
637680
/// Sets a scene object on the layout as a dynamic property with the given scene id.
638681
/// </summary>
@@ -674,6 +717,20 @@ type Layout() =
674717
static member getGeoById(id: StyleParam.SubPlotId) =
675718
(fun (layout: Layout) -> layout |> Layout.tryGetGeoById id |> Option.defaultValue (Geo.init ()))
676719

720+
/// <summary>
721+
/// 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).
722+
/// </summary>
723+
/// <param name="layout">The layout to get the geos from</param>
724+
static member getGeos (layout: Layout) =
725+
layout.GetProperties(includeInstanceProperties = false)
726+
|> Seq.choose (fun kv ->
727+
if StyleParam.SubPlotId.isValidGeoId kv.Key then
728+
match layout.TryGetTypedValue<Geo>(kv.Key) with
729+
| Some geo -> Some (kv.Key, geo)
730+
| None -> None
731+
else None
732+
)
733+
677734
/// <summary>
678735
/// Sets a geo object on the layout as a dynamic property with the given geo id.
679736
/// </summary>
@@ -717,6 +774,20 @@ type Layout() =
717774
static member getMapboxById(id: StyleParam.SubPlotId) =
718775
(fun (layout: Layout) -> layout |> Layout.tryGetMapboxById id |> Option.defaultValue (Mapbox.init ()))
719776

777+
/// <summary>
778+
/// 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).
779+
/// </summary>
780+
/// <param name="layout">The layout to get the mapboxes from</param>
781+
static member getMapboxes (layout: Layout) =
782+
layout.GetProperties(includeInstanceProperties = false)
783+
|> Seq.choose (fun kv ->
784+
if StyleParam.SubPlotId.isValidMapboxId kv.Key then
785+
match layout.TryGetTypedValue<Mapbox>(kv.Key) with
786+
| Some mapbox -> Some (kv.Key, mapbox)
787+
| None -> None
788+
else None
789+
)
790+
720791
/// <summary>
721792
/// Sets a mapbox object on the layout as a dynamic property with the given mapbox id.
722793
/// </summary>
@@ -762,6 +833,20 @@ type Layout() =
762833
static member getPolarById(id: StyleParam.SubPlotId) =
763834
(fun (layout: Layout) -> layout |> Layout.tryGetPolarById id |> Option.defaultValue (Polar.init ()))
764835

836+
/// <summary>
837+
/// 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).
838+
/// </summary>
839+
/// <param name="layout">The layout to get the polars from</param>
840+
static member getPolars (layout: Layout) =
841+
layout.GetProperties(includeInstanceProperties = false)
842+
|> Seq.choose (fun kv ->
843+
if StyleParam.SubPlotId.isValidPolarId kv.Key then
844+
match layout.TryGetTypedValue<Polar>(kv.Key) with
845+
| Some polar -> Some (kv.Key, polar)
846+
| None -> None
847+
else None
848+
)
849+
765850
/// <summary>
766851
/// Sets a polar object on the layout as a dynamic property with the given polar id.
767852
/// </summary>
@@ -807,6 +892,20 @@ type Layout() =
807892
static member getSmithById(id: StyleParam.SubPlotId) =
808893
(fun (layout: Layout) -> layout |> Layout.tryGetSmithById id |> Option.defaultValue (Smith.init ()))
809894

895+
/// <summary>
896+
/// 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).
897+
/// </summary>
898+
/// <param name="layout">The layout to get the smiths from</param>
899+
static member getSmiths (layout: Layout) =
900+
layout.GetProperties(includeInstanceProperties = false)
901+
|> Seq.choose (fun kv ->
902+
if StyleParam.SubPlotId.isValidSmithId kv.Key then
903+
match layout.TryGetTypedValue<Smith>(kv.Key) with
904+
| Some smith -> Some (kv.Key, smith)
905+
| None -> None
906+
else None
907+
)
908+
810909
/// <summary>
811910
/// Sets a smith object on the layout as a dynamic property with the given smith id.
812911
/// </summary>
@@ -852,6 +951,20 @@ type Layout() =
852951
static member getColorAxisById(id: StyleParam.SubPlotId) =
853952
(fun (layout: Layout) -> layout |> Layout.tryGetColorAxisById id |> Option.defaultValue (ColorAxis.init ()))
854953

954+
/// <summary>
955+
/// 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).
956+
/// </summary>
957+
/// <param name="layout">The layout to get the color axes from</param>
958+
static member getColorAxes (layout: Layout) =
959+
layout.GetProperties(includeInstanceProperties = false)
960+
|> Seq.choose (fun kv ->
961+
if StyleParam.SubPlotId.isValidColorAxisId kv.Key then
962+
match layout.TryGetTypedValue<ColorAxis>(kv.Key) with
963+
| Some colorAxis -> Some (kv.Key, colorAxis)
964+
| None -> None
965+
else None
966+
)
967+
855968
/// <summary>
856969
/// Sets a ColorAxis object on the layout as a dynamic property with the given ColorAxis id.
857970
/// </summary>
@@ -897,6 +1010,20 @@ type Layout() =
8971010
static member getTernaryById(id: StyleParam.SubPlotId) =
8981011
(fun (layout: Layout) -> layout |> Layout.tryGetTernaryById id |> Option.defaultValue (Ternary.init ()))
8991012

1013+
/// <summary>
1014+
/// 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).
1015+
/// </summary>
1016+
/// <param name="layout">The layout to get the ternaries from</param>
1017+
static member getTernaries (layout: Layout) =
1018+
layout.GetProperties(includeInstanceProperties = false)
1019+
|> Seq.choose (fun kv ->
1020+
if StyleParam.SubPlotId.isValidTernaryId kv.Key then
1021+
match layout.TryGetTypedValue<Ternary>(kv.Key) with
1022+
| Some ternary -> Some (kv.Key, ternary)
1023+
| None -> None
1024+
else None
1025+
)
1026+
9001027
/// <summary>
9011028
/// Sets a Ternary object on the layout as a dynamic property with the given Ternary id.
9021029
/// </summary>
@@ -945,6 +1072,20 @@ type Layout() =
9451072
static member tryGetLegendById(id: StyleParam.SubPlotId) =
9461073
(fun (layout: Layout) -> layout.TryGetTypedValue<Legend>(StyleParam.SubPlotId.toString id))
9471074

1075+
/// <summary>
1076+
/// Returns a sequence of key-value pairs of the layout's dynamic members that are valid legends (if the key matches and object can be cast to the correct type).
1077+
/// </summary>
1078+
/// <param name="layout">The layout to get the color axes from</param>
1079+
static member getLegends (layout: Layout) =
1080+
layout.GetProperties(includeInstanceProperties = false)
1081+
|> Seq.choose (fun kv ->
1082+
if StyleParam.SubPlotId.isValidLegendId kv.Key then
1083+
match layout.TryGetTypedValue<Legend>(kv.Key) with
1084+
| Some legend -> Some (kv.Key, legend)
1085+
| None -> None
1086+
else None
1087+
)
1088+
9481089
/// <summary>
9491090
/// Combines the given Legend object with the one already present on the layout.
9501091
/// </summary>

src/Plotly.NET/Layout/ObjectAbstractions/Common/NewSelection.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ type NewSelection() =
4747
line |> DynObj.setValue newSelection "line"
4848
Mode |> DynObj.setValueOptBy newSelection "mode" StyleParam.NewSelectionMode.convert
4949

50-
NewSelection)
50+
newSelection)

tests/Common/FSharpTestBase/TestUtils.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ module Objects =
217217

218218
let jsonFieldIsSetWith fieldName expected (object:#DynamicObj) =
219219
Expect.equal
220-
((object :> DynamicObj)?($"{fieldName}") |> JsonConvert.SerializeObject)
220+
((object :> DynamicObj)?($"{fieldName}") |> fun o -> JsonConvert.SerializeObject(o, JsonSerializerSettings(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)))
221221
expected
222222
($"Field `{fieldName}` not set correctly in serialized dynamic object.")
223223

tests/ConsoleApps/FSharpConsole/Program.fs

+37-30
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,43 @@ open Newtonsoft.Json
1212
[<EntryPoint>]
1313
let main argv =
1414

15-
[
16-
StyleParam.HoverInfo.X
17-
StyleParam.HoverInfo.XY
18-
StyleParam.HoverInfo.XYZ
19-
StyleParam.HoverInfo.XYZText
20-
StyleParam.HoverInfo.XYZTextName
21-
StyleParam.HoverInfo.Y
22-
StyleParam.HoverInfo.YZ
23-
StyleParam.HoverInfo.YZText
24-
StyleParam.HoverInfo.YZTextName
25-
StyleParam.HoverInfo.Z
26-
StyleParam.HoverInfo.ZText
27-
StyleParam.HoverInfo.ZTextName
28-
StyleParam.HoverInfo.Text
29-
StyleParam.HoverInfo.TextName
30-
StyleParam.HoverInfo.Name
31-
StyleParam.HoverInfo.All
32-
StyleParam.HoverInfo.None
33-
StyleParam.HoverInfo.Skip
34-
]
35-
|> List.mapi (fun i hi ->
36-
Chart.Point3D(
37-
xyz = [i + 1, i + 2, i + 3],
38-
Name = $"NAME: trace with {hi.ToString()}",
39-
Text = $"TEXT: trace with {hi.ToString()}",
40-
UseDefaults = false
41-
)
42-
|> GenericChart.mapTrace (Trace3DStyle.Scatter3D(HoverInfo = hi))
15+
let charts =
16+
[
17+
Chart.Point3D([1,2,3], UseDefaults = false)
18+
Chart.Point3D([1,2,3], UseDefaults = false)
19+
Chart.Point3D([1,2,3], UseDefaults = false)
20+
Chart.Point3D([1,2,3], UseDefaults = false)
21+
]
22+
23+
printfn "Individual Charts:"
24+
printfn "Layout:"
25+
charts
26+
|> Seq.iter (fun c ->
27+
GenericChart.getLayout c
28+
|> DynamicObj.DynObj.print
29+
)
30+
printfn "Traces:"
31+
charts
32+
|> Seq.iter (fun c ->
33+
GenericChart.getTraces c
34+
|> Seq.iter DynamicObj.DynObj.print
4335
)
44-
|> Chart.combine
45-
|> Chart.withSize(1000,1000)
36+
37+
let grid =
38+
charts
39+
|> Chart.Grid(2,2)
40+
41+
printfn "Grid:"
42+
printfn "Layout:"
43+
grid
44+
|> GenericChart.getLayout
45+
|> DynamicObj.DynObj.print
46+
printfn "Traces:"
47+
grid
48+
|> GenericChart.getTraces
49+
|> Seq.iter DynamicObj.DynObj.print
50+
51+
grid
4652
|> Chart.show
53+
4754
0

tests/CoreTests/CoreTests/CoreTests.fsproj

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<Compile Include="ChartAPIs\Combine.fs" />
3030
<Compile Include="CommonAbstractions\StyleParams.fs" />
3131
<Compile Include="CommonAbstractions\Colors.fs" />
32+
<Compile Include="LayoutObjects\Layout.fs" />
3233
<Compile Include="LayoutObjects\Slider.fs" />
3334
<Compile Include="LayoutObjects\LinearAxis.fs" />
3435
<Compile Include="ConfigObjects\Config.fs" />

0 commit comments

Comments
 (0)