[HTML5+JS+Canvas, PHP]アナログ時計の作り方

canvas_clock

Javascript と html5 の canvas を組み合わせてリアルタイムで動作するアナログ時計を作ってみました。
おまけですが PHP と GD を使ったバージョンも合わせて掲載しています。

動作サンプル

Html5 + Javascript + canvas

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

<script type="text/javascript">
<!--
window.onload = function()
{
	var width   = 100;
	var height  = 100;
	var centerX = Math.floor(width / 2);
	var centerY = Math.floor(height / 2);
	var canvas  = document.getElementById('clock');
	var context = canvas.getContext('2d');
	
	setInterval(drawClock, 1000);
	drawClock();
		
    function drawClock() {
		
		var date   = new Date();
		var hour   = date.getHours();
		var minute = date.getMinutes();
		var second = date.getSeconds();

		
		// 色の指定
		context.strokeStyle = '#666666';

		context.clearRect(0, 0, canvas.width, canvas.height);
		
		// 文字盤の描画
		context.beginPath();
		context.arc(centerX, centerY, centerX - 1, 0, Math.PI*2, false);
		context.stroke();
			
		context.save();
		context.translate(width/2, height/2);
		for(var i=0;i < 360; i+= 30){
			context.rotate( 30 * Math.PI / 180);
			context.beginPath();
			context.moveTo(0, centerY * 0.8);
			context.lineTo(0, centerY * 0.9);
			context.stroke();
		}
		context.translate(-width/2, -height/2);
		context.restore();
		
		context.strokeStyle = '#333333';
		
		drawHand(centerY * 0.5, hour * 30 + minute / 60 * 30);
		drawHand(centerY * 0.8, minute * 6 + second / 60 * 6);
		
		context.strokeStyle = '#EE0000';
		
		drawHand(centerY * 0.8, second * 6);
	}
	
	// 針の描画
	function drawHand(length, angle){
		context.save();
		context.translate(centerX, centerY);
		context.rotate( angle * Math.PI / 180);
		context.beginPath();
		context.moveTo(0, 0);
		context.lineTo(0, -length);
		context.stroke();
		context.restore();
	}
}
//-->
</script>

</head>
<body>
<canvas id="clock"></canvas>
</body>
</html>

rotate() だけでは回転軸を指定して回転させる事ができないので、translate() を使って画像全体をずらし、回転させた後で元の位置に戻すことで回転の中心を変えています。


PHP + GD

PHP の imagedrawellipse() と imagedrawline() の組み合わせでも一応作れます。
こちらはリアルタイム動作ではありません。また、サーバー側のリソースを消費するので使い道はあまり無いかもしれません。

clock

<?php
date_default_timezone_set('Asia/Tokyo');

$date   = new DateTime();
$hour   = (int)$date->format("h");
$minute = (int)$date->format("i");

$width  = 100;
$height = 100;

$centerX = floor($width / 2);
$centerY = floor($height / 2);

$img       = imagecreatetruecolor($width, $height);
$handColor = imagecolorallocate($img, 0x33, 0x33, 0x33);
$dialColor = imagecolorallocate($img, 0xAA, 0xAA, 0xAA);
$bg        = imagecolorallocate($img, 0xFF, 0xFF, 0xFF);

imagefill($img, 0, 0, $bg);
imagecolortransparent($img, $bg);

// 文字盤の描画
imageellipse($img, $centerX, $centerY, $width - 1, $width -1, $dialColor);

for($i=0;$i < 360;$i+=30){
	list($x1, $y1) = point_rotate($centerX * 0.8, $i);
	list($x2, $y2) = point_rotate($centerX * 0.9, $i);
	imageline($img, $x1 + $centerX, $y1 + $centerY, $x2 + $centerX, $y2 + $centerY, $dialColor);
}

// 針の描画
$hour = $hour % 12;
list($hourX, $hourY) = point_rotate($centerX * 0.5, $hour * 30 + $minute / 2);
imageline($img, $centerX, $centerY, $hourX + $centerX, $hourY + $centerY, $handColor);
list($minuteX, $minuteY) = point_rotate($centerX * 0.8, $minute * 6);
imageline($img, $centerX, $centerY, $minuteX + $centerX, $minuteY + $centerY, $handColor);

header("Content-type:image/png");
imagepng($img);
imagedestroy($img);


function point_rotate($r, $degree){
  $radian = deg2rad($degree - 90);
  $x = round($r * cos($radian));
  $y = round($r * sin($radian));
  return array($x, $y);
}

[PHP]背景の明るさを基準に見やすい文字色を組み合わせる(YUV輝度)

背景色によって白か黒の文字色を選ぶ時、黒い背景の時は白い文字、白い背景の文字の時は黒い文字が見やすいのは確かですが、青い背景や赤い背景、黄色の背景ではどうでしょうか?

RGB値はそれぞれ 0 ~ 255 の数値で表され、0 に近いほど暗くなり、255 に近いほど白っぽくなるのはご存知かと思います。
そこで、RGB の平均が半分を超えていれば明るい背景で、半分以下なら暗い背景であると単純に考えてしまうのは誤りです。

青(0, 0, 255) はその基準では暗い背景であり、確かに白い文字は見やすいです。

SAMPLE

緑色(0, 255, 0) も同じ平均値なので白い文字を使ってみます。

SAMPLE

かなり読みづらいのがわかると思います。
人間の視覚では RGB の青は暗く、緑は明るく見えるからです。

そこで映像分野でよく使われている輝度成分を利用します。
輝度(Y)は次の変換公式がよく使われています。

Y = 0.299 × R + 0.587 × G + 0.114 × B

では、RGBの平均を基準にしたものと、輝度を基準にしたもので文字の見え方がどう変わるか見てみます。

