Skip to content

Commit 0a66156

Browse files
committed
Add Chart.Grid and Chart.SingleStack
1 parent b64cd84 commit 0a66156

File tree

3 files changed

+180
-36
lines changed

3 files changed

+180
-36
lines changed

Diff for: src/FSharp.Plotly/ChartExtensions.fs

+108-17
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,46 @@ module ChartExtensions =
352352
|> Layout.SetLayoutGrid layoutGrid
353353
GenericChart.setLayout layout ch)
354354

355+
// Set the LayoutGrid options of a Chart
356+
[<CompiledName("WithLayoutGridStyle")>]
357+
static member withLayoutGridStyle([<Optional;DefaultParameterValue(null)>]?SubPlots : StyleParam.AxisId [] [],
358+
[<Optional;DefaultParameterValue(null)>]?XAxes : StyleParam.AxisId [],
359+
[<Optional;DefaultParameterValue(null)>]?YAxes : StyleParam.AxisId [],
360+
[<Optional;DefaultParameterValue(null)>]?Rows : int,
361+
[<Optional;DefaultParameterValue(null)>]?Columns : int,
362+
[<Optional;DefaultParameterValue(null)>]?RowOrder : StyleParam.LayoutGridRowOrder,
363+
[<Optional;DefaultParameterValue(null)>]?Pattern : StyleParam.LayoutGridPattern,
364+
[<Optional;DefaultParameterValue(null)>]?XGap : float,
365+
[<Optional;DefaultParameterValue(null)>]?YGap : float,
366+
[<Optional;DefaultParameterValue(null)>]?Domain : Domain,
367+
[<Optional;DefaultParameterValue(null)>]?XSide : StyleParam.LayoutGridXSide,
368+
[<Optional;DefaultParameterValue(null)>]?YSide : StyleParam.LayoutGridYSide
369+
) =
370+
(fun (ch:GenericChart) ->
371+
let layout = GenericChart.getLayout ch
372+
let updatedGrid =
373+
let currentGrid =
374+
match layout.TryGetTypedValue<LayoutGrid> "grid" with
375+
| Some grid -> grid
376+
| None -> LayoutGrid()
377+
currentGrid
378+
|> LayoutGrid.style(
379+
?SubPlots = SubPlots,
380+
?XAxes = XAxes ,
381+
?YAxes = YAxes ,
382+
?Rows = Rows ,
383+
?Columns = Columns ,
384+
?RowOrder = RowOrder,
385+
?Pattern = Pattern ,
386+
?XGap = XGap ,
387+
?YGap = YGap ,
388+
?Domain = Domain ,
389+
?XSide = XSide ,
390+
?YSide = YSide
391+
)
392+
let updatedLayout = layout |> Layout.SetLayoutGrid updatedGrid
393+
GenericChart.setLayout updatedLayout ch)
394+
355395
[<CompiledName("WithConfig")>]
356396
static member withConfig (config:Config) =
357397
(fun (ch:GenericChart) ->
@@ -450,41 +490,71 @@ module ChartExtensions =
450490
static member Combine(gCharts:seq<GenericChart>) =
451491
GenericChart.combine gCharts
452492

453-
493+
///Creates a Grid containing the given plots as subplots with the dimensions of the input (amount of columns equal to the largest inner sequence).
494+
///
495+
///Parameters:
496+
///
497+
///sharedAxes : Wether the subplots share one xAxis per column and one yAxis per row or not. (default:TopToBottom)
498+
///
499+
///rowOrder : the order in which the rows of the grid will be rendered (default:false)
500+
///
501+
///xGap : The space between columns of the grid relative to the x dimension of the grid
502+
///
503+
///yGap : The space between rows of the grid relative to the y dimension of the grid
504+
///
505+
///Use Chart.withLayoutGridStyle to further style the grid object contained in the returned chart.
454506
[<CompiledName("Grid")>]
455507
static member Grid ((gCharts:seq<#seq<GenericChart>>),
456-
sharedAxes:bool
508+
[<Optional;DefaultParameterValue(false)>]?sharedAxes:bool,
509+
[<Optional;DefaultParameterValue(null)>]?rowOrder:StyleParam.LayoutGridRowOrder,
510+
[<Optional;DefaultParameterValue(0.05)>] ?xGap,
511+
[<Optional;DefaultParameterValue(0.05)>] ?yGap
457512
) =
458513

459-
let nRows = Seq.length gCharts
460-
let rowWidth = 1. / float nRows
514+
let sharedAxes = defaultArg sharedAxes false
515+
let rowOrder = defaultArg rowOrder StyleParam.LayoutGridRowOrder.TopToBottom
516+
let xGap = defaultArg xGap 0.05
517+
let yGap = defaultArg yGap 0.05
461518

519+
let nRows = Seq.length gCharts
462520
let nCols = gCharts |> Seq.maxBy Seq.length |> Seq.length
463-
let colWidth = 1. / float nCols
464-
465521
let pattern = if sharedAxes then StyleParam.LayoutGridPattern.Coupled else StyleParam.LayoutGridPattern.Independent
466-
let grid =
467-
LayoutGrid.init(
468-
Rows=nRows,Columns=nCols,Pattern=pattern
469-
)
522+
523+
let generateDomainRanges (count:int) (gap:float) =
524+
[|0. .. (1. / (float count)) .. 1.|]
525+
|> fun doms ->
526+
doms
527+
|> Array.windowed 2
528+
|> Array.mapi (fun i x ->
529+
if i = 0 then
530+
x.[0], (x.[1] - (gap / 2.))
531+
elif i = (doms.Length - 1) then
532+
(x.[0] + (gap / 2.)),x.[1]
533+
else
534+
(x.[0] + (gap / 2.)) , (x.[1] - (gap / 2.))
535+
)
536+
537+
let yDomains = generateDomainRanges nRows yGap
538+
let xDomains = generateDomainRanges nCols xGap
539+
470540
gCharts
471541
|> Seq.mapi (fun rowIndex row ->
472542
row |> Seq.mapi (fun colIndex gChart ->
473-
let xdomain = (colWidth * float (colIndex-1), (colWidth * float colIndex))
474-
let ydomain = (1. - ((rowWidth * float rowIndex)),1. - (rowWidth * float (rowIndex-1)))
543+
let xdomain = xDomains.[colIndex]
544+
let ydomain = yDomains.[rowIndex]
475545

476546
let newXIndex, newYIndex =
477-
(if sharedAxes then colIndex + 1 else (rowIndex + colIndex + 1)),
478-
(if sharedAxes then rowIndex + 1 else (rowIndex + colIndex + 1))
547+
(if sharedAxes then colIndex + 1 else ((nRows * rowIndex) + (colIndex + 1))),
548+
(if sharedAxes then rowIndex + 1 else ((nRows * rowIndex) + (colIndex + 1)))
479549

480550

481551
let xaxis,yaxis,layout =
482552
let layout = GenericChart.getLayout gChart
483553
let xAxisName, yAxisName = StyleParam.AxisId.X 1 |> StyleParam.AxisId.toString, StyleParam.AxisId.Y 1 |> StyleParam.AxisId.toString
484-
554+
485555
let updateXAxis index domain axis =
486556
axis |> Axis.LinearAxis.style(Anchor=StyleParam.AxisAnchorId.X index,Domain=StyleParam.Range.MinMax domain)
487-
557+
488558
let updateYAxis index domain axis =
489559
axis |> Axis.LinearAxis.style(Anchor=StyleParam.AxisAnchorId.Y index,Domain=StyleParam.Range.MinMax domain)
490560
match (layout.TryGetTypedValue<Axis.LinearAxis> xAxisName),(layout.TryGetTypedValue<Axis.LinearAxis> yAxisName) with
@@ -526,9 +596,30 @@ module ChartExtensions =
526596
)
527597
|> Seq.map Chart.Combine
528598
|> Chart.Combine
529-
|> Chart.withLayoutGrid grid
599+
|> Chart.withLayoutGrid(
600+
LayoutGrid.init(
601+
Rows=nRows,Columns=nCols,XGap= xGap,YGap= yGap,Pattern=pattern,RowOrder=rowOrder
602+
)
603+
)
604+
605+
///Creates a chart stack from the input charts by stacking them on top of each other starting from the first chart.
606+
///
607+
///Parameters:
608+
///
609+
///sharedAxis : wether the stack has a shared x axis (default:true)
610+
[<CompiledName("SingleStack")>]
611+
static member SingleStack (charts:#seq<GenericChart>,
612+
[<Optional;DefaultParameterValue(true)>] ?sharedXAxis:bool) =
613+
614+
let sharedAxis = defaultArg sharedXAxis true
615+
let singleCol = seq {
616+
for i = 0 to ((Seq.length charts) - 1) do
617+
yield seq {Seq.item i charts}
618+
}
619+
Chart.Grid(gCharts = singleCol, sharedAxes = sharedAxis, rowOrder = StyleParam.LayoutGridRowOrder.BottomToTop)
530620

531621
/// Create a combined chart with the given charts merged
622+
[<Obsolete("Use Chart.Grid for multi column grid charts or singleStack for one-column stacked charts.")>]
532623
[<CompiledName("Stack")>]
533624
static member Stack ( [<Optional;DefaultParameterValue(null)>] ?Columns:int,
534625
[<Optional;DefaultParameterValue(null)>] ?Space) =

Diff for: src/FSharp.Plotly/Layout.fs

+10
Original file line numberDiff line numberDiff line change
@@ -397,4 +397,14 @@ type Layout() =
397397
layout
398398
)
399399

400+
401+
static member GetLayoutGrid
402+
(
403+
grid: LayoutGrid
404+
) =
405+
(fun (layout:Layout) ->
406+
grid |> DynObj.setValue layout "grid"
407+
layout
408+
)
409+
400410

Diff for: src/FSharp.Plotly/Playground.fsx

+62-19
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,15 @@ open FSharp.Plotly
3838
open GenericChart
3939

4040

41-
let grid ((gCharts:seq<#seq<GenericChart>>),sharedXAxes:bool,sharedYAxes:bool) =
42-
let nRows = Seq.length gCharts
43-
let rowWidth = 1. / float nRows
41+
let grid ((gCharts:seq<#seq<GenericChart>>),sharedAxes:bool,xGap,yGap) =
4442

43+
let nRows = Seq.length gCharts
4544
let nCols = gCharts |> Seq.maxBy Seq.length |> Seq.length
46-
let colWidth = 1. / float nCols
47-
48-
let xGap = 0.1
49-
let yGap = 0.1
50-
51-
let pattern = StyleParam.LayoutGridPattern.Independent
52-
53-
let xSide = StyleParam.LayoutGridXSide.Bottom
54-
let ySide = StyleParam.LayoutGridYSide.Left
45+
let pattern = if sharedAxes then StyleParam.LayoutGridPattern.Coupled else StyleParam.LayoutGridPattern.Independent
5546

5647
let grid =
5748
LayoutGrid.init(
58-
Rows=nRows,Columns=nCols,XGap= 0.05,YGap= 0.05
49+
Rows=nRows,Columns=nCols,XGap= xGap,YGap= yGap,Pattern=pattern
5950
)
6051

6152
let generateDomainRanges (count:int) (gap:float) =
@@ -82,8 +73,8 @@ let grid ((gCharts:seq<#seq<GenericChart>>),sharedXAxes:bool,sharedYAxes:bool) =
8273
let ydomain = yDomains.[rowIndex]
8374

8475
let newXIndex, newYIndex =
85-
(if sharedXAxes then colIndex + 1 else ((nRows * rowIndex) + (colIndex + 1))),
86-
(if sharedYAxes then rowIndex + 1 else ((nRows * rowIndex) + (colIndex + 1)))
76+
(if sharedAxes then colIndex + 1 else ((nRows * rowIndex) + (colIndex + 1))),
77+
(if sharedAxes then rowIndex + 1 else ((nRows * rowIndex) + (colIndex + 1)))
8778

8879

8980
let xaxis,yaxis,layout =
@@ -138,11 +129,47 @@ let grid ((gCharts:seq<#seq<GenericChart>>),sharedXAxes:bool,sharedYAxes:bool) =
138129

139130

140131
grid ([
141-
[Chart.Point([(0,1)]);Chart.Point([(1,1)])]
142-
[Chart.Point([(0,1)]);Chart.Point([(1,1)])]
143-
],false,true)
132+
[Chart.Point([(0,1)]);Chart.Point([(0,1)]);Chart.Point([(0,1)]);]
133+
[Chart.Point([(0,1)]);Chart.Point([(0,1)]);Chart.Point([(0,1)]);]
134+
[Chart.Point([(0,1)]);Chart.Point([(0,1)]);Chart.Point([(0,1)]);]
135+
],true, 0.05,0.05)
144136
|> Chart.Show
145137

138+
let stack ( columns:int, space) =
139+
(fun (charts:#seq<GenericChart>) ->
140+
141+
let col = columns
142+
let len = charts |> Seq.length
143+
let colWidth = 1. / float col
144+
let rowWidth =
145+
let tmp = float len / float col |> ceil
146+
1. / tmp
147+
let space =
148+
let s = defaultArg space 0.05
149+
if s < 0. || s > 1. then
150+
printfn "Space should be between 0.0 - 1.0. Automaticaly set to default (0.05)"
151+
0.05
152+
else
153+
s
154+
155+
let contains3d ch =
156+
ch
157+
|> existsTrace (fun t ->
158+
match t with
159+
| :? Trace3d -> true
160+
| _ -> false)
161+
162+
charts
163+
|> Seq.mapi (fun i ch ->
164+
let colI,rowI,index = (i%col+1), (i/col+1),(i+1)
165+
let xdomain = (colWidth * float (colI-1), (colWidth * float colI) - space )
166+
let ydomain = (1. - ((rowWidth * float rowI) - space ),1. - (rowWidth * float (rowI-1)))
167+
xdomain)
168+
)
169+
170+
let a =
171+
stack (2, None) [Chart.Point([0,1]);Chart.Point([0,1])]
172+
|> Array.ofSeq
146173

147174
let generateDomainRanges nRows nCols =
148175

@@ -158,4 +185,20 @@ let generateDomainRanges nRows nCols =
158185

159186
else failwith "negative amount of rows or columns is stupid."
160187

161-
generateDomainRanges 8 1
188+
generateDomainRanges 8 1
189+
190+
191+
192+
193+
[
194+
Chart.Point([(0,1)]) |> Chart.withY_AxisStyle("This title")
195+
Chart.Point([(0,1)])
196+
|> Chart.withY_AxisStyle("Must be set",Zeroline=false)
197+
Chart.Point([(0,1)])
198+
|> Chart.withY_AxisStyle("on the respective charts",Zeroline=false)
199+
]
200+
|> Chart.SingleStack
201+
|> Chart.withLayoutGridStyle(XSide=StyleParam.LayoutGridXSide.Bottom)
202+
|> Chart.withTitle("Hi i am the new SingleStackChart")
203+
|> Chart.withX_AxisStyle("im the shared xAxis")
204+
|> Chart.Show

0 commit comments

Comments
 (0)