aboutsummaryrefslogtreecommitdiffstats
path: root/html/js/git-deps-layout.coffee
blob: 09f435c09b881be826797e7cbc0d0da495483235 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
dagre = require "dagre"

gdd = require "./git-deps-data.coffee"

# The list of constraints to feed into WebCola.
constraints = []

# Group nodes by row, as assigned by the y coordinates returned from
# dagre's layout().  This will map a y coordinate onto all nodes
# within that row.
row_groups = {}

# Expose a container for externally accessible objects.  We can't
# directly expose the objects themselves because the references
# change each time they're constructed.  However we don't need this
# trick for the constraints arrays since we can easily empty that by
# setting length to 0.
externs = {}

dagre_layout = ->
    g = new dagre.graphlib.Graph()
    externs.graph = g

    # Set an object for the graph label
    g.setGraph {}

    # Default to assigning a new object as a label for each new edge.
    g.setDefaultEdgeLabel -> {}

    for node in gdd.nodes
        g.setNode node.sha1,
            label: node.name
            width: node.rect_width or 70
            height: node.rect_height or 30

    for parent_sha1, children of gdd.deps
        for child_sha1, bool of children
            g.setEdge parent_sha1, child_sha1

    dagre.layout g
    return g

dagre_row_groups = ->
    g = dagre_layout()
    row_groups = {}
    externs.row_groups = row_groups
    for sha1 in g.nodes
        x = g.node(sha1).x
        y = g.node(sha1).y
        row_groups[y] = []  unless y of row_groups
        row_groups[y].push
            sha1: sha1
            x: x
    return row_groups

build_constraints = ->
    row_groups = dagre_row_groups()

    constraints.length = 0 # FIXME: only rebuild constraints which changed

    # We want alignment constraints between all nodes which dagre
    # assigned the same y value.
    for y of row_groups
        row_nodes = row_groups[y]

        # No point having an alignment group with only one node in.
        if row_nodes.length > 1
            constraints.push build_alignment_constraint(row_nodes)

    # We also 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_y_coords = Object.keys(row_groups).sort()

    i = 0
    while i < row_y_coords.length - 1
        upper_y = row_y_coords[i]
        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]

        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

node = (sha1) ->
    externs.graph.node sha1

module.exports =
    # Variables
    constraints: constraints
    g: externs

    # Functions
    build_constraints: build_constraints
    node: node