1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Power BI カスタムビジュアル開発 : 積み上げ棒グラフの開発 : グラフの描画

Last updated at Posted at 2020-03-17

前回は積み上げ棒グラフに必要なデータの構造の検討と実装を行いました。今回は取得したデータを使ってグラフを描画します。

グラフの入れ物を用意

まずは必要なライブラリの読込みと、キャンバスの準備を行います。キャンバスは d3 を利用して SVG の中にグラフの領域を追加します。

1. import を visual.ts に追加。

import * as d3 from 'd3';

2. Visual クラスのプロパティを以下に差し替え。

private settings: VisualSettings;
private svg: d3.Selection<d3.BaseType, any, HTMLElement, any>;
private stackBarContainer: d3.Selection<d3.BaseType, any, any, any>;

3. update 関数を以下に差し替え。

public update(options: VisualUpdateOptions) {
    debugger;
    let viewModel = this.converData(options);

    let width = options.viewport.width;
    let height = options.viewport.height;
    let margin = { top: 0, bottom: 0, left: 0, right: 0 }
   
    this.svg
        .attr("width", width)
        .attr("height", height);
}

この状態で描画すると以下のように svc と g 要素が確認できます。
image.png

軸と凡例の要素

変換されたデータはそれぞれが軸と凡例の値を持っているため、値が重複しています。
image.png

update 関数に以下のコードを追加して重複を取り除きます。
let viewModel = this.converData(options); の下に以下コードを追加。

  • map ですべてのオブジェクトから必要な要素 (axis や legend) だけを抽出
  • filter で重複を除外
let axises = viewModel.dataPoints.map(d => d.axis).filter((element, index, array) => {
    return array.indexOf(element) === index
});
let legends = viewModel.dataPoints.map(d => d.legend).filter((element, index, array) => {
    return array.indexOf(element) === index
});

スケール用関数の作成

X 軸、Y 軸の計算は d3 の機能を使うと簡単に実装できます。

d3.scaleBand

特定範囲の数字を、帯(Band)毎に計算して返すため、X 軸の計算に利用します。
例えば以下のように関数を定義したとします。

let x = d3.scaleBand()
    .domain(["orange","apple","banana"])
    .range([0, 90])
    .padding(0.1);

console.log(x("orange"));
console.log(x("apple"));
console.log(x("banana"));

この場合 0 から 90 の数値に対して orange, apple, banana の要素ごとに間隔(padding) 10% を考慮した値を返します。
image.png
image.png

d3.scaleLinear

特定範囲の数値に対して、ある数値(Linear)に対してスケールした値を返すため、Y 軸の計算に利用します。
例えば以下のように関数を定義したとします。

let y = d3.scaleLinear()
    .domain([0, 1000])
    .rangeRound([0, 300])

console.log(y(0));
console.log(y(1000));
console.log(y(500));

この場合 0 から 1000 の値を 0 から 300 にスケールして返します。
image.png

また範囲を逆にすることもでき、棒グラフではよくこの形式を使います。

let y = d3.scaleLinear()
    .domain([0, 1000])
    .rangeRound([0, 300])

console.log(y(0));
console.log(y(1000));
console.log(y(500));

image.png

スケール関数の追加

上記を踏まえ、以下のコードを update 関数に追加します。

  • 値の範囲はカスタムビジュアルのサイズとマージンから計算
let x = d3.scaleBand()
    .domain(axises)
    .range([margin.left, width - margin.right])
    .padding(0.1);

let y = d3.scaleLinear()
    .domain([0, viewModel.maxValue])
    .rangeRound([height - margin.bottom, margin.top]);

凡例の色

凡例毎に別の色を付けたい場合、d3.scaleOrdinal を活用できます。scaleBand と似た動きをしますが、数値の代わりのオブジェクトを返すことができます。また d3 はあらかじめ色の配列を何パータンか提供しているため、今回はそちらを利用します。

update 関数に以下コードを追加します。これで凡例の値を渡すと対応する色を返してくれます。またドメインに指定していない値を渡すと、既定の #ccc を返します。

let color = d3.scaleOrdinal()
    .domain(legends)
    .range(d3.schemeYlOrBr[legends.length])
    .unknown("#ccc");

image.png

尚、schemeYlOrBr をはじめ用意されているものは、渡せる range の数に制限がある点注意してください。
image.png

チャートの描写

最後に積み上げ棒を描写します。d3 の機能を使ってすべてのデータについて、四角(rect)を描写します。

  • fill, x, y, height, width は全て計算で取得
this.stackBarContainer.selectAll('rect')
    .data(viewModel.dataPoints)
    .join('rect')
    .attr('fill', d => color(d.legend).toString())
    .attr("x", (d, i) => x(d.axis.toString()))
    .attr("y", d => y(d.value2))
    .attr("height", d => y(viewModel.maxValue - d.value))
    .attr("width", x.bandwidth());

最終的に update 関数は以下のようになります。

public update(options: VisualUpdateOptions) {
    debugger;
    let viewModel = this.converData(options);

    let axises = viewModel.dataPoints.map(d => d.axis).filter((element, index, array) => {
        return array.indexOf(element) === index
    });

    let legends = viewModel.dataPoints.map(d => d.legend).filter((element, index, array) => {
        return array.indexOf(element) === index
    });

    let width = options.viewport.width;
    let height = options.viewport.height;
    let margin = { top: 0, bottom: 0, left: 0, right: 0 }
    
    this.svg
        .attr("width", width)
        .attr("height", height);
    
    let x = d3.scaleBand()
        .domain(axises)
        .range([margin.left, width - margin.right])
        .padding(0.1);

    let y = d3.scaleLinear()
        .domain([0, viewModel.maxValue])
        .rangeRound([height - margin.bottom, margin.top]);

    let color = d3.scaleOrdinal()
        .domain(legends)
        .range(d3.schemeYlOrBr[legends.length])
        .unknown("#ccc");
    
    this.stackBarContainer.selectAll('rect')
        .data(viewModel.dataPoints)
        .join('rect')
        .attr('fill', d => color(d.legend).toString())
        .attr("x", (d, i) => x(d.axis.toString()))
        .attr("y", d => y(d.value2))
        .attr("height", d => y(viewModel.maxValue - d.value))
        .attr("width", x.bandwidth());
}

動作確認

全てのコードが書き終わったので、実際にカスタムビジュアルを実行します。意図した通りに描写されることを確認します。

image.png

まとめ

今回は d3 のスケール関数を利用しながら積み上げ棒グラフを描写しました。次回は X 軸、Y 軸および凡例の表示を行います。

次の記事へ
目次へ戻る

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?