🌟
WebFOCUS拡張グラフ開発でよく使うパターン
WebFOCUS拡張グラフの一般的なパターン
このドキュメントでは、WebFOCUS拡張グラフ開発でよく使われるコードパターンと実装例を紹介します。
GitHubにあるcom.ibi.の拡張グラフを一通り読んで作成したものです。
1. データ処理パターン
1.1 データの正規化
// シリーズブレークがない場合、データを2次元配列に正規化
if (renderConfig.dataBuckets.depth === 1) {
data = [data];
}
// 1つの測定値しかない場合、タイトルを配列に正規化
if (renderConfig.dataBuckets.buckets.value && !Array.isArray(renderConfig.dataBuckets.buckets.value.title)) {
renderConfig.dataBuckets.buckets.value.title = [renderConfig.dataBuckets.buckets.value.title];
}
1.2 バケットプロパティの安全な取得
// バケットが存在するか確認してから値を取得
function getBucketTitle(bucketName, renderConfig, defaultValue) {
if (renderConfig.dataBuckets &&
renderConfig.dataBuckets.buckets &&
renderConfig.dataBuckets.buckets[bucketName]) {
return renderConfig.dataBuckets.buckets[bucketName].title || defaultValue;
}
return defaultValue;
}
// 使用例
var valueTitle = getBucketTitle('value', renderConfig, 'Value');
1.3 データ配列の転置
// 2次元データ配列を転置する
function transposeData(data) {
return data[0].map(function (_, colIndex) {
return data.map(function(row) {
return row[colIndex];
});
});
}
// 使用例
var transposedData = transposeData(renderConfig.data);
2. レンダリングパターン
2.1 SVG要素の基本設定
function setupSVG(container, width, height, margin) {
// SVG要素のクリア
container.selectAll('*').remove();
// SVG要素の作成
var svg = container.append('svg')
.attr('width', width)
.attr('height', height);
// グラフエリアの作成
var chartArea = svg.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
return {
svg: svg,
chartArea: chartArea,
width: width - margin.left - margin.right,
height: height - margin.top - margin.bottom
};
}
// 使用例
var margin = {top: 20, right: 30, bottom: 40, left: 50};
var chart = setupSVG(d3.select(renderConfig.container), renderConfig.width, renderConfig.height, margin);
2.2 軸の設定と描画
function createAxes(svg, x, y, chartWidth, chartHeight, margin) {
// X軸の作成
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
// Y軸の作成
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
// X軸の描画
svg.append('g')
.attr('class', 'x-axis')
.attr('transform', 'translate(0,' + chartHeight + ')')
.call(xAxis);
// Y軸の描画
svg.append('g')
.attr('class', 'y-axis')
.call(yAxis);
return {
xAxis: xAxis,
yAxis: yAxis
};
}
// 使用例
var x = d3.scale.ordinal().domain(labels).rangeRoundBands([0, chart.width], 0.1);
var y = d3.scale.linear().domain([0, maxValue]).range([chart.height, 0]);
var axes = createAxes(chart.chartArea, x, y, chart.width, chart.height, margin);
2.3 レスポンシブフォントサイズ
function calculateResponsiveFontSize(width, height, baseSize, minSize, maxSize) {
var size = Math.min(width, height) / baseSize;
return Math.max(minSize, Math.min(maxSize, size)) + 'px';
}
// 使用例
var labelFontSize = calculateResponsiveFontSize(renderConfig.width, renderConfig.height, 40, 10, 16);
svg.selectAll('.axis-label')
.style('font-size', labelFontSize);
3. ツールチップとデータ選択のパターン
3.1 ツールチップの追加
function addTooltips(elements, renderConfig, seriesIndex) {
elements.each(function(d, i) {
renderConfig.modules.tooltip.addDefaultToolTipContent(this, seriesIndex, i, d);
});
// 最後にツールチップを更新
renderConfig.modules.tooltip.updateToolTips();
}
// 使用例
var bars = svg.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', function(d, i) {
return chart.buildClassName('riser', 0, i, 'bar');
});
addTooltips(bars, renderConfig, 0);
3.2 カスタムツールチップ内容
var config = {
// ...
modules: {
tooltip: {
supported: true,
autoContent: function(target, s, g, d, data) {
var content = '<div class="tooltip-title">' + d.labels + '</div>';
content += '<div class="tooltip-body">';
content += '<div class="tooltip-row">';
content += '<span class="tooltip-label">値:</span>';
content += '<span class="tooltip-value">' + chart.formatNumber(d.value, "#,###.##") + '</span>';
content += '</div>';
content += '</div>';
return content;
}
}
}
};
3.3 データ選択の有効化
function setupDataSelection(renderConfig, elements) {
// データ選択のサポートに必要なクラス名を追加
elements.attr('class', function(d, s, g) {
return chart.buildClassName('riser', s, g, 'bar');
});
// レンダリング完了後にデータ選択を有効化
renderConfig.renderComplete();
renderConfig.modules.dataSelection.activateSelection();
}
// 使用例
var bars = svg.selectAll('rect')
.data(data)
.enter().append('rect')
// ... その他の属性設定
setupDataSelection(renderConfig, bars);
4. エラー処理とデバッグ
4.1 try-catchでの安全なレンダリング
function renderCallback(renderConfig) {
try {
// チャートのレンダリング処理
var chart = renderConfig.moonbeamInstance;
var data = renderConfig.data;
// ... レンダリングコード
// 正常に完了
renderConfig.renderComplete();
} catch (e) {
// エラーをログ出力
console.error("レンダリングエラー:", e);
// エラーメッセージを表示
var container = d3.select(renderConfig.container);
container.selectAll('*').remove();
container.append('text')
.attr('x', renderConfig.width / 2)
.attr('y', renderConfig.height / 2)
.attr('text-anchor', 'middle')
.text('エラーが発生しました: ' + e.message);
// エラーが発生した場合も完了を通知
renderConfig.renderComplete();
}
}
4.2 デバッグ情報の表示
function showDebugInfo(container, renderConfig) {
if (!renderConfig.properties.debug) return;
var debugPanel = container.append('div')
.attr('class', 'debug-panel')
.style('position', 'absolute')
.style('top', '0')
.style('right', '0')
.style('background', 'rgba(255,255,255,0.9)')
.style('border', '1px solid #ccc')
.style('padding', '5px')
.style('font-size', '10px')
.style('max-width', '300px');
debugPanel.append('div')
.text('データ構造:')
.style('font-weight', 'bold');
debugPanel.append('pre')
.text(JSON.stringify(renderConfig.data, null, 2))
.style('max-height', '200px')
.style('overflow', 'auto');
debugPanel.append('div')
.text('プロパティ:')
.style('font-weight', 'bold');
debugPanel.append('pre')
.text(JSON.stringify(renderConfig.properties, null, 2));
}
// 使用例
function renderCallback(renderConfig) {
var container = d3.select(renderConfig.container);
// ... レンダリングコード
showDebugInfo(container, renderConfig);
renderConfig.renderComplete();
}
5. アニメーションパターン
5.1 バーチャートのアニメーション
function animateBars(bars, y, height, duration) {
bars.attr('y', height)
.attr('height', 0)
.transition()
.duration(duration)
.delay(function(d, i) { return i * 50; })
.attr('y', function(d) { return y(d.value); })
.attr('height', function(d) { return height - y(d.value); });
}
// 使用例
var bars = svg.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('x', function(d) { return x(d.label); })
.attr('width', x.rangeBand());
animateBars(bars, y, chartHeight, 800);
5.2 円グラフのアニメーション
function animatePie(arcs, arc, duration) {
arcs.each(function(d) {
d.startAngle = 0;
d.endAngle = 0;
})
.attr('d', arc)
.transition()
.duration(duration)
.attrTween('d', function(d) {
var interpolate = d3.interpolate(
{startAngle: 0, endAngle: 0},
{startAngle: d.startAngle, endAngle: d.endAngle}
);
return function(t) {
return arc(interpolate(t));
};
});
}
// 使用例
var arc = d3.svg.arc()
.innerRadius(0)
.outerRadius(radius);
var arcs = svg.selectAll('.arc')
.data(pie(data))
.enter().append('path')
.attr('class', 'arc')
.attr('fill', function(d, i) { return colors(i); });
animatePie(arcs, arc, 1000);
6. レスポンシブデザインパターン
6.1 可変マージン計算
function calculateMargin(width, height) {
return {
top: Math.max(10, height * 0.05),
right: Math.max(15, width * 0.05),
bottom: Math.max(30, height * 0.1),
left: Math.max(40, width * 0.1)
};
}
// 使用例
var margin = calculateMargin(renderConfig.width, renderConfig.height);
var chartWidth = renderConfig.width - margin.left - margin.right;
var chartHeight = renderConfig.height - margin.top - margin.bottom;
6.2 コンテナサイズに応じたレイアウト調整
function adjustLayout(width, height) {
var layout = {};
// 小さいサイズの場合のレイアウト
if (width < 400 || height < 300) {
layout.showLabels = false;
layout.fontSize = '8px';
layout.legendPosition = 'bottom';
}
// 中くらいのサイズの場合のレイアウト
else if (width < 600 || height < 400) {
layout.showLabels = true;
layout.fontSize = '10px';
layout.legendPosition = 'right';
}
// 大きいサイズの場合のレイアウト
else {
layout.showLabels = true;
layout.fontSize = '12px';
layout.legendPosition = 'right';
}
return layout;
}
// 使用例
var layout = adjustLayout(renderConfig.width, renderConfig.height);
if (layout.showLabels) {
// ラベルの描画処理
}
7. 配色パターン
7.1 ダイナミックカラースケール
function createColorScale(data, colorRange) {
var min = d3.min(data, function(d) { return d.value; });
var max = d3.max(data, function(d) { return d.value; });
return d3.scale.linear()
.domain([min, min + (max - min) / 2, max])
.range(colorRange || ['#2c7bb6', '#ffffbf', '#d7191c']);
}
// 使用例
var colorScale = createColorScale(data, ['#9ecae1', '#4292c6', '#084594']);
bars.attr('fill', function(d) { return colorScale(d.value); });
7.2 カテゴリ別の配色
function createCategoryColorScale(categories) {
return d3.scale.ordinal()
.domain(categories)
.range(d3.scale.category10().range());
}
// 使用例
var categories = data.map(function(d) { return d.category; });
var colorScale = createCategoryColorScale(categories);
svg.selectAll('.bar')
.data(data)
.attr('fill', function(d) { return colorScale(d.category); });
8. パフォーマンスパターン
8.1 大量のデータポイントの効率的な描画
function renderLargeDataSet(container, data, width, height) {
// データをビニングして描画数を減らす
var binSize = Math.ceil(data.length / 1000);
var binnedData = [];
for (var i = 0; i < data.length; i += binSize) {
var binValues = data.slice(i, i + binSize);
var sum = binValues.reduce(function(acc, val) { return acc + val.value; }, 0);
binnedData.push({
x: i,
value: sum / binValues.length // 平均値
});
}
// 縮小したデータセットを描画
container.selectAll('.point')
.data(binnedData)
.enter().append('circle')
.attr('class', 'point')
.attr('cx', function(d) { return xScale(d.x); })
.attr('cy', function(d) { return yScale(d.value); })
.attr('r', 2);
}
// 使用例
if (data.length > 1000) {
renderLargeDataSet(svg, data, width, height);
} else {
// 通常のレンダリング
}
8.2 キャンバスを使用した高速描画
function renderWithCanvas(container, data, width, height) {
// HTMLキャンバスを作成
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
container.node().appendChild(canvas);
var ctx = canvas.getContext('2d');
// データポイントを描画
ctx.fillStyle = 'steelblue';
data.forEach(function(d) {
var x = xScale(d.x);
var y = yScale(d.y);
ctx.beginPath();
ctx.arc(x, y, 2, 0, 2 * Math.PI);
ctx.fill();
});
}
// 使用例
if (data.length > 5000) {
renderWithCanvas(d3.select(renderConfig.container), data, width, height);
} else {
// SVGを使用した通常のレンダリング
}
9. データ変換パターン
9.1 階層データの平坦化
function flattenHierarchy(data) {
var result = [];
function traverse(node, parentPath) {
var currentPath = parentPath ? parentPath + '.' + node.name : node.name;
result.push({
name: node.name,
path: currentPath,
value: node.value || 0,
level: parentPath ? parentPath.split('.').length : 0
});
if (node.children && node.children.length) {
node.children.forEach(function(child) {
traverse(child, currentPath);
});
}
}
data.forEach(function(root) {
traverse(root, '');
});
return result;
}
// 使用例
var hierarchyData = [
{
name: "A",
children: [
{name: "A1", value: 5},
{name: "A2", value: 3}
]
}
];
var flatData = flattenHierarchy(hierarchyData);
9.2 時系列データの集計
function aggregateTimeData(data, interval) {
// データをタイムスタンプでソート
data.sort(function(a, b) {
return a.timestamp - b.timestamp;
});
var result = [];
var currentBucket = null;
var currentSum = 0;
var currentCount = 0;
data.forEach(function(d) {
// タイムスタンプを指定された間隔で丸める
var bucketTime = Math.floor(d.timestamp / interval) * interval;
if (currentBucket === null) {
currentBucket = bucketTime;
}
// 新しいバケットの場合、前のバケットを結果に追加
if (bucketTime !== currentBucket) {
result.push({
timestamp: currentBucket,
value: currentSum / currentCount
});
currentBucket = bucketTime;
currentSum = d.value;
currentCount = 1;
} else {
currentSum += d.value;
currentCount++;
}
});
// 最後のバケットを追加
if (currentCount > 0) {
result.push({
timestamp: currentBucket,
value: currentSum / currentCount
});
}
return result;
}
// 使用例
var hourlyData = aggregateTimeData(rawData, 3600000); // 1時間ごとに集計
10. その他の便利なユーティリティパターン
10.1 データの欠損値の処理
function handleMissingValues(data, defaultValue) {
return data.map(function(d) {
if (d.value === null || d.value === undefined || isNaN(d.value)) {
d.value = defaultValue;
d.isMissing = true;
}
return d;
});
}
// 使用例
var cleanData = handleMissingValues(data, 0);
bars.attr('class', function(d) {
return d.isMissing ? 'bar missing-data' : 'bar';
});
10.2 カスタムフォーマッタ
function createCustomFormatter(format, options) {
return function(value) {
if (value === null || value === undefined || isNaN(value)) {
return options.nullRepresentation || '';
}
var formattedValue = chart.formatNumber(value, format);
if (options.prefix) {
formattedValue = options.prefix + formattedValue;
}
if (options.suffix) {
formattedValue += options.suffix;
}
return formattedValue;
};
}
// 使用例
var currencyFormatter = createCustomFormatter('#,###.00', {
prefix: '¥',
nullRepresentation: 'N/A'
});
dataLabels.text(function(d) {
return currencyFormatter(d.value);
});
これらのパターンと例を参考にして、WebFOCUS拡張グラフの効率的な開発を行ってください。パターンを適切に組み合わせることで、保守性が高く、再利用可能なコードを作成できます。
Discussion