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/bd36fc43da4922454e2b to your computer and use it in GitHub Desktop.
Save nitaku/bd36fc43da4922454e2b to your computer and use it in GitHub Desktop.
Tape chart
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
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;
}
<!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>
(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