This sonified line chart (forked from mbostock's Line Chart) is constructed from a TSV file storing the closing value of AAPL stock over the last few years.
The sonification is produced by scaling stock prices to frequencies between low and high C, then playing tones as a cursor walks the x-axis using Tone.js.
xxxxxxxxxx
<meta charset="utf-8">
<style>
.axis--x path { display: none; }
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.cursor {
stroke-width: 2;
stroke: red;
fill: none;
}
</style>
<button onclick="play_pause()">Play/Pause</button>
<button onclick="restart()">Restart</button>
<svg width="960" height="480"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/0.8.0/tone.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%d-%b-%y");
var x = d3.scaleTime().rangeRound([0, width]);
var y = d3.scaleLinear().rangeRound([height, 0]);
var yfreq = d3.scaleLinear().range([65.4064, 1046.50]);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
var synth = new Tone.Synth({
"oscillator" : { "type" : "square" },
"envelope" : {
"attack" : 0.01,
"decay" : 0.2,
"sustain" : 0.2,
"release" : 0.2,
}
}).toMaster();
var playing = false, play_idx = 0, reset = false;
function play_pause() { playing = !playing; }
function restart() { play_idx = 0; reset = true; }
d3.tsv("data.tsv", function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
return d;
}, function(error, data) {
if (error) throw error;
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
yfreq.domain(d3.extent(data, function(d) { return d.close; }));
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.style("text-anchor", "end")
.text("Price ($)");
g.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var xidx = data.map(line.x());
var freqs = data.map((d) => yfreq(d.close));
function tick() {
if (playing && play_idx < xidx.length) {
synth.triggerAttackRelease(freqs[play_idx], 0.05);
d3.select(this).attr("x1", xidx[play_idx]).attr("x2", xidx[play_idx]);
play_idx++;
} else if (reset) {
d3.select(this).attr("x1", xidx[play_idx]).attr("x2", xidx[play_idx]);
reset = false;
}
d3.active(this).transition().on("start", tick);
}
g.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", height)
.attr("class", "cursor")
.transition()
.duration(50)
.ease(d3.easeLinear)
.on("start", tick);
});
</script>
Updated missing url https://cdnjs.cloudflare.com/ajax/libs/tone/0.8.0/Tone.min.js to https://cdnjs.cloudflare.com/ajax/libs/tone/0.8.0/tone.min.js
https://d3js.org/d3.v4.min.js
https://cdnjs.cloudflare.com/ajax/libs/tone/0.8.0/Tone.min.js