D3.jsを使ってテニストッププロのサーブゲーム勝率とリターンゲーム勝率を可視化してみた
公開日:
D3.jsとは
タイトルにあるようにD3.jsを使って、テニスのトッププロ(2018年10月現在ATPランク15位以内)のサーブゲーム勝率とリターンゲーム勝率の関係を可視化してみました。
D3.jsとは、JavaScriptのデータ可視化ライブラリのことです。(D3の公式サイト)Webサイト上で綺麗なグラフや凝ったビジュアライズをやりたい場合に多く利用されるライブラリのようです。
サーブやリターンのポイント率やゲーム勝率を2軸の散布図で表現する可視化手法はよくみます。散布図にすると、その選手がサーブが得意な選手かリターンが得意な選手か立ち位置がパッと見でわかるんですよね。
自分もデータを加工してJPEGとか写真にして貼り付けてSNSで公開するなんてことはよくやりますが、JavaScriptとかで表現できるようにしたく。
いい感じのライブラリがないかを探すとD3.jsがよさそうで。
グラフ化するにあたって、D3.js以外のライブラリも調べました。背景を色分けできる、プロット点近くに文字を表示する、などが条件だったんですが、他のライブラリはググってもみつからず。
汎用性が高いD3.jsを選んでやってみた次第です。
サーブゲーム勝率とリターンゲーム勝率
↓が可視化してみたグラフです。
2018.10.15付のデータを用いていて、参照元はATPの公式サイトです。
サーブが得意ゾーンとリターンが得意ゾーンと色分けしてますが、線引きはエイヤでやってます。
データ
こちらのデータを可視化してます。
今後のやりたいこと
こんな感じでインタラクティブなページをつくりたい
・選手名をクリックするとその選手のプロットがライトアップ
・過去のデータも表示したり最新データも更新
とかとか。
コード(statsvis.html)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
<head> <meta charset="utf-8"> <title>D3 Scatter Plot</title> <style> .axis svg{ fill:Aquamarine; opacity:0.1; stroke:#000; stroke-width:1; shape-rendering:crispEdges; } </style> <script src="/d3/d3.min.js"> </script> </head> <body> <script> var width = 400; // グラフの幅 var height = 300; // グラフの高さ var margin = { "top": 30, "bottom": 60, "right": 30, "left": 60 }; d3.csv("/d3/ServeReturnGameRating.csv").then(function(data) { var dataset = []; var label = []; console.log(data[0]['Player']); for(var i=0; i<data.length; i++){ dataset.push([data[i].ServiceGamesWon,data[i].ReturnGamesWon]); label.push([data[i].ServiceGamesWon,data[i].ReturnGamesWon,data[i].Player]);//data[i].Player] } //console.log(label); make(dataset,label); }); // 2. SVG領域の設定 function make(dataset,label){ console.log(label); var svg = d3.select("#result").append("svg").attr("width", width).attr("height", height); // 3. 軸スケールの設定 var xScale = d3.scaleLinear() .domain([70, d3.max(dataset, function(d) { return d[0]; })]) .range([margin.left, width - margin.right]); var yScale = d3.scaleLinear() .domain([10, d3.max(dataset, function(d) { return d[1]; })]) .range([height - margin.bottom, margin.top]); // 4. 軸の表示 var axisx = d3.axisBottom(xScale).ticks(5); var axisy = d3.axisLeft(yScale).ticks(5); svg.append("g") .attr("transform", "translate(" + 0 + "," + (height - margin.bottom) + ")") .call(axisx) .append("text") .attr("fill", "black") .attr("x", (width - margin.left - margin.right) / 2 + margin.left) .attr("y", 35) .attr("text-anchor", "middle") .attr("font-size", "10pt") .attr("font-weight", "bold") .text("Service Games Won %"); svg.append("g") .attr("transform", "translate(" + margin.left + "," + 0 + ")") .call(axisy) .append("text") .attr("fill", "black") .attr("x", -(height - margin.top - margin.bottom) / 2 - margin.top) .attr("y", -35) .attr("transform", "rotate(-90)") .attr("text-anchor", "middle") .attr("font-weight", "bold") .attr("font-size", "10pt") .text("Return Games Won %"); // 5. プロットの表示 svg.append("g") .selectAll("circle") .data(dataset) .enter() .append("circle") .attr("cx", function(d) { return xScale(d[0]); }) .attr("cy", function(d) { return yScale(d[1]); }) .attr("fill", "steelblue") .attr("r", 4); svg.append("g").selectAll("text") .data(label) .enter() .append("text") .text(function(d){ return d[2]; }) .attr("transform", function(d) { return "translate(" + (xScale(d[0])+2) + "," + (yScale(d[1])-4) + ")"; }) // 位置を調整 .attr("font-family", "tahoma") // フォントを指定 .attr("font-size", "9px") // 文字サイズを指定 .attr("fill", "black") // 文字の色を指定 var sZone = [[85,35], [78,10], [95,10], [95,35]]; svg.append("path") .datum(sZone) .style("fill", "FF0000") .style("opacity",0.1) .attr("d", d3.line() .x(function(d) { return xScale(d[0]); }) .y(function(d) { return yScale(d[1]); })); var rZone = [[70,35], [70,10], [78,10], [85,35]]; svg.append("path") .datum(rZone) .style("fill", "003200") .style("opacity",0.1) .attr("d", d3.line() .x(function(d) { return xScale(d[0]); }) .y(function(d) { return yScale(d[1]); })); } |