yuv

RGBでは緑、ピンクが見づらいですが、輝度を基準にしたものは解消されています。算出に用いた PHP のプログラムは以下です。

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Sample</title>
</head>
<body>
<?php
$colors = array(
	array(0, 0, 0),
	array(255, 255, 255),
	array(255, 0, 0),
	array(0, 255, 0),
	array(0, 0, 255),
	array(255, 255, 0),
	array(0, 255, 255),
	array(255, 0, 255)
);

foreach($colors as $color){
	list($r, $g, $b) = $color;
	$y = 0.299 * $r + 0.587 * $g + 0.114 * $b;
	$html = <<<EOD
<span style="background-color: rgb(%d, %d, %d);color: %s;">
SAMPLE</span><br />
EOD;

	$textColor = ($y > 127) ? 'black' : 'white';
	printf($html, $r, $g, $b, $textColor);
}
?>
</body>
</html>

参考: Wikipedia「YUV」
http://ja.wikipedia.org/wiki/YUV

[PHP]投稿数や出現回数に応じて文字サイズが変わるタグクラウド

ブログなどで記事数の多さによってタグリンクの文字サイズが変わるタグクラウドを設置する事はよくあります。
作り方はいくつもあると思いますが、2種類の方法で作ってみました。

tagcloud

タグ名・出現数・リンク先を指定する方法

<?php
// フォントサイズの指定
$max_size = 28;
$min_size = 8;

// name, counts, link
$tags = array(
	array('Technology',    3, '1.html'),
	array('Entertainment', 3, '2.html'),
	array('Movie',         3, '3.html'),
	array('Music',         5, '4.html'),
	array('Culture',       2, '5.html'),
	array('News',          3, '6.html'),
	array('Web',           5, '7.html'),
	array('Radio',         2, '8.html'),
	array('Business',      1, '9.html'),
	array('Photo',         2, '10.html'),
	array('Sports',        1, '11.html'),
	array('Art',           2, '12.html'),
	array('History',       1, '13.html'),
	array('Blog',          1, '14.html'),
	array('Hobby',         1, '15.html'),
	array('Food',          1, '16.html'),
	array('Communication', 1, '17.html')
);

// 多い順に並び替える場合
//usort($tags, function($a, $b){ return $b[1] - $a[1];});

function generate_tagcloud($tags, $max_size, $min_size){
	$counts = array();
	foreach($tags as $value){ $counts[] = $value[1]; }
	$sum = array_sum($counts);

	// 最小値と最大値
	$max = max($counts);
	$min = min($counts);

	foreach($tags as $tag){
		$perc = ($max == $min) ? 1.0 : ($tag[1] - $min) / ($max - $min);
		$size = round( ($max_size - $min_size) * $perc + $min_size );
		
		if(isset($tag[2])){
			printf(
				'<span style="font-size:%dpx;"><a href="%s">%s</a></span>' . "\n",
				 $size, h($tag[2]), h($tag[0])
			);
		} else {
			printf('<span style="font-size:%dpx">%s</span>' . "\n", $size, h($tag[0]) );
		}
	}
}

function h($str){
	return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
?>
<html>
<head><title>Sample</title>
<style type="text/css">
.tagcloud {width: 300px;padding: 0;"}
.tagcloud li { display: inline; }
</style>
</head>
<body>
  <div class="tagcloud">
  <?php generate_tagcloud($tags, $max_size, $min_size);?>
  </div>
</body>
</html>

タグがの出現数がわからない場合

<?php
// フォントサイズの指定
$max_size = 28;
$min_size = 8;

// タグ
$tags = array(
'Technology', 'Entertainment', 'Movie',
'Music', 'Culture', 'News', 'Music',
'Web', 'Technology', 'Radio', 'News',
'Movie', 'Music', 'Entertainment', 'Web',
'Business', 'Culture', 'Photo', 'Sports',
'Music', 'Web', 'Technology', 'Entertainment',
'Movie', 'Radio', 'Photo', 'Web', 'News',
'Art', 'History', 'Blog', 'Hobby', 'Art',
'Food', 'Web', 'Communication', 'Music'
);

// 対応するリンク
$links = array(
	'Web'           => 'web.html',
	'Music'         => 'music.html',
	'Technology'    => 'technology.html',
	'Entertainment' => 'entertainment.html'
	// 以下略
);

function generate_tagcloud($tags, $max_size, $min_size, $links=array()){
	// 出現回数を配列化
	$counts = array_count_values($tags);

	// 多い順に並び替える場合
	//arsort($counts);

	// 合計を取得
	$sum = array_sum($counts);

	// 最小値と最大値
	$max = max($counts);
	$min = min($counts);

	foreach($counts as $key => $value){
		$perc = ($max == $min) ? 1.0 : ($value - $min) / ($max - $min);
		$size = round( ($max_size - $min_size) * $perc + $min_size );
		
		if(isset($links[$key])){
			printf(
				'<span style="font-size:%dpx;"><a href="%s">%s</a></span>' . "\n",
				 $size, h($links[$key]), h($key)
			);
		} else {
			printf('<span style="font-size:%dpx">%s</span>' . "\n", $size, h($key) );
		}
	}
}

function h($str){
	return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
?>
<html>
<head>
  <title>Sample</title>
</head>
<body>
  <div class="tag cloud" style="width: 300px;padding: 0;">
  <?php generate_tagcloud($tags, $max_size, $min_size, $links);?>
  </div>
</body>
</html>

文字サイズの上限と下限を設定し、タグに関する配列を generate_tagcloud() に渡すとタグクラウドが生成されます。
$perc には 0.00〜1.00 の形でパーセンテージが格納されます。これを使って文字の濃淡を付けてみるのも良いと思います。