Extension:Graph/Interactive Graph Tutorial

This page is a translated version of the page Extension:Graph/Interactive Graph Tutorial and the translation is 96% complete.

このチュートリアルではインタラクティブなグラフの作成方法を説明するため、国ごとの出生率の変化を例題とします。表示する年を選ぶスライダーを設定し、地図を使って世界の出生率の分布を示します。 このグラフ限定のソースコードは独立したページavailable as a separate wiki pageで調べてください。インタラクティブなグラフのサンドボックスInteractive Graph Sandboxで使い方を実験できます。 またVegaの説明文書全文complete Vega documentationにも興味があるかもしれません。


Move the slider to change the year, and hover the mouse over countries to read the rate. Try it !

図形を描くには

まずいくつかの要素を描くところから始めます(marks)。 <graph mode=interactive>...</graph>タグで挟まれた部分がグラフ用コードで、グラフがインタラクティブでなくても見た目は同じです。 インタラクティブなグラフのサンドボックスInteractive Graph Sandboxを開き、以下のコードから<graph>タグ以外の部分をコピーして実験を始めましょう。

ヒント: 実験用にグラフのスペックをVega Editorにコピーするには、編集ではなく「ソースを編集」を押します。

ソースコードを参照
{
  // Vega 2を使って画像の大きさを設定
  "version": 2, "width": 300, "height": 80,
  // 四辺すべてのパディングを同じ値に設定
  "padding": 12,
  // 既定では背景色は透明
  "background": "#edf1f7",
  "marks": [
    {
      // 水平線を引く
      "name": "scrollLine",
      "type": "rule",
      "properties": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 40},
          "x2": {"value": 300},
          "stroke": {"value": "#000"},
          "strokeWidth": {"value": 2}
        }
      }
    },
    {
      // 三角形を描き、マウスを当てて移動させるように設定
      // あとで呼び出せるように、オブジェクトに名前を付けておく
      "name": "handle",
      "type": "path",
      "properties": {
        "enter": {
          "x": {"value": 200},
          "y": {"value": 40},
          // パスの構文はSVGのパス タグと同値
          "path": {"value": "m-5.5,-10l0,20l11.5,-10l-11.5,-10z"},
          "stroke": {"value": "#880"},
          "strokeWidth": {"value": 2.5}
        },
        "update": {"fill": {"value": "#fff"}},
        // マウスを当てたときのオブジェクトの配色を変更
        "hover": {"fill": {"value": "#f00"}}
      }
    }
  ]
}

isDragging 信号

ハンドルのオブジェクトをマウスで移動できるように、まずクリックの有無を検出する方法を考えます。 そこで信号signalを追加して、クリックすると真に変わり (isDragging) 結果を示す文を表示する (debug) ことにします:

ソースコードを参照
"signals": [
  {
    "name": "isDragging",
    "init": false,
    "streams": [
      {"type": "@handle:mousedown","expr": "true"},
      {"type": "mouseup","expr": "false"}
    ]
  },
],

// 「marks」に対して
  {
    "name": "debugIsDragging",
    "type": "text",
    "properties": {
      "enter": {
        "x": {"value": 250},
        "y": {"value": 0},
        "fill": {"value": "black"}
      },
      "update": {"text": {"signal": "isDragging"}}
    }
  }

handlePosition 信号

前節でオブジェクトにマウスが当たったかどうか検知したので、次にmousemove信号を追加し、isDragging信号が真のときだけ変化するよう設定します。 作ったばかりの信号は「update」部でハンドルの「x」座標に付与します:

ソースコードを参照
// 「signals」に対して
  {
    "name": "handlePosition",
    "init": 200,
    "streams": [
      {
        "type": "mousemove[isDragging]",
        "expr": "eventX()"
      }
    ]
  }

  // 「marks」に付与
  {
    "name": "handle",
    ...
    "update": {
      "x": {"signal": "handlePosition"},
      ...
    },
  },
  {
    "name": "debugHandlePosition",
    "type": "text",
    "properties": {
      "enter": {
        "x": {"value": 250},
        "y": {"value": 14},
        "fill": {"value": "black"}
      },
      "update": {"text": {"signal": "handlePosition"}}
    }
  }

ハンドルの目盛り上の位置の信号を設定

ハンドルの位置をピクセル値で示してもあまり役に立ちません - グラフのハンドルには図に有用な、例えば「年」のような位置を設定するほうが使いやすいからです。 Vegaのスケール(目盛り)は、データ(例=年)を画面上の座標に変換したり戻したり(インバート)する便利な仕掛けがあります。 この段階では1960から2013までの線的なスケール「yearsScale」を付け加え、グラフの幅いっぱいにマッピングします(パディングを除外)。 さらにscaledHandlePosition信号も新たに加え、マウスを置いた座標Xを「年」に変換します。

ソースコードを参照
// 「scales」のルート値を付与:
  "scales": [
    {
      "name": "yearsScale",
      "type": "linear",
      "zero": false,
      "domain": [1960, 2013],
      "range": "width"
    }
  ],

// 「signals」に対して付与:
    {
      "name": "scaledHandlePosition",
      "expr": "handlePosition",
      "scale": {"name": "yearsScale","invert": true}
    }

  // 「marks」に付与
    {
      "name": "debugScaledHandlePosition",
      "type": "text",
      "properties": {
        "enter": {
          "x": {"value": 250},
          "y": {"value": 28},
          "fill": {"value": "black"}
        },
        "update": {"text": {"signal": "scaledHandlePosition"}}
      }
    }

「年」の値のクリーンアップ

既に気がついたかもしれませんが、実はハンドルは上記の設定のままではグラフの横幅を超して動かすことができ、間違った結果を表示してしまいます。 またスケールの目盛りは整数ではなく、「年」の値として不釣合いです。 この状態を訂正するため、もう1件、後処理の信号「currentYear」を作り、必要な範囲の整数に設定します。 初期値は意味のある範囲に設定し、ハンドルバーの位置と「yearLabel」をその値に結び付けます。 注意する点は、ハンドルの位置は画面の座標に合わせる必要があるため、「yearsScale」を使って値を逆変換します。

ソースコードを参照
    // 新しい信号:
    {
      "name": "currentYear",
      "init": 2000,
      "expr": "clamp(parseInt(scaledHandlePosition),1960,2013)"
    }

    // yearLabelマークの更新:
    // 「年」を示す新しいマーク
    {
      "name": "yearLabel",
      "type": "text",
      "properties": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 25},
          "fontSize": {"value": 32},
          "fontWeight": {"value": "bold"},
          "fill": {"value": "steelblue"}
        },
        "update": {"text": {"signal": "currentYear"}}
      }
    },

    // ハンドルのマークを更新:
    {
      "name": "handle",
      "properties": {
        "update": {
          "x": {"scale": "yearsScale","signal": "currentYear"}
        },
      }
    }

クリーンアップして仕上げ

いよいよデバッグ用のマークをすべて削除できます。 また同じステップでスケールを決めるので、handlePositionとscaledHandlePositionの信号も無用です:

ソースコードを参照
{
  // We want to use Vega 2, and specify image size
  "version": 2, "width": 300, "height": 80,
  // Set padding to the same value on all sides
  "padding": 12,
  // By default the background is transparent
  "background": "#edf1f7",

  "signals": [
    {
      "name": "isDragging",
      "init": false,
      "streams": [
        {"type": "@handle:mousedown","expr": "true"},
        {"type": "mouseup","expr": "false"}
      ]
    },
    {
      "name": "scaledHandlePosition",
      "streams": [
        {
          "type": "mousemove[isDragging]",
          "expr": "eventX()",
          "scale": {"name": "yearsScale","invert": true}
        }
      ]
    },
    {
      "name": "currentYear",
      "init": 2000,
      "expr": "clamp(parseInt(scaledHandlePosition),1960,2013)"
    }
  ],

  "scales": [
    {
      "name": "yearsScale",
      "type": "linear",
      "zero": false,
      "domain": [1960, 2013],
      "range": "width"
    }
  ],

  "marks": [
    {
      // draw the year label in the upper left corner
      "name": "yearLabel",
      "type": "text",
      "properties": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 25},
          "fontSize": {"value": 32},
          "fontWeight": {"value": "bold"},
          "fill": {"value": "steelblue"}
        },
        "update": {"text": {"signal": "currentYear"} }
      }
    },
    {
      // Draw a horizontal line
      "name": "scrollLine",
      "type": "rule",
      "properties": {
        "enter": {
          "x": {"value": 0},
          "y": {"value": 40},
          "x2": {"value": 300},
          "stroke": {"value": "#000"},
          "strokeWidth": {"value": 2}
        }
      }
    },
    {
      // Draw a triangle shape with a hover effect
      // naming objects allows us to reference them later
      "name": "handle",
      "type": "path",
      "properties": {
        "enter": {
          "y": {"value": 40},
          // path syntax is the same as SVG's path tag
          "path": {"value": "m-5.5,-10l0,20l11.5,-10l-11.5,-10z"},
          "stroke": {"value": "#880"},
          "strokeWidth": {"value": 2.5}
        },
        "update": {
          "x": {"scale": "yearsScale","signal": "currentYear"},
          "fill": {"value": "#fff"}
        },
        // Change fill color of the object on mouse hover
        "hover": {"fill": {"value": "#f00"} }
      }
    }
  ]
}

次のステップ

パート2へ続く