データ可視化

Snow Flower Text は Vega / Vega-Lite によるデータの可視化をサポートしています。 データの可視化はデータの持つ特徴を視覚的に表現できデータ全体の概要を把握する場合に効果的です。AsciiDoc 文書で Vega 仕様のJSON 形式のテキストを記述することでデータ可視化図を文書に埋め込むことが出来ます。Snow Flower Text では Vega エンジンを内蔵していますので、 追加のインストールなしに利用を始めることができます。

vega ブロックは Snow Flower Text の独自拡張です。 Vega による可視化の画像はプリレンダリング方式で負荷が増加しないようになっているため、文書に埋め込む図が増えてもプレビューを確認しながら快適な編集を継続できます。

Vega は可視化を記述する言語です。データを扱うSQL言語のように、データの可視化を扱う言語として設計されています。 Vega-Lite は統計グラフィックスを素早く作成するために Vega 上に構築された高級言語です。 Vega の設計はスタンフォード大学とワシントン大学での長年の研究開発に基づいています。

この記事では vega ブロックの使い方について解説します。 Vega 及び Vega-Lite 仕様の JSON の詳細について知りたい場合は次の URL を参照してください。

特徴

AsciiDoc 文書は image マクロで単純に画像を埋め込むことが出来ますが、 Snow Flower Text の Vega 拡張機能は次の点で優れています。

データ交換が容易

Vega / Vega-Lite 仕様は、JSON 形式のみで視覚化を定義します。 データ交換する対象が明確で容易で、豊富なサンプルソースをテンプレートとして編集して利用できます。

検索可能

レンダリングされたチャートのラベルはテキスト検索可能な状態を維持します。 この特徴は HTML や PDF、DocBook といった形式にエクスポートした後にも継承されます。 ラスター画像を埋め込むより検索可能性(Findability)に優れています。

変更追跡可能

データの可視化を可読性の高いテキストで記述します。 したがって、複数のバージョンで変更された箇所は、 一般的なテキストの比較で簡単に見つけることが出来ます。 これは情報をバイナリで保持するオフィスソフトにはない優れた特徴です。 バイナリ形式のファイルと比較してトレーサビリティ(Traceability)が優れています。

ベクトルイメージ

可視化図はベクトル画像なのであらゆる媒体で鮮明で綺麗にレンダリングされます。 どれだけ拡大してもジャギーが発生することはありません。 ラスター画像を埋め込むより可搬性(Portability)に優れています。

vega ブロックの使い方

vega ブロックを使用することで Vega 及び VagaLite のデータ可視化言語を使用してデータを可視化できます。 次のように記述します。

[vega]
----
Vega or VegaLite JSON
----

vega ブロック内で、Vega あるいは Vega-Lite仕様の JSON で可視化図を定義します。 Snow Flower Text の vega ブロック拡張では、Vega 仕様のJSON と Vega-Lite 仕様にJSONを自動的に判別します。

例えば、シンプルな棒グラフは Vega-Lite で次のように記述します。

[vega]
----
{
"width": 600,
"data": {
"values": [
{"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43},
{"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53},
{"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52}
]
},
"mark": "bar",
"encoding": {
"x": {"field": "a", "type": "nominal", "axis": {"labelAngle": 0}},
"y": {"field": "b", "type": "quantitative"}
}
}
----

これは次のようにレンダリングされます。

Simple bar chart

Vega と Vega-Lite の使い分け

Vega-Lite は 必ず Vega に変換されてからベクトル画像に変換されます。 Vega-Lite から Vega の変換の際に軸、凡例、スケールなどの統計図のための定義が付け加えられます。

Vega-Lite で表現できる図は Vega でも表現出来ますが、 一般的な統計図の場合は Vega よりも Vega-Lite の方が簡潔で読みやすい記述になるので 優先的に使用することを推奨します。

書式の国際化

ブロックパラメタでロケールコードを指定することで、 数値・通貨・日付のプリセットの書式をロケールに対応させることが出来ます。 ロケールコードは IETF 標準化された短い文字列を使用します。

次のパラメタを指定できます。

localeNumber

数値の書式のためのロケールを指定します。 現在のバージョンでは次のロケールコードを指定できます。対応していないロケールコードを指定した場合は無視されます。

  • de-DE

  • en-CA

  • en-GB

  • en-US

  • es-ES

  • fr-CA

  • fr-FR

  • it-IT

  • ja-JP

  • ko-KR

  • nl-NL

  • pl-PL

  • pt-BR

  • ru-RU

  • sv-SE

  • zh-CN

  • zh-TW

localeTime

時間の書式のためのロケールを設定します。 現在のバージョンでは次のロケールコードを指定できます。対応していないロケールコードを指定した場合は無視されます。

  • da-DK

  • de-DE

  • en-CA

  • en-GB

  • en-US

  • es-ES

  • fr-CA

  • fr-FR

  • it-IT

  • ja-JP

  • ko-KR

  • nl-NL

  • pl-PL

  • pt-BR

  • ru-RU

  • sv-SE

  • zh-CN

  • zh-TW

localTime でロケールコード を指定することで、 プリセット書式を適用できます。

次の例では ja-JP(日本のロケール)を指定しています。

[vega, localeTime="ja-JP"]
----------------------------------------
{
"width": 600,
"data": {"url": "https://vega.github.io/editor/data/seattle-weather.csv"},
"mark": "bar",
"encoding": {
"x": {
"timeUnit": "month",
"field": "date",
"type": "ordinal",
"title": "Month of the year"
},
"y": {
"aggregate": "count",
"type": "quantitative"
},
"color": {
"field": "weather",
"type": "nominal",
"scale": {
"domain": ["sun", "fog", "drizzle", "rain", "snow"]
},
"title": "Weather type"
}
}
}
----------------------------------------

これは次のようにレンダリングされます。 x軸が指定したロケールの書式になっています。

表 1. ロケールコード一覧
Locale codeLanguageLocation

da-DK

Danish

Denmark

de-DE

German

Germany

en-CA

English

Canada

en-GB

English

United Kingdom

en-US

English

United States

es-ES

Spanish

Spain

fr-CA

French

Canada

fr-FR

French

France

it-IT

Italian

Italy

ja-JP

Japanese

Japan

ko-KR

Korean

Korea

nl-NL

Dutch

Netherlands

pl-PL

Polish

Poland

pt-BR

Portuguese

Brazil

ru-RU

Russian

Russia

sv-SE

Swedish

Sweden

zh-CN

Chinese (Simplified)

People’s Republic of China

zh-TW

Chinese (Traditional)

Taiwan

配色

Vega ではカテゴリカルデータに色を効果的に適用できるように 慎重に設計されたプリセット配色が用意されています。 特に次のプリセット配色は 一般的な色覚異常のあるユーザーにとって使いやすいものです。

tableau10

category10

tableau20

category20

次の例は aフィールドの塗り分けに tableau10 の配色名を指定しています。

[vega]
----
{
"width": 600,
"data": {
"values": [
{"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43},
{"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53},
{"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52}
]
},
"mark": "bar",
"encoding": {
"x": {
"field": "a",
"type": "nominal",
"axis": {"labelAngle": 0}
},
"y": {
"field": "b",
"type": "quantitative"
},
"color": {
"field": "a",
"scale": {"scheme": "tableau10"}
}
}
}
----

これは次のようにレンダリングされます。

{“scheme”: “tableau10”}

その他の配色については次の URL の記事の “Scheme Reference” を参照ください。

独自のカスタム・カラーパレットを作成して使用することもできます。 例えば企業のブランドに合わせた配色を作成するのに役立ちます。 詳しくは次の URL の記事の “Registering Additional Schemes” を参考にしてください。

vega と chart ブロックの使い分け

Snow Flower Text の AsciiDoc 拡張では、chart ブロックがあって棒グラフ、折線グラフ等の作図に対応しています。 vega と chart ブロックは機能的には部分的に重複していますが、 特徴が異なります。vega ブロック記述は chart ブロックよりも記述の難しくなりますが vega ブロックは表現できる範囲が広がります。 これはトレードオフの関係です。

Features of the chart block and vega block

Appendix A: サンプルソース

Bar chart

A simple bar chart with embedded data.
[vega]
----
{
"width": 600,
"data": {
"values": [
{"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43},
{"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53},
{"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52}
]
},
"mark": "bar",
"encoding": {
"x": {"field": "a", "type": "nominal", "axis": {"labelAngle": 0}},
"y": {"field": "b", "type": "quantitative"}
}
}
----

Horizontal Bar chart

Bar chart の x と y の定義を交換することで 水平方向にレイアウト出来ます。

[vega]
----
{
"width": 600,
"data": {
"values": [
{"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43},
{"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53},
{"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52}
]
},
"mark": "bar",
"encoding": {
"y": {"field": "a", "type": "nominal", "axis": {"labelAngle": 0}},
"x": {"field": "b", "type": "quantitative"}
}
}
----

Stacked Bar char

[vega]
----
{
"width": 600,
"data": {"url": "https://vega.github.io/editor/data/seattle-weather.csv"},
"mark": "bar",
"encoding": {
"x": {
"timeUnit": "month",
"field": "date",
"type": "ordinal",
"title": "Month of the year"
},
"y": {
"aggregate": "count",
"type": "quantitative"
},
"color": {
"field": "weather",
"type": "nominal",
"scale": {
"domain": ["sun", "fog", "drizzle", "rain", "snow"]
},
"title": "Weather type"
}
}
}
----

Horizontal Stacked Bar char

[vega]
----
{
"width": 600,
"data": {"url": "https://vega.github.io/editor/data/seattle-weather.csv"},
"mark": "bar",
"encoding": {
"y": {
"timeUnit": "month",
"field": "date",
"type": "ordinal",
"title": "Month of the year"
},
"x": {
"aggregate": "count",
"type": "quantitative"
},
"color": {
"field": "weather",
"type": "nominal",
"scale": {
"domain": ["sun", "fog", "drizzle", "rain", "snow"]
},
"title": "Weather type"
}
}
}
----

Grouped bar char

[vega]
----
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": { "url": "https://vega.github.io/editor/data/population.json"},
  "transform": [
    {"filter": "datum.year == 2000"},
    {"calculate": "datum.sex == 2 ? 'Female' : 'Male'", "as": "gender"}
  ],
  "width": {"step": 12},
  "mark": "bar",
  "encoding": {
    "column": {
      "field": "age", "type": "ordinal", "spacing": 10
    },
    "y": {
      "aggregate": "sum", "field": "people",
      "title": "population",
      "axis": {"grid": false}
    },
    "x": {
      "field": "gender",
      "axis": {"title": ""}
    },
    "color": {
      "field": "gender",
      "scale": {"range": ["#675193", "#ca8861"]}
    }
  },
  "config": {
    "view": {"stroke": "transparent"},
    "axis": {"domainWidth": 1}
  }
}
----

Line chart

[vega]
----
{
"width": 600,
"data": {"url": "https://vega.github.io/vega-lite/examples/data/stocks.csv"},
"transform": [{"filter": "datum.symbol==='GOOG'"}],
"mark": "line",
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"field": "price", "type": "quantitative"}
}
}
----

Area chart

[vega]
----
{
"width": 600,
"data": {
"url": "https://vega.github.io/vega-lite/examples/data/penguins.json"
},
"mark": "area",
"transform": [
{
"density": "Body Mass (g)",
"groupby": ["Species"],
"extent": [2500, 6500]
}
],
"encoding": {
"x": {"field": "value", "type": "quantitative", "title": "Body Mass (g)"},
"y": {"field": "density", "type": "quantitative", "stack": "zero"},
"color": {"field": "Species", "type": "nominal"}
}
}
----

Pie chart

[vega]
----
{
"data": {
"values": [
{"category": 1, "value": 4},
{"category": 2, "value": 6},
{"category": 3, "value": 10},
{"category": 4, "value": 3},
{"category": 5, "value": 7},
{"category": 6, "value": 8}
]
},
"mark": "arc",
"encoding": {
"theta": {"field": "value", "type": "quantitative"},
"color": {"field": "category", "type": "nominal"}
},
"view": {"stroke": null}
}
----

Ring chart

[vega]
----
{
"data": {
"values": [
{"category": 1, "value": 4},
{"category": 2, "value": 6},
{"category": 3, "value": 10},
{"category": 4, "value": 3},
{"category": 5, "value": 7},
{"category": 6, "value": 8}
]
},
"mark": {"type": "arc", "innerRadius": 50},
"encoding": {
"theta": {"field": "value", "type": "quantitative"},
"color": {"field": "category", "type": "nominal"}
},
"view": {"stroke": null}
}
----

2D Histogram Scatterplot

[vega]
----
{
"width": 400,
"data": {"url": "https://vega.github.io/vega-lite/examples/data/movies.json"},
"mark": "circle",
"encoding": {
"x": {
"bin": {"maxbins": 10},
"field": "IMDB Rating"
},
"y": {
"bin": {"maxbins": 10},
"field": "Rotten Tomatoes Rating"
},
"size": {"aggregate": "count"}
}
}
----

Heatmap

[vega]
----
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"data": {
"url": "https://vega.github.io/vega/data/seattle-weather.csv"
},
"title": "Daily Max Temperatures (C) in Seattle, WA",
"config": {
"view": {
"strokeWidth": 0,
"step": 13
},
"axis": {
"domain": false
}
},
"mark": "rect",
"encoding": {
"x": {
"field": "date",
"timeUnit": "date",
"type": "ordinal",
"title": "Day",
"axis": {
"labelAngle": 0,
"format": "%e"
}
},
"y": {
"field": "date",
"timeUnit": "month",
"type": "ordinal",
"title": "Month"
},
"color": {
"field": "temp_max",
"aggregate": "max",
"type": "quantitative",
"legend": {
"title": null
}
}
}
}
----

Sun Burst

[vega]
----
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 600,
"height": 600,
"padding": 5,
"autosize": "none",
"data": [
{
"name": "tree",
"url": "https://vega.github.io/vega/data/flare.json",
"transform": [
{
"type": "stratify",
"key": "id",
"parentKey": "parent"
},
{
"type": "partition",
"field": "size",
"sort": {"field": "value"},
"size": [{"signal": "2 * PI"}, {"signal": "width / 2"}],
"as": ["a0", "r0", "a1", "r1", "depth", "children"]
}
]
}
],
"scales": [
{
"name": "color",
"type": "ordinal",
"domain": {"data": "tree", "field": "depth"},
"range": {"scheme": "tableau20"}
}
],
"marks": [
{
"type": "arc",
"from": {"data": "tree"},
"encode": {
"enter": {
"x": {"signal": "width / 2"},
"y": {"signal": "height / 2"},
"fill": {"scale": "color", "field": "depth"},
"tooltip": {"signal": "datum.name + (datum.size ? ', ' + datum.size + ' bytes' : '')"}
},
"update": {
"startAngle": {"field": "a0"},
"endAngle": {"field": "a1"},
"innerRadius": {"field": "r0"},
"outerRadius": {"field": "r1"},
"stroke": {"value": "white"},
"strokeWidth": {"value": 0.5},
"zindex": {"value": 0}
},
"hover": {
"stroke": {"value": "red"},
"strokeWidth": {"value": 2},
"zindex": {"value": 1}
}
}
}
]
}
----
Download on the Mac App Store