D3.jsでローソク足チャートを描く

株価のローソク足チャートを描いてみます。これを描いてみたかったので、D3.js触ってみたんですよね。D3.jsには「TechanJS」というファイナンス用のライブラリが存在しており、これを利用することで簡単にチャートを作成できます。

TechanJSのギャラリー。いろんな表やオシレーターのサンプルがあります。

Gallery · andredumas/techan.js Wiki · GitHub

このライブラリを利用しなくともD3.jsだけでローソク足チャートは作成できますが、まあライブラリ使ったほうが圧倒的に楽ですよ。ということで、以下のようなチャートを作成してみました。昨年末のトヨタ自動車のチャートです。青い線は25日線です。

f:id:goodbyegangster:20170223214220p:plain

前回の投稿を参考に、Rails上でチャートを描画してみます。

RailsでD3.jsを使う - goodbyegangsterのブログ

コントローラの作成

「candlestick」というコントローラに、indexとlistというアクションを作成しておきます。

$ rails generate controller candlestick index list

D3.jsとTechanJSの参照設定

全ページで参照できるようにするため、「application.html.erb」ファイルに以下を記載しておきます。

<%= javascript_include_tag src=“https://d3js.org/d3.v4.min.js” %>

<%= javascript_include_tag src=“http://techanjs.org/techan.min.js” %>

$ vim app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>SampleD3</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

    <%= javascript_include_tag src="https://d3js.org/d3.v4.min.js" %>
    <%= javascript_include_tag src="http://techanjs.org/techan.min.js" %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

スタイルシートの記述

全ページで参照できるように「custom.scss」ファイルに記載しておきます。

$ vim app/assets/stylesheets/custom.scss
path.candle {
    stroke: #000000;
}

path.candle.body {
    stroke-width: 0;
}

path.candle.up {
    fill: #FF0000;
    stroke: #FF0000;
}

path.candle.down {
    fill: #00AA00;
    stroke: #00AA00;
}

path.volume {
    fill: #696969;
    stroke-width: 1;
}

.sma path.line {
    fill: none;
    stroke-width: 1;
}

.ma25 path.line {
    stroke: #0000cd;
}

アクションの記述

コントローラに必要なアクションを記載しておきます。indexは表示用のアクションなのでそのままですが、listはデータを渡す用のアクションにします。今回はサンプルなので、グラフ描画するようのデータは直書きしてしまいます。

$ vim app/controllers/candlestick_controller.rb
class CandlestickController < ApplicationController
  def index
  end

  def list
    data = [
      {Date: "2016-12-30", Open: "6829", High: "6913", Low: "6800", Close: "6878", Volume: "6708800"},
      {Date: "2016-12-29", Open: "6935", High: "6960", Low: "6838", Close: "6838", Volume: "11400700"},
      (中略)
      {Date: "2016-08-22", Open: "6078", High: "6109", Low: "6065", Close: "6100", Volume: "9130100"},
      {Date: "2016-08-19", Open: "5969", High: "6060", Low: "5956", Close: "6026", Volume: "11944300"}
    ]
    render :json => data
  end

end

D3.jsを利用するJavaScriptの記述

実際のD3.jsを利用するためのJavaScriptを「index.html.erb」に作成します。

$ vim app/views/candlestick/index.html.erb
<script>
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
        width = 960 - margin.left - margin.right,
        height = 400 - margin.top - margin.bottom;

// parse the date / time
var parseDate = d3.timeParse("%Y-%m-%d");

// set the ranges
var x = techan.scale.financetime()
        .range([0, width]);

var y = d3.scaleLinear()
        .range([300, 0]);

var yVolume = d3.scaleLinear()
        .range([height, 300]);

// define the candlestick
var candlestick = techan.plot.candlestick()
        .xScale(x)
        .yScale(y);

// define the sma
var sma = techan.plot.sma()
        .xScale(x)
        .yScale(y);

// define the volume
var volume = techan.plot.volume()
        .xScale(x)
        .yScale(yVolume);

// append the svg obgect to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// Get the data
d3.json('list', function(error, data) {
    var cAccessor = candlestick.accessor();
    var vAccessor = volume.accessor();

    // format the data & sort by date
    data = data.slice(0, 140).map(function(d) {
        return {
            date: parseDate(d.Date),
            open: +d.Open,
            high: +d.High,
            low: +d.Low,
            close: +d.Close,
            volume: +d.Volume
        };
    }).sort(function(a, b) { return d3.ascending(cAccessor.d(a), cAccessor.d(b)); });

    // Scale the range of the data
    x.domain(data.map(cAccessor.d));
    y.domain(techan.scale.plot.ohlc(data, cAccessor).domain());
    yVolume.domain(techan.scale.plot.volume(data, vAccessor.v).domain());

    // Add the volume
    svg.append("g")
            .attr("class", "volume")
            .data([data])
            .call(volume);

    // Add the candlestick
    svg.append("g")
            .attr("class", "candlestick")
            .data([data])
            .call(candlestick);

   // Add the sma25
   svg.append("g")
            .attr("class", "sma ma25")
            .datum(techan.indicator.sma().period(25)(data))
            .call(sma);

    // Add the X Axis
    svg.append("g")
            .attr("transform", "translate(0," + height + ")")
            .call(d3.axisBottom(x));

    // Add the Y Axis
    svg.append("g")
            .call(d3.axisLeft(y));

   // Add the Volume-Y Axis
   svg.append("g")
            .call(d3.axisLeft(yVolume)
               .ticks(3)
               .tickFormat(d3.format(",.3s")));

});

</script>

実際のD3.js利用している部分については、前回と同様の記述をしています。まず、画面の大きさやマージンを指定し、利用することになるX軸とY軸用のrangeを指定、描画するためのグラフ種類を定義(今回はローソク足と25日線と出来高)、svg領域を作成してあげたら、データを取り込んだ後、domainを設定、最後にsvg領域にグラフとX軸、Y軸を追加しています。

書き方のルールさえ分かっていれば、サンプルも多数用意してくれていたので、簡単にグラフを描画できました。