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

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

タグ:javascript

このブログエントリの続きです:
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 の機能や関数を使って自由に描画すればいいわけで、上の例では矩形を指定して色を付けるようにしています。



最近の PDF ファイルはテキスト情報が含まれています。そのため PDF 内をテキスト検索することができるようになっています:

このテキスト部分だけを機械的に抜き出す、というのが今回紹介するテーマです。具体的には Node.js を使って PDF からテキスト情報を抜き出すプログラムを実装してみました。

Node.js の場合、PDF を扱うことのできるライブラリがいくつか存在していますが、今回はこれを使いました。日本語を含むテキストであっても正しく取り出すことができそうでした:
pdf-parse

2019101201


また今回はこの公開 PDF からテキストを取り出してみます:
Kubernetesの基礎

2019101202


後述のコードでは対象 PDF ファイル名を指定して実行できるようにしています。そのため、上記リンク先から同 PDF (h2-takara-3.pdf)をダウンロードしておいてください。これ以外の PDF を使って試すこともできますが、その場合もあらかじめダウンロードしておいてください。

そして具体的なコード(pdf-sample.js)は以下のようになります:
//. pdf-sample.js
var fs = require( 'fs' ),
    pdf = require( 'pdf-parse' );

if( process.argv.length > 1 ){
  var filename = process.argv[2];
  var buf = fs.readFileSync( filename );
  pdf( buf ).then( function( data ){
    var text = data.text;
    console.log( text );
  }).catch( function( err ){
    console.log( err );
  });
}else{
  console.log( 'Usage: $ node pdf-sample ' );
}

pdf-parse を使っている部分を赤字にしました。エラー処理含めてこれだけです。

では実際に実行してみます。まず実行前の準備として pdf-parse ライブラリをインストールしておきます:
$ npm install pdf-parse


そして、対象 PDF ファイル名をパラメータ指定して node コマンドで実行します。例えばプログラムコード(pdf-sample.js)と同じフォルダに PDF ファイル(h2-takara-3.pdf)を保存した場合であれば、以下のように指定して実行します:
$ node pdf-sample ./h2-takara-3.pdf

正しく実行されると、以下のようにテキスト抽出結果がコンソール画面に表示されます:
$ node pdf-sample ./h2-takara-3.pdf


Kubernetesの基礎
InternetWeek 2018
2018年11月28日
日本アイ・ビー・エム株式会社
クラウド事業本部 高良真穂

発音/略称/Logo
綴りKubernetes
発音
koo-ber-net-ees
略称K8s
Kubernetes
12345678
クーベネティスのロゴ

K8sは一言で何ができる?
K8sは、コンテナのアプリ運用のためのOSS
1.コンテナの組み合わせ利用
2.スケールアウト
3.ロールアウト&ロールバック
4.永続ストレージ利用
5.自己修復(可用性)
6.クラスタの分割利用
7.監視&ログ分析

      :
 (中略) : © IBM Corporation 27 Kubernetesを 広めて勢力図を 変えるぞ 参道して シェアを取りに 行くぞ! 複合環境でも 便利♪ 豊富な資金力で 独走を維持するぞ ロックインから 解放だ! チャンス♪ K8sでクラウド・レースの展開に 変化があるかもしれない まとめ •Kubernetesはコンテナの運用基盤 •オンプレ&クラウドで共通のオペレーションで運用できる •必要なインフラ機能が提供され、高効率な運用を実現 •主要クラウドベンダー、ソフトウェア企業が賛同 Kubernetes ハンズオンへ

日本語含めて正しくテキストが抽出できました。

 

むかーしから存在している技術なのですが、イメージマップという便利な機能があります(最近あまり使われなくなったという印象もあります)。これは HTML ページ内の画像内にクリッカブルな領域を複数定義し、いずれかの領域がクリックされたら何らかの処理を行う、というものです。クリックされる領域によって処理内容(このページにジャンプするとか、この JavaScript 関数を実行するとか、・・)を変えることができる、というものです。

例えばこんな感じで実現します。いらすとやさんの『ホワイト企業~ブラック企業のイラスト』を例に紹介します:



このイラスト画像は横650ピクセル、縦137ピクセルです。その中にホワイト企業~ブラック企業が5段階のイラストで表示されています。

画像の左上の座標を ( 0, 0 ) とすると、このうち一番左の企業は、そのビルの矩形部分は左上が ( 37, 13 ) から右下 ( 100, 127 ) という矩形でできています。いま、このビルの矩形部分がクリックされたら "white" というメッセージを表示するようにしてみます:
2019100701


同様にして、5つのビルそれぞれにクリッカブルな矩形部分を定義し、それぞれがクリックされた時に以下のようなメッセージが表示されるようにしてみます:
ビル番号(左から何番目のビル)矩形範囲クリックされた時のメッセージ
1 ( 37, 13 ) - ( 100, 127 ) white
2 ( 165, 13 ) - ( 227, 127 ) lightgray
3 ( 291, 13 ) - ( 355, 127 ) gray
4 ( 481, 13 ) - ( 483, 127 ) darkgray
5 ( 546, 13 ) - ( 610, 127 ) black


これをイメージマップで実現すると、 HTML の該当箇所は以下のようになります:
<body>
  <div class="container">
    <img src="https://1.bp.blogspot.com/-BmJohMucVBI/XYhOV_VMQmI/AAAAAAABVHA/sZF8lMjPedUWSkxBwUGZXmVri2OFKEZ4gCNcBGAsYHQ/s650/company_white_black_kigyou_5dankai.png" border="0" usemap="#image_map"/><br/>
    <map name="image_map">
      <area shape="rect" coords="37,13,100,127" href="javascript:alert( 'white' )"/>
      <area shape="rect" coords="165,13,227,127" href="javascript:alert( 'rightgray' )"/>
      <area shape="rect" coords="291,13,355,127" href="javascript:alert( 'gray' )"/>
      <area shape="rect" coords="418,13,483,127" href="javascript:alert( 'darkgray' )"/>
      <area shape="rect" coords="546,13,610,127" href="javascript:alert( 'black' )"/>
    </map>
  </div>
</body>

<img> タグの usemap 属性でイメージマップの名称を定義します。そして <map> タグを使って、対象(name 属性で指定)の <img> のクリッカブル領域と、クリックされた時のハンドラを href 属性に定義します。上記例ではハンドラを javascript 関数の実行にしていますが、普通に URL を記述すればリンクを作成することもできます。

これで各ビルをクリックした時に、(左から順に)"white", "lightgray", "gray", "darkgray", "black" というメッセージが表示されるようになります。元は1枚の画像でしたが、クリッカブルな領域を5箇所定義することができ、それぞれのクリックハンドラを個別に設定することもできました:

(左から2番めのビルをクリックした時の様子)
2019100703


ここまでが普通のイメージマップです。このイメージマップ、非常に便利な反面で「レスポンシブ対応が難しい」という難点がありました。要はクリッカブルな矩形領域をピクセル絶対値で指定しているため、画像がそのまま表示された場合はいいのですが、レスポンシブ対応などで画面サイズに合わせて拡大縮小されて画面いっぱいに表示された場合、縮尺が変更になってしまい、絶対値で指定していたイメージマップの定義がおかしくなってしまうのでした。

具体的に同じコードをスマホのシミュレーターで確認した結果、下の赤点線部が "lightgray" のクリッカブル矩形部分となっていて、本来の位置からズレていました。これをどうにかしたい、というのが今回のテーマです:
2019100702


色々と調べましたが、答としては jQuery RWD Image Maps という jQuery プラグインを使うことで解決できました:
2019100700



以下に具体的な対応手順を紹介します。

まず HTML 内でレスポンシブのための宣言をしておきます。例えば以下のようなコードが既に含まれているものと仮定します:
  :
<meta name="viewport" content="width=device-width,initial-scale=1"/>
  :


次に jQuery と jQuery RWD Image Maps をロードします。以下の例では CDN からそれぞれをロードしています(jQuery RWD Image Maps の方を後にロードする必要があります):
  :
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery-rwdImageMaps/1.6/jquery.rwdImageMaps.min.js"></script>
  :

CSS で画像サイズの調整を行います。この例ではイメージマップを用いる画像の横幅を 100% に、高さは自動調整するように指定しています:
  :
<style>
img[usemap]{
  max-width: 100%;
  height: auto;
}
</style>
  :

そして最後に JavaScript で該当部分に RWD Image Maps を適用します:
  :
<script>
$(function(){
  $('img[usemap]').rwdImageMaps();
});
</script>
  :

これでスマホでおなじページにアクセスした時でもイメージマップの矩形部分がズレることなく利用できるようになりました:
2019100704




ふと思い立って「モールモール」というパズルゲームを作ってみました。ただし「ゲーム内で使う画像はいらすとや様から入手できるものに限る」という制約を自分に課してみました。つまりいらすとや様から提供されている画像のみを、そのまま(書き足したり、一部だけ切り取ったりせずに)使ってゲームを作る、という条件にしてみました。

「モールモール」はもともと 1985 年に当時のビクター音楽産業(現JVCケンウッド・ビクターエンタテインメント)から MSX 向けにリリースされた、主人公のモグラを操作するパズルゲームでした。その後、他機種への移植や続編のリリースなどもされましたが、シンプルなルールや操作性もあってか、当時の素人プログラマー達が自分の所有していた機種に勝手に移植して雑誌で発表されていることも珍しくありませんでした。

※雑誌にプログラムコードが数ページに渡って記載されていたり、「ソノシート」と呼ばれる片面レコードにプログラム情報が記載されて付録になっていたりした時代がありました。。

実は自分もその一人 σ(^^) です。僕はパソコンの所有が大学4年生になってからと比較的遅かったのですが、高校生の頃にポケットコンピュータ(当時の通称は「ポケコン」)と呼ばれる、コンピュータというよりもプログラミング機能付き電卓を持っていました。その中の1つがシャープの PC-1350 で、当時では画期的な縦4行表示とグラフィック機能を備えており、BASIC によるプログラミングもできるものでした。この「縦複数行表示」が当時のポケコンでは珍しく、広い画面を使って「モールモール」をはじめとするゲームを勝手に移植して遊んでいたのでした。その意味で今でも思い入れあるゲームの1つです。

この「モールモール」、キャラクターとして必要な画像は6種類(ドア、はしご、土、石、イモ、主人公)と比較的少なく、また基本操作も(パズルをギブアップする時などは例外ですが)上下左右の移動のみなので矢印キーだけで実現できます。敵という概念もないので考えている間にやられてしまったり、敵を撃つ必要もありません。リソース的にも非力なポケコンにピッタリのゲームでした(笑)。

で、今回のゲーム制作にあたり、いらすとや様から以下6個の画像を使わせていただいております:

ドア =いろいろな状態のドアのイラスト



はしご =木のはしごのイラスト



 =デジタルデータ風の背景素材(緑)



 =石垣のイラスト(背景素材)



イモ =スネークフルーツのイラスト



主人公 =もぐらのイラスト



