はじめに
amCharts と連携して、動きが面白いチャートを作成します。
アニメーション効果の棒グラフ Yellowfin と amCharts の連携では、自動で軸と数値が入れ替わっていく棒グラフを作成しました。第 2 弾は、マウスをホバーしたバーのアイコンが動く棒グラフを作成します。
1. 事前準備
1-1. 参照データ
Worldometerから、2025 年 3 月時点の世界各国地域の人口データを入手し、下記のようなテーブルを作成しました。総人口上位 5 か国 + 日本です。
country | flag | population |
---|---|---|
India | https://your_domain/india.png | 1463865525 |
China | https://your_domain/china.png | 1416096094 |
USA | https://your_domain/usa.png | 347275807 |
Indonesia | https://your_domain/indonesia.png | 285721236 |
Pakistan | https://your_domain/pakistan.png | 255219554 |
Japan | https://your_domain/japan.png | 123103479 |
1-2. ビューの作成まで
- 以前の記事 を参考に、『1-2. ビューの作成』 までの作業を実施します。
2. グラフの作成
- 準備が整ったら、グラフの作成に手順を進めます。新規でレポートを作成し、以下の手順に従ってグラフを作成します。
2-1. [データ] ステップ
2-2. [グラフ] ステップ
JavaScriptタブ
- 雛形を全て削除し、以下のコードに置き換えます。コードの処理の内容は後ほど説明します。
全容
generateChart = function(options) {
var $chartDrawDiv = $(options.divSelector);
var processedData = processData(options.dataset.data);
doDrawing($chartDrawDiv, processedData);
},
processData = function(dataset) {
var src = [];
var ds = [];
for (i=0;i<dataset.country.length;i++){
src.push({src:dataset.flag[i].raw_data})
ds.push({
name: dataset.country[i].raw_data,
steps: dataset.population[i].raw_data,
pictureSettings: src[i],
});
}
console.log(JSON.stringify(ds));
return ds;
},
doDrawing = function($chartDrawDiv, ds, obj) {
require(['https://cdn.amcharts.com/lib/5/index.js',
'https://cdn.amcharts.com/lib/5/xy.js',
'https://cdn.amcharts.com/lib/5/themes/Animated.js'], function(){
var $canvas = $('<div id="chartdiv"></div>');
$chartDrawDiv.append($canvas);
am5.ready(function() {
// ルートエレメントの設定
var root = am5.Root.new("chartdiv");
// テーマの設定
root.setThemes([
am5themes_Animated.new(root)
]);
// データセットの受け渡し
var data = ds;
//チャートの設定
var chart = root.container.children.push(
am5xy.XYChart.new(root, {
panX: false,
panY: false,
paddingLeft:0,
paddingRight:30,
wheelX: "none",
wheelY: "none"
})
);
//軸の設定
var yRenderer = am5xy.AxisRendererY.new(root, {
minorGridEnabled:true
});
yRenderer.grid.template.set("visible", false);
var yAxis = chart.yAxes.push(
am5xy.CategoryAxis.new(root, {
categoryField: "name",
renderer: yRenderer,
paddingRight:40
})
);
var xRenderer = am5xy.AxisRendererX.new(root, {
minGridDistance:80,
minorGridEnabled:true
});
var xAxis = chart.xAxes.push(
am5xy.ValueAxis.new(root, {
min: 0,
renderer: xRenderer
})
);
// シリーズなどの設定
var series = chart.series.push(
am5xy.ColumnSeries.new(root, {
name: "Income",
xAxis: xAxis,
yAxis: yAxis,
valueXField: "steps",
categoryYField: "name",
sequencedInterpolation: true,
calculateAggregates: true,
maskBullets: false,
tooltip: am5.Tooltip.new(root, {
dy: -30,
pointerOrientation: "horizontal",
labelText: "{valueX}"
})
})
);
series.columns.template.setAll({
strokeOpacity: 0,
cornerRadiusBR: 10,
cornerRadiusTR: 10,
cornerRadiusBL: 10,
cornerRadiusTL: 10,
maxHeight: 50,
fillOpacity: 0.8
});
//マウスをバーにホバーした時のブレットの動き
var currentlyHovered;
series.columns.template.events.on("pointerover", function(e) {
handleHover(e.target.dataItem);
});
series.columns.template.events.on("pointerout", function(e) {
handleOut();
});
function handleHover(dataItem) {
if (dataItem && currentlyHovered != dataItem) {
handleOut();
currentlyHovered = dataItem;
var bullet = dataItem.bullets[0];
bullet.animate({
key: "locationX",
to: 1,
duration: 600,
easing: am5.ease.out(am5.ease.cubic)
});
}
}
function handleOut() {
if (currentlyHovered) {
var bullet = currentlyHovered.bullets[0];
bullet.animate({
key: "locationX",
to: 0,
duration: 600,
easing: am5.ease.out(am5.ease.cubic)
});
}
}
//ブレットと画像の設定
var circleTemplate = am5.Template.new({});
series.bullets.push(function(root, series, dataItem) {
var bulletContainer = am5.Container.new(root, {});
var circle = bulletContainer.children.push(
am5.Circle.new(
root,
{
radius: 34
},
circleTemplate
)
);
var maskCircle = bulletContainer.children.push(
am5.Circle.new(root, { radius: 27 })
);
var imageContainer = bulletContainer.children.push(
am5.Container.new(root, {
mask: maskCircle
})
);
var image = imageContainer.children.push(
am5.Picture.new(root, {
templateField: "pictureSettings",
centerX: am5.p50,
centerY: am5.p50,
width: 60,
height: 60
})
);
return am5.Bullet.new(root, {
locationX: 0,
sprite: bulletContainer
});
});
// 数値と色の関係
series.set("heatRules", [
{
dataField: "valueX",
min: am5.color(0xe5dc36),
max: am5.color(0x5faa46),
target: series.columns.template,
key: "fill"
},
{
dataField: "valueX",
min: am5.color(0xe5dc36),
max: am5.color(0x5faa46),
target: circleTemplate,
key: "fill"
}
]);
series.data.setAll(data);
yAxis.data.setAll(data);
//ツールチップの動き
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {}));
cursor.lineX.set("visible", false);
cursor.lineY.set("visible", false);
cursor.events.on("cursormoved", function() {
var dataItem = series.get("tooltip").dataItem;
if (dataItem) {
handleHover(dataItem)
}
else {
handleOut();
}
})
// チャートのロード
series.appear();
chart.appear(1000, 100);
});
})
};
コード
簡単に処理内容を説明します。
generateChart
generateChart
generateChart = function(options) {
var $chartDrawDiv = $(options.divSelector);
var processedData = processData(options.dataset.data);
doDrawing($chartDrawDiv, processedData);
},
- JavaScript グラフの処理において、最初に呼び出されるのが
generateChart = function(options)
です。 - [データ] ステップでテーブルに追加した情報が options に格納されて、メソッドに受け渡されます。
- processData でデータ処理を行うメソッドを呼び出し、doDrawingで描画するメソッドをそれぞれ呼び出しています。各メソッドで実行する具体的な処理は後述します。
processData
- データとしては、以下のような連装配列を生成する必要があります。
ds
[
{
"name":"India",
"steps":1463865525,
"pictureSettings":{"src":"https://your_domain/india.png"}
},
{
"name":"China",
"steps":1416096094,
"pictureSettings":{"src":"https://your_domain/china.png"}
},
・・・
]
- そのための処理が以下です。
processData
processData = function(dataset) {
var src = [];
var ds = [];
for (i=0;i<dataset.country.length;i++){
src.push({src:dataset.flag[i].raw_data})
ds.push({
name: dataset.country[i].raw_data,
steps: dataset.population[i].raw_data,
pictureSettings: src[i],
});
}
console.log(JSON.stringify(ds));
return ds;
},
- 生成されるデータの中身を確認したい場合は、
console.log(JSON.stringify(ds))
で、データをコンソールに出力しておくと便利です。
CSS
- CSS タブに下記を記述します。後ほど、定義した id を JavaScript から呼び出します。
css
#chartdiv{
height: 500px;
width: 700px;
}
doDrawing
- チャートの描画に関わる処理を記述します。
doDrawing
doDrawing = function($chartDrawDiv, ds, obj) {
require(['https://cdn.amcharts.com/lib/5/index.js',
'https://cdn.amcharts.com/lib/5/xy.js',
'https://cdn.amcharts.com/lib/5/themes/Animated.js'], function(){
var $canvas = $('<div id="chartdiv"></div>');
$chartDrawDiv.append($canvas);
am5.ready(function() {
// ルートエレメントの設定
var root = am5.Root.new("chartdiv");
// テーマの設定
root.setThemes([
am5themes_Animated.new(root)
]);
// データセットの受け渡し
var data = ds;
//チャートの設定
var chart = root.container.children.push(
am5xy.XYChart.new(root, {
panX: false,
panY: false,
paddingLeft:0,
paddingRight:30,
wheelX: "none",
wheelY: "none"
})
);
//軸の設定
var yRenderer = am5xy.AxisRendererY.new(root, {
minorGridEnabled:true
});
yRenderer.grid.template.set("visible", false);
var yAxis = chart.yAxes.push(
am5xy.CategoryAxis.new(root, {
categoryField: "name",
renderer: yRenderer,
paddingRight:40
})
);
var xRenderer = am5xy.AxisRendererX.new(root, {
minGridDistance:80,
minorGridEnabled:true
});
var xAxis = chart.xAxes.push(
am5xy.ValueAxis.new(root, {
min: 0,
renderer: xRenderer
})
);
// シリーズなどの設定
var series = chart.series.push(
am5xy.ColumnSeries.new(root, {
name: "Income",
xAxis: xAxis,
yAxis: yAxis,
valueXField: "steps",
categoryYField: "name",
sequencedInterpolation: true,
calculateAggregates: true,
maskBullets: false,
tooltip: am5.Tooltip.new(root, {
dy: -30,
pointerOrientation: "horizontal",
labelText: "{valueX}"
})
})
);
series.columns.template.setAll({
strokeOpacity: 0,
cornerRadiusBR: 10,
cornerRadiusTR: 10,
cornerRadiusBL: 10,
cornerRadiusTL: 10,
maxHeight: 50,
fillOpacity: 0.8
});
//マウスをバーにホバーした時のブレットの動き
var currentlyHovered;
series.columns.template.events.on("pointerover", function(e) {
handleHover(e.target.dataItem);
});
series.columns.template.events.on("pointerout", function(e) {
handleOut();
});
function handleHover(dataItem) {
if (dataItem && currentlyHovered != dataItem) {
handleOut();
currentlyHovered = dataItem;
var bullet = dataItem.bullets[0];
bullet.animate({
key: "locationX",
to: 1,
duration: 600,
easing: am5.ease.out(am5.ease.cubic)
});
}
}
function handleOut() {
if (currentlyHovered) {
var bullet = currentlyHovered.bullets[0];
bullet.animate({
key: "locationX",
to: 0,
duration: 600,
easing: am5.ease.out(am5.ease.cubic)
});
}
}
//ブレットと画像の設定
var circleTemplate = am5.Template.new({});
series.bullets.push(function(root, series, dataItem) {
var bulletContainer = am5.Container.new(root, {});
var circle = bulletContainer.children.push(
am5.Circle.new(
root,
{
radius: 34
},
circleTemplate
)
);
var maskCircle = bulletContainer.children.push(
am5.Circle.new(root, { radius: 27 })
);
var imageContainer = bulletContainer.children.push(
am5.Container.new(root, {
mask: maskCircle
})
);
var image = imageContainer.children.push(
am5.Picture.new(root, {
templateField: "pictureSettings",
centerX: am5.p50,
centerY: am5.p50,
width: 60,
height: 60
})
);
return am5.Bullet.new(root, {
locationX: 0,
sprite: bulletContainer
});
});
// 数値と色の関係
series.set("heatRules", [
{
dataField: "valueX",
min: am5.color(0xe5dc36),
max: am5.color(0x5faa46),
target: series.columns.template,
key: "fill"
},
{
dataField: "valueX",
min: am5.color(0xe5dc36),
max: am5.color(0x5faa46),
target: circleTemplate,
key: "fill"
}
]);
series.data.setAll(data);
yAxis.data.setAll(data);
//ツールチップの動き
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {}));
cursor.lineX.set("visible", false);
cursor.lineY.set("visible", false);
cursor.events.on("cursormoved", function() {
var dataItem = series.get("tooltip").dataItem;
if (dataItem) {
handleHover(dataItem)
}
else {
handleOut();
}
})
// チャートのロード
series.appear();
chart.appear(1000, 100);
});
})
};
-
require[]
の中で、JavaScriptライブラリの場所を指定します。上記の例では CDN のアドレスを指定していますが、JS ライブラリをダウンロードして利用することも可能です。 -
var root = am5.Root.new("chartdiv")
で、CSS で定義した id を指定します。 - 先の処理で生成したデータセットを、
var data = ds
でライブラリに受け渡しています。 - それ以降の処理では、テーマ、チャート、軸、シリーズ、アニメーション、画像、色、ツールチップを設定した後、最後にチャートをロードしています。それぞれの設定箇所は、コメントアウト行をご確認下さい。
3. 動作確認
チャートを表示してみると、第 3 位の米国を大きく突き放し、インドと中国の人口の多さが目立ちます。
日本は国土が小さいため、他の 5 か国と比べて人口が少ないのは仕方のない部分もありますが、今後はさらに人口の減少が見込まれます。特に、生産労働人口の減少が顕著となる見込みです。果たして年金制度は維持できるんでしょうか。老後が心配、、、
4. おまけ
doDrawing
generateChart = function(options) {
var $chartDrawDiv = $(options.divSelector);
var processedData = processData(options.dataset.data);
doDrawing($chartDrawDiv, processedData);
},
processData = function(dataset) {
var src = [];
var ds = [];
for (i=0;i<dataset.country.length;i++){
src.push({src:dataset.flag[i].raw_data})
ds.push({
name: dataset.country[i].raw_data,
steps: dataset.population[i].raw_data,
pictureSettings: src[i],
});
}
console.log(JSON.stringify(ds));
return ds;
},
doDrawing = function($chartDrawDiv, ds, obj) {
require(['https://cdn.amcharts.com/lib/5/index.js',
'https://cdn.amcharts.com/lib/5/xy.js',
'https://cdn.amcharts.com/lib/5/themes/Animated.js'], function(){
var $canvas = $('<div id="chartdiv"></div>');
$chartDrawDiv.append($canvas);
am5.ready(function() {
// ルートエレメントの設定
var root = am5.Root.new("chartdiv");
// テーマの設定
root.setThemes([
am5themes_Animated.new(root)
]);
// データセットの受け渡し
var data = ds;
// チャートの設定
var chart = root.container.children.push(
am5xy.XYChart.new(root, {
panX: false,
panY: false,
wheelX: "none",
wheelY: "none",
paddingBottom: 50,
paddingTop: 40,
paddingLeft:0,
paddingRight:0
})
);
// 軸の設定
var xRenderer = am5xy.AxisRendererX.new(root, {
minorGridEnabled:true,
minGridDistance:60
});
xRenderer.grid.template.set("visible", false);
var xAxis = chart.xAxes.push(
am5xy.CategoryAxis.new(root, {
paddingTop:40,
categoryField: "name",
renderer: xRenderer
})
);
var yRenderer = am5xy.AxisRendererY.new(root, {});
yRenderer.grid.template.set("strokeDasharray", [3]);
var yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
min: 0,
renderer: yRenderer
})
);
// シリーズの追加
var series = chart.series.push(
am5xy.ColumnSeries.new(root, {
name: "Income",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "steps",
categoryXField: "name",
sequencedInterpolation: true,
calculateAggregates: true,
maskBullets: false,
tooltip: am5.Tooltip.new(root, {
dy: -30,
pointerOrientation: "vertical",
labelText: "{valueY}"
})
})
);
series.columns.template.setAll({
strokeOpacity: 0,
cornerRadiusBR: 10,
cornerRadiusTR: 10,
cornerRadiusBL: 10,
cornerRadiusTL: 10,
maxWidth: 50,
fillOpacity: 0.8
});
//マウスをバーにホバーした時のブレットの動き
var currentlyHovered;
series.columns.template.events.on("pointerover", function (e) {
handleHover(e.target.dataItem);
});
series.columns.template.events.on("pointerout", function (e) {
handleOut();
});
function handleHover(dataItem) {
if (dataItem && currentlyHovered != dataItem) {
handleOut();
currentlyHovered = dataItem;
var bullet = dataItem.bullets[0];
bullet.animate({
key: "locationY",
to: 1,
duration: 600,
easing: am5.ease.out(am5.ease.cubic)
});
}
}
function handleOut() {
if (currentlyHovered) {
var bullet = currentlyHovered.bullets[0];
bullet.animate({
key: "locationY",
to: 0,
duration: 600,
easing: am5.ease.out(am5.ease.cubic)
});
}
}
//ブレットと画像の設定
var circleTemplate = am5.Template.new({});
series.bullets.push(function (root, series, dataItem) {
var bulletContainer = am5.Container.new(root, {});
var circle = bulletContainer.children.push(
am5.Circle.new(
root,
{
radius: 34
},
circleTemplate
)
);
var maskCircle = bulletContainer.children.push(
am5.Circle.new(root, { radius: 27 })
);
var imageContainer = bulletContainer.children.push(
am5.Container.new(root, {
mask: maskCircle
})
);
var image = imageContainer.children.push(
am5.Picture.new(root, {
templateField: "pictureSettings",
centerX: am5.p50,
centerY: am5.p50,
width: 40,
height: 40
})
);
return am5.Bullet.new(root, {
locationY: 0,
sprite: bulletContainer
});
});
// 数値と色の関係
series.set("heatRules", [
{
dataField: "valueY",
min: am5.color(0xe5dc36),
max: am5.color(0x5faa46),
target: series.columns.template,
key: "fill"
},
{
dataField: "valueY",
min: am5.color(0xe5dc36),
max: am5.color(0x5faa46),
target: circleTemplate,
key: "fill"
}
]);
series.data.setAll(data);
xAxis.data.setAll(data);
//ツールチップの動き
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {}));
cursor.lineX.set("visible", false);
cursor.lineY.set("visible", false);
cursor.events.on("cursormoved", function () {
var dataItem = series.get("tooltip").dataItem;
if (dataItem) {
handleHover(dataItem);
} else {
handleOut();
}
});
// チャートのロード
series.appear();
chart.appear(1000, 100);
});
})
};
最後に
動きが面白いこと以外、特に通常の棒グラフと用途は変わりませんが、こんなチャートがあっても良いのでは。
では皆様、良いデータ分析を!