D3.jsを使ってアクセス状況を折れ線グラフ化
MariaDBに格納したアクセス履歴をPHPのAPIでJson形式にし、折れ線グラフで表示する
アクセス状況の可視化
処理の流れ
- PHP: MariaDBから1ヶ月間のアクセス件数を抽出し、Json形式で出力するAPIを作成
- JS: D3.jsのスクリプトで折れ線グラフを描画
ポイントは、メイン画面のサイズ変更に対応してグラフのサイズも変更
PHP API ソースはこちら
PHP API ソースはこちら
ini_set('display_errors',"on");
//
// サーバー情報
// 月を指定
$month = "";
if(isset($_GET["month"])){
$month = $_GET["month"];
}
// 1ヶ月前の日付
$one_month_date = date('Y-m-d H:i:s',strtotime("-1 month"));
// database へ接続
$dsn = "mysql:host=$hostname;dbname=$dbname;charset=utf8";
try{
$dbh = new PDO("$dsn",$user,$passwd,array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
} catch (PDOException $e){
echo "<br>$dsn,$user,$passwd<br>";
echo "Error:".$e->getMessage();
die();
}
// 一番最初のアクセスを検出
$sql = "SELECT ";
$sql.= "DATE_FORMAT(access_date,'%Y-%m-%d') AS date,";
$sql.= "COUNT(*) AS count,";
$sql.= "SUM(count) AS view ";
$sql.= "FROM access_tbl ";
$sql.= "WHERE ";
if($month==""){
$sql.= "access_date > '$one_month_date' ";
} else {
$sql.= "DATE_FORMAT(access_date,'%Y-%m') = '{$month}' ";
}
$sql.= "GROUP BY ";
$sql.= "DATE_FORMAT(access_date,'%Y-%m-%d') ";
$sql.= "ORDER BY access_date";
$month_list = array();
try {
$sth = $dbh->query($sql);
while ($row = $sth->fetch(PDO::FETCH_ASSOC)){
$month_list[]=$row;
}
} catch (PDOException $e){
echo "<br>$sql<br>";
echo "Error:".$e->getMessage();
die();
}
// 日付を補完
// その月の日数を取得
$m_width = 30;
if($month != ""){
$tmp = explode("-",$month);
$month_time = mktime(0,0,0,$tmp[1],1,$tmp[0]);
$m_width = date("t",$month_time)-1;
$one_month_date = date("Y-m-d",$month_time);
}
// アクセスが0の日を配列に追加
$d=0;
$i=0;
while(1){
$ad = date('Y-m-d',strtotime("{$one_month_date} + {$d} day"));
$add_ar = array(
$i => array(
'date' => $ad,
'count' => 0,
'view' => 0));
if($i>=count($month_list)){
$month_list = array_merge($month_list,$add_ar);
} else {
$access_date = $month_list[$i]["date"];
if($ad != $access_date){
if($i == 0) $month_list = array_merge($add_ar,$month_list);
else {
$ar1 = array_slice($month_list,0,$i);
$ar2 = array_slice($month_list,$i);
$month_list = array_merge($ar1,$add_ar,$ar2);
}
}
}
if($ad == date('Y-m-d') || $d>=$m_width) break;
$d++;
$i++;
}
// Json型式で出力
echo json_encode($month_list);
js ソースはこちら
// svgの基本構成
var svgWidth = 640;
var svgHeight = 400;
var padding = 30;
var aspect = svgWidth / svgHeight;
var svg = d3.select("#access_svg");
// 画面変更前の描画
svgWidth = window.innerWidth;
if(svgWidth > 1024) svgWidth = 1024;
svgHeight = svgWidth / aspect;
if(svgHeight > 400) svgHeight = 400;
svg.attr("width", svgWidth);
svg.attr("height",svgHeight);
svgChart(svgWidth,svgHeight);
// 画面を変更した場合の描画
d3.select(window)
.on("resize", function(){
svgWidth = window.innerWidth;
if(svgWidth > 1024) svgWidth = 1024;
svgHeight = svgWidth / aspect;
if(svgHeight > 400) svgHeight = 400;
svg.selectAll("path").remove();
svg.selectAll("g").remove();
svg.attr("width", svgWidth);
svg.attr("height",svgHeight);
svgChart(svgWidth,svgHeight);
});
// 折れ線グラフを描画
function svgChart(svgWidth,svgHeight){
// PHP API からJSONデータを受け取り処理を行う
d3.json("access_api.php").then(function(dataSet){
// 日付のフォーマット
var timeparser = d3.timeParse("%Y-%m-%d");
dataSet = dataSet.map(function(d){
return { date: timeparser(d.date), count: parseInt(d.count), view: parseInt(d.view) };
});
// X軸の設定
var xScale = d3.scaleTime()
.domain([d3.min(dataSet, function(d){return d.date;}), d3.max(dataSet, function(d){return d.date;})])
.range([padding, svgWidth - padding]);
// Y軸の設定
var yScale = d3.scaleLinear()
.domain([0,d3.max(dataSet, function(d){return d.view;})])
.range([svgHeight - padding, padding]);
// X軸の書式設定
var axisx = d3.axisBottom(xScale)
.ticks(10)
.tickFormat(d3.timeFormat("%d"));
// Y軸の書式設定
var axisy = d3.axisLeft(yScale);
// アクセス数の描画設定
var lineCount = d3.line()
.x(function(d) { return xScale(d.date); })
.y(function(d) { return yScale(d.count); });
// 閲覧数の描画設定
var lineView = d3.line()
.x(function(d) { return xScale(d.date); })
.y(function(d) { return yScale(d.view); })
// SVG に x軸を追加
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + 0 + ", " + (svgHeight - padding) + ")")
.call(axisx);
// SVG に Y軸を追加
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ", " + 0 + ")")
.call(axisy);
// アクセス数を追加
svg.append("path")
.datum(dataSet)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 1.5)
.attr("d", lineCount);
// 閲覧数を追加
svg.append("path")
.datum(dataSet)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 1.5)
.attr("d", lineView);
});
}
function clearChart(chartId){
d3.select(chartId).remove();
}