gitlab-org--gitlab-foss/app/assets/javascripts/pipelines/components/dag/interactions.js

154 lines
4.6 KiB
JavaScript

import * as d3 from 'd3';
import { LINK_SELECTOR, NODE_SELECTOR, IS_HIGHLIGHTED } from './constants';
export const highlightIn = 1;
export const highlightOut = 0.2;
const getCurrent = (idx, collection) => d3.select(collection[idx]);
const getLiveLinks = () => d3.selectAll(`.${LINK_SELECTOR}.${IS_HIGHLIGHTED}`);
const getOtherLinks = () => d3.selectAll(`.${LINK_SELECTOR}:not(.${IS_HIGHLIGHTED})`);
const getNodesNotLive = () => d3.selectAll(`.${NODE_SELECTOR}:not(.${IS_HIGHLIGHTED})`);
export const getLiveLinksAsDict = () => {
return Object.fromEntries(
getLiveLinks()
.data()
.map((d) => [d.uid, d]),
);
};
export const currentIsLive = (idx, collection) =>
getCurrent(idx, collection).classed(IS_HIGHLIGHTED);
const backgroundLinks = (selection) => selection.style('stroke-opacity', highlightOut);
const backgroundNodes = (selection) => selection.attr('stroke', '#f2f2f2');
const foregroundLinks = (selection) => selection.style('stroke-opacity', highlightIn);
const foregroundNodes = (selection) => selection.attr('stroke', (d) => d.color);
const renewLinks = (selection, baseOpacity) => selection.style('stroke-opacity', baseOpacity);
const renewNodes = (selection) => selection.attr('stroke', (d) => d.color);
export const getAllLinkAncestors = (node) => {
if (node.targetLinks) {
return node.targetLinks.flatMap((n) => {
return [n, ...getAllLinkAncestors(n.source)];
});
}
return [];
};
const getAllNodeAncestors = (node) => {
let allNodes = [];
if (node.targetLinks) {
allNodes = node.targetLinks.flatMap((n) => {
return getAllNodeAncestors(n.source);
});
}
return [...allNodes, node.uid];
};
export const highlightLinks = (d, idx, collection) => {
const currentLink = getCurrent(idx, collection);
const currentSourceNode = d3.select(`#${d.source.uid}`);
const currentTargetNode = d3.select(`#${d.target.uid}`);
/* Higlight selected link, de-emphasize others */
backgroundLinks(getOtherLinks());
foregroundLinks(currentLink);
/* Do the same to related nodes */
backgroundNodes(getNodesNotLive());
foregroundNodes(currentSourceNode);
foregroundNodes(currentTargetNode);
};
const highlightPath = (parentLinks, parentNodes) => {
/* de-emphasize everything else */
backgroundLinks(getOtherLinks());
backgroundNodes(getNodesNotLive());
/* highlight correct links */
parentLinks.forEach(({ uid }) => {
foregroundLinks(d3.select(`#${uid}`)).classed(IS_HIGHLIGHTED, true);
});
/* highlight correct nodes */
parentNodes.forEach((id) => {
foregroundNodes(d3.select(`#${id}`)).classed(IS_HIGHLIGHTED, true);
});
};
const restoreNodes = () => {
/*
When paths are unclicked, they can take down nodes that
are still in use for other paths. This checks the live paths and
rehighlights their nodes.
*/
getLiveLinks().each((d) => {
foregroundNodes(d3.select(`#${d.source.uid}`)).classed(IS_HIGHLIGHTED, true);
foregroundNodes(d3.select(`#${d.target.uid}`)).classed(IS_HIGHLIGHTED, true);
});
};
const restorePath = (parentLinks, parentNodes, baseOpacity) => {
parentLinks.forEach(({ uid }) => {
renewLinks(d3.select(`#${uid}`), baseOpacity).classed(IS_HIGHLIGHTED, false);
});
parentNodes.forEach((id) => {
d3.select(`#${id}`).classed(IS_HIGHLIGHTED, false);
});
if (d3.selectAll(`.${IS_HIGHLIGHTED}`).empty()) {
renewLinks(getOtherLinks(), baseOpacity);
renewNodes(getNodesNotLive());
return;
}
backgroundLinks(getOtherLinks());
backgroundNodes(getNodesNotLive());
restoreNodes();
};
export const restoreLinks = (baseOpacity) => {
/*
if there exist live links, reset to highlight out / pale
otherwise, reset to base
*/
if (d3.selectAll(`.${IS_HIGHLIGHTED}`).empty()) {
renewLinks(d3.selectAll(`.${LINK_SELECTOR}`), baseOpacity);
renewNodes(d3.selectAll(`.${NODE_SELECTOR}`));
return;
}
backgroundLinks(getOtherLinks());
backgroundNodes(getNodesNotLive());
};
export const toggleLinkHighlight = (baseOpacity, d, idx, collection) => {
if (currentIsLive(idx, collection)) {
restorePath([d], [d.source.uid, d.target.uid], baseOpacity);
restoreNodes();
return;
}
highlightPath([d], [d.source.uid, d.target.uid]);
};
export const togglePathHighlights = (baseOpacity, d, idx, collection) => {
const parentLinks = getAllLinkAncestors(d);
const parentNodes = getAllNodeAncestors(d);
const currentNode = getCurrent(idx, collection);
/* if this node is already live, make it unlive and reset its path */
if (currentIsLive(idx, collection)) {
currentNode.classed(IS_HIGHLIGHTED, false);
restorePath(parentLinks, parentNodes, baseOpacity);
return;
}
highlightPath(parentLinks, parentNodes);
};