D3.js で HAR ファイルから散布図を作成

前回、HAR (HTTP ARchive) ファイルから Python で作成した散布図を D3.js を使って SVG として作ってみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20170515/

はじめに

Node.js で D3.js を使用するために d3jsdom をインストールしておきます。

依存ライブラリのインストー
npm install --save d3 jsdom
package.json
{
  ・・・
  "dependencies": {
    "d3": "^4.8.0",
    "jsdom": "^10.1.0"
  }
}

散布図(SVG)の作成

標準入力から HAR ファイルの内容(JSON)を受け取り、time と bodySize の該当位置へ円を描画する事で散布図を表現してみました。

円の色をサブタイプ毎に分けることで色分けしています。

ついでに、ツールチップの表示やマウスイベント処理を追加してみました。

Web ブラウザで SVG ファイルを開いた際にマウスイベントを処理できるように attr を使ってマウスイベント処理(該当する円のサイズを変更)を設定しています。

index.js
const d3 = require('d3');

const jsdom = require('jsdom');
const { JSDOM } = jsdom;

const w = 800;
const h = 500;
const margin = { left: 80, top: 20, bottom: 50, right: 200 };
const legendMargin = { top: 30 };

const fontSize = '12pt';
const circleRadius = 5;

// X軸(time)の値の範囲
const xDomain = [0, 5000];
// Y軸(bodySize)の値の範囲
const yDomain = [0, 180000];

const colorMap = {
    'javascript': 'blue',
    'x-javascript': 'blue',
    'json': 'green',
    'gif': 'tomato',
    'jpeg': 'red',
    'png': 'pink',
    'html': 'lime',
    'css': 'turquoise'
};

const dom = new JSDOM();
const document = dom.window.document;

const toSubType = mtype => mtype.split(';')[0].split('/').pop();
const toColor = stype => colorMap[stype] ? colorMap[stype] : 'black';

process.stdin.resume();

let json = '';

process.stdin.on('data', chunk => json += chunk);

process.stdin.on('end', () => {
    const data = JSON.parse(json);

    const df = data.log.entries.map( d => {
        return {
            'url': d.request.url,
            'subType': toSubType(d.response.content.mimeType),
            'bodySize': d.response.bodySize,
            'time': d.time
        };
    });

    const svg = d3.select(document.body)
        .append('svg')
            .attr('xmlns', 'http://www.w3.org/2000/svg')
            .attr('width', w + margin.left + margin.right)
            .attr('height', h + margin.top + margin.bottom)
            .append('g')
                .attr('transform', `translate(${margin.left}, ${margin.top})`);

    const x = d3.scaleLinear().range([0, w]).domain(xDomain);
    const y = d3.scaleLinear().range([h, 0]).domain(yDomain);

    const xAxis = d3.axisBottom(x);
    const yAxis = d3.axisLeft(y);

    // X軸
    svg.append('g')
        .attr('transform', `translate(0, ${h})`)
        .call(xAxis);
    // Y軸
    svg.append('g')
        .call(yAxis);

    // X軸ラベル
    svg.append('text')
        .attr('x', w / 2)
        .attr('y', h + 40)
        .style('font-size', fontSize)
        .text('Time (ms)');
    // Y軸ラベル
    svg.append('text')
        .attr('x', -h / 2)
        .attr('y', -(margin.left) / 1.5)
        .style('font-size', fontSize)
        .attr('transform', 'rotate(-90)')
        .text('Body Size');

    // 円
    const point = svg.selectAll('circle')
        .data(df)
        .enter()
            .append('circle');
    // 円の設定
    point.attr('class', d => d.subType)
        .attr('cx', d => x(d.time))
        .attr('cy', d => y(d.bodySize))
        .attr('r', circleRadius)
        .attr('fill', d => toColor(d.subType))
        .append('title') // ツールチップの設定
            .text(d => d.url);

    // 凡例
    const legend = svg.selectAll('.legend')
        .data(d3.entries(colorMap))
        .enter()
            .append('g')
                .attr('class', 'legend')
                .attr('transform', (d, i) => {
                    const left = w + margin.left;
                    const top = margin.top + i * legendMargin.top;
                    return `translate(${left}, ${top})`;
                });

    legend.append('circle')
        .attr('r', circleRadius)
        .attr('fill', d => d.value);

    legend.append('text')
        .attr('x', circleRadius * 2)
        .attr('y', 4)
        .style('font-size', fontSize)
        // マウスイベント処理(該当する円のサイズを変更)
        .attr('onmouseover', d => 
            `document.querySelectorAll('circle.${d.key}').forEach(d => d.setAttribute('r', ${circleRadius} * 2))`)
        .attr('onmouseout', d => 
            `document.querySelectorAll('circle.${d.key}').forEach(d => d.setAttribute('r', ${circleRadius}))`)
        // 凡例のラベル
        .text(d => d.key);

    // SVG の出力
    console.log(document.body.innerHTML);
});
実行例
node index.js < a.har > a.svg

実行結果例

a.svg

f:id:fits:20170515220405p:plain

b.svg

f:id:fits:20170515220428p:plain