dc.graph
Version:
Graph visualizations integrated with crossfilter and dc.js
272 lines (264 loc) • 9.92 kB
JavaScript
dc_graph.fix_nodes = function(options) {
options = options || {};
var fix_nodes_group = dc_graph.fix_nodes_group(options.fix_nodes_group || 'fix-nodes-group');
var _fixedPosTag = options.fixedPosTag || 'fixedPos';
var _fixes = [], _nodes, _wnodes, _edges, _wedges;
var _execute = {
nodeid: function(n) {
return _mode.parent().nodeKey.eval(n);
},
sourceid: function(e) {
return _mode.parent().edgeSource.eval(e);
},
targetid: function(e) {
return _mode.parent().edgeTarget.eval(e);
},
get_fix: function(n) {
return _mode.parent().nodeFixed.eval(n);
},
fix_node: function(n, pos) {
n[_fixedPosTag] = pos;
},
unfix_node: function(n) {
n[_fixedPosTag] = null;
},
clear_fixes: function() {
_fixes = {};
},
register_fix: function(id, pos) {
_fixes[id] = pos;
}
};
function request_fixes(fixes) {
_mode.strategy().request_fixes(_execute, fixes);
tell_then_set(find_changes()).then(function() {
_mode.parent().redraw();
});
}
function new_node(nid, n, pos) {
_mode.strategy().new_node(_execute, nid, n, pos);
}
function new_edge(eid, sourceid, targetid) {
var source = _nodes[sourceid], target = _nodes[targetid];
_mode.strategy().new_edge(_execute, eid, source, target);
}
function find_changes() {
var changes = [];
_wnodes.forEach(function(n) {
var key = _mode.parent().nodeKey.eval(n),
fixPos = _fixes[key],
oldFixed = n.orig.value[_fixedPosTag],
changed = false;
if(oldFixed) {
if(!fixPos || fixPos.x !== oldFixed.x || fixPos.y !== oldFixed.y)
changed = true;
}
else changed = fixPos;
if(changed)
changes.push({n: n, fixed: fixPos ? {x: fixPos.x, y: fixPos.y} : null});
});
return changes;
}
function execute_change(n, fixed) {
if(fixed)
_execute.fix_node(n.orig.value, fixed);
else
_execute.unfix_node(n.orig.value);
}
function tell_then_set(changes) {
var callback = _mode.fixNode() || function(n, pos) { return Promise.resolve(pos); };
var promises = changes.map(function(change) {
var key = _mode.parent().nodeKey.eval(change.n);
return callback(key, change.fixed)
.then(function(fixed) {
execute_change(change.n, fixed);
});
});
return Promise.all(promises);
}
function set_changes(changes) {
changes.forEach(function(change) {
execute_change(change.n, change.fixed);
});
}
function tell_changes(changes) {
var callback = _mode.fixNode() || function(n, pos) { return Promise.resolve(pos); };
var promises = changes.map(function(change) {
var key = _mode.parent().nodeKey.eval(change.n);
return callback(key, change.fixed);
});
return Promise.all(promises);
}
function fix_all_nodes(tell) {
if(tell === undefined)
tell = true;
var changes = _wnodes.map(function(n) {
return {n: n, fixed: {x: n.cola.x, y: n.cola.y}};
});
if(tell)
return tell_then_set(changes);
else {
set_changes(changes);
return Promise.resolve(undefined);
}
}
function clear_fixes() {
_mode.strategy().clear_all_fixes && _mode.strategy().clear_all_fixes();
_execute.clear_fixes();
}
function on_data(diagram, nodes, wnodes, edges, wedges, ports, wports) {
_nodes = nodes;
_wnodes = wnodes;
_edges = edges;
_wedges = wedges;
if(_mode.strategy().on_data) {
_mode.strategy().on_data(_execute, nodes, wnodes, edges, wedges, ports, wports); // ghastly
var changes = find_changes();
set_changes(changes);
// can't wait for backend to acknowledge/approve so just set then blast
if(_mode.reportOverridesAsynchronously())
tell_changes(changes); // dangling promise
}
}
var _mode = {
parent: property(null).react(function(p) {
fix_nodes_group
.on('request_fixes.fix-nodes', p ? request_fixes : null)
.on('new_node.fix_nodes', p ? new_node : null)
.on('new_edge.fix_nodes', p ? new_edge : null);
if(p) {
p.on('data.fix-nodes', on_data);
} else if(_mode.parent())
_mode.parent().on('data.fix-nodes', null);
}),
// callback for setting & fixing node position
fixNode: property(null),
// save/load may want to nail everything / start from scratch
// (should probably be automatic though)
fixAllNodes: fix_all_nodes,
clearFixes: clear_fixes,
strategy: property(dc_graph.fix_nodes.strategy.fix_last()),
reportOverridesAsynchronously: property(true)
};
return _mode;
};
dc_graph.fix_nodes.strategy = {};
dc_graph.fix_nodes.strategy.fix_last = function() {
return {
request_fixes: function(exec, fixes) {
exec.clear_fixes();
fixes.forEach(function(fix) {
exec.register_fix(fix.id, fix.pos);
});
},
new_node: function(exec, nid, n, pos) {
exec.fix_node(n, pos);
},
new_edge: function(exec, eid, source, target) {
exec.unfix_node(source.orig.value);
exec.unfix_node(target.orig.value);
}
};
};
dc_graph.fix_nodes.strategy.last_N_per_component = function(maxf) {
maxf = maxf || 1;
var _age = 0;
var _allFixes = {};
return {
clear_all_fixes: function() {
_allFixes = {};
},
request_fixes: function(exec, fixes) {
++_age;
fixes.forEach(function(fix) {
_allFixes[fix.id] = {id: fix.id, age: _age, pos: fix.pos};
});
},
new_node: function(exec, nid, n, pos) {
++_age;
_allFixes[nid] = {id: nid, age: _age, pos: pos};
exec.fix_node(n, pos);
},
new_edge: function() {},
on_data: function(exec, nodes, wnodes, edges, wedges, ports, wports) {
++_age;
// add any existing fixes as requests
wnodes.forEach(function(n) {
var nid = exec.nodeid(n), pos = exec.get_fix(n);
if(pos && !_allFixes[nid])
_allFixes[nid] = {id: nid, age: _age, pos: pos};
});
// determine components
var components = [];
var dfs = dc_graph.undirected_dfs({
nodeid: exec.nodeid,
sourceid: exec.sourceid,
targetid: exec.targetid,
comp: function() {
components.push([]);
},
node: function(compid, n) {
components[compid].push(n);
}
});
dfs(wnodes, wedges);
// start from scratch
exec.clear_fixes();
// keep or produce enough fixed nodes per component
components.forEach(function(comp, i) {
var oldcomps = comp.reduce(function(cc, n) {
if(n.last_component) {
var counts = cc[n.last_component] = cc[n.last_component] || {
total: 0,
fixed: 0
};
counts.total++;
if(_allFixes[exec.nodeid(n)])
counts.fixed++;
}
return cc;
}, {});
var fixed_by_size = Object.keys(oldcomps).reduce(function(ff, compid) {
if(oldcomps[compid].fixed)
ff.push({compid: +compid, total: oldcomps[compid].total, fixed: oldcomps[compid].fixed});
return ff;
}, []).sort(function(coa, cob) {
return cob.total - coa.total;
});
var largest_fixed = fixed_by_size.length && fixed_by_size[0].compid;
var fixes = comp.filter(function(n) {
return !n.last_component || n.last_component === largest_fixed;
}).map(function(n) {
return _allFixes[exec.nodeid(n)];
}).filter(function(fix) {
return fix;
});
if(fixes.length > maxf) {
fixes.sort(function(f1, f2) {
return f2.age - f1.age;
});
fixes = fixes.slice(0, maxf);
}
fixes.forEach(function(fix) {
exec.register_fix(fix.id, fix.pos);
});
var kept = fixes.reduce(function(m, fix) {
m[fix.id] = true;
return m;
}, {});
comp.forEach(function(n) {
var nid = exec.nodeid(n);
if(!kept[nid])
_allFixes[nid] = null;
n.last_component = i+1;
});
});
}
};
};
dc_graph.fix_nodes_group = function(brushgroup) {
window.chart_registry.create_type('fix-nodes', function() {
return d3.dispatch('request_fixes', 'new_node', 'new_edge');
});
return window.chart_registry.create_group('fix-nodes', brushgroup);
};