Skip to content

Instantly share code, notes, and snippets.

@d3indepth
Last active March 18, 2019 08:20
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save d3indepth/b6d4845973089bc1012dec1674d3aff8 to your computer and use it in GitHub Desktop.
D3 curve explorer
license: gpl-3.0
height: 860
border: no
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<title>D3 Curve Explorer</title>
</head>
<style>
body {
font-family: "Helvetica Neue", Helvetica, sans-serif;
font-size: 12px;
user-select: none;
}
svg {
box-shadow: 0px 0px 40px -5px rgba(0,0,0,0.3);
border-radius: 5px;
margin: 20px;
}
svg path {
fill: none;
}
svg circle {
fill: white;
stroke: #aaa;
cursor: move;
}
svg .points-menu g {
opacity: 0.2;
}
svg .points-menu g.active {
cursor: pointer;
opacity: 1;
}
svg .points-menu line {
stroke: #777;
stroke-width: 3;
}
svg .points-menu rect {
fill: white;
}
.sidebar {
display: inline-block;
position: relative;
vertical-align: top;
margin-top: 20px;
width: 530px;
color: #444;
}
.sidebar .header {
font-size: 14px;
font-weight: bold;
text-align: right;
color: #aaa;
width: 100%;
}
.menu .item {
padding: 5px;
border: 1px solid #ddd;
margin: 4px 2px;
float: left;
cursor: pointer;
border-radius: 8px;
width: 160px;
text-align: center;
}
.sidebar .info {
float: left;
margin-top: 20px;
line-height: 1.5em;
}
</style>
<body>
<svg width="700" height="400">
<g>
<path></path>
<g class="points-menu" transform="translate(660, 380)">
<g class="remove-point">
<rect x="-6" y="-6" width="12" height="12"></rect>
<line x1="-6" x2="6"></line>
</g>
<g class="add-point" transform="translate(20,0)">
<rect x="-6" y="-6" width="12" height="12"></rect>
<line x1="-6" x2="6"></line><line y1="-6" y2="6"></line>
</g>
</g>
</g>
</svg>
<div class="sidebar">
<div class="header">D3 CURVE EXPLORER</div>
<div class="menu"></div>
<div class="info">
<span class="default">The JavaScript library <a href="http://d3js.org">D3</a> provides a number of <a href="https://github.com/d3/d3-shape#curves">curve types</a> to interpolate (or approximate) a set of points. Toggle each of the curve types using the buttons above. You can also add/remove/drag the points to change the shape of the curve.</span>
<span class="text"></span>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<script>
var curveTypes = [
{name: 'curveLinear', curve: d3.curveLinear, active: true, lineString: '', clear: false, info: 'Interpolates the points using linear segments.'},
{name: 'curveBasis', curve: d3.curveBasis, active: true, lineString: '', clear: true, info: 'Interpolates the start and end points and approximates the inner points using a B-spline.'},
{name: 'curveBasisClosed', curve: d3.curveBasisClosed, active: false, lineString: '', clear: false, info: 'Uses a closed B-Spline to approximate the points.'},
{name: 'curveBundle (ß=0)', curve: d3.curveBundle.beta(0), active: false, lineString: '', clear: true, info: 'Same as curveBasis with the addition of a paramter ß which determines how close to a straight line the curve is. If ß=0 the curve is straight.'},
{name: 'curveBundle (ß=0.5)', curve: d3.curveBundle.beta(0.5), active: false, lineString: '', clear: false, info: 'Same as curveBasis with the addition of a paramter ß which determines how close to a straight line the curve is.'},
{name: 'curveBundle (ß=1)', curve: d3.curveBundle.beta(1), active: false, lineString: '', clear: false, info: 'Same as curveBasis with the addition of a paramter ß which determines how close to a straight line the curve is. If ß=1 the curve is the same as curveBasis.'},
{name: 'curveCardinal (tension=0)', curve: d3.curveCardinal.tension(0), active: false, lineString: '', clear: true, info: "Interpolates the points using a cubic B-spline. A tension parameter determines how 'taut' the curve is. As tension approaches 1 the segments become linear."},
{name: 'curveCardinal (tension=0.5)', curve: d3.curveCardinal.tension(0.5), active: false, lineString: '', clear: false, info: "Interpolates the points using a cubic B-spline. A tension parameter determines how 'taut' the curve is. As tension approaches 1 the segments become linear."},
{name: 'curveCardinal (tension=1)', curve: d3.curveCardinal.tension(1), active: false, lineString: '', clear: false, info: "Interpolates the points using a cubic B-spline. A tension parameter determines how 'taut' the curve is. As tension approaches 1 the segments become linear."},
{name: 'curveCatmullRom (α=0)', curve: d3.curveCatmullRom.alpha(0), active: false, lineString: '', clear: true, info: 'Similar to curveCardinal (tension=0) but with a parameter α that determines the parameterisation used to interpolate the points. If α=0 the parameterisation is uniform.'},
{name: 'curveCatmullRom (α=0.5)', curve: d3.curveCatmullRom.alpha(0.5), active: false, lineString: '', clear: false, info: 'Similar to curveCardinal (tension=0) but with a parameter α that determines the parameterisation used to interpolate the points. If α=0.5 the parameterisation is centripetal and self intersecting loops are avoided.'},
{name: 'curveCatmullRom (α=1)', curve: d3.curveCatmullRom.alpha(1), active: false, lineString: '', clear: false, info: 'Similar to curveCardinal (tension=0) but with a parameter α that determines the parameterisation used to interpolate the points. If α=1 the parameterisation is chordal.'},
{name: 'curveMonotoneX', curve: d3.curveMonotoneX, active: false, lineString: '', clear: true, info: 'Interpolates the points with a cubic spline which are monotonic (i.e. always increasing or always decreasing) in y.'},
{name: 'curveMonotoneY', curve: d3.curveMonotoneY, active: false, lineString: '', clear: false, info: 'Interpolates the points with a cubic spline which are monotonic (i.e. always increasing or always decreasing) in x.'},
{name: 'curveNatural', curve: d3.curveNatural, active: false, lineString: '', clear: true, info: 'Interpolates the points with a cubic spline with zero 2nd derivatives at the endpoints.'},
{name: 'curveStep', curve: d3.curveStep, active: false, lineString: '', clear: true, info: 'Interpolates the points with alternating horizontal and vertical linear segments. The vertical segments lie midway between points.'},
{name: 'curveStepAfter', curve: d3.curveStepAfter, active: false, lineString: '', clear: false, info: 'Interpolates the points with alternating horizontal and vertical linear segments. The y value changes after the x value.'},
{name: 'curveStepBefore', curve: d3.curveStepBefore, active: false, lineString: '', clear: false, info: 'Interpolates the points with alternating horizontal and vertical linear segments. The y value changes before the x value.'}
];
var lineGenerator = d3.line();
var categoryScale = d3.scaleOrdinal(d3.schemeCategory10);
function colorScale(d) {return d === 0 ? '#777' : categoryScale(d);}
var points = [ [50, 330], [75, 200], [280, 75], [300, 75], [475, 300], [600, 200] ];
var numActivePoints = points.length;
var drag = d3.drag()
.on('drag', function(d, i) {
points[i][0] = d3.event.x;
points[i][1] = d3.event.y;
update();
});
function updateInfo(info) {
d3.select('.info .default').style('display', info ? 'none' : 'inline');
d3.select('.info .text').text(info);
}
function updateMenu() {
var u = d3.select('.menu')
.selectAll('div.item')
.data(curveTypes);
u.enter()
.append('div')
.classed('item', true)
.style('clear', function(d) { return d.clear ? 'left' : 'none'; })
.text(function(d) { return d.name; })
.on('click', function(d) {
d.active = !d.active;
update();
})
.on('mouseover', function(d) { updateInfo(d.info); })
.on('mouseout', function() { updateInfo(''); })
.merge(u)
.style('background-color', function(d, i) { return d.active ? colorScale(i) : '#fff'; })
.style('color', function(d, i) { return d.active ? 'white' : '#444'; });
}
function updatePointsMenu() {
d3.select('.remove-point')
.classed('active', numActivePoints > 2)
.on('click', function() {
if(numActivePoints <= 2) return;
numActivePoints--;
update();
});
d3.select('.add-point')
.classed('active', numActivePoints < points.length)
.on('click', function() {
if(numActivePoints >= points.length) return;
numActivePoints++;
update();
});
}
function updateLines() {
curveTypes.forEach(function(d) {
if(!d.active) return;
lineGenerator.curve(d.curve);
d.lineString = lineGenerator(points.slice(0, numActivePoints));
});
var u = d3.select('svg g')
.selectAll('path')
.data(curveTypes);
u.enter()
.append('path')
.merge(u)
.style('stroke', function(d, i) { return colorScale(i); })
.attr('d', function(d) { return d.lineString; })
.style('display', function(d) { return d.active ? 'inline' : 'none'; });
}
function updatePoints() {
var u = d3.select('g')
.selectAll('circle')
.data(points.slice(0, numActivePoints));
u.enter()
.append('circle')
.attr('r', 4)
.call(drag)
.merge(u)
.attr('cx', function(d) { return d[0];})
.attr('cy', function(d) { return d[1];});
u.exit().remove();
}
function update() {
updateMenu();
updatePointsMenu();
updateLines();
updatePoints();
}
update();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment