Skip to content

rintcius/elm-d3

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

81 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

elm-d3

elm-d3 provides Elm bindings for d3.js. It enables you create type-safe, composable widgets using HTML, SVG, and CSS. D3 acts as a conceptual basis for the library, as well as an alternative renderer for Elm.

Installation

First make sure that you have node.js installed, as well as the Elm compiler. Once you've installed those dependencies, clone the elm-d3 repository and run the following commands from the root directory:

npm install smash
make

This will locally install the smash utility and build three files in the root directory of the elm-d3 repository. They'll come in handy later.

  • elm-d3.library.js: contains all compiled code;
  • elm-d3.runtime.js: contains supporting, non-compiled JavaScript code; and
  • elm-d3.js: the previous two files, concatenated.

To get an example compiled and running in your browser, use the following commands:

elm --make --src-dir=src `./scripts/build-flags` examples/Circles.elm
open build/examples/Circles.html

If you're not using OS X, the last command won't work. In that case open build/examples/Circles.html however you normally would in your operating system. Once the page is open, move your mouse left and right to add and remove circles, and move your mouse up and down to change their brightness.

Usage

At the heart of this library is the Selection a type. It represents a d3 selection joined with one or more values of type a. Just like in d3, you can construct selections using select and selectAll, and perform operations on selections using functions like attr and style. You can also chain those operations together using the chain function or the infix operator |.. Both do the same thing—in essence, method chaining.

In addition, elm-d3 allows you to bind data to a Selection a and specify what should happen when you add, update, or remove data from that selection. You do this by using the bind function, or the |= infix operator, which takes a Selection a as its first argument, a function a -> [b] as its second, and produces a Widget a b. A Widget a b is in essence a Selection b that can be nested in a Selection a in a type-safe way. However, in order to chain Selection bs onto a Widget a b, you have to use the |- operator. This behaves a bit diferently than |. as it returns the original Widget a b rather than than Selection b that is its second argument. You can see an example of this below.

To use a Widget a b, you must apply the embed function to it, which will turn it into a Selection a.

Creating a Selection a doesn't actually draw anything on the screen. Think of it more along the lines of a reusable chart that you can build up incrementally. In order to render a Selection a, you have to pass it to the render function somewhere within the main entry point of your Elm program.

Example

Here's an example of using elm-d3 to draw two rectangles. One follows the x-component of your mouse position, and the other follows y-component of your mouse position. You can find this example in the examples directory of this repository.

module Boxes where

import D3(..)
import Mouse

size   = 300
margin = { top = 10, left = 10, right = 10, bottom = 10 }
dims   = { height = size - margin.top - margin.bottom
         , width  = size - margin.left - margin.right }

type Dimensions = { height : Float, width : Float }
type Margins = { top : Float, left : Float, right : Float, bottom : Float }

svg : Dimensions -> Margins -> Selection 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)

boxes : Widget (number, number) (number, number, String)
boxes =
  selectAll ".box"
  |= (\(x, y) -> [(x, 0, "cyan"), (0, y, "magenta")])
     |- enter <.> append "rect"
        |. str attr "class" "box"
        |. num attr "width"  100
        |. num attr "height" 100
        |. attr     "fill"   (\(_, _, c) _ -> c)
     |- update
        |. attr "x" (\(x, _, _) _ -> show x)
        |. attr "y" (\(_, y, _) _ -> show y)
     |- exit
        |. remove

translate : number -> number -> String
translate x y = "translate(" ++ (show x) ++ "," ++ (show y) ++ ")"

vis dims margin =
  svg dims margin
  |. embed boxes

main : Signal Element
main = render dims.height dims.width (vis dims margin) <~ Mouse.position

It's common practice when using d3 to start building your svg document with an initial group child element with a transform attribute to setup margins. Pass the svg function a Dimensions record and a Margins record and will return a Selection a that, when rendered, will do just that.

The boxes selection demonstrates how to do data joins in elm-d3. You setup your initial selection using selectAll, and then method chain a call to bind, which is like calling .data() in JavaScript. bind's first argument is a function that will take the data bound to the context and transform it into a list values&dmash;in this case tuples of type (number, number, String). This list of tuples is joined to the selection. The next three arguments are Selection (number, number, String)s that are applied to the enter, update, and exit selections, respectively.

Since elm is statically typed, you have to cast values manually. For example, attr will set the value of an attribute on a per-element basis, which means you have to pass it a function that will take the data bound to the element as well as its index, and produce a string. (All attributes on DOM elements are strings, after all.)

attr : String -> (a -> Int -> String) -> Selection a

If however, you want to set the value of an attribute to a constant number for every element in the selection, you can't pass that number directly to attr. You either have to wrap it in a function:

attr "height" (\_ _ -> 100)

Or you can use the num helper function before your application of attr

num attr "height" 100

There's also a str function that serves a similar purpose, except for string constants.

render actually draws Selection a to the screen. Its first two arguments are the height and width of the drawing area. The third is the Selection a that will be renderd. The final argument is the datum of type a that will be bound to the selection that will be rendered. In this case, we're getting or datum from a signal of mouse positions. Whenever the mouse position updates, the Elm runtime will automatically update screen to reflect those changes.

Further documentation

For more information about the API, the source file in src/D3.elm. Each function is preceded by a comment describing the equivalent expression in JavaScript. Types are also very instructive.

License

BSD3, see LICENSE file for its text.

About

Elm bindings for d3.js

Resources

License

Stars

Watchers

Forks

Packages

No packages published