-
Notifications
You must be signed in to change notification settings - Fork 36
CUFP 2014 Tutorial D3.js in Elm
Wiki ▸ [CUFP 2014 Tutorial](CUFP 2014 Tutorial) ▸ D3.js in Elm
The solution to the exercise in the previous section asked you to transform some D3 code into the reusable operator form, assuming that JavaScript had an infix operator |.
that acted like the chain
function. The result of this translation is syntactically very close to what the code would like in Elm, but with some very important differences, and types! Here's what it would look like in Elm:
import D3(..)
import Signal
circles : Widget [Float] Float
circles =
selectAll "circle"
|= (\x -> x)
|- enter <.> append "circle"
|. str attr "fill" "steelBlue"
|. fun attr "cx" (\d i -> show (25 + 50 * i))
|. num attr "cy" 150
|- update
|. fun attr "r" (\d i -> show d)
|- exit <.> remove
view : Selection [Float]
view =
static "svg"
|. num attr "width" 480
|. num attr "height" 640
|. embed circles
main : Signal Element
main =
render 640 480 view <~ (Signal.constant [8, 3, 15, 23, 12])
Try copy and pasting this code into a file called Circles.elm
in the root directory of the elm-d3 repo. Then build the program using the following command:
$ elm --make --src-dir=src `./scripts/build-flags` Circles.elm
You can find the resultant HTML file in build/Circles.html
. Open it in a web browser to see the results. Feel free to edit the code to make it change what and how it displays. The type system will ensure you don't get too far off track.
What follows is an explanation of the types and operators that are used in this example. The types are new and only have a loose correspondence to any concept in D3. Many of the operators however, will be familiar. Note that the functions involving selector strings are limited to the subtrees rooted at the elements in the context.
# Selection a
The type of a D3 operation in Elm. An operation of type Selection a assumes that its context will have data of type a bound to it. If a is a type variable then the operation can be used in any context. If a is a concrete type—such as Int
—this will limit the operations it can be composed with to those that also assume data of type Int
is bound to its context.
# select : String -> Selection a
Create a Selection containing the first descendant element of the context that matches the CSS selector string. If no element matches the selector string, the selection will be empty. If multiple elements match the selector string, the selection will contain only one element—the first one that matches.
# selectAll : String -> Selection a
Create a selection containing all the elements that are descendant from the context that match the CSS selector string.
# append : String -> Selection a
Append an element with the specified name as a child to each element in the context.
# static : String -> Selection a
Append an element with the specified name as a child to each element in the context. This element will only be appended once on the first render of the Selection. Subsequent renders will treat this operation as a select name
. It is essential to use this function in the update selection of a data bind (including the top-level definition of elements like in the example above). If you don't use this function but use append
instead, a new element will be appended to the context on each re-render.
# attr : String -> (a -> Int -> String) -> Selection a
Assign the the given attribute of each element in the context. The value is determined by calling the provided function with the data bound to the element and its index in the context.
In some situations where the value of the attribute you want to assign is syntactically a number or a string, the num
and str
helper functions will specialize the value parameter to those types, e.g.
num attr : String -> number -> Selection a
str attr : String -> String -> Selection a
# chain, (|.
), (<.>
) : Selection a -> Selection a -> Selection a
The method chaining operator. Performs the first operation with the current context and then uses the result as the context for the second operation.
This operation has three different that all do the same thing. One simply tends to look better than the other when used in some contexts.
# render : Int -> Int -> Selection a -> a -> Element
Turn a Selection into a value that the Elm runtime knows how to render. The first and second arguments are the width and height that should be allocated for this UI component by the Elm render. The third argument is the Selection that should be rendered, and the fourth argument is that data that should be bound to that Selection*.
When used without signal, this function will produce a static UI. However, if you are rendering a Selection M (where M is a concrete type) and you have a signal of type Signal M, then you can lift the render call to the signal to produce a reactive interface, like so:
main : Signal Element
main = (render 640 480 view) <~ signal
# Widget a b
The type of a D3 data bind in Elm. A data bind of type Widget a b will assume that its context will have data of type a bound to. The data bind knows how transform a value of type a into a list of values of type b. Any operations that use the Widget a b as a context must therefore be of type Selection b.
# chain', (|-
) : Widget a b -> Selection b -> Widget a b.
The equivalent of method chaining, but with a specialized, safe type for when method chaining on a data bind.
# bind, (|=
) : Selection a -> (a -> [b]) -> Widget a b
Create a data join based on the first argument and an array derived from the data bound to the context. Typically the first argument will be a Selection constructed from a selectAll
. The data that you wish to bind to the Selection is statically known, you can use a constant function that just returns the array of data you wish to use for the data join. For example, the following is perfectly valid:
selectAll "div"
|= (\_ -> [1, 2, 3])
# embed : Widget a b -> Selection a
Turn a Widget into a composable Selection. There is no way to render a Widget without using this function. After it's called, you can no longer chain operations below it. You can only chain the result to other Selection
s or render it directly.
-
Take the circles example from the top of the file and make it reactive. Use the x-component of the
Mouse.position
signal to only display the circles to the left of the mouse. use the y-component of theMouse.position
signal to scale the radii of the circles. -
Play around with transitions. Make the radius of a circle shrink down to
0
before removing it form the document. You'll need to use thetransition
selection as well as theattr
operator in order to accomplish this.