xxxxxxxxxx
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" type="text/css" href="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/inputs/button/style.css">
<link rel="stylesheet" type="text/css" href="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/fps-histogram.css">
<style>
body {
margin: 0;
position: relative;
}
#vis {
background: steelblue;
}
text {
white-space: pre;
}
.link {
stroke: #000;
stroke-width: 1.5px;
}
.node {
cursor: move;
fill: #ccc;
stroke: #000;
stroke-width: 1.5px;
opacity: 0.5;
}
.node.fixed {
opacity: 1;
stroke: red;
}
button, input {display: inline-block}
.input-div {
position: absolute;
top: 0;
left: 0;
/*white-space: pre;*/
margin: 0;
}
#timeDisplay #xAxis {
opacity: 0.6;
}
#timeDisplay .domain, #timeDisplay .tick line {
fill: none;
stroke: black;
}
#timeDisplay .tick text {
font-size: 10px;
}
#timeDisplay {
pointer-events: none;
}
.g-button {
color: #804700;
background: black;
border-color: orange;
}
.g-button.g-active {
color: orange;
background: #333333;
border-color: orange;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/cool-blue/d3-lib/transitions/end-all/1.0.0/endall.js" charset="UTF-8"></script>
<script src="https://cdn.jsdelivr.net/gh/cool-blue/d3-lib/inputs/select/select.js" charset="UTF-8"></script>
<script src="https://cdn.jsdelivr.net/gh/cool-blue/d3-lib/tool-tip/0.0.0/tool-tip.js" charset="UTF-8"></script>
<script src="https://cdn.jsdelivr.net/gh/cool-blue/d3-lib/inputs/number/input-number.js" charset="UTF-8"></script>
<script src="https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/elapsedtime/elapsed-time-2.0.js"></script>
<script src="https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/inputs/button/2.0.0/button.js"></script>
<script src="https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/plot/plot-transform.js"></script>
<script src="https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/plot/fps-histogram.js"></script>
<script src="https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/inputs/select/select.js"></script>
<div id="input-div">
<!--
<label><input id="showTimeLines" name="showTimeLines" value="show" type="checkbox">show timelines</label>
-->
<button onclick = 'refresh()'> refresh</button>
steps <input id="steps-selector" onchange = 'refresh()' type="number" name="steps" value = 5 min="1" max="100"/>
</div>
<div id="vis"></div>
<script>
var graph ={
"nodes": [
{"x": 469, "y": 410, move: true},
{"x": 493, "y": 364, move: true},
{"x": 442, "y": 365, move: true},
{"x": 467, "y": 314, move: true},
{"x": 477, "y": 248, move: true},
{"x": 425, "y": 207, move: true},
{"x": 402, "y": 155, move: true},
{"x": 369, "y": 196, move: true},
{"x": 350, "y": 148, move: true},
{"x": 539, "y": 222, move: true},
{"x": 594, "y": 235, move: true},
{"x": 582, "y": 185, move: true},
{"x": 633, "y": 200, move: true}
],
"links": [
{"source": 0, "target": 1},
{"source": 1, "target": 2},
{"source": 2, "target": 0},
{"source": 1, "target": 3},
{"source": 3, "target": 2},
{"source": 3, "target": 4},
{"source": 4, "target": 5},
{"source": 5, "target": 6},
{"source": 5, "target": 7},
{"source": 6, "target": 7},
{"source": 6, "target": 8},
{"source": 7, "target": 8},
{"source": 9, "target": 4},
{"source": 9, "target": 11},
{"source": 9, "target": 10},
{"source": 10, "target": 11},
{"source": 11, "target": 12},
{"source": 12, "target": 10}
]
};
/*
var graph ={
"nodes": [
{"x": 469, "y": 410, move: true},
{"x": 477, "y": 248, move: false},
{"x": 633, "y": 200, move: false}
],
"links": [
{"source": 0, "target": 1},
{"source": 1, "target": 2},
{"source": 2, "target": 0}
]
};
*/
var inputDiv = d3.select("#input-div"),
tooltip = d3.ui.tooltip({
base: "body",
offset: {
top: {ref: "bottom", offset: 6},
left: function(rect) {
return (rect.right + rect.left) / 2;
}
},
style: {background: "#ccc", color: "red"}
}),
easeings = ["linear", "quad", "cubic", "sin", "exp", "circle", "elastic", "back", "bounce"],
xEase = d3.ui.select({
base: inputDiv,
oninput: refresh,
data: easeings,
initial: "bounce",
onmouseover: tooltip("x"),
onmouseout: tooltip()
}),
yEase = d3.ui.select({
base: inputDiv,
oninput: refresh,
data: easeings,
initial: "circle",
onmouseover: tooltip("y"),
onmouseout: tooltip()
}),
toggleTransitions = {
label: "transitions",
onclick: function() {
this.blur();
this.value != this.value
},
value: false
},
cleanUp = {
label: "clean up",
onclick: function() {
this.blur();
this.value != this.value;
d3.selectAll(".dummy-segment").remove()
},
value: false
},
buttons = Object.defineProperties(
inputDiv.append("div")
.attr("id", "controls")
.style({display: "inline-block", padding: "0 6px 0 6px", "text-align": "center"})
.call(d3.ui.buttons.toggle, [toggleTransitions, cleanUp]),
{
"useTransitions": {
get: function() {
return toggleTransitions.value
}
},
"cleanUp": {
get: function() {
return cleanUp.value
}
},
"height": {
get: function(){
return this.node().getBoundingClientRect().height;
}
}
}),
aveTransTime = d3.ui.number({
attributes: {
varying: [{name: "tx", value: 0.1}, {name: "ty", value: 0.1}],
uniform: {min: 0.1, max: 10, step: 0.1}
},
events: {
varying: {mouseover: function(v){return tooltip(v.name)}},
uniform: {
change: refresh,
mouseout: tooltip()
}
}
}),
elapsedTime = outputs.ElapsedTime("#input-div", {
border: 0, margin: 0, "box-sizing": "border-box",
padding: "0 0 0 6px", background: "#2B303B", "color": "orange"
})
.message(function(value) {
var this_lap = this.lap().lastLap, aveLap = this.aveLap(this_lap)
return d3.format(" >4,.1f")(1 / aveLap) + " fps "
}),
hist = d3.ui.FpsMeter("#input-div", {display: "inline-block"}, {
height: 10, width: 100,
values: function(d){return 1/d},
domain: [0, 60]
});
var width = 960,
height = 500 - inputDiv.node().getBoundingClientRect().height - buttons.height - 3,
steps = function(){return +d3.select("#steps-selector").property("value")};
var colors = d3.scale.category10();
graph.nodes.forEach(function(d, i){
d.color = colors(i), d.index = i
});
var force = d3.layout.force()
.size([width, height])
.charge(-600)
.linkDistance(40)
.on("start", function() {
elapsedTime.start(100);
})
.on("tick", tick);
var drag = force.drag()
.on("dragstart", dragstart);
var svg = d3.select("#vis").append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link"),
nodes = svg.selectAll("#nodes");
d3.ns.prefix.CB = "CB:emit/drag/transition/or-whatever-you-feel-like";
// create an object for mapping the transition timelines
var mapTransitions = (function mapTransitions() {
var timeInterval, t, w, tAxis = d3.svg.axis().orient("top").tickFormat(d3.time.format("%H:%M:%S:%L")),
nodeTransitions = [],
timeDisplay,
nodes,
lane,
timeLines,
timeSegments,
mark;
var size = {
h: 10, leading: 3,
margin: {left: 30, right: 30, top: 40, get width() {return width - this.right}}
},
nodeTransition = Transition(500),
scaleTransition = Transition(1000),
element = d3.ui.select({
base: inputDiv,
data: [{text: "SVG element", value: "svg"}, {text: "dummy nodes", value: "custom"}],
element: function() {
return {
svg: "rect", custom: "CB:rect"
}[this.value()]
}
}),
method = d3.ui.select({
base: inputDiv,
// onUpdate: update,
data: [{text: "in timeline", value: "nodes"}, {text: "global", value: "global"}],
get dummyNode(){
return this.methods[this.value()]
},
methods: {
nodes: function () {
var n = Math.round(Math.random() * nodes.size()), tl = Math.round(Math.random());
return nodes.filter(function(d, i) {return i === n})
.selectAll("g").filter(function(d, i) {return i == tl});
},
global: function (){
return svg
}
},
get h(){
return size.h;
},
w: width,
get y(){
return {nodes: 0, global: Math.random()*height}[this.value()];
}
});
return function mapTransitions(selection, update) {
// build a data structure for the transitions on the proxy nodes
// convert:
// node {}
// transition []
// lock {}
// delay
// duration
// time
// event
// to this:
// node {} <- nodes
// index
// color <string>
// transitions []
// transition {} <- timeLines
// name
// color
// active
// stops []
// stop {} <- timeSegments
// stop
// id
// active get:
// duration
// t0
// t1
// y
// h
// nop
// overlaps function
// if a node is selected then it is an end event so flag it for deletion
if(update/* && update.transaction === "disconnect"*/) {
// get the location of the current transition of the event emitting node
var node, transition, stop, exitNodeStops, exitStops,
stops = nodeTransitions.filter(function(n, i) {
var f = n.index == update.node;
if(f) node = i;
return f
})[0]
.transitions.filter(function(t, i) {
var f = t.name == update.attr;
if(f) transition = i;
return f
})[0]
.stops,
s, j = 0;
/*
console.log(
[update.node, update.attr, update.transition.active].join("\t")
);
*/
// get a reference to the active stop in the transition
for(s = 0;
s < stops.length && stops[s].id != update.transition.active; s++)
/*console.log([s, "of", stops.length, stops[s].id].join("\t"))*/;
stop = stops[s];
// collect all segments representing stops scheduled to end before the start time
// of the current stop
// include the current stop (event source) in this collection
// first get all of the stops from the earliest, up to and including the current one
exitNodeStops = stops.slice(0, s + 1);
// filter out the stops that are scheduled to start later and remove them from the map
exitNodeStops = exitNodeStops
.filter(function(s, i){
var past = stop.t0 > s.t1 ||
update.transaction == "disconnect" && (stop === s ||
+stop.id >= +s.id && stop.t1 >= s.t0);
if(past){
stops.splice(i + j--, 1);
}
return past
});
/*
exitStops = exitNodeStops
.map(function(d) {
console.log([d.id, d.t1 - Date.now(), d.t1 - stop.t1].join("\t"));
return d.id
});
console.log(
[stop.t1 - Date.now(), d3.time.format("%H:%M:%S:%L")(new Date(stop.t1)), stop.nop ? "nop" : "active"
].join("\t")
);
printDiff();
printState();
*/
}
function printDiff(){
console.log("exitStops " + (exitNodeStops ? exitNodeStops.length : "none"));
if(exitNodeStops) {
exitNodeStops.forEach(function(s) {
var t1 = s.t1;
console.log([ "delete " +
s.id,
d3.time.format("%H:%M:%S:%L")(new Date(t1)),
(s.nop ? "nop" : "active")
].join("\t"))
})
}
}
function printState() {
console.log("state");
nodeTransitions.forEach(function(n) {
n.transitions.forEach(function(t) {
t.stops.forEach(function(s) {
var t0 = s.t1, t1 = s.t1;
console.log([
n.index, t.name, s.id,
t0, d3.time.format("%H:%M:%S:%L")(new Date(t0)),
t1, d3.time.format("%H:%M:%S:%L")(new Date(t1)),
(s.nop ? "nop" : "active"),
(s.node ? "node" : "deleted")
].join("\t"))
})
})
});
}
// build a visualisation based on the map
var oldScales = null;
// for a normal update, keep the same time scale
// otherwise, calculate the time scale based on the transitions data in the map
if(!update) {
// console.log("initialise " + initEvents++);
// make a clean snap-shot of the transitions
nodeTransitions = [];
// create a map of the transitions structure on the proxy nodes
// with transition stops sorted by id (temporal order)
selection.each(function d(nodeData) {
var n = this,
transitionNames = Object.keys(n).filter(function(k) {return k.match(/^__transition/)}),
transitions = transitionNames.map(function(k) {
return {
name: k.match(/^__transition_(.*?)__/)[1],
stops: Object.keys(n[k]).filter(function(id, i) {
//only include the stops and exclude the last one added by positionNodes
return id.match(/\d+/) && i != n[k].count-1;
}).sort(function(a, b){return a - b}).map(function(index, i) {
var l = n[k][index];
return {
stop: i,
id: index,
get active() { return n[k] ? n[k].active == index : null},
duration: l.duration,
t0: l.time + l.delay,
t1: l.time + l.delay + l.duration,
nop: l.tween.empty(),
h: size.h,
y: 0,
overlaps: function(seg2) {
var seg1 = this;
return (
seg1.t1 >= seg2.t0 && seg2.t1 >= seg1.t0 ||
seg2.t1 >= seg1.t0 && seg1.t1 >= seg2.t0
)
}
}
}),
active: n[k].active,
color: d3.select(n).datum().color
}
});
nodeTransitions.push({
index: nodeData.index,
color: nodeData.color,
transitions: transitions});
});
t = d3.time.scale()
.range([0, size.margin.width])
// find the min delay and the maximum finish time for all transitions
.domain([
d3.min(nodeTransitions, function(node) {
return d3.min(node.transitions, function(transition) {
return d3.min(transition.stops, function(stop) {
return stop.t0
})
})
}),
d3.max(nodeTransitions, function(node) {
return d3.max(node.transitions, function(transition) {
return d3.max(transition.stops, function(stop) {
return stop.t1
})
})
})
]);
// record the time interval, before nicing the domain
timeInterval = t.domain();
t.nice();
// a line segment for each transition
// width scale
w = d3.scale.linear()
.range(t.range().map(function(d) {
return d - t.range()[0];
}))
.domain(t.domain().map(function(d) {
return d - t.domain()[0];
}));
tAxis.scale(t);
// outer wrapper
timeDisplay = svg.selectAll("#timeDisplay").data([nodeTransitions]);
timeDisplay.enter().append("g")
.attr({
id: "timeDisplay",
transform: "translate(" + [size.margin.left, size.margin.top] + ")"
})
.append("g")
.attr({
id: "xAxis",
transform: "translate(0," + -size.leading + ")"
});
timeDisplay.exit().call(nodeTransition, fadeOut, {name: "timeDisplay", target: "opacity"});
// node wrappers
// bind node transition structures
nodes = timeDisplay.selectAll(".node-timeline").data(function(d) {
return d
}, function(d){return d.index});
nodes.enter().append("g")
.attr({
class: function(d){return "node-timeline _" + d.index},
opacity: 1
});
nodes.attr({
transform: function(d, i) {
return "translate(0," + (i * (2 * (size.h + size.leading))) + ")"
},
fill: function(d) {
return d.color;
}
});
if(buttons.cleanUp) {
nodes.exit().call(nodeTransition, {
then: fadeOut,
name: "nodes",
attr: ["opacity"],
data: [function(d) {return "transitions: " + d.transitions.length}]
});
}
// set up the selection for the time cursor early because it has the previous scales attached
// and this is needed for the pre-transition positioning the time segments
mark = nodes.selectAll(".mark")
.data([{scales: {t: t.copy(), w: w.copy()}}], function(d) {
// the key function is called before the new data is bound, so oldScales gets
// the previously bound value
if(!Array.isArray(this)) oldScales = d.scales;
return d;
})
.attr("class", "mark");
// add the cursor for current time
mark.enter().append("line")
.attr({
stroke: "black", "stroke-width": 1,
class: "mark"
});
// a lane for each attribute transitioning on each node
lane = d3.scale.ordinal().range([0,1]).domain(["cx","cy"]);
timeLines = nodes.selectAll(".timeLine").data(function(d) {
return d.transitions;
}, function(d){
return d.name
});
if(buttons.cleanUp) {
timeLines.exit().call(nodeTransition, {
then: fadeOut,
name: "timeLines",
attr: ["opacity"],
data: ["name", "active", function(d) {return "stops: " + d.stops.length}]
});
}
timeLines.enter().append("g").attr({
class: function(d){return "timeLine " + d.name},
opacity: 1
});
timeLines.attr({
transform: function(d) {
return "translate(0," + (lane(d.name) * (size.h + size.leading)) + ")";
}
});
// re-build the visualisation for the transition stops
timeSegments = timeLines.selectAll(".segment")
.data(function(d) {return d.stops}, function(d) {
return d.id;
});
// mark the exit nodes for disposal, fade them out and remove them
var tsExit = timeSegments.exit()
.attr({class: "garbage"});
tsExit.filter(function(d) {
return !d.nop;
}).call(nodeTransition, {
then: fadeOut,
name: "exit",
attr: ["opacity"],
data: ["id"],
debug: false
});
tsExit.filter(function(d) {
return d.nop;
}).remove();
// enter new nodes, faded out and position on the previous scale
// sort them to match the data sequence (temporal order)
timeSegments.enter().append("rect")
.attr({
class: function(d) {return "segment _" + d.id},
stroke: "black",
"stroke-opacity": 0,
opacity: 0,
x: function(d) {
return (oldScales ? oldScales.t : t)(d.t0)
},
width: function(d) {
return (oldScales ? oldScales.w : w)(d.duration)
},
y: 0, height: size.h
}).order();
timeSegments.each(function(d){
d.node = this;
});
// on the first pass, fade the new nodes in and slide all nodes into the new scale
// if its not just an update, slide the nodes into place
timeSegments
.call(nodeTransition, {
then: function(selection) {
selection.attr({
opacity: function o(d) {
return d.nop ? 0 : 0.6
},
x: function(d) {
return t(d.t0)
},
width: function(d) {
return w(d.duration)
}
});
},
name: "x",
attr: ["opacity"],
data: ["id"],
debug: false
});
// offset colliding segments
timeLines
.each(function() {
var segments = d3.select(this).selectAll(".segment");
segments.each(function(s1) {
// for each segment...
if(!s1.nop) {
// if the segment is not just a spacer
// create an array of overlapping siblings and store it on the node datum object
var l;
var key1 = this.t0 + "" + this.t1;
s1.group = [d3.select(this)];
s1.map = d3.map();
segments.each(function(s2, j) {
if(s2 !== s1 && (!s2.map || !s2.map.has(key1)) && !s2.nop && s1.overlaps(s2)) {
var key2 = this.t0 + "" + this.t1;
s1.group.push(d3.select(this));
s1.map.set(this, key2);
}
});
// if there are overlapping siblings...
if((l = s1.group.length) > 1) {
// divide them evenly in the vertical slot
s1.group.forEach(function(n, i) {
var h = size.h / l;
n.call(nodeTransition, {
then: function(transition) {
transition.attr({y: h * i, height: h})
},
name: "y"
})
})
}
}
})
});
} else{
// if refresh only, manually exit completed segments
if(update.transaction === "disconnect") {
// printState();
// exit the expired stops on the updating node
exitNodeStops.forEach(function ex(s){
var n = d3.select(s.node);
delete s.node;
if(n.datum().nop){
dummyElement(n);
n.remove();
}else
n.call(nodeTransition, {
then: fadeOut,
name: "exit",
attr: ["opacity"],
data: ["id"],
debug: false
});
})
}
// if only an update, pop the active nodes and restore their full height
var activeSegment = d3.select(stop.node);
if(activeSegment.size() && !activeSegment.datum().nop)
activeSegment
.attr({
opacity: 0.8,
"stroke-opacity": 1
})
.call(nodeTransition, {
name: "restoreHeight",
ease: "linear",
then: function restore(selection) {
// eagerly restore to full height
selection.attr({
y: 0, height: size.h
});
d3.select(selection.node()).datum().active = false;
}
});
}
var currentX = t(Date.now());
mark.attr({
y2: 2 * (size.h + size.leading),
x1: currentX,
x2: currentX
}).interrupt()
.transition().duration(t.domain()[1] - Date.now()).ease("linear")
.attr({x1: t.range()[1], x2: t.range()[1]})
.each("start", function() {
mark.attr({running: true})
})
.each("end", function() {
mark.attr({running: null})
});
if(!update) timeDisplay.select("#xAxis").attr("opacity", 1).call(scaleTransition, {
then: tAxis,
name: "tAxis",
attr: ["opacity"],
data: ["id"],
debug: false
});
function fadeOut(selection){
dummyElement(selection);
selection.attr({opacity: 0}).remove();
selection.attr({opacity: 0.3});
}
function dummyElement(base){
if(buttons.cleanUp) return base.remove();
method.dummyNode()
.append(element.element())
.attr({
x: Math.random()*method.w,
y: method.y,
height: Math.random()*method.h,
width: Math.random()*10,
fill: colors(Math.random()*10),
class: "dummy-segment"
});
}
};
function Transition(t) {
// generalised transition that accepts options controlling what is logged
// for the transition events and then pass remaining arguments to a then function
// the then function is called with the transition as the <this> context
function getTarget(node, target) {
return target && node && (target + " from " + node.attr(target)) || "";
}
function m(s){
var d = s.datum();
return function(k){
return typeof k == "function" ? k(d) : (k + ": " + d[k]);
}
}
function a(s){
return function(a){
a = d3.functor(a)(s);
return a + ": " + s.attr(a);
}
}
return function transition(selection, opt) {
opt = opt || {};
var name = d3.functor(opt.name)(selection), target = opt.target, then = opt.then,
data = (opt.data || []), attr = (opt.attr || []),
n = buttons.useTransitions ?
(name ? selection.transition(name) : selection.transition())
.duration(t)
.ease(opt.ease || "cubicInOut"):
selection;
function log(s, event){
if(opt.debug)
console.log([name].concat(data.map(m(s)), attr.map(a(s))
, [getTarget(selection, target), event]).join("\t"))
}
if (name && n.duration)
n
.each("start", function() {
log(d3.select(this), "start");
})
.each("interrupt", function() {
log(d3.select(this), "interrupted");
})
.each("end", function() {
log(d3.select(this), "end");
});
if(then)
n.call.bind(n, then).apply(n, [].slice.call(arguments, 1));
}
}
})();
//d3.json("graph.json", function(error, graph) {
// if (error) throw error;
force
.nodes(graph.nodes)
.links(graph.links);
link = link.data(graph.links)
.enter().append("line")
.attr("class", "link");
nodes = nodes.data([graph.nodes])
.enter().append("g")
.attr({id: "nodes"});
var node = nodes.selectAll(".node")
.data(function(d){return d})
.enter().append("g")
.attr("class", "node")
.classed("fixed", function(d){return d.move})
.on("dblclick", dblclick)
.call(drag)
.call(positionnodes);
var circle = node.append("circle")
.attr({
r: 12
})
.style({
fill: function(d) {return d.move ? d.color : null}
}),
label = node.append("text")
.attr({
"text-anchor": "middle",
"font-size": "12px",
dy: "0.35em"
});
//});
d3.timer(function(){
elapsedTime.mark();
if(elapsedTime.aveLap.history.length)
hist(elapsedTime.aveLap.history);
});
function tick(e) {
if(link && label && node) {
link.attr({
"x1": function a(d) { return d.source.x; },
"y1": function a(d) { return d.source.y; },
"x2": function a(d) { return d.target.x; },
"y2": function a(d) { return d.target.y; }
});
label.text(function(d) {
// return an array of transition stop counts
return d ?
d.transitions ?
d.transitions.map(function(t) {
return t.stops.filter(function(s) {
return !s.nop
}).length
}) :
null :
null;
});
node.attr("transform", function n(d) {return "translate(" + [d.x, d.y] + ")"})
}
force.alpha(0.1)
}
function refresh(){
node.call(positionnodes)
}
function dblclick(d) {
d3.select(this).classed("fixed", d.move = false)
.selectAll("circle").style("fill", null);
}
var dragFlag = {cx: 16, cy: 32};
function dragstart(d, attr) {
d.fixed |= dragFlag[attr] || 2;
d3.select(this).classed("fixed", d.move = true)
.selectAll("circle").style("fill", d.color);
}
function dragend(d, attr) {
d3.select(this).classed("fixed", d.fixed &= ~(dragFlag[attr] || 2))
}
function positionnodes(selection){
// reset the groups in the endAll detector
var endAll = d3.cbTransition.endAll(),
ease = {cx: xEase, cy: yEase},
// set up a structure of privately namespaced elements as transition proxies
// for the nodes with move set to true and bind to the same data
// ns = "CB:emit/drag/transition/or-whatever-you-feel-like",
// get the parent data with grouping preserved
// todo generalise this for a complex group structure
selectionData = selection.map(function(g){return d3.select(g.parentNode).datum()})[0],
transitions = d3.select("body").selectAll("transitions")
.data([selectionData.filter(function(d){
return d.move
})], function(d){return d.index}),
transitionsEnter = transitions.enter().append("CB:transitions"),
shadowNodes = transitions.selectAll("emitdrag")
.data(function(d){return d});
shadowNodes.enter().append("CB:emitdrag");
selection.style("fill", null); // reset the node colors
function updateTransitions(selection, update){
// if(!showTimeLines.checked) return;
selection = selection || d3.select("body").selectAll("transitions").selectAll("emitdrag");
return selection.call(mapTransitions, update);
}
// create a chain of transitions on the shadow nodes cx and cy attributes
["cx", "cy"].forEach(function(attr, index) {
var routes = {cx: ["x", "px"], cy: ["y", "py"]};
function connect(a){
return function(d, i) {
// select the proxy
var n = d3.select(this);
// and align it to the current position of the selected node
n.attr({cx: d.x, cy: d.y});
// redirect the selected node data to the attributes of the transition proxies
Object.defineProperty(d, routes[a][1], {
get: function() {return d[routes[a][0]] = +n.attr(a)},
set: function(_){
n.attr(a, _);
},
configurable: true,
enumerable: true
});
// map the current state after the transition cleanup is complete
// console.log("connect\t" + a + "\t" + n.datum().index);
updateTransitions(null, {
node: d.index,
attr: a,
transition: n.property(["__transition_", a, "__"].join("")),
selection: n,
transaction: "connect"
});
}
}
function disconnect(a){
return function(d, i) {
var n = d3.select(this), that = this;
Object.defineProperty(d, routes[a][1], {
value: +n.attr(a),
writable: true
});
// map the current state after the transition cleanup is complete
// window.setTimeout(function(){
// console.log("disconnect\t" + a + "\t" + n.datum().index);
updateTransitions.call(that, null, {
node: d.index,
attr: a,
transition: n.property(["__transition_", a, "__"].join("")),
selection: n,
transaction: "disconnect"
});
// }, 0);
}
}
function onStart(d, i) {
dragstart.call(selection.filter(function(p) {return p === d}).node(), d, attr);
connect(attr).call(this, d, i);
force.start();
if(!d.starts++) d.starts =1;
}
function onInterrupt(d, i){
// console.log(d.index + " interrupted")
dragend.call(selection.filter(function(p) {return p === d}).node(), d, attr);
disconnect(attr).call(this, d, i);
}
function onEnd(d, i) {
dragend.call(selection.filter(function(p) {return p === d}).node(), d, attr);
disconnect(attr).call(this, d, i);
if(!d.starts--) console.log("end before start!");
}
function cleanUp(selection){
// remove the shadow nodes after all their last transitions completes
selection.call(endAll, function(){
transitions.remove();
// map the current state after the transition cleanup is complete
window.setTimeout(function(){updateTransitions(null)}, 0);
}, "move-node");
}
d3.range(steps()).reduce(function(o, s) {
var minTime = 20;
function tms() {return (aveTransTime()[0] * 1000)}
return (
o.transition(attr)
// nop transition for delay
.duration(function(d) {
return d.delay = (minTime + Math.random() * tms()).toFixed()
})
.each("start.step", onStart)
.each("interrupt.nop", onInterrupt)
.each("end.nop", onEnd))
// operative transition
.transition()
.duration(function(d) {
return d.duration = (minTime + Math.random() * tms()).toFixed()
})
.ease(ease[attr].value())
.attr(Object.defineProperty({}, attr, {
value: function(d) {
var m = 1/5;
return (m + (1 - 2 * m) * Math.random()) * [width, height][index]
},
enumerable: true
}))
.each("start.step", onStart)
.each("interrupt", onInterrupt)
.each("end.step", onEnd)
}, shadowNodes.interrupt()) // delete any existing transitions on the initial object
// add a cleanup on the last transition in the chain
.transition("service").duration(0)
.call(cleanUp, attr);
});
shadowNodes.call(updateTransitions);
}
force.start();
</script>
</body>
</html>
Updated missing url https://rawgit.com/cool-Blue/d3-lib/master/transitions/end-all/1.0.0/endAll.js to https://cdn.jsdelivr.net/gh/cool-blue/d3-lib/transitions/end-all/1.0.0/endall.js
Updated missing url https://rawgit.com/cool-Blue/d3-lib/master/inputs/select/select.js to https://cdn.jsdelivr.net/gh/cool-blue/d3-lib/inputs/select/select.js
Updated missing url https://rawgit.com/cool-Blue/d3-lib/master/tool-tip/0.0.0/tool-tip.js to https://cdn.jsdelivr.net/gh/cool-blue/d3-lib/tool-tip/0.0.0/tool-tip.js
Updated missing url https://rawgit.com/cool-Blue/d3-lib/master/inputs/number/input-number.js to https://cdn.jsdelivr.net/gh/cool-blue/d3-lib/inputs/number/input-number.js
Updated missing url https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/elapsedTime/elapsed-time-2.0.js to https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/elapsedtime/elapsed-time-2.0.js
Updated missing url https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/inputs/button/2.0.0/button.js to https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/inputs/button/2.0.0/button.js
Updated missing url https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/plot-transform.js to https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/plot/plot-transform.js
Updated missing url https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/fps-histogram.js to https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/plot/fps-histogram.js
Updated missing url https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/inputs/select/select.js to https://cdn.jsdelivr.net/gh/repo/cool-blue/d3-lib/inputs/select/select.js
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js
https://rawgit.com/cool-Blue/d3-lib/master/transitions/end-all/1.0.0/endAll.js
https://rawgit.com/cool-Blue/d3-lib/master/inputs/select/select.js
https://rawgit.com/cool-Blue/d3-lib/master/tool-tip/0.0.0/tool-tip.js
https://rawgit.com/cool-Blue/d3-lib/master/inputs/number/input-number.js
https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/elapsedTime/elapsed-time-2.0.js
https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/inputs/button/2.0.0/button.js
https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/plot-transform.js
https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/fps-histogram.js
https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/inputs/select/select.js