aboutsummaryrefslogtreecommitdiffstats
path: root/html
diff options
context:
space:
mode:
Diffstat (limited to 'html')
-rw-r--r--html/js/git-deps-layout.coffee151
1 files changed, 129 insertions, 22 deletions
diff --git a/html/js/git-deps-layout.coffee b/html/js/git-deps-layout.coffee
index b3ea092..f3f66af 100644
--- a/html/js/git-deps-layout.coffee
+++ b/html/js/git-deps-layout.coffee
@@ -1,3 +1,10 @@
+DEBUG = true
+
+MIN_ROW_GAP = 80
+MIN_NODE_X_GAP = 50
+MAX_NODE_X_GAP = 200
+MAX_NODE_Y_GAP = 100
+
dagre = require "dagre"
gdd = require "./git-deps-data.coffee"
@@ -10,6 +17,10 @@ constraints = []
# within that row.
row_groups = {}
+debug = (msg) ->
+ if DEBUG
+ console.log msg
+
dagre_layout = ->
g = new dagre.graphlib.Graph()
exports.graph = g
@@ -52,21 +63,90 @@ dagre_row_groups = ->
build_constraints = ->
row_groups = dagre_row_groups()
+ debug "build_constraints"
+ for y, row_nodes of row_groups
+ debug y
+ debug row_nodes
constraints.length = 0 # FIXME: only rebuild constraints which changed
# We want alignment constraints between all nodes which dagre
# assigned the same y value.
+ row_alignment_constraints(row_groups)
+
+ # We need separation constraints ensuring that the left-to-right
+ # ordering within each row assigned by dagre is preserved.
for y, row_nodes of row_groups
# No point having an alignment group with only one node in.
- if row_nodes.length > 1
- constraints.push build_alignment_constraint(row_nodes)
+ continue if row_nodes.length <= 1
+
+ # Multiple constraints per row.
+ row_node_ordering_constraints(row_nodes)
- # We also need separation constraints ensuring that the
+ # Finally we need separation constraints ensuring that the
# top-to-bottom ordering assigned by dagre is preserved. Since
# all nodes within a single row are already constrained to the
# same y coordinate from above, it should be enough to only
# have separation between a single node in adjacent rows.
+ row_ordering_constraints(row_groups)
+
+debug_constraints = () ->
+ for c in constraints
+ debug c
+
+row_alignment_constraints = (row_groups) ->
+ row_alignment_constraint(row_nodes) \
+ for y, row_nodes of row_groups when row_nodes.length > 1
+
+row_alignment_constraint = (row_nodes) ->
+ debug 'row_alignment_constraint'
+ # A standard alignment constraint (one per row) is too strict
+ # because it doesn't give cola enough "wiggle room":
+ #
+ # constraint =
+ # axis: "y"
+ # type: "alignment"
+ # offsets: []
+ #
+ # for node in row_nodes
+ # constraint.offsets.push
+ # node: gdd.node_index[node.sha1],
+ # offset: 0
+ #
+ # constraints.push constraint
+ #
+ # So instead we use vertical min/max separation constraints:
+ i = 0
+ while i < row_nodes.length - 1
+ left = row_nodes[i]
+ right = row_nodes[i+1]
+ mm = max_unordered_separation_constraints \
+ 'y', MAX_NODE_Y_GAP,
+ gdd.node_index[left.sha1],
+ gdd.node_index[right.sha1]
+ exports.constraints = constraints = constraints.concat mm
+ i++
+ debug_constraints()
+ return
+
+row_node_ordering_constraints = (row_nodes) ->
+ debug 'row_node_ordering_constraints'
+ i = 0
+ while i < row_nodes.length - 1
+ left = row_nodes[i]
+ right = row_nodes[i+1]
+ mm = min_max_ordered_separation_constraints \
+ 'x', MIN_NODE_X_GAP, MAX_NODE_X_GAP,
+ gdd.node_index[left.sha1],
+ gdd.node_index[right.sha1]
+ exports.constraints = constraints = constraints.concat mm
+
+ i++
+ debug_constraints()
+ return
+
+row_ordering_constraints = (row_groups) ->
+ debug 'row_ordering_constraints'
row_y_coords = Object.keys(row_groups).sort()
i = 0
@@ -75,27 +155,54 @@ build_constraints = ->
lower_y = row_y_coords[i + 1]
upper_node = row_groups[upper_y][0]
lower_node = row_groups[lower_y][0]
- constraints.push
- gap: 30
- axis: "y"
- left: gdd.node_index[upper_node.sha1]
- right: gdd.node_index[lower_node.sha1]
+ constraints.push \
+ min_separation_constraint \
+ MIN_ROW_GAP, 'y',
+ gdd.node_index[upper_node.sha1],
+ gdd.node_index[lower_node.sha1]
i++
-
-build_alignment_constraint = (row_nodes) ->
- constraint =
- axis: "y"
- type: "alignment"
- offsets: []
-
- for i of row_nodes
- node = row_nodes[i]
- constraint.offsets.push
- node: gdd.node_index[node.sha1]
- offset: 0
-
- return constraint
+ debug_constraints()
+ return
+
+##################################################################
+# helpers
+
+# Uses approach explained here:
+# https://github.com/tgdwyer/WebCola/issues/62#issuecomment-69571870
+min_max_ordered_separation_constraints = (axis, min, max, left, right) ->
+ return [
+ min_separation_constraint(axis, min, left, right),
+ max_separation_constraint(axis, max, left, right)
+ ]
+
+# https://github.com/tgdwyer/WebCola/issues/66
+max_unordered_separation_constraints = (axis, max, left, right) ->
+ return [
+ max_separation_constraint(axis, max, left, right),
+ max_separation_constraint(axis, max, right, left)
+ ]
+
+min_separation_constraint = (axis, gap, left, right) ->
+ {} =
+ axis: axis
+ gap: gap
+ left: left
+ right: right
+
+# We use a negative gap and reverse the inequality, in order to
+# achieve a maximum rather than minimum separation gap. However this
+# does not prevent the nodes from overlapping or even swapping order.
+# For that you also need a min_separation_constraint, but it's more
+# convenient to use min_max_ordered_separation_constraints. See
+# https://github.com/tgdwyer/WebCola/issues/62#issuecomment-69571870
+# for more details.
+max_separation_constraint = (axis, gap, left, right) ->
+ {} =
+ axis: axis
+ gap: -gap
+ left: right
+ right: left
node = (sha1) ->
exports.graph.node sha1