Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:03
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 nitaku/1ea254444b4a3fb73dd3 to your computer and use it in GitHub Desktop.
Save nitaku/1ea254444b4a3fb73dd3 to your computer and use it in GitHub Desktop.
Archimedean spiral with a given angular length
WIDTH = 960
HEIGHT = 500
svg = d3.select('body').append('svg')
.attr
width: WIDTH
height: HEIGHT
.append('g')
.attr
transform: "translate(#{WIDTH/2},80)"
# WARNING the following assumes a=0
a = 0
# angle samples per turn (not good for large spirals!)
SAMPLES = 80
# draw axes
svg.append('line')
.attr
class: 'my_axis'
x1: -WIDTH
x2: WIDTH
svg.append('line')
.attr
class: 'my_axis'
y1: -HEIGHT
y2: HEIGHT
spiral = svg.append('path')
.attr
class: 'spiral'
# draw the controls
theta_max_scale = d3.scale.linear()
.domain([0, 360 * 12])
.range([-WIDTH/2+50, WIDTH/2-50])
.clamp(true)
theta_max_brush = d3.svg.brush()
.x(theta_max_scale)
.extent([0, 0])
THETA_SLIDER_Y = -50
svg.append('g')
.attr('class', 'x axis')
.attr('transform', "translate(0,#{THETA_SLIDER_Y})")
.call(d3.svg.axis()
.scale(theta_max_scale)
.orient('bottom')
.tickFormat((d) -> d + '°')
.tickSize(0)
.tickPadding(12))
.select('.domain')
.select(() -> this.parentNode.appendChild(this.cloneNode(true)) )
.attr('class', 'halo')
slider = svg.append('g')
.attr('class', 'slider')
.call(theta_max_brush)
slider.selectAll('.extent,.resize')
.remove()
slider.select('.background')
.attr('transform', "translate(0,#{THETA_SLIDER_Y-11})")
.attr('height', 22)
handle = slider.append('circle')
.attr('class', 'handle')
.attr('transform', "translate(0,#{THETA_SLIDER_Y})")
.attr('r', 9)
# initial animation
slider
.call(theta_max_brush.event)
.transition()
.duration(750)
.call(theta_max_brush.extent([2*360, 2*360]))
.call(theta_max_brush.event)
brushed = () ->
value = theta_max_brush.extent()[0]
if d3.event.sourceEvent # not a programmatic event
value = theta_max_scale.invert(d3.mouse(this)[0])
theta_max_brush.extent([value, value])
handle.attr('cx', theta_max_scale(value))
# redraw the spiral
redraw(2*Math.PI * value/360, 16)
theta_max_brush
.on('brush', brushed)
redraw = (theta_max, dist) ->
b = dist/(2*Math.PI)
# compute the radius
radius = a + b * theta_max
number_of_points = Math.ceil( theta_max/(2*Math.PI) * SAMPLES )
points = d3.range(0, number_of_points).map (i) ->
theta = i*2*Math.PI/SAMPLES
# PI/2 is subtracted from angles to have the spiral end at the top
return {theta: theta-Math.PI/2-theta_max, r: a + b*theta}
points.push {theta: -Math.PI/2, r: radius}
# draw the spiral
line_generator = d3.svg.line()
.x((d) -> d.r * Math.cos(d.theta))
.y((d) -> radius + d.r * Math.sin(d.theta)) # translate by radius to keep the final point in the same position
.interpolate('cardinal')
spiral
.datum(points)
.attr
d: line_generator
svg {
background-color: white;
}
.spiral {
fill: none;
stroke: black;
stroke-width: 2px;
}
.my_axis {
fill: none;
stroke: lightgray;
stroke-dasharray: 24 6 2 6;
shape-rendering: crispEdges;
}
.axis {
font: 10px sans-serif;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.axis .domain {
fill: none;
stroke: #000;
stroke-opacity: .3;
stroke-width: 10px;
stroke-linecap: round;
}
.axis .halo {
fill: none;
stroke: #ddd;
stroke-width: 8px;
stroke-linecap: round;
}
.slider .handle {
fill: #fff;
stroke: #000;
stroke-opacity: .5;
stroke-width: 1.25px;
pointer-events: none;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Archimedean spiral with a given angular length" />
<title>Archimedean spiral with a given angular length</title>
<link rel="stylesheet" href="index.css">
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<script src="index.js"></script>
</body>
</html>
(function() {
var HEIGHT, SAMPLES, THETA_SLIDER_Y, WIDTH, a, brushed, handle, redraw, slider, spiral, svg, theta_max_brush, theta_max_scale;
WIDTH = 960;
HEIGHT = 500;
svg = d3.select('body').append('svg').attr({
width: WIDTH,
height: HEIGHT
}).append('g').attr({
transform: "translate(" + (WIDTH / 2) + ",80)"
});
a = 0;
SAMPLES = 80;
svg.append('line').attr({
"class": 'my_axis',
x1: -WIDTH,
x2: WIDTH
});
svg.append('line').attr({
"class": 'my_axis',
y1: -HEIGHT,
y2: HEIGHT
});
spiral = svg.append('path').attr({
"class": 'spiral'
});
theta_max_scale = d3.scale.linear().domain([0, 360 * 12]).range([-WIDTH / 2 + 50, WIDTH / 2 - 50]).clamp(true);
theta_max_brush = d3.svg.brush().x(theta_max_scale).extent([0, 0]);
THETA_SLIDER_Y = -50;
svg.append('g').attr('class', 'x axis').attr('transform', "translate(0," + THETA_SLIDER_Y + ")").call(d3.svg.axis().scale(theta_max_scale).orient('bottom').tickFormat(function(d) {
return d + '°';
}).tickSize(0).tickPadding(12)).select('.domain').select(function() {
return this.parentNode.appendChild(this.cloneNode(true));
}).attr('class', 'halo');
slider = svg.append('g').attr('class', 'slider').call(theta_max_brush);
slider.selectAll('.extent,.resize').remove();
slider.select('.background').attr('transform', "translate(0," + (THETA_SLIDER_Y - 11) + ")").attr('height', 22);
handle = slider.append('circle').attr('class', 'handle').attr('transform', "translate(0," + THETA_SLIDER_Y + ")").attr('r', 9);
slider.call(theta_max_brush.event).transition().duration(750).call(theta_max_brush.extent([2 * 360, 2 * 360])).call(theta_max_brush.event);
brushed = function() {
var value;
value = theta_max_brush.extent()[0];
if (d3.event.sourceEvent) {
value = theta_max_scale.invert(d3.mouse(this)[0]);
theta_max_brush.extent([value, value]);
}
handle.attr('cx', theta_max_scale(value));
return redraw(2 * Math.PI * value / 360, 16);
};
theta_max_brush.on('brush', brushed);
redraw = function(theta_max, dist) {
var b, line_generator, number_of_points, points, radius;
b = dist / (2 * Math.PI);
radius = a + b * theta_max;
number_of_points = Math.ceil(theta_max / (2 * Math.PI) * SAMPLES);
points = d3.range(0, number_of_points).map(function(i) {
var theta;
theta = i * 2 * Math.PI / SAMPLES;
return {
theta: theta - Math.PI / 2 - theta_max,
r: a + b * theta
};
});
points.push({
theta: -Math.PI / 2,
r: radius
});
line_generator = d3.svg.line().x(function(d) {
return d.r * Math.cos(d.theta);
}).y(function(d) {
return radius + d.r * Math.sin(d.theta);
}).interpolate('cardinal');
return spiral.datum(points).attr({
d: line_generator
});
};
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment