Skip to content

Instantly share code, notes, and snippets.

@lloydroc
Last active March 19, 2018 04:40
Show Gist options
  • Save lloydroc/c615008f180ca98ff417d40f6bd4cc08 to your computer and use it in GitHub Desktop.
Save lloydroc/c615008f180ca98ff417d40f6bd4cc08 to your computer and use it in GitHub Desktop.
D3 Line Chart Sound Pressure
<html>
<head>
<meta charset="utf-8">
</head>
<style>
.peak-line {
fill: none;
stroke-width: 1;
stroke: red;
stroke-dasharray: 2,2;
}
.peak-line-label {
font-size: 8pt;
color: red;
}
.paths {
fill: none;
stroke-width: 2;
}
.paths-selected {
fill: none;
stroke-width: 4;
}
.points {
}
.point-selected {
}
.point-callout {
fill: grey;
}
.text-callout {
font-size: 8pt;
}
</style>
<body>
<svg width="900" height="300" id="timeseries"></svg>
</body>
<script src="https://d3js.org/d3.v5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.21.0/moment.min.js"></script>
<script>
var avg = [],
peak = [],
endTime = moment().valueOf(),
startTime = moment().subtract(1,'months').valueOf(),
n = 31*2,
step = (endTime-startTime)/n,
standardAvgWorkDay = gaussian(60,5),
standardAvgWeekendDay = gaussian(10,1),
colors = d3.scaleOrdinal(d3.schemeSet1);
var svg = d3.select("#timeseries"),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = svg.attr('width')-margin.left-margin.right,
height = svg.attr('height')-margin.top-margin.bottom;
var g,x,y;
function simulateData() {
for(var i=0;i<n;i++) {
var time = moment(i*step+startTime);
console.log(time.day());
if(time.day() == 6 || time.day() == 7) {
avgPoint = { t: time.valueOf(), y: standardAvgWeekendDay()}
peak.push({ t: time.valueOf(), y: avgPoint.y+Math.random()*2});
} else {
avgPoint = { t: time.valueOf(), y: standardAvgWorkDay()}
peak.push({ t: time.valueOf(), y: avgPoint.y+Math.random()*85});
}
avg.push(avgPoint);
}
}
simulateData();
drawChart();
drawSeries(peak,colors(6),0,'Peak');
drawSeries(avg,colors(2),1,'Avg');
function drawChart() {
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x = d3.scaleTime()
.domain([startTime, endTime])
.range([0, width])
.nice();
y = d3.scaleLinear()
.domain([0, 150])
.range([height,0])
.nice();
var labels = g.append('g');
labels.append('text')
.text('Noise Exposure [dB]')
.attr('x',width/2-30)
.attr('y',0)
.classed('chart-labels',true);
g.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(5,0)")
.call(d3.axisLeft(y));
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(5," + y(0) + ")")
.call(d3.axisBottom(x));
var line = d3.line()
.x(function(d) { return x(d.t); })
.y(function(d) { return y(d.y); })
.curve(d3.curveCatmullRom.alpha(0.5));
var lineZero = d3.line()
.x(function(d) { return x(d.t); })
.y(function(d) { return y(0); })
.curve(d3.curveCatmullRom.alpha(0.5));
var t = d3.transition()
.duration(2000)
.ease(d3.easeElasticOut);
var peakLineData = [{t: startTime, y: 140},{t: endTime, y: 140}];
var peakLine = g.append('g').selectAll('peakLine').data(peakLineData);
peakLine.enter().append('path').classed('peak-line',true).attr('d',line(peakLineData));
var peakLineText = g.append('text')
.text('Peak Sound Pressure 140dB')
.attr('x',x(endTime)-120)
.attr('y',y(142))
.classed('peak-line-label',true);
}
function drawSeries(data,color,legendIndex,legendLabel) {
var line = d3.line()
.x(function(d) { return x(d.t); })
.y(function(d) { return y(d.y); })
.curve(d3.curveCatmullRom.alpha(0.5));
var lineZero = d3.line()
.x(function(d) { return x(d.t); })
.y(function(d) { return y(0); })
.curve(d3.curveCatmullRom.alpha(0.5));
var t = d3.transition()
.duration(2000)
.ease(d3.easeElasticOut);
var peakLineData = [{t: startTime, y: 140},{t: endTime, y: 140}];
var peakLine = g.append('g').selectAll('peakLine').data(peakLineData);
peakLine.enter().append('path').classed('peak-line',true).attr('d',line(peakLineData));
var peakLineText = g.append('text')
.text('Peak Sound Pressure 140dB')
.attr('x',x(endTime)-120)
.attr('y',y(142))
.classed('peak-line-label',true);
var paths = g.append('g').selectAll('paths').data(data);
paths.enter().append('path')
.classed('paths',true)
.attr('stroke',color)
.on('mouseover', function(d) {
d3.select(this).classed('paths-selected',true);
var thisColor = d3.select(this).attr('stroke');
})
.on('mouseout', function(d) {
d3.select(this).classed('paths',true);
})
.attr('d',lineZero(data))
.transition(t)
.attr('d',line(data))
var points = g.append('g').selectAll('points').data(data);
points.enter().append('circle')
.attr('r',4)
.attr('cx',function(d) { return x(d.t)})
.attr('cy',function(d) { return y(0)})
.attr('class','points')
.attr('fill',color)
.on('mouseover', function(d) {
d3.select(this).attr('class','point-selected').attr('r',6);
var thisColor = d3.select(this).attr('stroke');
var callout = g.append('g').classed('callout',true)
function smartLabelX(d) {
var xv = x(d.t)-110;
if(xv < 0) return 0;
if(xv+220>x(endTime)) return x(endTime)-220;
return xv;
}
var calloutRect = callout.selectAll('point-callout').data([d])
calloutRect.enter()
.append('rect')
.attr('width',220)
.attr('height',50)
.attr('x',smartLabelX)
.attr('y',function(d) { return y(d.y)-60})
.attr('rx',5)
.attr('ry',5)
.classed('point-callout',true);
var triData = [
{x: x(d.t)-9, y: y(d.y)-10},
{x: x(d.t)+0, y: y(d.y)},
{x: x(d.t)+9, y: y(d.y)-10}
];
var triLine = d3.line()
.x(function(d) { ;return d.x; })
.y(function(d) { return d.y; });
var calloutTri = callout.selectAll('point-callout-tri').data(triData)
calloutTri.enter()
.append('path')
.attr('d',triLine(triData))
.classed('point-callout',true);
function createCalloutText(d,i) {
return i==0 ? new Date(d.t) : parseFloat(d.y).toFixed(1)+'dB';
}
var calloutText = callout.selectAll('text-callout').data([d,d]).enter()
.append('text')
.attr("x",function(d,i) { return i==0 ? smartLabelX(d)+10 : Math.min(x(d.t)-20,x(endTime)-100)})
.attr("y",function(d,i) { return i==0 ? y(d.y)-40 : y(d.y) - 20})
.text(createCalloutText)
.classed('text-callout',function(d,i) { return i==0 ? true : false})
})
.on('mouseout', function(d) {
d3.select(this).attr('class','points').attr('r',4);
g.selectAll('.callout').remove();
})
.transition(t)
.attr('cx',function(d) { return x(d.t)})
.attr('cy',function(d) { return y(d.y)})
var legend = g.append('g');
var legendRect = legend.selectAll('legendRect').data([{x: 10+legendIndex*75, y: 0}])
legendRect.enter()
.append('rect')
.attr('width',20)
.attr('height',10)
.attr('x',function(d) { return d.x})
.attr('y',function(d) { return d.y})
.attr('rx',3)
.attr('ry',3)
.attr('fill',color)
var legendText = legend.selectAll('legendText').data([{x: 32+legendIndex*75, y: 10}])
legendText.enter()
.append('text')
.attr('x',function(d) { return d.x})
.attr('y',function(d) { return d.y})
.style('color',color)
.text(legendLabel)
.classed('legend-text',true)
}
// returns a gaussian random function with the given mean and stdev.
function gaussian(mean, stdev) {
var y2;
var use_last = false;
return function() {
var y1;
if(use_last) {
y1 = y2;
use_last = false;
}
else {
var x1, x2, w;
do {
x1 = 2.0 * Math.random() - 1.0;
x2 = 2.0 * Math.random() - 1.0;
w = x1 * x1 + x2 * x2;
} while( w >= 1.0);
w = Math.sqrt((-2.0 * Math.log(w))/w);
y1 = x1 * w;
y2 = x2 * w;
use_last = true;
}
var retval = mean + stdev * y1;
if(retval > 0)
return retval;
return -retval;
}
}
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment