index.html
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>vis</title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="bootstrap.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div id="main" role="main">
<div id="vis"></div>
</div>
</div>
<script src="coffee-script.js"></script>
<script src="d3.js"></script>
<script src="queue.min.js"></script>
<script src="complex.js"></script>
<script type="text/coffeescript" src="vis.coffee"></script>
</body>
</html>
CustomTooltip.js
function CustomTooltip(tooltipId, width){
var tooltipId = tooltipId;
$("body").append("<div class='tooltip' id='"+tooltipId+"'></div>");
if(width){
$("#"+tooltipId).css("width", width);
}
hideTooltip();
function showTooltip(content, event){
$("#"+tooltipId).html(content);
$("#"+tooltipId).show();
updatePosition(event);
}
function hideTooltip(){
$("#"+tooltipId).hide();
}
function updatePosition(event){
var ttid = "#"+tooltipId;
var xOffset = 20;
var yOffset = 10;
var ttw = $(ttid).width();
var tth = $(ttid).height();
var wscrY = $(window).scrollTop();
var wscrX = $(window).scrollLeft();
var curX = (document.all) ? event.clientX + wscrX : event.pageX;
var curY = (document.all) ? event.clientY + wscrY : event.pageY;
var ttleft = ((curX - wscrX + xOffset*2 + ttw) > $(window).width()) ? curX - ttw - xOffset*2 : curX + xOffset;
if (ttleft < wscrX + xOffset){
ttleft = wscrX + xOffset;
}
var tttop = ((curY - wscrY + yOffset*2 + tth) > $(window).height()) ? curY - tth - yOffset*2 : curY + yOffset;
if (tttop < wscrY + yOffset){
tttop = curY + yOffset;
}
$(ttid).css('top', tttop + 'px').css('left', ttleft + 'px');
}
return {
showTooltip: showTooltip,
hideTooltip: hideTooltip,
updatePosition: updatePosition
}
}
queue.min.js
(function(){function n(n){function t(){for(;f=a<c.length&&n>p;){var u=a++,t=c[u],r=l.call(t,1);r.push(e(u)),++p,t[0].apply(null,r)}}function e(n){return function(u,l){--p,null==d&&(null!=u?(d=u,a=s=0/0,r()):(c[n]=l,--s?f||t():r()))}}function r(){null!=d?v(d):i?v(d,c):v.apply(null,[d].concat(c))}var o,f,i,c=[],a=0,p=0,s=0,d=null,v=u;return n||(n=1/0),o={defer:function(){return d||(c.push(arguments),++s,t()),o},await:function(n){return v=n,i=!1,s||r(),o},awaitAll:function(n){return v=n,i=!0,s||r(),o}}}function u(){}"undefined"==typeof module?self.queue=n:module.exports=n,n.version="1.0.4";var l=[].slice})();
style.css
body {
}
h1 {
font-size: 44px;
float:left;
clear:none;
display:inline;
width: 40%;
font-family: 'Open Sans Condensed', sans-serif;
text-transform:uppercase;
}
#vis {
min-height: 800px;
}
.active {
stroke: orange;
}
#text_select {
float:right;
margin-top: 10px;
}
.tooltip {
position: absolute;
top: 100px;
left: 100px;
-moz-border-radius:5px;
border-radius: 5px;
border: 2px solid #000;
background: #fff;
opacity: .9;
color: black;
padding: 10px;
width: 300px;
font-size: 12px;
z-index: 10;
}
.tooltip .title {
font-size: 18px;
}
.tooltip .name {
font-weight:bold;
}
#footer p {
text-align: center;
}
vis.coffee
root = exports ? this
Plot = () ->
width = 1040
height = 1490
data = []
lines = null
margin = {top: 5, right: 5, bottom: 5, left: 5}
xScale = d3.scale.linear().domain([0,10]).range([0,width])
yScale = d3.scale.linear().domain([0,10]).range([height,0])
xValue = (d) -> parseFloat(d.x)
yValue = (d) -> parseFloat(d.y)
mColor = "steelblue"
chart = (selection) ->
selection.each (rawData) ->
data = rawData
x1Extent = d3.extent(data, (d) -> d.x1)
x2Extent = d3.extent(data, (d) -> d.x2)
xScale.domain([Math.min(x1Extent[0],x2Extent[0]), Math.max(x1Extent[1], x2Extent[1])])
y1Extent = d3.extent(data, (d) -> d.y1)
y2Extent = d3.extent(data, (d) -> d.y2)
yScale.domain([Math.min(y1Extent[0],y2Extent[0]), Math.max(y1Extent[1], y2Extent[1])])
svg = d3.select(this).selectAll("svg").data([data])
gEnter = svg.enter().append("svg").append("g")
svg.attr("width", width + margin.left + margin.right )
svg.attr("height", height + margin.top + margin.bottom )
g = svg.select("g")
.attr("transform", "translate(#{margin.left},#{margin.top})")
lines = g.append("g").attr("id", "vis_points")
update()
update = () ->
lE = lines.selectAll(".line")
.data(data).enter()
.append("path")
.attr("class", "line")
.attr("stroke", mColor)
.attr("stroke-width", 5)
.attr("stroke-linecap", "round")
.attr('opacity', 0)
.attr("d", (d) -> "M#{xScale(d.x1)},#{yScale(d.y1)}L#{xScale(d.x2)},#{yScale(d.y2)}")
lE.transition()
.duration(1000)
.delay((d, i) -> i * 40 )
.attr('opacity', 1.0)
chart.height = (_) ->
if !arguments.length
return height
height = _
chart
chart.width = (_) ->
if !arguments.length
return width
width = _
chart
chart.margin = (_) ->
if !arguments.length
return margin
margin = _
chart
chart.x = (_) ->
if !arguments.length
return xValue
xValue = _
chart
chart.color = (_) ->
if !arguments.length
return mColor
mColor = _
chart
chart.y = (_) ->
if !arguments.length
return yValue
yValue = _
chart
return chart
root.Plot = Plot
root.plotData = (selector, data, plot) ->
d3.select(selector)
.datum(data)
.call(plot)
sentenceLengths = (text) ->
text = text.replace(/['\"\‘\’]/gm,"")
tregex = /\n|([^\r\n.!?]+([.!?]+|$))/gim
sentences = text.match(tregex).map((s) -> s.trim())
data = []
sentences.forEach (s) ->
d = {}
d.sentence = s
d.length = s.length
data.push(d)
data = data.filter (d) -> d.length > 3
data
findPositions = (data, lengthAttribute = "length", turn = -Math.PI / 2.0) ->
one = Complex(0,1)
currentTurn = turn
currentPos = Complex["0"]
currentX = 0
currentY = 0
data.forEach (d) ->
d[lengthAttribute] = +(d[lengthAttribute])
d.facing = (Math.PI / 2.0) + currentTurn
currentTurn += turn
mult = one.mult(Complex(d.facing,0))
mult = Complex(0,mult.i)
imgExp = Complex.exp(mult)
d.move = Complex(d[lengthAttribute],0).mult(imgExp)
currentPos = currentPos.add(d.move)
d.pos = currentPos
d.x2 = Math.round(d.pos.re)
d.y2 = Math.round(d.pos.i)
d.x1 = currentX
d.y1 = currentY
currentX = d.x2
currentY = d.y2
data
texts = {
'gatsby':{'title':'The Great Gatsby', 'file':'great_gatsby.txt', 'color':'#D1A145'}
'brave':{'title':'Brave New World', 'file':'brave_new_world.txt', 'color':'#70A4F2'}
'rye':{'title':'The Catcher in the Rye', 'file':'rye.txt', 'color':'#7B5749'}
'room':{'title':'A Room of One\'s Own', 'file':'room.txt', 'color':'#95B6E8'}
'farewell':{'title':'A Farewell to Arms', 'file':'farewell.txt', 'color':'#657782'}
'1984':{'title':'Nineteen Eighty-Four', 'file':'1984.txt', 'color':'#DF4C42'}
}
setupText = (text) ->
d3.select("#name").html(text.title)
id = '1984'
current = texts[id]
plot = Plot()
display = (error, text) ->
setupText(current)
plot.color(current.color)
data = sentenceLengths(text)
convertedData = findPositions(data)
plotData("#vis", convertedData, plot)
queue()
.defer(d3.text, "#{current.file}")
.await(display)