Skip to content

Instantly share code, notes, and snippets.

@narensulegai
Last active July 12, 2022 17:54
Show Gist options
  • Save narensulegai/7dbd0173c76dd1f6d03b0541f305cc27 to your computer and use it in GitHub Desktop.
Save narensulegai/7dbd0173c76dd1f6d03b0541f305cc27 to your computer and use it in GitHub Desktop.
Zoomable line chart
license: gpl-3.0
<!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