UNPKG

dc.graph

Version:

Graph visualizations integrated with crossfilter and dc.js

434 lines (403 loc) 16.8 kB
var qs = querystring.parse(); var steptime = +qs.interval || 1000, // ms per step pause = +qs.pause || 2500, // pause at end of loop showSteps = !(qs.showsteps === 'false'), transition = qs.transition || 0, stage = qs.stage || 'insmod', tickSize = qs.ticksize || 1, file = qs.file || null, paths = qs.paths || null, generate = qs.gen || null, shape = qs.shape || null, radius = +qs.radius || 25, fill = qs.fill || 'white', nodeStroke = qs.nodestroke || 'black', nodeStrokeWidth = qs.nodestrokewidth || 1, randomize = qs.randomize === 'true', doReinit = !(qs.reinit==="false"), doDisplacement = !(qs.displace==="false"), doAlignment = !(qs.align==="false"), doOrdering = !(qs.order==="false"), linkLength = +qs.linklength || 30, edgeStroke = qs.edgestroke || 'black', edgeStrokeWidth = qs.edgestrokewidth || 1, edgeOpacity = +qs.opacity || 1, layoutAlgorithm = qs.algo || 'cola', appLayout = null, useAppLayout = false, nodePrefix = qs.prefix || '', timeLimit = qs.limit !== undefined ? +qs.limit : 10000, explore = qs.explore; if(edgeStroke && (/[0-9A-Fa-f]{6}/.test(edgeStroke) || /[0-9A-Fa-f]{3}/.test(edgeStroke))) edgeStroke = '#' + edgeStroke; var min = 2, max = 12; var begin = 2, end = 12, curr = begin; var doRender = true; var demoDiagram = dc_graph.diagram('#graph'), runner; var overview; function do_status() { $('#now').css('left', (curr-min)/(max-min)*100 + '%'); $('#status').text('[' + begin + '..' + end + '] : ' + curr); } function show_stats(data_stats, layout_stats) { $('#graph-stats').html(['<table>', '<tr><td>Showing</td><td>' + layout_stats.nnodes + '/' + data_stats.totnodes + ' nodes</td></tr>', '<tr><td></td><td>' + layout_stats.nedges + '/' + data_stats.totedges + ' edges</td></tr>', '<tr><td>Last time</td><td>' + (runner.lastTime()/1000).toFixed(3) + 's</td></tr>', '<tr><td>Avg time</td><td>' + (runner.avgTime()/1000).toFixed(3) + 's</td></tr>', '</table>'].join('')); } function show_stepper() { $('#stepper').show(); $('#controls').width(300); } do_status(); var source; if(!generate && !file) file = "qfs.json"; appLayout = qs.applayout || file === 'qfs.json' && 'qfs'; if(appLayout === 'none' || !app_layouts[appLayout]) appLayout = null; if(appLayout) { useAppLayout = true; if('useapplayout' in qs) { useAppLayout = !!+qs.useapplayout; $('#use-app-layout').prop('checked', useAppLayout); } $('#app-options').show(); app_layouts[appLayout].init && app_layouts[appLayout].init(); } if(file) source = function(callback) { dc_graph.load_graph(file, callback); }; else if(generate) source = function(callback) { // name plus at least one number, separated by commas var parts = /^([a-zA-Z]+)([0-9]+(?:,[0-9]+)*)$/.exec(generate); if(!parts || !parts[0]) throw new Error("couldn't parse generator"); var name = parts[1], args = parts[2].split(',').map(function(n) { return +n; }); var env = { linkLength: linkLength, nodePrefix: nodePrefix }; dc_graph.generate(name, args, env, callback); }; if(shape) { var parts = shape.split(','); shape = {shape: parts[0]}; switch(parts[0]) { case 'polygon': shape.sides = +parts[1]; shape.skew = +parts[2] || 0; shape.distortion = +parts[3] || 0; shape.rotation = +parts[4] || 0; break; } shape.regular = qs.regular==='true'; shape.squeeze = qs.squeeze==='true'; } function show_type_graph(nodes, edges, sourceattr, targetattr) { $('#overview').show(); if(!overview) overview = dc_graph.diagram('#overview', 'overview'); var typegraph = dc_graph.build_type_graph(nodes, edges, function(n) { return n.name; }, function(n) { return n.type; }, function(e) { return e[sourceattr]; }, function(e) { return e[targetattr]; }); var tedges = dc_graph.flat_group.make(typegraph.edges, function(d) { return d.type; }), tnodes = dc_graph.flat_group.make(typegraph.nodes, function(d) { return d.type; }); overview.width(250) .height(250) .nodeRadius(15) .baseLength(25) .nodeLabel(function(n) { return n.value.type; }) .nodeDimension(tnodes.dimension).nodeGroup(tnodes.group) .edgeDimension(tedges.dimension).edgeGroup(tedges.group) .edgeSource(function(e) { return e.value.source; }) .edgeTarget(function(e) { return e.value.target; }) .render(); } source(function(error, data) { if(error) { console.log(error); return; } var graph_data = dc_graph.munge_graph(data), nodes = graph_data.nodes, edges = graph_data.edges, sourceattr = graph_data.sourceattr, targetattr = graph_data.targetattr, nodekeyattr = graph_data.nodekeyattr; if(randomize) { edges.forEach(function(e) { e.order = Math.random()*1000; }); nodes.forEach(function(n) { n.order = Math.random()*1000; }); } if(false) // appLayout) show_type_graph(nodes, edges, sourceattr, targetattr); var edge_flat = dc_graph.flat_group.make(edges, function(d) { return d[sourceattr] + '-' + d[targetattr] + (d.par ? ':' + d.par : ''); }), node_flat = dc_graph.flat_group.make(nodes, function(d) { return d[nodekeyattr]; }); appLayout && app_layouts[appLayout].data && app_layouts[appLayout].data(nodes, edges); runner = make_runner(run, step, function() { return curr < end ? steptime : pause; }); function run() { do_status(); if(doReinit) demoDiagram.initLayoutOnRedraw(explore || appLayout && useAppLayout); startDim.filterRange([0, curr]); $('#run-indicator').show(); if(doRender) { dc.renderAll(); doRender = false; } else dc.redrawAll(); done = false; } function step() { if(++curr>end) curr = begin; run(); } window.start_stop = function() { runner.toggle(); }; var rule_constraints = null; var rules = appLayout && app_layouts[appLayout].rules; if(rules) { rules.edges.forEach(function(c) { if(!doDisplacement && c.produce && !c.produce.type) c.disable = true; if(!doAlignment && c.produce && c.produce.type === 'alignment') c.disable = true; if(!doOrdering && c.produce && c.produce.type === 'ordering') c.disable = true; }); rule_constraints = dc_graph.constraint_pattern(rules); } function constrain(diagram, nodes, edges) { var constraintses = []; if(appLayout && useAppLayout && rule_constraints) constraintses.push(rule_constraints(diagram, nodes, edges)); if(appLayout && useAppLayout && app_layouts[appLayout].constraints) constraintses.push(app_layouts[appLayout].constraints(diagram, nodes, edges)); var circles = {}; nodes.forEach(function(n, i) { if(n.orig.value.circle) { var circ = n.orig.value.circle; if(!circles[circ]) circles[circ] = []; circles[circ].push({node: n.orig.key}); } }); constraintses.push(Object.keys(circles).map(function(circ) { return { type: 'circle', nodes: circles[circ] }; })); return Array.prototype.concat.apply([], constraintses); } var engine = dc_graph.spawn_engine(qs.layout || 'cola', qs, qs.worker != 'false'); demoDiagram .width('auto') .height('auto') .restrictPan(true) .layoutEngine(engine) .timeLimit(timeLimit) .transitionDuration(transition) .stageTransitions(stage) .nodeDimension(node_flat.dimension).nodeGroup(node_flat.group) .edgeDimension(edge_flat.dimension).edgeGroup(edge_flat.group) .edgeSource(function(e) { return e.value[sourceattr]; }) .edgeTarget(function(e) { return e.value[targetattr]; }) .nodeLabel(qs.label ? function(n) { return n.value.value[qs.label]; } : function(n) { return n.value.name.split('/'); }) .nodeShape(shape) .nodeRadius(radius) .nodeFill(appLayout && app_layouts[appLayout].colors || fill) .nodeStroke(nodeStroke) .nodeStrokeWidth(nodeStrokeWidth) .nodeFixed(appLayout && app_layouts[appLayout].node_fixed) .constrain(constrain) .edgeArrowhead(function(kv) { return kv.value.undirected ? null : 'vee'; }) .edgeOpacity(edgeOpacity) .edgeStroke(edgeStroke) .edgeStrokeWidth(edgeStrokeWidth) .on('end', function() { $('#run-indicator').hide(); runner.endStep(); show_stats({totnodes: nodes.length, totedges: edges.length}, demoDiagram.getStats()); }) .child('highlight-neighbors', dc_graph.highlight_neighbors({edgeStroke: 'orangered', edgeStrokeWidth: 3})); if(qs.elabel) demoDiagram.edgeLabel(function(e) { return e.value[qs.elabel]; }); if(engine.layoutAlgorithm() === 'cola') { demoDiagram .showLayoutSteps(showSteps); engine .tickSize(tickSize) .lengthStrategy(generate ? 'individual' : useAppLayout ? app_layouts[appLayout].lengthStrategy || 'none' : 'symmetric'); if(linkLength) engine.baseLength(linkLength); } appLayout && app_layouts[appLayout].initDiagram && app_layouts[appLayout].initDiagram(demoDiagram); if(randomize) { demoDiagram.nodeOrdering(function(kv) { return kv.value.order; }) .edgeOrdering(function(kv) { return kv.value.order; }); } var expander = null, expanded = {}; if(explore) { // second dimension on keys so that first will observe it expander = node_flat.crossfilter.dimension(function(d) { return d.name; }); function apply_expander_filter() { expander.filterFunction(function(key) { return expanded[key]; }); } function adjacent_edges(key) { return edge_flat.group.all().filter(function(kv) { return kv.value[sourceattr] === key || kv.value[targetattr] === key; }); } function out_edges(key) { return edge_flat.group.all().filter(function(kv) { return kv.value[sourceattr] === key; }); } function in_edges(key) { return edge_flat.group.all().filter(function(kv) { return kv.value[targetattr] === key; }); } function adjacent_nodes(key) { return adjacent_edges(key).map(function(kv) { return kv.value[sourceattr] === key ? kv.value[targetattr] : kv.value[sourceattr]; }); } var nodelist = demoDiagram.nodeGroup().all().map(function(n) { return { value: n.key, label: demoDiagram.nodeLabel()(n) }; }); apply_expander_filter(); if(qs.directional) { demoDiagram.child('expand-collapse', dc_graph.expand_collapse(function(key, dir) { // get_degree switch(dir) { case 'out': return out_edges(key).length; case 'in': return in_edges(key).length; default: throw new Error('unknown direction ' + dir); } }, function(key, dir) { // expand switch(dir) { case 'out': out_edges(key).forEach(function(e) { expanded[e.value[targetattr]] = true; }); break; case 'in': in_edges(key).forEach(function(e) { expanded[e.value[sourceattr]] = true; }); break; default: throw new Error('unknown direction ' + dir); } apply_expander_filter(); run(); }, function(key, collapsible, dir) { // collapse switch(dir) { case 'out': out_edges(key).forEach(function(e) { if(collapsible(e.value[targetattr])) expanded[e.value[targetattr]] = false; }); break; case 'in': in_edges(key).forEach(function(e) { if(collapsible(e.value[sourceattr])) expanded[e.value[sourceattr]] = false; }); break; default: throw new Error('unknown direction ' + dir); } apply_expander_filter(); run(); }, ['out', 'in'])); } else { demoDiagram.child('expand-collapse', dc_graph.expand_collapse(function(key) { // get_degree return adjacent_edges(key).length; }, function(key) { // expand adjacent_nodes(key).forEach(function(nk) { expanded[nk] = true; }); apply_expander_filter(); run(); }, function(key, collapsible) { // collapse adjacent_nodes(key).filter(collapsible).forEach(function(nk) { expanded[nk] = false; }); apply_expander_filter(); run(); })); } $('#search-wrapper') .show(); $('#search') .autocomplete({ source: nodelist, focus: function( event, ui ) { $( "#search" ).val( ui.item.label ); return false; }, select: function(event, ui) { $( "#search" ).val( ui.item.label ); expanded = {}; expanded[ui.item.value] = true; apply_expander_filter(); run(); return false; } }) .attr("autocomplete", "on"); } // this is kind of a brain-dead way to test transitions // i mean, you can cram the concept of adding and deleting stuff over time // into crossfilter data, but do you really want to do that? var startDim = node_flat.crossfilter.dimension(function(d) { return d.start || 0; }), startGroup = startDim.group(); $("#time-range").slider({ range: true, min: min, max: max, values: [begin, end], slide: function( event, ui ) { begin = ui.values[0]; end = ui.values[1]; do_status(); } }); $('#use-app-layout').change(function(val) { useAppLayout = $(this).is(':checked'); demoDiagram.lengthStrategy(useAppLayout ? 'none' : 'symmetric') .relayout(); doRender = true; if(!runner.isRunning()) run(); }); // do not brush too fast dc.constants.EVENT_DELAY = 100; runner.init(); if(qs.play) runner.toggle(); if(qs.infdraw) demoDiagram.layoutUnchanged(true); });