Elm: アナログ時計のsampleで現在時刻を表示させる(SVGの練習)

[Elm Advent Calendar 2016](http://qiita.com/advent-calendar/2016/elm)　7日目の記事です

# Elmのexample/timeを改変

Elmのexample codeにtimeがあるが
[examples/time](http://elm-lang.org/examples/time)

これは実際の時間を表示しているわけではなくて1秒きざみに秒針が動いてるだけみたい

ので、練習がてら実際の時間を表示させてみる(長針、短針、秒針を作る)のと

どのような流れで作ったか(無理やり画面に表示してデバッグなど)、自分のような初学者の助けになればと思って記載します

作ったもの
[ElmAnalogClock](https://yoheikoga.github.io/websitetest/elm-analog-clock/)

## 環境と最終的なコード

Elmは2016年11月に出た最新版の0.18で試しています

今回作った最終的なコードは以下になります
([github](https://github.com/YoheiKoga/ElmAnalogClockTimeNow)にも置いておきます)

`elm-lang/svg`が必要になるのでpackageをインストールしておくこと

```hs:TimeTest.elm
module TimeTest exposing (..)

import Html exposing (..)
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Time exposing (..)
import Date exposing (..)
import Task


main =
    Html.program
        { view = view
        , init = init
        , subscriptions = subscriptions
        , update = update
        }



-- MODEL


type alias Model =
    Time


init : ( Model, Cmd Msg )
init =
    ( 0, now )



-- UPDATE


type Msg
    = SetTime Time


update : Msg -> Model -> ( Model, Cmd Msg )
update (SetTime time) _ =
    ( time, Cmd.none )

-- Elm公式サイトのtimeのExampleの書き方に従うなら下記でもよい
-- update msg model =
--  case msg of
--    SetTime newTime ->
--      (newTime, Cmd.none)


now : Cmd Msg
now =
    Task.perform SetTime Time.now



-- SUBSCRIPTION


subscriptions : Model -> Sub Msg
subscriptions model =
    Time.every Time.second SetTime



-- VIEW

view : Model -> Html Msg
view model =
    let
        secondNow =
            Date.second <| Date.fromTime <| model

        minuteNow =
            Date.minute <| Date.fromTime <| model

        hourNow =
            (Date.hour <| Date.fromTime <| model) % 12

        angleSecond =
            toFloat <| 6 * secondNow

        angleMinute =
            toFloat <| 6 * minuteNow

        angleHour =
            -- 下記は(toFloat <| 30 * hourNow) + (angleMinute / 12) と同じ意味
            (+) (angleMinute / 12) <| (toFloat <| 30 * hourNow)

        handSecondX =
            toString (50 + 38 * sin (angleSecond * pi / 180))

        handSecondY =
            toString (50 - 38 * cos (angleSecond * pi / 180))

        handMinuteX =
            toString (50 + 32 * sin (angleMinute * pi / 180))

        handMinuteY =
            toString (50 - 32 * cos (angleMinute * pi / 180))

        handHourX =
            toString (50 + 25 * sin (angleHour * pi / 180))

        handHourY =
            toString (50 - 25 * cos (angleHour * pi / 180))
    in
        div []
            [ svg [ viewBox "0 0 100 100", width "300px" ]
                [ circle [ cx "50", cy "50", r "45", fill "#2a2a2a" ] []
                , circle [ cx "50", cy "50", r "40", fill "#f6f6f6" ] []
                , line [ x1 "50", y1 "50", x2 handSecondX, y2 handSecondY, stroke "#2a2a2a", strokeWidth "0.5" ] []
                , line [ x1 "50", y1 "50", x2 handMinuteX, y2 handMinuteY, stroke "#2a2a2a", strokeWidth "1" ] []
                , line [ x1 "50", y1 "50", x2 handHourX, y2 handHourY, stroke "#2a2a2a", strokeWidth "2" ] []
                ]
            -- 以下は時間のデバッグ用に表示
            , div [] [ Html.text ("model(UNIX time): " ++ (toString model)) ]
            , div [] [ Html.text ("Date.fromTime model: " ++ (toString (Date.fromTime model))) ]
            ]

```

## Javascriptではどうやって作るか

JSだとCanvasを使って作れるみたいで

下記あたりが参考になりそう

[HTML5 の Canvas でアナログ時計を作ってみた](http://qiita.com/okuchan/items/3c6908c5c90e5a70deab)

[【JavaScript】canvasでアナログ時計](https://www.softel.co.jp/blogs/tech/archives/3914)

[アナログ時計をHTML5のCanvasとJavaScriptで作ってみたAdd Star](http://d.hatena.ne.jp/senritsuki/20120804/p1)


## まずは時計の下地となる円盤を作ってみる

時計の円盤を作るだけなら、viewで表示してやるだけでよい

ただし、今回のように描画してやる場合はSVGが便利なので

導入するために`elm package install`しておく

```
$ elm package install elm-lang/svg -y
```

で、実際円盤を描画するには下記のようにする

```hs
import Svg exposing (svg, circle)
import Svg.Attributes exposing (viewBox, width, cx, cy, r, fill)

main =
  view


view = 
  svg [viewBox "0 0 100 100", width "300px"]
    -- 後に設定した円が上書きされるので、内側の円(#f6f6f6)は外側(#2a2a2a)より後に描画する
    [ circle[cx "50", cy "50", r "45", fill "#2a2a2a"][]
    , circle[cx "50", cy "50", r "40", fill "#f6f6f6"][]
    ]
```

`import Svg exposing (svg, circle)` はviewでsvg, 円を描く circle を使うのに必要になり

`import Svg.Attributes exposing (viewBox, width, cx, cy, r, fill)` はviewBoxなどパラメータに必要

例えば、後述する針を描きたい時は`line`を使うのでSvgをexposingする時に`line`を追加してやることになるし

線の太さ(strokeWidth)を変える場合は、Svg.Attributesをexposingする時に`strokeWidth`を追加してやることになる

ひとまずこれで、下記画像のように縁が黒くて中が薄灰色の円盤が表示される

![スクリーンショット 2016-12-02 12.01.15.jpg](https://qiita-image-store.s3.amazonaws.com/0/44492/174e2fe9-0d27-da99-2832-0105ecd61605.jpeg)


svgで何が表現できるかは、公式のpackageの関数一覧が参考になります

[Svg](http://package.elm-lang.org/packages/elm-lang/svg/2.0.0/Svg)

[Svg.Attributes](http://package.elm-lang.org/packages/elm-lang/svg/2.0.0/Svg-Attributes)



が、Svgの細かいところ(線の太さ変えたい時とか)は、Elmのpackageで使われてる名前と

svgの関数名が若干異なってるところもあるみたいで

例えば、Elmは関数名にハイフンが使えないみたいで、[SVGでは`stroke-width`と記述する](https://triple-underscore.github.io/SVG11/shapes.html#LineElement
)のところが
Elmだと`strokeWidth`になります

<br>

まずはsvgの公式でどんなことができるか確認して

[MDN SVG](https://developer.mozilla.org/ja/docs/Web/SVG)

同じ関数はどれかElmのSVGのほうで確認するといいかと思います

またこれ以降この記事では、exposingする時に記述量が多くなってしまうのですべて`exposing (..)`にしてしまいます

(今回は名前の衝突が無さそうなのでこれでいける)


## 針を追加する

次に、針を追加してみる

viewの部分に以下のようにlineに適当なパラメータを追加してやればよい

```hs
import Svg exposing (..)
import Svg.Attributes exposing (..)

main =
  view


view = 
  svg [viewBox "0 0 100 100", width "300px"]
    -- 後に設定した円が上書きされるので、内側の円(#f6f6f6)は(#2a2a2a)より後に描画する
    [ circle[cx "50", cy "50", r "45", fill "#2a2a2a"][]
    , circle[cx "50", cy "50", r "40", fill "#f6f6f6"][]

    -- 針となるlineを追加
    , line [ x1 "50", y1 "50", x2 "40", y2 "15", stroke "#2a2a2a", strokeWidth "0.5" ] []
    ]
```

これはsvgのことになるので詳細は省くが

**座標(x1, x2)と(x2, y2)を結ぶ線を、線の色"#2a2a2a", 線の太さ"0.5"**でlineを引くという意味である


これで円盤に線を記述することができた

![スクリーンショット 2016-12-02 12.11.46.jpg](https://qiita-image-store.s3.amazonaws.com/0/44492/a4ee0af1-2e1a-6e16-2b30-a54c4af750b1.jpeg)



## 針に現在時刻を反映させる


### そもそも現在時刻はどのように取得したらいいのか？

下記stack overflowを参考にした
[How do I get the current date in elm?](http://stackoverflow.com/questions/37910613/how-do-i-get-the-current-date-in-elm)


とりあえず現時刻のUNIX時間を取得するコードは下記になる

UNIX時間については -> [wikipedia](https://ja.wikipedia.org/wiki/UNIX%E6%99%82%E9%96%93)

```hs
import Html exposing (..)
import Time exposing (Time)
import Task


main : Program Never (Maybe Time) Msg
main =
  Html.program 
    { init = ( Nothing, now ) 
    , view = view
    , subscriptions = always Sub.none 
    , update = update
    }


-- MODEL

type alias Model = 
  Maybe Time


type Msg = 
  SetTime (Maybe Time)


-- UPDATE

update : Msg -> Model -> (Model, Cmd Msg)
update (SetTime time) _ = 
  (time, Cmd.none)


-- VIEW


view : Model -> Html Msg
view model =
  div [] [ text <| "(Optional) time at program launch was " ++ toString model ]


now : Cmd Msg
now = 
  Task.perform  (Just >> SetTime) Time.now

```

これで、プログラムにアクセスした時間のUNIXtimeが

`(Optional) time at program launch was Just 1480648806651`みたいな形で表示されるはず

<br>

上記例だとMaybeで取得されるので単純な時間を表示させるだけなら下記のようになる

```hs
import Html exposing (..)
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Time exposing (..)
import Date exposing (..)
import Task

main =
  Html.program
    { view = view
    , init = init
    , subscriptions = always Sub.none
    , update = update
    }

-- MODEL
type alias Model =
  Time

init =
  (0, now)

-- UPDATE

type Msg =
  SetTime Time

update : Msg -> Model -> (Model, Cmd Msg)
update (SetTime time) _ =
  (time, Cmd.none)

now : Cmd Msg
now =
  Task.perform SetTime Time.now

-- VIEW

view : Model -> Html Msg
view model =
  div [] [Html.text ("model(UNIX time): " ++ (toString model))]    
```


このあたり、自分もまだよくわかっていないのだが

TimeやDateを取得する場合ElmはTaskを使って非同期処理をするため、Taskが必要となり

上記のようなコードになるみたい(もっと簡単にできるのかはすみませんわからなかったです)

ひとまず、これで時間を取得することはできるようになったので

次に針に時間を反映させてみる


## 秒針を表示させるプログラム

下記コードは、現在時刻から秒針だけを表示するコードである

```hs
import Html exposing (..)
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Time exposing (..)
import Date exposing (..)
import Task


main =
  Html.program
    { view = view
    , init = init
    , subscriptions = always Sub.none
    , update = update
    }

-- MODEL
type alias Model =
  Time

init =
  (0, now)

-- UPDATE

type Msg =
  SetTime Time

update : Msg -> Model -> (Model, Cmd Msg)
update (SetTime time) _ =
  (time, Cmd.none)

now : Cmd Msg
now =
  Task.perform SetTime Time.now

-- VIEW


view : Model -> Html Msg
view model =

  let
    -- 現在の秒を取得
    secondNow =
      Date.second <| Date.fromTime <| model

    -- 現在の秒から、アナログ時計にするために角度を出す
    angleSecond =
      toFloat <| 6 * secondNow

    handSecondX =
      toString (50 + 35 * sin (angleSecond * pi / 180))
    handSecondY =
      toString (50 - 35 * cos (angleSecond * pi / 180))
  in
    div []
        [ svg [ viewBox "0 0 100 100", width "300px" ]
            [ circle [ cx "50", cy "50", r "45", fill "#2a2a2a" ] []
            , circle [ cx "50", cy "50", r "40", fill "#f6f6f6" ] []
            , line [x1 "50", y1 "50", x2 handSecondX, y2 handSecondY, stroke "#2a2a2a"][]
            ]
        ]
```


注目すべきはviewのところで、今コード時計の針の角度を得るために

1. modelはUNIX時間のことである
1. UNIX時間から現在の秒数にするには、`Date.fromTime`関数でmodelから現在日時に変換する
1. さらに、`Date.second`関数で現在日時から秒だけを取り出す(変換する)
1. 1秒間に6度角度が変化するので、現在の秒数に6をかけてさらにFloat型にしたものが、秒針の角度になる

としている。

得られた角度を、sinとcosの関数を使ってやれば時計の針が表現できて上にようなコードになる

(秒針の角度からアナログ時計の動きを作る方法はjavascriptの話になるのでjavascriptでどうやってアナログ時計を表現するかのサンプルを参考にされてください)


### デバッグのためにmodelなどを画面に出力させてやる

このあたりで、「modelって何が入ってるんだろ」とか「関数使ったらどんな出力結果になるのか」というのが

想像しにくかったので、自分は`view`の部分をいじって以下のようにデバッグしてあげてます

(デバッグしてやるために、svgをdivで囲ってあげてる)

```hs
-- VIEW

view : Model -> Html Msg
view model =

  let
    -- 現在の秒を取得
    secondNow =
      Date.second <| Date.fromTime <| model

    -- 現在の秒から、アナログ時計にするために角度を出す
    angleSecond =
      toFloat <| 6 * secondNow

    handSecondX =
      toString (50 + 35 * sin (angleSecond * pi / 180))
    handSecondY =
      toString (50 - 35 * cos (angleSecond * pi / 180))
  in
    div []
        [ svg [ viewBox "0 0 100 100", width "300px" ]
            [ circle [ cx "50", cy "50", r "45", fill "#2a2a2a" ] []
            , circle [ cx "50", cy "50", r "40", fill "#f6f6f6" ] []
            , line [x1 "50", y1 "50", x2 handSecondX, y2 handSecondY, stroke "#2a2a2a"][]
            ]
        -- 下記3つのdivはデバッグ用, modelとmodelに関数を適用した時の出力を確認
        , div [][Html.text (toString model)]
        , div [][Html.text (toString (Date.fromTime <| model))]
        , div [][Html.text (toString (Date.second <| Date.fromTime <| model))]
        ]
```

viewで何が出力されてるかとか、letで束縛されてる変数の中身とか途中の状態とか確認したい時

今のところ自分は上のような方法でやってます


### 長針と短針も追加する

以上を踏まえて、時間、分、秒を表示するプログラムは、viewの部分を以下のようにしてやればよい

```hs
-- VIEW


view : Model -> Html Msg
view model =

  let
    secondNow =
      Date.second <| Date.fromTime <| model
    minuteNow = 
      Date.minute <| Date.fromTime <| model
    hourNow =
      (Date.hour <| Date.fromTime <| model) % 12

    angleSecond =
      toFloat <| 6 * (Date.second <| Date.fromTime <| model)
    angleMinute =
      toFloat <| 6 * (Date.minute <| Date.fromTime <| model)
    angleHour =
      (toFloat <| 30 * ((Date.hour <| Date.fromTime <| model) % 12)) + angleMinute / 12


    handSecondX =
      toString (50 + 35 * sin (angleSecond * pi / 180))
    handSecondY =
      toString (50 - 35 * cos (angleSecond * pi / 180))

    handMinuteX =
      toString (50 + 35 * sin (angleMinute * pi / 180))
    handMinuteY =
      toString (50 - 35 * cos (angleMinute * pi / 180))

    handHourX =
      toString (50 + 25 * sin (angleHour * pi / 180))
    handHourY =
      toString (50 - 25 * cos (angleHour * pi / 180))


  in
    div []
        [ svg [ viewBox "0 0 100 100", width "300px" ]
            [ circle [ cx "50", cy "50", r "45", fill "#2a2a2a" ] []
            , circle [ cx "50", cy "50", r "40", fill "#f6f6f6" ] []
            , line [x1 "50", y1 "50", x2 handSecondX, y2 handSecondY, stroke "#2a2a2a", strokeWidth "0.5"][]
            , line [x1 "50", y1 "50", x2 handMinuteX, y2 handMinuteY, stroke "#2a2a2a", strokeWidth "1"][]
            , line [x1 "50", y1 "50", x2 handHourX, y2 handHourY, stroke "#2a2a2a", strokeWidth "2"][]
            ]
        , div [] [Html.text ("model(UNIX time): "        ++ (toString model))]
        , div [] [Html.text ("Date.fromTime model: "     ++ (toString (Date.fromTime model)))]
        ]
```

ちょっと冗長に書いています(本当はviewのところでsecondNowとか準備せず一気にやってよい)

ただ、このままだと逐次更新はされない(アクセスした時の時間だけ表示される)

更新されるように`update`をいじる

## 時計がアップデートされるようにする

といってもアップデートは簡単で、いままで`subscription = always Sub.none`としていた部分を

以下のようにしてやればよい(mainを書き換えて subscriptionの記述を追加する)


```hs
...

main =
    Html.program
        { view = view
        , init = init
        , subscriptions = subscriptions
        , update = update
        }

...


-- SUBSCRIPTION


subscriptions : Model -> Sub Msg
subscriptions model =
    Time.every Time.second SetTime
```

これで現在時刻で動くアナログ時計が描画できました

最終的なコードは一番上に書いてある通りです

Elmは色々いじってみると楽しそうな言語なので

まずはsampleを改変して遊んで見るのがいいかと思います

(時間の取得方法はわかったので、デジタル時計とかも作れそう)