これらの画像に手を加えず、そのまま使っています。モールモールでは「土」がなぜか緑色なのですが、デジタルデータ風背景画像が遠くからみるとそれっぽく見えるのが大発見でした(苦笑)。また「イモ」の画像は数種類あったのですが、一番イモっぽく見えたのが「スネークフルーツ」だったのも新発見でした。 (^^;


で、作ってみました。github リポジトリはこちらです:
https://github.com/dotnsf/molemole


リポジトリには画像は含まれていません。必要な画像はすべて動的にいらすとや様から直接ダウンロードして使っています:
2019071801


実質 index.html ファイルだけなので GitHub Pages でも公開しました。PC ブラウザでこちらのリンク先から遊ぶことができます:
https://dotnsf.github.io/molemole/

2019071701


簡単なルールなどは README.md に記載しているので、上記ページを参照ください。簡単に言うと「すべてのイモを取ってからドアまで行けばクリア」です。ただ重力を意識する必要があることと、落ちてくる石をうまく使わないとクリアできない面があったりします。

例えば上図は第0ステージですが、この面は普通にイモを取りに行って、そのままドアまで向かえばゴールです。よほど捻くれた取り方をしない限りは詰むことはないと思います。

でも第1ステージはこうなります:
2019071703


この面の場合、何も考えずにイモを取りに行ってしまう↓と・・・、ドアにたどり着く術がなくなってしまいます。こうなると "retry" ボタンでリセットするしかありません。石をうまく誘導しながらイモを取る必要があります:
2019071704

 
先に進むともう少し複雑なステージも待っています:
2019071702


README.md にステージ追加のカスタマイズ方法も乗せているので、興味ある人はダウンロード後に自分でステージを考えて追加して遊んでみてください。



jQuery の animate 関数を使う機会があります。

この animate 関数を使うと HTML 要素に比較的簡単にアニメーション要素を加えることができます。サンプルとして 200 x 200 の矩形の左下から右上に <p> 要素を移動させる例を紹介します("animate1" ボタンをクリックすると移動します):

 




このアニメーションのために記述した CSS および JavaScript は以下になります:
<style>
div.sample{
  position: relative;
  border: 1px solid #ccc;
  width: 200px;
  height: 200px;
}
div.sample p.point{
  position: absolute;
  left: 0px;
  top: 180px;
  margin: 0;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  background-color: #d00;
}
</style>
<script>
function animateStraight(){
  var $point = $('#point1');

  var startAnimate1 = function(){
    $point.animate( { left: 180, top: 0 } );
  };

  startAnimate1();
}

</script>

<div  class="sample">
  <p  id="point1" class="point"> </p>
</div>
<button  onclick="animateStraight();">animate1</button>

</body>

初期状態で { left: 0, top: 180 } (矩形の左下)に設置されている id='#point1' の <p> 要素に対して animate() 関数を実行し、そのパラメータに { left: 180, top:0 } (矩形の右上)を指定しています。これだけで現在位置から目的位置までの間を(直線的に)移動するアニメーションが実現できます。

#細かく説明すると、#point1 の <p> 要素の left パラメータを 0 から 180 へ、top パラメータを 180 から 0 へ少しずつ移動させることで実現できています。

ここで紹介したのは位置に関するパラメータでしたが、位置以外でも数字で指定できる要素を使って現在の値から目的の値までアニメーション処理させるだけであれば同様に実現できます。


さて、本ブログエントリで紹介するのは曲線的な軌道でアニメーション移動処理させたい場合の方法です。例えば左下から右上まで、円軌道でアニメーション処理させたい場合にどうすればよいか、という場合の方法を紹介します。

この実現のために animate() 実行時のパラメータの中で step 関数を使います:
    $point.animate(
      { count: 1 },
      {
        step: function( current ){
             :
        }
      }
    );

上の例では値を変化させるパラメータに count を指定しています。値を(初期値の 0 から)1 へ変化させるという指定ですが、この count 自体は位置には関係ありません(つまりこの animate 関数自体では画面上のアニメーションの処理は行っておらず、単に count 変数を 0 から 1 へ変化させているだけです)。

アニメーション処理を行っているのは animate 関数実行時の2つ目のパラメータである step 関数です。この関数は count 関数の値が少しずつ変化するたびに、その値を引数(current)にして実行されるます。なのでこの step 関数の中で current の値を使って要素の移動位置となる x 座標および y 座標を計算して css で位置移動する、という方法によってアニメーション処理を実現します。

具体的にはこのようになります("animate2" ボタンをクリックすると移動します):

 





<style>
div.sample{
  position: relative;
  border: 1px solid #ccc;
  width: 200px;
  height: 200px;
}
div.sample p.point{
  position: absolute;
  left: 0px;
  top: 180px;
  margin: 0;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  background-color: #d00;
}
</style>
<script>
function getXY( t ){
  var r = 180;

  var x = r * t;
  var y = Math.sqrt( ( r * r ) - ( x * x ) );

  return { x: x, y: y };
}

function animateCurve(){
  var $point = $('#point2');

  var current = 0;
  var startAnimate2 = function(){
    $point.animate(
      { count: 1 },
      {
        step: function( current ){
           var point = getXY( current );
           $point.css( { left: point.x, top: point.y } );
        }
      }
    );
  };

  startAnimate2();
}
</script>

<div  class="sample">
  <p  class="point" id="point2"> </p>
</div>
<button  onclick="animateCurve();">animate2</button>

step 関数の中で current の値をパラメータに指定して getXY() 関数を実行しています。getXY() 関数では x2 + y2 = 1802 (中心座標 [0, 0] 、半径 180 の円)上において、[ 0, -180 ] から [ 180, 0 ] まで移動する間の [ x, y ] の軌跡を計算して返しています(※)。また、この getXY() 関数によって返された値を使って、<p> 要素の位置を変える、という処理を加えています。これによって animate() 関数で count の値を 0 から 1 まで直線的に移動させながら、<p> 要素の位置を円曲線に沿って移動させる、という処理を実現しています。

※この円における x 座標は 0 から 180 まで変化します。したがって、x = current * 180 で求めることができます。また x2 + y2 = 1802 より y = (1802 - x2 )の平方根なので、この数値を計算して求めています。これが getXY() 関数の中身です。

この方法を応用することで(曲線の軌道計算式がわかっていれば)animate() 関数で要素を曲線軌道でアニメーション移動させることができます。



このページのトップヘ