Instantly share code, notes, and snippets.

@yuki-matsushita /data.json Secret
Last active Jun 11, 2018

Embed
What would you like to do?
D3.js Line Chart with Tooltip
[
{"day" : "2018/5/10", "value": 77190},
{"day" : "2018/5/11", "value": 77150},
{"day" : "2018/5/12", "value": 76650},
{"day" : "2018/5/13", "value": 77040},
{"day" : "2018/5/14", "value": 77100},
{"day" : "2018/5/15", "value": 76940},
{"day" : "2018/5/16", "value": 77410},
{"day" : "2018/5/17", "value": 77670},
{"day" : "2018/5/18", "value": 77710},
{"day" : "2018/5/19", "value": 77920},
{"day" : "2018/5/20", "value": 78230},
{"day" : "2018/5/21", "value": 77410}
]
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="style.css" media="all" />
</head>
<body>
<div id="contents">
<div id="chartArea">
<svg width="960" height="500" id="chart"></svg>
</div>
</div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="line_chart.js"></script>
</body>
</html>
// SVG要素の選択とサイズの取得
var svg = d3.select("svg"),
margin = {top: 60, right: 30, bottom: 40, left: 40},
width = svg.attr("width"),
height = svg.attr("height"),
cWidth = width - margin.left - margin.right,
cHeight = height - margin.top - margin.bottom;
var formatTime = d3.timeFormat("%m/%d");
var formatClass = d3.timeFormat("date-%m%d");
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// マウスカーソルからのフォーカスの位置推定
var parseTime = d3.timeParse("%Y/%m/%e");
var bisectDate = d3.bisector(function(d) { return d.day; }).left;
var x = d3.scaleTime().range([0, cWidth]);
var y = d3.scaleLinear().range([cHeight, 0]);
var line = d3.line()
.x(function(d) { return x(d.day); })
.y(function(d) { return y(d.value); });
d3.json("data.json").then(
function(data) {
data.forEach(function(d) {
d.day = parseTime(d.day);
d.value = +d.value;
});
x.domain(d3.extent(data, function(d) { return d.day; }));
y.domain([d3.min(data, function(d) { return d.value; }) / 1.005, d3.max(data, function(d) { return d.value; }) * 1.005]);
// 横の目盛り
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + cHeight + ")")
.call(
d3.axisBottom(x)
.ticks(12)
.tickFormat(d3.timeFormat("%Y/%m/%d"))
);
// 横の目盛りを日付のみにして土日にクラスを付与する
var ticks = d3.selectAll(".axis--x text");
ticks.attr("class", function(d){
if(d3.timeFormat("%a")(d) == "Sat") return "sat";
if(d3.timeFormat("%a")(d) == "Sun") return "sun";
return "weekday";
}).html(function(d) {return formatTime(d);});
// 縦の目盛り
g.append("g")
.attr("class", "axis axis--y")
.call(
d3.axisLeft(y)
.ticks(6)
.tickSizeInner(-cWidth)
.tickFormat(function(d) { return d/1000 + "k"; }))
.append("text")
.attr("class", "axis-title")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.attr("fill", "#5D6971")
.text("アクセス量");
g.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var focus = g.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("line")
.attr("class", "x-hover-line hover-line")
.attr("y1", 0)
.attr("y2", cHeight);
var circle = focus.append("circle")
.attr("r", 7.5);
svg.append("rect")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "overlay")
.attr("width", cWidth)
.attr("height", cHeight)
.on("mouseover", function() {
tooltip.style("display", "block");
focus.style("display", null); })
.on("mouseout", function() {
tooltip.style("display", "none");
focus.style("display", "none");
})
.on("mousemove", mousemove);
// Tool tips と Focus
var tooltip = d3.select("#contents").append("div").attr("class", "tooltip"),
tt_date = tooltip.append("time").attr("class", "tt_date"),
tt_value = tooltip.append("div").attr("class", "tt_value");
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.day > d1.day - x0 ? d1 : d0;
tt_date.html(function() {return formatTime(d.day);});
tt_value.html(function() {return "アクセス量:"+d.value});
// マウスの位置によりtooltipを表示位置を変更(右側 or 左側)
var centerX = cWidth / 2;
var tooltipPosX = 5,
tooltipPosY = -15;
if(d3.mouse(this)[0] > centerX) {
// tooltipの大きさ分、左側にx座標をずらす
tooltipPosX = -tooltip.node().getBoundingClientRect().width;
}
tooltip.transition()
.duration(200)
.ease(d3.easeLinear)
.style("left", (d3.event.pageX + tooltipPosX) + "px")
.style("top", (d3.event.pageY - tooltipPosY) + "px");
focus.attr("transform", "translate(" + x(d.day) + "," + 0 + ")");
focus.select(".x-hover-line").attr("y2", cHeight);
circle.attr("transform", "translate(" + 0 + "," + y(d.value) + ")");
}
});
@charset "UTF-8";
/* CSS Document */
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #D4D8DA;
stroke-width: 2px;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: #1490d8;
stroke-width: 5px;
}
.overlay {
fill: none;
pointer-events: all;
}
.hover-line {
stroke: #ccc;
stroke-width: 1px;
stroke-dasharray: 2;
}
#chartArea {
background-color: #fff;
}
.tick line{
opacity: 0.4;
}
.tooltip {
position: absolute;
padding: 5px 12px 10px;
background: #fff;
-webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.4);
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.4);
border-radius: 5px;
color : #666;
display: none;
pointer-events: none;
opacity: 0.9;
}
.tt_date{
font-weight: bold;
font-size: 8px;
}
.tt_value{
font-size: 12px;
}
.sat{
fill:#1874CD;
}
.sun{
fill:#f2594b;
}
.bar.focus{
fill:#c1d5e6;
}
.focus circle {
fill: #F1F3F3;
stroke: #1490d8;
stroke-width: 5px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment