Skip to content

Commit

Permalink
feat(Layout): add GoogleLayout
Browse files Browse the repository at this point in the history
  • Loading branch information
daybrush committed Sep 22, 2017
1 parent a7296b3 commit 4a31b7a
Show file tree
Hide file tree
Showing 2 changed files with 395 additions and 0 deletions.
152 changes: 152 additions & 0 deletions src/layouts/GoogleLayout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import dijkstra from "./lib/dijkstra";

const _style = {
vertical: ["top", "height", "left", "width"],
horizontal: ["left", "width", "top", "height"],
};

class GoogleLayout {
constructor(options) {
this.startPoint = 0;
this.endPoint = 0;
}
append(items) {
this._layout(items, 0, items.length, true);
}
prepend(items) {
this._layout(items, 0, items.length, false);
}
_layout(items, _i, _j, isAppend) {
const graph = _start => {
const results = {};
const start = +_start.replace(/[^0-9]/g, "");
const length = _j + 1;

for (let i = start + 1; i < length; ++i) {
if (i - start > 8) {
break;
}
let cost = this._getCost(items, start, i);

if (cost < 0 && i === length - 1) {
cost = 0;
}
if (cost !== null) {
results[`node${i}`] = Math.pow(cost, 2);
}
}

return results;
};
const path = dijkstra.find_path(graph, `node${_i}`, `node${_j}`);

this._setStyle(items, path, isAppend);
}
layout() {
this.startPoint = 0;
this.endPoint = 0;
const items = this.items;
const length = items.length;
let j = 0;

for (let i = 0; i < length; i = j) {
const item = items[i];
const groupKey = item.groupKey;

for (j = i; j < length; ++j) {
if (items[j].groupKey !== groupKey) {
break;
}
}
this._layout(items, i, j, true);
}
}
setViewport(width, height) {
this.width = width;
this.height = height;
}
getMinPoint() {
return this.startPoint;
}
getMaxPoint() {
return this.endPoint;
}
getStartPoint() {
return this.startPoint;
}
getEndPoint() {
return this.endPoint;
}
_getWidth(items) {
const w = items.reduce((sum, item) => sum + item.size.height / item.size.width, 0);
const margin = this.options.margin;

return (this.height - (margin ? margin * (items.length - 1) : 0)) / w;
}
_getHeight(items) {
const h = items.reduce((sum, item) => sum + item.size.width / item.size.height, 0);
const margin = this.options.margin;

return (this.width - (margin ? margin * (items.length - 1) : 0)) / h;
}
_getSize(items, _size1, _size2) {
const size = items.reduce((sum, item) => sum + item.size[_size2] / item.size[_size1], 0);
const margin = this.options.margin;

return (this[_size2] - (margin ? margin * (items.length - 1) : 0)) / size;
}
_getCost(items, i, j) {
const direction = this.options.direction;
const size = direction === "horizontal" ?
this._getWidth(items.slice(i, j)) : this._getHeight(items.slice(i, j));
const min = this.options.minSize || 0;
const max = ("maxSize" in this.options) ? this.options.maxSize : Infinity;

if (size < min) {
return Math.pow(size - min, 2);
} else if (size > max) {
return Math.pow(size - max, 2);
}
return 0;
}
_setStyle(items, path, isAppend) {
const direction = _style[this.options.direction] ? this.options.direction : "vertical";
const style = _style[direction];
// if direction is vertical, [pos1, size1, pos2, size2] is [top, height, left, width]
// if direction is horizontal, [pos1, size1, pos2, size2] is [left, width, top, height]
const [_pos1, _size1, _pos2, _size2] = style;
const length = path.length;
const margin = this.options.margin || 0;
const _point = isAppend ? "endPoint" : "startPoint";
const mark = (isAppend ? 1 : -1);

for (let i = 0; i < length - 1; ++i) {
const path1 = parseInt(path[i].replace("node", ""), 10);
const path2 = parseInt(path[i + 1].replace("node", ""), 10);
const _items = items.slice(path1, path2);
const _length = _items.length;
const size1 = this._getSize(_items, _size1, _size2);
// The same startPoint and endPoint means that the layout is initialized.
const pos1Margin = this.startPoint === this.endPoint ? 0 : margin;
// The position of the item being prepended is subtracted from size.
const pos1 = mark * pos1Margin + (isAppend ? 0 : -size1) + this[_point];

for (let j = 0; j < _length; ++j) {
const item = _items[j];
const size2 = item.size[_size2] / item.size[_size1] * size1;
// First item's margin is zero.
// Next item have margin.
const pos2 = j === 0 ? 0 :
(_items[j - 1].position[_pos2] + _items[j - 1].size[_size2] + margin);

item.position[_pos1] = pos1;
item.position[_pos2] = pos2;
item.size[_size1] = size1;
item.size[_size2] = size2;
}
this[_point] += mark * (pos1Margin + size1);
}
}
}

export default GoogleLayout;
243 changes: 243 additions & 0 deletions src/layouts/lib/dijkstra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/******************************************************************************
* Created 2008-08-19.
*
* Dijkstra path-finding functions. Adapted from the Dijkstar Python project.
*
* Copyright (C) 2008
* Wyatt Baldwin <self@wyattbaldwin.com>
* All rights reserved
*
* Licensed under the MIT license.
*
* http://www.opensource.org/licenses/mit-license.php
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*****************************************************************************/
(function(){
var dijkstra = {
single_source_shortest_paths: function(graph, s, d) {
// Predecessor map for each node that has been encountered.
// node ID => predecessor node ID
var predecessors = {};

// Costs of shortest paths from s to all nodes encountered.
// node ID => cost
var costs = {};
costs[s] = 0;

// Costs of shortest paths from s to all nodes encountered; differs from
// `costs` in that it provides easy access to the node that currently has
// the known shortest path from s.
// XXX: Do we actually need both `costs` and `open`?
var open = new BinaryHeap(function (x) { return x.cost; });
open.push({value: s, cost: 0});

var closest,
u,
cost_of_s_to_u,
adjacent_nodes,
cost_of_e,
cost_of_s_to_u_plus_cost_of_e,
cost_of_s_to_v,
first_visit;
while (open.size()) {
// In the nodes remaining in graph that have a known cost from s,
// find the node, u, that currently has the shortest path from s.
closest = open.pop();
u = closest.value;
cost_of_s_to_u = closest.cost;

// Get nodes adjacent to u...
adjacent_nodes = graph(u) || {};

// ...and explore the edges that connect u to those nodes, updating
// the cost of the shortest paths to any or all of those nodes as
// necessary. v is the node across the current edge from u.
for (var v in adjacent_nodes) {
// Get the cost of the edge running from u to v.
cost_of_e = adjacent_nodes[v];

// Cost of s to u plus the cost of u to v across e--this is *a*
// cost from s to v that may or may not be less than the current
// known cost to v.
cost_of_s_to_u_plus_cost_of_e = cost_of_s_to_u + cost_of_e;

// If we haven't visited v yet OR if the current known cost from s to
// v is greater than the new cost we just found (cost of s to u plus
// cost of u to v across e), update v's cost in the cost list and
// update v's predecessor in the predecessor list (it's now u).
cost_of_s_to_v = costs[v];
first_visit = (typeof costs[v] === 'undefined');
if (first_visit || cost_of_s_to_v > cost_of_s_to_u_plus_cost_of_e) {
costs[v] = cost_of_s_to_u_plus_cost_of_e;
open.push({value: v, cost: cost_of_s_to_u_plus_cost_of_e});
predecessors[v] = u;
}
}
}

if (typeof costs[d] === 'undefined') {
var msg = ['Could not find a path from ', s, ' to ', d, '.'].join('');
throw new Error(msg);
}

return predecessors;
},

extract_shortest_path_from_predecessor_list: function(predecessors, d) {
var nodes = [];
var u = d;
var predecessor;
while (u) {
nodes.push(u);
predecessor = predecessors[u];
u = predecessors[u];
}
nodes.reverse();
return nodes;
},

find_path: function(graph, s, d) {
var predecessors = dijkstra.single_source_shortest_paths(graph, s, d);
return dijkstra.extract_shortest_path_from_predecessor_list(
predecessors, d);
}

};

function BinaryHeap(scoreFunction){
this.content = [];
this.scoreFunction = scoreFunction;
}

BinaryHeap.prototype = {
push: function(element) {
// Add the new element to the end of the array.
this.content.push(element);
// Allow it to bubble up.
this.bubbleUp(this.content.length - 1);
},

pop: function() {
// Store the first element so we can return it later.
var result = this.content[0];
// Get the element at the end of the array.
var end = this.content.pop();
// If there are any elements left, put the end element at the
// start, and let it sink down.
if (this.content.length > 0) {
this.content[0] = end;
this.sinkDown(0);
}
return result;
},

remove: function(node) {
var len = this.content.length;
// To remove a value, we must search through the array to find
// it.
for (var i = 0; i < len; i++) {
if (this.content[i] === node) {
// When it is found, the process seen in 'pop' is repeated
// to fill up the hole.
var end = this.content.pop();
if (i !== len - 1) {
this.content[i] = end;
if (this.scoreFunction(end) < this.scoreFunction(node)){
this.bubbleUp(i);
}else{
this.sinkDown(i);
}
}
return;
}
}
throw new Error('Node not found.');
},

size: function() {
return this.content.length;
},

bubbleUp: function(n) {
// Fetch the element that has to be moved.
var element = this.content[n];
// When at 0, an element can not go up any further.
while (n > 0) {
// Compute the parent element's index, and fetch it.
var parentN = Math.floor((n + 1) / 2) - 1,
parent = this.content[parentN];
// Swap the elements if the parent is greater.
if (this.scoreFunction(element) < this.scoreFunction(parent)) {
this.content[parentN] = element;
this.content[n] = parent;
// Update 'n' to continue at the new position.
n = parentN;
}
// Found a parent that is less, no need to move it further.
else {
break;
}
}
},

sinkDown: function(n) {
// Look up the target element and its score.
var length = this.content.length,
element = this.content[n],
elemScore = this.scoreFunction(element);

while(true) {
// Compute the indices of the child elements.
var child2N = (n + 1) * 2, child1N = child2N - 1;
// This is used to store the new position of the element,
// if any.
var swap = null;
// If the first child exists (is inside the array)...
if (child1N < length) {
// Look it up and compute its score.
var child1 = this.content[child1N],
child1Score = this.scoreFunction(child1);
// If the score is less than our element's, we need to swap.
if (child1Score < elemScore){
swap = child1N;
}
}
// Do the same checks for the other child.
if (child2N < length) {
var child2 = this.content[child2N],
child2Score = this.scoreFunction(child2);
if (child2Score < (swap == null ? elemScore : child1Score)){
swap = child2N;
}
}

// If the element needs to be moved, swap it, and continue.
if (swap !== null) {
this.content[n] = this.content[swap];
this.content[swap] = element;
n = swap;
}
// Otherwise, we are done.
else {
break;
}
}
}
};

/**
* Browserify 지원을 위한 모듈화 코드
*/
if(typeof module !== 'undefined' && module.exports) {
module.exports = dijkstra;
}else{
window.dijkstra = dijkstra;
}
})();

0 comments on commit 4a31b7a

Please sign in to comment.