<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v5.js"></script>
<script src="./js/grid.js"></script>
</head>
<body>
<div id="grid"></div>
<span id="data" style="display:none;">
[
[
{"x":1,"y":1,"width":100,"height":100,"selected":false},
{"x":101,"y":1,"width":250,"height":50,"selected":false}
],
[
{"x":101,"y":51,"width":100,"height":50,"selected":false},
{"x":201,"y":51,"width":150,"height":50,"selected":false}
],
[
{"x":1,"y":101,"width":50,"height":300,"selected":false},
{"x":51,"y":101,"width":50,"height":150,"selected":false},
{"x":101,"y":101,"width":100,"height":150,"selected":false},
{"x":201,"y":101,"width":150,"height":150,"selected":false}
],
[
{"x":51,"y":251,"width":50,"height":150,"selected":false},
{"x":101,"y":251,"width":100,"height":150,"selected":false},
{"x":201,"y":251,"width":150,"height":150,"selected":false}
]
]
</span>
</body>
</html>
document.addEventListener("DOMContentLoaded", function () {
// html内のJson文字列をデシリアイズします。
const gridData = JSON.parse(document.querySelector("#data").textContent);
const grid = d3
.select("#grid")
.append("svg")
.attr("width", "510px")
.attr("height", "510px");
const row = grid
.selectAll()
.data(gridData)
.enter()
.append("g")
.attr("class", "row");
const noSelectedColor = "#ffffff";
const selectedColor = "#2C93E8";
const column = row
.selectAll()
.data(d) { return d; })
.enter()
.append("rect")
.attr("class", "square")
.attr("x", function (d) { return d.x; })
.attr("y", function (d) { return d.y; })
.attr("width", function (d) { return d.width; })
.attr("height", function (d) { return d.height; })
.style("fill", noSelectedColor)
.style("stroke", "#222")
.on('click', function (d) {
d.selected = !d.selected;
let cell = d3.select(this);
if (d.selected) {
cell.style("fill", selectedColor);
} else {
cell.style("fill", noSelectedColor);
}
});
});
解説
上から順番に解説します。
const grid = d3
.select("#grid")
.append("svg")
.attr("width", "510px")
.attr("height", "510px");
<!-- 実行後 -->
<div id="grid">
<svg width="510px" height="510px">
</svg></div>
selectメソッドのセレクターはjsやcssなどと同じです。
取得した d3.selection オブジェクトに append メソッドで svg タグが挿入されます。append をすることでタグが実際に生成されます。append の戻り値は d3.selection オブジェクトです。
attr メソッドで属性を追加します。
const row = grid
.selectAll()
.data(gridData)
.enter()
.append("g")
.attr("class", "row");
<div id="grid">
<svg width="510px" height="510px">
<g class="row"></g>
<g class="row"></g>
<g class="row"></g>
<g class="row"></g>
</svg>
</div>
data メソッドでグリッドのオブジェクトを割り当てます。サンプルでは Json 文字列からオブジェクトを生成しています。
enter メソッドで data メソッドで割り当てられたオブジェクトがd3.selectionオブジェクトにセットされます。
append("g")でオブジェクトの各配列要素に g 要素 をバインドし、タグを生成します。
(よくあるサンプルだと始めの selectAll で selectAll("row") などして、空の selection オブジェクトを取得するのを見かけます。ですが、引数は空でいいと思うのですが、わざわざ"row"などして空振りする効果がよくわかりません。)
d3.jsのセレクタとタグ操作
D3.jsで<g>要素を活用する
const noSelectedColor = "#ffffff";
const selectedColor = "#2C93E8";
const gridStrokeColor = "#222222";
const column = row
.selectAll()
.data(d => d)
.enter()
.append("rect")
.attr("class", "square")
.attr("x", d => d.x)
.attr("y", d => d.y)
.attr("width", d => d.width)
.attr("height", d => d.height)
.style("fill", noSelectedColor)
.style("stroke", gridStrokeColor)
.on('click', function (d) {
d.selected = !d.selected;
let cell = d3.select(this);
if (d.selected) {
cell.style("fill", selectedColor);
} else {
cell.style("fill", noSelectedColor);
}
});
<div id="grid">
<svg width="510px" height="510px">
<g class="row">
<rect class="square" x="1" y="1" width="100" height="100" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
<rect class="square" x="101" y="1" width="250" height="50" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
</g>
<g class="row">
<rect class="square" x="101" y="51" width="100" height="50" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
<rect class="square" x="201" y="51" width="150" height="50" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
</g>
<g class="row">
<rect class="square" x="1" y="101" width="50" height="300" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
<rect class="square" x="51" y="101" width="50" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
<rect class="square" x="101" y="101" width="100" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
<rect class="square" x="201" y="101" width="150" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
</g>
<g class="row">
<rect class="square" x="51" y="251" width="50" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
<rect class="square" x="101" y="251" width="100" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
<rect class="square" x="201" y="251" width="150" height="150" style="fill: rgb(255, 255, 255); stroke: rgb(34, 34, 34);">
</rect>
</g>
</svg>
</div>
ここでは、先程 g 要素にバインドした配列の行オブジェクトの要素から rect を生成します。x や y、width、height、fill、stroke は SVG の属性です。
jQuery のように、on メソッドでイベントを付与できます。ここでは、クリックすると背景色を切り替えるイベントを付与しています。
(ただ、ここでもう一度 data(d => d).enter() しないといけないのがよくわからない。)