LoginSignup
7
6

More than 3 years have passed since last update.

[React / amCharts4] ローソク足(candlestick chart)を描画

Last updated at Posted at 2020-09-22

概要

amChartsで、Reactアプリケーション上にローソク足(candlestickチャート)を描画するまで。

なぜamCharts?

同一チャートエリア内に、candlestickチャートとscatterを描画したかったが、以下のライブラリではできないか、またはできなさそうと判断したため。
無料で使用できる有名どころのライブラリをいくつか調べたところ、amChartsであればできそうだったため、消去法で採用。

ChartJS, ApexChart, HighCharts 不採用の理由詳細
  • ChartJS
    candlestickを扱っているような動画としょぼいデモページはあるが、ドキュメントに記載がない?(見つからない)
    そして、この動画に掲載されているWEBサイトはどこに?
  • ApexChart
    実際に使ってみたが、問題があった

    • candlestickチャートのxaxisのtypeとして'category'を指定すると
      表示はできるものの、scatterのx軸上の位置を指定することができず、scatterの表示箇所が全て左端に寄ってしまった
    • candelstickチャートのxaxisのtypeとして'datetime'を指定すると
      scatterをx軸上の期待した位置に表示することができるが、'datetime'では、取引所が閉まっている期間(つまり、データが存在しない期間)のローソク足の位置に無駄な隙間が空いてしまうため、'datetime'は不適切
  • HighCharts
    実際に使ってみたわけではないが、調べた限りでは、scatterとcandlestickを同一チャート内に描画することができなさそう
    別々のオブジェクトに対して描画しているサンプルしか見つけることができなかった

※いずれも、2020/09/22時点の状況のため、状況が変わっている可能性があります

これに対してamChartsでは、candlestickscatterを同一のXYChartというオブジェクト上に描画できそうであった

他にも多少調べてみたが、料金がかかるものだったり、ドキュメントが不親切でどこまでできるかよくわからないものしか見つけることができなかった。
(GoogleChartは評判が良くなさそうだったので調べてすらいない。)
ちなみに、料金がかかるものは非常に良いものだったので、本当はそっちを使いたかった。

準備

$ npm install @amcharts/amcharts4

実装

1. まずは Getting Started で基本的なグラフから

公式ドキュメントのCreating_a_chartに書かれている通りにコーディングすれば、すぐに以下のようなグラフを確認できる。

image.png

右端の日付が見切れているが、ウィンドウサイズに対してレスポンシブな上に、表示範囲の拡大縮小時の動作がすばらしく美しい。

ChartJS と ApexChart は使用経験がありますが、そのどちらよりも相当洗練されています。
これはかなり期待できる(`・ω・´)

2. 次に candlestick チャートを描画

やり方はここに書いてあります。
が、掲載されているものから若干修正したので、一応修正後のソースコードも掲載。

ソースコード

ソースコードはここをクリックして確認
src/App.js
import React, { Component } from 'react';
import AmChartSample from './AmChartSample';

class App extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (
      <>
        <AmChartSample />
      </>
    );
  }
}

export default App;
src/AmChartSample.js
import React, { Component } from 'react';
import * as am4core from "@amcharts/amcharts4/core";
import * as am4charts from "@amcharts/amcharts4/charts";
import am4themes_animated from "@amcharts/amcharts4/themes/animated";

am4core.useTheme(am4themes_animated);


class AmChartSample extends Component {
  componentDidMount() {
    this.chart = prepareChart();
  }

  componentWillUnmount() {
    if (this.chart) {
      this.chart.dispose();
    }
  }

  render() {
    return (
      <div id="chartdiv" style={{ width: "100%", height: "500px" }}></div>
    );
  }
}

function prepareChart() {
  // ... chart code goes here ...
  const chart = am4core.create('chartdiv', am4charts.XYChart);
  chart.dateFormatter.inputDateFormat = 'yyyy-MM-dd';
  chart.paddingRight = 20;

  const dateAxis = chart.xAxes.push(new am4charts.DateAxis());
  dateAxis.renderer.grid.template.location = 0;
  dateAxis.renderer.minGridDistance = 60;
  // INFO: dateAxis.skipEmptyPeriods を true にしておくと、休日の位置に無駄な余白が表示されずに済む
  // dateAxis.skipEmptyPeriods = true;

  const valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  valueAxis.tooltip.disabled = true;
  valueAxis.renderer.minWidth = 35;

  var series = chart.series.push(new am4charts.CandlestickSeries());
  series.dataFields.dateX = "date";
  series.dataFields.valueY = "close";
  series.dataFields.openValueY = "open";
  series.dataFields.lowValueY = "low";
  series.dataFields.highValueY = "high";
  // ${変数} を "" や '' で囲むとwarningメッセージが表示されるので`$`を削除した
  series.tooltipText = "Open: [bold]{openValueY.value}[/]\nLow: [bold]{lowValueY.value}[/]\nHigh: [bold]{highValueY.value}[/]\nClose: [bold]{valueY.value}[/]";

  chart.cursor = new am4charts.XYCursor();

  const scrollbarX = new am4charts.XYChartScrollbar();
  scrollbarX.series.push(series);
  chart.scrollbarX = scrollbarX;

  // amChartsのCodePenはこっちのスクロールバーを使っていたけれど、上のやつの方がきれいじゃない?お好みで
  // chart.scrollbarX = new am4core.Scrollbar();

  chart.data = candleData;

  return chart;  
}

const candleData = [{
  "date": "2018-08-01",
  "open": "136.65",
  "high": "136.96",
  "low": "134.15",
  "close": "136.49"
}, {
  "date": "2018-08-02",
  "open": "135.26",
  "high": "135.95",
  "low": "131.50",
  "close": "131.85"
}, {
  ...
}, {
  "date": "2018-10-16",
  "open": "172.69",
  "high": "173.04",
  "low": "169.18",
  "close": "172.75"
}];

export default AmChartSample;

candleData を確認されたい方はここをクリック
src/AmChartSample.js
const candleData = [{
  "date": "2018-08-01",
  "open": "136.65",
  "high": "136.96",
  "low": "134.15",
  "close": "136.49"
}, {
  "date": "2018-08-02",
  "open": "135.26",
  "high": "135.95",
  "low": "131.50",
  "close": "131.85"
}, {
  "date": "2018-08-05",
  "open": "132.90",
  "high": "135.27",
  "low": "128.30",
  "close": "135.25"
}, {
  "date": "2018-08-06",
  "open": "134.94",
  "high": "137.24",
  "low": "132.63",
  "close": "135.03"
}, {
  "date": "2018-08-07",
  "open": "136.76",
  "high": "136.86",
  "low": "132.00",
  "close": "134.01"
}, {
  "date": "2018-08-08",
  "open": "131.11",
  "high": "133.00",
  "low": "125.09",
  "close": "126.39"
}, {
  "date": "2018-08-09",
  "open": "123.12",
  "high": "127.75",
  "low": "120.30",
  "close": "125.00"
}, {
  "date": "2018-08-12",
  "open": "128.32",
  "high": "129.35",
  "low": "126.50",
  "close": "127.79"
}, {
  "date": "2018-08-13",
  "open": "128.29",
  "high": "128.30",
  "low": "123.71",
  "close": "124.03"
}, {
  "date": "2018-08-14",
  "open": "122.74",
  "high": "124.86",
  "low": "119.65",
  "close": "119.90"
}, {
  "date": "2018-08-15",
  "open": "117.01",
  "high": "118.50",
  "low": "111.62",
  "close": "117.05"
}, {
  "date": "2018-08-16",
  "open": "122.01",
  "high": "123.50",
  "low": "119.82",
  "close": "122.06"
}, {
  "date": "2018-08-19",
  "open": "123.96",
  "high": "124.50",
  "low": "120.50",
  "close": "122.22"
}, {
  "date": "2018-08-20",
  "open": "122.21",
  "high": "128.96",
  "low": "121.00",
  "close": "127.57"
}, {
  "date": "2018-08-21",
  "open": "131.22",
  "high": "132.75",
  "low": "130.33",
  "close": "132.51"
}, {
  "date": "2018-08-22",
  "open": "133.09",
  "high": "133.34",
  "low": "129.76",
  "close": "131.07"
}, {
  "date": "2018-08-23",
  "open": "130.53",
  "high": "135.37",
  "low": "129.81",
  "close": "135.30"
}, {
  "date": "2018-08-26",
  "open": "133.39",
  "high": "134.66",
  "low": "132.10",
  "close": "132.25"
}, {
  "date": "2018-08-27",
  "open": "130.99",
  "high": "132.41",
  "low": "126.63",
  "close": "126.82"
}, {
  "date": "2018-08-28",
  "open": "129.88",
  "high": "134.18",
  "low": "129.54",
  "close": "134.08"
}, {
  "date": "2018-08-29",
  "open": "132.67",
  "high": "138.25",
  "low": "132.30",
  "close": "136.25"
}, {
  "date": "2018-08-30",
  "open": "139.49",
  "high": "139.65",
  "low": "137.41",
  "close": "138.48"
}, {
  "date": "2018-09-03",
  "open": "139.94",
  "high": "145.73",
  "low": "139.84",
  "close": "144.16"
}, {
  "date": "2018-09-04",
  "open": "144.97",
  "high": "145.84",
  "low": "136.10",
  "close": "136.76"
}, {
  "date": "2018-09-05",
  "open": "135.56",
  "high": "137.57",
  "low": "132.71",
  "close": "135.01"
}, {
  "date": "2018-09-06",
  "open": "132.01",
  "high": "132.30",
  "low": "130.00",
  "close": "131.77"
}, {
  "date": "2018-09-09",
  "open": "136.99",
  "high": "138.04",
  "low": "133.95",
  "close": "136.71"
}, {
  "date": "2018-09-10",
  "open": "137.90",
  "high": "138.30",
  "low": "133.75",
  "close": "135.49"
}, {
  "date": "2018-09-11",
  "open": "135.99",
  "high": "139.40",
  "low": "135.75",
  "close": "136.85"
}, {
  "date": "2018-09-12",
  "open": "138.83",
  "high": "139.00",
  "low": "136.65",
  "close": "137.20"
}, {
  "date": "2018-09-13",
  "open": "136.57",
  "high": "138.98",
  "low": "136.20",
  "close": "138.81"
}, {
  "date": "2018-09-16",
  "open": "138.99",
  "high": "140.59",
  "low": "137.60",
  "close": "138.41"
}, {
  "date": "2018-09-17",
  "open": "139.06",
  "high": "142.85",
  "low": "137.83",
  "close": "140.92"
}, {
  "date": "2018-09-18",
  "open": "143.02",
  "high": "143.16",
  "low": "139.40",
  "close": "140.77"
}, {
  "date": "2018-09-19",
  "open": "140.15",
  "high": "141.79",
  "low": "139.32",
  "close": "140.31"
}, {
  "date": "2018-09-20",
  "open": "141.14",
  "high": "144.65",
  "low": "140.31",
  "close": "144.15"
}, {
  "date": "2018-09-23",
  "open": "146.73",
  "high": "149.85",
  "low": "146.65",
  "close": "148.28"
}, {
  "date": "2018-09-24",
  "open": "146.84",
  "high": "153.22",
  "low": "146.82",
  "close": "153.18"
}, {
  "date": "2018-09-25",
  "open": "154.47",
  "high": "155.00",
  "low": "151.25",
  "close": "152.77"
}, {
  "date": "2018-09-26",
  "open": "153.77",
  "high": "154.52",
  "low": "152.32",
  "close": "154.50"
}, {
  "date": "2018-09-27",
  "open": "153.44",
  "high": "154.60",
  "low": "152.75",
  "close": "153.47"
}, {
  "date": "2018-09-30",
  "open": "154.63",
  "high": "157.41",
  "low": "152.93",
  "close": "156.34"
}, {
  "date": "2018-10-01",
  "open": "156.55",
  "high": "158.59",
  "low": "155.89",
  "close": "158.45"
}, {
  "date": "2018-10-02",
  "open": "157.78",
  "high": "159.18",
  "low": "157.01",
  "close": "157.92"
}, {
  "date": "2018-10-03",
  "open": "158.00",
  "high": "158.08",
  "low": "153.50",
  "close": "156.24"
}, {
  "date": "2018-10-04",
  "open": "158.37",
  "high": "161.58",
  "low": "157.70",
  "close": "161.45"
}, {
  "date": "2018-10-07",
  "open": "163.49",
  "high": "167.91",
  "low": "162.97",
  "close": "167.91"
}, {
  "date": "2018-10-08",
  "open": "170.20",
  "high": "171.11",
  "low": "166.68",
  "close": "167.86"
}, {
  "date": "2018-10-09",
  "open": "167.55",
  "high": "167.88",
  "low": "165.60",
  "close": "166.79"
}, {
  "date": "2018-10-10",
  "open": "169.49",
  "high": "171.88",
  "low": "153.21",
  "close": "162.23"
}, {
  "date": "2018-10-11",
  "open": "163.01",
  "high": "167.28",
  "low": "161.80",
  "close": "167.25"
}, {
  "date": "2018-10-14",
  "open": "167.98",
  "high": "169.57",
  "low": "163.50",
  "close": "166.98"
}, {
  "date": "2018-10-15",
  "open": "165.54",
  "high": "170.18",
  "low": "165.15",
  "close": "169.58"
}, {
  "date": "2018-10-16",
  "open": "172.69",
  "high": "173.04",
  "low": "169.18",
  "close": "172.75"
}];

export default AmChartSample;

  • 上記ソースコードの通りだと以下のように 休日などのデータがない部分に余計な隙間がある。 image.png
  • dateAxis.skipEmptyPeriods = true;にすると以下のように image.png 投資関係やってる人からしたらこの表示方法がデフォルトのはず。
    ちなみに、このオプションは以下の記事で見つけました。
    Amchart (three) candle chart

参考情報

番外編

3. candlestickと一緒にscatterなどを表示させる

当初筆者がやりたかったことはこれです( ・´ー・`)ドヤッ

