summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/pipelines/components/graph_shared/drawing_utils.js
blob: 65c215be79459e23265927cb13691f9fd704236f (plain)
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
import * as d3 from 'd3';

export const createUniqueLinkId = (stageName, jobName) => `${stageName}-${jobName}`;

/**
 * This function expects its first argument data structure
 * to be the same shaped as the one generated by `parseData`,
 * which contains nodes and links. For each link,
 * we find the nodes in the graph, calculate their coordinates and
 * trace the lines that represent the needs of each job.
 * @param {Object} nodeDict - Resulting object of `parseData` with nodes and links
 * @param {String} containerID - Id for the svg the links will be draw in
 * @returns {Array} Links that contain all the information about them
 */

export const generateLinksData = ({ links }, containerID, modifier = '') => {
  const containerEl = document.getElementById(containerID);
  return links.map((link) => {
    const path = d3.path();

    const sourceId = link.source;
    const targetId = link.target;

    const modifiedSourceId = `${sourceId}${modifier}`;
    const modifiedTargetId = `${targetId}${modifier}`;

    const sourceNodeEl = document.getElementById(modifiedSourceId);
    const targetNodeEl = document.getElementById(modifiedTargetId);

    const sourceNodeCoordinates = sourceNodeEl.getBoundingClientRect();
    const targetNodeCoordinates = targetNodeEl.getBoundingClientRect();
    const containerCoordinates = containerEl.getBoundingClientRect();

    // Because we add the svg dynamically and calculate the coordinates
    // with plain JS and not D3, we need to account for the fact that
    // the coordinates we are getting are absolutes, but we want to draw
    // relative to the svg container, which starts at `containerCoordinates(x,y)`
    // so we substract these from the total. We also need to remove the padding
    // from the total to make sure it's aligned properly. We then make the line
    // positioned in the center of the job node by adding half the height
    // of the job pill.
    const paddingLeft = parseFloat(
      window.getComputedStyle(containerEl, null).getPropertyValue('padding-left'),
    );
    const paddingTop = parseFloat(
      window.getComputedStyle(containerEl, null).getPropertyValue('padding-top'),
    );

    const sourceNodeX = sourceNodeCoordinates.right - containerCoordinates.x - paddingLeft;
    const sourceNodeY =
      sourceNodeCoordinates.top -
      containerCoordinates.y -
      paddingTop +
      sourceNodeCoordinates.height / 2;
    const targetNodeX = targetNodeCoordinates.x - containerCoordinates.x - paddingLeft;
    const targetNodeY =
      targetNodeCoordinates.y -
      containerCoordinates.y -
      paddingTop +
      sourceNodeCoordinates.height / 2;

    // Start point
    path.moveTo(sourceNodeX, sourceNodeY);

    // Make cross-stages lines a straight line all the way
    // until we can safely draw the bezier to look nice.
    // The adjustment number here is a magic number to make things
    // look nice and should change if the padding changes. This goes well
    // with gl-px-6. gl-px-8 is more like 100.
    const straightLineDestinationX = targetNodeX - 60;
    const controlPointX = straightLineDestinationX + (targetNodeX - straightLineDestinationX) / 2;

    if (straightLineDestinationX > 0) {
      path.lineTo(straightLineDestinationX, sourceNodeY);
    }

    // Add bezier curve. The first 4 coordinates are the 2 control
    // points to create the curve, and the last one is the end point (x, y).
    // We want our control points to be in the middle of the line
    path.bezierCurveTo(
      controlPointX,
      sourceNodeY,
      controlPointX,
      targetNodeY,
      targetNodeX,
      targetNodeY,
    );

    return {
      ...link,
      source: sourceId,
      target: targetId,
      ref: createUniqueLinkId(sourceId, targetId),
      path: path.toString(),
    };
  });
};