Last active
August 29, 2015 14:03
-
-
Save nitaku/bd36fc43da4922454e2b to your computer and use it in GitHub Desktop.
Tape chart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
WIDTH = 960 | |
HEIGHT = 500 | |
svg = d3.select('body').append('svg') | |
.attr | |
width: WIDTH | |
height: HEIGHT | |
.append('g') | |
.attr | |
transform: "translate(#{WIDTH/2},160)" | |
# WARNING the following assumes a=0 | |
a = 0 | |
D = 40 | |
b = D/(2*Math.PI) | |
# angle samples per turn (not good for large spirals!) | |
SAMPLES = 80 | |
# draw axes | |
svg.append('line') | |
.attr | |
class: 'my_axis debug' | |
x1: -WIDTH | |
x2: WIDTH | |
svg.append('line') | |
.attr | |
class: 'my_axis debug' | |
y1: -HEIGHT | |
y2: HEIGHT | |
spiral = svg.append('path') | |
.attr | |
class: 'spiral' | |
START = 0 | |
END = 100 | |
STEP = 0.1 | |
time = d3.range(START, END+STEP, STEP).map (t) -> {t: t} | |
lin_start = 30 | |
lin_end = 44 | |
LIN_WIDTH = 500 | |
t2l = d3.scale.linear() | |
.domain([lin_start, lin_end]) | |
.range([-LIN_WIDTH/2, LIN_WIDTH/2]) | |
# delta = LIN_WIDTH / (lin_end-lin_start) | |
delta = t2l(1) - t2l(0) | |
# left spiral | |
theta_max_l = Math.sqrt(delta*(lin_start-START)/b) # a = 0 | |
radius_l = a + b*theta_max_l | |
delta_theta_l = delta / radius_l | |
t2ltheta = d3.scale.linear() | |
.domain([START,lin_start]) | |
.range([-Math.PI/2-theta_max_l, -Math.PI/2]) | |
t2lr = d3.scale.linear() | |
.domain([START,lin_start]) | |
.range([0,radius_l]) | |
# right spiral | |
theta_max_r = Math.sqrt(delta*(END-lin_end)/b) # a = 0 | |
radius_r = a + b*theta_max_r | |
delta_theta_r = delta / radius_r | |
t2rtheta = d3.scale.linear() | |
.domain([lin_end,END]) | |
.range([-Math.PI/2, -Math.PI/2+theta_max_r]) | |
t2rr = d3.scale.linear() | |
.domain([lin_end,END]) | |
.range([radius_r,0]) | |
spiral_layout = (d) -> | |
if d.t < lin_start | |
# left spiral | |
#theta = t * delta_theta_l | |
# PI/2 + theta_max is subtracted from theta to have the spiral end at the top | |
#r = a + b*theta | |
#theta = theta-Math.PI/2-theta_max_l | |
# y is translated by radius to have the spiral end at the top | |
# x is translated by LIN_WIDTH/2 to match the spiral with the line | |
#return {t: t, theta: theta, r: r, x: -LIN_WIDTH/2 + r*Math.cos(theta), y: radius_l + r*Math.sin(theta)} | |
d.theta = t2ltheta(d.t) | |
d.r = t2lr(d.t) | |
d.x = -LIN_WIDTH/2 + d.r*Math.cos(d.theta) | |
d.y = radius_l + d.r*Math.sin(d.theta) | |
return d | |
if d.t <= lin_end | |
# line | |
d.x = t2l(d.t) | |
d.y = 0 | |
return d | |
# else | |
# right spiral | |
#theta = (t-lin_end-STEP) * delta_theta_r | |
# PI/2 + theta_max is subtracted from theta to have the spiral end at the top | |
#r = a + b*theta | |
#theta = theta-Math.PI/2-theta_max_r | |
# y is translated by radius to have the spiral end at the top | |
# x is translated by LIN_WIDTH/2 to match the spiral with the line | |
d.theta = t2rtheta(d.t) | |
d.r = t2rr(d.t) | |
d.x = +LIN_WIDTH/2 + d.r*Math.cos(d.theta) | |
d.y = radius_r + d.r*Math.sin(d.theta) | |
return d | |
data = d3.range(0,100,1).concat([100]) | |
data = data.map (t) -> | |
d = {t: t} | |
if t % 10 is 0 | |
d.highlight = true | |
if t is 42 | |
d.answer = true | |
return d | |
# draw the spiral | |
line_generator = d3.svg.line() | |
.x((d) -> d.x) | |
.y((d) -> d.y) | |
.interpolate('linear') | |
spiral = svg.append('path') | |
.datum(time.map spiral_layout) | |
.attr | |
class: 'spiral' | |
d: line_generator | |
svg.selectAll('.dot') | |
.data(data.map spiral_layout) | |
.enter().append('circle') | |
.attr | |
class: 'dot' | |
cx: (d) -> d.x | |
cy: (d) -> d.y | |
r: (d) -> if d.highlight or d.answer then 4 else 2 | |
fill: (d) -> if d.answer then 'rgb(231, 41, 138)' else 'rgb(27, 158, 119)' | |
svg.append('circle') | |
.attr | |
class: 'radius_indicator debug' | |
cx: -LIN_WIDTH/2 | |
cy: radius_l | |
r: radius_l | |
svg.append('circle') | |
.attr | |
class: 'radius_indicator debug' | |
cx: LIN_WIDTH/2 | |
cy: radius_r | |
r: radius_r | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
svg { | |
background-color: white; | |
} | |
.spiral { | |
fill: none; | |
stroke: #DDD; | |
stroke-width: 2px; | |
} | |
.my_axis { | |
fill: none; | |
stroke: lightgray; | |
stroke-dasharray: 24 6 2 6; | |
shape-rendering: crispEdges; | |
} | |
.radius_indicator { | |
fill: none; | |
stroke: gray; | |
stroke-dasharray: 3 6; | |
} | |
.dot { | |
stroke: black; | |
stroke-width: 0.5; | |
} | |
.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; | |
} | |
.debug { | |
display: none; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="description" content="Tape chart" /> | |
<title>Tape chart</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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
var D, END, HEIGHT, LIN_WIDTH, SAMPLES, START, STEP, WIDTH, a, b, data, delta, delta_theta_l, delta_theta_r, lin_end, lin_start, line_generator, radius_l, radius_r, redraw, spiral, spiral_layout, svg, t2l, t2lr, t2ltheta, t2rr, t2rtheta, theta_max_l, theta_max_r, time; | |
WIDTH = 960; | |
HEIGHT = 500; | |
svg = d3.select('body').append('svg').attr({ | |
width: WIDTH, | |
height: HEIGHT | |
}).append('g').attr({ | |
transform: "translate(" + (WIDTH / 2) + ",160)" | |
}); | |
a = 0; | |
D = 40; | |
b = D / (2 * Math.PI); | |
SAMPLES = 80; | |
svg.append('line').attr({ | |
"class": 'my_axis debug', | |
x1: -WIDTH, | |
x2: WIDTH | |
}); | |
svg.append('line').attr({ | |
"class": 'my_axis debug', | |
y1: -HEIGHT, | |
y2: HEIGHT | |
}); | |
spiral = svg.append('path').attr({ | |
"class": 'spiral' | |
}); | |
START = 0; | |
END = 100; | |
STEP = 0.1; | |
time = d3.range(START, END + STEP, STEP).map(function(t) { | |
return { | |
t: t | |
}; | |
}); | |
lin_start = 30; | |
lin_end = 44; | |
LIN_WIDTH = 500; | |
t2l = d3.scale.linear().domain([lin_start, lin_end]).range([-LIN_WIDTH / 2, LIN_WIDTH / 2]); | |
delta = t2l(1) - t2l(0); | |
theta_max_l = Math.sqrt(delta * (lin_start - START) / b); | |
radius_l = a + b * theta_max_l; | |
delta_theta_l = delta / radius_l; | |
t2ltheta = d3.scale.linear().domain([START, lin_start]).range([-Math.PI / 2 - theta_max_l, -Math.PI / 2]); | |
t2lr = d3.scale.linear().domain([START, lin_start]).range([0, radius_l]); | |
theta_max_r = Math.sqrt(delta * (END - lin_end) / b); | |
radius_r = a + b * theta_max_r; | |
delta_theta_r = delta / radius_r; | |
t2rtheta = d3.scale.linear().domain([lin_end, END]).range([-Math.PI / 2, -Math.PI / 2 + theta_max_r]); | |
t2rr = d3.scale.linear().domain([lin_end, END]).range([radius_r, 0]); | |
spiral_layout = function(d) { | |
if (d.t < lin_start) { | |
d.theta = t2ltheta(d.t); | |
d.r = t2lr(d.t); | |
d.x = -LIN_WIDTH / 2 + d.r * Math.cos(d.theta); | |
d.y = radius_l + d.r * Math.sin(d.theta); | |
return d; | |
} | |
if (d.t <= lin_end) { | |
d.x = t2l(d.t); | |
d.y = 0; | |
return d; | |
} | |
d.theta = t2rtheta(d.t); | |
d.r = t2rr(d.t); | |
d.x = +LIN_WIDTH / 2 + d.r * Math.cos(d.theta); | |
d.y = radius_r + d.r * Math.sin(d.theta); | |
return d; | |
}; | |
data = d3.range(0, 100, 1).concat([100]); | |
data = data.map(function(t) { | |
var d; | |
d = { | |
t: t | |
}; | |
if (t % 10 === 0) { | |
d.highlight = true; | |
} | |
if (t === 42) { | |
d.answer = true; | |
} | |
return d; | |
}); | |
line_generator = d3.svg.line().x(function(d) { | |
return d.x; | |
}).y(function(d) { | |
return d.y; | |
}).interpolate('linear'); | |
spiral = svg.append('path').datum(time.map(spiral_layout)).attr({ | |
"class": 'spiral', | |
d: line_generator | |
}); | |
svg.selectAll('.dot').data(data.map(spiral_layout)).enter().append('circle').attr({ | |
"class": 'dot', | |
cx: function(d) { | |
return d.x; | |
}, | |
cy: function(d) { | |
return d.y; | |
}, | |
r: function(d) { | |
if (d.highlight || d.answer) { | |
return 4; | |
} else { | |
return 2; | |
} | |
}, | |
fill: function(d) { | |
if (d.answer) { | |
return 'rgb(231, 41, 138)'; | |
} else { | |
return 'rgb(27, 158, 119)'; | |
} | |
} | |
}); | |
svg.append('circle').attr({ | |
"class": 'radius_indicator debug', | |
cx: -LIN_WIDTH / 2, | |
cy: radius_l, | |
r: radius_l | |
}); | |
svg.append('circle').attr({ | |
"class": 'radius_indicator debug', | |
cx: LIN_WIDTH / 2, | |
cy: radius_r, | |
r: radius_r | |
}); | |
}).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment