まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

MySQL テーブル内にバイナリデータを格納する場合、blob 型の列を定義することになります。具体的には blob 型にも複数あり、想定される最大サイズに応じて使うことになります:
最大サイズ(バイト)格納するデータの例
tinyblob255(あまり使われない?)
blob65,535(あまり使われない?)
mediumblob16,777,215画像など
largeblob4,294,967,295動画、音声など


格納するデータの内容によって型を決める必要がありますが、例えば画像、音声、動画といったメディア系のファイルを格納しようとすると tinyblob や blob 型では足りないと思われるので、ほぼ mediumblob か largeblob を使うことになると思われます。1データの最大サイズが 16MB を超える想定があるかどうかで使い分けることになります。ただし largeblob でも 4GB が最大となります。

MySQL で mediumblob 型を使う場合のテーブル定義の例としてはこのようになります:
create table images( id int primary key, img mediumblob ); 

上記例では images テーブル内に mediumblob 型の img 列を定義しました。このテーブルにデータを読み書きする Java のサンプルは後述のようになります。なお後述のサンプル共通で getConnection() という関数を使っていますが、これは java.sql.Connection クラスのインスタンスを返す関数で、具体的には以下のような内容となります:
public Connection getConnection(){
  Connection conn = null;
  try{
    Class.forName( "com.mysql.jdbc.Driver" );
    conn = DriverManager.getConnection( "jdbc:mysql://servername/dbname", "user", "pass" );
  }catch( Exception e ){
    e.printStackTrace();
  }

  return conn;
}


まずはデータの書き込み(insert)です。格納したいバイナリデータが byte[] 型の変数 data に格納されている場合、以下のようなコードで上述の images テーブルに insert することができます:
import java.sql.*;
   : 
 
 
try{
  Connection conn = getConnection();
  String sql = "insert into images( id, img ) values ( ?, ? )";
  PreparedStatement stmt = conn.prepareStatement( sql );
 
 
  stmt.setInt( 1, id ); 
  stmt.setBytes( 2, data ); //. data は byte[] 型 
  int r = stmt.executeUpdate(); 

  stmt.close(); 
  conn.close(); 
}catch( Exception e ){ 
  e.printStackTrace(); 
}

一方、読み出し(select)は以下のようになります。ResultSet からバイナリストリームを取得して ByteArrayOutputStream に書き出し、最後に byte[] 型の変数に変換して取り出しています:
import java.sql.*; 
 : 


byte[] r = null; 

try{ 
  Connection conn = getConnection(); 
  String sql = "select img from images where id = " + id;
  Statement stmt = conn.createStatement(); 

  ResultSet rs = stmt.executeQuery( sql );
  rs.first(); 
  InputStream is = rs.getBinaryStream( 1 );
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  byte[] bs = new byte[1024];
  int size = 0;
  while( ( size = is.read( bs ) ) != -1 ){
    baos.write( bs, 0, size );
  } 

  r = baos.toByteArray();  //. byte[] 型に変換してデータを取得

  stmt.close();
  conn.close(); 
}catch( Exception e ){
  e.printStackTrace();
  r = null; 
}

バイナリデータをサービスシステム内のどこに格納するべきか、という設計の問題を考慮した上で使うべきだと思いますが、この方法で MySQL に格納しておけば、MySQL のバックアップを取ればバイナリデータもまとめてバックアップすることができ、同様に MySQL をリストアすればバイナリデータごとリストアできる、というメリットがあります。


このブログエントリの続きです:
Chart.js のクリックイベントハンドラ


JavaScript で各種グラフを便利・簡単につくれる Chart.js のカスタマイズとして、アイテムをクリックした時のイベントをハンドリングする方法を上記で紹介しました。今回はその応用として「クリックしたアイテムがどれかわかるように視覚化する」カスタマイズです。

具体例としてはこういう感じを想定しています。たとえば普通に棒グラフが描かれているとします:
2019110401


この左から2番目の棒をクリックしようとしているものとします。画面上ではマウスホバーまでしているので、左から2番目の棒の上にツールチップが表示されています:
2019110402


そしてこの左から2番目の棒をクリックした時に「ここがクリックされた」ことを明示することが目的です。下の例では左から2番目の棒のエリアを赤枠で囲って強調されるようにしています:
2019110403


具体的なコード例は以下になります。どの棒がクリックされたか、については click イベントハンドリングを行っていますが、そのあたりの詳細は前回のエントリを参照してください:
<html>
<head>
<meta charset="utf8"/>
<script type="text/javascript" src="//code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.2.1/Chart.min.js"></script>

<script>
$(function(){
  var data_labels = [ 0, 1, 2, 3, 4 ];
  var data = [ 1, 2, 4, 3, 0 ];

  var ctx1 = document.getElementById( 'myChart1' );
  var graph1 = {
    type: 'bar',
    data: {
      labels: data_labels,
      datasets: [{
        label: 'A',
        borderWidth: 1,
        backgroundColor: "#14c759",
        borderColor: "#14c759",
        data: data
      }]
    },
    options: {
      title: {
        display: true,
        text: 'クリックイベントハンドリング例',
        padding: 3
      },
      scales: {
        xAxes: [{
          categoryPercentage: 0.4
        }],
        yAxes: [{
          display: true,
          scaleLabel: {
            display: true,
            labelString: ''
          }
        }]
      },
      tooltips: {
        mode: 'label'
      }
    }
  };
  var myChart1 = new Chart( ctx1, graph1 );

  //. クリックイベント
  ctx1.addEventListener( 'click', function( event ){
    var item = myChart1.getElementAtEvent( event );
    if( item.length == 0 ){
      return;
    }
    item = item[0];

    var idx = item._index;        //. 左から何番目のアイテムがクリックされたか?
    var ctx = item._chart.ctx;    //. クリックされたアイテムのチャート描画部分のコンテキスト

//. ctx に対して、idx 番目のアイテムがある箇所を強調表示する。
//. 以下の例では idx 番目のアイテムがあるエリアを赤枠で囲って強調している

//. 描画する矩形の開始地点座標と、矩形の幅・高さを求める var x_right = item._xScale.chart.chartArea.right; var x_left = item._xScale.chart.chartArea.left; var x_width = ( x_right - x_left ) / data_labels.length; var y_height = item._yScale.height; var y_top = item._yScale.top; var draw_x0 = x_width * idx;
//. strokeRect 関数で対象エリアに矩形を描画する ctx.lineWidth = 5; ctx.strokeStyle = 'red'; ctx.strokeRect( draw_x0 + x_left, y_top, x_width, y_height );
}); }); </script> </head> <body> <div class="container"> <canvas id="myChart1" style="position:relative; width:800; height:200"></canvas> </div> </body> </html>

簡単に解説すると、クリックしたアイテムを item 変数に入れた後に item._chart.ctx を参照して(チャートが描画される Canvas の)コンテキスト変数を取得する、というのがミソです。これが取得できてしまえば、後はここに Canvas の機能や関数を使って自由に描画すればいいわけで、上の例では矩形を指定して色を付けるようにしています。



公私で Chart.js をよく使っています。D3.js と並ぶビジュアライゼーションライブラリですが、各種グラフを描く、という目的であれば Chart.js の方が使いやすいと思っています:
2019101600


この Chart.js を使って、例えば積み上げ式の棒グラフを描くのであれば、こんな感じで記述します。HTML5 Canvas と JavaScript を使ってデータやオプションを指定して描画します:
<html>
<head>
<meta charset="utf8"/>
<script type="text/javascript" src="//code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.2.1/Chart.min.js"></script>
<script>
$(function(){
  //. 乱数を使ってデータを生成
  var label_data = [];
  var red_data = [];
  var green_data = [];
  var blue_data = [];
  for( var i = 0; i < 10; i ++ ){
    label_data.push( i );
    red_data.push( Math.floor( Math.random() * 100 ) );
    green_data.push( Math.floor( Math.random() * 100 ) );
    blue_data.push( Math.floor( Math.random() * 100 ) );
  }

  var ctx = document.getElementById( 'myChart' );
  var myChart = new Chart( ctx, {
    type: 'bar',
    data: {
      labels: label_data,
      datasets: [{
        label: 'Red',
        borderWidth: 1,
        backgroundColor: "#ffaaaa",
        borderColor: "#ff5555",
        data: red_data
      },
      {
        label: 'Green',
        borderWidth: 1,
        backgroundColor: "#aaffaa",
        borderColor: "#55ff55",
        data: green_data
      },{
        label: 'Blue',
        borderWidth: 1,
        backgroundColor: "#aaaaff",
        borderColor: "#5555ff",
        data: blue_data
      }]
    },
    options: {
      title: {
        display: true,
        text: '3色',
        padding: 3
      },
      scales: {
        xAxes: [{
          stacked: true,
          categoryPercentage: 0.4
        }],
        yAxes: [{
          stacked: true
        }]
      },
      legend: {
        labels: {
          boxWidth: 30,
          padding: 20
        },
        display: true
      },
      tooltips: {
        mode: 'label'
      }
    }
  });
});
</script>
</head>
<body>
<canvas id="myChart" style="position:relative; width:800; height:200;"></canvas>
</body>
</html>



このページをウェブブラウザで表示すると以下のようになります。単にグラフが表示されるだけではなく、マウスオーバー時のインタラクティブな処理なども自動的に行われます:

2019101601


また canvas のコンテキスト要素を使うことで Chart.js のチャートに各データ(棒グラフであれば棒の部分)をクリックした時のイベントハンドリングを実装することも可能です:
<html>
<head>
<meta charset="utf8"/>
<script type="text/javascript" src="//code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.2.1/Chart.min.js"></script>
<script>
$(function(){
  //. 乱数を使ってデータを生成
  var label_data = [];
  var red_data = [];
  var green_data = [];
  var blue_data = [];
  for( var i = 0; i < 10; i ++ ){
    label_data.push( i );
    red_data.push( Math.floor( Math.random() * 100 ) );
    green_data.push( Math.floor( Math.random() * 100 ) );
    blue_data.push( Math.floor( Math.random() * 100 ) );
  }

  var ctx = document.getElementById( 'myChart' );
  var myChart = new Chart( ctx, {
    type: 'bar',
    data: {
      labels: label_data,
      datasets: [{
        label: 'Red',
        borderWidth: 1,
        backgroundColor: "#ffaaaa",
        borderColor: "#ff5555",
        data: red_data
      },
      {
        label: 'Green',
        borderWidth: 1,
        backgroundColor: "#aaffaa",
        borderColor: "#55ff55",
        data: green_data
      },{
        label: 'Blue',
        borderWidth: 1,
        backgroundColor: "#aaaaff",
        borderColor: "#5555ff",
        data: blue_data
      }]
    },
    options: {
      title: {
        display: true,
        text: '3色',
        padding: 3
      },
      scales: {
        xAxes: [{
          stacked: true,
          categoryPercentage: 0.4
        }],
        yAxes: [{
          stacked: true
        }]
      },
      legend: {
        labels: {
          boxWidth: 30,
          padding: 20
        },
        display: true
      },
      tooltips: {
        mode: 'label'
      }
    }
  });

  //. クリックイベントハンドラー
  ctx.addEventListener( 'click', function( evt ){
    var item = myChart.getElementByEvent( evt );
    if( item.length == 0 ){
      return;
    }

    item = item[0];  //. クリックしたオブジェクトデータ(棒)
  });
});
</script>
</head>
<body>
<canvas id="myChart" style="position:relative; width:800; height:200;"></canvas>
</body>
</html>

で、上記で取得した item というのが「クリックされたオブジェクト」となり、上記のような方法で取得できるのですが、意外と難しかったのが「配列の何番目のデータがクリックされたのか?」を調べる方法でした。上記の方法でクリックされたオブジェクトそのものを取得することはできますし、その中の数値などを取り出すこともできますが、配列の何番目のデータがクリックされたのか? という情報を取得するのに手間取ってしまいまいました。

この辺り、業務で Chart.js を使う機会があったのでそのタイミングで調べてみました。答はこんな方法でした:
<html>
<head>
<meta charset="utf8"/>
<script type="text/javascript" src="//code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.2.1/Chart.min.js"></script>
<script>
$(function(){
  //. 乱数を使ってデータを生成
  var label_data = [];
  var red_data = [];
  var green_data = [];
  var blue_data = [];
  for( var i = 0; i < 10; i ++ ){
    label_data.push( i );
    red_data.push( Math.floor( Math.random() * 100 ) );
    green_data.push( Math.floor( Math.random() * 100 ) );
    blue_data.push( Math.floor( Math.random() * 100 ) );
  }

  var ctx = document.getElementById( 'myChart' );
  var myChart = new Chart( ctx, {
    type: 'bar',
    data: {
      labels: label_data,
      datasets: [{
        label: 'Red',
        borderWidth: 1,
        backgroundColor: "#ffaaaa",
        borderColor: "#ff5555",
        data: red_data
      },
      {
        label: 'Green',
        borderWidth: 1,
        backgroundColor: "#aaffaa",
        borderColor: "#55ff55",
        data: green_data
      },{
        label: 'Blue',
        borderWidth: 1,
        backgroundColor: "#aaaaff",
        borderColor: "#5555ff",
        data: blue_data
      }]
    },
    options: {
      title: {
        display: true,
        text: '3色',
        padding: 3
      },
      scales: {
        xAxes: [{
          stacked: true,
          categoryPercentage: 0.4
        }],
        yAxes: [{
          stacked: true
        }]
      },
      legend: {
        labels: {
          boxWidth: 30,
          padding: 20
        },
        display: true
      },
      tooltips: {
        mode: 'label'
      }
    }
  });

  //. クリックイベントハンドラー
  ctx.addEventListener( 'click', function( evt ){
    var item = myChart.getElementByEvent( evt );
    if( item.length == 0 ){
      return;
    }

    item = item[0];
    var index = item._index;  //. 配列の何番目のデータがクリックされたか
    var item_data = item._chart.config.data.datasets;  //. クリックされたオブジェクトのデータセット
    console.log( index );
    console.log( item_data );
  });
});
</script>
</head>
<body>
<canvas id="myChart" style="position:relative; width:800; height:200;"></canvas>
</body>
</html>

最初はこの方法がわからず、クリックイベントのマウス位置やオフセット位置から計算しようとしていたのですが、これだとブラウザごとの違いを意識する必要があり、面倒なコードになってしまっていました。

結論としては上記の item オブジェクトの中の _index という属性に隠れていたようです。ちなみに最初のオブジェクトは 0、2番目のオブジェクトの場合は 1 になるようです。



 

このページのトップヘ