-
Notifications
You must be signed in to change notification settings - Fork 36
/
Voronoi.elm
134 lines (115 loc) · 4.26 KB
/
Voronoi.elm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
-- This is an Elm reimplementation of a popular Voronoi diagram example using
-- elm-d3. The original can be found here:
--
-- http://bl.ocks.org/mbostock/4060366
--
-- From the root directory of the elm-d3 project, compile it using the
-- following commands:
--
-- elm-make examples/Voronoi.elm --output=voronoi.html
--
-- On OS X, you can then open the file in the browser using the following
-- command:
--
-- open voronoi.html
--
-- Note that due to recent changes in the Elm compiler, it is no longer
-- possible to linking of external JavaScript code while building projects. So,
-- you will have to manually link D3.js into this HTML file, as well as the
-- voronoi.css stylesheet, which you can find in the examples/ directory.
--
module Voronoi where
-- Import D3 and dump all its names into the current scope. Also import Voronoi
-- diagram-related operations, but keep those names qualified.
--
import D3 exposing (..)
import D3.Voronoi
-- Import Mouse to access the mouse's position. Also import Random and dump its
-- names into the current scope.
--
import Mouse
import Random exposing (..)
import String
import Time exposing (inMilliseconds, timestamp)
import Graphics.Element exposing (Element)
import Signal exposing ((<~), (~), constant)
-- Type declaractions for records that represent dimensions and margins.
--
type alias Dimensions = { height : Float, width : Float }
type alias Margins = { top : Float, left : Float, right : Float, bottom : Float }
width = 960
height = 500
margin = { top = 0, left = 0, right = 0, bottom = 0 }
dims = { height = height - margin.top - margin.bottom
, width = width - margin.left - margin.right }
-- A function that will create an <svg> element with a nested <g> element. The
-- function will use the given dimensions and margins the set the width and
-- height of the <svg> element.
--
svg : Dimensions -> Margins -> D3 a a
svg ds ms =
static "svg"
|. num attr "height" (ds.height + ms.top + ms.bottom)
|. num attr "width" (ds.width + ms.left + ms.right)
|. static "g"
|. str attr "transform" (translate margin.left margin.top)
circles : D3 (List D3.Voronoi.Point) D3.Voronoi.Point
circles =
selectAll "circle"
|= (List.drop 1)
|- enter <.> append "circle"
|. num attr "r" 1.5
|. str attr "fill" "black"
|- update
|. fun attr "cx" (\p _ -> toString p.x)
|. fun attr "cy" (\p _ -> toString p.y)
voronoi : D3 (List D3.Voronoi.Point) (List D3.Voronoi.Point)
voronoi =
selectAll "path"
|. bind cells
|- enter <.> append "path"
|- update
|. fun attr "d" (\ps _ -> path ps)
|. fun attr "class" (\_ i -> "q" ++ (toString ((%) i 9)) ++ "-9")
cells : List D3.Voronoi.Point -> List (List D3.Voronoi.Point)
cells = D3.Voronoi.cellsWithClipping margin.right margin.top dims.width dims.height
-- Helper function for creating an SVG path string for the given polygon.
--
path : List D3.Voronoi.Point -> String
path ps =
let pair p = (toString p.x) ++ "," ++ (toString p.y)
in "M" ++ (String.join "L" (List.map pair ps)) ++ "Z"
-- Helper function for creating an SVG transformation string that represents a
-- translation.
--
translate : number -> number -> String
translate x y = "translate(" ++ (toString x) ++ "," ++ (toString y) ++ ")"
-- Generates a list of random points of the given length. The list is returned
-- as a Signal, thought the signal will never take on any other value.
--
genPoints : Int -> Generator (List D3.Voronoi.Point)
genPoints n =
let points =
customGenerator <| \seed ->
let (x, seed') = generate (float 0 dims.width ) seed in
let (y, seed'') = generate (float 0 dims.height) seed' in
({ x = x, y = y }, seed'')
in
list n points
genPoints' : Int -> Seed -> List D3.Voronoi.Point
genPoints' n s = fst (generate (genPoints n) s)
-- Hoops to get a random seed.
--
seed : Signal Seed
seed = (\(time, _) -> initialSeed (floor (inMilliseconds time))) <~ (timestamp (constant ()))
vis dims margin =
svg dims margin
|- voronoi
|- circles
main : Signal Element
main =
let mouse (x, y) = { x = (toFloat x) - margin.left, y = (toFloat y) - margin.top }
randomPoints = genPoints' 100 <~ seed
points = (\m ps -> mouse m :: ps) <~ Mouse.position ~ randomPoints
in
render dims.width dims.height (vis dims margin) <~ points