Skip to content

Instantly share code, notes, and snippets.

@dannyko
Last active September 14, 2015 13:01
Show Gist options
  • Save dannyko/649beb7bc989f1db1eb4 to your computer and use it in GitHub Desktop.
Save dannyko/649beb7bc989f1db1eb4 to your computer and use it in GitHub Desktop.
Inner-Product Visualization (2D)

Inner-product Visualization (2D)

This is a visualization of the inner (dot) product of two vectors in two-dimensional Euclidean space.

See this page on LinkedIn for a longer description.

It shows the geometric definition of the inner product of two vectors

(a, b) = |a| |b| cos θ

and how applying this geometric definition to the orthogonal expansion of the vectors provides a derivation of the equivalent Cartesian coordinates formula

(a, b) = a(1) b(1) + a(2) b(2).

In other words,

(a, b)
= (ax + ay, bx + by) 
= (ax, bx) + (ax, by) + (ay, bx) + (ay, by)
= |ax| |bx| cos 0 + |ax| |by| cos pi/2 + |ay| |bx| cos pi/2 + |ay| |by| cos 0
= |ax| |bx| + |ay| |by|
= a(1) b(1) + a(2) b(2).

By virtue of the orthogonality of the basis vectors, all of the cosine factors reduce to zeros and ones, leaving only the x- and y- coordinates of the vectors. This is the essential idea underlying the "sum of products of coordinates" formula --- the coordinates represent the lengths of the projections of the vector onto some complete orthogonal basis set of vectors.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.js"></script>
<style>
body {
font: 13pt courier;
font-weight: bold;
background-color: #111;
overflow-y:hidden;
color: #ccc;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.red_line {
fill: none;
stroke: crimson;
stroke-width: 2px;
stroke-linecap: round;
opacity: 0.8;
}
.blue {
fill: #66C;
opacity:0.8;
}
.red {
fill: crimson;
opacity:0.8;
}
.blue_line {
fill: none;
stroke: #66C;
stroke-width: 2px;
stroke-linecap: round;
opacity: 0.8;
}
.yellow {
fill: #CC4 ;
opacity:0.8;
}
</style>
</head>
<body>
<script type="text/javascript">
var width = 320 ;
var height = 320 ;
var dom = 4 ;
var maxL = dom * 2 / 5 ;
var minL = 0 ;
var maxL2 = maxL * maxL ;
var minL2 = minL * minL ;
var markHeight = 4 ;
var pad = .45 ;
var x = d3.scale.linear()
.domain([-dom, dom])
.range([0, width]) ;
var y = d3.scale.linear()
.domain([-dom, dom])
.range([height, 0]) ;
var invMark = x.invert(markHeight) - x.invert(0) ;
var scale = 1 - invMark ;
maxL -= invMark ;
minL += invMark ;
var line = d3.svg.line()
.interpolate('basis')
.x(function(d) { return x(d.x) ; })
.y(function(d) { return y(d.y) ; }) ;
var svg = d3
.select("body")
.append("svg")
.attr("viewBox", '0 0 ' + 2 * width + ' ' + 2 * height) ;
// .attr("preserveAspectRatio", "xMidYMin meet") ;
// .style('max-height', '90%')
// .style('min-height', '600px') ;
svg
.append('defs')
.append('marker')
.attr('id', 'redMarker')
.attr('orient', 'auto')
.attr('markerWidth', 2)
.attr('markerHeight', markHeight)
.attr('refX', 0.1)
.attr('refY', 2)
.append('path')
.attr('d', 'M0,0 V4 L2,2 Z')
.attr('fill', 'crimson') ;
svg
.append('marker')
.attr('id', 'blueMarker')
.attr('orient', 'auto')
.attr('markerWidth', 2)
.attr('markerHeight', markHeight)
.attr('refX', 0.1)
.attr('refY', 2)
.append('path')
.attr('d', 'M0,0 V4 L2,2 Z')
.attr('fill', '#66C') ;
var g = svg
.append("g")
.style('opacity', 0) ;
//.attr("transform", "rotate(-60, 160, 160)")
var gs = g.append('g') ; // more-or-less static stuff that is not part of the animation
var unitCircle = gs
.append('circle')
.attr('fill', '#FFF')
.attr('opacity', .05)
.attr('r', x(1) - x(0))
.attr('cx', x(0))
.attr('cy', y(0))
var ga = g.append('g') ;
var redLine = ga
.append('path')
.style('pointer-events', 'none')
.attr('class', 'red_line')
.attr('marker-end', 'url(#redMarker)') ;
var gb = g.append('g') ;
var blueLine = gb
.append('path')
.style('pointer-events', 'none')
.attr('class', 'blue_line')
.attr('marker-end', 'url(#blueMarker)') ;
var root2 = Math.sqrt(2) ;
var r2i = 1 / root2 ;
var t0 = Math.PI / 2 - 0.1 - 0.5 * Math.random() ;
var t1 = 0.2 + 0.3 * (Math.random() - 0.5) ;
var l1 = 0.6 * maxL + (0.4 * Math.random() * maxL) ;
var l2 = 0.6 * maxL + (0.4 * Math.random() * maxL) ;
var da = [{x: 0, y: 0}, {x: Math.cos(t0) * scale * l1, y: Math.sin(t0) * scale * l1}] ;
var db = [{x: 0, y: 0}, {x: Math.cos(t1) * scale * l2, y: Math.sin(t1) * scale * l2}] ;
var dur = 710 ;
var longDelay = 2 * dur ;
redLine
.datum(da)
.attr("d", line)
blueLine
.datum(db)
.attr("d", line)
//var dragSwitch = true ;
var length = function(d) {
return Math.sqrt(d.x * d.x + d.y * d.y) ;
}
var circHover = function(node) {
//dragSwitch = true ;
d3.select(node)
.transition()
.ease('linear')
.duration(dur * 0.5)
.style('opacity', 0.3) ;
} ;
var circOut = function(node) {
//dragSwitch = false ;
d3.select(node)
.transition()
.ease('linear')
.duration(dur * 0.5)
.style('opacity', 0) ;
} ;
var unitSwitch = false ;
var set_unit = function(animateSwitch) {
if(animateSwitch === undefined) animateSwitch = false ;
var xk, yk, l ;
xk = da[1].x ;
yk = da[1].y ;
l = Math.sqrt(xk * xk + yk * yk) / scale ;
da[1].x /= l ;
da[1].y /= l ;
xk = db[1].x ;
yk = db[1].y ;
l = Math.sqrt(xk * xk + yk * yk) / scale ;
db[1].x /= l ;
db[1].y /= l ;
if(animateSwitch) {
redLine
.transition()
.ease('linear')
.duration(dur * 0.5)
.attr('d', line)
blueLine
.transition()
.ease('linear')
.duration(dur * 0.5)
.attr('d', line) ;
redCircle
.transition()
.ease('linear')
.duration(dur * 0.5)
.attr('cx', x(da[1].x))
.attr('cy', y(da[1].y)) ;
blueCircle
.transition()
.ease('linear')
.duration(dur * 0.5)
.attr('cx', x(db[1].x))
.attr('cy', y(db[1].y))
.each('end', vecText) ;
aText
.transition()
.ease('linear')
.duration(dur * 0.5)
.attr('x', x(da[1].x + pad * da[1].x / length(da[1])))
.attr('y', y(da[1].y + pad * da[1].y / length(da[1]))) ;
bText
.transition()
.ease('linear')
.duration(dur * 0.5)
.attr('x', x(db[1].x + pad * db[1].x / length(db[1])))
.attr('y', y(db[1].y + pad * db[1].y / length(db[1]))) ;
} else {
redLine.attr('d', line) ;
blueLine.attr('d', line) ;
redCircle
.attr('cx', x(da[1].x))
.attr('cy', y(da[1].y)) ;
blueCircle
.attr('cx', x(db[1].x))
.attr('cy', y(db[1].y)) ;
aText
.attr('x', x(da[1].x + pad * da[1].x / length(da[1])))
.attr('y', y(da[1].y + pad * da[1].y / length(da[1]))) ;
bText
.attr('x', x(db[1].x + pad * db[1].x / length(db[1])))
.attr('y', y(db[1].y + pad * db[1].y / length(db[1]))) ;
}
} ;
//
// arc * angle:
//
var angle = function() {
var angA = angl(da) ;
var angB = angl(db) ;
if(Math.abs(angA - angB) > Math.PI) {
if(angA < 0 && angB > 0) {
angA += 2 * Math.PI ;
} else if(angA > 0 && angB < 0) {
angB += 2 * Math.PI ;
} else if(angA > 0 && angB > 0) {
if(angA > Math.PI) {
angA -= 2 * Math.PI ;
} else {
angB -= 2 * Math.PI ;
}
} else {
if(angA < -Math.PI) {
angA += 2 * Math.PI ;
} else {
angB += 2 * Math.PI ;
}
}
}
return [angA, angB] ;
} ;
var angl = function(d) {
return (Math.PI / 2 - Math.atan2(d[1].y, d[1].x)) ;
} ;
var ang = angle() ;
var arc = d3.svg.arc()
.innerRadius(8)
.outerRadius(10)
.startAngle(ang[0])
.endAngle(ang[1]) ;
var arcLine = gs
.insert("path", ":first-child")
.attr("d", arc)
.attr('class', 'yellow')
.attr("transform", 'translate(' + width / 2 + ',' + height / 2 + ')') ;
var get_mid = function() {
var lenA = Math.sqrt(da[1].x * da[1].x + da[1].y * da[1].y) ;
var lenB = Math.sqrt(db[1].x * db[1].x + db[1].y * db[1].y) ;
var xMid = 0.5 * (da[1].x / lenA + db[1].x / lenB) ;
var yMid = 0.5 * (da[1].y / lenA + db[1].y / lenB) ;
var mLen = Math.sqrt(xMid * xMid + yMid * yMid) ;
var mscale = .5 / mLen ;
xMid *= mscale ;
yMid *= mscale ;
return [xMid, yMid] ;
}
var mid = get_mid() ;
var thetaText = gs
.append('text')
.attr('x', x(mid[0]))
.attr('y', y(mid[1]))
.text('θ') //
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '6pt')
.attr('class', 'yellow') ;
//
// ******************
// ****** drag ******
// ******************
//
var circDrag = function(d, element) {
//var xy0 = d3.mouse(g.node()) ;
//if(dragSwitch === false) {
// return ;
//}
var dx = d3.event.dx ;
var dy = d3.event.dy ;
var dd = d.datum() ;
var x1 = dd[1].x ;
var y1 = dd[1].y ;
var xi = x(x1) + dx ;
var yi = y(y1) + dy ;
x1 = x.invert(xi) ;
y1 = y.invert(yi) ;
var l2 = x1 * x1 + y1 * y1 ;
var len = Math.sqrt(l2) ;
if(l2 > maxL2) {
var li = 1 / len ;
x1 = x1 * li * maxL ;
y1 = y1 * li * maxL ;
len = maxL ;
}
if(l2 < minL2) {
var li = 1 / len ;
x1 = x1 * li * minL ;
y1 = y1 * li * minL ;
len = minL ;
}
d3.select(element)
.attr('cx', x(x1))
.attr('cy', y(y1)) ;
dd[1].x = x1 ;
dd[1].y = y1 ;
if(unitSwitch) {
set_unit() ;
} else {
d.attr('d', line) ; // console.log('drag', element, xy0) ;
}
var ang = angle() ;
arc
.startAngle(ang[0])
.endAngle(ang[1]) ;
arcLine.attr('d', arc)
mid = get_mid() ;
thetaText
.attr('x', x(mid[0]))
.attr('y', y(mid[1])) ;
aText
.attr('x', x(da[1].x + pad * da[1].x / length(da[1])))
.attr('y', y(da[1].y + pad * da[1].y / length(da[1]))) ;
bText
.attr('x', x(db[1].x + pad * db[1].x / length(db[1])))
.attr('y', y(db[1].y + pad * db[1].y / length(db[1]))) ;
vecText() ;
var dax = [{x: 0, y: 0}, {x: da[1].x, y: 0}] ;
var day = [{x: 0, y: 0}, {x: 0, y: da[1].y}] ;
var dbx = [{x: 0, y: 0}, {x: db[1].x, y: 0}] ;
var dby = [{x: 0, y: 0}, {x: 0, y: db[1].y}] ;
ax.datum(dax) ;
ay.datum(day) ;
bx.datum(dbx) ;
by.datum(dby) ;
ax.attr('d', line) ;
ay.attr('d', line) ;
bx.attr('d', line) ;
by.attr('d', line) ;
axText
.attr('x', x(dax[1].x + 0.5 * pad * dax[1].x / length(dax[1])) + (shift * Math.min(0, dax[1].x / Math.abs(dax[1].x))))
.attr('y', y(dax[1].y + 0.5 * pad * dax[1].y / length(dax[1]))) ;
ayText
.attr('x', x(day[1].x + 0.5 * pad * day[1].x / length(day[1])))
.attr('y', y(day[1].y + 0.5 * pad * day[1].y / length(day[1])) - (shift * Math.min(0, day[1].y / Math.abs(day[1].y)))) ;
bxText
.attr('x', x(dbx[1].x + 0.5 * pad * dbx[1].x / length(dbx[1])) + (shift * Math.min(0, dbx[1].x / Math.abs(dbx[1].x))))
.attr('y', y(dbx[1].y + 0.5 * pad * dbx[1].y / length(dbx[1]))) ;
byText
.attr('x', x(dby[1].x + 0.5 * pad * dby[1].x / length(dby[1])))
.attr('y', y(dby[1].y + 0.5 * pad * dby[1].y / length(dby[1])) - (shift * Math.min(0, dby[1].y / Math.abs(dby[1].y)))) ;
} ;
//
// **************************
// ****** primary text ******
// **************************
//
var aText = ga
.append('text')
.text('a')
.attr('x', x(da[1].x + pad * da[1].x / length(da[1])))
.attr('y', y(da[1].y + pad * da[1].y / length(da[1])))
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '12pt')
.attr('class', 'red') ;
var bText = gb
.append('text')
.text('b')
.attr('x', x(db[1].x + pad * db[1].x / length(db[1])))
.attr('y', y(db[1].y + pad * db[1].y / length(db[1])))
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '12pt')
.attr('class', 'blue') ;
var redCircle = ga
.insert('circle', ":first-child")
.datum(redLine)
.attr('fill', '#0C0')
.attr('opacity', 0)
.attr('r', 20)
.attr('cx', x(da[1].x))
.attr('cy', y(da[1].y))
.on('mouseup', function() { circOut(this) })
.on('mousedown', function() { circHover(this) })
.on('mouseleave', function() { circOut(this) })
.on('mouseenter', function() { circHover(this) }) ;
var blueCircle = gb
.insert('circle', ":first-child")
.datum(blueLine)
.attr('fill', '#0C0')
.attr('opacity', 0)
.attr('r', 20)
.attr('cx', x(db[1].x))
.attr('cy', y(db[1].y))
.on('mouseup', function() { circOut(this) })
.on('mousedown', function() { circHover(this) })
.on('mouseleave', function() { circOut(this) })
.on('mouseenter', function() { circHover(this) }) ;
blueCircle
.call(d3.behavior.drag()
.origin(Object)
.on("dragstart", function(d) {
circHover(blueCircle.node()) ;
blueCircle
.on('mouseup', null)
.on('mousedown', null)
.on('mouseleave', null)
.on('mouseenter', null) ;
})
.on("drag", function(d) { circDrag(d, this) } )
.on("dragend", function(d) {
circOut(blueCircle.node())
blueCircle
.on('mouseup', function() { circOut(this) })
.on('mousedown', function() { circHover(this) })
.on('mouseleave', function() { circOut(this) })
.on('mouseenter', function() { circHover(this) }) ;
})
) ;
redCircle
.call(d3.behavior.drag()
.origin(Object)
.on("dragstart", function(d) {
circHover(redCircle.node()) ;
redCircle
.on('mouseup', null)
.on('mousedown', null)
.on('mouseleave', null)
.on('mouseenter', null) ;
})
.on("drag", function(d) { circDrag(d, this) } )
.on("dragend", function(d) {
circOut(redCircle.node())
redCircle
.on('mouseup', function() { circOut(this) })
.on('mousedown', function() { circHover(this) })
.on('mouseleave', function() { circOut(this) })
.on('mouseenter', function() { circHover(this) }) ;
})
) ;
var unitBox = gs
.append('rect') ;
var unitToggle = function() {
var fill ;
if(unitSwitch) {
unitSwitch = false ;
fill = '#111' ;
} else {
unitSwitch = true ;
fill = '#999' ;
var animateSwitch = true ;
set_unit(animateSwitch) ;
}
unitBox
.transition()
.duration(100)
.ease('linear')
.attr('fill', fill) ;
} ;
unitBox
.attr('x', 20)
.attr('y', 10)
.attr('rx', 2)
.attr('ry', 2)
.attr('fill', '#111')
.attr('width', 10)
.attr('height', 10)
.attr('stroke', '#CCC')
.attr('stroke-width', 1.5)
.style('cursor', 'pointer')
.on('click', unitToggle) ;
var unitText = gs
.append('text') ;
unitText
.attr('x', 34)
.attr('y', 17)
.text('unit length')
.style('font', 'courier')
.style('font-size', '6pt')
.style('fill', '#ccc')
.style('cursor', 'pointer')
.on('click', unitToggle) ;
var dotDef = gs
.append('text')
.attr('x', 120)
.attr('y', 42)
.style('font', 'courier')
.style('font-size', '6pt')
.style('fill', '#ccc') ;
var boxy = d3.scale.linear()
.domain([-maxL * maxL, maxL * maxL])
.range([-200 / height, 200 / height]) ;
var boxc = d3.scale.linear()
.domain([-maxL * maxL, 0, maxL * maxL])
.range([d3.rgb('#95F'), d3.rgb('#959'), d3.rgb('#F59')]) ;
var dotbarX = 103 ;
var dotBar = gs.append('path')
.datum([{x: x.invert(dotbarX), y: y.invert(42)}, {x: x.invert(dotbarX), y: y.invert(42) + boxy(0)}])
.attr('d', line)
.attr('stroke-width', 10)
.attr('stroke', boxc(1)) ;
var unscale = function(d) {
var len = length(d) ;
len = (len + invMark) / len ;
return [d.x * len, d.y * len] ;
} ;
var aVec = gs
.append('text')
.attr('x', 120)
.attr('y', 10)
.style('font', 'courier')
.style('font-size', '6pt')
.style('fill', '#ccc') ;
var aLen = gs
.append('text')
.attr('x', 240)
.attr('y', 10)
.style('font', 'courier')
.style('font-size', '6pt')
.style('fill', '#ccc') ;
var bVec = gs
.append('text')
.attr('x', 120)
.attr('y', 20)
.style('font', 'courier')
.style('font-size', '6pt')
.style('fill', '#ccc') ;
var bLen = gs
.append('text')
.attr('x', 240)
.attr('y', 20)
.style('font', 'courier')
.style('font-size', '6pt')
.style('fill', '#ccc') ;
var thetaVal = gs
.append('text')
.attr('x', 120)
.attr('y', 30)
.style('font', 'courier')
.style('font-size', '6pt')
.style('fill', '#ccc') ;
var cosVal = gs
.append('text')
.attr('x', 240)
.attr('y', 30)
.style('font', 'courier')
.style('font-size', '6pt')
.style('fill', '#ccc') ;
var vecText = function() {
var a = unscale(da[1]) ;
var b = unscale(db[1]) ;
var dot = a[0] * b[0] + a[1] * b[1] ;
aVec.html('a = (' + a[0].toPrecision(3) + ', ' + a[1].toPrecision(3) + ')') ;
bVec.html('b = (' + b[0].toPrecision(3) + ', ' + b[1].toPrecision(3) + ')') ;
aLen.html('|a| &nbsp;&nbsp;= ' + Math.sqrt(a[0] * a[0] + a[1] * a[1]).toPrecision(4)) ;
bLen.html('|b| &nbsp;&nbsp;= ' + Math.sqrt(b[0] * b[0] + b[1] * b[1]).toPrecision(4)) ;
var ang = angle() ;
thetaVal.html('θ = ' + Math.abs(ang[0] - ang[1]).toPrecision(3)) ;
cosVal.html('cos θ = ' + Math.cos(Math.abs(ang[0] - ang[1])).toPrecision(3)) ;
dotDef.html('&#10216;a, b&#10217; = |a| |b| cos θ = a1 b1 + a2 b2 = ' + dot.toPrecision(3)) ;
dotBar
.datum([{x: x.invert(dotbarX), y: y.invert(42)}, {x: x.invert(dotbarX), y: y.invert(42) + boxy(dot)}])
.attr('stroke', boxc(dot))
.attr('d', line)
} ;
vecText() ;
//
// ****************************
// ****** secondary text ******
// ****************************
//
var atx = Number(aText.attr('x')) ; // + (txtShift * Number(aText.attr('x')) / Math.abs(Number(aText.attr('x')))) ;
var aty = Number(aText.attr('y')) ; // + (txtShift * Number(aText.attr('y')) / Math.abs(Number(aText.attr('y')))) ;
var btx = Number(bText.attr('x')) ; // + (txtShift * Number(bText.attr('x')) / Math.abs(Number(bText.attr('x')))) ;
var bty = Number(bText.attr('y')) ; // + (txtShift * Number(bText.attr('y')) / Math.abs(Number(bText.attr('y')))) ;
var yCut = 20 ;
var dya = Math.abs(x(da[1].y) - x(0)) ;
var dyb = Math.abs(x(db[1].y) - x(0)) ;
if(dya < yCut) {
aty += yCut * dya / Math.abs(dya) ;
}
if(dyb < yCut) {
bty += yCut * dyb / Math.abs(dyb) ;
}
ga1 = ga
.append('g')
.style('opacity', 0) ;
gb1 = gb
.append('g')
.style('opacity', 0) ;
gax = ga1.append('g') ;
gay = ga1.append('g') ;
gbx = gb1.append('g') ;
gby = gb1.append('g') ;
var ax = gax
.append('path')
.attr('class', 'red_line')
.attr('marker-end', 'url(#redMarker)') ;
var ay = gay
.append('path')
.attr('class', 'red_line')
.attr('marker-end', 'url(#redMarker)') ;
var bx = gbx
.append('path')
.attr('class', 'blue_line')
.attr('marker-end', 'url(#blueMarker)') ;
var by = gby
.append('path')
.attr('class', 'blue_line')
.attr('marker-end', 'url(#blueMarker)') ;
var dax = [{x: 0, y: 0}, {x: da[1].x, y: 0}] ;
var day = [{x: 0, y: 0}, {x: 0, y: da[1].y}] ;
var dbx = [{x: 0, y: 0}, {x: db[1].x, y: 0}] ;
var dby = [{x: 0, y: 0}, {x: 0, y: db[1].y}] ;
ax.datum(dax) ;
ay.datum(day) ;
bx.datum(dbx) ;
by.datum(dby) ;
ax.attr('d', line) ;
ay.attr('d', line) ;
bx.attr('d', line) ;
by.attr('d', line) ;
var shift = 15 ;
var axText = gax
.append('text')
.html('ax')
.attr('x', x(dax[1].x + 0.5 * pad * dax[1].x / length(dax[1])) + (shift * Math.min(0, dax[1].x / Math.abs(dax[1].x))))
.attr('y', y(dax[1].y + 0.5 * pad * dax[1].y / length(dax[1])))
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '10pt')
.attr('class', 'red')
.style('opacity', 0) ;
var ayText = gay
.append('text')
.html('ay')
.attr('x', x(day[1].x + 0.5 * pad * day[1].x / length(day[1])))
.attr('y', y(day[1].y + 0.5 * pad * day[1].y / length(day[1])) - (shift * Math.min(0, day[1].y / Math.abs(day[1].y))))
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '10pt')
.attr('class', 'red')
.style('opacity', 0) ;
var bxText = gbx
.append('text')
.html('bx')
.attr('x', x(dbx[1].x + 0.5 * pad * dbx[1].x / length(dbx[1])) + (shift * Math.min(0, dbx[1].x / Math.abs(dbx[1].x))))
.attr('y', y(dbx[1].y + 0.5 * pad * dbx[1].y / length(dbx[1])))
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '10pt')
.attr('class', 'blue')
.style('opacity', 0) ;
var byText = gby
.append('text')
.html('by')
.attr('x', x(dby[1].x + 0.5 * pad * dby[1].x / length(dby[1])))
.attr('y', y(dby[1].y + 0.5 * pad * dby[1].y / length(dby[1])) - (shift * Math.min(0, dby[1].y / Math.abs(dby[1].y))))
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '10pt')
.attr('class', 'blue')
.style('opacity', 0) ;
g
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1) ;
//
// ***********************
// ****** animation ******
// ***********************
//
var animating = false ;
var yShift = 60 ;
var xShifta = -width * 0.15 ;
var xShiftb = width * 0.65 ;
var ga1 ;
var gb1 ;
var gax ;
var gay ;
var gbx ;
var gby ;
var gdt ;
var gc = g
.append('g')
.style('opacity', 0) ;
var gn = gc
.append('g')
.style('pointer-events', 'all')
.style('opacity', 0) ;
var animate = function() {
if(animating) return ; // don't respond to animation requests until previous one has completed (one at a time)
animating = true ;
gs
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 0)
.each('end', function() {
goText
.transition()
.duration(dur)
.style('opacity', 0)
.transition()
.delay(dur)
.duration(dur)
.text('back')
.style('opacity', 1) ;
goBox
.on('click', null)
.on('click', home) ;
}) ;
//ga.style('opacity', 0) ;
//gb.style('opacity', 0) ;
ga1
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1) ;
gb1
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1) ;
gc
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1) ;
aText
.html('a = ax + ay')
.transition()
.duration(longDelay)
.ease('linear')
.style('opacity', 0.4)
.style('font-size', '6pt') ;
bText
.html('b = bx + by')
.transition()
.duration(longDelay)
.ease('linear')
.style('opacity', 0.4)
.style('font-size', '6pt') ;
byText
.transition()
.duration(dur)
.delay(longDelay)
.ease('linear')
.style('opacity', 1) ;
bxText
.transition()
.duration(dur)
.delay(longDelay)
.ease('linear')
.style('opacity', 1) ;
ayText
.transition()
.duration(dur)
.delay(longDelay)
.ease('linear')
.style('opacity', 1) ;
axText
.transition()
.duration(dur)
.delay(longDelay)
.ease('linear')
.style('opacity', 1) ;
redLine
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 0.4) ;
blueLine
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 0.4) ;
// var txtShift = -40 ;
var gScale = 1 ;
g
.transition()
.transition()
.duration(dur)
.ease('linear')
.attr('transform', 'translate(' + width/4 + ',' + height/4 + ')scale(' + gScale + ')translate(' + -width/4 + ',' + -height/4 + ')') ;
ga
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1)
.attr('transform', 'translate(' + xShifta + ',' + yShift + ')translate(' + width/4 + ',' + height/4 + ')scale(1)translate(' + -width/4 + ',' + -height/4 + ')') ;
gb
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1)
.attr('transform', 'translate(' + xShiftb + ',' + yShift + ')translate(' + width/4 + ',' + height/4 + ')scale(1)translate(' + -width/4 + ',' + -height/4 + ')') ;
var xgc = 60 ;
var dTxt = gc
.append('text')
.attr('x', xgc + 20)
.attr('y', 35)
.html('&#10216;a, b&#10217; =')
.style('fill', '#999')
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '10pt') ;
var dotTxt = gc
.append('text')
.attr('x', xgc + 90)
.attr('y', 35)
.html('&#10216;ax + ay, bx + by&#10217; ')
.style('fill', '#999')
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '10pt') ;
gc
.transition()
.delay(dur)
.duration(2 * dur)
.ease('linear')
.style('opacity', 1) ;
gdt = gc
.append('g')
.style('opacity', 0) ;
var anim2 = function() {
nextButton.on('click', null) ;
fade_out(gn) ;
dotTxt
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 0)
.remove()
.each('end', function() {
dotTxt = [] ;
dTxt = [['x', 'x'], ['x', 'y'], ['y', 'x'], ['y', 'y']] ;
tTxt = ['0', 'π/2', 'π/2', '0'] ;
for(var k = 0 ; k < 4 ; k++) {
dotTxt[k] = gdt
.append('g')
.style('pointer-events', 'all') ;
dotTxt[k]
.append('text')
.attr('x', xgc + 90 + k * 85)
.attr('y', 35)
.style('fill', '#999')
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '10pt') ;
dotTxt[k]
.append('rect')
.attr('x', xgc + 90 + k * 85)
.attr('y', 20)
.attr('width', 60)
.attr('height', 25)
.attr('fill', '#FFF')
.attr('opacity', '.01') ;
var sk = '&#10216;a' + dTxt[k][0] + ', b' + dTxt[k][1] + '&#10217;' ;
if(k < 3) {
gdt
.append('text')
.attr('x', xgc + 159 + k * 85)
.attr('y', 35)
.style('fill', '#999')
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '10pt')
.text('+') ;
}
dotTxt[k].select('text').html(sk) ;
}
dotTxt[0]
.on('mouseenter', function() {
dotTxt[0]
.select('text')
.transition()
.duration(dur)
.ease('linear')
.style('fill', '#FF9') ;
arrows_up(gax, gbx) ;
var k = 0 ;
animTxt
.html('|a' + dTxt[k][0] + '| |b' + dTxt[k][1] + '| cos ' + tTxt[k] + ' = a1 b1')
.transition()
.duration(0.5 * dur)
.ease('linear')
.style('opacity', 1) ;
})
.on('mouseleave', function() {
dotTxt[0]
.select('text')
.transition()
.duration(dur)
.ease('linear')
.style('fill', '#999') ;
arrows_down(gax, gbx) ;
fade_out(animTxt) ;
})
dotTxt[1]
.on('mouseenter', function() {
dotTxt[1]
.select('text')
.transition()
.duration(dur)
.ease('linear')
.style('fill', '#FF9') ;
arrows_up(gax, gby)
var k = 1 ;
animTxt.html('|a' + dTxt[k][0] + '| |b' + dTxt[k][1] + '| cos ' + tTxt[k] + ' = 0')
.transition()
.duration(0.5 * dur)
.ease('linear')
.style('opacity', 1) ;
})
.on('mouseleave', function() {
dotTxt[1]
.select('text')
.transition()
.duration(dur)
.ease('linear')
.style('fill', '#999') ;
arrows_down(gax, gby)
fade_out(animTxt) ;
})
dotTxt[2]
.on('mouseenter', function() {
dotTxt[2]
.select('text')
.transition()
.duration(dur)
.ease('linear')
.style('fill', '#FF9') ;
arrows_up(gay, gbx)
var k = 2 ;
animTxt.html('|a' + dTxt[k][0] + '| |b' + dTxt[k][1] + '| cos ' + tTxt[k] + ' = 0')
.transition()
.duration(0.5 * dur)
.ease('linear')
.style('opacity', 1) ;
})
.on('mouseleave', function() {
dotTxt[2]
.select('text')
.transition()
.duration(dur)
.ease('linear')
.style('fill', '#999') ;
arrows_down(gay, gbx)
fade_out(animTxt) ;
})
dotTxt[3]
.on('mouseenter', function() {
dotTxt[3]
.select('text')
.transition()
.duration(dur)
.ease('linear')
.style('fill', '#FF9') ;
arrows_up(gay, gby)
var k = 3 ;
animTxt
.html('|a' + dTxt[k][0] + '| |b' + dTxt[k][1] + '| cos ' + tTxt[k] + ' = a2 b2')
.transition()
.duration(0.5 * dur)
.ease('linear')
.style('opacity', 1) ;
})
.on('mouseleave', function() {
dotTxt[3]
.select('text')
.transition()
.duration(dur)
.ease('linear')
.style('fill', '#999') ;
arrows_down(gay, gby)
fade_out(animTxt) ;
})
var animTxt = gdt
.append('text')
.attr('x', 160)
.attr('y', 80)
.style('opacity', 1)
.style('fill', '#FF9')
.style('pointer-events', 'none')
.style('font', 'courier')
.style('font-size', '10pt') ;
var pause = 4 * dur ;
animTxt.text('mouse over the terms above to animate')
gdt
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1) ;
}) ;
} ;
var nextButton = gn
.append('rect')
.attr('x', 10)
.attr('y', 46)
.attr('rx', 2)
.attr('ry', 2)
.attr('width', 46)
.attr('height', 15)
.attr('fill', '#CCC')
.attr('stroke', 'none')
.style('cursor', 'pointer')
.on('click', anim2) ;
var nextTxt = gn
.append('text')
.attr('x', 18.5)
.attr('y', 56.5)
.text('next')
.style('font', 'courier')
.style('font-size', '7pt')
.style('fill', '#111')
.style('pointer-events', 'none') ;
gn
.transition()
.delay(longDelay)
.duration(dur)
.ease('linear')
.style('opacity', 1) ;
} ;
var fade_out = function(element) {
element
.transition()
.duration(0.5 * dur)
.ease('linear')
.style('opacity', 0) ;
}
var home = function() {
redLine
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1) ;
blueLine
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1) ;
aText
.html('a')
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1)
.style('font-size', '12pt')
bText
.html('b')
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 1)
.style('font-size', '12pt')
ga1
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 0) ;
gb1
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 0) ;
gc
.transition()
.duration(dur)
.ease('linear')
.style('opacity', 0)
.each('end', function() {
gdt.remove() ;
}) ;
ga
.transition()
.delay(dur)
.duration(dur)
.ease('linear')
.attr('transform', 'translate(0,0)scale(1)translate(0,0)') ;
gb
.transition()
.delay(dur)
.duration(dur)
.ease('linear')
.attr('transform', 'translate(0,0)scale(1)translate(0,0)') ;
g
.transition()
.delay(dur)
.duration(dur)
.ease('linear')
.attr('transform', 'translate(0,0)scale(1)translate(0,0)') ;
gs
.transition()
.delay(2 * dur)
.duration(dur)
.ease('linear')
.style('opacity', 1)
.each('end', function() {
axText.style('opacity', 0) ;
bxText.style('opacity', 0) ;
ayText.style('opacity', 0) ;
byText.style('opacity', 0) ;
gn.style('opacity', 0) ;
goText
.transition()
.duration(dur)
.style('opacity', 0)
.transition()
.delay(dur)
.duration(0.5 * dur)
.text('start')
.style('opacity', 1) ;
goBox
.on('click', null)
.on('click', animate) ;
}) ;
animating = false ;
}
var yShift2 = -0.8 * yShift ;
var xShift = 0.5 * (Math.abs(xShifta) + Math.abs(xShiftb)) ;
var xShift1 = xShift ;
var xShift2 = -xShift ;
var arrows_up = function(a, b) {
a
.transition()
.delay(.2 * dur)
.duration(dur)
.ease('linear')
.attr('transform', 'translate(' + xShift1 + ',' + yShift2 + ')') ;
b
.transition()
.delay(.2 * dur)
.duration(dur)
.ease('linear')
.attr('transform', 'translate(' + xShift2 + ',' + yShift2 + ')') ;
} ;
var arrows_down = function(a, b) {
a
.transition()
.delay(.2 * dur)
.duration(dur)
.ease('linear')
.attr('transform', 'translate(0,0)') ;
b
.transition()
.delay(.2 * dur)
.duration(dur)
.ease('linear')
.attr('transform', 'translate(0,0)') ;
} ;
var gbox = g.append('g') ;
var goBox = gbox.append('rect')
.attr('x', 10)
.attr('y', 29)
.attr('rx', 2)
.attr('ry', 2)
.attr('fill', '#CCC')
.attr('width', 46)
.attr('height', 15)
.attr('stroke', 'none')
.style('cursor', 'pointer')
.on('click', animate) ;
var goText = gbox
.append('text') ;
goText
.attr('x', 19)
.attr('y', 39.5)
.text('start')
.style('font', 'courier')
.style('font-size', '7pt')
.style('fill', '#111')
.style('pointer-events', 'none') ;
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment