3)レーダーコンポーネント
今までのコンポーネントはHTMLをレンダリングしていましたが、レーダーコンポーネントはSVG でレンダリングします。HTMLかSVG かの違いだけで、大きな違いはありません。なお、SVG はIE9以降のモダンなブラウザであれば対応していますので、ほとんどの環境で問題なく描画できます。IE6,7,8にも対応させるのであれば、VMLという技術も合わせて使う必要がありますが、今回は対応しません。VML(Vector Markup Language)は、2次元コンピュータグラフィックスをコンピュータ内部で表現するデータ形式であるベクター形式の画像を描画するための XML 言語です。
まず、Radar.jsxを見ていきます。
var React = require'react';var RadarForeground = require'./RadarForeground';var RadarBackground = require'./RadarBackground';var Constants = require'../Constants';var Radar = ReactcreateClassreturn<svg key="Radar" width=ConstantsRADER_WIDTH height=ConstantsRADER_HEIGHT><RadarBackground yearMonth=thispropsyearMonth categories=thispropscategories isChildCategory=thispropsisChildCategory/><RadarForeground dotPosition=thispropsdotPosition selectedOssId=thispropsselectedOssId/></svg>;;moduleexports = Radar;
上記のコードをみると分かりますが、svgタグの内部に二つのコンポーネントがあります。
メソッド名 | 描画するもの |
---|---|
RadarBackground | レーダーの背景 |
RadarForeground | ランキングを表すドット |
実際の描画はそれぞれのコンポーネント内で行われます。まずは、RadarBackgroundから見ていきましょう。
RadarBackground
return<g>this_circlesthis_bordersthis_ranksthis_categoryName</g>;
renderメソッドは見ると、4つのメソッドから成り立っています。それぞれを順番に見ていきます。
メソッド名 | 説明 |
---|---|
_circles | 背景の円を描画 |
_borders | 円を区切る直線 |
_ranks | 1,2,3,4と描かれている目盛り |
_categoryName | カテゴリ名 |
_circlesメソッド
このメソッドは静的に円を4つ描きます。生成されるSVG は下記の通りです。特に難しいことはしていません。
上記のDOM を生成するための配列を返します。circleタグを配列に詰めて返すだけです。ソースコードは下記の通りです。
var circles = ;for var i = 0; i < 4; i++circlespush<circle key="circle-" + i cx=ConstantsRADER_CENTER_X cy=ConstantsRADER_CENTER_Y r=i + 1 * ConstantsRADER_SPACING fill=ConstantsRADER_FILL stroke=ConstantsRADER_COLOR></circle>;return circles;
_bordersメソッド
このメソッドはカテゴリの数によって引く線の数を変更します。カテゴリ名が5つであれば、5本。カテゴリ名が6つであれば、6本です。引く本数によって角度を計算し、描画します。実際に生成されるSVG は下記の通りです。
これをソースコードで表すと下記のようになります。このとき、カテゴリの数で描画する線の数は変わるので、カテゴリの数から角度を計算し、三角関数を使用してXY座標に変換します。
var borders = ;for var i = 0 len = thispropscategorieslength; i < len; i++var seta = 2 * MathPI / len * i;var y = ConstantsRADER_RADIUS * Mathsinseta - MathPI / 2;var x = ConstantsRADER_RADIUS * Mathcosseta - MathPI / 2;borderspush<path key="borders-" + i fill=ConstantsRADER_FILL stroke=ConstantsRADER_COLOR d='M320,330l' + x + ',' + y + 'z'></path>;return borders;
_ranksメソッド
レーダーのランクを表す数字を描画します。これはレーダーによって変わるものではないため、静的に以下のSVG を描画します。
4321
ソースコードは下記の通りです。
var ranks = ;for var i = 1; i <= 4; i++var y = ConstantsRADER_CENTER_Y - ConstantsRADER_SPACING * i + ConstantsRADER_SPACING / 2;rankspush<text key="ranks-" + i className="rank-text" x=ConstantsRADER_CENTER_X y=y font="10px Arial">5 - i</text>;return ranks;
_categoryNameメソッド
カテゴリの数だけカテゴリ名を描画します。実際に描画されるSVG のサンプルは下記の通りです。
ライブラリ/フレームワークアプリケーションツールミドルウェアプラットフォーム言語処理系
このとき、先ほど_bordersメソッドと同じように描画した線と線のちょうど中間にカテゴリ名のテキストが描画されるように計算します。
var _this = this;return thispropscategoriesmapvar textStyle =fontSize: '14px'fontFamily: 'Arial'textAnchor: 'middle';if !_thispropsisChildCategory textStylecursor = 'pointer';var translate = 'translate(' + ConstantsRADER_CENTER_X + ',' + ConstantsRADER_CENTER_Y + ')';var arc = 360 / _thispropscategorieslength;var radian = arc * i + arc / 2;var rotate = 'rotate(' + radian + ')';var transform = translate + ' ' + rotate;// 下側に来たラベルは180度回転させるvar labelRot = radian > 90 && radian < 270 ? 180 : 0;return<g key='category-label-group-' + i transform=transform><text key='category-label-' + i className="category-label" data-categoryid=categoryid transform='rotate(' + labelRot + ',0,-300)' y="-300" stroke="none" fill="#666666" style=textStyle>categorydisplayName</text></g>;;
その他のメソッド
componentDidMountメソッドは、このコンポーネントがDOM ツリーに追加された際に実行されます。ここでは、bodyに対してclickイベントを追加しています。bodyタグにclickイベントを割り当てておくことで、下位のDOM に発生したclickイベントを全てここでキャッチすることができます。各要素に対してclickイベントハンドラを割り当てるよりも効率的なため、イベントはなるべく上の階層で受け取るようにするのがベターです。
リスナとして登録している_categoryClickメソッドは下記の通りです。
if etargettagNametoUpperCase !== 'TEXT' || thispropsisChildCategory return;var categoryId = etargetgetAttribute'data-categoryid';thistransitionToConstantsROOT_PATH + 'radarScope/category/' + categoryId + '/' + thispropsyearMonth;
クリックされたタグがSVG のtextタグであった場合に、画面遷移します。つまり、この場合はカテゴリをクリックされた場合と同義になります。ReactRouterのtransitionToメソッドを実行します。
RadarForeground
var React = require'react';var uuid = require'node-uuid';var Dot = require'./Dot';var RadarStore = require'../stores/RadarStore';var Constants = require'../Constants';var RadarForeground = ReactcreateClassvar _this = this;var positions = thispropsdotPosition;var x = {};return<g key="foreground">positionsmapreturn <Dot key='dot-' + posproductid num=posnum fill=poscolor product=posproduct x=posx y=posy selectedOssId=_thispropsselectedOssId/></g>;;moduleexports = RadarForeground;
RadarForegroundではドットを描画しています。単純にpropsとしてわたされたdotPositionの情報をもとにDotを作成しています。
Dot
Dotはそれぞれ上記のドット一つ一つを表します。単体で考えるとなにも難しくありません。ただし、Dotコンポーネントはポジションが変わった際に、アニメーションを行います。この機能にはSVG のSMIL アニメーション機能のanimateTransformタグを使用していますが、React.jsがこのタグをうまく認識してくれなかったため、componentDidUpdateメソッドでanimateTransformタグを作成します。
実際のDOM は以下のようになります。
110
また、SMIL に対応していないブラウザでも同じように表示するために、分岐が発生しています。
var circleStyle =fill: thispropsfillstroke: thispropsfill;var textStyle =fontFamily: 'Arial'stroke: 'none'fill: '#ffffff'textAnchor: 'middle'fontSize: '9px'fontWeight: 'bold';if !smilSupport || !this_prevXvar transform = 'translate(' + thispropsx + ',' + thispropsy + ')';elsevar transform = 'translate(' + this_prevX + ',' + this_prevY + ')';var key = "dot-" + thispropsnum;return<g key=key ref="g" transform=transform>this_selectedCirclekey<circle key=key + '-circle' r="8" style=circleStyle onMouseOver=this_onMouseOver></circle><text key=key + '-text' y="3" style=textStyle onMouseOver=this_onMouseOver>thispropsnum</text></g>;
animateTransformタグについてはcomponentDidUpdateメソッドで直接DOM を操作しています。
終わりに
業務時間外の時間を費やした1ヶ月でしたが、React.jsを使用してOSS Radar Scope® を再実装することができました(※Internet Explorer へは未対応のため、他のブラウザで表示してください。また、表示されましたら、左の列の"表示月"を指定してみてください)。
React.jsを使用するとあまり複雑化せずに、見通しの良いコードを書くことができると感じています。React.jsを使用した複数人での開発は未経験ですが、コンポーネントを明確に分けることができるので、従来のjQueryよりは開発がしやすいのではないかと思います。今後、実際の開発案件でも使うようになったら良いと思っております。