Skip to content

Instantly share code, notes, and snippets.

@matt-mcdaniel
Last active September 18, 2015 19:10
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 matt-mcdaniel/f1cda1ba12f9904e65a0 to your computer and use it in GitHub Desktop.
Save matt-mcdaniel/f1cda1ba12f9904e65a0 to your computer and use it in GitHub Desktop.
Interpolation and Area Charts

Interpolation/Easing with Randomized Data

An area chart with randomized data used to expirement with interpolations and easing functions.

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
svg { width: 100%; height: 100%; }
</style>
<link href="styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="button-container">
<button onclick="changeData()">
Update Data
</button>
<p>Interpolator</p>
<button class="interpolate-button" onclick="changeInterpolation()"></button>
<div class="ease-container">
<p>Easing Function</p>
<button class="ease-button" onclick="changeEasing()"></button>
<div class="radio">
<input name="ease-type" class="ease-suffix" type="radio" value="in">
<label>in</label>
<input name="ease-type" class="ease-suffix" type="radio" value="out">
<label>out</label>
<input name="ease-type" class="ease-suffix" type="radio" value="in-out">
<label>in-out</label>
</div>
</div>
<p>Duration</p>
<input type="range" class="duration" value="400" min="0" max="1200"/>
<label class="dur"></label>
</div>
<figure>
<svg></svg>
</figure>
<script>
var eases = ['linear', 'poly(2)', 'cubic', 'sin', 'exp', 'circle', 'elastic', 'back', 'bounce'];
var interps = ['linear', 'linear-closed', 'step', 'step-before', 'step-after', 'basis', 'basis-open', 'basis-closed', 'bundle', 'cardinal', 'cardinal-open', 'cardinal-closed', 'monotone'];
var ease = 'back',
easeCount = eases.indexOf(ease),
interp = 'cardinal',
interpCount = interps.indexOf(interp),
duration = 600;
// tooltip dimensions
var tt ={ w: 80, h: 50 };
// generate array of random numbers
function generate(min, max) {
var arr = [];
for (var i = 0; i < 6; i++) {
var rand = Math.random() * (max-min) + min;
var rounded = Math.round(rand * 100)/100;
arr.push(rounded);
}
// add endpoints
var startPoint = arr[0] - 0.2;
var endPoint = arr[arr.length - 1] - 0.2;
arr.unshift(startPoint);
arr.push(endPoint);
return arr;
};
var margin = {
top: 20,
right: 40,
bottom: 40,
left: 40
}
var data = [
{
name: 'state',
bg: '#C2E3F9',
stroke: '#0362AD',
opacity: 0.5,
data: [2.2, 2.4, 3.9, 4.6, 2.19, 4.5, 3.45, 3.25]
},
{
name: 'national',
bg: '#F7CDCD',
stroke: '#D9534F',
opacity: 0.5,
data: [3.6, 3.8, 3.15, 3.4, 2.93, 4.3, 4.1, 3.9]
},
{
name: 'facility',
bg: '#F9E0B2',
stroke: '#F0A611',
opacity: 0.84,
data: [2.8, 3, 3.5, 4.2, 2.35, 4.8, 2.9, 2.7]
},
]
// ** Updates ** //
// update data
function changeData() {
for (var obj in data) {
var updated = generate(2, 4.8);
data[obj]['data'] = updated;
}
update();
}
// change easing
function changeEasing() {
d3.selectAll('.ease-suffix').property('checked', false);
easeCount = ++easeCount % eases.length;
ease = eases[easeCount];
d3.select('.ease-button').html(ease);
update();
}
// easing suffix
d3.selectAll('.ease-suffix').on('click', function() {
console.info(this.value);
ease = eases[easeCount] + '-' + this.value;
d3.select('.ease-button').html(ease);
update();
console.info(ease);
});
// change interpolation
function changeInterpolation() {
interpCount = ++interpCount % interps.length;
interp = interps[interpCount];
d3.select('.interpolate-button').html(interp);
update();
}
d3.select('.duration').on('input', function() {
duration = this.value;
d3.select('.dur').html(duration + 'ms');
update();
})
// ** DOM Elements ** //
d3.select('.ease-button').html(ease);
d3.select('.interpolate-button').html(interp);
d3.select('.dur').html(duration + ' ms');
var w = d3.select('figure').node().clientWidth - margin.left - margin.right;
var h = d3.select('figure').node().clientHeight - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain([0,1,2,3,4,5,6, 7])
.rangePoints([0, w], -1.5);
var y = d3.scale.linear()
.domain([0, 5])
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.tickPadding([20])
.tickSize(0, 0)
.tickValues([1,2,3,4,5,6]);
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.tickSize(-(w), 0)
.tickPadding([20])
.tickFormat(d3.format('r'))
.tickValues([1, 2, 3, 4, 5]);
// Define the div for the tooltip
var tooltip = d3.select('figure').append('div')
.classed('tooltip', true)
.style({
opacity: 0,
width: tt.w + 'px',
height: tt.h + 'px'
});
// Tooltip Text
tooltip.append('div')
.classed('text', true);
// Triangle after tooltip
tooltip.append('div')
.classed('triangle', true)
.style({
bottom: ( (tt.h/4) * -1 ) + 'px',
left: (tt.w / 2) - (tt.h/3) + 'px',
'border-top': (tt.h/3) + 'px solid white',
'border-right': (tt.h/3) + 'px solid transparent',
'border-left': (tt.h/3) + 'px solid transparent'
});
var clip = d3.select('svg').append('defs').append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr({
id: 'clip-rect',
x: 0,
y: 0,
width: w,
height: h
});
var svg = d3.select('svg')
.attr('width', w + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom);
// X Axis
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(' + margin.left + ',' + parseInt(h + margin.top) + ')')
.call(xAxis)
// Y Axis
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(yAxis);
var g = svg.append('g')
.attr('clip-path', 'url(#clip)')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var area = d3.svg.area()
.interpolate(interp)
.x(function(d, i) { return x(i); })
.y0(h)
.y1(function(d) { return y(d); });
var stacks = g.selectAll('.stacks')
.data(data)
.enter().append('g')
.classed('stacks', true);
stacks.append('path')
.attr({
fill: function(d) { return d.bg; },
stroke: function(d) { return d.stroke; },
'stroke-width': 2,
opacity: function(d) { return d.opacity; }
})
.attr('d', function(d) { return area(d.data) });
var lines = stacks.selectAll('line')
.data(function(d) { return d.data; })
.enter().append('line')
.attr({
x1: function(d,i) {
return x(i)
},
y1: h,
x2: function(d,i) {
return x(i)
},
y2: function(d) {
return y(d)
}
})
.each(function(d, i) {
var parent = this.parentNode.__data__;
d3.select(this)
.attr({
stroke: parent.stroke,
'stroke-width': 2,
opacity: function(d, i) {
if (parent.name === 'facility') {
return 1;
} else {
return 0;
}
}
});
});
var circles = stacks.selectAll('circle')
.data(function(d) { return d.data; })
.enter().append('circle')
.attr({
r: 10,
'stroke-width': 2,
cx: function(d, i) {
return x(i);
},
cy: function(d) {
return y(d);
},
opacity: function(d, i) {
if (!i || i === data[0].data.length -1) {
return 0;
} else {
return 1;
}
}
})
.each(function() {
var parent = this.parentNode.__data__;
this._parentAttrs = parent;
d3.select(this)
.attr({
fill: parent.bg,
stroke: parent.stroke,
'stroke-width': 2
});
});
// Tooltip on hover
circles
.on('mousemove', function() {
d3.select(this).transition().duration(50).attr('r', 13);
var parent = this._parentAttrs;
var text = d3.select(this).data()[0];
tooltip.select('.text').html(text);
var locX = d3.event.pageX - (tt.w / 2);
var locY = d3.event.pageY - ( tt.h + (tt.h/1.3) );
tooltip.style({
opacity: 1,
left: locX + 'px',
top: locY + 'px',
'background-color': parent.stroke
});
tooltip.select('.triangle').style({
'border-top-color': parent.stroke
});
})
.on('mouseleave', function() {
d3.select(this).transition().duration(50).attr('r', 10);
tooltip.style({
opacity: 0,
left: 0,
right: 0
});
});
function update() {
area.interpolate(interp);
stacks = stacks.data(data);
stacks.select('path')
.transition()
.ease(ease)
.duration(duration)
.attr('d', function(d) { return area(d.data); });
circles = stacks.selectAll('circle')
.data(function(d) { return d.data; });
circles.transition()
.duration(duration)
.ease(ease)
.attr({
cx: function(d, i) { return x(i); },
cy: function(d) { return y(d); }
});
lines = stacks.selectAll('line')
.data(function(d) { return d.data; });
lines.transition()
.duration(duration)
.ease(ease)
.attr({
x1: function(d,i) {
return x(i)
},
y1: h,
x2: function(d,i) {
return x(i)
},
y2: function(d) {
return y(d)
}
})
}
</script>
</body>
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
body {
font-family: sans-serif;
}
.button-container {
display: flex;
justify-content: center;
align-items: center;
}
p {
font-size: 12px;
color: #a0a0a0;
display: inline;
}
button {
position: relative;
padding: 1em;
margin: 2em 4em 2em 1em;
width: 9em;
}
.ease-container {
position: relative;
text-align: center;
}
.radio {
position: absolute;
right: 0;
bottom: 0.5em;
text-align: center;
margin-right: 3.5em;
bottom: 0;
font-size: 11px;
}
input[type='range'] {
margin: 2em;
width: 14em;
}
label {
margin-right: 0.5em;
color: #a0a0a0;
font-size: 80%;
}
figure {
margin: auto;
width: 900px;
height: 410px;
}
circle {
cursor: pointer;
}
.axis text {
fill: #a0a0a0;
font: 14px 'Arial';
}
.axis path,
.axis line {
fill: none;
stroke: #d8d8d8;
stroke-width: 2;
shape-rendering: crispEdges;
}
.y line {
stroke: #d8d8d8;
stroke-width: 2;
stroke-opacity: 0.5;
}
.tooltip {
position: absolute;
text-align: center;
}
.text {
font-family: 'Oswald';
font-weight: 400;
font-size: 24px;
margin-top: 0.25em;
color: white;
width: 100%;
height: 100%;
}
.triangle {
position: absolute;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment