Last active
July 12, 2022 17:54
-
-
Save narensulegai/7dbd0173c76dd1f6d03b0541f305cc27 to your computer and use it in GitHub Desktop.
Zoomable line chart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
* { | |
box-sizing: border-box; | |
} | |
body { | |
padding: 0; | |
margin: 0; | |
} | |
#chart{ | |
display: flex; | |
justify-content: center; | |
margin-top: 50px; | |
} | |
svg { | |
} | |
.xaxis .tick text { | |
fill: grey; | |
} | |
.xaxis .domain, .xaxis .tick line { | |
stroke: gray; | |
} | |
.xaxis .tick line { | |
stroke-width: 0.2; | |
} | |
text { | |
font-family: "Metropolis", sans-serif; | |
font-size: 16px; | |
} | |
.lollipop { | |
cursor: pointer; | |
} | |
.lollipop circle { | |
fill: darkturquoise; | |
stroke-width: 2; | |
} | |
.lollipop line { | |
stroke-width: 2; | |
} | |
.line-chart { | |
stroke-width: 2; | |
stroke: steelblue; | |
fill: none; | |
} | |
.selection { | |
stroke: transparent; | |
} | |
</style> | |
<div id="chart"></div> | |
<script> | |
const data = [ | |
{"date": "2012-04-15T00:00:00.000Z", "value": 0}, | |
{"date": "2012-04-16T00:00:00.000Z", "value": 1}, | |
{"date": "2012-04-17T00:00:00.000Z", "value": 2}, | |
{"date": "2012-04-18T00:00:00.000Z", "value": 3}, | |
{"date": "2012-04-19T00:00:00.000Z", "value": 4}, | |
{"date": "2012-04-22T00:00:00.000Z", "value": 100}, | |
{"date": "2012-04-23T00:00:00.000Z", "value": 10}, | |
{"date": "2012-04-24T00:00:00.000Z", "value": 12}, | |
{"date": "2012-04-25T00:00:00.000Z", "value": 20}, | |
{"date": "2012-04-26T00:00:00.000Z", "value": 120}, | |
{"date": "2012-04-29T00:00:00.000Z", "value": 110}, | |
{"date": "2012-05-01T00:00:00.000Z", "value": 0} | |
]; | |
</script> | |
<script> | |
const getDateMonth = (date) => { | |
const [weekday, month, day, year] = date.toDateString().split(' '); | |
return {day, month} | |
} | |
const height = 400; | |
const width = 800; | |
const margin = {top: 20, right: 20, bottom: 20, left: 20}; | |
const x = d3.scaleLinear() | |
.domain(d3.extent(data, d => new Date(d.date))) | |
.range([0, width]) | |
const y = d3.scaleLinear() | |
.domain([0, d3.max(data, d => d.value)]) | |
.range([height/2 - margin.top - margin.bottom, 0]) | |
const svg = d3.create("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
const container = svg.append('g') | |
.attr("transform", `translate(${margin.left},0)`) | |
const line = (x, y) => { | |
return d3.line() | |
.x(d => x(new Date(d.date))) | |
.y(d => y(d.value)) | |
} | |
document.getElementById('chart').append(svg.node()); | |
//X Axis | |
container.append("g") | |
.call(d3.axisBottom(x) | |
.ticks(5) | |
.tickSize(20) | |
.tickPadding(-10) | |
.tickSizeOuter(0) | |
.tickFormat(d => { | |
const {day, month} = getDateMonth(new Date(d)) | |
return `${day}. ${month}` | |
})) | |
.attr('class', 'xaxis') | |
.attr("transform", `translate(0,${height - margin.bottom})`) | |
.attr('text-anchor', 'right') | |
const topXAxis = container.append("g") | |
.attr("transform", `translate(0,${margin.top})`) | |
container.selectAll('.xaxis .tick') | |
.call(ele => { | |
ele.select('text') | |
.attr('x', 2) | |
}) | |
const lineChart = container.append("g") | |
.attr("transform", `translate(0,${height/2 + margin.top})`) | |
.append("path") | |
.attr("class", "line-chart") | |
.datum(data) | |
container.append('text') | |
.attr('y', 40) | |
.text('Events') | |
container.append('text') | |
.attr('y', 200) | |
.text('Performance') | |
const lolli = container.append('g') | |
.attr("transform", `translate(0,${110 + margin.top})`); | |
// lollipop base | |
container.append('g') | |
.attr("transform", `translate(0,${150 + margin.top + 10})`) | |
.append("line") | |
.attr("stroke", "grey") | |
.attr("x1", 0) | |
.attr("x2", width) | |
.attr("y1", 0) | |
.attr("y2", 0) | |
const lollipop = lolli.selectAll("g") | |
.data(data) | |
.enter() | |
.append("g") | |
.attr('class', 'lollipop') | |
.on('mouseover', function () { | |
const ele = d3.select(this) | |
ele.select('circle') | |
.attr('r', 10) | |
.attr("cy", -10) | |
.attr("stroke", "red") | |
ele.select('line') | |
.attr('y1', -10) | |
.attr("stroke", "red") | |
ele.select('image') | |
.attr("y", -20) | |
.attr("x", d => x(new Date(d.date)) - 10) | |
.attr("width", 20) | |
.attr("height", 20) | |
}) | |
.on('mouseout', function () { | |
const ele = d3.select(this) | |
ele.select('circle') | |
.attr('r', 5) | |
.attr("cy", 0) | |
.attr("stroke", "grey") | |
ele.select('line') | |
.attr('y1', 0) | |
.attr("stroke", "grey") | |
ele.select('image') | |
.attr("x", d => x(new Date(d.date)) - 5) | |
.attr("y", -5) | |
.attr("width", 10) | |
.attr("height", 10) | |
}); | |
const lollipopBase = lollipop | |
.append("line") | |
.attr("y1", 0) | |
.attr("y2", 50) | |
.attr("stroke", "grey") | |
const lollipopCircle = lollipop | |
.append("circle") | |
.attr("r", 5) | |
.attr("stroke", "grey") | |
const lollipopIcon = lollipop | |
.append("image") | |
.attr("xlink:href", "https://d29fhpw069ctt2.cloudfront.net/icon/image/38033/preview.svg") | |
.attr("width", 10) | |
.attr("height", 10) | |
const xOrg = x.copy(); | |
const defaultSelection = x.range(); | |
const brush = d3.brushX() | |
.extent([[0, 201], [width, 230]]) | |
.on("brush", brushed) | |
.on("end", brushEnded) | |
const brushContainer = container.append("g") | |
.attr("transform", `translate(0,${height - 220})`) | |
.call(brush) | |
.call(brush.move, defaultSelection); | |
function brushed() { | |
const [start, end] = d3.event.selection; | |
x.domain([xOrg.invert(start), xOrg.invert(end)]) | |
lineChart | |
.attr("d", line(x, y)); | |
topXAxis | |
.call(d3.axisTop(x) | |
.ticks(6) | |
.tickSize(0) | |
.tickFormat(d => { | |
const {day, month} = getDateMonth(new Date(d)) | |
return `${month} ${day}` | |
})); | |
lollipopCircle | |
.attr("cx", d => x(new Date(d.date))) | |
.attr("cy", 0) | |
lollipopIcon | |
.attr("x", d => x(new Date(d.date)) - 5) | |
.attr("y", -5) | |
lollipopBase | |
.attr("x1", d => x(new Date(d.date))) | |
.attr("x2", d => x(new Date(d.date))) | |
} | |
function brushEnded() { | |
if (d3.event.selection == null) { | |
brushContainer.call(brush.move, defaultSelection); | |
} | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment