D3.jsを使ってアクセス状況を折れ線グラフ化

MariaDBに格納したアクセス履歴をPHPのAPIでJson形式にし、折れ線グラフで表示する

アクセス状況の可視化

処理の流れ

ポイントは、メイン画面のサイズ変更に対応してグラフのサイズも変更
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();
}