Skip to content

Instantly share code, notes, and snippets.

@matsrorbecker
Last active June 29, 2019 05:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matsrorbecker/70fef5a848b2c9f61b59ce0840c8c95c to your computer and use it in GitHub Desktop.
Save matsrorbecker/70fef5a848b2c9f61b59ce0840c8c95c to your computer and use it in GitHub Desktop.
Temperature in Oskarshamn
license: mit
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://polyfill.io/v3/polyfill.min.js?features=Promise%2Ces6"></script>
<script src="https://d3js.org/d3.v5.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<script>
const width = window.innerWidth
const height = Math.min(window.innerHeight, width * 0.5)
const margin = {top: 20, right: 20, bottom: 60, left: 30}
const svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
const renderChart = (data) => {
const [min, max] = d3.extent(data, d => d.temperature)
const absMax = d3.max([Math.abs(min), Math.abs(max)])
// scales
const yScale = d3.scaleLinear()
.domain([-absMax, absMax])
.range([height - margin.bottom, margin.top])
.nice()
const times = data.map(d => new Date(`${d.date}T${d.time}`))
const xScale = d3.scaleTime()
.domain(d3.extent(times))
.range([margin.left, width - margin.right])
const xDomain = xScale.domain()
// axes
const yAxis = d3.axisLeft()
.scale(yScale)
const xAxis = d3.axisBottom()
.scale(xScale)
.tickFormat(d3.timeFormat('%Y-%m-%d'))
svg.append('g')
.attr('class', 'y-axis')
.attr('transform', `translate(${margin.left}, 0)`)
.call(yAxis)
svg.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0, ${height - margin.bottom})`)
.call(xAxis)
// clip path
svg.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('x', margin.left)
.attr('y', margin.right)
.attr('width', width - margin.left - margin.right)
.attr('height', height - margin.top - margin.bottom)
// line
const line = d3.line()
.x(d => xScale(new Date(`${d.date}T${d.time}`)))
.y(d => yScale(d.temperature))
svg.append('path')
.attr('class', 'line')
.attr('clip-path', 'url(#clip)')
.datum(data)
.attr('fill', 'none')
.attr('stroke', '#000')
.attr('stroke-width', 1)
.attr('d', line)
// hidden line in foreground
svg.append('path')
.attr('class', 'hidden-line')
.attr('clip-path', 'url(#clip)')
.datum(data)
.attr('fill', 'none')
.attr('stroke', '#000')
.attr('stroke-width', 10)
.attr('opacity', 0)
.attr('d', line)
// vertical 'cursor'
svg.append('g')
.attr('class', 'cursor')
.style('display', 'none')
.append('line')
.attr('stroke', '#000')
.attr('opacity', 0.4)
.attr('stroke-width', 1)
.attr('y1', margin.top)
.attr('y2', height - margin.bottom)
// 'display'
let {date, time, temperature} = data[0]
svg.append('text')
.attr('class', 'display')
.attr('x', width/2)
.attr('y', height)
.attr('dy', '-1em')
.attr('text-anchor', 'middle')
.attr('font-family', 'sans-serif')
.text(`${date} kl ${time}: ${temperature > 0 ? '+' : ''}${temperature}°C`)
// zoom stuff
let idleTimeout
const idled = () => idleTimeout = null
const updateChart = () => {
const selection = d3.event.selection
if (!selection) {
if (!idleTimeout) {
return idleTimeout = setTimeout(idled, 350)
}
xScale.domain(xDomain)
} else {
xScale.domain([xScale.invert(selection[0]), xScale.invert(selection[1])])
svg.select('.brush').call(brush.move, null)
}
svg.select('.x-axis')
.transition()
.duration(750)
.call(xAxis)
svg.select('.line')
.transition()
.duration(750)
.attr('d', line)
svg.select('.hidden-line')
.transition()
.duration(750)
.attr('d', line)
}
const brush = d3.brushX()
.extent([[margin.left, margin.top], [width - margin.right, height - margin.bottom]])
.on('end', updateChart)
svg.append('g')
.attr('class', 'brush')
.call(brush)
const bisectTime = d3.bisector(d => d).left
// show/hide cursor
d3.select('.overlay')
.on('mousemove', (_, i, nodes) => {
const xPos = d3.mouse(nodes[i])[0]
d3.select('.cursor').style('display', null)
.attr('transform', `translate(${xPos}, 0)`)
// find closest data value, update display
const index = bisectTime(times, xScale.invert(xPos))
const d = data[index]
d3.select('.display').text(`${d.date} kl ${d.time}: ${d.temperature > 0 ? '+' : ''}${d.temperature}°C`)
})
.on('mouseout', () => {
d3.select('.cursor').style('display', 'none')
})
}
// fetch the data
d3.csv('https://rorbecker.com/mats/temperature_oskarshamn.csv', d => ({
date: d.date,
time: d.time,
temperature: +d.temperature
}))
.then(data => renderChart(data))
.catch(error => console.error(error))
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment