Skip to content

Instantly share code, notes, and snippets.

@da1fujimoto
Last active December 12, 2019 01:34
Show Gist options
  • Save da1fujimoto/2dae0d6abe3711de022920bffc54543a to your computer and use it in GitHub Desktop.
Save da1fujimoto/2dae0d6abe3711de022920bffc54543a to your computer and use it in GitHub Desktop.
D3 Line Chart Demo v5
license: mit
[[{"date":"2019/1/1","value":70},{"date":"2019/2/1","value":65.1},{"date":"2019/3/1","value":81.6},{"date":"2019/4/1","value":55.3},{"date":"2019/5/1","value":30.6},{"date":"2019/6/1","value":55.4},{"date":"2019/7/1","value":72.9},{"date":"2019/8/1","value":82.2},{"date":"2019/9/1","value":66.4},{"date":"2019/10/1","value":73.3},{"date":"2019/11/1","value":25.4},{"date":"2019/12/1","value":57.4}],[{"date":"2019/1/1","value":55.8},{"date":"2019/1/15","value":80},{"date":"2019/2/1","value":33.4},{"date":"2019/2/15","value":65.3},{"date":"2019/3/1","value":45.2},{"date":"2019/3/15","value":70.9},{"date":"2019/4/1","value":82.1},{"date":"2019/4/15","value":66.6},{"date":"2019/5/1","value":45.3},{"date":"2019/5/15","value":33.2},{"date":"2019/6/1","value":90.2},{"date":"2019/6/15","value":34.5},{"date":"2019/7/1","value":45.3},{"date":"2019/7/15","value":33.2},{"date":"2019/8/1","value":90.2},{"date":"2019/8/15","value":34.5}]]
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="description" content="">
<title></title>
<!-- Custom CSS -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<div>
<select name="" id="select-data" class="form-control">
<option value="0">Data 1</option>
<option value="1">Data 2</option>
</select>
</div>
<div class="canvas"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://unpkg.com/d3-interpolate-path/build/d3-interpolate-path.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js"></script>
<script src="main.js"></script>
</body>
const svg = d3.select('.canvas').append('svg')
.attr('width', 800)
.attr('height', 450);
const margin = {top: 50, right: 30, bottom: 50, left: 30};
const width = svg.attr('width') - margin.left - margin.right;
const height = svg.attr('height') - margin.top - margin.bottom;
const g = svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
const timeparser = d3.timeParse('%Y/%m/%d');
const format = d3.timeFormat('%Y/%m');
const selector = document.querySelector('#select-data');
const color = d3.color('#85a7cc');
const bisect = d3.bisector(d => d.date);
// data read
d3.json('dataset.json').then(data => {
// event listener
selector.addEventListener('change', () => {
update(data[selector.value]);
});
create(data[selector.value]);
});
// scaler
const x = d3.scaleTime().range([0, width]);
const y = d3.scaleLinear().range([height, 0]);
// line path generator
const line = d3.line()
.x(d => x(d.date))
.y(d => y(d.value))
.curve(d3.curveCatmullRom.alpha(0.4));
const path = g.append('path')
.attr('fill', 'none')
.attr('stroke', color)
.attr('stroke-width', 2);
// line area
const lineArea = g.append('path');
const linearGradient = g.append('defs')
.append('linearGradient')
.attr('id', 'linear-gradient')
.attr('gradientTransform', 'rotate(90)');
linearGradient.append('stop')
.attr('offset', '0%')
.attr('stop-color', color.brighter(1.5));
linearGradient.append('stop')
.attr('offset', '100%')
.attr('stop-color', '#fff');
const area = d3.area()
.x(d => x(d.date))
.y0(y(0))
.y1(d => y(d.value))
.curve(d3.curveCatmullRom.alpha(0.4));
// axis settings
const xAxisCall = d3.axisBottom(x)
.tickFormat(format);
const yAxisCall = d3.axisLeft(y);
const xAxis = g.append('g')
.attr('class', 'axis axis-x')
.attr('transform', `translate(0, ${height})`);
const yAxis = g.append('g')
.attr('class', 'axis axis-y')
.call(yAxisCall);
// tooltip
const tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(d => `<p>${format(d.date)}</p><p>${d.value}%</p>`)
g.call(tip);
const dashline = g.append('line');
const dashline2 = g.append('line');
const circle = g.append('circle')
.attr('class', 'point-circle');
const showTip = (data, i) => {
circle.attr('cx', x(data[i].date))
.attr('cy', y(data[i].value))
.attr('r', 4)
.attr('fill', '#fff')
.attr('stroke', color)
.attr('stroke-width', 2)
.style('display', 'block')
dashline.attr('x1', x(data[i].date))
.attr('x2', x(data[i].date))
.attr('y1', 0)
.attr('y2', height)
.attr('stroke', '#ccc')
.attr('stroke-width', '1px')
.attr('stroke-dasharray', '2')
.style('display', 'block');
dashline2.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(data[i].value))
.attr('y2', y(data[i].value))
.attr('stroke', '#ccc')
.attr('stroke-width', '1px')
.attr('stroke-dasharray', '2')
.style('display', 'block');
tip.show(data[i], circle.node());
}
// create graph
const create = data => {
data = data.map(d => {
return {date: timeparser(d.date), value: d.value}
});
x.domain([d3.min(data, d => d.date), d3.max(data, d => d.date)]);
y.domain([0, d3.max(data, d => d.value)]);
xAxis.call(xAxisCall);
yAxis.call(yAxisCall);
path.data([data]).attr('d', line);
lineArea.data([data]).attr('d', area).style("fill", "url(#linear-gradient)");
g.on('mousemove', () => {
const xcord = x.invert(d3.mouse(g.node())[0]);
showTip(data, bisect.left(data, xcord));
});
g.on('mouseleave', () => {
circle.style('display', 'none');
dashline.style('display', 'none');
dashline2.style('display', 'none');
tip.hide();
});
};
// updage graph
const update = data => {
data = data.map(d => {
return {date: timeparser(d.date), value: d.value}
});
x.domain([d3.min(data, d => d.date), d3.max(data, d => d.date)]);
y.domain([0, d3.max(data, d => d.value)]);
xAxis.transition().duration(1000)
.ease(d3.easeCircleInOut)
.call(xAxisCall);
yAxis.transition().duration(1000)
.ease(d3.easeCircleInOut)
.call(yAxisCall);
const dBeforeLine = path.attr('d');
const dBeforeArea = lineArea.attr('d');
path.data([data])
.transition().duration(1000)
.ease(d3.easeCircleInOut)
.attrTween('d', (d, i, n) => {
const previous = d3.select(n[i]).attr('d');
const current = line(d);
return d3.interpolatePath(previous, current);
});
lineArea.data([data])
.transition().duration(1000)
.ease(d3.easeCircleInOut)
.style("fill", "url(#linear-gradient)")
.attrTween('d', (d, i, n) => {
const previous = d3.select(n[i]).attr('d');
const current = area(d);
return d3.interpolatePath(previous, current);
});
g.on('mousemove', () => {
const xcord = x.invert(d3.mouse(g.node())[0]);
showTip(data, bisect.left(data, xcord));
});
g.on('mouseleave', () => {
circle.style('display', 'none');
dashline.style('display', 'none');
dashline2.style('display', 'none');
tip.hide();
});
};
svg {
margin-left: auto;
margin-right: auto;
display: block;
}
.chart-tip {
position: absolute;
width: auto;
height: auto;
padding: 10px 15px;
border-radius: 4px;
background: rgba(255,255,255,.95);
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
visibility: hidden;
font-size: 12px;
font-weight: bold;
max-width: 160px;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 10px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
font-size: 10px;
}
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment