-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
395 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
})(); |