4
7

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.

Vue.js公式サイトの「SVGグラフの例」を改善する

Last updated at Posted at 2018-03-30

Vue.js公式サイトの「SVGグラフの例」は、SVGでレーダーチャートを描き、値がスライダで動的に操作できます。さらに、データを新たに加えたり除いたりすることも可能です。この作例をECMAScript 2015 (ECMAScript 6)の構文に改めたうえ、コードの手直しも加えてみました(サンプル001)。その改善の要点をかいつまんでご説明します。コードの詳しい解説は「Vue.js + ES6: SVGでレーダーチャートを操作する」をお読みください。

サンプル001■ Vue.js + ES6: Radar chart with SVG controlled dynamically - final

See the Pen Vue.js + ES6: Radar chart with SVG controlled dynamically - final by Fumio Nonaka (@FumioNonaka) on CodePen.

#ECMAScript 2015の構文に書き替える
公式作例は、つぎのようなECMAScript 5の構文で書かれています。

var stats = [

]

Vue.component('polygraph', {

	computed: {
		points: function () {

		}
	},

});

ECMAScript 2015では、変数は基本的にconstまたはletで宣言します。また、オブジェクトにメソッドを定めるとき、コロン(:)とfunctionキーワードが省けます(「ECMAScript 2015での新しい表記法」)。

const stats = [

];

Vue.component('polygraph', {

	computed: {
		points() {

		}
	},

});

#SVGの円のデータとコードを関連づける
公式作例でレーダーチャートの頂点座標を求める関数には、つぎのような直打ちされた数値があります。

function valueToPoint (value, index, total) {

	var y = -value * 0.8

	var tx = x * cos - y * sin + 100
	var ty = x * sin + y * cos + 100

}

この数値は、SVGの<circle>要素の属性値からきています。円の中心座標が(100, 100)、半径はチャートの値が100%のとき80ピクセルということです。

<script type="text/x-template" id="polygraph-template">
<g>

	<circle cx="100" cy="100" r="80"></circle>

</g>
</script>

そこで、これら<circle>要素の属性値はオブジェクトのプロパティにまとめ、JavaScriptコードはその値を参照することにします。

const circle = {cx: 100, cy: 100, r: 80};

function valueToPoint(value, index, total) {

	const y = -value * circle.r / 100;

	const tx = x * cos - y * sin + circle.cx;
	const ty = x * sin + y * cos + circle.cy;

}

<body>要素とテンプレートは、v-bindディレクティブ(省略記法:)でオブジェクトをバインディングして用います。

<body>
<div id="demo">
	<svg width="200" height="200">
		<polygraph :stats="stats" :circle="circle"></polygraph>
	</svg>

</div>
</body>
<script type="text/x-template" id="polygraph-template">
<g>

	<circle :cx="circle.cx" :cy="circle.cy" :r="circle.r"></circle>

</g>
</script>

#原点からの距離と角度により座標を求める

任意の点$(x, y)$を、原点$(0, 0)$から角度$\theta$回した座標$(x', y')$はつぎの式で求められます(「任意の座標を原点から指定した角度回す」参照)。座標の原点からの距離を求めなくて済み、多くの点を同じ角度回す場合は便利です。

x' = x\cos\theta - y\sin\theta\\
y' = x\sin\theta + y\cos\theta

公式作例では、レーダーチャートの頂点座標をこの式で導いています。

function valueToPoint (value, index, total) {
	var x = 0
	var y = -value * 0.8
	var angle = Math.PI * 2 / total * index
	var cos = Math.cos(angle)
	var sin = Math.sin(angle)
	var tx = x * cos - y * sin + 100
	var ty = x * sin + y * cos + 100
	return {
		x: tx,
		y: ty
	}
}

けれど、この作例では頂点座標の原点からの距離が予め(パーセンテージで)与えられており、角度は頂点ごとに異なります。この場合、距離$r$と角度$\theta$から座標を求めるつぎの式の方が端的でしょう。

x = r\cos\theta\\
y = r\sin\theta

####三角関数のsinとcos
図001

function valueToPoint(value, index, total) {
	const r = value * circle.r / 100;
	const angle = Math.PI * 2 / total * index - Math.PI / 2;
	const tx = r * Math.cos(angle) + circle.cx;
	const ty = r * Math.sin(angle) + circle.cy;
	return {
		x: tx,
		y: ty
	};
}

#Vue.js「スタイルガイド」に合わせる
Vue.js「スタイルガイド」で「優先度A: 必須」の修正も加えます。まず、コンポーネントのpropsです。

Vue.component('polygraph', {
	props: ['stats'],

});

プロパティには、少なくともタイプを定めるべきです(「プロパティの定義」参照)。

Vue.component('polygraph', {
	props: {
		stats: Array,

	},

});

つぎに、v-forディレクティブには、key属性を与えなければなりません(「キー付きv-for」参照)。keyがないと、開発用のVue.jsはコンソールにつぎのような警告を示します。

[Vue tip]: : component lists rendered with v-for should have explicit keys. See https://vuejs.org/guide/list.html#key for more info.

そこで、レーダーチャート用のデータに、一意のプロパティを加えます。Array.prototype.map()メソッドでインデックスの値を与えることで、手打ちの手間は省きました。

const stats = [
	{label: 'A', value: 100},
	{label: 'B', value: 100},
	{label: 'C', value: 100},
	{label: 'D', value: 100},
	{label: 'E', value: 100},
	{label: 'F', value: 100}
].map((stat, index) => {
	stat.id = index;
	return stat;
});

そして、v-forディレクティブが用いられた要素には、keyにプロパティ値を定めます。

<body>
<div id="demo">

	<div v-for="stat in stats"
		:key="stat.id">

	</div>

</div>
</body>
<script type="text/x-template" id="polygraph-template">
<g>

	<axis-label
		v-for="(stat, index) in stats"

		:key="stat.id">
	</axis-label>
</g>
</script>

データを加えるメソッドも、一意の数値をプロパティとして加えなければなりません。Array.prototype.map()メソッドですでに使われている値を取り出し、Math.max()メソッドにより得た最大値に1を加えたのが新たな値です。ECMAScript 2015から備わったスプレッド構文...は、配列をカンマ(,)区切りの引数として渡します。

const vm = new Vue({

	methods: {
		add(event) {

			this.stats.push({

				id: this.getNewId()
			})
		},

		getNewId() {
			const ids = this.stats.map((stat) => stat.id);
			return Math.max(...ids) + 1;
		}
	}
});

#その他の修正
ほかにも、細かい修正を3つほど加えました。第1に、公式作例はVue()コンストラクタのオプションオブジェクトにelでアプリケーションとする要素を定めています。

new Vue({
	el: '#demo',

}) 

このelは省いて、DOMContentLoadedイベントのリスナー関数からvm.$mount()メソッドを呼び出すようにしました。これで、JavaScriptコードは<head>要素に書けます。

const vm = new Vue({

});
document.addEventListener('DOMContentLoaded', (event) =>
	vm.$mount('#demo')
);

第2に、公式作例は新たなデータを加えるとき、テキストが空かどうかしか確かめていません。これですと、スペースだけでも通ってしまいます。

new Vue({

	methods: {
		add: function (e) {

			if (!this.newLabel) return

			this.newLabel = ''
		},

	}
})

そこで、String.prototype.trim()メソッドを用いて、スペースだけの場合も弾くこととしました。

const vm = new Vue({

	methods: {
		add(event) {

			const newLabel = this.newLabel.trim();
			this.newLabel = '';
			if (!newLabel) {return}

		},

	}
});

第3は、公式作例がデータの削除にArray.prototype.indexOf()メソッドを使っていることです。このメソッドを用いるとき気をつけなければならないのは、参照する配列そのものが変わってしまうことです。配列要素のループ処理で用いると、取り出す順序が壊されるおそれもあります。

new Vue({

	methods: {

		remove: function (stat) {
			if (this.stats.length > 3) {
				this.stats.splice(this.stats.indexOf(stat), 1)
			} else {

			}
		}
	}
})

もっとも、この例では配列そのものからデータを除きたいわけですし、ループ処理でもありません。けれど、ECMAScript 5.1の新しいメソッドとしてArray.prototype.filter()を使うことにしました。引数の関数に定めたのは、削除する要素以外という条件です。これで、その要素の除かれた配列に改められます。

const vm = new Vue({

	methods: {

		remove(stat) {
			const stats = this.stats;
			if (stats.length > 3) {
				this.stats = stats.filter((_stat) => _stat !== stat);
			} else {

			}
		},

	}
});
4
7
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
4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?