// Piecewise linear curves with Bounding Box Michael R Hansen // 25-09-2024 // a simple example with a focus on how functions fit together // --- do they compose "nicely" // Piecewise linear curves type Point = float * float type Curve = Point * Point list // (p0, [p1;...;pn]), where 0<=n type BoundingBox = Point * Point // The task: compute the bounding box of a curve using a higher-order fold function // findBB: Curve -> BoundingBox // Remember: // List.foldBack: ('a -> 'b -> b) -> 'a list -> 'b -> 'b // foldBack f [x1; x2; ...; xn] e = f x1 (f x2 (...(f xn e)...)) // A version that fits foldBack // extBB Point -> BoundingBox -> BoundingBox // extend bounding // extBB: Point -> BoundingBox -> BoundingBox let extBB (x,y) ((x0,y0),(xm,ym)) = ((min x x0, min y y0),(max x xm, max y ym));; let findBB(p0,ps) = List.foldBack extBB ps (p0,p0);; // fits nicely // it does not fit fold --- some adaption is needed: let findBB'(p0,ps) = List.fold (fun bb p -> extBB p bb) (p0,p0) ps;; // adaption needed // Remember // List.fold: ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a // fold g e [x1; x2; ...; xn] = g (g (... (g (g e x1) x2) ...)) xn // A version that fits fold // extBB1: BoundingBox -> Point -> BoundingBox let extBB1 ((x0,y0),(xm,ym)) (x,y) = ((min x x0, min y y0),(max x xm, max y ym));; let findBB1(p0,ps) = List.fold extBB1 (p0,p0) ps;; // fits nicely // this version does not fit foldBack --- adaption needed: let findBB'''(p0,ps) = List.foldBack (fun p bb -> extBB1 bb p) ps (p0,p0);; // A recursive version // It does not really matter which version of extBB is used // extBB2: Point * BoundingBox -> BoundingBox // Parentheses in arguments are slightly annoying though let extBB2((x,y),((x0,y0),(xm,ym))) = ((min x x0, min y y0),(max x xm, max y ym));; let rec findBBHelp bb = function | [] -> bb | p::cs -> findBBHelp (extBB2(p,bb)) cs;; let findBB3(p0,ps) = findBBHelp (p0,p0) ps;; (* A minimal curve library *) type Vector = float * float // join: Curve -> Curve -> Curve -- safe parenthesis let join (c1,ps1) (c2,ps2) = (c1, ps1 @ c2::ps2) // movePoint: Vector -> Point -> Point -- safe parenthesis, follow convention // fits use of map below let movePoint (vx,vy) (x,y) = (x+vx, y+vy) : Point // move: Vector -> Curve -> Curve let move v (p0,ps) = (movePoint v p0, List.map (movePoint v) ps) // Visualization #r "nuget: Plotly.NET";; open Plotly.NET // Two main functions // toChart: Curve -> string option -> GenericChart // show: GenericChart list -> unit // linesOf: Curve -> Plotly.NET.GenericChart list let rec linesOf ((x0,y0),ps) = match ps with | [] -> [] | (x1,y1)::tail -> let chls = linesOf ((x1,y1), tail) Chart.Line([x0;x1],[y0;y1], LineColor = Color.fromString "blue", ShowLegend = false)::chls // bbToChart: BoundingBox -> GenericChart list let bbToChart((x0,y0), (xm,ym)) = [ Chart.Point([(x0,y0)], MultiText=["(x0,y0)"], MultiTextPosition=[StyleParam.TextPosition.BottomCenter], ShowLegend = false); Chart.Point([(xm,ym)], MultiText=["(xm,ym)"], MultiTextPosition=[StyleParam.TextPosition.TopCenter], ShowLegend = false); Chart.Line([x0;x0], [y0;ym] ,LineColor = Color.fromString "red", ShowLegend = false); Chart.Line([x0;xm], [ym;ym] ,LineColor = Color.fromString "red", ShowLegend = false); Chart.Line([xm;xm], [ym;y0],LineColor = Color.fromString "red", ShowLegend = false); Chart.Line([xm;x0], [y0;y0],LineColor = Color.fromString "red", ShowLegend = false) ];; // toChart: Curve -> string option -> GenericChart let toChart (((x0,y0),_) as c) so = let start = Chart.Point([(x0,y0)], MultiText=["start"], MultiTextPosition=[StyleParam.TextPosition.BottomCenter], ShowLegend = false) let chs = linesOf c let ((x0,y0),(xm,ym)) as bb = findBB3 c let bbch = bbToChart bb let lo = match so with | Some sl -> [Chart.Point([(x0+(xm-x0)/2.0,y0-0.5)], MultiText=[sl], MultiTextPosition=[StyleParam.TextPosition.BottomCenter], ShowLegend = false)] | None -> [] start :: lo @ chs @ bbch |> Chart.combine;; // show: GenericChart list -> unit let show chs = chs |> Chart.combine |> Chart.show;; // A simple example let c1 = ((3.,1.5), [(1.,2.); (4.,4.); (2.,3.); (2.,5.); (6.,1.)]) let ch0 = toChart c1 None // show [ch0] let ch1 = toChart c1 (Some "c1") //show [ch1];; let c2 = move (7.0,2.0) c1;; let ch2 = toChart c2 (Some "move (7.0,2.0) c1");; let c3 = move (15.0,0.0) (join c1 c2) let ch3 = toChart c3 (Some "move (15.0,0.0) (join c1 c2)");; show [ch1;ch2;ch3];; // There are several alternative curve representations // For example // type Curve = Point * Vector list // move is simpler using this representation: just move start point. // join becomes more complicated