Graphic autoscaling
I have the following codes to draw an unit circle
open System
open Microsoft.FSharp.Collections
open Microsoft.FSharp.Math
open System.Drawing
open System.Windows.Forms
let make_point (x:float) (y:float) = (fun bit -> if bit = 0.0 then x else y)
let x_of (point:float->float) = point 0.0
let y_of (point:float->float) = point 1.0
let unit_circle (t:float) =
make_point (sin <| 2.0 * Math.PI * t)
(cos <| 2.0 * Math.PI * t)
let draw_connected (curve:float->float->float) (values: float list)=
let form = new Form(Text = "Curve")
let drawCurve (g:Graphics) =
for t in values do
let p = curve t
开发者_如何学Go g.DrawEllipse(Pens.Red,
float32 (x_of p * 50.0 + (float)form.ClientSize.Width / 2.0),
float32 (y_of p * 50.0 + (float)form.ClientSize.Height / 2.0),
float32 1,
float32 1)
form.Paint.Add(fun e -> drawCurve e.Graphics)
form.Show()
draw_connected unit_circle ([0.0 .. 0.01 .. 1.0])
I am not entirely satisfied because I have to manual "scale" the x and y coordinates by 50 to make the circle visible. Is there a way to get F# do the scaling automatically?
Thanks.
I think the code is representing a 2D point as a function taking 3 args - a flag, x & y. The flag indicates which of x and y to return. It would make (slightly) more sense for a start if the flag was a bool rather than a float. I'm guessing the code has been converted from another language which only has floats?
Here's a slightly more comprehensible version:
open System
open Microsoft.FSharp.Collections
open Microsoft.FSharp.Math
open System.Drawing
open System.Windows.Forms
open System.Threading
type Point = {x : float; y : float}
let unit_circle (angle : float) =
{
x = (sin <| 2.0 * Math.PI * angle)
y = (cos <| 2.0 * Math.PI * angle)
}
let draw_connected (curve : float -> Point) (radius : float) (angles : float list) =
let form = new Form(Text = "Curve")
let drawCurve (gfx : Graphics) =
for angle in angles do
let p = curve angle
gfx.DrawEllipse(Pens.Red,
float32 (p.x * radius + (float)form.ClientSize.Width / 2.0),
float32 (p.y * radius + (float)form.ClientSize.Height / 2.0),
float32 1,
float32 1)
form.Paint.Add (fun pntEvntArgs -> drawCurve pntEvntArgs.Graphics)
form.Show ()
form
let form = draw_connected unit_circle 50.0 ([0.0 .. 0.01 .. 1.0])
while form.Created do
Thread.Sleep (1)
Application.DoEvents ()
done
Not sure why the circle is rendered as a collection of 1 pixel ellipses.
In any case, as Tomas says, either the circle has to be scaled or the coordinate system does. Otherwise you'll end up with a 1 pixel circle.
I didn't fully try to understand your code, but perhaps you could use the scale transformation that can be specified to the Graphics
object. This changes the coordinate system of Graphics
, so all drawing that you perform (e.g. using DrawEllipse
) is automatically scaled - you could set scaling in a way such that unit circle appears as circle with radius 50.
- To set the transformation, use the
ScaleTransfrom
method (see MSDN documentation for more information) ofGraphics
instance (the valueg
in your code).
As Tomas said you can use scaling transformations. If you want to draw a circle using small curves you can use multiple DrawCurve
calls :)
I've changed a bit jon's code for that purpose:
- used
System.Drawing.Point
type instead of your record - modified
unit_circle
so that it returns a tuple representing coordinates x and y - turned your list of angles into a sequence of sequence of angles. This will be useful since we can have a variable number of knots for our curve (a cardinal spline), represented by the constant
N
implemented a
splitRepeatEvery
method, e.g:Seq.splitRepeatEvery 3 { 1 .. 10 }
returnsseq [seq [1; 2; 3]; seq [3; 4; 5]; seq [5; 6; 7]; seq [7; 8; 9]; seq [9; 10]]
Here's the code:
module Seq =
/// Split a sequence into pieces, each n items long
/// repeating elements between start and end of subsequences.
let splitRepeatEvery (count : int) (source : seq<'T>) =
if not (count > 1) then failwith "count must be superior to 1"
seq { use e = source.GetEnumerator()
let hasNext = ref (e.MoveNext())
while !hasNext do
let (block:option<'T>[]) = Array.create count None
for i in 0 .. count - 1 do
do block.[i] <- if !hasNext then Some(e.Current) else None
if (i <> count - 1) then do hasNext := e.MoveNext()
yield seq { yield! block }
|> Seq.filter (fun x -> x.IsSome)
|> Seq.map (function Some(e) -> e | _ -> failwith "" ) }
let unit_circle (angle : float) =
(sin <| 2.0 * Math.PI * angle), (cos <| 2.0 * Math.PI * angle)
let draw_connected curve radius (seqOfAngles : float seq seq) knotsCount =
let form = new Form(Text = "Curve")
let computeBoundingBox points =
let search f acc array =
Array.fold (fun (x,y) (p:Point) -> f p.X x, f p.Y y) acc array
let minX, minY = search min (form.ClientSize.Width, form.ClientSize.Height) points
let maxX, maxY = search max (0,0) points
new Rectangle(minX, minY, abs(minX-maxX), abs(minY-maxY))
let drawCurves (gfx : Graphics) =
// Create a buffer for storing our knots
let buffer = Array.create knotsCount (new Point())
let mutable i = 0
for angles in seqOfAngles do
for angle in angles do
let x, y = curve angle
let X = int(x * radius + (float)form.ClientSize.Width / 2.0)
let Y = int(y * radius + (float)form.ClientSize.Height / 2.0)
let P = new Point(X, Y)
buffer.[i] <- P
i <- i + 1
let knots = buffer.[0..(i-1)]
// Draw spline only if we have one or more knots
if knots.Length <> 1 then
gfx.DrawCurve(Pens.Red, knots)
// For debug: compute BBox of an array of points and draw it
let debugRect = computeBoundingBox knots
gfx.DrawRectangle(Pens.Black, debugRect)
// Don't forget to reset position in buffer between each spline draw call
i <- 0
form.Paint.Add (fun pntEvntArgs -> drawCurves pntEvntArgs.Graphics)
form.Show ()
form
// Define constants
let STEP = 0.050
let N = 4
// Define a new sequence of sequence of angles
let s = {0.0 .. STEP .. 1.0} |> Seq.splitRepeatEvery N
let form = draw_connected unit_circle 120.0 s N
// For debug: print sequence of sequence of angles
s |> Seq.iter (fun s -> Seq.iter (fun x -> printf "%f " x) s; printfn "")
while form.Created do
Thread.Sleep (1)
Application.DoEvents ()
done
You can play with different values of N
(number of knots for the splines) and STEP
(but beware STEP
should be choosen so that 1.0f is a multiple of STEP
or a floating point number so that last element of last sequence is close enough to 1.0f else the last spline won't connect to the first one!). And voila!
alt text http://img818.imageshack.us/img818/9765/circles3.png
精彩评论