A work in progress. A force-directed graph using transforms to move nodes, point-along-path interpolation to move nodes in ellipses, etc.
Last active
October 8, 2021 15:27
-
-
Save kenpenn/9476266 to your computer and use it in GitHub Desktop.
d3 gangnam style
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
/* d3 gangnam style! | |
* simplifed test case | |
* moving force-directed graph nodes via transform | |
* coded by Ken Penn | |
*/ | |
(function () { | |
window.reqAniFrame = function(win, t) { | |
return win["r" + t] || win["webkitR" + t] || win["mozR" + t] || win["msR" + t] || function(fn) { setTimeout(fn, 60) } | |
} (window, "equestAnimationFrame"); | |
var gs = { | |
svgBox : d3.select('.psy-svg-box'), | |
svg : d3.select('.psy-svg-box svg'), | |
height : 0, | |
ctrY : 0, | |
baseY : 806, // maximized viewport height on my 15" macbook | |
adjY : 0, | |
width : 0, | |
ctrX : 0, | |
baseX : 1392, // maximized viewport width on my 15" macbook | |
adjX : 0, | |
smallest : 0, | |
nodes : '', | |
links : '', | |
figs : [], | |
fignewt : 0, | |
trace : true, | |
// bpm : 60000 / 64, | |
bpm : 60000 / 128, // 128 bpm, ~469 ms | |
init : function () { | |
var fig = {}; | |
// create a stick figure, start the graph | |
fig = gs.crtGrp(gs.figs.length); | |
setTimeout(function () { gs.standUp(fig); }, gs.bpm * 4); | |
setTimeout(function () { gs.akimbo(fig); }, gs.bpm * 8); | |
setTimeout(function () { gs.gandy(fig); | |
gs.jive(fig); | |
//gs.slideGrp(fig, 16); | |
}, gs.bpm * 12); | |
setTimeout(function () { gs.wave(fig); }, gs.bpm * 24); | |
}, | |
setDims : function () { | |
var part, | |
dims = gs.svgBox.node().getBoundingClientRect(); | |
// set the dimensions for the svg element | |
gs.height = dims.height; | |
gs.ctrY = gs.height / 2; | |
gs.width = dims.width; | |
gs.ctrX = gs.width / 2; | |
gs.smallest = gs.height < gs.width ? gs.height : gs.width; | |
gs.svg = gs.svgBox.select('svg') | |
.attr('height', gs.height) | |
.attr('width', gs.width); | |
// scale the body parts | |
gs.adjX = gs.width / gs.baseX; | |
gs.adjY = gs.height / gs.baseY; | |
gs.adjust = gs.smallest === gs.height ? gs.adjY : gs.adjX; | |
for (part in gs.parts) { | |
if (gs.parts.hasOwnProperty(part)) { | |
if (gs.parts[part].ld) { | |
gs.parts[part].ld = gs.adjust * gs.parts[part].ld > 4 ? | |
Math.round(gs.adjust * gs.parts[part].ld) : 4; | |
} | |
} | |
} | |
recurse(gs.bod); | |
function recurse (part) { | |
if (part.reqX) { | |
part.reqX = Math.round(part.reqX * gs.adjust); | |
} | |
if (part.reqY) { | |
part.reqY = Math.round(part.reqY * gs.adjust); | |
} | |
if (part.children) { | |
part.children.forEach(recurse); | |
} | |
} | |
}, | |
crtGrp : function (ct) { | |
var fig = gs.svg.append('g') | |
.classed('fig-' + ct, true); | |
fig.nodes = ''; | |
fig.links = ''; | |
// init the force layout | |
fig.force = d3.layout.force() | |
.on('tick', function (d) { gs.tick(fig); }) | |
.size([gs.width, gs.height]); | |
gs.update(fig); | |
gs.classLine(fig); | |
gs.figs.push(fig); | |
return fig; | |
}, | |
classLine : function (fig) { | |
fig.links.each(function (d) { | |
d3.select(this).classed('src-' + d.source.name, true) | |
.classed('trg-' + d.target.name, true); | |
}); | |
}, | |
update : function (fig) { | |
var root = gs.clone(gs.bod), | |
fit = 0, | |
charge = 0, | |
gravity = 0; | |
fig.nodes = gs.flatten(root), | |
fig.links = d3.layout.tree().links(fig.nodes), | |
fit = Math.sqrt(fig.nodes.length / (gs.smallest * gs.smallest)), | |
charge = ( -1 / fit ) * .5 * gs.adjust, | |
gravity = ( 5 * fit ); | |
fig.selectAll('line.link').remove(); | |
fig.selectAll('circle.node').remove(); | |
// start the force layout | |
fig.force | |
.charge(charge) | |
.linkDistance( function (d) { return gs.parts[d.target.part].ld; }) | |
.gravity(gravity) | |
.nodes(fig.nodes) | |
.links(fig.links) | |
.start(); | |
// Update the links… | |
fig.links = fig.selectAll('line.link') | |
.data(fig.links, function(d) { return d.target.id; }); | |
// Enter any new links. | |
fig.links.enter().insert('line', '.node') | |
.attr({ 'class' : 'link', | |
x1 : function(d) { return d.source.x; }, | |
y1 : function(d) { return d.source.y; }, | |
x2 : function(d) { return d.target.x; }, | |
y2 : function(d) { return d.target.y; } | |
}); | |
// Exit any old links. | |
fig.links.exit().remove(); | |
// Update the nodes… | |
fig.nodes = fig.selectAll('circle.node') | |
.data(fig.nodes, function(d) { return d.id; }) | |
// Enter any new nodes | |
fig.nodes.enter() | |
.append('circle') | |
.attr('class', function (d) { | |
var outer = d.outer? ' outer' : ''; | |
return 'node ' + d.name + outer; | |
}) | |
.attr('transform', function(d) { | |
return 'translate(' + gs.to3(d.x) + ',' + gs.to3(d.y) + ')'; | |
}) | |
.attr('r', function (d) { | |
var r, adj; | |
adj = gs.adjX < gs.adjY ? gs.adjX : gs.adjY; | |
r = Math.ceil(gs.parts[d.part].r * adj); | |
r = r > 4 ? r : 4; | |
return r; | |
}) | |
.call(fig.force.drag) | |
// Exit any old nodes. | |
fig.nodes.exit().remove(); | |
}, | |
tick : function (fig) { | |
fig.nodes.attr('transform', function(d) { | |
return 'translate(' + gs.to3(d.x) + ',' + gs.to3(d.y) + ')'; | |
}); | |
fig.links.attr('x1', function(d) { return d.source.x; }) | |
.attr('y1', function(d) { return d.source.y; }) | |
.attr('x2', function(d) { return d.target.x; }) | |
.attr('y2', function(d) { return d.target.y; }); | |
}, | |
standUp : function (fig) { | |
var cb = function (d) { | |
if ( d.outer && d.name !== 'head' || d.name === 'bod') { | |
return d.fixed = true; | |
} | |
}, | |
ctrX = fig.ctrX || gs.ctrX, | |
ctrY = fig.ctrY || gs.ctrY; | |
fig.lines = fig.selectAll('line.link'); | |
fig.nodes.each(function (d, i) { | |
var mov = { d : d, | |
el : this, | |
endX : ctrX + d.reqX, | |
endY : ctrY + d.reqY, | |
fig : fig | |
}; | |
fig.force.stop(); | |
mov = gs.buildMove(mov); | |
gs.transNode(mov, cb); | |
}); | |
}, | |
akimbo : function (fig) { | |
var hands = fig.selectAll('.node.lHand, .node.rHand'); | |
fig.selectAll('circle.lElbow, circle.rElbow') | |
.each(function (d) { return d.fixed = true; }) | |
hands.each(function (d) { | |
var mov = { | |
d : d, | |
el : this, | |
fig : fig | |
}; | |
if (d.name === 'lHand') { | |
mov.endX = gs.to3(gs.ctrX + (gs.adjust * 33)); | |
mov.endY = gs.to3(gs.ctrY + (gs.adjY * -48)); | |
} else { | |
mov.endX = gs.to3(gs.ctrX + (gs.adjust * -33)); | |
mov.endY = gs.to3(gs.ctrY + (gs.adjY * -48)); | |
} | |
mov = gs.buildMove(mov); | |
gs.transNode(mov); | |
}); | |
}, | |
jive : function (fig) { | |
var hands = fig.selectAll('.node.lHand, .node.rHand'), | |
lbows = fig.selectAll('.node.lElbow, .node.rElbow'), | |
radX = gs.adjust * 5, | |
radY = gs.adjust * 25; | |
hands.each(function (d) { jivin(d, this); }); | |
function elbows () { | |
lbows.each(function (d) { jivin(d, this); }); | |
} | |
function jivin (d, el) { | |
var mov = { d : d, | |
el : el, | |
pc : 'jive-', | |
fig: fig | |
}; | |
mov = gs.buildMove(mov); | |
if (d.name === ('lHand' || 'rElbow') ) { | |
mov.endX = mov.begX - radX; | |
} else { | |
mov.endX = mov.begX + radX; | |
} | |
mov.endY = gs.to3(mov.begY - (gs.adjust * 10)); | |
mov = gs.buildMove(mov); | |
mov.pc = mov.pc + d.name; | |
crtPath(mov); | |
gs.transNode(mov, function (d) { | |
gs.ptAlongPath({ path : fig.select('path.' + mov.pc), | |
circle : d3.select(mov.el), | |
lines : mov.lines, | |
count : mov.d.part === 'hand' ? 8 : 7, | |
fig : mov.fig | |
}); | |
if (mov.d.name === 'lHand') { elbows(); } | |
}); | |
} | |
function crtPath (mov) { | |
var rad; | |
if ( mov.d.name === ('rHand' || 'rElbow' ) ) { | |
rad = -radX; | |
} else { | |
rad = radX; | |
} | |
mov.fig.append('path') | |
.attr('d', 'M ' + mov.endX + ',' + mov.endY + | |
' a ' + rad + ',' + radY + ' 0 0,0 ' + | |
-rad + ',' + (gs.adjY * -8) + | |
' a ' + rad + ',' + radY + ' 0 1,0 ' + | |
rad + ',' + (gs.adjY * 8) + ' z' | |
) | |
.attr('class', mov.pc) | |
.attr('stroke', 'none') | |
.attr('fill', 'none') | |
} | |
}, | |
gandy : function(fig) { | |
var hoppers = fig.selectAll('.node.lFoot, .node.lKnee, .node.rFoot, .node.rKnee'); | |
hoppers.each(function(d) { | |
var hopper = gs.getRectCtr(this); | |
var radX = gs.to3(60 * gs.adjust); | |
var radY = gs.to3(8 * radX); | |
var dir = d.name === ('lFoot' || 'lKnee') ? 1 : -1; | |
var sweep = d.name === ('lFoot' || 'lKnee') ? [1,0] : [0,1]; | |
var stroke = d.name === 'lFoot' ? 'limegreen' : 'magenta'; | |
var dpath; | |
if (d.part === 'knee') { | |
radX *= 0.5; | |
radY *= 0.5; | |
} | |
dpath = 'M' + hopper.x + ',' + hopper.y + | |
' a' + radX + ',' + radY + ' 0 0,' + sweep[0] + ' ' + | |
(dir * radX) + ',0 v1' + | |
' a' + radX + ',' + radY + ' 0 0,' + sweep[1] + ' ' + | |
(-dir * radX) + ',0 v-1 z'; | |
fig.append('path') | |
.classed(d.name + '-hop', true) | |
.attr('d', dpath) | |
.attr('stroke', 'none') | |
.attr('fill', 'none') | |
}); | |
hoppers.each(function (d) { | |
var hopper = gs.getRectCtr(this); | |
var mov = { d : d, | |
endX : hopper.x, | |
endY : hopper.y, | |
el : this, | |
fig : fig | |
}; | |
mov = gs.buildMove(mov); | |
gs.transNode(mov, function (d) { | |
if ( d.name === 'rFoot' || d.name === 'rKnee' ) { | |
hop(); | |
} else { | |
setTimeout(function () { hop(); }, gs.bpm); | |
} | |
function hop() { | |
gs.ptAlongPath({ path : fig.select('path.' + d.name + '-hop'), | |
circle : d3.select(mov.el), | |
lines : mov.lines, | |
count : 8, | |
fig : fig, | |
dur : gs.bpm * 2 | |
}); | |
} | |
}); | |
}); | |
}, | |
wave : function (fig) { | |
var fist = fig.select('circle.lHand'); | |
var strX = gs.ctrX - (gs.adjust * 80); | |
var strY = gs.ctrY - (gs.adjust * 220); | |
var radX = gs.adjust * 65; | |
var radY = gs.adjust * 25; | |
var cb = function (d) { return d.fixed = true; }; | |
fig.append('path') | |
.attr('d', 'M ' + strX + ',' + strY + | |
' a ' + radX + ',' + radY + ' 10 0,0 ' + | |
-radX + ',' + (gs.adjY * -8) + | |
' a ' + radX + ',' + radY + ' 10 1,0 ' + | |
radX + ',' + (gs.adjY * 8) + ' z' | |
) | |
.attr('class', 'wave') | |
.attr('stroke', 'none') | |
.attr('fill', 'none') | |
fist.each(function (d) { | |
var mov = { d : d, | |
endX : strX, | |
endY : strY, | |
el : this, | |
pc : 'wave', | |
fig : fig | |
}; | |
mov = gs.buildMove(mov); | |
gs.transNode(mov, function (d) { | |
gs.ptAlongPath({ path : fig.select('path.' + mov.pc), | |
circle : d3.select(mov.el), | |
lines : mov.lines, | |
count : 7, | |
fig : fig | |
}); | |
}); | |
}); | |
elbows(); | |
function elbows() { | |
var lBows = fig.selectAll('.node.lElbow, .node.rElbow'); | |
lBows.each(function (d) { | |
var mov = { el : this, | |
d : d, | |
dur : gs.bpm * 2, | |
fig : fig | |
}; | |
if (d.name === 'lElbow') { | |
mov.endX = gs.ctrX - (111 * gs.adjust); | |
mov.endY = gs.ctrY - (130 * gs.adjust); | |
} else { | |
mov.endX = gs.ctrX + (48 * gs.adjust); | |
mov.endY = gs.ctrY - (57 * gs.adjust); | |
} | |
mov = gs.buildMove(mov); | |
gs.crtLine(mov); | |
gs.transNode(mov); | |
}); | |
} | |
}, | |
crtLine : function(mov) { | |
console.dir(mov) | |
mov.fig.append('path') | |
.attr('d', 'M ' + mov.begX + ',' + mov.begY + | |
'L ' + mov.endX + ',' + mov.endY | |
) | |
.attr('fill', 'none') | |
.attr('stroke', 'none') | |
}, | |
buildMove : function (mov) { | |
var begXY = gs.getTransXY(mov.el); | |
mov.begX = begXY.x; | |
mov.begY = begXY.y; | |
mov.lines = { | |
source : mov.fig.selectAll('line.src-' + mov.d.name), | |
target : mov.fig.selectAll('line.trg-' + mov.d.name), | |
} | |
return mov; | |
}, | |
transNode : function (mov, callback) { | |
var dur = mov.dur || gs.bpm * 2; | |
d3.select(mov.el) | |
.transition() | |
.duration(dur) | |
.attr('transform', function(d) { return 'translate(' + mov.endX + ',' + mov.endY + ')'; }) | |
.tween('tweenLine', function (d) { return function (t) { gs.tweenLine(t, mov) }}) | |
.each('end', function (d) { | |
d.x = mov.endX; | |
d.y = mov.endY; | |
d.px = mov.endX; | |
d.py = mov.endY; | |
mov.fig.force.resume(); | |
if (callback) { callback(d); } | |
}); | |
}, | |
slideGrp : function ( grp, ct, x, arr ) { | |
var idx = x || 0; | |
var slides = arr || [ { x: 100, y: 0 }, { x: 0, y: 0 }, | |
{ x: -100, y: 0 }, { x: 0, y: 0 }/*, | |
{ x: 200, y: 0 }, { x: 0, y: 0 }, | |
{ x: -200, y: 0 }, { x: 0, y: 0 }*/ | |
]; | |
var len = slides.length; | |
grp.transition() | |
.duration(gs.bpm * 2) | |
.attr('transform', function(d) { return 'translate(' + slides[idx].x + ',' + slides[idx].y + ')'; }) | |
.each('end', function (d) { | |
ct -= 1; | |
if (!ct) {return;} | |
idx = idx === len - 1 ? 0 : idx + 1; | |
reqAniFrame(function () { | |
gs.slideGrp ( grp, ct, idx, slides); | |
}); | |
}); | |
}, | |
tweenLine : function (t, mov) { | |
var cr = gs.getRectCtr(mov.el); | |
var trc = { | |
el : mov.el.parentElement, | |
cx : cr.x, | |
cy : cr.y, | |
rad : 3 | |
}; | |
mov.fig.force.stop(); | |
// manipulating the line(s) the node is attached to | |
mov.lines.source.each(function (d) { | |
d.source.x = cr.x; | |
d.source.y = cr.y; | |
d3.select(this).attr('x1', cr.x) | |
.attr('y1', cr.y); | |
}); | |
mov.lines.target.each(function (d) { | |
d.target.x = cr.x; | |
d.target.y = cr.y; | |
d.target.px = cr.x; | |
d.target.py = cr.y; | |
d3.select(this).attr('x2', cr.x) | |
.attr('y2', cr.y); | |
}); | |
// mov.fig.force.resume(); | |
if ( gs.trace ) { gs.tracers(trc) } | |
}, | |
ptAlongPath : function(m) { | |
m.circle.transition() | |
.duration(m.dur || gs.bpm) | |
.attrTween('transform', gs.transAlong(m)) | |
.tween('lines', gs.ptLines(m)) | |
.each('end', function () { | |
m.fig.force.resume(); | |
if (m.count) { | |
m.count -= 1; | |
reqAniFrame(function () { gs.ptAlongPath(m); }); | |
} | |
}); | |
}, | |
ptLines : function(m) { | |
var path = m.path.node(), | |
len = path.getTotalLength(); | |
return function(d, i, a) { | |
return function(t) { | |
var p = path.getPointAtLength(t * len); | |
// manipulating the line(s) the node is attached to | |
m.lines.source.each(function (d) { | |
d3.select(this).attr('x1', p.x) | |
.attr('y1', p.y); | |
}); | |
m.lines.target.each(function (d) { | |
d3.select(this).attr('x2', p.x) | |
.attr('y2', p.y); | |
}); | |
}; | |
}; | |
}, | |
transAlong : function(m) { | |
var path = m.path.node(), | |
len = path.getTotalLength(); | |
return function(d, i, a) { | |
return function(t) { | |
var p = path.getPointAtLength(t * len), | |
trc = { | |
el : path.parentNode, | |
cx : p.x, | |
cy : p.y | |
}; | |
if ( gs.trace ) { gs.tracers(trc) } | |
return 'translate(' + p.x + ',' + p.y + ')'; | |
}; | |
}; | |
}, | |
getTransXY : function (el) { | |
var s = d3.select(el).attr('transform'), | |
t = []; | |
s = s.substring(s.indexOf('translate(')); | |
s = s.substring(0, s.indexOf(')')); | |
t = s.replace('translate(','') | |
.replace(')','') | |
.split(','); | |
return { x : parseFloat(t[0]), y : parseFloat(t[1]) }; | |
}, | |
getRectCtr : function (el) { | |
var cr = el.getBoundingClientRect(); | |
return { x : gs.to3((cr.left + cr.right) * 0.5), | |
y : gs.to3((cr.top + cr.bottom) * 0.5) | |
}; | |
}, | |
tracers : function (trc) { | |
setTimeout( function () { | |
var d3el = d3.select(trc.el), | |
rad = trc.rad || 2, | |
dur = trc.dur || gs.bpm, | |
fill = trc.fill || 'magenta'; | |
d3el.append('circle') | |
.attr('r', rad) | |
.attr('cx', trc.cx) | |
.attr('cy', trc.cy) | |
.attr('fill', fill ) | |
.attr('stroke', 'none') | |
.transition() | |
.duration(dur) | |
.delay(300) | |
.attr('opacity', 0) | |
.remove(); | |
}, 200) | |
}, | |
tweenChk : function (t, mov, d) { | |
var cr = gs.getRectCtr(mov.el), | |
cx = cr.x, | |
cy = cr.y, | |
pel = mov.el.parentElement, | |
trc = { | |
el : pel, | |
cx : cx, | |
cy : cy, | |
dur : gs.bpm * 2 | |
}; | |
gs.tracers(trc); | |
}, | |
showXY : function () { | |
d3.select('body').on('mousemove', function() { | |
console.log('page X: ' + d3.event.pageX +'\npage Y: ' + d3.event.pageY) | |
}); | |
}, | |
showPos : function (fig) { | |
var adjustX = parseFloat(gs.to3(gs.baseX / gs.width)), | |
adjustY = parseFloat(gs.to3(gs.baseY / gs.height)); | |
console.log('adjust x: ' + adjustX); | |
console.log('adjust y: ' + adjustY); | |
fig.nodes.each(function (d, i) { | |
var str = '\n' + d.name + | |
'\nadjust x: ' + Math.round((d.x - gs.ctrX) * adjustX) + | |
' actual x: ' + Math.round(d.x - gs.ctrX) + | |
'\nadjust y: ' + Math.round((d.y - gs.ctrY) * adjustY) + | |
' actual y: ' + Math.round(d.y - gs.ctrY); | |
console.log(str); | |
}); | |
}, | |
flatten : function (fig) { | |
var nodes = [], | |
i = 0; | |
function recurse (node) { | |
if (node.children) { | |
node.children.forEach(recurse); | |
} else { | |
node.outer = true; | |
} | |
if (!node.id) { node.id = ++ i; } | |
nodes.push(node); | |
} | |
recurse(fig); | |
return nodes; | |
}, | |
parts : { | |
bod : { ld : 10 }, | |
head : { ld : 50, r : 24 }, | |
arm : { ld : 40 }, | |
elbow : { ld : 50 }, | |
hand : { ld : 50, r : 12 }, | |
hips : { ld : 70 }, | |
leg : { ld : 10 }, | |
knee : { ld : 60 }, | |
foot : { ld : 100, r : 15 } | |
}, | |
bod : { | |
name : 'bod', part : 'bod', reqX : 0, reqY : -125, | |
children : [ { name : 'hips', part : 'hips', reqX : 0, reqY : -26, | |
children : [ { name : 'lLeg', part : 'leg', reqX : -17, reqY : 3, | |
children : [ { name : 'lKnee', part: 'knee', reqX : -46, reqY : 72, | |
children : [ { name : 'lFoot', part : 'foot', reqX : -37, reqY : 180 } ] | |
} | |
] | |
}, | |
{ name : 'rLeg', part : 'leg', reqX : 20, reqY : 7, | |
children : [ { name : 'rKnee', part: 'knee', reqX : 46, reqY : 72, | |
children : [ { name : 'rFoot', part : 'foot', reqX : 37, reqY : 180 } ] | |
} | |
] | |
} | |
] | |
}, | |
{ name : 'head', part : 'head', reqX : 0, reqY : -180 }, | |
{ name : 'lArm', part : 'arm', reqX : -45, reqY : -120, | |
children : [ { name : 'lElbow', part: 'elbow', reqX : -70, reqY : -60, | |
children : [ { name : 'lHand', part : 'hand', reqX : -45, reqY : 11 } ] | |
} | |
] | |
}, | |
{ name : 'rArm', part : 'arm', reqX : 45, reqY : -120, | |
children : [ { name : 'rElbow', part: 'elbow', reqX : 70, reqY : -60, | |
children : [ { name : 'rHand', part : 'hand', reqX : 45, reqY : 11 } ] | |
} | |
] | |
} | |
] | |
}, | |
clone : function (src) { // courtesy of David Walsh | |
function mixin (dest, source, copyFunc) { | |
var name, s, i, empty = {}; | |
for(name in source) { | |
// the (!(name in empty) || empty[name] !== s) condition avoids copying properties in 'source' | |
// inherited from Object.prototype. For example, if dest has a custom toString() method, | |
// don't overwrite it with the toString() method that source inherited from Object.prototype | |
s = source[name]; | |
if(!(name in dest) || (dest[name] !== s && (!(name in empty) || empty[name] !== s))) { | |
dest[name] = copyFunc ? copyFunc(s) : s; | |
} | |
} | |
return dest; | |
} | |
if(!src || typeof src != 'object' || Object.prototype.toString.call(src) === '[object Function]') { | |
// null, undefined, any non-object, or function | |
return src; // anything | |
} | |
if(src.nodeType && 'cloneNode' in src) { | |
// DOM Node | |
return src.cloneNode(true); // Node | |
} | |
if(src instanceof Date) { | |
// Date | |
return new Date(src.getTime()); // Date | |
} | |
if(src instanceof RegExp) { | |
// RegExp | |
return new RegExp(src); // RegExp | |
} | |
var r, i, l; | |
if(src instanceof Array) { | |
// array | |
r = []; | |
i = 0; | |
l = src.length; | |
for(i; i < l; ++i) { | |
if(i in src) { | |
r.push(gs.clone(src[i])); | |
} | |
} | |
// we don't clone functions for performance reasons | |
// }else if(d.isFunction(src)) { | |
// // function | |
// r = function() { return src.apply(this, arguments); }; | |
} else { | |
// generic objects | |
r = src.constructor ? new src.constructor() : {}; | |
} | |
return mixin(r, src, gs.clone); | |
}, //end clone | |
to3 : function (n) { return parseFloat(n.toFixed(3)) } | |
}; | |
gs.setDims(); | |
gs.init(); | |
if (window.gs) { | |
window.gangnamStyle = gs; | |
} else { | |
window.gs = gs; | |
} | |
}()); |
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"> | |
<title>d3 Gangnam Style!</title> | |
<style> | |
.cover-box { | |
position: absolute; | |
top: 0; | |
left: 0; | |
height: 100%; | |
width: 100%; | |
} | |
.psy-svg-box { | |
position: absolute; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
} | |
/* svg styles */ | |
line { | |
stroke: goldenrod; | |
stroke-width: 1.5px; | |
} | |
circle.node { | |
cursor: pointer; | |
fill: #000; | |
stroke: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="cover-box"> | |
<div class="psy-svg-box"> | |
<svg></svg> | |
</div> | |
</div> | |
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.9/d3.min.js'></script> | |
<script src="gangnam.js"></script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment