Elm logo
elm
examples

elm-visualization

/

examples

/

Concentric

Edit on Ellie

Concentric

Having fun with arcs and animations.

module Concentric exposing (main)


import Browser
import Browser.Events
import Color exposing (Color)
import Path
import Random
import Scale exposing (defaultBandConfig)
import Scale.Color
import Shape exposing (defaultPieConfig)
import TypedSvg exposing (g, rect, svg)
import TypedSvg.Attributes exposing (fill, transform, viewBox)
import TypedSvg.Attributes.InPx exposing (height, width)
import TypedSvg.Core exposing (Svg, text)
import TypedSvg.Types exposing (Paint(..), Transform(..))


type alias Model =
    { data : List (List Float), frame : Int }


type Msg
    = Generated (List (List Float))
    | Tick Int


w : Float
w =
    900


h : Float
h =
    450


arcWidth : Float
arcWidth =
    20


radius : Float
radius =
    (max w h / 2) + 50


scale =
    Scale.band { defaultBandConfig | paddingInner = 0.3 } ( arcWidth, radius ) (List.range 0 (floor (radius / arcWidth)))


colorScale =
    Scale.sequential Scale.Color.plasmaInterpolator ( 0, 1 )


getColor : Int -> Int -> Color
getColor ring value =
    let
        seed =
            Random.initialSeed ((ring + 1) * (value + 1))

        gen =
            Random.map (Scale.convert colorScale) (Random.float 0.2 0.7)

        ( val, _ ) =
            Random.step gen seed
    in
    val


tau =
    pi * 2


view : Model -> Svg msg
view model =
    svg [ viewBox 0 0 w h ]
        [ rect [ width w, height h, fill (Paint (Color.rgb255 50 50 50)) ] []
        , model.data
            |> List.indexedMap
                (\index data ->
                    if model.frame > index * 100 then
                        data
                            |> Shape.pie
                                { defaultPieConfig
                                    | innerRadius = Scale.convert scale index
                                    , outerRadius = Scale.convert scale index + Scale.bandwidth scale
                                    , padAngle = 0.03
                                    , cornerRadius = arcWidth
                                    , sortingFn = \_ _ -> EQ
                                    , startAngle = toFloat index
                                    , endAngle = toFloat index + tau
                                }
                            |> List.indexedMap
                                (\subIndex datum ->
                                    Path.element (Shape.arc datum)
                                        [ fill (Paint (getColor index subIndex))
                                        , transform [ Rotate (toFloat model.frame / 100 * alternaring index) 0 0 ]
                                        ]
                                )
                            |> g []

                    else
                        text ""
                )
            |> g
                [ transform [ Translate (w / 2) (h / 2) ]
                ]
        ]


alternaring v =
    toFloat ((modBy 2 v * 2) - 1)


dataGenerator =
    Random.list (floor (radius / arcWidth))
        (Random.int 2 10
            |> Random.andThen (\len -> Random.list len (Random.float 0.1 5))
        )


main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }


init : () -> ( Model, Cmd Msg )
init () =
    ( { data = []
      , frame = 0
      }
    , Random.generate Generated dataGenerator
    )


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Generated data ->
            ( { model | data = data }, Cmd.none )

        Tick i ->
            ( { model | frame = model.frame + i }, Cmd.none )


subscriptions : Model -> Sub Msg
subscriptions _ =
    Browser.Events.onAnimationFrameDelta (round >> Tick)