Chart.js パン操作サンプル

Chart.jsでグラフをパン(PANまたはドラッグ、スクロール)操作するサンプルを作成しました。

Chart.jsはプラグインを読み込むことで機能拡張することができます。この拡張性・柔軟性の高さがChart.jsの人気の一因かと思います(メンテする方は大変そうですが)。プラグインは公式サイトでも公開されており、信頼性についても心配なさそうです。

Popular Extensions – chartjs.org

今回グラフをパン操作するにあたり、公式サイトで公開されているchartjs-plugin-zoomプラグインを使用しました。サンプルに加えこのプラグインの使い方をさらっと説明します。どなたかの参考になればと思います。

See the Pen Chart.js(pan) by senmyou (@senmyou) on CodePen.

プラグインの読み込み

chartjs-plugin-zoomプラグインではhammer.jsを利用しています。このためchartjs-plugin-zoomプラグインを読み込む前にhammer.jsを読み込む必要があります(読み込む順番が逆の場合エラーは出ませんがジェスチャーイベントがリスナーに登録されません)。

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-zoom/0.6.3/chartjs-plugin-zoom.js'></script>
if (Hammer) {
  var mc = new Hammer.Manager(node);
  mc.add(new Hammer.Pinch());
  mc.add(new Hammer.Pan({
    threshold: panThreshold
  }));
  .....
  mc.on('panstart', function(e) {
    currentDeltaX = 0;
    currentDeltaY = 0;
    handlePan(e);
  });
  mc.on('panmove', handlePan);
  mc.on('panend', function(e) {
    currentDeltaX = null;
    currentDeltaY = null;
    zoomNS.panCumulativeDelta = 0;
    setTimeout(function() { panning = false; }, 500);
  });

オプションの設定

  • options.pan.enabledとoptions.zoom.enabledをtrueに設定します。なにやらPANのみ行う場合もzoom.enabledをtrueにする必要があるみたいです(modeは空文字)。
    Pan doesn’t work without `zoom.enabled` #132

  • options.pan.rangeMin/rangeMaxにパン操作の有効範囲を設定します。パンやズーム操作時にrangeMin/rangeMaxに達すると、そこで操作を止めてくれます。設定するかどうかは任意で、設定しなくても問題なく動きます。

  • options.xAxes.min/maxにCanvas要素内に表示する範囲を設定します。これを設定しないと全データをCanvas要素いっぱいに拡大縮小して表示します。

options: {
  pan: {
    enabled: true,
    mode: "x",
    rangeMin: {
      x: rangeMin
    },
    rangeMax: {
      x: rangeMax
    },
  },
  zoom: {
    enabled: true,
    mode: ""
  },
  xAxes: [{
    type: 'time',
    time: {
      min: min,
      max: max
    },

最後に現在作成中のアプリで使用する予定のグラフを掲載します。データの作成部分はデバッグ中なのでオプションの設定だけ参考にして頂けたらと思います。Chart.jsはグラフのスタイリングを細かく設定できますしプラグインもたくさんあるのでデザインを決めるのに迷ってしまいますね。

See the Pen Chart.js(pan) by senmyou (@senmyou) on CodePen.

JavaScript よく使う配列メソッド

個人的によく使うJavaScriptの配列メソッドをまとめました。

ループ処理

forEach / map / filter

全要素について処理を実施(中断しない)

forEach(コールバック関数)
for文の代用
新しい配列 = map(コールバック関数)
コールバック関数が返した値を新しい配列に格納
新しい配列 = filter(コールバック関数)
trueを返す要素のみを新しい配列に格納
const items = ["A", "B", "C", "D", "E"];
items.forEach((value, index) => {
    console.log(value, index);
});
// A 0
// B 1
// C 2
// D 3
// E 4

const items1 = ["A", "B", "C", "D", "E"];
const label1 = items.map((value, index) => {
    return index + ": " + value;
});
console.log(label1); // ["0: A", "1: B", "2: C", "3: D", "4: E"]
console.log(items1); // ["A", "B", "C", "D", "E"]

const items2 = [{id: 1, name: "A"},
                {id: 2, name: "B"},
                {id: 3, name: "C"},
                {id: 4, name: "D"},
                {id: 5, name: "E"}];
const label2 = items2.map((item, index) => {
    return item.name;
});
console.log(label2); // ["A", "B", "C", "D", "E"]

const items3 = [{id: 1, name: "A"},
                {id: 2, name: "B"},
                {id: 3, name: "C"},
                {id: 4, name: "D"},
                {id: 5, name: "E"}];
const label3 = items3.filter((item, index) => {
    return item.id > 3;
});
console.log(label3); // [{id: 4, name: "D"}, {id: 5, name: "E"}]

要素の追加

push / unshift / splice

push(要素)
配列の一番後ろに追加(複数可)
unshift(要素)
配列の一番前に追加(複数可)
削除された要素 = splice(index, 削除する数, 追加する要素)
削除する数を0にすると追加のみ実施、元の配列が書き換わる
const items1 = ["A", "B", "C", "D", "E"];
items1.push("*");
console.log(items1); // ["A", "B", "C", "D", "E", "*"]

const items2 = ["A", "B", "C", "D", "E"];
items2.unshift("*");
console.log(items2); // ["*", "A", "B", "C", "D", "E"]

const items3 = ["A", "B", "C", "D", "E"];
items3.splice(1, 0, "*");
console.log(items3); // ["A", "*", "B", "C", "D", "E"]

要素の削除(取り出し)

pop / shift / splice

一番後ろの要素 = pop()
配列の一番後ろを取り出す、空の場合はundefined
一番前の要素 = shift()
配列の一番前を取り出す、空の場合はundefined
削除された要素 = splice(index, 削除する数, 追加する要素)
追加する要素を省略すると削除のみ、削除する数も省略するとindex以降をすべて削除、元の配列が書き換わる
const items1 = ["A", "B", "C", "D", "E"];
items1.pop();
console.log(items1); // ["A", "B", "C", "D"]

const items2 = ["A", "B", "C", "D", "E"];
items2.shift();
console.log(items2); // ["B", "C", "D", "E"]

const items3 = ["A", "B", "C", "D", "E"];
items3.splice(1, 2);
console.log(items3); // ["A", "D", "E"]

const items4 = ["A", "B", "C", "D", "E"];
items4.splice(2); // items4[2]以降を削除
console.log(items4); // ["A", "B"]

切り取り

slice

切り取った要素 = slice(開始index, 終了index(含まない))
終了indexを省略すると最後まで切り取り、開始indexも省略すると全要素切り取り(配列のコピー)、元の配列を書き換えない
const items1 = ["A", "B", "C", "D", "E"];
const parts1 = items1.slice(1, 2);
console.log(parts1); // ["B"]
console.log(items1); // ["A", "B", "C", "D", "E"]

const items2 = ["A", "B", "C", "D", "E"];
const parts2 = items2.slice(1); // items2[1]以降を切り取り
console.log(parts2); // ["B", "C", "D", "E"]
console.log(items2); // ["A", "B", "C", "D", "E"]

Memo

直訳

  • splice … 解いて組み継ぎする、つなぎ合わせる
  • slice … 切る、切り取る
見つけた要素のindex = indexOf(探す要素, 検索開始index)
検索開始indexは省略可、見つからない場合は-1を返す
見つけた要素のindex = lastIndexOf(探す要素, 検索開始index)
検索開始indexは省略可、見つからない場合は-1を返す
見つけた要素のindex = findIndex(コールバック関数)
コールバック関数がtrueを返すまで検索、見つからない場合は-1を返す
見つけた要素 = find(コールバック関数)
コールバック関数がtrueを返すまで検索、見つからない場合はundefinedを返す
const items = ["A", "B", "C", "B", "A"];

const index1 = items.indexOf("B"); // indexOf(探す値, 開始index)
console.log(index1);     // 1

const index2 = items.lastIndexOf("B"); // lastIndexOf(探す値, 開始index)
console.log(index2);     // 3

const index3 = items.findIndex(value => value === "B"); // findIndex(callback)
console.log(index3);     // 1

const found = items.find(value => value === "B"); // find(callback)
console.log(found);     // B

テスト判定

some / every

true or false = some(コールバック関数)
コールバック関数で1要素でもtrueを返す場合はtrue
true or false = every(コールバック関数)
コールバック関数で全要素がtrueを返す場合はtrue(1要素でもfalseを返す場合はfalse)
const items = [0, 0, 1, 0, 1];

const some1 = items.some(value => value === 1); // 0->0->1->break
console.log(some1);     // true

const every0 = items.every(value => value === 0); // 0->0->1->break
console.log(every0);    // false

ソート

sort

sort(比較関数)
比較関数の戻り値の正負に従い要素を並べ替える
const items1 = [2, 0, 1, 4, 3];
items1.sort((a, b) =>  a - b);
console.log(items1);     // [0, 1, 2, 3, 4]

const items2 = [2, 0, 1, 4, 3];
items2.sort((a, b) =>  b - a);
console.log(items2);     // [4, 3, 2, 1, 0]

Memo

Array/Stringオブジェクトの同名メソッド

  • indexOf
  • lastIndexOf
  • slice

Cordova AdMobプラグインで広告を表示

AdMobプラグインを使用するとGoogle AdMobの提供する広告配信サービスを利用してモバイルアプリに広告を表示することができます。

floatinghotpot/cordova-admob-pro

バナー広告

AdMob.createBanner({
  adId: 'ca-app-pub-0123456789012345/0123456789',
  position: AdMob.AD_POSITION.BOTTOM_CENTER,
//debug
//  isTesting: true,
  overlap: true,
  autoShow: true,
  adSize: 'SMART_BANNER'
});

バナー広告(AD_POSITION.POS_XY)

var elt = document.getElementById("med_page");
var rect = elt.getBoundingClientRect();
var y = (rect.bottom - rect.top) + window.pageYOffset;  // タブバーを含まない画面の高さ

y *= parseFloat(document.body.style.zoom);  // 元の画面サイズ(CSSピクセル、dp単位)に戻す
if (window.screen.height > 720) {  // スマートバナーの高さを引く
  y -= 90;
} else if (window.screen.height > 400){
  y -= 50;
} else {
  y -= 32;
}
y *= window.devicePixelRatio;  // CSSピクセル(dp)からデバイスピクセルに変換する

AdMob.createBanner({
  adId: 'ca-app-pub-0123456789012345/0123456789',
  position: AdMob.AD_POSITION.POS_XY,
  x: 0,
  y: y,
//debug
//  isTesting: true,
  overlap: true,
  autoShow: true,
  adSize: 'SMART_BANNER'
});

インタースティシャル広告

var elem = document.getElementById("myNavigator");
elem.addEventListener("postpop", function(e) {
  if (e.leavePage.id === "graph_page") {  // グラフ画面からポップした後
    showInterstitialPolling(10);
  }
});
elem.addEventListener("postpush", function(e) {
  if (e.enterPage.id === "graph_page") {  // グラフ画面へプッシュした後
    if (window.AdMob) {
      init_ad();
    }
  }
});

function showInterstitialPolling(count) {
  if (--count < 0) return;
  if (window.AdMob) {
    AdMob.isInterstitialReady(function(isready) {
      if (isready) {
        AdMob.showInterstitial();
      } else {
        setTimeout(function() {showInterstitialPolling(count)}, 200);
      }
    });
  }
}
function init_ad() {
  if (window.AdMob) {
    AdMob.prepareInterstitial({
      adId: 'ca-app-pub-0123456789012345/0123456789',
//debug
//      isTesting: true,
      autoShow: false
    });
  }
}

Note

adIdプロパティにはアプリIDではなく広告ユニットIDを設定します(数字の区切りに「~」ではなく「/」を使用している方です)。