改悪。

じっくり書き直してみたいものだ。

<!DOCTYPE html>
<title></title>
<meta charset="UTF-8">
<body>

<div><canvas width="400" height="300" id="sample"></canvas></div>
<script>

// Copyright 2007-2009 futomi  http://www.html5.jp/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// radar.js v1.0.2

if( typeof html5jp == 'undefined' ) {
	html5jp = new Object();
}
if( typeof html5jp.graph == 'undefined' ) {
	html5jp.graph = new Object();
}

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* コンストラクタ
* ---------------------------------------------------------------- */
html5jp.graph.radar = function (id) {
	var elm = document.getElementById(id);
	if(! elm) { return; }
	if( ! elm.nodeName.match(/^CANVAS$/i) ) { return; }
	if( ! elm.parentNode.nodeName.match(/^DIV$/i) ) { return; };
	/* CANVAS要素 */
	if ( ! elm.getContext ){ return; }
	this.canvas = elm;
	/* 2D コンテクストの生成 */
	this.ctx = this.canvas.getContext('2d');
	this.canvas.style.margin = "0";
	this.canvas.parentNode.style.position = "relative";
	this.canvas.parentNode.style.padding = "0";
	/* CANVAS要素の親要素となるDIV要素の幅と高さをセット */
	this.canvas.parentNode.style.width = this.canvas.width + "px";
	this.canvas.parentNode.style.height = this.canvas.height + "px";
};
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 描画
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype.draw = function(items, inparams) {
	if( ! this.ctx ) {return;}
	/* パラメータの初期化 */
	var params = {
		aCap: [],
		aCapColor: "#000000",
		aCapFontSize: "12px",
		aCapFontFamily: "Arial,sans-serif",
		aMax: null,
		aMin: 0,
		backgroundColor: "#ffffff",
		cBackgroundColor: "#eeeeee",
		cBackgroundGradation: true,
		chartShape: "polygon",
		faceColors: null,
		_faceColors: ["rgb(24,41,206)", "rgb(198,0,148)", "rgb(214,0,0)", "rgb(255,156,0)", "rgb(33,156,0)", "rgb(33,41,107)", "rgb(115,0,90)", "rgb(132,0,0)", "rgb(165,99,0)", "rgb(24,123,0)"],
		faceAlpha: 0.1,
		borderAlpha: 0.5,
		borderWidth: 1,
		axisColor: "#aaaaaa",
		axisWidth: 1,
		aLinePositions: "auto",
		aLineWidth: 1,
		aLineColor: "#cccccc",
		sLabel: true,
		sLabelColor: "#000000",
		sLabelFontSize: "10px",
		sLabelFontFamily: "Arial,sans-serif",
		legend: true,
		legendFontSize: "12px",
		legendFontFamily: "Arial,sans-serif",
		legendColor: "#000000"
	};
	if( inparams && typeof(inparams) == 'object' ) {
		for( var key in inparams ) {
			if( key.match(/^_/) ) { continue; }
			params[key] = inparams[key];
		}
	}
	if( params.faceColors != null && params.faceColors.length > 0 ) {
		for( var i=0; i<params._faceColors.length; i++ ) {
			var c = params.faceColors[i];
			var co = this._csscolor2rgb(c);
			if( co == null ) {
				params.faceColors[i] = params._faceColors[i];
			} else {
				params.faceColors[i] = c;
			}
		}
	} else {
		params.faceColors = params._faceColors;
	}
	this.params = params;
	/* CANVASの背景を塗る */
	if( params.backgroundColor ) {
		this.ctx.beginPath();
		this.ctx.fillStyle = params.backgroundColor;
		this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
	}
	/* CANVAS要素の横幅が縦幅の1.5倍未満、または縦幅が200未満であれば凡例は強制的に非表示 */
	if(this.canvas.width / this.canvas.height < 1.5 || this.canvas.height < 200) {
		params.legend == false;
	}
	/* CANVAS要素の座標 */
	var canvas_pos = this._getElementAbsPos(this.canvas);
	/* チャートの中心座標と半径 */
	var cpos = {
		x: this.canvas.width / 2,
		y: this.canvas.height / 2,
		r: Math.min(this.canvas.width, this.canvas.height) * 0.75 / 2
	};
	if(params.legend == true) {
		cpos.r = Math.min(this.canvas.width, this.canvas.height) * 0.7 / 2
		cpos.x = this.canvas.height * 0.1 + cpos.r;
	}
	/* 項目の数(最大10個) */
	var item_num = items.length;
	if(item_num > 10) { item_num = 10; }
	params.itemNum = item_num;
	/* 指標の最大数を算出(多角形の角数) 最小3角・最大24角 */
	var angle_num = 0;
	for(var i=0; i<items.length; i++) {
		var n = items[i].length;
		if(angle_num <= n - 1) {
			angle_num = n - 1;
		}
	}
	if(angle_num < 3) {
		angle_num = 3;
	} else if(angle_num > 24) {
		angle_num = 24;
	}
	params.angleNum = angle_num;
	/* 各軸の角度(ラジアン)を算出(右方向を0度とし反時計回りの角度) */
	var axis_angles = [Math.PI/2];
	for(var i=1; i<angle_num; i++) {
		axis_angles.push( Math.PI / 2 - Math.PI * 2 * i / angle_num );
	}
	/* チャートの形状を描画 */
	this._draw_chart_shape(params, cpos, axis_angles);
	/* 全項目の最大値・最小値と項目数を算出 */
	var max_v = null;
	var min_v = null;
	var max_n = 0;
	for(var i=0; i<item_num; i++) {
		var n = items[i].length;
		for(var j=1; j<n; j++) {
			var v = items[i][j];
			if( isNaN(v) ) {
				throw new Error('Item data is invalid. : ' + n);
			}
			if(max_v == null) {
				max_v = v;
			} else if(v >= max_v) {
				max_v = v;
			}
			if(min_v == null) {
				min_v = v;
			} else if(v <= min_v) {
				min_v = v;
			}
		}
		if(n - 1 >= max_n) {
			max_n = n - 1;
		}
	}
	if( typeof(params.aMin) != "number" ) {
		params.aMin = 0;
	}
	if( typeof(params.aMax) != "number" ) {
		params.aMax = max_v;
	}
	/* 補助線の位置を自動算出 */
	if( typeof(params.aLinePositions) == "string" && params.aLinePositions == "auto" ) {
		params.aLinePositions = this._aline_positions_auto_calc(params.aMin, params.aMax);
	}
	/* 補助線を描画 */
	this._draw_aline(params, cpos, axis_angles);
	/* 軸を描画 */
	this._draw_axis(params, cpos, axis_angles);
	/* スケールラベルを描画 */
	this._draw_scale_label(params, cpos, axis_angles);//*********************
	/* 各項目のデフォルト色を定義 */
	/* チャートを描写 */
	for(var i=0; i<items.length; i++) {
		this._draw_radar_chart(params, cpos, axis_angles, items[i], params.faceColors[i]);
	}
	/* キャプションを描画 */
	this._draw_caption(params, cpos, axis_angles);
	/* 凡例を描画 */
	this._draw_legend(items, params, cpos);
};

/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
* 以下、内部関数
* ──────────────────────────────── */

/* ------------------------------------------------------------------
補助線の位置を自動算出
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._aline_positions_auto_calc = function(min, max) {
	var range = max - min;
	var power10 = Math.floor( Math.log(range) / Math.log(10) );
	var unit = Math.pow( 10,  power10);
	if( (Math.log(range) / Math.log(10)) % 1 == 0 ) {
		unit = unit / 10;
	}
	var keta_age = 1;
	if(unit < 1) {
		keta_age += Math.abs(power10);
	}
	var p = Math.pow(10, keta_age);
	range = range * p;
	unit = unit * p;
	min = min * p;
	max = max * p;
	var array = [min];
	var unum = range / unit;
	if( unum > 5 ) {
		unit = unit * 2;
	} else if( unum <= 2 ) {
		unit = unit * 3 / 10
	}
	var i = 1;
	while(min+unit*i<=max) {
		array.push(min+unit*i);
		i++;
	}
	for(var i=0; i<array.length; i++) {
		array[i] = array[i] / p;
	}
	return array;
};
/* ------------------------------------------------------------------
凡例を描画
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._draw_legend = function(items, params, cpos) {
	if(params.legend != true) { return; }
	/* DIV要素を仮に挿入してみて高さを調べる(1行分の高さ) */
	var s = this._getTextBoxSize('あTEST', params.legendFontSize, params.legendFontFamily);
	/* 凡例の各種座標を算出 */
	var lpos = {
		x: Math.round( cpos.x + cpos.r + this.canvas.width * 0.15 ),
		y: Math.round( ( this.canvas.height - ( s.h * params.itemNum + s.h * 0.2 * (params.itemNum - 1) ) ) / 2 ),
		h: s.h
	};
	lpos.cx = lpos.x + Math.round( lpos.h * 1.5 ); // 文字表示開始位置(x座標)
	lpos.cw = this.canvas.width - lpos.cx;         // 文字表示幅
	/* 描画 */
	for(var i=0; i<params.itemNum; i++) {
		/* 文字 */
		this._drawText(lpos.cx, lpos.y, items[i][0], params.legendFontSize, params.legendFontFamily, params.legendColor);
		/* 記号(背景) */
		this._make_path_legend_mark(lpos.x, lpos.y, s.h, s.h);
		this.ctx.fillStyle = params.cBackgroundColor;
		this.ctx.fill();
		/* 記号(塗り) */
		//this._make_path_legend_mark(lpos.x, lpos.y, s.h, s.h);
		this.ctx.fillStyle = params.faceColors[i];
		this.ctx.globalAlpha = params.faceAlpha;
		this.ctx.fill();
		/* 枠線 */
		//this._make_path_legend_mark(lpos.x, lpos.y, s.h, s.h);
		this.ctx.strokeStyle = params.faceColors[i];
		this.ctx.globalAlpha = params.borderAlpha;
		this.ctx.stroke();
		/* */
		lpos.y = lpos.y + lpos.h * 1.2;
	}
};
html5jp.graph.radar.prototype._make_path_legend_mark = function(x,y,w,h) {
	this.ctx.beginPath();
	this.ctx.moveTo(x, y);
	this.ctx.lineTo(x+w, y);
	this.ctx.lineTo(x+w, y+h);
	this.ctx.lineTo(x, y+h);
	this.ctx.closePath();
};
/* ------------------------------------------------------------------
キャプションを描画
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._draw_caption = function(params, cpos, axis_angles) {
	if( typeof(params.aCap) != "object" || params.aCap.length < 1 ) { return; }
	var n = params.aCap.length;
	if(n > params.angleNum) { n = params.angleNum; }
	for(var i=0; i<n; i++) {
		var text = params.aCap[i];
		/* テキスト領域のサイズを算出 */
		var s = this._getTextBoxSize(text, params.aCapFontSize, params.aCapFontFamily);
		/* テキストを描画すべき左上端の座標を算出 */
		var ang = axis_angles[i];
		var x = cpos.x + cpos.r * 1.15 * Math.cos(ang) - s.w / 2;
		var y = cpos.y - cpos.r * 1.15 * Math.sin(ang) - s.h / 2;
		if( x < this.canvas.width * 0.02 ) { x = this.canvas.width * 0.02; }
		if( x + s.w > this.canvas.width * 0.98 ) { x = this.canvas.width * 0.98 - s.w; }
		if( y < this.canvas.height * 0.02 ) { y = this.canvas.height * 0.02; }
		if( y + s.h > this.canvas.height * 0.98 ) { y = this.canvas.height * 0.98 - s.h; }
		x = Math.round(x);
		y = Math.round(y);
		/* テキストを描画 */
		this._drawText(x, y, text, params.aCapFontSize, params.aCapFontFamily, params.aCapColor);
	}
};
/* ------------------------------------------------------------------
スケールラベルを描画
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._draw_scale_label = function(params, cpos, axis_angles) {
	if( params.sLabel != true) { return; }
	if( typeof(params.aLinePositions) != "object" || params.aLinePositions.length < 1 ) { return; }
	for(var i=0; i<params.aLinePositions.length; i++) {
		if( typeof(params.aLinePositions[i]) != "number" ) { continue; }
		if( params.aLinePositions[i] < params.aMin ) { continue; }
		var text = params.aLinePositions[i].toString();
		/* テキスト領域のサイズを算出 */
		var s = this._getTextBoxSize(text, params.sLabelFontSize, params.sLabelFontFamily);
		var r = ( ( params.aLinePositions[i] - params.aMin ) * cpos.r / ( params.aMax - params.aMin ) ) ;
		/* テキストを描画すべき左上端の座標を算出 */
		for (var j = 0, J = i?params.angleNum:1; j < J; j++) {
			var ang = axis_angles[j];
    		var x = Math.sin(ang) * r;
	    	var y = Math.cos(ang) * r;
		/* テキストを描画 */
			this._drawText(Math.round (cpos.x - x + s.w + 3), Math.round (cpos.y - y - ( s.h / 2 )), text, params.sLabelFontSize, params.sLabelFontFamily, params.sLabelColor);
		}
	}
};
/* ------------------------------------------------------------------
チャートを描画
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._draw_radar_chart = function(params, cpos, axis_angles, values, color) {
	/* チャート面を塗りつぶす */
	this._make_path_for_radar_chart(params, cpos, axis_angles, values);
	this.ctx.globalAlpha = params.faceAlpha;
	this.ctx.fillStyle = color;
	this.ctx.fill();
	/* チャート境界線を引く */
	//this._make_path_for_radar_chart(params, cpos, axis_angles, values);
	this.ctx.globalAlpha = params.borderAlpha;
	this.ctx.lineWidth = params.borderWidth;
	this.ctx.strokeStyle = color;
	this.ctx.stroke();
	/* this.ctx.globalAlpha の値を初期値に戻す */
	this.ctx.globalAlpha = 1;
};
html5jp.graph.radar.prototype._make_path_for_radar_chart = function(params, cpos, axis_angles, values) {
	var r0 = 0;
	if( typeof(values[1]) == "number" ) {
		r0 = cpos.r * (values[1] - params.aMin ) / (params.aMax - params.aMin);
		if( r0 < 0 ) { r0 = 0; }
	}
	this.ctx.beginPath();
	this.ctx.moveTo( Math.round( cpos.x + r0 * Math.cos(axis_angles[0]) ), Math.round( cpos.y - r0 * Math.sin(axis_angles[0]) ) );
	for(var i=1; i<axis_angles.length; i++) {
		var r = 0;
		if( typeof(values[i+1]) == "number" ) {
			r = cpos.r * ( values[i+1] - params.aMin ) / (params.aMax - params.aMin);
			if( r < 0 ) { r = 0; }
		}
		this.ctx.lineTo( Math.round( cpos.x + r * Math.cos(axis_angles[i]) ), Math.round( cpos.y - r * Math.sin(axis_angles[i]) ) );
	}
	this.ctx.closePath();
};
/* ------------------------------------------------------------------
軸を描画
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._draw_axis = function(params, cpos, axis_angles) {
	if( typeof(params.axisWidth) != "number" || params.axisWidth <= 0 ) {
		return;
	}
	for(var i=0; i<axis_angles.length; i++) {
		this.ctx.beginPath();
		this.ctx.lineWidth = params.axisWidth;
		this.ctx.strokeStyle = params.axisColor;
		this.ctx.moveTo(cpos.x, cpos.y);
		this.ctx.lineTo( Math.round( cpos.x + cpos.r * Math.cos(axis_angles[i]) ), Math.round( cpos.y - cpos.r * Math.sin(axis_angles[i]) ) );
		this.ctx.stroke();
	}
};
/* ------------------------------------------------------------------
補助線を描画
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._draw_aline = function(params, cpos, axis_angles) {
	if( typeof(params.aLineWidth) != "number" || params.aLineWidth <= 0 ) {
		return;
	}
	if( typeof(params.aLinePositions) != "object" || params.aLinePositions.length < 1 ) {
		return;
	}
	for(var i=0; i<params.aLinePositions.length; i++) {
		if(params.aLinePositions[i] < params.aMin) { continue; }
		var r = cpos.r * ( params.aLinePositions[i] - params.aMin ) / (params.aMax - params.aMin);
		if( r <= 0 ) { continue; }
		this.ctx.beginPath();
		this.ctx.lineWidth = params.aLineWidth;
		this.ctx.strokeStyle = params.aLineColor;
		if(params.chartShape == "polygon") {
			this.ctx.moveTo( Math.round( cpos.x + r * Math.cos(axis_angles[0]) ), Math.round( cpos.y - r * Math.sin(axis_angles[0]) ) );
			for(var j=1; j<axis_angles.length; j++) {
				this.ctx.lineTo( Math.round( cpos.x + r * Math.cos(axis_angles[j]) ), Math.round( cpos.y - r * Math.sin(axis_angles[j]) ) );
			}
			this.ctx.closePath();
		} else if(params.chartShape == "circle") {
			this.ctx.arc(cpos.x, cpos.y, r, 0, Math.PI*2, false);
		} else {
			throw new Error('Option parameter [chartChape] is invalid. : ' + params.chartShape);
		}
		this.ctx.stroke();
	}
};
/* ------------------------------------------------------------------
チャートの形状を描画
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._draw_chart_shape = function(params, cpos, axis_angles) {
	/* チャート形状の塗り */
	this._make_path_chart_shape(params, cpos, axis_angles);
	this.ctx.fillStyle = params.cBackgroundColor;
	this.ctx.fill();
	/* チャート形状のグラデーション */
	if( params.cBackgroundGradation == true && ! document.uniqueID ) {
		this._make_path_chart_shape(params, cpos, axis_angles);
		var radgrad = this.ctx.createRadialGradient(cpos.x,cpos.y,0,cpos.x,cpos.y,cpos.r);
		radgrad.addColorStop(0, "rgba(0,0,0,0)");
		radgrad.addColorStop(0.8, "rgba(0,0,0,0.01)");
		radgrad.addColorStop(1, "rgba(0,0,0,0.1)");
		this.ctx.fillStyle = radgrad;
		this.ctx.fill();
	}
};
html5jp.graph.radar.prototype._make_path_chart_shape = function(params, cpos, axis_angles) {
	this.ctx.beginPath();
	if(params.chartShape == "circle") {
		this.ctx.arc(cpos.x, cpos.y, cpos.r, 0, Math.PI*2, false);
	} else if(params.chartShape == "polygon") {
		this.ctx.moveTo(cpos.x, cpos.y-cpos.r);
		for(var i=0; i<axis_angles.length; i++) {
			var edge_x = Math.round( cpos.x + cpos.r * Math.cos(axis_angles[i]) );
			var edge_y = Math.round( cpos.y - cpos.r * Math.sin(axis_angles[i]) );
			this.ctx.lineTo(edge_x, edge_y);
		}
		this.ctx.closePath();
	} else {
		throw new Error('Option parameter [chartChape] is invalid. : ' + params.chartShape);
	}
};
/* ------------------------------------------------------------------
文字列を描画
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._drawText = function(x, y, text, font_size, font_family, color) {
	var div = document.createElement('DIV');
	div.appendChild( document.createTextNode(text) );
	div.style.fontSize = font_size;
	div.style.fontFamily = font_family;
	div.style.color = color;
	div.style.margin = "0";
	div.style.padding = "0";
	div.style.position = "absolute";
	div.style.left = x.toString() + "px";
	div.style.top = y.toString() + "px";
	this.canvas.parentNode.appendChild(div);
}
/* ------------------------------------------------------------------
文字列表示領域のサイズを取得
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._getTextBoxSize = function(text, font_size, font_family) {
	var tmpdiv = document.createElement('DIV');
	tmpdiv.appendChild( document.createTextNode(text) );
	tmpdiv.style.fontSize = font_size;
	tmpdiv.style.fontFamily = font_family;
	tmpdiv.style.margin = "0";
	tmpdiv.style.padding = "0";
	tmpdiv.style.visible = "hidden";
	tmpdiv.style.position = "absolute";
	tmpdiv.style.left = "0px";
	tmpdiv.style.top = "0px";
	this.canvas.parentNode.appendChild(tmpdiv);
	var o = {
		w: tmpdiv.offsetWidth,
		h: tmpdiv.offsetHeight
	};
	tmpdiv.parentNode.removeChild(tmpdiv);
	return o;
}
/* ------------------------------------------------------------------
ブラウザー表示領域左上端を基点とする座標系におけるelmの左上端の座標
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._getElementAbsPos = function(elm) {
	var obj = new Object();
	obj.x = elm.offsetLeft;
	obj.y = elm.offsetTop;
	while(elm.offsetParent) {
		elm = elm.offsetParent;
		obj.x += elm.offsetLeft;
		obj.y += elm.offsetTop;
	}
	return obj;
};
/* ------------------------------------------------------------------
* CSS色文字列をRGBに変換
* ---------------------------------------------------------------- */
html5jp.graph.radar.prototype._csscolor2rgb = function (c) {
	if( ! c ) { return null; }
	var color_map = {
		black: "#000000",
		gray: "#808080",
		silver: "#c0c0c0",
		white: "#ffffff",
		maroon: "#800000",
		red: "#ff0000",
		purple: "#800080",
		fuchsia: "#ff00ff",
		green: "#008000",
		lime: "#00FF00",
		olive: "#808000",
		yellow: "#FFFF00",
		navy: "#000080",
		blue: "#0000FF",
		teal: "#008080",
		aqua: "#00FFFF"
	};
	c = c.toLowerCase();
	var o = new Object();
	if( c.match(/^[a-zA-Z]+$/) && color_map[c] ) {
		c = color_map[c];
	}
	var m = null;
	if( m = c.match(/^\#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i) ) {
		o.r = parseInt(m[1], 16);
		o.g = parseInt(m[2], 16);
		o.b = parseInt(m[3], 16);
	} else if( m = c.match(/^\#([a-f\d]{1})([a-f\d]{1})([a-f\d]{1})$/i) ) {
		o.r = parseInt(m[1]+"0", 16);
		o.g = parseInt(m[2]+"0", 16);
		o.b = parseInt(m[3]+"0", 16);
	} else if( m = c.match(/^rgba*\(\s*(\d+),\s*(\d+),\s*(\d+)/i) ) {
		o.r = m[1];
		o.g = m[2];
		o.b = m[3];
	} else if( m = c.match(/^rgba*\(\s*(\d+)\%,\s*(\d+)\%,\s*(\d+)\%/i) ) {
		o.r = Math.round(m[1] * 255 / 100);
		o.g = Math.round(m[2] * 255 / 100);
		o.b = Math.round(m[3] * 255 / 100);
	} else {
		return null;
	}
	return o;
};

window.onload = function() {
  var rc = new html5jp.graph.radar("sample");
  if( ! rc ) { return; }
  var items = [
    ["商品A", 5, 2, 4, 5, 3, 2, 4, 4],
    ["商品B", 3, 4, 3, 4, 5, 4, 5, 1]
  ];
  var params = {
    aCap: ["安さ", "性能", "デザイン", "人気", "使いやすさ", "寿命", "軽さ", "強さ"],
	sLabel: true
  }
  rc.draw(items, params);
};
</script>