Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。

# 概要
TECH::EXPERTのカリキュラムでオリジナルのミニアプリを作成する機会があり、
同期で学習中の方がamChartsを使っていてかっこいいなぁ〜と思い、自分なりに調べてみました。
いくつか工夫したところがあったので共有します。
体重管理アプリのようなものを想定してます。
体重を計る日とそうでない日がマチマチにある場合のグラフを作成します。
例：
7月1日 70kg
7月2日 71kg
7月4日 73kg
7月7日 68kg
7月8日 69kg
7月9日 67kg
=>7月3日と7月６日の記録がない

##作成する前提
[amCharts](https://www.amcharts.com/)がCDNで読み込めている　
参考文献[amcharts 4 Demos を使ってグラフを作成](https://qiita.com/Ktym44977057/items/0766e4524f7cae1b8d5b)(amChartsの導入方法が記載されています）
使用するグラフの種類：[Line Chart with Scroll and Zoom](https://www.amcharts.com/demos/line-chart-with-scroll-and-zoom/)
[![Image from Gyazo](https://i.gyazo.com/6e709ec5f61410e3a1e671ce25b98a90.gif)](https://gyazo.com/6e709ec5f61410e3a1e671ce25b98a90)
diaryモデルにweightカラムがある
diaries_controler.rbにindexアクションが用意されてある


##編集するファイル
・コントローラーファイル
・ビューファイル
###コントローラーファイル
```ruby:diaries_controller.rb
class DiariesController < ApplicationController
  def index
    @diaries=Diary.all.order('created_at ASC')
    @weights=@diaries.map(&:weight)
    @dates=@diaries.map{|diary| diary.created_at.strftime('%Y/%m/%d') }
  end
end
```
今回は、weigthカラムの値ビューファイルに渡します。
配列で渡したいので上記のようにmapメソッドを使いました。
eachでは下記のように記載します。

```ruby:diaries_controller.rb
##eachの場合
@weights=[]
@diaries.each do |diary|
 @weigth << diary.weight
end
```
"&:"の記法は処理が1つの場合しか使えないので、@datesはデフォルトの記法です。

###ビューファイル
```erb:index.erb
<!-- Styles -->
<style>
#chartdiv {
  width: 100%;
  height: 500px;
}
</style>

<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/kelly.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>

<!-- Chart code -->
<script>
am4core.ready(function() {

// Themes begin
am4core.useTheme(am4themes_kelly);
am4core.useTheme(am4themes_animated);
// Themes end

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);

//JSON形式で値を渡す
const weights = <%== JSON.dump(@weights) %>;
const dates = <%== JSON.dump(@dates) %>;
//表示期間を計算
var firstDate = new Date(dates[0])
var lastDate = new Date(dates.slice(-1)[0])
var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1

// Add data
chart.data = generateChartData();

// Create axes
var dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.minGridDistance = 50;

var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());

// Create series
var series = chart.series.push(new am4charts.LineSeries());
series.dataFields.valueY = "weight";
series.dataFields.dateX = "date";
series.strokeWidth = 2;
series.minBulletDistance = 10;
series.tooltipText = "{valueY}";
series.tooltip.pointerOrientation = "vertical";
series.tooltip.background.cornerRadius = 20;
series.tooltip.background.fillOpacity = 0.5;
series.tooltip.label.padding(12,12,12,12)

// Add scrollbar
chart.scrollbarX = new am4charts.XYChartScrollbar();
chart.scrollbarX.series.push(series);

// Add cursor
chart.cursor = new am4charts.XYCursor();
chart.cursor.xAxis = dateAxis;
chart.cursor.snapToSeries = series;

//不連続な間隔(日付)で投稿された値を表示する
function generateChartData() {
  var chartData = [];
  for (var j =0; j< weights.length; j++){
    for (var i = 0; i < termDate; i++) {
      var newDate = new Date(firstDate)
      newDate.setDate(newDate.getDate() + i); //初日からi日分たす
      if ((new Date(dates[j])) - (newDate)==0){
        weight =weights[j]
        chartData.push({
            date: newDate,
            weight: weight
        });
      }
    }
  }
  return chartData;
}

}); // end am4core.ready()
</script>
<div id="chartdiv"></div>
```
基本はDemo sourceをコピペしてください。
[![Image from Gyazo](https://i.gyazo.com/0517efb54232d8a652ac9a476c19e776.png)](https://gyazo.com/0517efb54232d8a652ac9a476c19e776)
styleがビューファイルにありますが、本記事の趣旨とズレるのでそのままビューファイルに記載してます。
また、[Demo source](https://www.amcharts.com/demos/line-chart-with-scroll-and-zoom/)の"visits"は"weight"としてます。（単数形です..[Demo source](https://www.amcharts.com/demos/line-chart-with-scroll-and-zoom/)はなぜ複数形にしてるんだろう...?)
下記コピペ部分とは違うところの説明です。

**1.コントローラーの変数をJavascriptで使えるようにする**

```erb:index.erb
<script>
//JSON形式で値を渡す
const weights = <%== JSON.dump(@weights) %>;
const dates = <%== JSON.dump(@dates) %>;
</script>
```
JSONでコントローラーの値をわたします。
こちらは[railsのcontrollerからjavascriptに対して変数を渡す](https://qiita.com/Ktym44977057/items/49a8096ec3f06401c664)を参考にさせていただきました。

**２.表示する期間を算出する**

```erb:index.erb
<script>
//表示期間を計算
var firstDate = new Date(dates[0])　//一番最初
var lastDate = new Date(dates.slice(-1)[0])　//一番最後
var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1　//表示する期間
</script>
```
コントローラー側で配列にしたとき、

```diaries_controller.rb
@diaries=Diary.all.order('created_at ASC')
```
としてるので、昇順（日付が古い順)になってます。なので、容易に一番最初を最後を取得できますね。
termDateは期間を計算しました。日付の場合そのまま引き算をできないので、/ 1000 / 60 / 60 / 24としてます。
+1は、たとえば　3日から5日までの期間を出すために"5-3=2"では1日たりないので+1としてます。

**3.不連続な間隔(日付)で投稿された値を表示する**
[Demo source](https://www.amcharts.com/demos/line-chart-with-scroll-and-zoom/)だと下記のように表現されているところです。

```erb:Demo.erb
<script>
function generateChartData() {
    var chartData = [];
    var firstDate = new Date();
    firstDate.setDate(firstDate.getDate() - 1000);
    var visits = 1200;
    for (var i = 0; i < 500; i++) {
        // we create date objects here. In your data, you can have date strings
        // and then set format of your dates using chart.dataDateFormat property,
        // however when possible, use date objects, as this will speed up chart rendering.
        var newDate = new Date(firstDate);
        newDate.setDate(newDate.getDate() + i);
        
        visits += Math.round((Math.random()<0.5?1:-1)*Math.random()*10);

        chartData.push({
            date: newDate,
            visits: visits
        });
    }
    return chartData;
}
</script>
```
簡易的に説明するならば、初期値1200であとは、ランダムで数値をあれやこれやとかえて、
i=1から500まで表現してます。
これはこれで学習するところがあっておもしろかったです。
本記事ではコントローラーから取得した値を下記のようにしてます。

```erb:index.erb
<script>
//不連続な間隔(日付)で投稿された値を表示する
function generateChartData() {
  var chartData = [];
  for (var j =0; j< weights.length; j++){
    for (var i = 0; i < termDate; i++) {
      var newDate = new Date(firstDate)
      newDate.setDate(newDate.getDate() + i); //初日からi日分たす
      if ((new Date(dates[j])) - (newDate)==0){
        weight =weights[j]
        chartData.push({
            date: newDate,
            weight: weight
        });
      }
    }
  }
  return chartData;
}
</script>
```
[1]変数をinteger型の変数を２つ用意してます（i,j)
[2]jはweights.lengthの数まで繰り返します。つまり体重のレコードの数までですね。
[3]iをｊにネストしてます。こちらはtermDateの数だけ繰り返します。つまり、”２.表示する期間を算出する”で計算してあげた期間分繰り返します。
[4]表示するのは、あくまで、体重のレコードが存在する日付のみなので、jの日付（体重記録がある日付）とiの日付(newDate)の差が0のときのみ、日付と体重をハッシュの形にし、chartDataに入れてあげます。


#完成!!
[![Image from Gyazo](https://i.gyazo.com/d51b41b7b2c6647af358272eedbc374e.gif)](https://gyazo.com/d51b41b7b2c6647af358272eedbc374e)

gifでは"Jul 11"と"Jul 12"に記録がありません。

##参考文献
[amcharts 4 Demos を使ってグラフを作成](https://qiita.com/Ktym44977057/items/0766e4524f7cae1b8d5b)
[railsのcontrollerからjavascriptに対して変数を渡す](https://qiita.com/Ktym44977057/items/49a8096ec3f06401c664)