Friday 9 December 2016

Plots have I laid, inductions dangerous

Time to do some F#.

First I want a function that will tabulate a function f.

f is to be a function of two floats x and y returning a float. The table is to operate over an x-axis range and a y-axis range, each described by a tuple of floats.  So here is a sample function of the type I mean:

let testfun (x:float) (y:float) =
    let r = sqrt ((x * x) + (y * y))
    if r < 2.0
    then
        if r < 1.0
        then 7.0
        else 4.0
    else
        0.0

My tabulate function is to return a two-dimensional array of floats.  Here it is:

let tabulate funct (xrange : (float*float)) (yrange : (float*float)) : float[,]=
    let x0 = fst xrange
    let dx = (snd xrange - fst xrange) / 50.0
    let y0 = fst yrange
    let dy = (snd yrange - fst yrange) / 50.0
    Array2D.init 51 51 (fun i j -> funct (x0+(float i * dx)) (y0+(float j * dy)))

The function Array2D.init takes the size of the two dimensions of the array and a function that will populate each element in the array, given the indices I and j. I'm breaking my ranges into 50 parts so I want 51 elements in each direction.

Now to project this table of values as in isometric type sheet: create a new array of the same size but here each element is a tuple of values p and q that represent screen coordinates. p works down from the top left and q works across. So p and q depend on the index i and j, and p also depends on the value of the array that I've passed through.  I've scaled the value by 10.0 pixels quite arbitrarily. The grid is drawn by going right 5 pixels per cell and either up or down by 3.5 pixels for p and q axis, so:

let project (valueTable : float [,]) : (float * float)[,] =
    Array2D.init 51 51
        (fun (i:int) (j:int) ->
            let p = (float i * 5.0) + (float j * 5.0) + 20.0
            let q = (float i * 3.5) - (float j * 3.5) + 300.0 - (valueTable.[i,j] * 10.0)
            (p,q)) 

Right, now I want to plot out the projected table as an SVG element. After some research it looks like this is the thing to do:

let plot (a:(float * float)[,]) : unit =
    use file = System.IO.File.CreateText("plotout.svg")
    fprintfn file """<svg xmlns="http://www.w3.org/2000/svg" version="1.1">"""
    for j in 0..49 do
        for i in 0..49 do
            fprintf file """<polygon fill="#FFFFFF" """
            fprintf file """stroke="#000000" stroke-width="1" points="""
            fprintfn file "\"%.2f,%.2f %.2f,%.2f %.2f,%.2f %.2f,%.2f\" "
                (fst a.[i,j]) (snd a.[i,j])
                (fst a.[i,j+1]) (snd a.[i,j+1])
                (fst a.[i+1,j+1]) (snd a.[i+1,j+1])
                (fst a.[i+1,j]) (snd a.[i+1,j])
            fprintfn file """/>"""
    fprintfn file """</svg>"""

The SVG document contains a polygon element which has a fill colour and a stroke width and colour and a list of points - each point is represented by a two floats separated by a comma. For each element i, j I'm drawing a four sided shape with the points from i,j - i+1,j - i+1,j+1 - i,j+1.

So finally to tabulate and plot my test function in the range -3 to 3:

let main () =
    (tabulate testfun (-3.0, 3.0) (-3.0, 3.0)) |> project |> plot

Then you can open the resulting file in the browser:



This looks a bit rough round the back of the raised area.  Really I need to plot starting from the cells that are furthest away and then working forward, so get the hidden ones properly hidden. Still, nice first try.

No comments:

Post a Comment