React D3 lodash

セレクタで年度ごとのグラフを表示できるようにしてみました

グラフのデータを扱うために lodashを利用してみました

underscore.jsより高機能だとか

まだよくわからないけど、動作しました

lodashの疑問

下記関数funcA内でlodash の _.filter を 変数を使用しデータを抽出したとき

funcAが最初に呼び出されたときには正しくデータを抽出するが

2回目以後は抽出できずに空となる。 なぜだろう??

変数ではなくリテラルにすると、何度呼び出してもデータは抽出される

funcA内で同じ_.filterを複数実行した場合、 1回目のfuncA呼び出しではすべてで抽出ができるが2回目以後では駄目である

そもそもこのような使い方はしないのか

funcA = (_year) => {
  ...
  var yearData = _.filter(this.state.data,{year:_year});
  ...
}  
        

d3Chart.js

var d3 = require('d3');

  var d3Chart = {};

  d3Chart.create = (el, state) => {
    var svg = d3.select(el).append('svg')
        .attr('class', 'd3')
        .attr('width', state.svg.width)
        .attr('height', state.svg.height);

    var scales = d3Chart._scales(svg, state.domain, state.range);

    /* 軸 */
    var xAxis = d3.svg.axis()
                    .scale(scales.x)
                    .tickValues(state.axis.xTicks)
                    .tickPadding(10)
                    .tickFormat(d3.format("d"));

    var xAxisGroup = svg.append("g")
                        .attr("transform",
                              "translate(0,"+ scales.y(0)+")")
                        .attr("stroke","white")
                        .attr("stroke-width","2")
                        .style("fill","none")
                        .call(xAxis);   

    var yAxis = d3.svg.axis()
                    .scale(scales.y)
                    .orient(["left"])
                    .tickPadding(2)
                    .tickValues(state.axis.yTicks)
                    .tickFormat(d3.format("d"));

    var yAxisGroup = svg.append("g")
                        .attr("transform",
                              "translate(" + scales.x(0) + ",0)")
                        .attr("stroke","white")
                        .attr("stroke-width","2")
                        .style("fill","none")
                        .call(yAxis);                            

    svg.append('g')
        .attr('class', 'd3-points');
    svg.append('g')
        .attr('class', 'd3-paths');

    d3Chart.update(el, state);
  };

  d3Chart.update = (el, state) => {
    var scales = d3Chart._scales(el, state.domain, state.range);
    d3Chart._drawPath(el, scales, state.data);
    d3Chart._drawPoints(el, scales, state.data);
  };

  d3Chart.destroy = (el) => {
    // Any clean-up would go here
    // in this example there is nothing to do
  };

  d3Chart._drawPath = (el, scales, data) => {
    var svg = d3.select(el).select('svg');


    var line = d3.svg.line()
                   .x(function(d) { return scales.x(d.x); })
                   .y(function(d) { return scales.y(d.y); })
                   .interpolate("linear");

                  
    var g = svg.selectAll('.d3-paths');
    g.selectAll('.d3-path').remove();               
    g.append("path")
         .attr("stroke", "white")
         .attr("stroke-width", 2)
         .attr("fill", "none")
         .attr("class","d3-path")            
         .attr("d", line(data));
  };

   
  d3Chart._drawPoints = (el, scales, data) => {

    var g = d3.select(el).selectAll('.d3-points');

    var point = g.selectAll('.d3-point')
          .data(data);      
    // ENTER
    point.enter().append('circle')
        .attr('class', 'd3-point');

    // ENTER & UPDATE
    point.attr('cx', function(d) { return scales.x(d.x); })
        .attr('cy', function(d) { return scales.y(d.y); })
        .attr('r', function(d) { return 5; });

    // EXIT
    point.exit()
        .remove();
  };
  d3Chart._scales = (el, domain, range) => {
    // Scales Object
    function Scales(x, y){
      this.x = x;
      this.y = y;
      return this;
    };
    var xScale = d3.scale.linear()
                      .domain(domain.x)
                      .range(range.x);
    var yScale = d3.scale.linear()
                      .domain(domain.y)
                      .range(range.y);;
    let scales = new Scales(xScale,yScale);  
    return scales;
  };

  module.exports = d3Chart;    

Chart.js

var React = require('react');
var _ = require('lodash');
var d3Chart = require('./d3Chart');

var yearData = [];

// Select Component
class Select extends React.Component {

 static propTypes = {
    years: React.PropTypes.array,
  }

  state = {
    options:this.props.years,
    year: this.props.years[0],
  }

  handleChange = (e) => {
    this.setState({year:e.target.value});
    this.props.onChange(e.target.value);
  }

  render() {

    var options = this.state.options.map( (option,i)=>{
      var classNM = 'option';
      var key = 'option'+i;
      return (
        <option value={option} key={key}>{option}</option>
      );
    });  
    return (
      <form>
        <h3>
        <span className="label label-default">Year</span>
        <select 
          label="Year" 
          defaultValue={options[0]}
          className="select"
          onChange={this.handleChange}
          ref="select"
        > 
          {options}
        </select>
        </h3>
      </form>
    );
  }

};

// Chart component
class Chart extends React.Component{
  constructor(props) {
    super(props);
  }

  static propTypes = {
    data: React.PropTypes.array,
    domain: React.PropTypes.object,
    range: React.PropTypes.object,
    axis: React.PropTypes.object,
    years: React.PropTypes.array,
    year: React.PropTypes.number
  }

  state = {
    year: this.props.years[0]
  }
  componentDidMount = () => {
    // 年ごにデータを抽出し配列に保存
    var size = _.size(this.props.years);
    for (var i=0; i<size;i++){
      var $year = this.props.years[i];
      yearData[$year] =  _.flatten(
                      _.pluck(_.filter(this.props.data,
                              {year:$year}),'value')
                   );

    };
    // Chart 初期描画
    var el = React.findDOMNode(this);
    d3Chart.create(el, this.getChartState(this.state.year));
  }

  componentDidUpdate = () => {
//    var el = React.findDOMNode(this);
//    d3Chart.update(el, this.getChartState(2014));
  }
  // 年が変更されたときの処理
  handleChange = (year) => {
    var el = React.findDOMNode(this);
    d3Chart.update(el, this.getChartState(year));
  }

  getChartState = (_year) => {
    return {
      data: yearData[_year],
      svg: this.props.svg,
      domain: this.props.domain,
      range: this.props.range,
      axis: this.props.axis,
      years: this.props.years
    };
  }

  componentWillUnmount = () => {
    var el = React.findDOMNode(this);
    d3Chart.destroy(el);
  }

  render() {
    return (
      <div className="chart">
        <Select 
          years={this.props.years}
          onChange={this.handleChange}
        />
      </div>
    );
  }
};

module.exports = Chart;    

app.js

var React = require('react');
var _ = require('lodash');
var Chart = require('./Chart');
require('./d3graph.css');

// グラフ用データ
var data = 
    [
      {
        'year':2013,
            'value':[
                      {x: 1, y: 11.3},
                      {x: 2, y: 10.2},
                      {x: 3, y: 11.1},
                      {x: 5, y: 8.4},
                      {x: 7, y: 9.5},
                      {x: 8, y: 8.1},
                      {x: 10, y: 7.5},
                      {x: 12, y: 6.8},
                    ]
      },
      {
        'year':2014,
            'value':[
                      {x: 1, y: 14.3},
                      {x: 3, y: 11.7},
                      {x: 5, y: 8.4},
                      {x: 8, y: 6.3},
                      {x: 10, y: 6.8},
                      {x: 12, y: 7.8},
                    ]
      },
      {
        'year':2015,
            'value':[
                      {x: 2, y: 12.3},
                      {x: 4, y: 10.7},
                      {x: 6, y: 7.4},
                      {x: 10, y: 5.8},
                      {x: 11, y: 6.8},
                    ]
      },

    ]
  
;

// svg用の属性
var svg = {height:400,width:500};
var domain = {x: [0, 13], y: [0, 15]};
var range = {x: [50, 450], y: [350, 50]};
var axis = {
  xTicks: [1,2,3,4,5,6,7,8,9,10,11,12],
  yTicks: [2,4,6,8,10,12,14]
};

// dataから年を抽出
var years = _.pluck(data,'year'); 

// Chart コンポーネントの出力
React.render(
  <Chart
     data={data}
     svg={svg}
     domain={domain}
     range={range}
     axis={axis}
     years={years}
  />,
  document.getElementById('svg01')
);