Web技術で真ん中がくりぬかれたようなドーナツ型の円形グラフを作る
2026年10月19日 Posted 野々瀨(フロントエンドエンジニア)
Webの技術で真ん中がくり抜かれたようなドーナツ型の円形グラフを、ライブラリを使わずに作る方法をご紹介します。

Canvas APIで作る
Canvas APIを使用して作る方法です。まずcanvas要素を記述します。
<canvas width="160" height="160"></canvas>
次のようなJavaScriptを記述します。
const datas = [
{
value : 36,
color : "#ffb1b1"
},
{
value : 20,
color : "#ffbe35"
},
{
value : 25,
color : "#bddd77"
},
{
value : 5,
color : "#38f9d1"
},
{
value : 14,
color : "#99baff"
}
];
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const halfWidth = canvas.width / 2;
const total = datas.reduce((t, d) => t + d.value, 0);
const outRadius = (halfWidth / 3) * 2; // 中央を抜く大きさ(半径)
let prevRadians = -90 * (Math.PI / 180); // 円の開始を90度垂直に
let prevAngle = 0;
datas.forEach(data => {
const angle = data.value / total * 360;
const radians = (prevAngle + angle - 90) * (Math.PI / 180);
ctx.beginPath();
ctx.fillStyle = data.color;
ctx.arc(halfWidth, halfWidth, halfWidth, prevRadians, radians);
ctx.lineTo(halfWidth, halfWidth);
ctx.closePath();
ctx.fill();
prevRadians = radians;
prevAngle += angle;
});
// くり抜く
ctx.beginPath();
ctx.fillStyle = "#fff";
ctx.arc(halfWidth, halfWidth, outRadius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
ポイント
データの値の合計を100%として360度でそれぞれ割って、そこから角度を求めて円を描いていっています。
CanvasRenderingContext2D.arcメソッドで円形のグラフを作っています。
中央のくりぬきは白塗りの円形を作ってそれっぽく見せています。
CSSのconic-gradient関数で作る
CSSのconic-gradient関数で作る方法です。まず空の要素を記述します。
<div class="sample"></div>
次のようなCSSを記述します。
.sample {
--val-1: 36;
--val-2: 20;
--val-3: 25;
--val-4: 5;
--val-5: 14;
--color-1: #ffb1b1;
--color-2: #ffbe35;
--color-3: #bddd77;
--color-4: #38f9d1;
--color-5: #99baff;
--total: calc(var(--val-1) + var(--val-2) + var(--val-3) + var(--val-4) + var(--val-5));
--angle-1: calc(var(--val-1) / var(--total) * 360deg);
--angle-2: calc(var(--angle-1) + (var(--val-2) / var(--total) * 360deg));
--angle-3: calc(var(--angle-2) + (var(--val-3) / var(--total) * 360deg));
--angle-4: calc(var(--angle-3) + (var(--val-4) / var(--total) * 360deg));
border-radius: 50%;
width: 160px;
aspect-ratio: 1 / 1;
background:
radial-gradient(#fff 46%, transparent 46.6%),
conic-gradient(
var(--color-1) var(--angle-1),
var(--color-2) var(--angle-1), var(--color-2) var(--angle-2),
var(--color-3) var(--angle-2), var(--color-3) var(--angle-3),
var(--color-4) var(--angle-3), var(--color-4) var(--angle-4),
var(--color-5) var(--angle-4), var(--color-5)
);
}
ポイント
calc関数で値の割合と角度を算出します。
conic-gradient関数で扇型のグラデーションを作れるので、それを応用して角度と色を当てます。
中央のくりぬきはradial-gradient関数で白塗りの円形を作ります。
SVGで作る
SVGで作る方法です。SVG自身は自動で計算はできないので、あらかじめ別途算出したりJavaScriptで動的に処理したりといったことが必要です。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="160" height="160" transform="rotate(-90)">
<circle cx="50" cy="50" r="41" fill="none" stroke-width="18" stroke="#ffb1b1" stroke-dasharray="92.6928,257.48" />
<circle cx="50" cy="50" r="41" fill="none" stroke-width="18" stroke="#ffbe35" stroke-dasharray="51.496,314" stroke-dashoffset="-92.6928" />
<circle cx="50" cy="50" r="41" fill="none" stroke-width="18" stroke="#bddd77" stroke-dasharray="64.37,314" stroke-dashoffset="-144.188" />
<circle cx="50" cy="50" r="41" fill="none" stroke-width="18" stroke="#38f9d1" stroke-dasharray="12.874,314" stroke-dashoffset="-208.5588" />
<circle cx="50" cy="50" r="41" fill="none" stroke-width="18" stroke="#99baff" stroke-dasharray="36.0472,314" stroke-dashoffset="-221.4328" />
</svg>
ポイント
円を描く際、水平方向の右側から開始されるため、垂直上から開始するようにsvg要素のtransform属性で-90度回転させます。
stroke-width属性でグラフ部分の太さを指定します。
r属性で半径を指定しますが、stroke-width属性による太さは外側と内側に広がって描画されるため、stroke-width属性の値の半分の値を引いた値をr属性に指定します。
stroke-dasharray属性で線を破線にします。グラフのデータを反映するための指定で、値をデータの円周の長さの割合,円周の長さで指定します。
stroke-dashoffset属性で破線の開始位置を指定します。直前までの「データの円周の長さの割合」を足した値を指定します。stroke-dashoffset属性は引っ張るという意味合いなので、負で指定することで前進します。例えば三つ目のデータは一つ目と二つ目の「データの円周の長さの割合」を足した値を負の値で指定します。
PHPのGDで作る
// データ
$datas = [
[
'value' => 36,
'color' => '#ffb1b1'
],
[
'value' => 20,
'color' => '#ffbe35'
],
[
'value' => 25,
'color' => '#bddd77'
],
[
'value' => 5,
'color' => '#38f9d1'
],
[
'value' => 14,
'color' => '#99baff'
]
];
// グラフの大きさ
$size = 160;
// ジャギーを少なくするためのスケール
$scale = 3;
/**
* 16進数カラーを10進数RGBカラーに変換
*/
function hex2rgb($hex_color) {
preg_match('/^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i', $hex_color, $m);
return array_combine(
['red', 'green', 'blue'],
array_map(fn($color) => hexdec($color), array_slice($m, -3))
);
}
$half_width = $size * $scale / 2;
$total = array_sum(array_map(fn ($x) => $x['value'], $datas));
$out_radius = ($size * $scale / 3) * 2;
$image = imagecreatetruecolor($size, $size);
$graph = imagecreatetruecolor($size * $scale, $size * $scale);
imagefill($image, 0, 0, imagecolorallocate($image, ...hex2rgb('#ffffff')));
imagefill($graph, 0, 0, imagecolorallocate($graph, ...hex2rgb('#ffffff')));
$prev_angle = -90;
foreach ($datas as $data) {
// 色
$color = imagecolorallocate($graph, ...hex2rgb($data['color']));
// 角度
$angle = ceil($data['value'] / $total * 360);
imagefilledarc(
$graph,
$half_width,
$half_width,
$size * $scale,
$size * $scale,
$prev_angle,
$prev_angle + $angle,
$color,
IMG_ARC_PIE
);
$prev_angle += $angle;
}
// くり抜き
imagefilledarc(
$graph,
$half_width,
$half_width,
(int)$out_radius,
(int)$out_radius,
0,
360,
imagecolorallocate($image, ...hex2rgb('#ffffff')),
IMG_ARC_PIE
);
// リサンプリング
imagecopyresampled($image, $graph, 0, 0, 0, 0, $size, $size, $size * $scale, $size * $scale);
header('Content-type: image/png');
imagepng($image);
imagedestroy($image);
ポイント
色は分かりやすくするため、用意するデータは16進数のRGBで指定しています。
そこから内部的に利用できるように、10進数へ変換する関数hex2rgbを用意しています。
通常GDで円を描くとジャギーが発生するため、グラフ用と書き出し用の二つのイメージを用意し、グラフを数倍の大きさで用意。imagecopyresampledでリサンプリングして標準の大きさにリサイズします。
GDの多くの関数はfloat型には対応していないので、float型になってしまう部分はint型に変換しています。