image.png

candlestickだけでなく、LineやScatterが描画されています!

ソースコード

好き勝手いじっているのでわかりにくくなっているかもしれませんが、ご容赦ください(o*。_。)oペコッ

ソースコードはここをクリックして確認
src/AmChartSample.js
import React, { Component } from 'react';
import * as am4core from '@amcharts/amcharts4/core';
import * as am4charts from '@amcharts/amcharts4/charts';
import am4themes_animated from '@amcharts/amcharts4/themes/animated';

am4core.useTheme(am4themes_animated);


class AmChartSample extends Component {
  componentDidMount() {
    this.chart = prepareChart();
  }

  componentWillUnmount() {
    if (this.chart) {
      this.chart.dispose();
    }
  }

  render() {
    return (
      <div id='chartdiv' style={{ width: '100%', height: '500px' }}></div>
    );
  }
}

function prepareChart() {
  // ... chart code goes here ...
  const chart = am4core.create('chartdiv', am4charts.XYChart);
  chart.cursor = new am4charts.XYCursor();
  chart.dateFormatter.inputDateFormat = 'yyyy-MM-dd';
  chart.paddingRight = 20;

  const dateAxis = chart.xAxes.push(new am4charts.DateAxis());
  dateAxis.renderer.grid.template.location = 0;
  dateAxis.renderer.minGridDistance = 60;
  dateAxis.skipEmptyPeriods = true;

  const valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  valueAxis.tooltip.disabled = true;
  valueAxis.renderer.minWidth = 35;

  const candlestick = addCandlestick(chart)
  addLineSample(chart);
  addScatterSample(chart)

  const scrollbarX = new am4charts.XYChartScrollbar();
  scrollbarX.series.push(candlestick);
  chart.scrollbarX = scrollbarX;
  // chart.scrollbarX = new am4core.Scrollbar();

  return chart;
}

function addCandlestick(chart) {
  const candlestick = chart.series.push(new am4charts.CandlestickSeries());
  candlestick.dataFields.dateX = 'date';
  candlestick.dataFields.valueY = 'close';
  candlestick.dataFields.openValueY = 'open';
  candlestick.dataFields.lowValueY = 'low';
  candlestick.dataFields.highValueY = 'high';
  candlestick.tooltipText = 'Open: [bold]{openValueY.value}[/]\nLow: [bold]{lowValueY.value}[/]\nHigh: [bold]{highValueY.value}[/]\nClose: [bold]{valueY.value}[/]';

  chart.data = candleData;

  return candlestick;
}

function addLineSample(chart) {
  const lineSample = chart.series.push(new am4charts.LineSeries());
  lineSample.dataFields.dateX = 'value';
  lineSample.dataFields.valueY = 'value2';
  lineSample.strokeWidth = 2
  lineSample.stroke = chart.colors.getIndex(3);
  lineSample.strokeOpacity = 0.7;
  lineSample.data = [
    { 'value': '2018-08-05', 'value2': 140 },
    { 'value': '2018-08-26', 'value2': 170 }
  ];
}

function addScatterSample(chart) {
  const lineSample = chart.series.push(new am4charts.LineSeries());
  lineSample.dataFields.dateX = 'value';
  lineSample.dataFields.valueY = 'value2';
  lineSample.strokeWidth = 2
  lineSample.stroke = chart.colors.getIndex(3);
  lineSample.strokeOpacity = 0.0;
  lineSample.data = [
    { 'value': '2018-08-08', 'value2': 140 },
    { 'value': '2018-09-03', 'value2': 150 }
  ];

  // Add a bullet
  let bullet = lineSample.bullets.push(new am4charts.Bullet());
  // Add a triangle to act as am arrow
  let arrow = bullet.createChild(am4core.Triangle);
  arrow.horizontalCenter = "middle";
  arrow.verticalCenter = "middle";
  arrow.strokeWidth = 0;
  arrow.fill = chart.colors.getIndex(0);
  arrow.direction = "top";
  arrow.width = 12;
  arrow.height = 12;
}

const candleData = [{
  "date": "2018-08-01",
  "open": "136.65",
  "high": "136.96",
  "low": "134.15",
  "close": "136.49"
}, {
  "date": "2018-08-02",
  "open": "135.26",
  "high": "135.95",
  "low": "131.50",
  "close": "131.85"
}, {
  ...
}, {
  "date": "2018-10-16",
  "open": "172.69",
  "high": "173.04",
  "low": "169.18",
  "close": "172.75"
}];

export default AmChartSample;

番外編の参考

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6