はじめまして!SI部の杉田と申します。
現在マークアップエンジニア目指して勉強中です。
今日はその中で勉強した、「インラインSVG」についてご紹介したいと思います!

目次

・概要
・導入編(インラインSVGについて)
・土台編(レーダーチャートの土台を作る)
・項目表示編(レーダーチャートにチャートの項目を作る)
・チャート編(フォームで入力した値でチャートを作る)
・ソース
・さいごに

概要

そもそもSVGとはScalable Vector Graphicsの略で、ベクター形式の画像フォーマットのことです。
JPEGやPNGなどのビットマップ形式とは違い、SVG画像は画像が拡大縮小回転されて表示されても画質が落ちたりすることがありません。
インラインSVGは、HTHML5で追加されたcanvasという要素に似ていて、HTML要素に直接SVGを記述することができます。

使い方は

<svg></svg>

で表示する場所を作り、その内側で以下のタグを使うことができます。
それそれが属性を持っており、javascript等で操作し細かく思い通りの図形を作ることができます。

・circle(円)
・polygon(多角形)
・rect(四角形)
・ellipse (楕円)
・line(線)
・polyline (連続直線)
・text (テキスト)

土台編(レーダーチャートの土台を作る)

それでは早速レーダーチャートを作っていきましょう!
ゴールは下記のようなレーダーチャートです。
レーダーチャート見本

<div class="svg_container">
<svg id="chart_container">
<g id="chart_foundation_pentagons"></g>
<g id="chart_Polygon"></g>
<g id="marker_points_wrap"></g>
</svg>
</div>

まず、レーダーチャートを描画する領域として、svg_containerにSVGタグを作成します。
このSVGタグに対して、後述のcircleタグやpolygonタグをいれて、レーダーチャートを作っていくという流れになります。
タグは、SVGタグの中で要素をグループ分けする際に使用します。

今回は、
・レーダーチャートの土台(<g id=”chart_foundation_pentagons”>)
・レーダーチャート(<g id=”chart_Polygon”>)
・レーダーチャートのポイントに乗る点(<g id=”marker_points_wrap”>)
を分けて作っていきます。

#svg_container{
 width:600px;
 height:450px;
 margin:auto;
}
#chart_container{
 width:500px;
 height:500px;
 display: block;
 margin: auto;
}

SVGタグには、cssで500px×500pxに設定し、親タグのsvg_containerにはすこし大きめの600pxX450pxを設定しました。
id=chart_containerが付いたSVG要素ですが、display:block;をかけることでその後のmargin:auto;が生きてきます。
なお、今回の投稿はCSSの記述を全て解説しているわけではありませんので、気になる方は最後に記載したソースで
確認してみてください。

次に、レーダーチャートの土台を作成していきます。
今回は要素をそのままHTMLに書いていくのではなく、javascriptを使って動的に作ってみたいと思います。

//五角形を生成
function createPentagons(dis){
 //polygon要素を生成
 var Pentagon=document.createElementNS("http://www.w3.org/2000/svg","polygon");
 var pentagonPoints ="";
 for (var i = 0; i < 5; i++){
var x = Math.cos(((72*i)-90)*Math.PI/180)*dis;
var y = Math.sin(((72*i)-90)*Math.PI/180)*dis;
pentagonPoints += (x+250) +","+ (y+220) + " ";
}
 allPentagonPoints.push(pentagonPoints);
 //座標を設定
 Pentagon.setAttribute("points",pentagonPoints);
 return Pentagon;
}

//レーダーチャートの土台を作る。生成した五角形を表示
function drawChartFoundation(){
 //中心からの距離ごとに五角形をつくる
 var distance = [1,50,100,150,200];
 for(var i=0; i<distance.length; i++){
  document.getElementById("chart_foundation_pentagons").appendChild(createPentagons(distance[i]));
 }
}

4行目を見てください。

document.createElementNS("http://www.w3.org/2000/svg","polygon");

javascriptでは、SVG要素を作成する際このように記述します。
この記述は「SVGDocumentでpolygonを作る」という意味になります。
※今回jQueryも使っていますが、jQeryはHTMLDOM要素を操作する前提で作られたものなので、SVGDOM要素を操作する際は正しく動作しません。なので、SVGを操作する場合はjqueryを使用していません。

これをふまえて土台となる五角形を作るのですが・・・

var pentagonPoints ="";
 for (var i = 0; i < 5; i++){
var x = Math.cos(((72*i)-90)*Math.PI/180)*dis;
var y = Math.sin(((72*i)-90)*Math.PI/180)*dis;
pentagonPoints += (x+250) +","+ (y+220) + " ";
}

突然の数学です。
上記で五角形の各角度の座標を割り出しているのですが、五角形の描き方まで解説すると
大変なことになりそうなので、インラインSVGに関する部分を解説します。
コンパスを使って五角形を描くのは割りと簡単ですが、プログラムで描くのは至難ですね・・・

createPentagons関数では、五角形のpolygon要素を生成しています。

Pentagon.setAttribute("points",pentagonPoints);

11行目で、作成したpolygon要素のsetSttributeメソッドを使ってpointsプロパティを設定しています。

pentagonPoints変数には、五角形の各角度の座標が、x,yの5つのセットがスペースで区切られ文字列で入っています。
これがHTMLになると<polygon points=”x,y x,y x,y x,y x,y”>となります。
pointsプロパティの値は、一番上、右上、右下、左下、左上の順に並んでいます。

drawChartFoundation関数では、中心からの距離ごとにcreatePentagons関数で作られた五角形を<g id=”chart_foundation_pentagons”>に挿入していますので、最終的にgタグの中には5つの五角形要素が入ることになりますね。

作った五角形は下記のようにCSSで装飾を加えます。

#chart_foundation_pentagons {
 fill:none;//塗りつぶしなし
 stroke: #7ac5e9;//線の色を設定
 stroke-width:1px;//線の太さ
 stroke-dasharray:3px;//線を破線にしたときの、破線の幅
}

これで土台ができました。
レーダーチャート土台
続いて、レーダーチャートの項目を表示していきます。

項目表示編(レーダーチャートにチャートの項目を作る)

//レーダーチャートの項目を表示
function drawChartSkillLabels(){
 $(skills).each(function(index,val){
  var maxPentagonNum = allPentagonPoints.length-1;
  var maxPentagonPointsX = allPentagonPoints[maxPentagonNum].split(" ")[index].split(",")[0];
  var maxPentagonPointsY = allPentagonPoints[maxPentagonNum].split(" ")[index].split(",")[1];
  var text = document.createElementNS("http://www.w3.org/2000/svg","text");
  text.innerHTML = val
  text.setAttribute("x",maxPentagonPointsX)
  text.setAttribute("y",maxPentagonPointsY)
  text.setAttribute("class","skill_label")
  if(index >= (skills.length/2+0.5)){
   text.setAttribute("text-anchor","end")
  }
  document.getElementById("chart_foundation_pentagons").appendChild(text);
 })
}

drawChartSkillLabels関数では表示項目であるスキルごとに処理を行います。
skillsという配列変数には、表示項目の[“HTML”,”CSS”,”javascript”,”PHP”,”Ruby”]という値が入っています。

下記のコードを見てみましょう。

text.setAttribute("x",maxPentagonPointsX)
text.setAttribute("y",maxPentagonPointsY)

上記の2行でtext要素のx,yプロパティに、x座標とy座標を設定して表示する位置を指定します。
ここで必要になるのが土台の1番外側の五角形の座標、つまりallPentagonPoints配列の4番目の座標群です。
ですが、4番目の座標たちは全て文字列でつながっているので、text要素の表示位置を決めるために”x”と”y”に分割しなければなりません。
それが3,4行目の処理で、splitを使いスペースと、カンマで区切っていきます。

allPentagonPoints配列の各五角形の角の座標は、一番上、右上、右下、左下、左上の順に並んでいます。
例えば、”CSS”を表示する位置は、一番外側の五角形の右上なのでallPentagonPoints配列のうち4番目の座標の2番目の座標を取得する必要があります。
そして取得した座標を使い、項目文字列の位置を設定します。ですが、このままだと左半分の項目がチャートに被ってしまうので、下記のように設定します。

text.setAttribute("text-anchor","end")

これは表示する基準点を操作しており、左半分の項目は語尾が基準点になるように設定しています。

スキルのラベルには下記のようなCSSを適応させました。

.skill_label{
 margin-right: 10px;
 margin-left: 10px;
 display: block;
 fill:#808080;
 stroke: none;
 stroke-dasharray:0px;
}

インラインSVGのテキスト要素は、文字列ですが保持している情報は、HTMLのテキスト要素とは違いがあります。
たとえば、線の色はfillプロパティを使用設定します。strokeプロパティに色を設定すると中抜き文字になりますので、デザインにこだわる際使用してみると面白いかもしれませんね。

これで土台が仕上がりました。
土台にスキルの文字列を追加
続いて、土台の上に乗せるチャートを作成していきます。

チャート編(フォームで入力した値でチャートを作る)

フォームに入力された値で、土台の上にチャートをのせます。
まずフォームを作るために、HTMLを追加しましょう。

<div id="select_skill_container">
 <input type="submit" value="submit" id="submit"/>
</div>
//スキルごとのレベル選択フォームを作成
function drawSelectLevelForm(){
 var selectLavelElem =""
 var optionElem = ""
 for(var i=0; i<=4; i++){
  optionElem += '<option value="'+ [i] +'">level'+ [i] +'</option>'
 }
 for(var j=0; j<skills.length; j++){
  selectLavelElem += '<label for="select_level_'+ skills[j] +'">'+ skills[j] +'</label><select id="select_level_'+ skills[j] +'" class="select_level" >' + optionElem +'</select>'
 }
 $("#select_skill_container").prepend(selectLavelElem)
}

スキルごとにレベル4まで選択できるようなフォームをjavascriptでループをまわして作成します。
option要素にはレベルを判別するための番号を設定しました。
文字列でHTMLを作って上記で追加したHTML要素に挿入しているだけです。

//フォームで入力した値でチャートを生成
$("#submit").click(function(){
 $(".status,.marker_point").remove();
 //polygon要素を生成
 var chartPolygon = document.createElementNS("http://www.w3.org/2000/svg","polygon");
 var chartPolygonPoint =""
 //chartPolygonの座標を設定
 $(".select_level").each(function(i){
  var levelNum = parseInt($(this).val());
  var selectedPoint = allPentagonPoints[levelNum].split(" ")[i].split(",");
  chartPolygonPoint += selectedPoint[0] +","+ selectedPoint[1] + " ";
  drawchartPointMarker(selectedPoint[0],selectedPoint[1]);
 });
 chartPolygon.setAttribute("points",chartPolygonPoint);
 chartPolygon.setAttribute("class", "status");
 document.getElementById("chart_Polygon").appendChild(chartPolygon);
});

先ほどHTML側にsubmitボタンを作っておいたので、ボタンを押すと入力された値でチャートが出力されるように処理を追加します。

var selectedPoint = allPentagonPoints[levelNum].split(" ")[i].split(",");

“select_level”というクラス名が付いたoption要素のvalを抜き出して処理対象のスキルを特定します。
select要素の全てを順番に見て、各スキルのレベルを判別し、座標をallPentagonPoints変数から取り出します。
取り出された座標は上記の記述で、chartPolygonPoints変数に追加されていき、polygon要素のpointsプロパティで設定できるように整形しておきます。
そしてid=”chart_Polygon”のgタグにchartPolygon変数を挿入していくことで、チャートを描画することができます。

//赤い多角形の頂点に点を表示
function drawchartPointMarker(x,y){
 //circle要素を生成
 var marker = document.createElementNS("http://www.w3.org/2000/svg","circle");
 marker.setAttribute("cx",x)
 marker.setAttribute("cy",y)
 marker.setAttribute("r",5)
 marker.setAttribute("class","marker_point")
 document.getElementById("marker_points_wrap").appendChild(marker);
}

スキルとレベルに一致した座標を、allPentagonPoints変数から取り出した後、スペースとカンマで区切り、drawchartPointMarker関数に渡してあげれば、チャートの頂点に●をおくことができます。
polygon要素と同様にcircle要素を生成し、setAttributeメソッドでcx(x座標)、cy(y座標)、r(半径)を設定します。
またクラス名を設定しておき、submitボタンを押すたびに●点がリセットできるようにしました。

これで完成です!ブラウザの設定から拡大縮小をいじってみてください。
完成
描かれたレーダーチャートは拡大しても劣化せず、きれいに表示されていると思います!

<!DOCTYPE html>
<html>
  <meta charset="UTF-8">
  <head>
   <title>インラインSVGでレーダーチャートを作る</title>
  </head>
  <script src="jquery-2.0.2.min.js" type="text/javascript" charset="utf-8"></script>
  <script>
   $(function(){
    var allPentagonPoints = []
     var skills = ["HTML","CSS","javascript","PHP","Ruby"]
     drawSelectLevelForm()
     drawChartFoundation()
     drawChartSkillLabels()

     //五角形を生成
     function createPentagons(dis){
      //polygon要素を生成
      var Pentagon=document.createElementNS("http://www.w3.org/2000/svg","polygon");
      var pentagonPoints ="";
      for (var i = 0; i < 5; i++){
var x = Math.cos(((72*i)-90)*Math.PI/180)*dis;
var y = Math.sin(((72*i)-90)*Math.PI/180)*dis;
pentagonPoints += (x+250) +","+ (y+220) + " ";
}
      allPentagonPoints.push(pentagonPoints);
       //座標を設定
       Pentagon.setAttribute("points",pentagonPoints);
       return Pentagon;
      }

     //レーダーチャートの土台を作る。生成した五角形を表示
     function drawChartFoundation(){
      //中心からの距離ごとに五角形をつくる
      var distance = [1,50,100,150,200];
      for(var i=0; i<distance.length; i++){
document.getElementById("chart_foundation_pentagons").appendChild(createPentagons(distance[i]));
      }
     }

     //レーダーチャートの項目を表示
     function drawChartSkillLabels(){
      $(skills).each(function(index,val){
       var maxPentagonNum = allPentagonPoints.length-1;
       var maxPentagonPointsX = allPentagonPoints[maxPentagonNum].split(" ")[index].split(",")[0];
       var maxPentagonPointsY = allPentagonPoints[maxPentagonNum].split(" ")[index].split(",")[1];
       var text = document.createElementNS("http://www.w3.org/2000/svg","text");
       text.innerHTML = val
       text.setAttribute("x",maxPentagonPointsX)
       text.setAttribute("y",maxPentagonPointsY)
       text.setAttribute("class","skill_label")
       if(index >= (skills.length/2+0.5)){
        text.setAttribute("text-anchor","end")
       }
       document.getElementById("chart_foundation_pentagons").appendChild(text);
       })
      }

     //スキルごとのレベル選択フォームを作成
     function drawSelectLevelForm(){
      var selectLavelElem =""
      var optionElem = ""
      for(var i=0; i<=4; i++){
       optionElem += '<option value="'+ [i] +'">level'+ [i] +'</option>'
      }
      for(var j=0; j<skills.length; j++){
       selectLavelElem += '<label for="select_level_'+ skills[j] +'">'+ skills[j] +'</label><select id="select_level_'+ skills[j] +'" class="select_level" >' + optionElem +'</select>'
      }
      $("#select_skill_container").prepend(selectLavelElem)
     }

     //フォームで入力した値でチャートを生成
     $("#submit").click(function(){
      $(".status,.marker_point").remove();
      //polygon要素を生成
      var chartPolygon = document.createElementNS("http://www.w3.org/2000/svg","polygon");
      var chartPolygonPoint =""
      //chartPolygonの座標を設定
      $(".select_level").each(function(i){
       var levelNum = parseInt($(this).val());
       var selectedPoint = allPentagonPoints[levelNum].split(" ")[i].split(",");
       chartPolygonPoint += selectedPoint[0] +","+ selectedPoint[1] + " ";
       drawchartPointMarker(selectedPoint[0],selectedPoint[1]);
      });
      chartPolygon.setAttribute("points",chartPolygonPoint);
      chartPolygon.setAttribute("class", "status");
      document.getElementById("chart_Polygon").appendChild(chartPolygon);
     });

     //赤い多角形の頂点に点を表示
     function drawchartPointMarker(x,y){
      //circle要素を生成
      var marker = document.createElementNS("http://www.w3.org/2000/svg","circle");
      marker.setAttribute("cx",x)
      marker.setAttribute("cy",y)
      marker.setAttribute("r",5)
      marker.setAttribute("class","marker_point")
      document.getElementById("marker_points_wrap").appendChild(marker);
     }
    })
  </script>
  <style>
   body,input,select{
    font-family: verdana;
    color: #808080;
   }
   #svg_container{
    width:600px;
    height:450px;
    margin:auto;
   }
   #chart_container{
    width:500px;
    height:500px;
    display: block;
    margin: auto;
   }
   #chart_foundation_pentagons {
    fill:none;
    stroke: #7ac5e9;
    stroke-width:1px;
    stroke-dasharray:3px;
   }
   .skill_label{
    margin-right: 10px;
    margin-left: 10px;
    display: block;
    fill:#808080;
    stroke: none;
    stroke-dasharray:0px;
   }
   #chart_Polygon{
    position: absolute;
    fill:#ff8e8e;
    stroke: none;
    filter:alpha(opacity=50);
    -moz-opacity: 0.5;
    opacity: 0.5;
    z-index: 0;
   }
   #marker_points_wrap{
    fill:#808080;
   }
   #select_skill_container{
    width:650px;
    height:450px;
    margin:auto;
   }
  </style>
  <body>
   <div class="svg_container">
    <svg id="chart_container">
     <g id="chart_foundation_pentagons"></g>
     <g id="chart_Polygon"></g>
     <g id="marker_points_wrap"></g>
    </svg>
   </div>
   <div id="select_skill_container">
    <input type="submit" value="submit" id="submit"/>
   </div>
  </body>
</html>

さいごに

今回初めて、技術ブログで投稿させて頂きました。誰かに説明するということはとても難しいことだと実感しました。
学んだことをアウトプットすることはインプットだけで終わらせてしまうよりずっと効果があると思うので、
これからも続けていきたいと思います。