element where we want to display the graph.
16 | const graphElem = document.getElementById('git-g');
17 |
18 | // Clear anything currently displayed in this element.
19 | d3.select(graphElem).selectAll('*').remove();
20 |
21 | // Create a D3 selection with this element.
22 | const svg = d3.select(graphElem);
23 |
24 | // Create a new instance of dagreD3's renderer constructor.
25 | // dagreD3 is the global.dagreD3 object from dagre-d3.js.
26 | const renderer = new dagreD3.Renderer();
27 |
28 | // Append our graph to the page.
29 | const layout = dagreD3.layout().rankDir('LR');
30 | renderer.layout(layout)
31 | .run(dagreD3.json.decode(nodes, links), svg.append('g'));
32 |
33 | // Adjust the height our SVG to fit the content.
34 | const h = document.querySelector('#git-g g').getBoundingClientRect().height;
35 | let newHeight = h + 40;
36 | newHeight = newHeight < 80 ? 80 : newHeight;
37 | const $svg = document.getElementById('git-svg');
38 | $svg.setAttribute('height', newHeight);
39 |
40 | // Add zoom functionality.
41 | d3.select($svg).call(d3.behavior.zoom().on('zoom', () => {
42 | const ev = d3.event;
43 | svg.select('g')
44 | .attr('transform', `translate(${ev.translate}) scale(${ev.scale})`);
45 | }));
46 | };
47 |
48 | gitVisualization.createGraph = (nestedCommitArr) => {
49 | const gitGraph = new DAG();
50 | for (let i = 0; i < nestedCommitArr.length - 1; i++) {
51 | if (nestedCommitArr[i][1].match(/\s/)) {
52 | nestedCommitArr[i][1] = nestedCommitArr[i][1].split(/\s/);
53 | }
54 | gitGraph.addEdges(nestedCommitArr[i][0], nestedCommitArr[i][2], null, nestedCommitArr[i][1]);
55 | }
56 |
57 | const result = {};
58 | const nodes = [];
59 | const links = [];
60 | const names = gitGraph.names;
61 | const vertices = gitGraph.vertices;
62 | let linkNum = 1;
63 |
64 | // For each commit in the names array...
65 | for (let i = 0; i < names.length; i++) {
66 | // Create a node and push it to the nodes array.
67 | const node = {};
68 | const hash = names[i];
69 | node.id = hash;
70 | node.value = {};
71 | node.value.label = hash;
72 | node.value.message = vertices[hash].value || 'No commit message available';
73 | nodes.push(node);
74 |
75 | // Create a link for each of the commit's parents and push it to the links array.
76 | const parents = vertices[hash].incomingNames;
77 | for (let j = 0; j < parents.length; j ++) {
78 | const link = {};
79 | link.u = hash;
80 | link.v = parents[j];
81 | link.value = {};
82 | link.value.label = `link ${linkNum}`;
83 | links.push(link);
84 | linkNum ++;
85 | }
86 | }
87 |
88 | result.nodes = nodes;
89 | result.links = links;
90 | return result;
91 | };
92 |
93 | export default gitVisualization;
94 |
--------------------------------------------------------------------------------
/AnimationData/StructureSchema.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-param-reassign */
2 | /* eslint-disable strict */
3 | /* eslint-disable no-unused-expressions */
4 |
5 | // Use strict mode so that we can use let and const.
6 | 'use strict';
7 |
8 | const exec = require('child_process').exec;
9 | const simpleGit = require('simple-git');
10 | const path = require('path');
11 |
12 | const container = {
13 | css: null,
14 | html: null,
15 | js: null,
16 | rb: null,
17 | py: null,
18 | json: null,
19 | pdf: null,
20 | png: null,
21 | };
22 |
23 | function modifiedAnimation(info, object, item, string) {
24 | for (let i = 0, len = info.length; i < len; i++) {
25 | if (info[i].indexOf(item) > -1) {
26 | object.level = '#ccc';
27 | object.icon = `assets/64pxRed/${string}.png`;
28 | return;
29 | }
30 | }
31 | return;
32 | }
33 |
34 | function terminalParse(item, object) {
35 | let itemParse;
36 | if (item[item.length - 1] === '/') {
37 | itemParse = 'folder';
38 | object.name = item.substring(0, item.length - 1);
39 | object.type = 'directory';
40 | object.icon = 'assets/64pxBlue/folder.png';
41 | } else {
42 | itemParse = item.replace(/^\w+./, '');
43 | if (!(itemParse in container)) itemParse = 'file';
44 | object.icon = `assets/64pxBlue/${itemParse}.png`;
45 | return itemParse;
46 | }
47 | return itemParse;
48 | }
49 |
50 | const schemaMaker = (termOutput, directoryName, modified) => {
51 | let schema = {
52 | name: directoryName,
53 | children: [],
54 | position_x: '-10px',
55 | position_y: '-20px',
56 | value: 40,
57 | icon: 'assets/64pxBlue/folder.png',
58 | level: '#ccc',
59 | };
60 |
61 | // Loop through reply and put it into a structure D3 can read
62 | termOutput.forEach(index => {
63 | if (index === '' || (index[0] === '.' && index.substring(0, 4) !== '.git')) return undefined;
64 | const elementObj = {
65 | name: index,
66 | level: '#ccc',
67 | };
68 | if (index.substring(0, 4) === '.git') {
69 | if (index.substring(index.length - 1) === '/') {
70 | elementObj.icon = 'assets/folder.png';
71 | elementObj.name = index.substring(0, index.length - 1);
72 | } else {
73 | elementObj.icon = 'assets/git.png';
74 | }
75 | schema.children.push(elementObj);
76 | return undefined;
77 | }
78 |
79 | const temp = terminalParse(index, elementObj);
80 | if (modified) modifiedAnimation(modified, elementObj, index, temp);
81 | schema.children.push(elementObj);
82 | return undefined;
83 | });
84 | schema = [schema];
85 | return schema;
86 | };
87 |
88 | module.exports = {
89 | dataSchema(pwd, asyncWaterfallCallback) {
90 | // child process that gets all items in a directory
91 | const command = `cd ${pwd}; ls -ap;`;
92 |
93 | exec(command, (err, stdout) => {
94 | const stdoutArr = stdout.split('\n');
95 | const currentDirectoryName = path.parse(pwd).name;
96 | let modifiedFiles;
97 | // git command to check git status
98 | simpleGit(pwd).status((error, i) => {
99 | modifiedFiles = i.modified;
100 | const schema = schemaMaker(stdoutArr, currentDirectoryName, modifiedFiles);
101 | process.send ? process.send({ schema }) : asyncWaterfallCallback(null, schema);
102 | return schema;
103 | });
104 | });
105 | },
106 | schemaMaker,
107 | };
108 |
--------------------------------------------------------------------------------
/components/Lesson.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | // We don't need to define ipcRenderer because it will be loaded by the time this file runs.
3 |
4 | import React, { Component } from 'react';
5 | import lessons from './../lessons/lesson-list';
6 |
7 | export default class Lesson extends Component {
8 | constructor(props) {
9 | super(props);
10 | this.handleButtonClick = this.handleButtonClick.bind(this);
11 | }
12 |
13 | handleButtonClick(lesson, slideNumber, errorVisible) {
14 | // If this slide has a buttonFunction, run it.
15 | if (lesson[slideNumber].buttonFunction) {
16 | lesson[slideNumber].buttonFunction();
17 |
18 | // Listen for the result of the buttonFunction test.
19 | ipcRenderer.once('test-result-2', (event, arg) => {
20 | // If the user passed the test (if arg is true), advance and hide the error message.
21 | if (arg) {
22 | this.advance(lesson, slideNumber);
23 | if (errorVisible) this.props.setErrorVisibility(false);
24 | // If the user failed the test, show the error message.
25 | } else {
26 | this.props.setErrorVisibility(true);
27 | }
28 | });
29 |
30 | // If the slide does not have a buttonFunction, simply advance.
31 | } else {
32 | this.advance(lesson, slideNumber);
33 | }
34 | }
35 |
36 | advance(lesson, slideNumber) {
37 | // If we're on the last slide, go to slide 0.
38 | // If not, go to the next slide.
39 | const destination = slideNumber === lesson.length - 1 ? 0 : slideNumber + 1;
40 | this.props.changeSlide(destination);
41 | }
42 |
43 | buildStyles() {
44 | const styles = {};
45 |
46 | styles.lesson = { float: 'left', height: '100%', width: '35%', overflow: 'scroll',
47 | fontFamily: 'Helvetica, sans-serif', backgroundColor: 'white' };
48 | styles.padder = { padding: '16px' };
49 | styles.img = { float: 'right' };
50 | styles.error = { color: 'red' };
51 |
52 | return styles;
53 | }
54 |
55 | // The X icon is from https://www.iconfinder.com/icons/118584/x_icon#size=32
56 | render() {
57 | const currentLesson = lessons[this.props.lessonNumber].content;
58 | const styles = this.buildStyles();
59 |
60 | // Render the error message only if it should be visible.
61 | const error = this.props.errorVisible ?
62 |
63 | { currentLesson[this.props.slideNumber].errorMessage }
64 |
:
65 | undefined;
66 |
67 | return (
68 |
69 |
70 |
71 |
74 |
Slide {this.props.slideNumber + 1} of {currentLesson.length}
75 | {currentLesson[this.props.slideNumber].lessonText}
76 | {error}
77 |
{
78 | this.handleButtonClick(currentLesson, this.props.slideNumber,
79 | this.props.errorVisible); }
80 | }
81 | >
82 | { currentLesson[this.props.slideNumber].buttonText }
83 |
84 |
85 |
86 |
87 | );
88 | }
89 | }
90 |
91 | Lesson.propTypes = {
92 | lessonNumber: React.PropTypes.number,
93 | slideNumber: React.PropTypes.number,
94 | errorVisible: React.PropTypes.bool,
95 | changeSlide: React.PropTypes.func,
96 | hideLesson: React.PropTypes.func,
97 | setErrorVisibility: React.PropTypes.func,
98 | };
99 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict */
2 |
3 | // Use strict mode so that we can use let and const.
4 | 'use strict';
5 |
6 | const electron = require('electron');
7 | const app = electron.app;
8 | const ipcMain = require('electron').ipcMain;
9 | const BrowserWindow = electron.BrowserWindow;
10 | const animationDataSchema = require('./AnimationData/StructureSchema');
11 | const async = require('async');
12 |
13 | // Require the child_process module so we can communicate with the user's terminal
14 | const exec = require('child_process').exec;
15 | const fork = require('child_process').fork;
16 |
17 | let mainWindow = null;
18 |
19 | function ptyChildProcess() {
20 | const ptyInternal = require.resolve('./ptyInternal');
21 | const forkProcess = fork(ptyInternal);
22 |
23 | // Prevent the app from showing dummy data on initial load.
24 | ipcMain.on('ready-for-schema', (event, arg) => forkProcess.send({ message: arg }));
25 |
26 | // Ensure that we have Git data to show the first time the users toggles to the Git view.
27 | ipcMain.on('ready-for-git', (event, arg) => forkProcess.send({ message: arg }));
28 |
29 | // For the lesson, ensure that we know the user's current directory before testing whether they
30 | // created a 'new-project' directory.
31 | ipcMain.on('ready-for-dir', (event, arg) => forkProcess.send({ message: arg }));
32 |
33 | // When user inputs data in terminal, start fork and run pty inside.
34 | // Each keystroke is an arg.
35 | ipcMain.on('command-message', (event, arg) => {
36 | forkProcess.send({ message: arg });
37 | forkProcess.removeAllListeners('message');
38 | forkProcess.on('message', (message) => {
39 | // Send what is diplayed in terminal
40 | if (message.data) event.sender.send('terminal-reply', message.data);
41 | // Send animation schema
42 | if (message.schema) event.sender.send('direc-schema', message.schema);
43 | // Send Git data
44 | if (message.gitGraph) event.sender.send('git-graph', message.gitGraph);
45 | // Send current directory
46 | if (message.currDir) event.sender.send('curr-dir', message.currDir);
47 | });
48 | });
49 | }
50 |
51 | // Run the appropriate test to see whether the user is ready to advance in the lesson.
52 | function slideTests() {
53 | // Listen for commands from the lesson file.
54 | ipcMain.on('command-to-run', (event, arg) => {
55 | // Upon receiving a command, run it in the terminal.
56 | exec(arg, (err, stdout) => {
57 | // Send the terminal's response back to the lesson.
58 | if (err) return event.sender.send('terminal-output', err.toString());
59 | return event.sender.send('terminal-output', stdout);
60 | });
61 | });
62 |
63 | // Listen for a Boolean from the lesson file.
64 | ipcMain.on('test-result-1', (event, arg) => {
65 | // Upon receiving it, send it to the Dashboard.
66 | event.sender.send('test-result-2', arg);
67 | });
68 | }
69 |
70 |
71 | app.on('window-all-closed', () => {
72 | if (process.platform !== 'darwin') {
73 | app.quit();
74 | }
75 | });
76 |
77 | app.on('ready', () => {
78 | const dummy = new BrowserWindow({ show: false });
79 | // Force replace icon to load
80 | dummy.setProgressBar(-1);
81 |
82 | mainWindow = new BrowserWindow({
83 | width: 1200,
84 | height: 700,
85 | minWidth: 900,
86 | minHeight: 500,
87 | titleBarStyle: 'hidden-inset',
88 | });
89 |
90 | mainWindow.loadURL(`file://${__dirname}/index.html`);
91 |
92 | // Initialize fork
93 | mainWindow.webContents.on('did-finish-load', () => {
94 | setTimeout(async.waterfall([
95 | async.apply(animationDataSchema.dataSchema, process.env.HOME),
96 | (data) => {
97 | mainWindow.webContents.send('direc-schema', data);
98 | },
99 | ]), 0);
100 |
101 | mainWindow.webContents.send('term-start-data', `${process.env.HOME} $ `);
102 |
103 | ptyChildProcess();
104 | slideTests();
105 | });
106 |
107 | // Set mainWindow back to null when the window is closed.
108 | mainWindow.on('closed', () => {
109 | mainWindow = null;
110 | });
111 | });
112 |
--------------------------------------------------------------------------------
/components/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { render } from 'react-dom';
3 |
4 | // Import other components
5 | import Animation from './Animation';
6 | import Lesson from './Lesson';
7 | import Dropdown from './Dropdown';
8 | import Terminal from './Terminal';
9 |
10 | // Import the list of existing lessons
11 | import lessons from './../lessons/lesson-list';
12 |
13 | export default class Dashboard extends Component {
14 | constructor(props) {
15 | super(props);
16 | this.setErrorVisibility = this.setErrorVisibility.bind(this);
17 | this.setStructureAnimationVisibility = this.setStructureAnimationVisibility.bind(this);
18 | this.setDropdownVisibility = this.setDropdownVisibility.bind(this);
19 | this.showLesson = this.showLesson.bind(this);
20 | this.hideLesson = this.hideLesson.bind(this);
21 | this.changeSlide = this.changeSlide.bind(this);
22 | this.state = {
23 | lessonNumber: undefined,
24 | slideNumber: undefined,
25 | dropdownVisible: props.initialDropdownVisible,
26 | structureAnimationVisible: props.initialStructureAnimationVisible,
27 | lessonVisible: props.initialLessonVisible,
28 | errorVisible: props.initialErrorVisible,
29 | };
30 | }
31 |
32 | setErrorVisibility(boolean) {
33 | this.setState({
34 | errorVisible: boolean,
35 | });
36 | }
37 |
38 | setStructureAnimationVisibility(boolean) {
39 | this.setState({
40 | structureAnimationVisible: boolean,
41 | });
42 | }
43 |
44 | setDropdownVisibility() {
45 | this.setState({
46 | dropdownVisible: !this.state.dropdownVisible,
47 | });
48 | }
49 |
50 | showLesson(index) {
51 | this.setState({
52 | lessonNumber: index,
53 | slideNumber: 0,
54 | lessonVisible: true,
55 | });
56 | }
57 |
58 | hideLesson() {
59 | this.setState({
60 | lessonVisible: false,
61 | });
62 | }
63 |
64 | changeSlide(number) {
65 | this.setState({
66 | slideNumber: number,
67 | });
68 | }
69 |
70 | buildStyles() {
71 | const styles = {};
72 |
73 | styles.dashboard = { height: '100%', width: '100%' };
74 | styles.dropdown = { height: '40px', width: '100%', backgroundColor: 'tranparent',
75 | borderBottom: '1px solid #D2D2D2' };
76 | styles.main = { height: '100vh', width: '100%' };
77 | styles.upperHalf = { height: '55%', width: '100%' };
78 | styles.span = { textAlign: 'center', marginLeft: '10%', bottom: '45%', position: 'absolute',
79 | color: '#B0AEAE', fontSize: '300%', fontFamily: 'monospace' };
80 | styles.lowerHalf = { height: '45%', width: '100%', backgroundColor: '#151414',
81 | borderTop: '10px solid #D2D2D2' };
82 |
83 | return styles;
84 | }
85 |
86 | render() {
87 | const styles = this.buildStyles();
88 |
89 | // Create an array of lesson names to pass down to Dropdown as props.
90 | // (We don't need to pass down all the lesson contents.)
91 | const lessonInfo = lessons.map(lesson =>
92 | ({
93 | name: lesson.name,
94 | iconPath: lesson.iconPath,
95 | })
96 | );
97 |
98 | // Render the lesson only if it should be visible.
99 | const lesson = this.state.lessonVisible ?
100 | : undefined;
104 |
105 | return (
106 |
107 |
108 |
112 |
113 |
114 |
120 |
121 | {lesson}
122 |
123 |
124 |
125 |
126 | );
127 | }
128 | }
129 |
130 | Dashboard.propTypes = {
131 | initialDropdownVisible: React.PropTypes.bool,
132 | initialStructureAnimationVisible: React.PropTypes.bool,
133 | initialLessonVisible: React.PropTypes.bool,
134 | initialErrorVisible: React.PropTypes.bool,
135 | };
136 |
137 | Dashboard.defaultProps = {
138 | initialDropdownVisible: false,
139 | initialStructureAnimationVisible: true,
140 | initialLessonVisible: false,
141 | initialErrorVisible: false,
142 | };
143 |
144 | render( , document.getElementById('dashboard-container'));
145 |
--------------------------------------------------------------------------------
/components/StructureAnimation.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | // We don't need to define ipcRenderer because it will be loaded by the time this file runs.
3 | /* eslint-disable no-param-reassign */
4 |
5 | import React, { Component } from 'react';
6 | import d3 from 'd3';
7 | import Tree from './Tree';
8 | import Link from './Link';
9 |
10 | // Import default file structure data
11 | import treeData from './../AnimationData/treeStructure';
12 |
13 | // In this component, React is responsible for DOM structure (adding and removing elements)
14 | // and D3 is responsible for styling.
15 | // This blog post provided inspiration:
16 | // https://medium.com/@sxywu/on-d3-react-and-a-little-bit-of-flux-88a226f328f3#.ztcxqykek
17 |
18 | export default class StructureAnimation extends Component {
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | treeData: props.initialTreeData,
23 | margin: props.initialMargin,
24 | windowWidth: props.initialWindowWidth,
25 | windowHeight: props.initialWindowHeight,
26 | };
27 | }
28 |
29 | componentDidMount() {
30 | // This fires on initial load and when the user toggles between the structure and Git views.
31 | ipcRenderer.send('ready-for-schema', '\n');
32 | ipcRenderer.on('direc-schema', (e, arg) => {
33 | this.updateTree(arg);
34 | });
35 | }
36 |
37 | updateTree(newSchema) {
38 | this.setState({
39 | treeData: newSchema,
40 | windowHeight: window.innerHeight,
41 | windowWidth: window.innerWidth,
42 | });
43 | }
44 |
45 | buildStyles(windowWidth, windowHeight, margin) {
46 | const styles = {};
47 |
48 | // Create variables to determine the size of the tree and the SVG containing it.
49 | styles.viewBoxWidth = this.state.windowWidth * 7 / 12;
50 | styles.viewBoxHeight = this.state.windowHeight * 7 / 24;
51 | styles.viewBoxString = `0 0 ${styles.viewBoxWidth} ${styles.viewBoxHeight}`;
52 | styles.translationValue = `translate(${margin.left}, ${margin.top})`;
53 |
54 | return styles;
55 | }
56 |
57 | buildTree(viewBoxHeight, viewBoxWidth) {
58 | const layout = {};
59 |
60 | // Create a tree layout.
61 | // The first argument provided to size() is the maximum x-coordinate D3 will assign.
62 | // The second argument is the maximum y-coordinate.
63 | // We're switching width and height here because d3 by default makes trees that branch
64 | // vertically, and we want a tree that branches horizontally.
65 | // In other words, nodes that are on the same level will have the same y-coordinate
66 | // but different x-coordinates.
67 | const tree = d3.layout.tree()
68 | .size([viewBoxHeight * 0.93, viewBoxWidth * 0.90]);
69 |
70 | // The first node in the array is the root of the tree.
71 | // Set its initial coordinates - where it should enter.
72 | const root = this.state.treeData[0];
73 | root.x0 = viewBoxHeight / 2;
74 | root.y0 = 0;
75 |
76 | // Create an array of nodes associated with the root.
77 | // (The returned array is basically a flattened version of treeData.)
78 | // Before the next line runs, each node has children, level, name, and value properties.
79 | // After the next line runs, each node also has parent, depth, x, and y properties.
80 | // (D3 tree nodes always have parent, child, depth, x, and y properties.)
81 | // We will pass one node from this array to each Tree as props.data.
82 | layout.nodes = tree.nodes(root);
83 |
84 | // Create an array of objects representing all parent-child links
85 | // in the nodes array we just created.
86 | layout.linkSelection = tree.links(layout.nodes);
87 |
88 | layout.nodes.forEach(d => {
89 | // The default y-coordinates provided by d3.tree will make the tree stretch
90 | // all the way across the screen.
91 | // We want to compress the tree a bit so that there's room for file/directory names
92 | // to the right of the deepest level.
93 | d.y *= 0.8;
94 |
95 | // If the node has a parent, set its initial coordinates to the parent's initial coordinates.
96 | // In other words, the parent and child should enter from the same place.
97 | if (d.parent) {
98 | d.x0 = d.parent.x0;
99 | d.y0 = d.parent.y0;
100 | }
101 | });
102 |
103 | return layout;
104 | }
105 |
106 | render() {
107 | const styles = this.buildStyles(this.state.windowWidth, this.state.windowHeight,
108 | this.state.margin);
109 |
110 | const layout = this.buildTree(styles.viewBoxHeight, styles.viewBoxWidth);
111 |
112 | // Create a counter variable that we'll use to stagger the items in our animation.
113 | let counter = 1;
114 |
115 | const trees = layout.nodes && layout.nodes.map((node) => {
116 | // Save the starting value of node.y as node.yOriginal so we can use it in the future.
117 | if (node.yOriginal === undefined) node.yOriginal = node.y;
118 |
119 | // Give the node an index property to determine how it will be staggered.
120 | node.index = counter ++;
121 |
122 | // If node.index is odd, adjust node.y, which determines the position of this tree and the
123 | // link to it.
124 | if (node.index % 2 === 1) node.y = node.yOriginal * 0.9;
125 |
126 | // Parse node.name to extract a unique key for this tree.
127 | node.name = node.name.trim();
128 | const nameEndsWithSlash = node.name.indexOf('/') === node.name.length - 1;
129 | const key = nameEndsWithSlash ? node.name.slice(0, node.name.length - 1) : node.name;
130 | return ( );
131 | });
132 |
133 | const links = layout.linkSelection && layout.linkSelection.map((link) => {
134 | link.target.name = link.target.name.trim();
135 | const nameEndsWithSlash = link.target.name.indexOf('/') === link.target.name.length - 1;
136 | const key = nameEndsWithSlash ? link.target.name.slice(0, link.target.name.length - 1) :
137 | link.target.name;
138 | return ( );
139 | });
140 |
141 | return (
142 |
143 |
144 |
145 | {links}
146 | {trees}
147 |
148 |
149 |
150 | );
151 | }
152 | }
153 |
154 | StructureAnimation.defaultProps = {
155 | initialTreeData: treeData,
156 | initialMargin: { top: 0, left: 20 },
157 | // The initial window dimensions are specified in app.on('ready') in main.js.
158 | initialWindowWidth: 1200,
159 | initialWindowHeight: 700,
160 | };
161 |
162 | StructureAnimation.propTypes = {
163 | initialTreeData: React.PropTypes.array,
164 | initialMargin: React.PropTypes.object,
165 | initialWindowWidth: React.PropTypes.number,
166 | initialWindowHeight: React.PropTypes.number,
167 | };
168 |
--------------------------------------------------------------------------------
/lessons/git-on-your-computer.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | /* eslint-disable no-undef */
3 | // We don't need to define ipcRenderer because it will be loaded by the time this file runs.
4 |
5 | // Import React so we can use JSX.
6 | import React from 'react';
7 |
8 | // Create a variable to hold the name of the current directory.
9 | // We will update this variable once we start running tests.
10 | let currentDirectory;
11 |
12 | // Create an array of slides to export.
13 | const slides = [
14 | {
15 | lessonText:
16 |
17 |
Welcome!
18 |
If you're learning to code, chances are you've heard about something called Git. Git can be intimidating for beginners - but it doesn't have to be!
19 |
In this lesson, you'll...
20 |
21 | Set up Git
22 | Set up a project
23 | Learn some basic Git commands that you can use to track your new project
24 | Learn a bit about GitHub, a popular website that uses Git
25 |
26 |
Don't worry - we'll walk you through each step. Ready to get started?
27 |
,
28 | buttonText: 'Ready!',
29 | buttonFunction() {
30 | // Start listening for updates to currentDirectory
31 | ipcRenderer.on('curr-dir', (event, arg) => {
32 | currentDirectory = arg;
33 | });
34 | ipcRenderer.send('test-result-1', true);
35 | },
36 |
37 | }, {
38 | lessonText:
39 |
40 |
Step 1: Set up Git
41 |
Meet the terminal
42 |
For this step, we'll use the terminal , which lets you and your Mac communicate using just text.
43 |
Software developers use their terminals every day, but no need to worry if this tool is new to you. We've embedded a terminal in this app to help you learn the ropes - it's the black box to the right.
44 |
, // '
45 | buttonText: 'Got it!',
46 |
47 | }, {
48 | lessonText:
49 |
50 |
Step 1: Set up Git
51 |
Check your version
52 |
To check whether your computer already has Git installed, type git --version
53 | and then click Enter.
54 |
If you see a version number - like git version 2.6.4 - you have Git
55 | installed, and you're ready for step two.
56 |
If not, you can download Git from http://git-scm.com/downloads. Then follow the
57 | directions at the top of this page to confirm that Git is installed correctly.
58 |
, // '
59 | buttonText: "OK - what's next?",
60 | buttonFunction() {
61 | // Check whether the user has installed Git
62 | ipcRenderer.send('command-to-run', 'git --version');
63 | if (!currentDirectory) ipcRenderer.send('ready-for-dir', '\n');
64 | ipcRenderer.once('terminal-output', (event, arg) => {
65 | // If terminal-output contains the text 'git version', the user should pass.
66 | // If not, the user shouldn't.
67 | ipcRenderer.send('test-result-1', arg.indexOf('git version') > -1);
68 | });
69 | },
70 | errorMessage: "Oops! It looks like you haven't installed Git. Try again and then click the button below.",
71 |
72 | }, {
73 | lessonText:
74 |
75 |
Step 2: Set up a project
76 |
Make a directory
77 |
OK, let's take a quick break from Git to set up our project.
78 |
In the past, you've probably used the Finder or a program like Microsoft Word to create new folders and files. For this lesson, we'll use the terminal.
79 |
Ready? Type mkdir new-project and then click Enter.
80 |
You'll see this folder being added to your current directory above.
81 |
,
82 | buttonText: 'Done!',
83 | buttonFunction() {
84 | // Check whether the user has created new-project
85 | const commandToRun = `cd ${currentDirectory}; cd new-project`;
86 | ipcRenderer.send('command-to-run', commandToRun);
87 | ipcRenderer.once('terminal-output', (event, arg) => {
88 | // If we can't cd into new-project, the terminal will create an error, and arg will be a
89 | // string starting with 'Error.' In this case, the user should fail the test, so we'll
90 | // return a falsy value: zero. Otherwise, the user should pass.
91 | ipcRenderer.send('test-result-1', arg.indexOf('Error'));
92 | });
93 | },
94 | errorMessage: "Oops! It looks like you haven't created a new directory called 'new-project'. Try again and then click the button below.",
95 |
96 | }, {
97 | lessonText:
98 |
99 |
Step 2: Set up a project
100 |
Navigate to your new directory
101 |
As you just saw, you just created a new folder called "new-project." (The "mkdir" command is short for "make directory," and "directory" is just a fancy word for folder.)
102 |
Now type cd new-project and click Enter to navigate to your new directory. (The "cd" command is short for "change directory.") This is where you'll store all the files for your project.
103 |
, // '
104 | buttonText: 'Got it!',
105 | buttonFunction() {
106 | // Check whether the user has navigated into new-project.
107 | // If so, currentDirectory will end with 'new-project'
108 | const endOfCurrentPath = currentDirectory.slice(-11);
109 | ipcRenderer.send('test-result-1', endOfCurrentPath === 'new-project');
110 | },
111 | errorMessage: "Oops! It looks like you haven't navigated into the 'new-project' directory. Try again and then click the button below.",
112 |
113 | }, {
114 | lessonText:
115 |
116 |
Step 2: Set up a project
117 |
Create a file
118 |
Now let's add a file to your folder.
119 |
In the terminal, type touch new-file.txt and click Enter.
120 |
Notice that we haven't been typing the word "git" in the commands we're using to create directories and files and to navigate through them. That's because these commands aren't specific to Git - but we'll get back to Git now.
121 |
,
122 | buttonText: 'Onward!',
123 | buttonFunction() {
124 | // This test and subsequent tests depend on the user being inside the new-project
125 | // directory they created.
126 | // Check whether the user has created new-file.txt inside the currentDirectory;
127 | const commandToRun = `cd ${currentDirectory}; ls`;
128 | ipcRenderer.send('command-to-run', commandToRun);
129 | ipcRenderer.once('terminal-output', (event, arg) => {
130 | // If the terminal-output contains 'new-file.txt', the user should pass.
131 | ipcRenderer.send('test-result-1', arg.indexOf('new-file.txt') > -1);
132 | });
133 | },
134 | errorMessage: "Oops! It looks like you haven't created a file called 'new-file.txt', or you aren't inside the 'new-project' directory. Try again and then click the button below.",
135 |
136 | }, {
137 | lessonText:
138 |
139 |
Step 3: Learn Git commands
140 |
git init
141 |
Now that we've created a project, we need to tell Git that we want to track it. To do this, type git init and then click Enter. This initializes a new Git repository, or "repo."
142 |
, // '
143 | buttonText: "What's a repo?",
144 | buttonFunction() {
145 | // Check whether the user has initialized their repo successfully
146 | const commandToRun = `cd ${currentDirectory}; git status`;
147 | ipcRenderer.send('command-to-run', commandToRun);
148 | ipcRenderer.once('terminal-output', (event, arg) => {
149 | // If the repo hasn't been initialized, the terminal will create an error,
150 | // and arg will be a string starting with 'Error.'
151 | // In this case, the user should fail the test, so we'll return a falsy value: zero.
152 | // Otherwise, the user should pass.
153 | ipcRenderer.send('test-result-1', arg.indexOf('Error'));
154 | });
155 | },
156 | errorMessage: "Oops! It looks like you haven't initialized your repo, or you aren't inside the 'new-project' directory. Try again and then click the button below.",
157 |
158 | }, {
159 | lessonText:
160 |
161 |
Step 3: Learn Git commands
162 |
git status
163 |
A repo contains all the files Git is tracking for a project, as well as the revision history for these files. All this information is stored in a special folder called ".git" inside your project folder.
164 |
Your .git folder is hidden by default, so you won't see it in your Finder. However, you can use the terminal to make sure you've initialized your repository correctly. Just type git status and then click Enter. As long as you don't see this error message - fatal: Not a git repository - you're good to go.
165 |
,
166 | buttonText: "I'm good to go!",
167 |
168 | }, {
169 | lessonText:
170 |
171 |
Step 3: Learn Git commands
172 |
git add
173 |
So you've initialized a Git repository, and you've created the first file for your project. Now it's time to tell Git that you'd like to track that file.
174 |
Type git add new-file.txt and click Enter to add this file to your "staging area." This tells Git that you want to track changes to new-file.txt, and that you're getting ready to make a commit.
175 |
, // '
176 | buttonText: "What's a commit?",
177 | buttonFunction() {
178 | // Check whether the user has git-added new-file.text
179 | const commandToRun = `cd ${currentDirectory}; git status`;
180 | ipcRenderer.send('command-to-run', commandToRun);
181 | ipcRenderer.once('terminal-output', (event, arg) => {
182 | // Assume the user has failed until they prove otherwise.
183 | let didUserPass = false;
184 | // If new-file.txt has been added to the staging area, arg should contain 'Changes to be
185 | // committed' followed by either 'modified: new-file.txt' or 'new file: new-file.txt'
186 | // There should NOT be an instance of 'Changes not staged for commit' between 'Changes to be
187 | // committed' and 'new-file.txt'. Let's check these conditions with regex.
188 | const regExp = /Changes to be committed([\s\S]+)new-file[.]txt/;
189 | const result = regExp.exec(arg);
190 | // If the result is truthy (isn't null), arg contains 'Changes to be committed' followed by
191 | // 'new-file.txt', and we should continue examine the result.
192 | if (result) {
193 | // If the match contains 'Changes not staged for commit', keep didUserPass false. If not,
194 | // change didUserPass to true.
195 | if (result[1].indexOf('Changes not staged for commit') === -1) {
196 | didUserPass = true;
197 | }
198 | }
199 | ipcRenderer.send('test-result-1', didUserPass);
200 | });
201 | },
202 | errorMessage: "Oops! It looks like you haven't added new-file.txt to your staging area, or you aren't inside the 'new-project' directory. Try again and then click the button below.",
203 |
204 | }, {
205 | lessonText:
206 |
207 |
Step 3: Learn Git commands
208 |
git commit -m
209 |
You can think of a commit as a snapshot of your code at a specific point in time. Git saves each commit to your repo, along with a message you write to describe the status of the project.
210 |
So let's commit - type git commit -m "create file to store text for project " and click Enter. (The "-m" is for "message," and you can replace the bolded text with whatever message you like.)
211 |
It's a best practice to commit early and often, and to write descriptive commit messages.
212 | The more descriptive your commit messages are, the easier it will be to find specific
213 | commits when you want to refer back to them in the future!
214 |
,
215 | buttonText: "What's next?",
216 | buttonFunction() {
217 | // Check whether the user has git-committed new-file.text.
218 | const commandToRun = `cd ${currentDirectory}; git log; git status`;
219 | ipcRenderer.send('command-to-run', commandToRun);
220 | ipcRenderer.once('terminal-output', (event, arg) => {
221 | let didUserPass = true;
222 | // If arg.indexOf('fatal') is 0, arg starts with 'fatal',
223 | // and there are no commits in the git log.
224 | if (!arg.indexOf('fatal')) {
225 | didUserPass = false;
226 | // Otherwise, check the git status.
227 | } else {
228 | // If it doesn't contain 'nothing to commit, working directory clean',
229 | // the user has uncommitted changes.
230 | if (arg.indexOf('nothing to commit, working directory clean') === -1) didUserPass = false;
231 | // Otherwise, we can be confident that the user committed,
232 | // so we can keep didUserPass true.
233 | }
234 | ipcRenderer.send('test-result-1', didUserPass);
235 | });
236 | },
237 | errorMessage: "Oops! It looks like you have changes that you haven't committed, or you aren't inside the 'new-project' directory. Try again and then click the button below.",
238 |
239 | }, {
240 | lessonText:
241 |
242 |
Step 3: Learn Git commands
243 |
Take a break from Git to edit your file
244 |
OK, you've made a commit for this project - the first snapshot of many! But it wasn't a very exciting snapshot, because new-file.txt was empty. So let's add some text to the file and then commit again.
245 |
Typically, developers write code using a text editor like Atom or Sublime Text. For this lesson, though, we'll practice editing a file directly from the terminal - something you can do even without Git.
246 |
In the terminal, type echo "This will be the best project ever. " > new-file.txt and click Enter. (Again, you can replace the bolded part with whatever text you wish.)
247 |
In the animation above, you'll notice that new-file.txt turns red when you modify it. This means it has changes that you haven't committed yet.
248 |
,
249 | buttonText: 'Done!',
250 | buttonFunction() {
251 | // Check whether the user has edited new-file.text
252 | const commandToRun = `cd ${currentDirectory}; git status`;
253 | ipcRenderer.send('command-to-run', commandToRun);
254 | ipcRenderer.once('terminal-output', (event, arg) => {
255 | // If the user has modified new-file.txt, arg should contain
256 | // 'modified:' + whitespace + 'new-file.txt'.
257 | const regExp = /(modified:\s+new-file[.]txt)/;
258 | ipcRenderer.send('test-result-1', regExp.test(arg));
259 | });
260 | },
261 | errorMessage:
262 | "Oops! It looks like you haven't made any changes to new-file.txt since your last commit, or you aren't inside the 'new-project' directory. Try again and then click the button below.",
263 |
264 | }, {
265 | lessonText:
266 |
267 |
Step 3: Learn Git commands
268 |
git diff
269 |
To make sure we actually edited the file, type git diff and click Enter.
270 |
This will show us the differences between the current version of the project and our most
271 | recent commit. Remember: last time we committed, new-file.txt was empty.
272 |
,
273 | buttonText: 'I see my changes!',
274 |
275 | }, {
276 | lessonText:
277 |
278 |
Congrats!
279 |
You've finished our lesson on using Git on your computer.
280 |
Now you're ready to start learning about GitHub, a popular website that makes it easy to
281 | back up your Git projects online and collaborate with other developers.
282 |
,
283 | buttonText: 'Learn about Github',
284 |
285 | }, {
286 | lessonText:
287 |
288 |
The GitHub lesson is coming soon, but it isn't ready yet. Would you like to repeat the
289 | lesson you just finished?
290 |
, // '
291 | buttonText: 'Sure!',
292 | },
293 | ];
294 |
295 | export { slides as lesson1 };
296 |
297 | // A valuable resource for this lesson was https://github.com/jlord/git-it-electron.
298 |
--------------------------------------------------------------------------------
/visualizations/dependencies/dagre-d3.js:
--------------------------------------------------------------------------------
1 | const d3 = require('d3');
2 |
3 | ;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o Math.abs(dx) * h) {
367 | // Intersection is top or bottom of rect.
368 | if (dy < 0) {
369 | h = -h;
370 | }
371 | sx = dy === 0 ? 0 : h * dx / dy;
372 | sy = h;
373 | } else {
374 | // Intersection is left or right of rect.
375 | if (dx < 0) {
376 | w = -w;
377 | }
378 | sx = w;
379 | sy = dx === 0 ? 0 : w * dy / dx;
380 | }
381 |
382 | return {x: x + sx, y: y + sy};
383 | };
384 |
385 |
386 | },{"dagre":4}],3:[function(require,module,exports){
387 | module.exports = '0.0.1';
388 |
389 | },{}],4:[function(require,module,exports){
390 | /*
391 | Copyright (c) 2012-2013 Chris Pettitt
392 |
393 | Permission is hereby granted, free of charge, to any person obtaining a copy
394 | of this software and associated documentation files (the "Software"), to deal
395 | in the Software without restriction, including without limitation the rights
396 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
397 | copies of the Software, and to permit persons to whom the Software is
398 | furnished to do so, subject to the following conditions:
399 |
400 | The above copyright notice and this permission notice shall be included in
401 | all copies or substantial portions of the Software.
402 |
403 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
404 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
405 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
406 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
407 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
408 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
409 | THE SOFTWARE.
410 | */
411 | exports.Digraph = require("graphlib").Digraph;
412 | exports.Graph = require("graphlib").Graph;
413 | exports.layout = require("./lib/layout/layout");
414 | exports.version = require("./lib/version");
415 |
416 | },{"./lib/layout/layout":6,"./lib/version":11,"graphlib":12}],5:[function(require,module,exports){
417 | var util = require("../util");
418 |
419 | module.exports = instrumentedRun;
420 | module.exports.undo = undo;
421 |
422 | function instrumentedRun(g, debugLevel) {
423 | var timer = util.createTimer();
424 | var reverseCount = util.createTimer().wrap("Acyclic Phase", run)(g);
425 | if (debugLevel >= 2) console.log("Acyclic Phase: reversed " + reverseCount + " edge(s)");
426 | }
427 |
428 | function run(g) {
429 | var onStack = {},
430 | visited = {},
431 | reverseCount = 0;
432 |
433 | function dfs(u) {
434 | if (u in visited) return;
435 |
436 | visited[u] = onStack[u] = true;
437 | g.outEdges(u).forEach(function(e) {
438 | var t = g.target(e),
439 | a;
440 |
441 | if (t in onStack) {
442 | a = g.edge(e);
443 | g.delEdge(e);
444 | a.reversed = true;
445 | ++reverseCount;
446 | g.addEdge(e, t, u, a);
447 | } else {
448 | dfs(t);
449 | }
450 | });
451 |
452 | delete onStack[u];
453 | }
454 |
455 | g.eachNode(function(u) { dfs(u); });
456 |
457 | return reverseCount;
458 | }
459 |
460 | function undo(g) {
461 | g.eachEdge(function(e, s, t, a) {
462 | if (a.reversed) {
463 | delete a.reversed;
464 | g.delEdge(e);
465 | g.addEdge(e, t, s, a);
466 | }
467 | });
468 | }
469 |
470 | },{"../util":10}],6:[function(require,module,exports){
471 | var util = require("../util"),
472 | rank = require("./rank"),
473 | acyclic = require("./acyclic"),
474 | Digraph = require("graphlib").Digraph,
475 | Graph = require("graphlib").Graph,
476 | Set = require("graphlib").data.Set;
477 |
478 | module.exports = function() {
479 | // External configuration
480 | var config = {
481 | // How much debug information to include?
482 | debugLevel: 0,
483 | };
484 |
485 | var timer = util.createTimer();
486 |
487 | // Phase functions
488 | var
489 | order = require("./order")(),
490 | position = require("./position")();
491 |
492 | // This layout object
493 | var self = {};
494 |
495 | self.orderIters = delegateProperty(order.iterations);
496 |
497 | self.nodeSep = delegateProperty(position.nodeSep);
498 | self.edgeSep = delegateProperty(position.edgeSep);
499 | self.universalSep = delegateProperty(position.universalSep);
500 | self.rankSep = delegateProperty(position.rankSep);
501 | self.rankDir = delegateProperty(position.rankDir);
502 | self.debugAlignment = delegateProperty(position.debugAlignment);
503 |
504 | self.debugLevel = util.propertyAccessor(self, config, "debugLevel", function(x) {
505 | timer.enabled(x);
506 | order.debugLevel(x);
507 | position.debugLevel(x);
508 | });
509 |
510 | self.run = timer.wrap("Total layout", run);
511 |
512 | self._normalize = normalize;
513 |
514 | return self;
515 |
516 | /*
517 | * Constructs an adjacency graph using the nodes and edges specified through
518 | * config. For each node and edge we add a property `dagre` that contains an
519 | * object that will hold intermediate and final layout information. Some of
520 | * the contents include:
521 | *
522 | * 1) A generated ID that uniquely identifies the object.
523 | * 2) Dimension information for nodes (copied from the source node).
524 | * 3) Optional dimension information for edges.
525 | *
526 | * After the adjacency graph is constructed the code no longer needs to use
527 | * the original nodes and edges passed in via config.
528 | */
529 | function buildAdjacencyGraph(inputGraph) {
530 | var g = new Digraph();
531 |
532 | inputGraph.eachNode(function(u, value) {
533 | if (value === undefined) value = {};
534 | g.addNode(u, {
535 | width: value.width,
536 | height: value.height
537 | });
538 | });
539 |
540 | inputGraph.eachEdge(function(e, u, v, value) {
541 | if (value === undefined) value = {};
542 | var newValue = {
543 | e: e,
544 | minLen: value.minLen || 1,
545 | width: value.width || 0,
546 | height: value.height || 0,
547 | points: []
548 | };
549 |
550 | g.addEdge(null, u, v, newValue);
551 |
552 | // If input graph is not directed, we create also add a reverse edge.
553 | // After we've run the acyclic algorithm we'll remove one of these edges.
554 | if (!inputGraph.isDirected()) {
555 | g.addEdge(null, v, u, newValue);
556 | }
557 | });
558 |
559 | return g;
560 | }
561 |
562 | function run(inputGraph) {
563 | var rankSep = self.rankSep();
564 | var g;
565 | try {
566 | // Build internal graph
567 | g = buildAdjacencyGraph(inputGraph);
568 |
569 | if (g.order() === 0) {
570 | return g;
571 | }
572 |
573 | // Make space for edge labels
574 | g.eachEdge(function(e, s, t, a) {
575 | a.minLen *= 2;
576 | });
577 | self.rankSep(rankSep / 2);
578 |
579 | // Reverse edges to get an acyclic graph, we keep the graph in an acyclic
580 | // state until the very end.
581 | acyclic(g, config.debugLevel);
582 |
583 | // Our intermediate graph is always directed. However, the input graph
584 | // may be undirected, so we create duplicate edges in opposite directions
585 | // in the buildAdjacencyGraph function. At this point one of each pair of
586 | // edges was reversed, so we remove the redundant edge.
587 | if (!inputGraph.isDirected()) {
588 | removeDupEdges(g);
589 | }
590 |
591 | // Determine the rank for each node. Nodes with a lower rank will appear
592 | // above nodes of higher rank.
593 | rank(g, config.debugLevel);
594 |
595 | // Normalize the graph by ensuring that every edge is proper (each edge has
596 | // a length of 1). We achieve this by adding dummy nodes to long edges,
597 | // thus shortening them.
598 | normalize(g);
599 |
600 | // Order the nodes so that edge crossings are minimized.
601 | order.run(g);
602 |
603 | // Find the x and y coordinates for every node in the graph.
604 | position.run(g);
605 |
606 | // De-normalize the graph by removing dummy nodes and augmenting the
607 | // original long edges with coordinate information.
608 | undoNormalize(g);
609 |
610 | // Reverses points for edges that are in a reversed state.
611 | fixupEdgePoints(g);
612 |
613 | // Reverse edges that were revered previously to get an acyclic graph.
614 | acyclic.undo(g);
615 |
616 | // Construct final result graph and return it
617 | return createFinalGraph(g, inputGraph.isDirected());
618 | } finally {
619 | self.rankSep(rankSep);
620 | }
621 | }
622 |
623 | function removeDupEdges(g) {
624 | var visited = new Set();
625 | g.eachEdge(function(e, u, v, value) {
626 | if (visited.has(value.e)) {
627 | g.delEdge(e);
628 | }
629 | visited.add(value.e);
630 | });
631 | }
632 |
633 | /*
634 | * This function is responsible for "normalizing" the graph. The process of
635 | * normalization ensures that no edge in the graph has spans more than one
636 | * rank. To do this it inserts dummy nodes as needed and links them by adding
637 | * dummy edges. This function keeps enough information in the dummy nodes and
638 | * edges to ensure that the original graph can be reconstructed later.
639 | *
640 | * This method assumes that the input graph is cycle free.
641 | */
642 | function normalize(g) {
643 | var dummyCount = 0;
644 | g.eachEdge(function(e, s, t, a) {
645 | var sourceRank = g.node(s).rank;
646 | var targetRank = g.node(t).rank;
647 | if (sourceRank + 1 < targetRank) {
648 | for (var u = s, rank = sourceRank + 1, i = 0; rank < targetRank; ++rank, ++i) {
649 | var v = "_D" + (++dummyCount);
650 | var node = {
651 | width: a.width,
652 | height: a.height,
653 | edge: { id: e, source: s, target: t, attrs: a },
654 | rank: rank,
655 | dummy: true
656 | };
657 |
658 | // If this node represents a bend then we will use it as a control
659 | // point. For edges with 2 segments this will be the center dummy
660 | // node. For edges with more than two segments, this will be the
661 | // first and last dummy node.
662 | if (i === 0) node.index = 0;
663 | else if (rank + 1 === targetRank) node.index = 1;
664 |
665 | g.addNode(v, node);
666 | g.addEdge(null, u, v, {});
667 | u = v;
668 | }
669 | g.addEdge(null, u, t, {});
670 | g.delEdge(e);
671 | }
672 | });
673 | }
674 |
675 | /*
676 | * Reconstructs the graph as it was before normalization. The positions of
677 | * dummy nodes are used to build an array of points for the original "long"
678 | * edge. Dummy nodes and edges are removed.
679 | */
680 | function undoNormalize(g) {
681 | g.eachNode(function(u, a) {
682 | if (a.dummy && "index" in a) {
683 | var edge = a.edge;
684 | if (!g.hasEdge(edge.id)) {
685 | g.addEdge(edge.id, edge.source, edge.target, edge.attrs);
686 | }
687 | var points = g.edge(edge.id).points;
688 | points[a.index] = { x: a.x, y: a.y, ul: a.ul, ur: a.ur, dl: a.dl, dr: a.dr };
689 | g.delNode(u);
690 | }
691 | });
692 | }
693 |
694 | /*
695 | * For each edge that was reversed during the `acyclic` step, reverse its
696 | * array of points.
697 | */
698 | function fixupEdgePoints(g) {
699 | g.eachEdge(function(e, s, t, a) { if (a.reversed) a.points.reverse(); });
700 | }
701 |
702 | function createFinalGraph(g, isDirected) {
703 | var out = isDirected ? new Digraph() : new Graph();
704 | g.eachNode(function(u, value) { out.addNode(u, value); });
705 | g.eachEdge(function(e, u, v, value) {
706 | out.addEdge("e" in value ? value.e : e, u, v, value);
707 | delete value.e;
708 | });
709 | return out;
710 | }
711 |
712 | /*
713 | * Given a function, a new function is returned that invokes the given
714 | * function. The return value from the function is always the `self` object.
715 | */
716 | function delegateProperty(f) {
717 | return function() {
718 | if (!arguments.length) return f();
719 | f.apply(null, arguments);
720 | return self;
721 | };
722 | }
723 | };
724 |
725 | },{"../util":10,"./acyclic":5,"./order":7,"./position":8,"./rank":9,"graphlib":12}],7:[function(require,module,exports){
726 | var util = require("../util");
727 |
728 | module.exports = function() {
729 | var config = {
730 | iterations: 24, // max number of iterations
731 | debugLevel: 0
732 | };
733 |
734 | var timer = util.createTimer();
735 |
736 | var self = {};
737 |
738 | self.iterations = util.propertyAccessor(self, config, "iterations");
739 |
740 | self.debugLevel = util.propertyAccessor(self, config, "debugLevel", function(x) {
741 | timer.enabled(x);
742 | });
743 |
744 | self._initOrder = initOrder;
745 |
746 | self.run = timer.wrap("Order Phase", run);
747 | self.crossCount = crossCount;
748 | self.bilayerCrossCount = bilayerCrossCount;
749 |
750 | return self;
751 |
752 | function run(g) {
753 | var layering = initOrder(g);
754 | var bestLayering = copyLayering(layering);
755 | var bestCC = crossCount(g, layering);
756 |
757 | if (config.debugLevel >= 2) {
758 | console.log("Order phase start cross count: " + bestCC);
759 | }
760 |
761 | var cc, i, lastBest;
762 | for (i = 0, lastBest = 0; lastBest < 4 && i < config.iterations; ++i, ++lastBest) {
763 | cc = sweep(g, i, layering);
764 | if (cc < bestCC) {
765 | bestLayering = copyLayering(layering);
766 | bestCC = cc;
767 | lastBest = 0;
768 | }
769 | if (config.debugLevel >= 3) {
770 | console.log("Order phase iter " + i + " cross count: " + bestCC);
771 | }
772 | }
773 |
774 | bestLayering.forEach(function(layer) {
775 | layer.forEach(function(u, i) {
776 | g.node(u).order = i;
777 | });
778 | });
779 |
780 | if (config.debugLevel >= 2) {
781 | console.log("Order iterations: " + i);
782 | console.log("Order phase best cross count: " + bestCC);
783 | }
784 |
785 | if (config.debugLevel >= 4) {
786 | console.log("Final layering:");
787 | bestLayering.forEach(function(layer, i) {
788 | console.log("Layer: " + i, layer);
789 | });
790 | }
791 |
792 | return bestLayering;
793 | }
794 |
795 | function initOrder(g) {
796 | var layering = [];
797 | g.eachNode(function(n, a) {
798 | var layer = layering[a.rank] || (layering[a.rank] = []);
799 | layer.push(n);
800 | });
801 | return layering;
802 | }
803 |
804 | /*
805 | * Returns a function that will return the predecessors for a node. This
806 | * function differs from `g.predecessors(u)` in that a predecessor appears
807 | * for each incident edge (`g.predecessors(u)` treats predecessors as a set).
808 | * This allows pseudo-weighting of predecessor nodes.
809 | */
810 | function multiPredecessors(g) {
811 | return function(u) {
812 | var preds = [];
813 | g.inEdges(u).forEach(function(e) {
814 | preds.push(g.source(e));
815 | });
816 | return preds;
817 | };
818 | }
819 |
820 | /*
821 | * Same as `multiPredecessors(g)` but for successors.
822 | */
823 | function multiSuccessors(g) {
824 | return function(u) {
825 | var sucs = [];
826 | g.outEdges(u).forEach(function(e) {
827 | sucs.push(g.target(e));
828 | });
829 | return sucs;
830 | };
831 | }
832 |
833 | function sweep(g, iter, layering) {
834 | var i;
835 | if (iter % 2 === 0) {
836 | for (i = 1; i < layering.length; ++i) {
837 | sortLayer(layering[i], multiPredecessors(g), layerPos(layering[i-1]));
838 | }
839 | } else {
840 | for (i = layering.length - 2; i >= 0; --i) {
841 | sortLayer(layering[i], multiSuccessors(g), layerPos(layering[i+1]));
842 | }
843 | }
844 | return crossCount(g, layering);
845 | }
846 |
847 | /*
848 | * Given a list of nodes, a function that returns neighbors of a node, and
849 | * a mapping of the neighbor nodes to their weights, this function sorts
850 | * the node list by the barycenter calculated for each node.
851 | */
852 | function sortLayer(nodes, neighbors, weights) {
853 | var pos = layerPos(nodes);
854 | var bs = barycenters(nodes, neighbors, weights);
855 |
856 | var toSort = nodes.filter(function(u) { return bs[u] !== -1; });
857 | toSort.sort(function(x, y) {
858 | return bs[x] - bs[y] || pos[x] - pos[y];
859 | });
860 |
861 | for (var i = nodes.length - 1; i >= 0; --i) {
862 | if (bs[nodes[i]] !== -1) {
863 | nodes[i] = toSort.pop();
864 | }
865 | }
866 | }
867 |
868 | /*
869 | * Given a list of nodes, a function that returns neighbors of a node, and
870 | * a mapping of the neighbor nodes to their weights, this function returns
871 | * a mapping of the input nodes to their calculated barycenters. The
872 | * barycenter values are the average weights of all neighbors of the
873 | * node. If a node has no neighbors it is assigned a barycenter of -1.
874 | */
875 | function barycenters(nodes, neighbors, weights) {
876 | var bs = {}; // barycenters
877 |
878 | nodes.forEach(function(u) {
879 | var vs = neighbors(u);
880 | var b = -1;
881 | if (vs.length > 0)
882 | b = util.sum(vs.map(function(v) { return weights[v]; })) / vs.length;
883 | bs[u] = b;
884 | });
885 |
886 | return bs;
887 | }
888 |
889 | function copyLayering(layering) {
890 | return layering.map(function(l) { return l.slice(0); });
891 | }
892 |
893 | function crossCount(g, layering) {
894 | var cc = 0;
895 | var prevLayer;
896 | layering.forEach(function(layer) {
897 | if (prevLayer) {
898 | cc += bilayerCrossCount(g, prevLayer, layer);
899 | }
900 | prevLayer = layer;
901 | });
902 | return cc;
903 | }
904 |
905 | /*
906 | * This function searches through a ranked and ordered graph and counts the
907 | * number of edges that cross. This algorithm is derived from:
908 | *
909 | * W. Barth et al., Bilayer Cross Counting, JGAA, 8(2) 179–194 (2004)
910 | */
911 | function bilayerCrossCount(g, layer1, layer2) {
912 | var layer2Pos = layerPos(layer2);
913 |
914 | var indices = [];
915 | layer1.forEach(function(u) {
916 | var nodeIndices = [];
917 | g.outEdges(u).forEach(function(e) { nodeIndices.push(layer2Pos[g.target(e)]); });
918 | nodeIndices.sort(function(x, y) { return x - y; });
919 | indices = indices.concat(nodeIndices);
920 | });
921 |
922 | var firstIndex = 1;
923 | while (firstIndex < layer2.length) firstIndex <<= 1;
924 |
925 | var treeSize = 2 * firstIndex - 1;
926 | firstIndex -= 1;
927 |
928 | var tree = [];
929 | for (var i = 0; i < treeSize; ++i) { tree[i] = 0; }
930 |
931 | var cc = 0;
932 | indices.forEach(function(i) {
933 | var treeIndex = i + firstIndex;
934 | ++tree[treeIndex];
935 | var weightSum = 0;
936 | while (treeIndex > 0) {
937 | if (treeIndex % 2) {
938 | cc += tree[treeIndex + 1];
939 | }
940 | treeIndex = (treeIndex - 1) >> 1;
941 | ++tree[treeIndex];
942 | }
943 | });
944 |
945 | return cc;
946 | }
947 | };
948 |
949 |
950 | function layerPos(layer) {
951 | var pos = {};
952 | layer.forEach(function(u, i) { pos[u] = i; });
953 | return pos;
954 | }
955 |
956 | },{"../util":10}],8:[function(require,module,exports){
957 | var util = require("../util");
958 |
959 | /*
960 | * The algorithms here are based on Brandes and Köpf, "Fast and Simple
961 | * Horizontal Coordinate Assignment".
962 | */
963 | module.exports = function() {
964 | // External configuration
965 | var config = {
966 | nodeSep: 50,
967 | edgeSep: 10,
968 | universalSep: null,
969 | rankSep: 30,
970 | rankDir: "TB",
971 | debugLevel: 0
972 | };
973 |
974 | var timer = util.createTimer();
975 |
976 | var self = {};
977 |
978 | self.nodeSep = util.propertyAccessor(self, config, "nodeSep");
979 | self.edgeSep = util.propertyAccessor(self, config, "edgeSep");
980 | // If not null this separation value is used for all nodes and edges
981 | // regardless of their widths. `nodeSep` and `edgeSep` are ignored with this
982 | // option.
983 | self.universalSep = util.propertyAccessor(self, config, "universalSep");
984 | self.rankSep = util.propertyAccessor(self, config, "rankSep");
985 | self.rankDir = util.propertyAccessor(self, config, "rankDir");
986 | self.debugLevel = util.propertyAccessor(self, config, "debugLevel", function(x) {
987 | timer.enabled(x);
988 | });
989 |
990 | self.run = timer.wrap("Position Phase", run);
991 |
992 | return self;
993 |
994 | function run(g) {
995 | var layering = [];
996 | g.eachNode(function(u, node) {
997 | var layer = layering[node.rank] || (layering[node.rank] = []);
998 | layer[node.order] = u;
999 | });
1000 |
1001 | var conflicts = findConflicts(g, layering);
1002 |
1003 | var xss = {};
1004 | ["u", "d"].forEach(function(vertDir) {
1005 | if (vertDir === "d") layering.reverse();
1006 |
1007 | ["l", "r"].forEach(function(horizDir) {
1008 | if (horizDir === "r") reverseInnerOrder(layering);
1009 |
1010 | var dir = vertDir + horizDir;
1011 | var align = verticalAlignment(g, layering, conflicts, vertDir === "u" ? "predecessors" : "successors");
1012 | xss[dir]= horizontalCompaction(g, layering, align.pos, align.root, align.align);
1013 |
1014 | if (config.debugLevel >= 3)
1015 | debugPositioning(vertDir + horizDir, g, layering, xss[dir]);
1016 |
1017 | if (horizDir === "r") flipHorizontally(xss[dir]);
1018 |
1019 | if (horizDir === "r") reverseInnerOrder(layering);
1020 | });
1021 |
1022 | if (vertDir === "d") layering.reverse();
1023 | });
1024 |
1025 | balance(g, layering, xss);
1026 | g.eachNode(function(v) {
1027 | var xs = [];
1028 | for (var alignment in xss) {
1029 | xDebug(alignment, g, v, xss[alignment][v]);
1030 | xs.push(xss[alignment][v]);
1031 | }
1032 | xs.sort(function(x, y) { return x - y; });
1033 | x(g, v, (xs[1] + xs[2]) / 2);
1034 | });
1035 |
1036 | // Translate layout so left edge of bounding rectangle has coordinate 0
1037 | var minX = util.min(g.nodes().map(function(u) { return x(g, u) - width(g, u) / 2; }));
1038 | g.eachNode(function(u) { x(g, u, x(g, u) - minX); });
1039 |
1040 | // Align y coordinates with ranks
1041 | var posY = 0;
1042 | layering.forEach(function(layer) {
1043 | var maxHeight = util.max(layer.map(function(u) { return height(g, u); }));
1044 | posY += maxHeight / 2;
1045 | layer.forEach(function(u) { y(g, u, posY); });
1046 | posY += maxHeight / 2 + config.rankSep;
1047 | });
1048 | };
1049 |
1050 | /*
1051 | * Generate an ID that can be used to represent any undirected edge that is
1052 | * incident on `u` and `v`.
1053 | */
1054 | function undirEdgeId(u, v) {
1055 | return u < v
1056 | ? u.toString().length + ":" + u + "-" + v
1057 | : v.toString().length + ":" + v + "-" + u;
1058 | }
1059 |
1060 | function findConflicts(g, layering) {
1061 | var conflicts = {}, // Set of conflicting edge ids
1062 | pos = {}; // Position of node in its layer
1063 |
1064 | if (layering.length <= 2) return conflicts;
1065 |
1066 | layering[1].forEach(function(u, i) { pos[u] = i; });
1067 | for (var i = 1; i < layering.length - 1; ++i) {
1068 | var prevLayer = layering[i];
1069 | var currLayer = layering[i+1];
1070 | var k0 = 0; // Position of the last inner segment in the previous layer
1071 | var l = 0; // Current position in the current layer (for iteration up to `l1`)
1072 |
1073 | // Scan current layer for next node that is incident to an inner segement
1074 | // between layering[i+1] and layering[i].
1075 | for (var l1 = 0; l1 < currLayer.length; ++l1) {
1076 | var u = currLayer[l1]; // Next inner segment in the current layer or
1077 | // last node in the current layer
1078 | pos[u] = l1;
1079 |
1080 | var k1 = undefined; // Position of the next inner segment in the previous layer or
1081 | // the position of the last element in the previous layer
1082 | if (g.node(u).dummy) {
1083 | var uPred = g.predecessors(u)[0];
1084 | if (g.node(uPred).dummy)
1085 | k1 = pos[uPred];
1086 | }
1087 | if (k1 === undefined && l1 === currLayer.length - 1)
1088 | k1 = prevLayer.length - 1;
1089 |
1090 | if (k1 !== undefined) {
1091 | for (; l <= l1; ++l) {
1092 | g.predecessors(currLayer[l]).forEach(function(v) {
1093 | var k = pos[v];
1094 | if (k < k0 || k > k1)
1095 | conflicts[undirEdgeId(currLayer[l], v)] = true;
1096 | });
1097 | }
1098 | k0 = k1;
1099 | }
1100 | }
1101 | }
1102 |
1103 | return conflicts;
1104 | }
1105 |
1106 | function verticalAlignment(g, layering, conflicts, relationship) {
1107 | var pos = {}, // Position for a node in its layer
1108 | root = {}, // Root of the block that the node participates in
1109 | align = {}; // Points to the next node in the block or, if the last
1110 | // element in the block, points to the first block's root
1111 |
1112 | layering.forEach(function(layer) {
1113 | layer.forEach(function(u, i) {
1114 | root[u] = u;
1115 | align[u] = u;
1116 | pos[u] = i;
1117 | });
1118 | });
1119 |
1120 | layering.forEach(function(layer) {
1121 | var prevIdx = -1;
1122 | layer.forEach(function(v) {
1123 | var related = g[relationship](v), // Adjacent nodes from the previous layer
1124 | mid; // The mid point in the related array
1125 |
1126 | if (related.length > 0) {
1127 | related.sort(function(x, y) { return pos[x] - pos[y]; });
1128 | mid = (related.length - 1) / 2;
1129 | related.slice(Math.floor(mid), Math.ceil(mid) + 1).forEach(function(u) {
1130 | if (align[v] === v) {
1131 | if (!conflicts[undirEdgeId(u, v)] && prevIdx < pos[u]) {
1132 | align[u] = v;
1133 | align[v] = root[v] = root[u];
1134 | prevIdx = pos[u];
1135 | }
1136 | }
1137 | });
1138 | }
1139 | });
1140 | });
1141 |
1142 | return { pos: pos, root: root, align: align };
1143 | }
1144 |
1145 | // This function deviates from the standard BK algorithm in two ways. First
1146 | // it takes into account the size of the nodes. Second it includes a fix to
1147 | // the original algorithm that is described in Carstens, "Node and Label
1148 | // Placement in a Layered Layout Algorithm".
1149 | function horizontalCompaction(g, layering, pos, root, align) {
1150 | var sink = {}, // Mapping of node id -> sink node id for class
1151 | maybeShift = {}, // Mapping of sink node id -> { class node id, min shift }
1152 | shift = {}, // Mapping of sink node id -> shift
1153 | pred = {}, // Mapping of node id -> predecessor node (or null)
1154 | xs = {}; // Calculated X positions
1155 |
1156 | layering.forEach(function(layer) {
1157 | layer.forEach(function(u, i) {
1158 | sink[u] = u;
1159 | maybeShift[u] = {};
1160 | if (i > 0)
1161 | pred[u] = layer[i - 1];
1162 | });
1163 | });
1164 |
1165 | function updateShift(toShift, neighbor, delta) {
1166 | if (!(neighbor in maybeShift[toShift])) {
1167 | maybeShift[toShift][neighbor] = delta;
1168 | } else {
1169 | maybeShift[toShift][neighbor] = Math.min(maybeShift[toShift][neighbor], delta);
1170 | }
1171 | }
1172 |
1173 | function placeBlock(v) {
1174 | if (!(v in xs)) {
1175 | xs[v] = 0;
1176 | var w = v;
1177 | do {
1178 | if (pos[w] > 0) {
1179 | var u = root[pred[w]];
1180 | placeBlock(u);
1181 | if (sink[v] === v) {
1182 | sink[v] = sink[u];
1183 | }
1184 | var delta = sep(g, pred[w]) + sep(g, w);
1185 | if (sink[v] !== sink[u]) {
1186 | updateShift(sink[u], sink[v], xs[v] - xs[u] - delta);
1187 | } else {
1188 | xs[v] = Math.max(xs[v], xs[u] + delta);
1189 | }
1190 | }
1191 | w = align[w];
1192 | } while (w !== v);
1193 | }
1194 | }
1195 |
1196 | // Root coordinates relative to sink
1197 | util.values(root).forEach(function(v) {
1198 | placeBlock(v);
1199 | });
1200 |
1201 | // Absolute coordinates
1202 | // There is an assumption here that we've resolved shifts for any classes
1203 | // that begin at an earlier layer. We guarantee this by visiting layers in
1204 | // order.
1205 | layering.forEach(function(layer) {
1206 | layer.forEach(function(v) {
1207 | xs[v] = xs[root[v]];
1208 | if (v === root[v] && v === sink[v]) {
1209 | var minShift = 0;
1210 | if (v in maybeShift && Object.keys(maybeShift[v]).length > 0) {
1211 | minShift = util.min(Object.keys(maybeShift[v])
1212 | .map(function(u) {
1213 | return maybeShift[v][u] + (u in shift ? shift[u] : 0);
1214 | }
1215 | ));
1216 | }
1217 | shift[v] = minShift;
1218 | }
1219 | });
1220 | });
1221 |
1222 | layering.forEach(function(layer) {
1223 | layer.forEach(function(v) {
1224 | xs[v] += shift[sink[root[v]]] || 0;
1225 | });
1226 | });
1227 |
1228 | return xs;
1229 | }
1230 |
1231 | function findMinCoord(g, layering, xs) {
1232 | return util.min(layering.map(function(layer) {
1233 | var u = layer[0];
1234 | return xs[u];
1235 | }));
1236 | }
1237 |
1238 | function findMaxCoord(g, layering, xs) {
1239 | return util.max(layering.map(function(layer) {
1240 | var u = layer[layer.length - 1];
1241 | return xs[u];
1242 | }));
1243 | }
1244 |
1245 | function balance(g, layering, xss) {
1246 | var min = {}, // Min coordinate for the alignment
1247 | max = {}, // Max coordinate for the alginment
1248 | smallestAlignment,
1249 | shift = {}; // Amount to shift a given alignment
1250 |
1251 | var smallest = Number.POSITIVE_INFINITY;
1252 | for (var alignment in xss) {
1253 | var xs = xss[alignment];
1254 | min[alignment] = findMinCoord(g, layering, xs);
1255 | max[alignment] = findMaxCoord(g, layering, xs);
1256 | var w = max[alignment] - min[alignment];
1257 | if (w < smallest) {
1258 | smallest = w;
1259 | smallestAlignment = alignment;
1260 | }
1261 | }
1262 |
1263 | // Determine how much to adjust positioning for each alignment
1264 | ["u", "d"].forEach(function(vertDir) {
1265 | ["l", "r"].forEach(function(horizDir) {
1266 | var alignment = vertDir + horizDir;
1267 | shift[alignment] = horizDir === "l"
1268 | ? min[smallestAlignment] - min[alignment]
1269 | : max[smallestAlignment] - max[alignment];
1270 | });
1271 | });
1272 |
1273 | // Find average of medians for xss array
1274 | for (var alignment in xss) {
1275 | g.eachNode(function(v) {
1276 | xss[alignment][v] += shift[alignment];
1277 | });
1278 | }
1279 | }
1280 |
1281 | function flipHorizontally(xs) {
1282 | for (var u in xs) {
1283 | xs[u] = -xs[u];
1284 | }
1285 | }
1286 |
1287 | function reverseInnerOrder(layering) {
1288 | layering.forEach(function(layer) {
1289 | layer.reverse();
1290 | });
1291 | }
1292 |
1293 | function width(g, u) {
1294 | switch (config.rankDir) {
1295 | case "LR": return g.node(u).height;
1296 | default: return g.node(u).width;
1297 | }
1298 | }
1299 |
1300 | function height(g, u) {
1301 | switch(config.rankDir) {
1302 | case "LR": return g.node(u).width;
1303 | default: return g.node(u).height;
1304 | }
1305 | }
1306 |
1307 | function sep(g, u) {
1308 | if (config.universalSep !== null) {
1309 | return config.universalSep;
1310 | }
1311 | var w = width(g, u);
1312 | var s = g.node(u).dummy ? config.edgeSep : config.nodeSep;
1313 | return (w + s) / 2;
1314 | }
1315 |
1316 | function x(g, u, x) {
1317 | switch (config.rankDir) {
1318 | case "LR":
1319 | if (arguments.length < 3) {
1320 | return g.node(u).y;
1321 | } else {
1322 | g.node(u).y = x;
1323 | }
1324 | break;
1325 | default:
1326 | if (arguments.length < 3) {
1327 | return g.node(u).x;
1328 | } else {
1329 | g.node(u).x = x;
1330 | }
1331 | }
1332 | }
1333 |
1334 | function xDebug(name, g, u, x) {
1335 | switch (config.rankDir) {
1336 | case "LR":
1337 | if (arguments.length < 3) {
1338 | return g.node(u)[name];
1339 | } else {
1340 | g.node(u)[name] = x;
1341 | }
1342 | break;
1343 | default:
1344 | if (arguments.length < 3) {
1345 | return g.node(u)[name];
1346 | } else {
1347 | g.node(u)[name] = x;
1348 | }
1349 | }
1350 | }
1351 |
1352 | function y(g, u, y) {
1353 | switch (config.rankDir) {
1354 | case "LR":
1355 | if (arguments.length < 3) {
1356 | return g.node(u).x;
1357 | } else {
1358 | g.node(u).x = y;
1359 | }
1360 | break;
1361 | default:
1362 | if (arguments.length < 3) {
1363 | return g.node(u).y;
1364 | } else {
1365 | g.node(u).y = y;
1366 | }
1367 | }
1368 | }
1369 |
1370 | function debugPositioning(align, g, layering, xs) {
1371 | layering.forEach(function(l, li) {
1372 | var u, xU;
1373 | l.forEach(function(v) {
1374 | var xV = xs[v];
1375 | if (u) {
1376 | var s = sep(g, u) + sep(g, v);
1377 | if (xV - xU < s)
1378 | console.log("Position phase: sep violation. Align: " + align + ". Layer: " + li + ". " +
1379 | "U: " + u + " V: " + v + ". Actual sep: " + (xV - xU) + " Expected sep: " + s);
1380 | }
1381 | u = v;
1382 | xU = xV;
1383 | });
1384 | });
1385 | }
1386 | };
1387 |
1388 | },{"../util":10}],9:[function(require,module,exports){
1389 | var util = require("../util"),
1390 | components = require("graphlib").alg.components,
1391 | filter = require("graphlib").filter;
1392 | PriorityQueue = require("graphlib").data.PriorityQueue,
1393 | Set = require("graphlib").data.Set;
1394 |
1395 | module.exports = function(g, debugLevel) {
1396 | var timer = util.createTimer(debugLevel >= 1);
1397 | timer.wrap("Rank phase", function() {
1398 | initRank(g);
1399 |
1400 | components(g).forEach(function(cmpt) {
1401 | var subgraph = g.filterNodes(filter.nodesFromList(cmpt));
1402 | feasibleTree(subgraph);
1403 | normalize(subgraph);
1404 | });
1405 | })();
1406 | };
1407 |
1408 | function initRank(g) {
1409 | var minRank = {};
1410 | var pq = new PriorityQueue();
1411 |
1412 | g.eachNode(function(u) {
1413 | pq.add(u, g.inEdges(u).length);
1414 | minRank[u] = 0;
1415 | });
1416 |
1417 | while (pq.size() > 0) {
1418 | var minId = pq.min();
1419 | if (pq.priority(minId) > 0) {
1420 | throw new Error("Input graph is not acyclic: " + g.toString());
1421 | }
1422 | pq.removeMin();
1423 |
1424 | var rank = minRank[minId];
1425 | g.node(minId).rank = rank;
1426 |
1427 | g.outEdges(minId).forEach(function(e) {
1428 | var target = g.target(e);
1429 | minRank[target] = Math.max(minRank[target], rank + (g.edge(e).minLen || 1));
1430 | pq.decrease(target, pq.priority(target) - 1);
1431 | });
1432 | }
1433 | }
1434 |
1435 | function feasibleTree(g) {
1436 | var remaining = new Set(g.nodes()),
1437 | minLen = []; // Array of {u, v, len}
1438 |
1439 | // Collapse multi-edges and precompute the minLen, which will be the
1440 | // max value of minLen for any edge in the multi-edge.
1441 | var minLenMap = {};
1442 | g.eachEdge(function(e, u, v, edge) {
1443 | var id = incidenceId(u, v);
1444 | if (!(id in minLenMap)) {
1445 | minLen.push(minLenMap[id] = { u: u, v: v, len: 1 });
1446 | }
1447 | minLenMap[id].len = Math.max(minLenMap[id].len, edge.minLen || 1);
1448 | });
1449 |
1450 | function slack(mle /* minLen entry*/) {
1451 | return Math.abs(g.node(mle.u).rank - g.node(mle.v).rank) - mle.len;
1452 | }
1453 |
1454 | // Remove arbitrary node - it is effectively the root of the spanning tree.
1455 | remaining.remove(g.nodes()[0]);
1456 |
1457 | // Finds the next edge with the minimum slack.
1458 | function findMinSlack() {
1459 | var result,
1460 | eSlack = Number.POSITIVE_INFINITY;
1461 | minLen.forEach(function(mle /* minLen entry */) {
1462 | if (remaining.has(mle.u) !== remaining.has(mle.v)) {
1463 | var mleSlack = slack(mle);
1464 | if (mleSlack < eSlack) {
1465 | if (!remaining.has(mle.u)) {
1466 | result = { treeNode: mle.u, graphNode: mle.v, len: mle.len};
1467 | } else {
1468 | result = { treeNode: mle.v, graphNode: mle.u, len: -mle.len };
1469 | }
1470 | eSlack = mleSlack;
1471 | }
1472 | }
1473 | });
1474 |
1475 | return result;
1476 | }
1477 |
1478 | while (remaining.size() > 0) {
1479 | var result = findMinSlack();
1480 | remaining.remove(result.graphNode);
1481 | g.node(result.graphNode).rank = g.node(result.treeNode).rank + result.len;
1482 | }
1483 | }
1484 |
1485 | function normalize(g) {
1486 | var m = util.min(g.nodes().map(function(u) { return g.node(u).rank; }));
1487 | g.eachNode(function(u, node) { node.rank -= m; });
1488 | }
1489 |
1490 | /*
1491 | * This id can be used to group (in an undirected manner) multi-edges
1492 | * incident on the same two nodes.
1493 | */
1494 | function incidenceId(u, v) {
1495 | return u < v ? u.length + ":" + u + "-" + v : v.length + ":" + v + "-" + u;
1496 | }
1497 |
1498 | },{"../util":10,"graphlib":12}],10:[function(require,module,exports){
1499 | /*
1500 | * Returns the smallest value in the array.
1501 | */
1502 | exports.min = function(values) {
1503 | return Math.min.apply(null, values);
1504 | };
1505 |
1506 | /*
1507 | * Returns the largest value in the array.
1508 | */
1509 | exports.max = function(values) {
1510 | return Math.max.apply(null, values);
1511 | };
1512 |
1513 | /*
1514 | * Returns `true` only if `f(x)` is `true` for all `x` in `xs`. Otherwise
1515 | * returns `false`. This function will return immediately if it finds a
1516 | * case where `f(x)` does not hold.
1517 | */
1518 | exports.all = function(xs, f) {
1519 | for (var i = 0; i < xs.length; ++i) {
1520 | if (!f(xs[i])) {
1521 | return false;
1522 | }
1523 | }
1524 | return true;
1525 | };
1526 |
1527 | /*
1528 | * Accumulates the sum of elements in the given array using the `+` operator.
1529 | */
1530 | exports.sum = function(values) {
1531 | return values.reduce(function(acc, x) { return acc + x; }, 0);
1532 | };
1533 |
1534 | /*
1535 | * Returns an array of all values in the given object.
1536 | */
1537 | exports.values = function(obj) {
1538 | return Object.keys(obj).map(function(k) { return obj[k]; });
1539 | };
1540 |
1541 | exports.createTimer = function(enabled) {
1542 | var self = {};
1543 |
1544 | // Default to disabled
1545 | enabled = enabled || false;
1546 |
1547 | self.enabled = function(x) {
1548 | if (!arguments.length) return enabled;
1549 | enabled = x;
1550 | return self;
1551 | };
1552 |
1553 | self.wrap = function(name, func) {
1554 | return function() {
1555 | var start = enabled ? new Date().getTime() : null;
1556 | try {
1557 | return func.apply(null, arguments);
1558 | } finally {
1559 | if (start) console.log(name + " time: " + (new Date().getTime() - start) + "ms");
1560 | }
1561 | };
1562 | };
1563 |
1564 | return self;
1565 | };
1566 |
1567 | exports.propertyAccessor = function(self, config, field, setHook) {
1568 | return function(x) {
1569 | if (!arguments.length) return config[field];
1570 | config[field] = x;
1571 | if (setHook) setHook(x);
1572 | return self;
1573 | };
1574 | };
1575 |
1576 | },{}],11:[function(require,module,exports){
1577 | module.exports = '0.3.0';
1578 |
1579 | },{}],12:[function(require,module,exports){
1580 | exports.Graph = require("./lib/Graph");
1581 | exports.Digraph = require("./lib/Digraph");
1582 | exports.CGraph = require("./lib/CGraph");
1583 | exports.CDigraph = require("./lib/CDigraph");
1584 | require("./lib/graph-converters");
1585 |
1586 | exports.alg = {
1587 | isAcyclic: require("./lib/alg/isAcyclic"),
1588 | components: require("./lib/alg/components"),
1589 | dijkstra: require("./lib/alg/dijkstra"),
1590 | dijkstraAll: require("./lib/alg/dijkstraAll"),
1591 | findCycles: require("./lib/alg/findCycles"),
1592 | floydWarshall: require("./lib/alg/floydWarshall"),
1593 | prim: require("./lib/alg/prim"),
1594 | tarjan: require("./lib/alg/tarjan"),
1595 | topsort: require("./lib/alg/topsort")
1596 | };
1597 |
1598 | exports.converter = {
1599 | json: require("./lib/converter/json.js")
1600 | };
1601 |
1602 | exports.data = {
1603 | PriorityQueue: require("./lib/data/PriorityQueue"),
1604 | Set: require("./lib/data/Set")
1605 | };
1606 |
1607 | var filter = require("./lib/filter");
1608 | exports.filter = {
1609 | all: filter.all,
1610 | nodesFromList: filter.nodesFromList
1611 | };
1612 |
1613 | exports.version = require("./lib/version");
1614 |
1615 | },{"./lib/CDigraph":14,"./lib/CGraph":15,"./lib/Digraph":16,"./lib/Graph":17,"./lib/alg/components":18,"./lib/alg/dijkstra":19,"./lib/alg/dijkstraAll":20,"./lib/alg/findCycles":21,"./lib/alg/floydWarshall":22,"./lib/alg/isAcyclic":23,"./lib/alg/prim":24,"./lib/alg/tarjan":25,"./lib/alg/topsort":26,"./lib/converter/json.js":28,"./lib/data/PriorityQueue":29,"./lib/data/Set":30,"./lib/filter":31,"./lib/graph-converters":32,"./lib/version":34}],13:[function(require,module,exports){
1616 | var filter = require("./filter"),
1617 | /* jshint -W079 */
1618 | Set = require("./data/Set");
1619 |
1620 | module.exports = BaseGraph;
1621 |
1622 | function BaseGraph() {
1623 | // The value assigned to the graph itself.
1624 | this._value = undefined;
1625 |
1626 | // Map of node id -> { id, value }
1627 | this._nodes = {};
1628 |
1629 | // Map of edge id -> { id, u, v, value }
1630 | this._edges = {};
1631 |
1632 | // Used to generate a unique id in the graph
1633 | this._nextId = 0;
1634 | }
1635 |
1636 | // Number of nodes
1637 | BaseGraph.prototype.order = function() {
1638 | return Object.keys(this._nodes).length;
1639 | };
1640 |
1641 | // Number of edges
1642 | BaseGraph.prototype.size = function() {
1643 | return Object.keys(this._edges).length;
1644 | };
1645 |
1646 | // Accessor for graph level value
1647 | BaseGraph.prototype.graph = function(value) {
1648 | if (arguments.length === 0) {
1649 | return this._value;
1650 | }
1651 | this._value = value;
1652 | };
1653 |
1654 | BaseGraph.prototype.hasNode = function(u) {
1655 | return u in this._nodes;
1656 | };
1657 |
1658 | BaseGraph.prototype.node = function(u, value) {
1659 | var node = this._strictGetNode(u);
1660 | if (arguments.length === 1) {
1661 | return node.value;
1662 | }
1663 | node.value = value;
1664 | };
1665 |
1666 | BaseGraph.prototype.nodes = function() {
1667 | var nodes = [];
1668 | this.eachNode(function(id) { nodes.push(id); });
1669 | return nodes;
1670 | };
1671 |
1672 | BaseGraph.prototype.eachNode = function(func) {
1673 | for (var k in this._nodes) {
1674 | var node = this._nodes[k];
1675 | func(node.id, node.value);
1676 | }
1677 | };
1678 |
1679 | BaseGraph.prototype.hasEdge = function(e) {
1680 | return e in this._edges;
1681 | };
1682 |
1683 | BaseGraph.prototype.edge = function(e, value) {
1684 | var edge = this._strictGetEdge(e);
1685 | if (arguments.length === 1) {
1686 | return edge.value;
1687 | }
1688 | edge.value = value;
1689 | };
1690 |
1691 | BaseGraph.prototype.edges = function() {
1692 | var es = [];
1693 | this.eachEdge(function(id) { es.push(id); });
1694 | return es;
1695 | };
1696 |
1697 | BaseGraph.prototype.eachEdge = function(func) {
1698 | for (var k in this._edges) {
1699 | var edge = this._edges[k];
1700 | func(edge.id, edge.u, edge.v, edge.value);
1701 | }
1702 | };
1703 |
1704 | BaseGraph.prototype.incidentNodes = function(e) {
1705 | var edge = this._strictGetEdge(e);
1706 | return [edge.u, edge.v];
1707 | };
1708 |
1709 | BaseGraph.prototype.addNode = function(u, value) {
1710 | if (u === undefined || u === null) {
1711 | do {
1712 | u = "_" + (++this._nextId);
1713 | } while (this.hasNode(u));
1714 | } else if (this.hasNode(u)) {
1715 | throw new Error("Graph already has node '" + u + "':\n" + this.toString());
1716 | }
1717 | this._nodes[u] = { id: u, value: value };
1718 | return u;
1719 | };
1720 |
1721 | BaseGraph.prototype.delNode = function(u) {
1722 | this._strictGetNode(u);
1723 | this.incidentEdges(u).forEach(function(e) { this.delEdge(e); }, this);
1724 | delete this._nodes[u];
1725 | };
1726 |
1727 | // inMap and outMap are opposite sides of an incidence map. For example, for
1728 | // Graph these would both come from the _incidentEdges map, while for Digraph
1729 | // they would come from _inEdges and _outEdges.
1730 | BaseGraph.prototype._addEdge = function(e, u, v, value, inMap, outMap) {
1731 | this._strictGetNode(u);
1732 | this._strictGetNode(v);
1733 |
1734 | if (e === undefined || e === null) {
1735 | do {
1736 | e = "_" + (++this._nextId);
1737 | } while (this.hasEdge(e));
1738 | }
1739 | else if (this.hasEdge(e)) {
1740 | throw new Error("Graph already has edge '" + e + "':\n" + this.toString());
1741 | }
1742 |
1743 | this._edges[e] = { id: e, u: u, v: v, value: value };
1744 | addEdgeToMap(inMap[v], u, e);
1745 | addEdgeToMap(outMap[u], v, e);
1746 |
1747 | return e;
1748 | };
1749 |
1750 | // See note for _addEdge regarding inMap and outMap.
1751 | BaseGraph.prototype._delEdge = function(e, inMap, outMap) {
1752 | var edge = this._strictGetEdge(e);
1753 | delEdgeFromMap(inMap[edge.v], edge.u, e);
1754 | delEdgeFromMap(outMap[edge.u], edge.v, e);
1755 | delete this._edges[e];
1756 | };
1757 |
1758 | BaseGraph.prototype.copy = function() {
1759 | var copy = new this.constructor();
1760 | copy.graph(this.graph);
1761 | this.eachNode(function(u, value) { copy.addNode(u, value); });
1762 | this.eachEdge(function(e, u, v, value) { copy.addEdge(e, u, v, value); });
1763 | return copy;
1764 | };
1765 |
1766 | BaseGraph.prototype.filterNodes = function(filter) {
1767 | var copy = this.copy();
1768 | this.nodes().forEach(function(u) {
1769 | if (!filter(u)) {
1770 | copy.delNode(u);
1771 | }
1772 | });
1773 | return copy;
1774 | };
1775 |
1776 | BaseGraph.prototype._strictGetNode = function(u) {
1777 | var node = this._nodes[u];
1778 | if (node === undefined) {
1779 | throw new Error("Node '" + u + "' is not in graph:\n" + this.toString());
1780 | }
1781 | return node;
1782 | };
1783 |
1784 | BaseGraph.prototype._strictGetEdge = function(e) {
1785 | var edge = this._edges[e];
1786 | if (edge === undefined) {
1787 | throw new Error("Edge '" + e + "' is not in graph:\n" + this.toString());
1788 | }
1789 | return edge;
1790 | };
1791 |
1792 | function addEdgeToMap(map, v, e) {
1793 | (map[v] || (map[v] = new Set())).add(e);
1794 | }
1795 |
1796 | function delEdgeFromMap(map, v, e) {
1797 | var vEntry = map[v];
1798 | vEntry.remove(e);
1799 | if (vEntry.size() === 0) {
1800 | delete map[v];
1801 | }
1802 | }
1803 |
1804 |
1805 | },{"./data/Set":30,"./filter":31}],14:[function(require,module,exports){
1806 | var Digraph = require("./Digraph"),
1807 | compoundify = require("./compoundify");
1808 |
1809 | var CDigraph = compoundify(Digraph);
1810 |
1811 | module.exports = CDigraph;
1812 |
1813 | CDigraph.fromDigraph = function(src) {
1814 | var g = new CDigraph(),
1815 | graphValue = src.graph();
1816 |
1817 | if (graphValue !== undefined) {
1818 | g.graph(graphValue);
1819 | }
1820 |
1821 | src.eachNode(function(u, value) {
1822 | if (value === undefined) {
1823 | g.addNode(u);
1824 | } else {
1825 | g.addNode(u, value);
1826 | }
1827 | });
1828 | src.eachEdge(function(e, u, v, value) {
1829 | if (value === undefined) {
1830 | g.addEdge(null, u, v);
1831 | } else {
1832 | g.addEdge(null, u, v, value);
1833 | }
1834 | });
1835 | return g;
1836 | };
1837 |
1838 | CDigraph.prototype.toString = function() {
1839 | return "CDigraph " + JSON.stringify(this, null, 2);
1840 | };
1841 |
1842 | },{"./Digraph":16,"./compoundify":27}],15:[function(require,module,exports){
1843 | var Graph = require("./Graph"),
1844 | compoundify = require("./compoundify");
1845 |
1846 | var CGraph = compoundify(Graph);
1847 |
1848 | module.exports = CGraph;
1849 |
1850 | CGraph.fromGraph = function(src) {
1851 | var g = new CGraph(),
1852 | graphValue = src.graph();
1853 |
1854 | if (graphValue !== undefined) {
1855 | g.graph(graphValue);
1856 | }
1857 |
1858 | src.eachNode(function(u, value) {
1859 | if (value === undefined) {
1860 | g.addNode(u);
1861 | } else {
1862 | g.addNode(u, value);
1863 | }
1864 | });
1865 | src.eachEdge(function(e, u, v, value) {
1866 | if (value === undefined) {
1867 | g.addEdge(null, u, v);
1868 | } else {
1869 | g.addEdge(null, u, v, value);
1870 | }
1871 | });
1872 | return g;
1873 | };
1874 |
1875 | CGraph.prototype.toString = function() {
1876 | return "CGraph " + JSON.stringify(this, null, 2);
1877 | };
1878 |
1879 | },{"./Graph":17,"./compoundify":27}],16:[function(require,module,exports){
1880 | /*
1881 | * This file is organized with in the following order:
1882 | *
1883 | * Exports
1884 | * Graph constructors
1885 | * Graph queries (e.g. nodes(), edges()
1886 | * Graph mutators
1887 | * Helper functions
1888 | */
1889 |
1890 | var util = require("./util"),
1891 | BaseGraph = require("./BaseGraph"),
1892 | /* jshint -W079 */
1893 | Set = require("./data/Set");
1894 |
1895 | module.exports = Digraph;
1896 |
1897 | /*
1898 | * Constructor to create a new directed multi-graph.
1899 | */
1900 | function Digraph() {
1901 | BaseGraph.call(this);
1902 |
1903 | /*! Map of sourceId -> {targetId -> Set of edge ids} */
1904 | this._inEdges = {};
1905 |
1906 | /*! Map of targetId -> {sourceId -> Set of edge ids} */
1907 | this._outEdges = {};
1908 | }
1909 |
1910 | Digraph.prototype = new BaseGraph();
1911 | Digraph.prototype.constructor = Digraph;
1912 |
1913 | /*
1914 | * Always returns `true`.
1915 | */
1916 | Digraph.prototype.isDirected = function() {
1917 | return true;
1918 | };
1919 |
1920 | /*
1921 | * Returns all successors of the node with the id `u`. That is, all nodes
1922 | * that have the node `u` as their source are returned.
1923 | *
1924 | * If no node `u` exists in the graph this function throws an Error.
1925 | *
1926 | * @param {String} u a node id
1927 | */
1928 | Digraph.prototype.successors = function(u) {
1929 | this._strictGetNode(u);
1930 | return Object.keys(this._outEdges[u])
1931 | .map(function(v) { return this._nodes[v].id; }, this);
1932 | };
1933 |
1934 | /*
1935 | * Returns all predecessors of the node with the id `u`. That is, all nodes
1936 | * that have the node `u` as their target are returned.
1937 | *
1938 | * If no node `u` exists in the graph this function throws an Error.
1939 | *
1940 | * @param {String} u a node id
1941 | */
1942 | Digraph.prototype.predecessors = function(u) {
1943 | this._strictGetNode(u);
1944 | return Object.keys(this._inEdges[u])
1945 | .map(function(v) { return this._nodes[v].id; }, this);
1946 | };
1947 |
1948 | /*
1949 | * Returns all nodes that are adjacent to the node with the id `u`. In other
1950 | * words, this function returns the set of all successors and predecessors of
1951 | * node `u`.
1952 | *
1953 | * @param {String} u a node id
1954 | */
1955 | Digraph.prototype.neighbors = function(u) {
1956 | return Set.unionAll([this.successors(u), this.predecessors(u)]).keys();
1957 | };
1958 |
1959 | /*
1960 | * Returns all nodes in the graph that have no in-edges.
1961 | */
1962 | Digraph.prototype.sources = function() {
1963 | var self = this;
1964 | return this._filterNodes(function(u) {
1965 | // This could have better space characteristics if we had an inDegree function.
1966 | return self.inEdges(u).length === 0;
1967 | });
1968 | };
1969 |
1970 | /*
1971 | * Returns all nodes in the graph that have no out-edges.
1972 | */
1973 | Digraph.prototype.sinks = function() {
1974 | var self = this;
1975 | return this._filterNodes(function(u) {
1976 | // This could have better space characteristics if we have an outDegree function.
1977 | return self.outEdges(u).length === 0;
1978 | });
1979 | };
1980 |
1981 | /*
1982 | * Returns the source node incident on the edge identified by the id `e`. If no
1983 | * such edge exists in the graph this function throws an Error.
1984 | *
1985 | * @param {String} e an edge id
1986 | */
1987 | Digraph.prototype.source = function(e) {
1988 | return this._strictGetEdge(e).u;
1989 | };
1990 |
1991 | /*
1992 | * Returns the target node incident on the edge identified by the id `e`. If no
1993 | * such edge exists in the graph this function throws an Error.
1994 | *
1995 | * @param {String} e an edge id
1996 | */
1997 | Digraph.prototype.target = function(e) {
1998 | return this._strictGetEdge(e).v;
1999 | };
2000 |
2001 | /*
2002 | * Returns an array of ids for all edges in the graph that have the node
2003 | * `target` as their target. If the node `target` is not in the graph this
2004 | * function raises an Error.
2005 | *
2006 | * Optionally a `source` node can also be specified. This causes the results
2007 | * to be filtered such that only edges from `source` to `target` are included.
2008 | * If the node `source` is specified but is not in the graph then this function
2009 | * raises an Error.
2010 | *
2011 | * @param {String} target the target node id
2012 | * @param {String} [source] an optional source node id
2013 | */
2014 | Digraph.prototype.inEdges = function(target, source) {
2015 | this._strictGetNode(target);
2016 | var results = Set.unionAll(util.values(this._inEdges[target])).keys();
2017 | if (arguments.length > 1) {
2018 | this._strictGetNode(source);
2019 | results = results.filter(function(e) { return this.source(e) === source; }, this);
2020 | }
2021 | return results;
2022 | };
2023 |
2024 | /*
2025 | * Returns an array of ids for all edges in the graph that have the node
2026 | * `source` as their source. If the node `source` is not in the graph this
2027 | * function raises an Error.
2028 | *
2029 | * Optionally a `target` node may also be specified. This causes the results
2030 | * to be filtered such that only edges from `source` to `target` are included.
2031 | * If the node `target` is specified but is not in the graph then this function
2032 | * raises an Error.
2033 | *
2034 | * @param {String} source the source node id
2035 | * @param {String} [target] an optional target node id
2036 | */
2037 | Digraph.prototype.outEdges = function(source, target) {
2038 | this._strictGetNode(source);
2039 | var results = Set.unionAll(util.values(this._outEdges[source])).keys();
2040 | if (arguments.length > 1) {
2041 | this._strictGetNode(target);
2042 | results = results.filter(function(e) { return this.target(e) === target; }, this);
2043 | }
2044 | return results;
2045 | };
2046 |
2047 | /*
2048 | * Returns an array of ids for all edges in the graph that have the `u` as
2049 | * their source or their target. If the node `u` is not in the graph this
2050 | * function raises an Error.
2051 | *
2052 | * Optionally a `v` node may also be specified. This causes the results to be
2053 | * filtered such that only edges between `u` and `v` - in either direction -
2054 | * are included. IF the node `v` is specified but not in the graph then this
2055 | * function raises an Error.
2056 | *
2057 | * @param {String} u the node for which to find incident edges
2058 | * @param {String} [v] option node that must be adjacent to `u`
2059 | */
2060 | Digraph.prototype.incidentEdges = function(u, v) {
2061 | if (arguments.length > 1) {
2062 | return Set.unionAll([this.outEdges(u, v), this.outEdges(v, u)]).keys();
2063 | } else {
2064 | return Set.unionAll([this.inEdges(u), this.outEdges(u)]).keys();
2065 | }
2066 | };
2067 |
2068 | /*
2069 | * Returns a string representation of this graph.
2070 | */
2071 | Digraph.prototype.toString = function() {
2072 | return "Digraph " + JSON.stringify(this, null, 2);
2073 | };
2074 |
2075 | /*
2076 | * Adds a new node with the id `u` to the graph and assigns it the value
2077 | * `value`. If a node with the id is already a part of the graph this function
2078 | * throws an Error.
2079 | *
2080 | * @param {String} u a node id
2081 | * @param {Object} [value] an optional value to attach to the node
2082 | */
2083 | Digraph.prototype.addNode = function(u, value) {
2084 | u = BaseGraph.prototype.addNode.call(this, u, value);
2085 | this._inEdges[u] = {};
2086 | this._outEdges[u] = {};
2087 | return u;
2088 | };
2089 |
2090 | /*
2091 | * Removes a node from the graph that has the id `u`. Any edges incident on the
2092 | * node are also removed. If the graph does not contain a node with the id this
2093 | * function will throw an Error.
2094 | *
2095 | * @param {String} u a node id
2096 | */
2097 | Digraph.prototype.delNode = function(u) {
2098 | BaseGraph.prototype.delNode.call(this, u);
2099 | delete this._inEdges[u];
2100 | delete this._outEdges[u];
2101 | };
2102 |
2103 | /*
2104 | * Adds a new edge to the graph with the id `e` from a node with the id `source`
2105 | * to a node with an id `target` and assigns it the value `value`. This graph
2106 | * allows more than one edge from `source` to `target` as long as the id `e`
2107 | * is unique in the set of edges. If `e` is `null` the graph will assign a
2108 | * unique identifier to the edge.
2109 | *
2110 | * If `source` or `target` are not present in the graph this function will
2111 | * throw an Error.
2112 | *
2113 | * @param {String} [e] an edge id
2114 | * @param {String} source the source node id
2115 | * @param {String} target the target node id
2116 | * @param {Object} [value] an optional value to attach to the edge
2117 | */
2118 | Digraph.prototype.addEdge = function(e, source, target, value) {
2119 | return BaseGraph.prototype._addEdge.call(this, e, source, target, value,
2120 | this._inEdges, this._outEdges);
2121 | };
2122 |
2123 | /*
2124 | * Removes an edge in the graph with the id `e`. If no edge in the graph has
2125 | * the id `e` this function will throw an Error.
2126 | *
2127 | * @param {String} e an edge id
2128 | */
2129 | Digraph.prototype.delEdge = function(e) {
2130 | BaseGraph.prototype._delEdge.call(this, e, this._inEdges, this._outEdges);
2131 | };
2132 |
2133 | // Unlike BaseGraph.filterNodes, this helper just returns nodes that
2134 | // satisfy a predicate.
2135 | Digraph.prototype._filterNodes = function(pred) {
2136 | var filtered = [];
2137 | this.eachNode(function(u) {
2138 | if (pred(u)) {
2139 | filtered.push(u);
2140 | }
2141 | });
2142 | return filtered;
2143 | };
2144 |
2145 |
2146 | },{"./BaseGraph":13,"./data/Set":30,"./util":33}],17:[function(require,module,exports){
2147 | /*
2148 | * This file is organized with in the following order:
2149 | *
2150 | * Exports
2151 | * Graph constructors
2152 | * Graph queries (e.g. nodes(), edges()
2153 | * Graph mutators
2154 | * Helper functions
2155 | */
2156 |
2157 | var util = require("./util"),
2158 | BaseGraph = require("./BaseGraph"),
2159 | /* jshint -W079 */
2160 | Set = require("./data/Set");
2161 |
2162 | module.exports = Graph;
2163 |
2164 | /*
2165 | * Constructor to create a new undirected multi-graph.
2166 | */
2167 | function Graph() {
2168 | BaseGraph.call(this);
2169 |
2170 | /*! Map of nodeId -> { otherNodeId -> Set of edge ids } */
2171 | this._incidentEdges = {};
2172 | }
2173 |
2174 | Graph.prototype = new BaseGraph();
2175 | Graph.prototype.constructor = Graph;
2176 |
2177 | /*
2178 | * Always returns `false`.
2179 | */
2180 | Graph.prototype.isDirected = function() {
2181 | return false;
2182 | };
2183 |
2184 | /*
2185 | * Returns all nodes that are adjacent to the node with the id `u`.
2186 | *
2187 | * @param {String} u a node id
2188 | */
2189 | Graph.prototype.neighbors = function(u) {
2190 | this._strictGetNode(u);
2191 | return Object.keys(this._incidentEdges[u])
2192 | .map(function(v) { return this._nodes[v].id; }, this);
2193 | };
2194 |
2195 | /*
2196 | * Returns an array of ids for all edges in the graph that are incident on `u`.
2197 | * If the node `u` is not in the graph this function raises an Error.
2198 | *
2199 | * Optionally a `v` node may also be specified. This causes the results to be
2200 | * filtered such that only edges between `u` and `v` are included. If the node
2201 | * `v` is specified but not in the graph then this function raises an Error.
2202 | *
2203 | * @param {String} u the node for which to find incident edges
2204 | * @param {String} [v] option node that must be adjacent to `u`
2205 | */
2206 | Graph.prototype.incidentEdges = function(u, v) {
2207 | this._strictGetNode(u);
2208 | if (arguments.length > 1) {
2209 | this._strictGetNode(v);
2210 | return v in this._incidentEdges[u] ? this._incidentEdges[u][v].keys() : [];
2211 | } else {
2212 | return Set.unionAll(util.values(this._incidentEdges[u])).keys();
2213 | }
2214 | };
2215 |
2216 | /*
2217 | * Returns a string representation of this graph.
2218 | */
2219 | Graph.prototype.toString = function() {
2220 | return "Graph " + JSON.stringify(this, null, 2);
2221 | };
2222 |
2223 | /*
2224 | * Adds a new node with the id `u` to the graph and assigns it the value
2225 | * `value`. If a node with the id is already a part of the graph this function
2226 | * throws an Error.
2227 | *
2228 | * @param {String} u a node id
2229 | * @param {Object} [value] an optional value to attach to the node
2230 | */
2231 | Graph.prototype.addNode = function(u, value) {
2232 | u = BaseGraph.prototype.addNode.call(this, u, value);
2233 | this._incidentEdges[u] = {};
2234 | return u;
2235 | };
2236 |
2237 | /*
2238 | * Removes a node from the graph that has the id `u`. Any edges incident on the
2239 | * node are also removed. If the graph does not contain a node with the id this
2240 | * function will throw an Error.
2241 | *
2242 | * @param {String} u a node id
2243 | */
2244 | Graph.prototype.delNode = function(u) {
2245 | BaseGraph.prototype.delNode.call(this, u);
2246 | delete this._incidentEdges[u];
2247 | };
2248 |
2249 | /*
2250 | * Adds a new edge to the graph with the id `e` between a node with the id `u`
2251 | * and a node with an id `v` and assigns it the value `value`. This graph
2252 | * allows more than one edge between `u` and `v` as long as the id `e`
2253 | * is unique in the set of edges. If `e` is `null` the graph will assign a
2254 | * unique identifier to the edge.
2255 | *
2256 | * If `u` or `v` are not present in the graph this function will throw an
2257 | * Error.
2258 | *
2259 | * @param {String} [e] an edge id
2260 | * @param {String} u the node id of one of the adjacent nodes
2261 | * @param {String} v the node id of the other adjacent node
2262 | * @param {Object} [value] an optional value to attach to the edge
2263 | */
2264 | Graph.prototype.addEdge = function(e, u, v, value) {
2265 | return BaseGraph.prototype._addEdge.call(this, e, u, v, value,
2266 | this._incidentEdges, this._incidentEdges);
2267 | };
2268 |
2269 | /*
2270 | * Removes an edge in the graph with the id `e`. If no edge in the graph has
2271 | * the id `e` this function will throw an Error.
2272 | *
2273 | * @param {String} e an edge id
2274 | */
2275 | Graph.prototype.delEdge = function(e) {
2276 | BaseGraph.prototype._delEdge.call(this, e, this._incidentEdges, this._incidentEdges);
2277 | };
2278 |
2279 |
2280 | },{"./BaseGraph":13,"./data/Set":30,"./util":33}],18:[function(require,module,exports){
2281 | var Set = require("../data/Set");
2282 |
2283 | module.exports = components;
2284 |
2285 | /**
2286 | * Finds all [connected components][] in a graph and returns an array of these
2287 | * components. Each component is itself an array that contains the ids of nodes
2288 | * in the component.
2289 | *
2290 | * This function only works with undirected Graphs.
2291 | *
2292 | * [connected components]: http://en.wikipedia.org/wiki/Connected_component_(graph_theory)
2293 | *
2294 | * @param {Graph} g the graph to search for components
2295 | */
2296 | function components(g) {
2297 | var results = [];
2298 | var visited = new Set();
2299 |
2300 | function dfs(v, component) {
2301 | if (!visited.has(v)) {
2302 | visited.add(v);
2303 | component.push(v);
2304 | g.neighbors(v).forEach(function(w) {
2305 | dfs(w, component);
2306 | });
2307 | }
2308 | }
2309 |
2310 | g.nodes().forEach(function(v) {
2311 | var component = [];
2312 | dfs(v, component);
2313 | if (component.length > 0) {
2314 | results.push(component);
2315 | }
2316 | });
2317 |
2318 | return results;
2319 | }
2320 |
2321 | },{"../data/Set":30}],19:[function(require,module,exports){
2322 | var PriorityQueue = require("../data/PriorityQueue"),
2323 | Digraph = require("../Digraph");
2324 |
2325 | module.exports = dijkstra;
2326 |
2327 | /**
2328 | * This function is an implementation of [Dijkstra's algorithm][] which finds
2329 | * the shortest path from **source** to all other nodes in **g**. This
2330 | * function returns a map of `u -> { distance, predecessor }`. The distance
2331 | * property holds the sum of the weights from **source** to `u` along the
2332 | * shortest path or `Number.POSITIVE_INFINITY` if there is no path from
2333 | * **source**. The predecessor property can be used to walk the individual
2334 | * elements of the path from **source** to **u** in reverse order.
2335 | *
2336 | * This function takes an optional `weightFunc(e)` which returns the
2337 | * weight of the edge `e`. If no weightFunc is supplied then each edge is
2338 | * assumed to have a weight of 1. This function throws an Error if any of
2339 | * the traversed edges have a negative edge weight.
2340 | *
2341 | * This function takes an optional `incidentFunc(u)` which returns the ids of
2342 | * all edges incident to the node `u` for the purposes of shortest path
2343 | * traversal. By default this function uses the `g.outEdges` for Digraphs and
2344 | * `g.incidentEdges` for Graphs.
2345 | *
2346 | * This function takes `O((|E| + |V|) * log |V|)` time.
2347 | *
2348 | * [Dijkstra's algorithm]: http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
2349 | *
2350 | * @param {Graph} g the graph to search for shortest paths from **source**
2351 | * @param {Object} source the source from which to start the search
2352 | * @param {Function} [weightFunc] optional weight function
2353 | * @param {Function} [incidentFunc] optional incident function
2354 | */
2355 | function dijkstra(g, source, weightFunc, incidentFunc) {
2356 | var results = {},
2357 | pq = new PriorityQueue();
2358 |
2359 | weightFunc = weightFunc || function() { return 1; };
2360 | incidentFunc = incidentFunc || (g.isDirected()
2361 | ? function(u) { return g.outEdges(u); }
2362 | : function(u) { return g.incidentEdges(u); });
2363 |
2364 | g.nodes().forEach(function(u) {
2365 | var distance = u === source ? 0 : Number.POSITIVE_INFINITY;
2366 | results[u] = { distance: distance };
2367 | pq.add(u, distance);
2368 | });
2369 |
2370 | var u, uEntry;
2371 | while (pq.size() > 0) {
2372 | u = pq.removeMin();
2373 | uEntry = results[u];
2374 | if (uEntry.distance === Number.POSITIVE_INFINITY) {
2375 | break;
2376 | }
2377 |
2378 | incidentFunc(u).forEach(function(e) {
2379 | var incidentNodes = g.incidentNodes(e),
2380 | v = incidentNodes[0] !== u ? incidentNodes[0] : incidentNodes[1],
2381 | vEntry = results[v],
2382 | weight = weightFunc(e),
2383 | distance = uEntry.distance + weight;
2384 |
2385 | if (weight < 0) {
2386 | throw new Error("dijkstra does not allow negative edge weights. Bad edge: " + e + " Weight: " + weight);
2387 | }
2388 |
2389 | if (distance < vEntry.distance) {
2390 | vEntry.distance = distance;
2391 | vEntry.predecessor = u;
2392 | pq.decrease(v, distance);
2393 | }
2394 | });
2395 | }
2396 |
2397 | return results;
2398 | }
2399 |
2400 | },{"../Digraph":16,"../data/PriorityQueue":29}],20:[function(require,module,exports){
2401 | var dijkstra = require("./dijkstra");
2402 |
2403 | module.exports = dijkstraAll;
2404 |
2405 | /**
2406 | * This function finds the shortest path from each node to every other
2407 | * reachable node in the graph. It is similar to [alg.dijkstra][], but
2408 | * instead of returning a single-source array, it returns a mapping of
2409 | * of `source -> alg.dijksta(g, source, weightFunc, incidentFunc)`.
2410 | *
2411 | * This function takes an optional `weightFunc(e)` which returns the
2412 | * weight of the edge `e`. If no weightFunc is supplied then each edge is
2413 | * assumed to have a weight of 1. This function throws an Error if any of
2414 | * the traversed edges have a negative edge weight.
2415 | *
2416 | * This function takes an optional `incidentFunc(u)` which returns the ids of
2417 | * all edges incident to the node `u` for the purposes of shortest path
2418 | * traversal. By default this function uses the `outEdges` function on the
2419 | * supplied graph.
2420 | *
2421 | * This function takes `O(|V| * (|E| + |V|) * log |V|)` time.
2422 | *
2423 | * [alg.dijkstra]: dijkstra.js.html#dijkstra
2424 | *
2425 | * @param {Graph} g the graph to search for shortest paths from **source**
2426 | * @param {Function} [weightFunc] optional weight function
2427 | * @param {Function} [incidentFunc] optional incident function
2428 | */
2429 | function dijkstraAll(g, weightFunc, incidentFunc) {
2430 | var results = {};
2431 | g.nodes().forEach(function(u) {
2432 | results[u] = dijkstra(g, u, weightFunc, incidentFunc);
2433 | });
2434 | return results;
2435 | }
2436 |
2437 | },{"./dijkstra":19}],21:[function(require,module,exports){
2438 | var tarjan = require("./tarjan");
2439 |
2440 | module.exports = findCycles;
2441 |
2442 | /*
2443 | * Given a Digraph **g** this function returns all nodes that are part of a
2444 | * cycle. Since there may be more than one cycle in a graph this function
2445 | * returns an array of these cycles, where each cycle is itself represented
2446 | * by an array of ids for each node involved in that cycle.
2447 | *
2448 | * [alg.isAcyclic][] is more efficient if you only need to determine whether
2449 | * a graph has a cycle or not.
2450 | *
2451 | * [alg.isAcyclic]: isAcyclic.js.html#isAcyclic
2452 | *
2453 | * @param {Digraph} g the graph to search for cycles.
2454 | */
2455 | function findCycles(g) {
2456 | return tarjan(g).filter(function(cmpt) { return cmpt.length > 1; });
2457 | }
2458 |
2459 | },{"./tarjan":25}],22:[function(require,module,exports){
2460 | var Digraph = require("../Digraph");
2461 |
2462 | module.exports = floydWarshall;
2463 |
2464 | /**
2465 | * This function is an implementation of the [Floyd-Warshall algorithm][],
2466 | * which finds the shortest path from each node to every other reachable node
2467 | * in the graph. It is similar to [alg.dijkstraAll][], but it handles negative
2468 | * edge weights and is more efficient for some types of graphs. This function
2469 | * returns a map of `source -> { target -> { distance, predecessor }`. The
2470 | * distance property holds the sum of the weights from `source` to `target`
2471 | * along the shortest path of `Number.POSITIVE_INFINITY` if there is no path
2472 | * from `source`. The predecessor property can be used to walk the individual
2473 | * elements of the path from `source` to `target` in reverse order.
2474 | *
2475 | * This function takes an optional `weightFunc(e)` which returns the
2476 | * weight of the edge `e`. If no weightFunc is supplied then each edge is
2477 | * assumed to have a weight of 1.
2478 | *
2479 | * This function takes an optional `incidentFunc(u)` which returns the ids of
2480 | * all edges incident to the node `u` for the purposes of shortest path
2481 | * traversal. By default this function uses the `outEdges` function on the
2482 | * supplied graph.
2483 | *
2484 | * This algorithm takes O(|V|^3) time.
2485 | *
2486 | * [Floyd-Warshall algorithm]: https://en.wikipedia.org/wiki/Floyd-Warshall_algorithm
2487 | * [alg.dijkstraAll]: dijkstraAll.js.html#dijkstraAll
2488 | *
2489 | * @param {Graph} g the graph to search for shortest paths from **source**
2490 | * @param {Function} [weightFunc] optional weight function
2491 | * @param {Function} [incidentFunc] optional incident function
2492 | */
2493 | function floydWarshall(g, weightFunc, incidentFunc) {
2494 | var results = {},
2495 | nodes = g.nodes();
2496 |
2497 | weightFunc = weightFunc || function() { return 1; };
2498 | incidentFunc = incidentFunc || (g.isDirected()
2499 | ? function(u) { return g.outEdges(u); }
2500 | : function(u) { return g.incidentEdges(u); });
2501 |
2502 | nodes.forEach(function(u) {
2503 | results[u] = {};
2504 | results[u][u] = { distance: 0 };
2505 | nodes.forEach(function(v) {
2506 | if (u !== v) {
2507 | results[u][v] = { distance: Number.POSITIVE_INFINITY };
2508 | }
2509 | });
2510 | incidentFunc(u).forEach(function(e) {
2511 | var incidentNodes = g.incidentNodes(e),
2512 | v = incidentNodes[0] !== u ? incidentNodes[0] : incidentNodes[1],
2513 | d = weightFunc(e);
2514 | if (d < results[u][v].distance) {
2515 | results[u][v] = { distance: d, predecessor: u };
2516 | }
2517 | });
2518 | });
2519 |
2520 | nodes.forEach(function(k) {
2521 | var rowK = results[k];
2522 | nodes.forEach(function(i) {
2523 | var rowI = results[i];
2524 | nodes.forEach(function(j) {
2525 | var ik = rowI[k];
2526 | var kj = rowK[j];
2527 | var ij = rowI[j];
2528 | var altDistance = ik.distance + kj.distance;
2529 | if (altDistance < ij.distance) {
2530 | ij.distance = altDistance;
2531 | ij.predecessor = kj.predecessor;
2532 | }
2533 | });
2534 | });
2535 | });
2536 |
2537 | return results;
2538 | }
2539 |
2540 | },{"../Digraph":16}],23:[function(require,module,exports){
2541 | var topsort = require("./topsort");
2542 |
2543 | module.exports = isAcyclic;
2544 |
2545 | /*
2546 | * Given a Digraph **g** this function returns `true` if the graph has no
2547 | * cycles and returns `false` if it does. This algorithm returns as soon as it
2548 | * detects the first cycle.
2549 | *
2550 | * Use [alg.findCycles][] if you need the actual list of cycles in a graph.
2551 | *
2552 | * [alg.findCycles]: findCycles.js.html#findCycles
2553 | *
2554 | * @param {Digraph} g the graph to test for cycles
2555 | */
2556 | function isAcyclic(g) {
2557 | try {
2558 | topsort(g);
2559 | } catch (e) {
2560 | if (e instanceof topsort.CycleException) return false;
2561 | throw e;
2562 | }
2563 | return true;
2564 | }
2565 |
2566 | },{"./topsort":26}],24:[function(require,module,exports){
2567 | var Graph = require("../Graph"),
2568 | PriorityQueue = require("../data/PriorityQueue");
2569 |
2570 | module.exports = prim;
2571 |
2572 | /**
2573 | * [Prim's algorithm][] takes a connected undirected graph and generates a
2574 | * [minimum spanning tree][]. This function returns the minimum spanning
2575 | * tree as an undirected graph. This algorithm is derived from the description
2576 | * in "Introduction to Algorithms", Third Edition, Cormen, et al., Pg 634.
2577 | *
2578 | * This function takes a `weightFunc(e)` which returns the weight of the edge
2579 | * `e`. It throws an Error if the graph is not connected.
2580 | *
2581 | * This function takes `O(|E| log |V|)` time.
2582 | *
2583 | * [Prim's algorithm]: https://en.wikipedia.org/wiki/Prim's_algorithm
2584 | * [minimum spanning tree]: https://en.wikipedia.org/wiki/Minimum_spanning_tree
2585 | *
2586 | * @param {Graph} g the graph used to generate the minimum spanning tree
2587 | * @param {Function} weightFunc the weight function to use
2588 | */
2589 | function prim(g, weightFunc) {
2590 | var result = new Graph(),
2591 | parents = {},
2592 | pq = new PriorityQueue();
2593 |
2594 | if (g.order() === 0) {
2595 | return result;
2596 | }
2597 |
2598 | g.eachNode(function(u) {
2599 | pq.add(u, Number.POSITIVE_INFINITY);
2600 | result.addNode(u);
2601 | });
2602 |
2603 | // Start from an arbitrary node
2604 | pq.decrease(g.nodes()[0], 0);
2605 |
2606 | var init = false;
2607 | while (pq.size() > 0) {
2608 | var u = pq.removeMin();
2609 | if (u in parents) {
2610 | result.addEdge(null, u, parents[u]);
2611 | } else if (init) {
2612 | throw new Error("Input graph is not connected: " + g);
2613 | } else {
2614 | init = true;
2615 | }
2616 |
2617 | g.incidentEdges(u).forEach(function(e) {
2618 | var incidentNodes = g.incidentNodes(e),
2619 | v = incidentNodes[0] !== u ? incidentNodes[0] : incidentNodes[1],
2620 | pri = pq.priority(v);
2621 | if (pri !== undefined) {
2622 | var edgeWeight = weightFunc(e);
2623 | if (edgeWeight < pri) {
2624 | parents[v] = u;
2625 | pq.decrease(v, edgeWeight);
2626 | }
2627 | }
2628 | });
2629 | }
2630 |
2631 | return result;
2632 | }
2633 |
2634 | },{"../Graph":17,"../data/PriorityQueue":29}],25:[function(require,module,exports){
2635 | var Digraph = require("../Digraph");
2636 |
2637 | module.exports = tarjan;
2638 |
2639 | /**
2640 | * This function is an implementation of [Tarjan's algorithm][] which finds
2641 | * all [strongly connected components][] in the directed graph **g**. Each
2642 | * strongly connected component is composed of nodes that can reach all other
2643 | * nodes in the component via directed edges. A strongly connected component
2644 | * can consist of a single node if that node cannot both reach and be reached
2645 | * by any other specific node in the graph. Components of more than one node
2646 | * are guaranteed to have at least one cycle.
2647 | *
2648 | * This function returns an array of components. Each component is itself an
2649 | * array that contains the ids of all nodes in the component.
2650 | *
2651 | * [Tarjan's algorithm]: http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm
2652 | * [strongly connected components]: http://en.wikipedia.org/wiki/Strongly_connected_component
2653 | *
2654 | * @param {Digraph} g the graph to search for strongly connected components
2655 | */
2656 | function tarjan(g) {
2657 | if (!g.isDirected()) {
2658 | throw new Error("tarjan can only be applied to a directed graph. Bad input: " + g);
2659 | }
2660 |
2661 | var index = 0,
2662 | stack = [],
2663 | visited = {}, // node id -> { onStack, lowlink, index }
2664 | results = [];
2665 |
2666 | function dfs(u) {
2667 | var entry = visited[u] = {
2668 | onStack: true,
2669 | lowlink: index,
2670 | index: index++
2671 | };
2672 | stack.push(u);
2673 |
2674 | g.successors(u).forEach(function(v) {
2675 | if (!(v in visited)) {
2676 | dfs(v);
2677 | entry.lowlink = Math.min(entry.lowlink, visited[v].lowlink);
2678 | } else if (visited[v].onStack) {
2679 | entry.lowlink = Math.min(entry.lowlink, visited[v].index);
2680 | }
2681 | });
2682 |
2683 | if (entry.lowlink === entry.index) {
2684 | var cmpt = [],
2685 | v;
2686 | do {
2687 | v = stack.pop();
2688 | visited[v].onStack = false;
2689 | cmpt.push(v);
2690 | } while (u !== v);
2691 | results.push(cmpt);
2692 | }
2693 | }
2694 |
2695 | g.nodes().forEach(function(u) {
2696 | if (!(u in visited)) {
2697 | dfs(u);
2698 | }
2699 | });
2700 |
2701 | return results;
2702 | }
2703 |
2704 | },{"../Digraph":16}],26:[function(require,module,exports){
2705 | var Digraph = require("../Digraph");
2706 |
2707 | module.exports = topsort;
2708 | topsort.CycleException = CycleException;
2709 |
2710 | /*
2711 | * Given a graph **g**, this function returns an ordered list of nodes such
2712 | * that for each edge `u -> v`, `u` appears before `v` in the list. If the
2713 | * graph has a cycle it is impossible to generate such a list and
2714 | * **CycleException** is thrown.
2715 | *
2716 | * See [topological sorting](https://en.wikipedia.org/wiki/Topological_sorting)
2717 | * for more details about how this algorithm works.
2718 | *
2719 | * @param {Digraph} g the graph to sort
2720 | */
2721 | function topsort(g) {
2722 | if (!g.isDirected()) {
2723 | throw new Error("topsort can only be applied to a directed graph. Bad input: " + g);
2724 | }
2725 |
2726 | var visited = {};
2727 | var stack = {};
2728 | var results = [];
2729 |
2730 | function visit(node) {
2731 | if (node in stack) {
2732 | throw new CycleException();
2733 | }
2734 |
2735 | if (!(node in visited)) {
2736 | stack[node] = true;
2737 | visited[node] = true;
2738 | g.predecessors(node).forEach(function(pred) {
2739 | visit(pred);
2740 | });
2741 | delete stack[node];
2742 | results.push(node);
2743 | }
2744 | }
2745 |
2746 | var sinks = g.sinks();
2747 | if (g.order() !== 0 && sinks.length === 0) {
2748 | throw new CycleException();
2749 | }
2750 |
2751 | g.sinks().forEach(function(sink) {
2752 | visit(sink);
2753 | });
2754 |
2755 | return results;
2756 | }
2757 |
2758 | function CycleException() {}
2759 |
2760 | CycleException.prototype.toString = function() {
2761 | return "Graph has at least one cycle";
2762 | };
2763 |
2764 | },{"../Digraph":16}],27:[function(require,module,exports){
2765 | // This file provides a helper function that mixes-in Dot behavior to an
2766 | // existing graph prototype.
2767 |
2768 | var /* jshint -W079 */
2769 | Set = require("./data/Set");
2770 |
2771 | module.exports = compoundify;
2772 |
2773 | // Extends the given SuperConstructor with the ability for nodes to contain
2774 | // other nodes. A special node id `null` is used to indicate the root graph.
2775 | function compoundify(SuperConstructor) {
2776 | function Constructor() {
2777 | SuperConstructor.call(this);
2778 |
2779 | // Map of object id -> parent id (or null for root graph)
2780 | this._parents = {};
2781 |
2782 | // Map of id (or null) -> children set
2783 | this._children = { null: new Set() };
2784 | }
2785 |
2786 | Constructor.prototype = new SuperConstructor();
2787 | Constructor.prototype.constructor = Constructor;
2788 |
2789 | Constructor.prototype.parent = function(u, parent) {
2790 | this._strictGetNode(u);
2791 |
2792 | if (arguments.length < 2) {
2793 | return this._parents[u];
2794 | }
2795 |
2796 | if (u === parent) {
2797 | throw new Error("Cannot make " + u + " a parent of itself");
2798 | }
2799 | if (parent !== null) {
2800 | this._strictGetNode(parent);
2801 | }
2802 |
2803 | this._children[this._parents[u]].remove(u);
2804 | this._parents[u] = parent;
2805 | this._children[parent].add(u);
2806 | };
2807 |
2808 | Constructor.prototype.children = function(u) {
2809 | if (u !== null) {
2810 | this._strictGetNode(u);
2811 | }
2812 | return this._children[u].keys();
2813 | };
2814 |
2815 | Constructor.prototype.addNode = function(u, value) {
2816 | u = SuperConstructor.prototype.addNode.call(this, u, value);
2817 | this._parents[u] = null;
2818 | this._children[u] = new Set();
2819 | this._children[null].add(u);
2820 | return u;
2821 | };
2822 |
2823 | Constructor.prototype.delNode = function(u) {
2824 | // Promote all children to the parent of the subgraph
2825 | var parent = this.parent(u);
2826 | this._children[u].keys().forEach(function(child) {
2827 | this.parent(child, parent);
2828 | }, this);
2829 |
2830 | this._children[parent].remove(u);
2831 | delete this._parents[u];
2832 | delete this._children[u];
2833 |
2834 | return SuperConstructor.prototype.delNode.call(this, u);
2835 | };
2836 |
2837 | Constructor.prototype.copy = function() {
2838 | var copy = SuperConstructor.prototype.copy.call(this);
2839 | this.nodes().forEach(function(u) {
2840 | copy.parent(u, this.parent(u));
2841 | }, this);
2842 | return copy;
2843 | };
2844 |
2845 | return Constructor;
2846 | }
2847 |
2848 | },{"./data/Set":30}],28:[function(require,module,exports){
2849 | var Graph = require("../Graph"),
2850 | Digraph = require("../Digraph"),
2851 | CGraph = require("../CGraph"),
2852 | CDigraph = require("../CDigraph");
2853 |
2854 | exports.decode = function(nodes, edges, Ctor) {
2855 | Ctor = Ctor || Digraph;
2856 |
2857 | if (typeOf(nodes) !== "Array") {
2858 | throw new Error("nodes is not an Array");
2859 | }
2860 |
2861 | if (typeOf(edges) !== "Array") {
2862 | throw new Error("edges is not an Array");
2863 | }
2864 |
2865 | if (typeof Ctor === "string") {
2866 | switch(Ctor) {
2867 | case "graph": Ctor = Graph; break;
2868 | case "digraph": Ctor = Digraph; break;
2869 | case "cgraph": Ctor = CGraph; break;
2870 | case "cdigraph": Ctor = CDigraph; break;
2871 | default: throw new Error("Unrecognized graph type: " + Ctor);
2872 | }
2873 | }
2874 |
2875 | var graph = new Ctor();
2876 |
2877 | nodes.forEach(function(u) {
2878 | graph.addNode(u.id, u.value);
2879 | });
2880 |
2881 | // If the graph is compound, set up children...
2882 | if (graph.parent) {
2883 | nodes.forEach(function(u) {
2884 | if (u.children) {
2885 | u.children.forEach(function(v) {
2886 | graph.parent(v, u.id);
2887 | });
2888 | }
2889 | });
2890 | }
2891 |
2892 | edges.forEach(function(e) {
2893 | graph.addEdge(e.id, e.u, e.v, e.value);
2894 | });
2895 |
2896 | return graph;
2897 | };
2898 |
2899 | exports.encode = function(graph) {
2900 | var nodes = [];
2901 | var edges = [];
2902 |
2903 | graph.eachNode(function(u, value) {
2904 | var node = {id: u, value: value};
2905 | if (graph.children) {
2906 | var children = graph.children(u);
2907 | if (children.length) {
2908 | node.children = children;
2909 | }
2910 | }
2911 | nodes.push(node);
2912 | });
2913 |
2914 | graph.eachEdge(function(e, u, v, value) {
2915 | edges.push({id: e, u: u, v: v, value: value});
2916 | });
2917 |
2918 | var type;
2919 | if (graph instanceof CDigraph) {
2920 | type = "cdigraph";
2921 | } else if (graph instanceof CGraph) {
2922 | type = "cgraph";
2923 | } else if (graph instanceof Digraph) {
2924 | type = "digraph";
2925 | } else if (graph instanceof Graph) {
2926 | type = "graph";
2927 | } else {
2928 | throw new Error("Couldn't determine type of graph: " + graph);
2929 | }
2930 |
2931 | return { nodes: nodes, edges: edges, type: type };
2932 | };
2933 |
2934 | function typeOf(obj) {
2935 | return Object.prototype.toString.call(obj).slice(8, -1);
2936 | }
2937 |
2938 | },{"../CDigraph":14,"../CGraph":15,"../Digraph":16,"../Graph":17}],29:[function(require,module,exports){
2939 | module.exports = PriorityQueue;
2940 |
2941 | /**
2942 | * A min-priority queue data structure. This algorithm is derived from Cormen,
2943 | * et al., "Introduction to Algorithms". The basic idea of a min-priority
2944 | * queue is that you can efficiently (in O(1) time) get the smallest key in
2945 | * the queue. Adding and removing elements takes O(log n) time. A key can
2946 | * have its priority decreased in O(log n) time.
2947 | */
2948 | function PriorityQueue() {
2949 | this._arr = [];
2950 | this._keyIndices = {};
2951 | }
2952 |
2953 | /**
2954 | * Returns the number of elements in the queue. Takes `O(1)` time.
2955 | */
2956 | PriorityQueue.prototype.size = function() {
2957 | return this._arr.length;
2958 | };
2959 |
2960 | /**
2961 | * Returns the keys that are in the queue. Takes `O(n)` time.
2962 | */
2963 | PriorityQueue.prototype.keys = function() {
2964 | return this._arr.map(function(x) { return x.key; });
2965 | };
2966 |
2967 | /**
2968 | * Returns `true` if **key** is in the queue and `false` if not.
2969 | */
2970 | PriorityQueue.prototype.has = function(key) {
2971 | return key in this._keyIndices;
2972 | };
2973 |
2974 | /**
2975 | * Returns the priority for **key**. If **key** is not present in the queue
2976 | * then this function returns `undefined`. Takes `O(1)` time.
2977 | *
2978 | * @param {Object} key
2979 | */
2980 | PriorityQueue.prototype.priority = function(key) {
2981 | var index = this._keyIndices[key];
2982 | if (index !== undefined) {
2983 | return this._arr[index].priority;
2984 | }
2985 | };
2986 |
2987 | /**
2988 | * Returns the key for the minimum element in this queue. If the queue is
2989 | * empty this function throws an Error. Takes `O(1)` time.
2990 | */
2991 | PriorityQueue.prototype.min = function() {
2992 | if (this.size() === 0) {
2993 | throw new Error("Queue underflow");
2994 | }
2995 | return this._arr[0].key;
2996 | };
2997 |
2998 | /**
2999 | * Inserts a new key into the priority queue. If the key already exists in
3000 | * the queue this function returns `false`; otherwise it will return `true`.
3001 | * Takes `O(n)` time.
3002 | *
3003 | * @param {Object} key the key to add
3004 | * @param {Number} priority the initial priority for the key
3005 | */
3006 | PriorityQueue.prototype.add = function(key, priority) {
3007 | if (!(key in this._keyIndices)) {
3008 | var entry = {key: key, priority: priority};
3009 | var index = this._arr.length;
3010 | this._keyIndices[key] = index;
3011 | this._arr.push(entry);
3012 | this._decrease(index);
3013 | return true;
3014 | }
3015 | return false;
3016 | };
3017 |
3018 | /**
3019 | * Removes and returns the smallest key in the queue. Takes `O(log n)` time.
3020 | */
3021 | PriorityQueue.prototype.removeMin = function() {
3022 | this._swap(0, this._arr.length - 1);
3023 | var min = this._arr.pop();
3024 | delete this._keyIndices[min.key];
3025 | this._heapify(0);
3026 | return min.key;
3027 | };
3028 |
3029 | /**
3030 | * Decreases the priority for **key** to **priority**. If the new priority is
3031 | * greater than the previous priority, this function will throw an Error.
3032 | *
3033 | * @param {Object} key the key for which to raise priority
3034 | * @param {Number} priority the new priority for the key
3035 | */
3036 | PriorityQueue.prototype.decrease = function(key, priority) {
3037 | var index = this._keyIndices[key];
3038 | if (priority > this._arr[index].priority) {
3039 | throw new Error("New priority is greater than current priority. " +
3040 | "Key: " + key + " Old: " + this._arr[index].priority + " New: " + priority);
3041 | }
3042 | this._arr[index].priority = priority;
3043 | this._decrease(index);
3044 | };
3045 |
3046 | PriorityQueue.prototype._heapify = function(i) {
3047 | var arr = this._arr;
3048 | var l = 2 * i,
3049 | r = l + 1,
3050 | largest = i;
3051 | if (l < arr.length) {
3052 | largest = arr[l].priority < arr[largest].priority ? l : largest;
3053 | if (r < arr.length) {
3054 | largest = arr[r].priority < arr[largest].priority ? r : largest;
3055 | }
3056 | if (largest !== i) {
3057 | this._swap(i, largest);
3058 | this._heapify(largest);
3059 | }
3060 | }
3061 | };
3062 |
3063 | PriorityQueue.prototype._decrease = function(index) {
3064 | var arr = this._arr;
3065 | var priority = arr[index].priority;
3066 | var parent;
3067 | while (index > 0) {
3068 | parent = index >> 1;
3069 | if (arr[parent].priority < priority) {
3070 | break;
3071 | }
3072 | this._swap(index, parent);
3073 | index = parent;
3074 | }
3075 | };
3076 |
3077 | PriorityQueue.prototype._swap = function(i, j) {
3078 | var arr = this._arr;
3079 | var keyIndices = this._keyIndices;
3080 | var tmp = arr[i];
3081 | arr[i] = arr[j];
3082 | arr[j] = tmp;
3083 | keyIndices[arr[i].key] = i;
3084 | keyIndices[arr[j].key] = j;
3085 | };
3086 |
3087 | },{}],30:[function(require,module,exports){
3088 | var util = require("../util");
3089 |
3090 | module.exports = Set;
3091 |
3092 | /**
3093 | * Constructs a new Set with an optional set of `initialKeys`.
3094 | *
3095 | * It is important to note that keys are coerced to String for most purposes
3096 | * with this object, similar to the behavior of JavaScript's Object. For
3097 | * example, the following will add only one key:
3098 | *
3099 | * var s = new Set();
3100 | * s.add(1);
3101 | * s.add("1");
3102 | *
3103 | * However, the type of the key is preserved internally so that `keys` returns
3104 | * the original key set uncoerced. For the above example, `keys` would return
3105 | * `[1]`.
3106 | */
3107 | function Set(initialKeys) {
3108 | this._size = 0;
3109 | this._keys = {};
3110 |
3111 | if (initialKeys) {
3112 | initialKeys.forEach(function(key) { this.add(key); }, this);
3113 | }
3114 | }
3115 |
3116 | /**
3117 | * Applies the [intersect](#intersect) function to all sets in the given array
3118 | * and returns the result as a new Set.
3119 | *
3120 | * @param {Set[]} sets the sets to intersect
3121 | */
3122 | Set.intersectAll = function(sets) {
3123 | if (sets.length === 0) {
3124 | return new Set();
3125 | }
3126 |
3127 | var result = new Set(sets[0].keys());
3128 | sets.forEach(function(set) {
3129 | result = result.intersect(set);
3130 | });
3131 | return result;
3132 | };
3133 |
3134 | /**
3135 | * Applies the [union](#union) function to all sets in the given array and
3136 | * returns the result as a new Set.
3137 | *
3138 | * @param {Set[]} sets the sets to union
3139 | */
3140 | Set.unionAll = function(sets) {
3141 | var result = new Set();
3142 | sets.forEach(function(set) {
3143 | result = result.union(set);
3144 | });
3145 | return result;
3146 | };
3147 |
3148 | /**
3149 | * Returns the size of this set in `O(1)` time.
3150 | */
3151 | Set.prototype.size = function() {
3152 | return this._size;
3153 | };
3154 |
3155 | /**
3156 | * Returns the keys in this set. Takes `O(n)` time.
3157 | */
3158 | Set.prototype.keys = function() {
3159 | return util.values(this._keys);
3160 | };
3161 |
3162 | /**
3163 | * Tests if a key is present in this Set. Returns `true` if it is and `false`
3164 | * if not. Takes `O(1)` time.
3165 | */
3166 | Set.prototype.has = function(key) {
3167 | return key in this._keys;
3168 | };
3169 |
3170 | /**
3171 | * Adds a new key to this Set if it is not already present. Returns `true` if
3172 | * the key was added and `false` if it was already present. Takes `O(1)` time.
3173 | */
3174 | Set.prototype.add = function(key) {
3175 | if (!(key in this._keys)) {
3176 | this._keys[key] = key;
3177 | ++this._size;
3178 | return true;
3179 | }
3180 | return false;
3181 | };
3182 |
3183 | /**
3184 | * Removes a key from this Set. If the key was removed this function returns
3185 | * `true`. If not, it returns `false`. Takes `O(1)` time.
3186 | */
3187 | Set.prototype.remove = function(key) {
3188 | if (key in this._keys) {
3189 | delete this._keys[key];
3190 | --this._size;
3191 | return true;
3192 | }
3193 | return false;
3194 | };
3195 |
3196 | /**
3197 | * Returns a new Set that only contains elements in both this set and the
3198 | * `other` set. They keys come from this set.
3199 | *
3200 | * If `other` is not a Set it is treated as an Array.
3201 | *
3202 | * @param {Set} other the other set with which to perform an intersection
3203 | */
3204 | Set.prototype.intersect = function(other) {
3205 | // If the other Set does not look like a Set...
3206 | if (!other.keys) {
3207 | other = new Set(other);
3208 | }
3209 | var result = new Set();
3210 | this.keys().forEach(function(k) {
3211 | if (other.has(k)) {
3212 | result.add(k);
3213 | }
3214 | });
3215 | return result;
3216 | };
3217 |
3218 | /**
3219 | * Returns a new Set that contains all of the keys in `this` set and `other`
3220 | * set. If a key is in `this` set, it is used in preference to the `other` set.
3221 | *
3222 | * If `other` is not a Set it is treated as an Array.
3223 | *
3224 | * @param {Set} other the other set with which to perform a union
3225 | */
3226 | Set.prototype.union = function(other) {
3227 | if (!(other instanceof Set)) {
3228 | other = new Set(other);
3229 | }
3230 | var result = new Set(this.keys());
3231 | other.keys().forEach(function(k) {
3232 | result.add(k);
3233 | });
3234 | return result;
3235 | };
3236 |
3237 | },{"../util":33}],31:[function(require,module,exports){
3238 | /* jshint -W079 */
3239 | var Set = require("./data/Set");
3240 |
3241 | exports.all = function() {
3242 | return function() { return true; };
3243 | };
3244 |
3245 | exports.nodesFromList = function(nodes) {
3246 | var set = new Set(nodes);
3247 | return function(u) {
3248 | return set.has(u);
3249 | };
3250 | };
3251 |
3252 | },{"./data/Set":30}],32:[function(require,module,exports){
3253 | var Graph = require("./Graph"),
3254 | Digraph = require("./Digraph");
3255 |
3256 | // Side-effect based changes are lousy, but node doesn't seem to resolve the
3257 | // requires cycle.
3258 |
3259 | /**
3260 | * Returns a new directed graph using the nodes and edges from this graph. The
3261 | * new graph will have the same nodes, but will have twice the number of edges:
3262 | * each edge is split into two edges with opposite directions. Edge ids,
3263 | * consequently, are not preserved by this transformation.
3264 | */
3265 | Graph.prototype.toDigraph =
3266 | Graph.prototype.asDirected = function() {
3267 | var g = new Digraph();
3268 | this.eachNode(function(u, value) { g.addNode(u, value); });
3269 | this.eachEdge(function(e, u, v, value) {
3270 | g.addEdge(null, u, v, value);
3271 | g.addEdge(null, v, u, value);
3272 | });
3273 | return g;
3274 | };
3275 |
3276 | /**
3277 | * Returns a new undirected graph using the nodes and edges from this graph.
3278 | * The new graph will have the same nodes, but the edges will be made
3279 | * undirected. Edge ids are preserved in this transformation.
3280 | */
3281 | Digraph.prototype.toGraph =
3282 | Digraph.prototype.asUndirected = function() {
3283 | var g = new Graph();
3284 | this.eachNode(function(u, value) { g.addNode(u, value); });
3285 | this.eachEdge(function(e, u, v, value) {
3286 | g.addEdge(e, u, v, value);
3287 | });
3288 | return g;
3289 | };
3290 |
3291 | },{"./Digraph":16,"./Graph":17}],33:[function(require,module,exports){
3292 | // Returns an array of all values for properties of **o**.
3293 | exports.values = function(o) {
3294 | return Object.keys(o).map(function(k) { return o[k]; });
3295 | };
3296 |
3297 | },{}],34:[function(require,module,exports){
3298 | module.exports = '0.5.7';
3299 |
3300 | },{}]},{},[1])
3301 | ;
3302 |
--------------------------------------------------------------------------------