FileMaker + amCharts で BarChartRace、試しに五大商社の株価推移を見る

# はじめに
## 対象読者
* [Claris FileMaker](https://www.claris.com/ja/filemaker/pro/) ユーザ

https://www.claris.com/ja/filemaker/pro/

* FileMaker に興味がある人
* BarChartRace を手軽に作りたい人
* 次の前置きを要参照

## 前置き
* 以下のイベントへの参加記事です
  * [Qiita Engineer Festa 2022](https://qiita.com/official-campaigns/engineer-festa/2022)

https://qiita.com/official-campaigns/engineer-festa/2022

  * [Claris FileMaker で作った App を JavaScript で拡張したらどうなる？！](https://qiita.com/official-events/0005a02478acfc50dd13)

https://qiita.com/official-events/0005a02478acfc50dd13

## 環境

## what is amCharts
* [公式サイト](https://www.amcharts.com/javascript-charts/)
  * 以下 OG title だと `amCharts 4` となっているけれど、現行の最新版は `amCharts 5`がリリースされたところです ( 2022/07 時点 )

https://www.amcharts.com/javascript-charts/

* グラフ／チャート系が充実した JavaScript ライブラリ
* ロゴつきなら無料で使える

> You can download and use all amCharts 5 products for free. The only limitation of the free version is that a small amCharts logo will be displayed in the corner of your charts.

## what is BarChartRace
* Bar Chart Race / バーチャートレース
* 横棒グラフが時間軸に沿って動的に変化していくアニメーション
* [amCharts 公式サイトにおけるデモ](https://www.amcharts.com/demos/bar-chart-race/)

https://www.amcharts.com/demos/bar-chart-race/

* 次の項も参照

## 最終的に出来上がるモノ
* 以下のような感じ

<a href="https://gyazo.com/83135dd146e8a36d5e9cd776c831cdd3"><img src="https://i.gyazo.com/83135dd146e8a36d5e9cd776c831cdd3.gif" alt="Image from Gyazo" width="1545"/></a>

# 下準備その１
## ファイル作成
* `FileMaker-BarChartRace.fmp12`

## 表示用テーブル作成
* `races`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/48e6148c-5003-d1fe-159d-d6fbd794611c.png)


## フィールド作成
* `id` : 数字
* `g_for_webview` : テキスト、グローバル

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/40635ea5-af01-5f45-3d31-e4d521ddfe3e.png)


## スクリプト作成
### set_global_var.amcharts_tags
* 今回挿入する amCharts のタグをグローバル変数として定義しておく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/ce89a9da-d7b7-0c6b-1612-dc9010272b84.png)

```html
"<script src=\"https://cdn.amcharts.com/lib/5/index.js\"></script>" & ¶ &
"<script src=\"https://cdn.amcharts.com/lib/5/xy.js\"></script>" & ¶ &
"<script src=\"https://cdn.amcharts.com/lib/5/themes/Animated.js\"></script>"
```

### set_global_var.style_tags
* 今回挿入する CSS style のタグをグローバル変数として定義しておく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/143b2274-9d2a-1484-cea8-0976a908e3ec.png)

```html
"<style>
#chartdiv {
  height: 500px;
  width: 100%;
}
</style>"
```

### open
* スクリプトトリガで `OnLayoutEnter`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/6d836d9c-6231-68d1-c73a-db7034598ce0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/dbaf6836-f470-cfc9-5871-f77d9f150120.png)


## レイアウト作成
### レイアウト全体
* 最低限以下のように配置

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/999054e9-5a6a-2940-6d78-aa2789acf26a.png)


### WEB ビューア
* 以下のような計算式で

```java
Let (
  [
    amcharts_tags = $$amcharts_tags;
    style_tags = $$style_tags;

    result = races::g_for_webview;
    result = Substitute ( result ; "[[[amcharts_tags]]]" ; amcharts_tags );
    result = Substitute ( result ; "[[[style_tags]]]" ; style_tags )
  ];

  result
)
```

## g_for_webview への入力
* HTML テンプレートを `g_for_webview` フィールドへ入力

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
</body>
</html>
```

## デモからコードを転記して動作確認
### 転記
* [公式サイトのデモページ](https://www.amcharts.com/demos/bar-chart-race/
) に書かれている以下のコードを `<body></body>` の間に挿入

https://www.amcharts.com/demos/bar-chart-race/

* その際、以下の箇所は `[[[style_tags]]]` と `[[[amcharts_tags]]]` とに書き換えておく
  * グローバル変数を呼び出すため

```html
<!-- Styles -->
<style>
#chartdiv {
  width: 100%;
  height: 500px;
}
</style>

<!-- Resources -->
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
```

* 長いけれどひとまず全部載せておく……

```html
[[[style_tags]]]
[[[amcharts_tags]]]

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

// Data
var allData = {
  "2002": {
    "Friendster": 0,
    "Facebook": 0,
    "Flickr": 0,
    "Google Buzz": 0,
    "Google+": 0,
    "Hi5": 0,
    "Instagram": 0,
    "MySpace": 0,
    "Orkut": 0,
    "Pinterest": 0,
    "Reddit": 0,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 0,
    "WeChat": 0,
    "Weibo": 0,
    "Whatsapp": 0,
    "YouTube": 0
  },
  "2003": {
    "Friendster": 4470000,
    "Facebook": 0,
    "Flickr": 0,
    "Google Buzz": 0,
    "Google+": 0,
    "Hi5": 0,
    "Instagram": 0,
    "MySpace": 0,
    "Orkut": 0,
    "Pinterest": 0,
    "Reddit": 0,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 0,
    "WeChat": 0,
    "Weibo": 0,
    "Whatsapp": 0,
    "YouTube": 0
  },
  "2004": {
    "Friendster": 5970054,
    "Facebook": 0,
    "Flickr": 3675135,
    "Google Buzz": 0,
    "Google+": 0,
    "Hi5": 0,
    "Instagram": 0,
    "MySpace": 980036,
    "Orkut": 4900180,
    "Pinterest": 0,
    "Reddit": 0,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 0,
    "WeChat": 0,
    "Weibo": 0,
    "Whatsapp": 0,
    "YouTube": 0
  },
  "2005": {
    "Friendster": 7459742,
    "Facebook": 0,
    "Flickr": 7399354,
    "Google Buzz": 0,
    "Google+": 0,
    "Hi5": 9731610,
    "Instagram": 0,
    "MySpace": 19490059,
    "Orkut": 9865805,
    "Pinterest": 0,
    "Reddit": 0,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 0,
    "WeChat": 0,
    "Weibo": 0,
    "Whatsapp": 0,
    "YouTube": 1946322
  },
  "2006": {
    "Friendster": 8989854,
    "Facebook": 0,
    "Flickr": 14949270,
    "Google Buzz": 0,
    "Google+": 0,
    "Hi5": 19932360,
    "Instagram": 0,
    "MySpace": 54763260,
    "Orkut": 14966180,
    "Pinterest": 0,
    "Reddit": 248309,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 0,
    "WeChat": 0,
    "Weibo": 0,
    "Whatsapp": 0,
    "YouTube": 19878248
  },
  "2007": {
    "Friendster": 24253200,
    "Facebook": 0,
    "Flickr": 29299875,
    "Google Buzz": 0,
    "Google+": 0,
    "Hi5": 29533250,
    "Instagram": 0,
    "MySpace": 69299875,
    "Orkut": 26916562,
    "Pinterest": 0,
    "Reddit": 488331,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 0,
    "WeChat": 0,
    "Weibo": 0,
    "Whatsapp": 0,
    "YouTube": 143932250
  },
  "2008": {
    "Friendster": 51008911,
    "Facebook": 100000000,
    "Flickr": 30000000,
    "Google Buzz": 0,
    "Google+": 0,
    "Hi5": 55045618,
    "Instagram": 0,
    "MySpace": 72408233,
    "Orkut": 44357628,
    "Pinterest": 0,
    "Reddit": 1944940,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 0,
    "WeChat": 0,
    "Weibo": 0,
    "Whatsapp": 0,
    "YouTube": 294493950
  },
  "2009": {
    "Friendster": 28804331,
    "Facebook": 276000000,
    "Flickr": 41834525,
    "Google Buzz": 0,
    "Google+": 0,
    "Hi5": 57893524,
    "Instagram": 0,
    "MySpace": 70133095,
    "Orkut": 47366905,
    "Pinterest": 0,
    "Reddit": 3893524,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 0,
    "WeChat": 0,
    "Weibo": 0,
    "Whatsapp": 0,
    "YouTube": 413611440
  },
  "2010": {
    "Friendster": 0,
    "Facebook": 517750000,
    "Flickr": 54708063,
    "Google Buzz": 166029650,
    "Google+": 0,
    "Hi5": 59953290,
    "Instagram": 0,
    "MySpace": 68046710,
    "Orkut": 49941613,
    "Pinterest": 0,
    "Reddit": 0,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 43250000,
    "WeChat": 0,
    "Weibo": 19532900,
    "Whatsapp": 0,
    "YouTube": 480551990
  },
  "2011": {
    "Friendster": 0,
    "Facebook": 766000000,
    "Flickr": 66954600,
    "Google Buzz": 170000000,
    "Google+": 0,
    "Hi5": 46610848,
    "Instagram": 0,
    "MySpace": 46003536,
    "Orkut": 47609080,
    "Pinterest": 0,
    "Reddit": 0,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 92750000,
    "WeChat": 47818400,
    "Weibo": 48691040,
    "Whatsapp": 0,
    "YouTube": 642669824
  },
  "2012": {
    "Friendster": 0,
    "Facebook": 979750000,
    "Flickr": 79664888,
    "Google Buzz": 170000000,
    "Google+": 107319100,
    "Hi5": 0,
    "Instagram": 0,
    "MySpace": 0,
    "Orkut": 45067022,
    "Pinterest": 0,
    "Reddit": 0,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 146890156,
    "Twitter": 160250000,
    "WeChat": 118123370,
    "Weibo": 79195730,
    "Whatsapp": 0,
    "YouTube": 844638200
  },
  "2013": {
    "Friendster": 0,
    "Facebook": 1170500000,
    "Flickr": 80000000,
    "Google Buzz": 170000000,
    "Google+": 205654700,
    "Hi5": 0,
    "Instagram": 117500000,
    "MySpace": 0,
    "Orkut": 0,
    "Pinterest": 0,
    "Reddit": 0,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 293482050,
    "Twitter": 223675000,
    "WeChat": 196523760,
    "Weibo": 118261880,
    "Whatsapp": 300000000,
    "YouTube": 1065223075
  },
  "2014": {
    "Friendster": 0,
    "Facebook": 1334000000,
    "Flickr": 0,
    "Google Buzz": 170000000,
    "Google+": 254859015,
    "Hi5": 0,
    "Instagram": 250000000,
    "MySpace": 0,
    "Orkut": 0,
    "Pinterest": 0,
    "Reddit": 135786956,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 388721163,
    "Twitter": 223675000,
    "WeChat": 444232415,
    "Weibo": 154890345,
    "Whatsapp": 498750000,
    "YouTube": 1249451725
  },
  "2015": {
    "Friendster": 0,
    "Facebook": 1516750000,
    "Flickr": 0,
    "Google Buzz": 170000000,
    "Google+": 298950015,
    "Hi5": 0,
    "Instagram": 400000000,
    "MySpace": 0,
    "Orkut": 0,
    "Pinterest": 0,
    "Reddit": 163346676,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 475923363,
    "Twitter": 304500000,
    "WeChat": 660843407,
    "Weibo": 208716685,
    "Whatsapp": 800000000,
    "YouTube": 1328133360
  },
  "2016": {
    "Friendster": 0,
    "Facebook": 1753500000,
    "Flickr": 0,
    "Google Buzz": 0,
    "Google+": 398648000,
    "Hi5": 0,
    "Instagram": 550000000,
    "MySpace": 0,
    "Orkut": 0,
    "Pinterest": 143250000,
    "Reddit": 238972480,
    "Snapchat": 238648000,
    "TikTok": 0,
    "Tumblr": 565796720,
    "Twitter": 314500000,
    "WeChat": 847512320,
    "Weibo": 281026560,
    "Whatsapp": 1000000000,
    "YouTube": 1399053600
  },
  "2017": {
    "Friendster": 0,
    "Facebook": 2035750000,
    "Flickr": 0,
    "Google Buzz": 0,
    "Google+": 495657000,
    "Hi5": 0,
    "Instagram": 750000000,
    "MySpace": 0,
    "Orkut": 0,
    "Pinterest": 195000000,
    "Reddit": 297394200,
    "Snapchat": 0,
    "TikTok": 239142500,
    "Tumblr": 593783960,
    "Twitter": 328250000,
    "WeChat": 921742750,
    "Weibo": 357569030,
    "Whatsapp": 1333333333,
    "YouTube": 1495657000
  },
  "2018": {
    "Friendster": 0,
    "Facebook": 2255250000,
    "Flickr": 0,
    "Google Buzz": 0,
    "Google+": 430000000,
    "Hi5": 0,
    "Instagram": 1000000000,
    "MySpace": 0,
    "Orkut": 0,
    "Pinterest": 246500000,
    "Reddit": 355000000,
    "Snapchat": 0,
    "TikTok": 500000000,
    "Tumblr": 624000000,
    "Twitter": 329500000,
    "WeChat": 1000000000,
    "Weibo": 431000000,
    "Whatsapp": 1433333333,
    "YouTube": 1900000000
  }
};


// Create root element
// https://www.amcharts.com/docs/v5/getting-started/#Root_element
var root = am5.Root.new("chartdiv");

root.numberFormatter.setAll({
  numberFormat: "#a",

  // Group only into M (millions), and B (billions)
  bigNumberPrefixes: [
    { number: 1e6, suffix: "M" },
    { number: 1e9, suffix: "B" }
  ],

  // Do not use small number prefixes at all
  smallNumberPrefixes: []
});

var stepDuration = 2000;


// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
root.setThemes([am5themes_Animated.new(root)]);


// Create chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/
var chart = root.container.children.push(am5xy.XYChart.new(root, {
  panX: true,
  panY: true,
  wheelX: "none",
  wheelY: "none"
}));


// We don't want zoom-out button to appear while animating, so we hide it at all
chart.zoomOutButton.set("forceHidden", true);


// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var yRenderer = am5xy.AxisRendererY.new(root, {
  minGridDistance: 20,
  inversed: true
});
// hide grid
yRenderer.grid.template.set("visible", false);

var yAxis = chart.yAxes.push(am5xy.CategoryAxis.new(root, {
  maxDeviation: 0,
  categoryField: "network",
  renderer: yRenderer
}));

var xAxis = chart.xAxes.push(am5xy.ValueAxis.new(root, {
  maxDeviation: 0,
  min: 0,
  strictMinMax: true,
  extraMax: 0.1,
  renderer: am5xy.AxisRendererX.new(root, {})
}));

xAxis.set("interpolationDuration", stepDuration / 10);
xAxis.set("interpolationEasing", am5.ease.linear);


// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
var series = chart.series.push(am5xy.ColumnSeries.new(root, {
  xAxis: xAxis,
  yAxis: yAxis,
  valueXField: "value",
  categoryYField: "network"
}));

// Rounded corners for columns
series.columns.template.setAll({ cornerRadiusBR: 5, cornerRadiusTR: 5 });

// Make each column to be of a different color
series.columns.template.adapters.add("fill", function (fill, target) {
  return chart.get("colors").getIndex(series.columns.indexOf(target));
});

series.columns.template.adapters.add("stroke", function (stroke, target) {
  return chart.get("colors").getIndex(series.columns.indexOf(target));
});

// Add label bullet
series.bullets.push(function () {
  return am5.Bullet.new(root, {
    locationX: 1,
    sprite: am5.Label.new(root, {
      text: "{valueXWorking.formatNumber('#.# a')}",
      fill: root.interfaceColors.get("alternativeText"),
      centerX: am5.p100,
      centerY: am5.p50,
      populateText: true
    })
  });
});

var label = chart.plotContainer.children.push(am5.Label.new(root, {
  text: "2002",
  fontSize: "8em",
  opacity: 0.2,
  x: am5.p100,
  y: am5.p100,
  centerY: am5.p100,
  centerX: am5.p100
}));

// Get series item by category
function getSeriesItem(category) {
  for (var i = 0; i < series.dataItems.length; i++) {
    var dataItem = series.dataItems[i];
    if (dataItem.get("categoryY") == category) {
      return dataItem;
    }
  }
}

// Axis sorting
function sortCategoryAxis() {
  // sort by value
  series.dataItems.sort(function (x, y) {
    return y.get("valueX") - x.get("valueX"); // descending
    //return x.get("valueX") - y.get("valueX"); // ascending
  });

  // go through each axis item
  am5.array.each(yAxis.dataItems, function (dataItem) {
    // get corresponding series item
    var seriesDataItem = getSeriesItem(dataItem.get("category"));

    if (seriesDataItem) {
      // get index of series data item
      var index = series.dataItems.indexOf(seriesDataItem);
      // calculate delta position
      var deltaPosition =
        (index - dataItem.get("index", 0)) / series.dataItems.length;
      // set index to be the same as series data item index
      if (dataItem.get("index") != index) {
        dataItem.set("index", index);
        // set deltaPosition instanlty
        dataItem.set("deltaPosition", -deltaPosition);
        // animate delta position to 0
        dataItem.animate({
          key: "deltaPosition",
          to: 0,
          duration: stepDuration / 2,
          easing: am5.ease.out(am5.ease.cubic)
        });
      }
    }
  });
  // sort axis items by index.
  // This changes the order instantly, but as deltaPosition is set, they keep in the same places and then animate to true positions.
  yAxis.dataItems.sort(function (x, y) {
    return x.get("index") - y.get("index");
  });
}

var year = 2002;

// update data with values each 1.5 sec
var interval = setInterval(function () {
  year++;

  if (year > 2018) {
    clearInterval(interval);
    clearInterval(sortInterval);
  }

  updateData();
}, stepDuration);

var sortInterval = setInterval(function () {
  sortCategoryAxis();
}, 100);

function setInitialData() {
  var d = allData[year];

  for (var n in d) {
    series.data.push({ network: n, value: d[n] });
    yAxis.data.push({ network: n });
  }
}

function updateData() {
  var itemsWithNonZero = 0;

  if (allData[year]) {
    label.set("text", year.toString());

    am5.array.each(series.dataItems, function (dataItem) {
      var category = dataItem.get("categoryY");
      var value = allData[year][category];

      if (value > 0) {
        itemsWithNonZero++;
      }

      dataItem.animate({
        key: "valueX",
        to: value,
        duration: stepDuration,
        easing: am5.ease.linear
      });
      dataItem.animate({
        key: "valueXWorking",
        to: value,
        duration: stepDuration,
        easing: am5.ease.linear
      });
    });

    yAxis.zoom(0, itemsWithNonZero / yAxis.dataItems.length);
  }
}

setInitialData();
setTimeout(function () {
  year++;
  updateData();
}, 50);

// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
series.appear(1000);
chart.appear(1000, 100);

}); // end am5.ready()
</script>

<!-- HTML -->
<div id="chartdiv"></div>
```

### 動作確認
* ここまでで、ひとまず以下のようなアニメーションがされればヨシ
    * GIF アニメーションでちょっともっさりした見え方になってますが、実動はもっと高速です

<a href="https://gyazo.com/7ea75e2944a550534b227b66ec96fd9b"><img src="https://i.gyazo.com/7ea75e2944a550534b227b66ec96fd9b.gif" alt="Image from Gyazo" width="1507.5"/></a>

# 下準備その２
## 表示したいデータを決める
* さて、ここまではデモ用のコード内に直書きされていたデータでの動作確認でした。次は、実際の FileMaker テーブル内にあるデータで動作させられるように進めます
* 表示させたいデータ用のテーブルを作りましょう。今回は既にクロス集計されたデータを使い、レコードの区分けは `年` によるものとして、それ以外はフィールドで区切るようにします
* じゃあ何のデータを表示させようかということで、今回は、日本の五大商社の年末株価（終値）の推移でも見てみましょう
    * [日経 - 伊藤忠商事](https://www.nikkei.com/nkd/company/history/yprice/?scode=8001)
    * [日経 - 丸紅](https://www.nikkei.com/nkd/company/history/yprice/?scode=8002)
    * [日経 - 三井物産](https://www.nikkei.com/nkd/company/history/yprice/?scode=8031)
    * [日経 - 住友商事](https://www.nikkei.com/nkd/company/history/yprice/?scode=8053)
    * [日経 - 三菱商事](https://www.nikkei.com/nkd/company/history/yprice/?scode=8058)

https://www.nikkei.com/nkd/company/history/yprice/?scode=8001

https://www.nikkei.com/nkd/company/history/yprice/?scode=8002

https://www.nikkei.com/nkd/company/history/yprice/?scode=8031

https://www.nikkei.com/nkd/company/history/yprice/?scode=8053

https://www.nikkei.com/nkd/company/history/yprice/?scode=8058

## FileMaker データ用のテーブルを作成
* 今回は年毎に区切るということで `annal_reports` というテーブル名にしておきます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/2bfd6f68-bafe-b19b-0a10-517a1c83d6ed.png)


## フィールド作成
* `id` : 数字
* `year` : 数字
* その他、乱暴にも各商社名をそのままフィールド名とします。全部数字で
    * これができるのが FileMaker の驚きなところでもあり良いところでもあり

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/63b0639a-611e-60a5-79c9-43e8bfacd330.png)


## ダミーデータ準備
* ちょっと面倒なのですが、先ほど貼った以下の各ページから、各年の `終値（円）` の値を取ってきて csv データにします（手動でがんばる）
    * 何かもっと良いデータ提供サイトあったら教えてください……

https://www.nikkei.com/nkd/company/history/yprice/?scode=8001

https://www.nikkei.com/nkd/company/history/yprice/?scode=8002

https://www.nikkei.com/nkd/company/history/yprice/?scode=8031

https://www.nikkei.com/nkd/company/history/yprice/?scode=8053

https://www.nikkei.com/nkd/company/history/yprice/?scode=8058

```csvs
"year", "伊藤忠商事", "丸紅", "三井物産", "住友商事", "三菱商事"
2013, 1299, 756, 1465, 1321, 2017
2014, 1292, 724.7, 1621.5, 1242, 2217
2015, 1442, 625.1, 1445.5, 1240.5, 2028
2016, 1552, 662.8, 1607, 1375.5, 2490
2017, 2103, 816.1, 1832, 1915, 3113
2018, 1867, 772.4, 1690.5, 1561.5, 3020
2019, 2534.5, 810.6, 1946, 1624, 2900
2020, 2964, 686.1, 1889.5, 1365.5, 2541
2021, 3518, 1119.5, 2723.5, 1700.5, 3652
```

## ダミーデータ投下
* 上記 csv データをインポート
  * 上記、見やすくするために半角スペースなど入れていますが、消してからインポートしないと半角スペースごと取り込まれてしまうという問題に遭遇します、FileMaker さん……

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/4be90406-5ab6-5326-77bc-8c477022b3a7.png)

* csv でやるとヘッダ行までインポートされるので不要レコード削除……

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/3603cc89-79ba-9126-0269-e7ef28d9f585.png)

* 以下のようにレコードが揃っていればヨシ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/26f72791-0191-a4c2-13a1-e0a05864541a.png)


## データ表示部分の形式を確認
* amCharts のデモから転記してきたコード内に、データ部分が含まれているので、そこでどのような形式を要求されているのか確認
* 以下のように `year` の中に各項目と値が記述されている

```js
  "2002": {
    "Friendster": 0,
    "Facebook": 0,
    "Flickr": 0,
    "Google Buzz": 0,
    "Google+": 0,
    "Hi5": 0,
    "Instagram": 0,
    "MySpace": 0,
    "Orkut": 0,
    "Pinterest": 0,
    "Reddit": 0,
    "Snapchat": 0,
    "TikTok": 0,
    "Tumblr": 0,
    "Twitter": 0,
    "WeChat": 0,
    "Weibo": 0,
    "Whatsapp": 0,
    "YouTube": 0
  },
  "2003": {
    // 以下略
```

* よって、以下のようなものが出力されるとヨシ

```js
  "2013": {
    "伊藤忠商事": 1299,
    "丸紅": 756,
    "三井物産": 1465,
    "住友商事": 1321,
    "三菱商事": 2017
  },
  "2014": {
  // 以下略
```

## レイアウトと計算フィールド作成
### レイアウト
* `for_json` という軽量簡潔なレイアウトを用意しておく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/0d6240ad-a108-da2b-6613-31465cfdd639.png)

### 計算フィールド
* `c_json` という名の、以下のような計算フィールドを作成

```java
While (
  [ 
    fileName = Get ( ファイル名 );
    layoutName = "for_json";
    fields = FieldNames ( fileName ; layoutName);
    fields = Substitute ( fields ; "year" ; "" );
    fields = Substitute ( fields ; "c_json" ; "" );
    fieldsCount = ValueCount ( fields ); 
    i = 0;
    result = ""
  ];

  i < fieldsCount;

  [
    i = i + 1;
    name = GetValue ( fields ; i );
    value = GetField ( name );
    fieldType = FieldType ( fileName ; name ); 
    jsonType = If (
                 PatternCount ( fieldType ; "Number");
                   JSONNumber;
                   JSONString
               );
    result = JSONSetElement ( result; name; value; jsonType )
  ];

  "\"" & year & "\": " & 
  JSONFormatElements ( result ) & ","
)
```

### 単一レコードの JSON データ確認
* `annual_reports` レイアウトに `c_json` フィールドを設置してみて出力結果を確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/33f76b62-4502-4853-5fcd-d1815a8c9631.png)


## 対象レコードの JSON データ化
### リレーション作成
* `races` テーブルを親にして any 条件でリレーションを作成
  * このあたり、`type` みたいなフィールドを `races` と `annual_repots` 両方に持たせてそれをキーにし、特定の条件のレポートだけに絞り込む、というやり方もアリ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/4bf40e5b-5609-a0a8-d180-5ab7c907f34d.png)

### 計算フィールド作成
* `races` テーブルにも `c_json` 計算フィールドを作成

```java
List ( races⇔annual_reports◎any::c_json )
```

### 出力確認
* `races` レイアウトに `c_json` フィールドを設置してみて出力結果を確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/ff2bc91b-7f37-cc0e-b2f0-2ba8922eafee.png)



# 締め作業
## `g_for_webview` の書き換え
* ここまで来れば、もうあと一歩……

### データ部分の書き換え
* `g_for_webview` の以下箇所 `allData = {` より下の行からがデータにあたるので……

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/ff25fc50-0979-e0b2-41c1-7a9aaf710887.png)

* 以下のように書き換える

```js
// Data
var allData = {
[[[c_json]]]
};
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/0c16617e-d8dc-f978-fb7a-7c69e2cfa95c.png)

### 年情報の書き換え
* サンプルコードの中では年情報が固定で直接書かれてしまっています

```js
var label = chart.plotContainer.children.push(am5.Label.new(root, {
  text: "2002",
  fontSize: "8em",
  opacity: 0.2,
  x: am5.p100,
  y: am5.p100,
  centerY: am5.p100,
  centerX: am5.p100
}));

// 中略

var year = 2002;

// update data with values each 1.5 sec
var interval = setInterval(function () {
  year++;

  if (year > 2018) {
    clearInterval(interval);
    clearInterval(sortInterval);
  }

  updateData();
}, stepDuration);
```

* 上記の `2002` と `2018` は動的に変わって欲しいので `[[[start_year]]]` `[[[end_year]]]` として、以下のように書き換えておきます

```js
var label = chart.plotContainer.children.push(am5.Label.new(root, {
  text: "[[[start_year]]]",
  fontSize: "8em",
  opacity: 0.2,
  x: am5.p100,
  y: am5.p100,
  centerY: am5.p100,
  centerX: am5.p100
}));

// 中略

var year = [[[start_year]]];

// update data with values each 1.5 sec
var interval = setInterval(function () {
  year++;

  if (year > [[[end_year]]]) {
    clearInterval(interval);
    clearInterval(sortInterval);
  }

  updateData();
}, stepDuration);
```

### 前年データの投入
* amCharts できれいに描画開始したい場合、開始年より一年前のレコードですべての値が `0` になっているものが用意されている方がよい様子

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37739/ce9e17a1-e3b5-dbe9-ea37-34d276504340.png)


## WEB ビューアの書き換え
* 計算式を以下のように

```java
Let (
  [
    amcharts_tags = $$amcharts_tags;
    style_tags = $$style_tags;

    json = races::c_json;

    start_year = Min ( races⇔annual_reports◎any::year );
    end_year = Max ( races⇔annual_reports◎any::year );


    result = races::g_for_webview;

    result = Substitute ( result ; "[[[amcharts_tags]]]" ; amcharts_tags );
    result = Substitute ( result ; "[[[style_tags]]]" ; style_tags );

    result = Substitute ( result ; "[[[json]]]" ; json );

    result = Substitute ( result ; "[[[start_year]]]" ; start_year );
    result = Substitute ( result ; "[[[end_year]]]" ; end_year )
  ];

  result
)
```

## 動作確認
* 以下のようにアニメーションされればヨシ！
  * 実際に FileMaker Pro 上で動作させている場合はもっと高速にぬるぬる動きますよ

<a href="https://gyazo.com/83135dd146e8a36d5e9cd776c831cdd3"><img src="https://i.gyazo.com/83135dd146e8a36d5e9cd776c831cdd3.gif" alt="Image from Gyazo" width="1545"/></a>


# おわりに
## 雑感
* 長かった！　書くのに時間がかかった！　ここまでお読みいただきありがとうございました🙏
* `amCharts` については、この BarChartRace 以外にも色々なグラフ表現が充実しているので、便利に使えると思います。ただちょっと慣れないとサンプル読むのに時間がかかるかもしれません。他にもコレで実装例出してみて欲しい、など希望があればコメントください
* FileMaker グラフとは

## GitHub 公開リポジトリ
* https://github.com/hi-noguchi/FileMaker-amCharts-BarChartRace

https://github.com/hi-noguchi/FileMaker-amCharts-BarChartRace

* .fmp12 ファイルのダウンロードや DDR ( Database Design Report ) の確認ができるようになっています。issue や Pull-req も歓迎