Debugging a Fourier Interpolation of NOAA currents data.
Referenced from @fil's block
Built with blockbuilder.org
xxxxxxxxxx
<head>
<meta charset="utf-8">
<title>Bay Water Trail Tide Chart</title>
<style>
#tidal-chart {
padding: 25px;
}
.zero-line path {
stroke-width: 0;
stroke: none;
}
.zero-line .tick line {
stroke: grey;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
.area { fill: lightsteelblue; }
.tide-chart-title,
text {
font-family: sans-serif;
}
text { font-size: 13px; }
</style>
</head>
<body>
<div class="loading-msg"><h3>Loading...</h3></div>
<div id="tidal-chart"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/fourier@0.2.1/fourier.js"></script>
<script>
//
// This example interpolates values using a Fourier Transformation interpolation
// with help from Philippe Rivière's block: https://bl.ocks.org/Fil/6e83bb512f22ec579f6226b7692530fb
//
var loadingMsg = d3.select('.loading-msg');
var chartContainer = "#tidal-chart";
var margin = { top: 20, right: 20, bottom: 50, left: 50 };
var width = 900 - margin.left - margin.right;
var height = 420 - margin.top - margin.bottom;
// date time parser
var parseTime = d3.timeParse('%Y-%m-%d %I:%M %p');
var formatTime = d3.timeFormat('%B %d %I:%M%p');
// callback which is invoked upon data load finishing
function main(err, rsp) {
if (err) throw err;
// this is a 3 year dataset, so only grab data for close to today's date
var today = new Date();
var beforeToday = +today - (1000 * 60 * 60 * 12);
var tommorrow = +today + (1000 * 60 * 60 * 24);
var data = rsp.filter(function(d) {
return +parseTime(d.date_time) >= beforeToday && +parseTime(d.date_time) <= tommorrow;
})
.map(function(d) {
d.date_time = +parseTime(d.date_time);
d.speed = +d.speed;
return d;
});
console.log(data);
// x & y scales
var x = d3.scaleLinear()
.domain(d3.extent(data.map(d => d.date_time)))
.range([0, width]);
var xlabel = d3.scaleTime()
.domain(d3.extent(data.map(d => new Date(d.date_time))))
.range([0, width]);
// TO DO: pad the domain so the curve doesn't touch the bottom or top of the chart
var y = d3.scaleLinear()
.domain(d3.extent(data.map(d => d.speed)))
// .domain([-0.6, 0.6])
.range([height, 0]);
// compute the Fast Fourier transform
const a = data.filter(d => d.event !== 'slack'),
points = 256,
c = points / a.length,
d = points / data.length,
f = fourier.dft(a.map((d, i) => d.speed), a.map((d, i) => 0)),
zeros = d3.range(points - f[0].length).map(d => 0),
h = [ [...zeros,...f[0]], [...zeros,...f[1]]],
g = fourier.idft(...h);
// interpolate the values
function inter(i) {
return c * (g[0][i] || 0)
}
// interpolate the time
var t = d3.scaleLinear()
.domain(d3.range(data.length))
.range(data.map(d => d.date_time));
var line = d3.line()
.x(i => x(t(i / d)))
.y(i => y(inter(i)));
// gridlines in x axis function
function make_x_gridlines() {
return d3.axisBottom(x).ticks(7)
}
// gridlines in y axis function
function make_y_gridlines() {
return d3.axisLeft(y).ticks(5)
}
// create chart title with current date from data
// TO DO: add human readable name for station using look up table
var title = d3.select(chartContainer).append('h3')
.attr('class', 'tide-chart-title')
.text('NOAA Current Predictions for SFB1213 on ' + formatTime(new Date()));
// create the svg element with an offset group element
var svg = d3.select(chartContainer).append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// x-axis
svg.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(xlabel));
// y-axis
svg.append('g')
.call(d3.axisLeft(y));
// add the X gridlines
svg.append("g")
.attr("class", "grid grid-x")
.attr("transform", "translate(0," + height + ")")
.call(make_x_gridlines()
.tickSize(-height)
.tickFormat("")
);
// add the Y gridlines
svg.append("g")
.attr("class", "grid grid-y")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
);
// add line for showing "0" y-axis value
svg.append('g')
.attr('class', 'zero-line')
.call(d3.axisLeft(y).ticks(1).tickFormat('').tickSize(-width));
// create the line path
svg.append('path')
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('d', line(d3.range(points)));
// add reference circles
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('r', 4)
.attr('cx', d => x(d.date_time))
.attr('cy', d => y(d.speed))
// y axis label
svg.append('text')
.attr('text-anchor', 'middle')
.attr('transform', 'translate(' + (-margin.left + 12) + ',' + (height / 2) + ') rotate(-90)')
.text('Speed (Knots)');
// x axis label
svg.append('text')
.attr('text-anchor', 'middle')
.attr('transform', 'translate(' + (width / 2) + ',' + (height + (margin.bottom - 10)) + ')')
.text('Time of Day');
// hide our loading message
loadingMsg.style('display', 'none');
}
// get the data & then make the chart
d3.csv('SFB1213.csv', main);
</script>
</body>
</html>
https://d3js.org/d3.v4.min.js
https://unpkg.com/fourier@0.2.1/fourier.js