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

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

HTML5 の Canvas を使うことで HTML の画面内にコンテキストを利用した図形を比較的自由に描くことができるようになります。この機能の1つである arc() 関数を使うと、下図のような「円の弧」を描画できます(塗りつぶすかどうかは選択できます):
2019112203


この弧を描く際のコードは以下のようなものです:
  :
<script>
  var canvas1 = document.getElementById( 'myCanvas1' );
  if( !canvas1 || !canvas1.getContext ){
    return false;
  }
  var ctx1 = canvas1.getContext( '2d' );

  //. 円の情報
  var r = 80;
  var x0 = 100;
  var y0 = 100;

  var deg = 190;
  ctx1.beginPath();
  ctx1.arc( x0, y0, r, -90 * Math.PI / 180, deg * Math.PI / 180, false );
  ctx1.fillStyle = "rgba( 255, 128, 128, 0.8 )";
  ctx1.fill();
</script>

<body>
<canvas id="myCanvas1"></canvas>
</body>
  :

で、このような図形を描くのが目的であればいいのですが、パックマンのような(円からピザの一部を切り取ったような)画像を描きたい場合はこれでは目的の画像とは異なります。なんとかしてパックマン型に塗りつぶした弧を描く方法はないでしょうか? というのが今回のテーマです。





2019112202


結論としては可能で、「arc() 関数の実行直後に円中心に向かって直線を引く」のがその答えになります:

  :
<script>
  var canvas1 = document.getElementById( 'myCanvas1' );
  if( !canvas1 || !canvas1.getContext ){
    return false;
  }
  var ctx1 = canvas1.getContext( '2d' );

  //. 円の情報
  var r = 80;
  var x0 = 100;
  var y0 = 100;

  var deg = 190;
  ctx1.beginPath();
  ctx1.arc( x0, y0, r, -90 * Math.PI / 180, deg * Math.PI / 180, false );
  ctx1.lineTo( x0, y0 );
  ctx1.fillStyle = "rgba( 255, 128, 128, 0.8 )";
  ctx1.fill();
</script>

<body>
<canvas id="myCanvas1"></canvas>
</body>
  :

これでパックマンが描画できました:
2019112200

 

MIT(マサチューセッツ工科大学)が開発した教育用プログラミング環境「スクラッチ」は、ウェブブラウザでこちらのサイトを訪れることで利用することができます。一般的にはこの方法で利用することが(後述の方法と比較して常に最新版が利用できる、といったメリットもあり)推奨されます:
2019112001


最新バージョン(2019/11/18 時点では 3)と比較しての互換性などはありませんが、バージョン 1.x ではメッセージングによる外部連携を行うことができました。このバージョンは、今でもダウンロードしてインストールすることで自分の環境から利用することができます:
https://scratch.mit.edu/scratch_1.4


2019年11月現在、普通にスクラッチを利用する場合の環境は上述2つのいずれかになると思っています。ただインターネット環境に制約があるケースなど、特殊な環境でも最新版を利用することができないわけではありません。今回はそのような特殊なケースを想定し、Kubernetes (以下 k8s)環境の中にインストールしたスクラッチを使う方法を紹介します。

なお、以下つらつらと記述していますが、そんなに特別なことを書いているつもりはなく、「ごく普通に k8s に MIT スクラッチのイメージをデプロイ」しているだけです。どちらかというと自分の備忘録としてのブログエントリです。


【準備】
まず k8s 環境を用意します。独自にインストールした k8s 環境でもいいし、minikube 環境でもいいし、クラウドベンダーのサービスを使うもアリです。自分の環境からの利用に支障がないものを使ってください。なお以下では IBM Cloud から提供されている IKS(IBM Kubernetes Services) を使った例を紹介しています。IKS の場合は kubectl コマンドに加え、ibmcloud コマンドの導入も必要になります。詳しくはこちらを参照ください:
https://github.com/dotnsf/iks-handson/tree/master/Lab0

また minikube 環境を Windows 10 + WSL 環境下で作る場合の手順は以前のブログエントリで紹介したことがあります。こちらの環境を使う場合は以下を参照ください:
Windows 10 に minikube を導入して WSL から利用する


以下の手順に進む前に ibmcloud コマンドを使った IBM Cloud へのログインや環境変数の設定などを済ませておくようにしてください。詳しくは上述のリンク先を参照ください。


【デプロイ】
MIT スクラッチの docker イメージを使って k8s 上にデプロイします。今回はこのイメージを使わせていただきました:
https://hub.docker.com/r/kadok0520/mit-scratch3

(↑ MIT スクラッチ version 3 の docker イメージのようです)


デプロイ手順は以下になります(以下では mit-scratch3 という名称でデプロイしています):
$ kubectl run mit-scratch3 --image=kadok0520/mit-scratch3

$ kubectl get pod (pod の状態を参照し、 READY が 1/1 になっていることを確認)

NAME                                READY   STATUS    RESTARTS   AGE
pod/mit-scratch3-795d945bcb-2mj2b   1/1     Running   0          16h



"kubectl get all" の結果、mit-scratch3 の pod が動いていることが確認できました。

このままだと外からの利用ができないため、ポートフォワーディングを使って公開します。イメージのドキュメントによると、このイメージは内部的には 8601 番ポートを使って動作するようなので、このポート番号を指定して expose した上で、service の状態を確認します:
$ kubectl expose depoyment mit-scratch3 --type="NodePort" --port=8601

$ kubectl get service mit-scratch3 (service の状態を参照し、 POST を確認)
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE mit-scratch3 NodePort 172.21.228.178 8601:32686/TCP 16h

"kubectl get service" の結果、mit-scratch3 は 32686 番ポートで外部公開されたことが確認できました。

また、このサービスのパブリック IP アドレスも確認します。パブリック IP アドレスの確認方法は使っている k8s 環境によって異なりますが、IBM Cloud の IKS の場合であれば次のコマンドで確認できます("mycluster" 部分は作成したクラスタの名称):
$ ibmcloud ks workers mycluster

OK
ID                                                     パブリック IP   プライベート IP   フレーバー   状態     状況     ゾーン   バージョン
kube-bn92ui5d00ng3ftqj7gg-mycluster-default-00000052   184.173.5.49    10.47.84.230      free         normal   Ready   hou02    1.14.8_1538

上記例であれば 184.173.5.49 がパブリック IP アドレスとなっていることが確認できました。つまりこの例であれば、ここまでの結果から http://184.173.5.49:32686/ にアクセスすることで k8s 環境内にデプロイされた MIT スクラッチにアクセスできる、ということになります。


【動作確認】
上記手順で確認した URL にウェブブラウザでアクセスして動作確認します:

2019111800


動いているようです。


【後作業】
作成した環境(pod, service, deployment)を削除するには以下のコマンドを実行します:
$ kubectl get all (mit-scratch3 の pod, service, deployment が存在していることを確認)

$ kubectl delete deployment mit-scratch3 (deployment を削除)

$ kubectl delete service mit-scratch3 (service を削除)

$ kubectl get all (mit-scratch3 の pod, service, deployment が削除されたことを確認)




Chart.js を使うと、レスポンシブ対応した各種グラフを簡単に実現できて便利です。便利でよく使うのですが、それ故にカスタマイズしたくなることも少なからず発生します。

例えばこんな例です。Chart.js を使うと水平の積み上げ棒グラフもこんな感じで作れるのですが・・・
2019111701

  :
  :

<script src="//code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js"></script>

  :
  :

<script>
var data = {
  labels: [ 'カレーライス', 'ラーメン', 'ハンバーグ', 'サラダ' ],
  datasets: [{
    label: '脂質',
    data: [ 500, 300, 400, 100 ],
    backgroundColor: 'rgba( 255, 100, 100, 1 )'
  },
  {
    label: 'たんぱく質',
    data: [ 300, 250, 400, 200 ],
    backgroundColor: 'rgba( 100, 100, 255, 1 )'
  },
  {
    label: '炭水化物',
    data: [ 200, 300, 100, 50 ],
    backgroundColor: 'rgba( 100, 255, 100, 1 )'
  }],
};
var options = {
  scales: {
    title: {
      display: true,
      text: '三大栄養素別比較',
      padding: 3
    },
    xAxes: [{
      display: true,
      scaleLabel: {
        display: true,
        labelString: 'mg'
      },
      ticks: {
        min: 0
      },
      stacked: true  //. 積み上げ棒グラフ
    }],
    yAxes: [{
      display: true,
      scaleLabel: {
        display: true,
        labelString: ''
      },
      stacked: true  //. 積み上げ棒グラフ
    }]
  },
  legend: {
    labels: {
      boxWidth: 30,
      padding: 20
    },
    display: true
  },
  tooltips: {
    titleFontSize: 20,
    bodyFontSize: 20,
    mode: 'label'
  }
};

$(function(){
  var ctx1 = document.getElementById( 'myChart1' );
  var graph1 = {
    type: 'horizontalBar',
    data: data,
    options: options
  };
  var myChart1 = new Chart( ctx1, graph1 );
});
</script>

  :
  :

<canvas id="myChart1" style="position:relative; width:900; height:300;"></canvas>

  :
  :




このグラフの特定部分に追加の描画を加えたくなることがあります。例えばある値やエリアを強調表示するために塗りつぶしたい、といったカスタマイズです。カスタマイズ後の例としてはこんな感じ:
2019111702

  ↑横軸の 450 ~ 550 部分を薄くピンクで塗りつぶして強調


これを実現するには Chart.js のカスタマイズが必要になります。具体的にはヘルパー機能を使って水平棒グラフの draw() メソッドを拡張し、本来の draw() メソッド実行後に Canvas のグラフィックコンテキストを使って直線や矩形を追加で描画する、というカスタマイズを行います(変更箇所をで表記):
  :
  :

var options = {
  scales: {
    title: {
      display: true,
      text: '三大栄養素別比較',
      padding: 3
    },
    xAxes: [{
      display: true,
      scaleLabel: {
        display: true,
        labelString: 'mg'
      },
      ticks: {
        min: 0
      },
      stacked: true  //. 積み上げ棒グラフ
    }],
    yAxes: [{
      display: true,
      scaleLabel: {
        display: true,
        labelString: ''
      },
      stacked: true  //. 積み上げ棒グラフ
    }]
  },
  lineAtX1: 450,    //. この位置に矩形を描画
  lineAtX2: 550,    //. この位置に矩形を描画
  legend: {
    labels: {
      boxWidth: 30,
      padding: 20
    },
    display: true
  },
  tooltips: {
    //enabled: false,
    titleFontSize: 20,
    bodyFontSize: 20,
    mode: 'label'
  }
};


//. hozirontalBar を拡張
var originalLineDraw = Chart.controllers.horizontalBar.prototype.draw;
Chart.helpers.extend(Chart.controllers.horizontalBar.prototype, {
  draw: function () {
    //. 本来の hozizontalBar を描画
    originalLineDraw.apply(this, arguments);

    var chart = this.chart;
    var ctx = chart.chart.ctx;  //. グラフィックコンテキスト

    var lineAtX1 = chart.config.options.lineAtX1;
    var lineAtX2 = chart.config.options.lineAtX2;
    if( lineAtX1 && lineAtX2 ){
      var xaxis = chart.scales['x-axis-0'];
      var yaxis = chart.scales['y-axis-0'];

      //. 軸の値をグラフィックコンテキストの座標に変換
      var x1 = xaxis.getPixelForValue( lineAtX1 );
      var y1 = yaxis.top;

      var x2 = xaxis.getPixelForValue( lineAtX2 );
      var y2 = yaxis.bottom;

      ctx.save();
      ctx.beginPath();

      ctx.lineWidth = 5;
      ctx.fillStyle = 'rgba(255,200,200,0.1)';
      ctx.fillRect( x1, y1, x2 - x1, y2 - y1 );

      ctx.restore();
    }
  }
});

  :
  :


変に HTML5 Canvas に慣れていると、グラフィックコンテキストを取得して自由に描画して、・・・というカスタマイズを考えてしまうのですが、Chart.js でそのようにすると描画した部分を別のタイミングで Chart.js が上書きしてしまったりして、想定通りにカスタマイズできないことが多くあります。そのためか、上記のようなカスタマイズのためのヘルパーが用意されていて、その中でカスタマイズを行う、という手法を実装する必要があるようです。


このページのトップヘ