D3.js 범위 선택(d3.brush) 사용법
예제 프로그램
예제 프로그램에 표시된 회색 사각형을 드래그하거나 그래프의 아무것도 없는 곳을 클릭하고 드래그하여 범위를 선택할 수 있다. 회색 사각형 모서리를 드래그하여 범위의 크기를 변경할 수 있다.
예제 코드
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 Brush</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<!-- 1. 선택 시 스타일 설정 -->
<style>
.selected {
fill: red;
stroke: brown;
}
</style>
<script>
// 2. 산점도 표시
var width = 800; // 그래프 넓이
var height = 600; // 그래프 높이
var margin = { "top": 30, "bottom": 30, "right": 30, "left": 30 };
var randomX = d3.randomUniform(0.5, 10);
var randomY = d3.randomNormal(0.5, 0.12);
var data = d3.range(500).map(function () { return [randomX(), randomY()]; });
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xScale = d3.scaleLinear()
.domain([0, 10])
.range([0, width - margin.right - margin.left]);
var yScale = d3.scaleLinear()
.domain([0, 1])
.range([height - margin.bottom - margin.top, 0]);
var dot = g.append("g")
.attr("fill-opacity", 0.2)
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return xScale(d[0]) })
.attr("cy", function (d) { return yScale(d[1]) })
.attr("r", 5);
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + (height - margin.bottom) + ")")
.call(d3.axisBottom(xScale));
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(d3.axisLeft(yScale));
// 3. brush 설정
var brush = d3.brush()
.extent([
[0, 0],
[width - margin.left - margin.right, height - margin.top - margin.bottom]
])
.on("start brush", brushed);
g.append("g")
.call(brush)
.call(brush.move, [
[xScale(2), yScale(0.8)],
[xScale(5), yScale(0.3)]
]);
function brushed(event) {
var x0 = xScale.invert(event.selection[0][0]);
var y1 = yScale.invert(event.selection[0][1]);
var x1 = xScale.invert(event.selection[1][0]);
var y0 = yScale.invert(event.selection[1][1]);
dot.classed("selected",
function (d) {
return (x0 <= d[0] && d[0] <= x1) && (y0 <= d[1] && d[1] <= y1);
}
);
}
</script>
</body>
</html>
코드 설명
1. 선택시 스타일 설정
선택되었을 때, 플롯에 설정할 스타일을 정의한다.
<style>
.selected {
fill: red;
stroke: brown;
}
</style>
2. 산포도 표시
산포도를 만드는 방법에 대한 자세한 내용은 여기를 참조한다. 관련 부분을 설명한다.
산포도의 원본 데이터를 만든다.
var randomX = d3.randomUniform(0.5, 10);
var randomY = d3.randomNormal(0.5, 0.12);
var data = d3.range(500).map(function() { return [randomX(), randomY()]; });
d3.randomUniform
은 인수에 설정한 범위에서 난수를 발생시키는 함수를 설정하는 것이고, d3.randomNormal
은 평균값과 표준편차를 인수로 하여 가우스 분포에 따른 난수를 발생시키는 함수를 설정하는 것이다. 이 함수들과 d3.range(500).map()
를 사용하여 500점의 2차원 배열 데이터를 작성한다.
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
body
요소 내에 SVG 요소를 설정하고 그 안에 "g"
요소를 설정한다. 플롯 표시 및 범위 선택을 위한 브러시를 "g"
요소 내에 설정한다.
3. brush 설정
범위 선택을 위한 brush를 설정하는 프로토타입을 설정하고 call
메소드로 호출하여 brush를 설정한다.
var brush = d3.brush()
.extent([
[0, 0],
[width - margin.left - margin.right, height - margin.top - margin.bottom]
])
.on("start brush", brushed);
g.append("g")
.call(brush)
.call(brush.move, [
[xScale(2), yScale(0.8)],
[xScale(5), yScale(0.3)]
]);
call
메소드에서 brush
를 설정하면 다음 SVG 요소가 선택한 요소 내(이번에는 "g"
요소)로 설정된다.
<g class="brush" fill="none" pointer-events="all" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">
<rect class="overlay" pointer-events="all" cursor="crosshair" x="0" y="0" width="960" height="500"></rect>
<rect class="selection" cursor="move" fill="#777" fill-opacity="0.3" stroke="#fff" shape-rendering="crispEdges" x="112" y="194" width="182" height="83"></rect>
<rect class="handle handle--n" cursor="ns-resize" x="107" y="189" width="192" height="10"></rect>
<rect class="handle handle--e" cursor="ew-resize" x="289" y="189" width="10" height="93"></rect>
<rect class="handle handle--s" cursor="ns-resize" x="107" y="272" width="192" height="10"></rect>
<rect class="handle handle--w" cursor="ew-resize" x="107" y="189" width="10" height="93"></rect>
<rect class="handle handle--nw" cursor="nwse-resize" x="107" y="189" width="10" height="10"></rect>
<rect class="handle handle--ne" cursor="nesw-resize" x="289" y="189" width="10" height="10"></rect>
<rect class="handle handle--se" cursor="nwse-resize" x="289" y="272" width="10" height="10"></rect>
<rect class="handle handle--sw" cursor="nesw-resize" x="107" y="272" width="10" height="10"></rect>
</g>
또한 d3.brush
에는 다음 설정이 가능하다.
설정 | 설명 |
---|---|
d3.brush.move() |
브러시 선택 영역을 이동한다.[[x0, y0], [x1, y1]] 의 2차원 배열로 설정한. |
d3.brush.extent() |
브러시의 이동 가능 범위를 설정한다.[[x0, y0], [x1, y1]] 의 2차원 배열로 설정한다. |
d3.brush.handleSize() |
브러시의 핸들 크기를 설정한다. 기본값은 6이다. |
d3.brush.on(typenames , function) |
이벤트 시 호출할 함수를 설정한다. typenames 는 다음의 3종류로부터 설정한다. start - 선택 개시시. brush - 범위 선택의 변경시. end - 선택 종료시. |
이번에는 on
메소드를 사용하여 start
, brush시에 호출하는 이벤트 리스너(brushed
)를 설정한다.
.on("start brush", brushed);
이벤트 리스너(이벤트 시에 호출되는 함수)는 이벤트 리스너가 불려 갔을 때에 설정되는 event
의 필드를 사용해 설정한다.
이벤트 필드 | 설명 |
---|---|
event.target |
관련 brush 비헤이비어에 대한 참조 |
event.type |
현재 이벤트. "start" , "brush" , "end" 중 어느 것? |
event.selection |
현재 brush의 선택 범위. [[x0, y0], [x1, y1]] 의 2차원 배열. |
event.sourceEvent |
mousemove , touchmove 등 brush 이벤트의 근원이 되는 이벤트 종류 |
필드 선택을 사용하여 선택한 범위 내의 산포도 플롯에 스타일을 설정한다. .invert
는 산포도상의 좌표를 화면상의 좌표로 변환하는 함수 xScale
, yScale
을 역변환하여 화면상의 좌표에서 산포도상의 좌표로 변환하는 함수로 변경하는 메소드이다.
function brushed(event) {
var x0 = xScale.invert(event.selection[0][0]);
var y1 = yScale.invert(event.selection[0][1]);
var x1 = xScale.invert(event.selection[1][0]);
var y0 = yScale.invert(event.selection[1][1]);
dot.classed("selected",
function (d) {
return (x0 <= d[0] && d[0] <= x1) && (y0 <= d[1] && d[1] <= y1);
}
);
}
classed
메서드를 사용하여 두 번째 인수의 조건식이 참인 경우 "selected"
클래스를 설정하고 거짓인 경우 제외한다.
마무리
터치 패널의 범위 선택이 여기서 소개한 벙법으로는 어렵고, D3.js 범위 선택(d3.brush) 사용법 - 터치 패널 대응 이 페이지를 참고하길 바란다.