Last active
June 18, 2025 23:18
-
-
Save dbose/b53add5d0a07648463e7f728e43a8336 to your computer and use it in GitHub Desktop.
swim-lane-in-dbt-docs
This file contains hidden or 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
const $ = require('jquery'); | |
const _ = require('underscore'); | |
const graphlib = require('graphlib'); | |
const selectorGraph = require('./selector_graph'); | |
const colorValidation = require('./validate_node_color'); | |
angular | |
.module('dbt') | |
.factory('graph', [ | |
'$state', '$window', '$q', 'selectorService', 'project', 'locationService', | |
function($state, $window, $q, selectorService, projectService, locationService) { | |
var graph_options = { | |
vertical: { | |
userPanningEnabled: false, | |
boxSelectionEnabled: false, | |
maxZoom: 1.5, | |
}, | |
horizontal: { | |
userPanningEnabled: true, | |
boxSelectionEnabled: false, | |
maxZoom: 1, | |
minZoom: 0.05, | |
} | |
} | |
// SWIM-LANES: Priority order for categories (filters and orders swim-lanes) | |
const priorityOrder = { | |
'sources': 0, | |
'validated': 10, | |
'integration': 20, | |
'staging': 30, | |
'datamart': 40, | |
'reporting': 50, | |
'access': 60 | |
}; | |
// SWIM-LANES: Extract category from node name prefix first, then path | |
function getSwimLaneCategory(node) { | |
var category = null; | |
// First try prefix-based categorization (name-based) | |
if (node.name || node.label) { | |
var nodeName = node.name || node.label || ''; | |
// Map common prefixes to categories | |
var prefixMap = { | |
'seed_': 'sources', | |
'src_': 'sources', | |
'source_': 'sources', | |
'raw_': 'sources', | |
'stg_': 'staging', | |
'staging_': 'staging', | |
'val_': 'validated', | |
'validated_': 'validated', | |
'int_': 'integration', | |
'intermediate_': 'integration', | |
'integration_': 'integration', | |
'dm_': 'datamart', | |
'mart_': 'datamart', | |
'datamart_': 'datamart', | |
'rpt_': 'reporting', | |
'report_': 'reporting', | |
'reporting_': 'reporting', | |
'acc_': 'access', | |
'access_': 'access' | |
}; | |
// Check for matching prefixes | |
for (var prefix in prefixMap) { | |
if (nodeName.toLowerCase().startsWith(prefix)) { | |
category = prefixMap[prefix]; | |
break; | |
} | |
} | |
} | |
// If prefix-based didn't work, try path-based categorization as fallback | |
if (!category && (node.original_file_path || node.path)) { | |
var path = node.original_file_path || node.path || ''; | |
var pathParts = path.split('/'); | |
if (pathParts.length >= 2 && pathParts[0] === 'models') { | |
category = pathParts[1]; | |
} else if (pathParts.length >= 1 && pathParts[0]) { | |
category = pathParts[0]; | |
} | |
} | |
// Only return categories that are in priorityOrder | |
return (category && priorityOrder.hasOwnProperty(category)) ? category : null; | |
} | |
// SWIM-LANES: Color mapping for different categories | |
function getCategoryColor(category) { | |
var colors = { | |
'sources': '#6b7280', // Gray | |
'validated': '#3b82f6', // Blue | |
'integration': '#10b981', // Emerald | |
'staging': '#f59e0b', // Amber | |
'datamart': '#8b5cf6', // Violet | |
'reporting': '#06b6d4', // Cyan | |
'access': '#64748b' // Slate | |
}; | |
return colors[category] || '#9ca3af'; // Default gray for unknown | |
} | |
function getCategoryBorderColor(category) { | |
var colors = { | |
'sources': '#4b5563', // Darker gray | |
'validated': '#1d4ed8', // Darker blue | |
'integration': '#059669', // Darker emerald | |
'staging': '#d97706', // Darker amber | |
'datamart': '#7c3aed', // Darker violet | |
'reporting': '#0891b2', // Darker cyan | |
'access': '#475569' // Darker slate | |
}; | |
return colors[category] || '#6b7280'; // Default darker gray for unknown | |
} | |
var layouts = { | |
none: { | |
name: 'null', | |
}, | |
left_right: { | |
name: 'dagre', | |
rankDir: 'LR', | |
rankSep: 200, | |
edgeSep: 30, | |
nodeSep: 50, | |
}, | |
top_down: { | |
name: 'preset', | |
positions: function(node) { | |
var primary_node_id = $state.params.unique_id; | |
if (!primary_node_id) { | |
return {x: 0, y: 0}; | |
} | |
var dag = service.graph.pristine.dag; | |
var parents = _.sortBy(selectorGraph.ancestorNodes(dag, primary_node_id, 1)); | |
var children = _.sortBy(selectorGraph.descendentNodes(dag, primary_node_id, 1)); | |
var is_parent = _.partial(_.includes, parents); | |
var is_child = _.partial(_.includes, children); | |
var parent_subgraph = dag.filterNodes(is_parent) | |
var child_subgraph = dag.filterNodes(is_child) | |
var parent_nodes = graphlib.alg.topsort(parent_subgraph); | |
var child_nodes = graphlib.alg.topsort(child_subgraph).reverse(); | |
return getNodeVertPosition(primary_node_id, parent_nodes, child_nodes, node.data('id')) | |
} | |
}, | |
} | |
var service = { | |
loading: true, | |
loaded: $q.defer(), | |
graph_element: null, | |
orientation: 'sidebar', | |
expanded: false, | |
graph: { | |
options: graph_options.vertical, | |
pristine: { | |
nodes: {}, | |
edges: {}, | |
dag: null | |
}, | |
elements: [], | |
layout: layouts.none, | |
style: [ | |
// SWIM-LANES: Swim-lane header styling (bigger labels) | |
{ | |
selector: '.swimlane-header', | |
style: { | |
'background-color': '#374151', | |
'border-color': '#6b7280', | |
'border-width': 2, | |
'font-size': '18px', // Bigger font | |
'font-weight': 'bold', | |
'color': '#ffffff', | |
'shape': 'roundrectangle', | |
'width': 'label', | |
'height': 'label', | |
'padding': '12px', // More padding for bigger labels | |
'content': 'data(label)', | |
'text-valign': 'center', | |
'text-halign': 'center', | |
'selectable': false, | |
'grabbable': false, | |
'z-index': 10 | |
} | |
}, | |
// SWIM-LANES: Vertical boundary lines (dashed effect using small rectangles) | |
{ | |
selector: '.swimlane-boundary', | |
style: { | |
'width': 3, | |
'height': 20, // Height of each dash | |
'background-color': '#94a3b8', | |
'background-opacity': 0.7, | |
'shape': 'rectangle', | |
'border-width': 0, | |
'selectable': false, | |
'grabbable': false, | |
'z-index': -1 | |
} | |
}, | |
{ | |
selector: 'edge.vertical', | |
style: { | |
'curve-style': 'unbundled-bezier', | |
'target-arrow-shape': 'triangle-backcurve', | |
'target-arrow-color': '#027599', | |
'arrow-scale': 1.5, | |
'line-color': '#027599', | |
'width': 3, | |
'target-distance-from-node': '5px', | |
'source-endpoint': '0% 50%', | |
'target-endpoint': '0deg', | |
} | |
}, | |
{ | |
selector: 'edge.horizontal', | |
style: { | |
'curve-style': 'unbundled-bezier', | |
'target-arrow-shape': 'triangle-backcurve', | |
'target-arrow-color': '#006f8a', | |
'arrow-scale': 1.5, | |
'target-distance-from-node': '10px', | |
'source-distance-from-node': '5px', | |
'line-color': '#006f8a', | |
'width': 3, | |
'source-endpoint': '50% 0%', | |
'target-endpoint': '270deg' | |
} | |
}, | |
{ | |
selector: 'edge[selected=1]', | |
style: { | |
'line-color': '#bd6bb6', | |
'target-arrow-color': '#bd6bb6', | |
'z-index': 1, // draw on top of non-selected nodes | |
} | |
}, | |
{ | |
selector: 'node[display="none"]', | |
style: { | |
display: 'none' | |
} | |
}, | |
{ | |
selector: 'node.vertical', | |
style: { | |
'text-margin-x': '5px', | |
'background-color': '#0094b3', | |
'border-color': '#0094b3', | |
'font-size': '16px', | |
'shape': 'ellipse', | |
'color': '#fff', | |
'width': '5px', | |
'height': '5px', | |
'padding': '5px', | |
'content': 'data(label)', | |
'font-weight': 300, | |
'text-valign': 'center', | |
'text-halign': 'right', | |
} | |
}, | |
{ | |
selector: 'node.horizontal', | |
style: { | |
// SWIM-LANES: Enhanced with category colors (with fallback for non-priority categories) | |
'background-color': function(ele) { | |
var category = getSwimLaneCategory(ele.data()); | |
return category ? getCategoryColor(category) : '#0094b3'; // Default blue for non-priority categories | |
}, | |
'border-color': function(ele) { | |
var category = getSwimLaneCategory(ele.data()); | |
return category ? getCategoryBorderColor(category) : '#0094b3'; // Default blue for non-priority categories | |
}, | |
'border-width': 3, | |
'font-size': '24px', | |
'shape': 'roundrectangle', | |
'color': '#fff', | |
'width': 'label', | |
'height': 'label', | |
'padding': '12px', | |
'content': 'data(label)', | |
'font-weight': 300, | |
'font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif', | |
'text-valign': 'center', | |
'text-halign': 'center', | |
'ghost': 'yes', | |
'ghost-offset-x': '2px', | |
'ghost-offset-y': '4px', | |
'ghost-opacity': 0.5, | |
'text-outline-color': '#000', | |
'text-outline-width': '1px', | |
'text-outline-opacity': 0.2 | |
} | |
}, | |
{ | |
selector: 'node[resource_type="source"]', | |
style: { | |
'background-color': '#5fb825', | |
'border-color': '#5fb825', | |
} | |
}, | |
{ | |
selector: 'node[resource_type="exposure"]', | |
style: { | |
'background-color': '#ff694b', | |
'border-color': '#ff694b', | |
} | |
}, | |
{ | |
selector: 'node[resource_type="metric"]', | |
style: { | |
'background-color': '#ff5688', | |
'border-color': '#ff5688', | |
} | |
}, | |
{ | |
selector: 'node[resource_type="semantic_model"]', | |
style: { | |
'background-color': '#ffa8c2', | |
'border-color': '#ffa8c2', | |
} | |
}, | |
{ | |
selector: 'node[resource_type="saved_query"]', | |
style: { | |
'background-color': '#ff7f50', | |
'border-color': '#ff7f50', | |
} | |
}, | |
{ | |
selector: 'node[language="python"]', | |
style: { | |
'background-color': '#6a5acd', | |
'border-color': '#6a5acd', | |
} | |
}, | |
{ | |
selector: 'node[node_color]', | |
style: { | |
'background-color': 'data(node_color)', | |
'border-color': 'data(node_color)', | |
} | |
}, | |
{ | |
selector: 'node[selected=1]', | |
style: { | |
'background-color': '#bd6bb6', | |
'border-color': '#bd6bb6', | |
} | |
}, | |
{ | |
selector: 'node.horizontal[selected=1]', | |
style: { | |
'background-color': '#88447d', | |
'border-color': '#88447d', | |
} | |
}, | |
{ | |
selector: 'node.horizontal.dirty', | |
style: { | |
'background-color': '#919599', | |
'border-color': '#919599', | |
} | |
}, | |
{ | |
selector: 'node[hidden=1]', | |
style: { | |
'background-color': '#919599', | |
'border-color': '#919599', | |
'background-opacity': 0.5, | |
} | |
}, | |
{ | |
selector: 'node[access="private"]', | |
style: { | |
'background-opacity': 0.2, | |
'border-width': 2, | |
'ghost': 'no', | |
} | |
}, | |
], | |
ready: function(e) { | |
console.log("graph ready"); | |
}, | |
} | |
} | |
service.setGraphReady = function(graph_element) { | |
service.loading = false; | |
service.loaded.resolve(); | |
service.graph_element = graph_element; | |
// SWIM-LANES: Organize nodes into swim-lanes after graph is ready | |
if (graph_element && service.orientation === 'fullscreen') { | |
setTimeout(function() { | |
service.organizeIntoSwimLanes(); | |
}, 100); // Small delay to ensure layout is complete | |
} | |
} | |
// SWIM-LANES: Function to organize nodes into vertical swim-lanes (like JIRA columns) | |
service.organizeIntoSwimLanes = function() { | |
if (!service.graph_element) return; | |
var cy = service.graph_element; | |
var allNodes = cy.nodes(); | |
var regularNodes = allNodes.not('.swimlane-header'); | |
// Remove any existing swim-lane headers and boundaries | |
cy.remove('.swimlane-header'); | |
cy.remove('.swimlane-boundary'); | |
// Group nodes by category | |
var nodesByCategory = {}; | |
var categories = []; | |
regularNodes.forEach(function(node) { | |
var category = getSwimLaneCategory(node.data()); | |
if (category) { | |
if (!nodesByCategory[category]) { | |
nodesByCategory[category] = []; | |
categories.push(category); | |
} | |
nodesByCategory[category].push(node); | |
} | |
}); | |
if (categories.length === 0) return; // No categorized nodes | |
// Sort categories by priority (left to right: lowest priority number = leftmost) | |
categories.sort(function(a, b) { | |
var priorityA = priorityOrder[a] !== undefined ? priorityOrder[a] : 999; | |
var priorityB = priorityOrder[b] !== undefined ? priorityOrder[b] : 999; | |
return priorityA - priorityB; // This ensures sources (0) comes before staging (30) | |
}); | |
// Get current graph bounds | |
var bounds = regularNodes.boundingBox(); | |
var laneWidth = bounds.w / categories.length; | |
var laneSpacing = 30; // Spacing between swim-lanes | |
// Position nodes into vertical swim-lanes and add headers | |
_.each(categories, function(category, index) { | |
var nodesInLane = nodesByCategory[category]; | |
var laneLeftX = bounds.x1 + (index * laneWidth); | |
var laneRightX = laneLeftX + laneWidth; | |
var laneCenterX = laneLeftX + (laneWidth / 2); | |
var usableWidth = laneWidth - (2 * laneSpacing); | |
// Sort nodes by their Y position to maintain top-bottom flow within swim-lane | |
nodesInLane.sort(function(a, b) { | |
return a.position('y') - b.position('y'); | |
}); | |
// Compact vertical distribution within the swim-lane | |
_.each(nodesInLane, function(node, nodeIndex) { | |
var currentPos = node.position(); | |
var newX; | |
if (nodesInLane.length === 1) { | |
// Single node: center it in the lane | |
newX = laneCenterX; | |
} else { | |
// Compact distribution: use minimal spacing to keep nodes close together | |
var maxNodesPerRow = Math.ceil(Math.sqrt(nodesInLane.length)); // Square-ish arrangement | |
var nodeWidth = 120; // Approximate node width + spacing | |
var totalWidth = Math.min(maxNodesPerRow * nodeWidth, usableWidth); | |
var startX = laneCenterX - (totalWidth / 2); | |
var row = Math.floor(nodeIndex / maxNodesPerRow); | |
var col = nodeIndex % maxNodesPerRow; | |
var nodesInThisRow = Math.min(maxNodesPerRow, nodesInLane.length - (row * maxNodesPerRow)); | |
if (nodesInThisRow === 1) { | |
newX = laneCenterX; // Center single nodes | |
} else { | |
var rowSpacing = totalWidth / (nodesInThisRow - 1); | |
newX = startX + (col * rowSpacing); | |
} | |
} | |
node.position({ | |
x: newX, // Compact horizontal distribution within swim-lane | |
y: currentPos.y // Keep Y position from Dagre (minimal change) | |
}); | |
}); | |
// Add swim-lane header at the top of the column | |
cy.add({ | |
group: 'nodes', | |
data: { | |
id: 'swimlane-header-' + category, | |
label: category.charAt(0).toUpperCase() + category.slice(1) | |
}, | |
position: { | |
x: laneCenterX, | |
y: bounds.y1 - 100 // Position higher to accommodate bigger labels | |
}, | |
classes: 'swimlane-header' | |
}); | |
}); | |
// Add vertical boundary lines between swim-lanes (dashed effect) | |
_.each(categories, function(category, index) { | |
if (index > 0) { // Don't add boundary before first swim-lane | |
var laneLeftX = bounds.x1 + (index * laneWidth); | |
var lineHeight = bounds.h; | |
var dashLength = 20; | |
var gapLength = 15; | |
var totalDashUnit = dashLength + gapLength; | |
var numDashes = Math.ceil(lineHeight / totalDashUnit); | |
// Create dashed line using multiple small rectangles | |
for (var dashIndex = 0; dashIndex < numDashes; dashIndex++) { | |
var dashY = bounds.y1 + (dashIndex * totalDashUnit) + (dashLength / 2); | |
cy.add({ | |
group: 'nodes', | |
data: { | |
id: 'swimlane-boundary-' + index + '-' + dashIndex, | |
label: '' | |
}, | |
position: { | |
x: laneLeftX, | |
y: dashY | |
}, | |
classes: 'swimlane-boundary' | |
}); | |
} | |
} | |
}); | |
// Make swim-lane headers and boundaries non-interactive | |
cy.nodes('.swimlane-header').ungrabify(); | |
cy.nodes('.swimlane-boundary').ungrabify(); | |
} | |
service.ready = function(cb) { | |
service.loaded.promise.then(function() { | |
cb(service); | |
}); | |
} | |
function getNodeVertPosition(primary_node, parents, children, this_node) { | |
var num_nodes = 1 + Math.max(parents.length, children.length); | |
var scale_x = 100 / num_nodes; | |
var scale_y = 100; | |
var config; | |
if (primary_node == this_node) { | |
return {x: 0, y: 0} | |
} else if (_.includes(parents, this_node)) { | |
config = {set: parents, index: _.indexOf(parents, this_node), factor: -1, type:'parent'} | |
} else if (_.includes(children, this_node)) { | |
config = {set: children, index: _.indexOf(children, this_node), factor: 1, type: 'child'} | |
} else { | |
return {x: 0, y: 0} | |
} | |
var size = config.set.length; | |
if (config.type == 'parent') { | |
var res = { | |
x: (0 + config.index) * scale_x, | |
y: -(2 * scale_y) - (size - config.index - 1) * scale_y | |
} | |
} else { | |
var res = { | |
x: (0 + config.index) * scale_x, | |
y: (2 * scale_y) + (size - config.index - 1) * scale_y | |
} | |
} | |
return res | |
} | |
function setNodes(node_ids, highlight, classes) { | |
var nodes = _.map(node_ids, function(id) { return service.graph.pristine.nodes[id] }); | |
var edges = []; | |
_.flatten(_.each(node_ids, function(id) { | |
var node_edges = service.graph.pristine.edges[id] | |
_.each(node_edges, function(edge) { | |
if (_.includes(node_ids, edge.data.target) && _.includes(node_ids, edge.data.source)) { | |
edges.push(edge); | |
} | |
}); | |
})); | |
var elements = _.compact(nodes).concat(_.compact(edges)); | |
_.each(service.graph.elements, function(el) { | |
el.data['display'] = 'none'; | |
el.data['selected'] = 0; | |
el.data['hidden'] = 0; | |
el.classes = classes; | |
}); | |
_.each(elements, function(el) { | |
el.data['display'] = 'element'; | |
el.classes = classes; | |
if (highlight && _.includes(highlight, el.data.unique_id)) { | |
el.data['selected'] = 1; | |
} | |
// models can be hidden if docs.show === false | |
if (! ( _.get(el,['data', 'docs','show'],true)) ) { | |
el.data['hidden'] = 1; | |
} | |
// models can be shown in a different color if docs.node_color is set | |
// we also validate that the color is either a valid hex color or a valid color name | |
var color_config = _.get(el, ['data', 'docs', 'node_color']) | |
if (color_config && colorValidation.isValidColor(color_config)) { | |
el.data['node_color'] = color_config; | |
} | |
}); | |
service.graph.elements = _.filter(elements, function(e) { return e.data.display == 'element'}); | |
return node_ids; | |
} | |
service.manifest = {}; | |
service.packages = []; | |
service.selected_node = null; | |
service.getCanvasHeight = function() { | |
return ($window.innerHeight * 0.8) + "px" | |
} | |
projectService.ready(function(project) { | |
service.manifest = project; | |
service.packages = _.uniq(_.map(service.manifest.nodes, 'package_name')); | |
_.each(_.filter(service.manifest.nodes, function(node) { | |
// operation needs to be a graph type so that the parent/child map can be resolved even though we won't be displaying it | |
var is_graph_type = _.includes(['model', 'seed', 'source', 'snapshot', 'analysis', 'exposure', 'metric', 'semantic_model', 'operation', 'saved_query'], node.resource_type); | |
var is_singular_test = node.resource_type === 'test' && !node.hasOwnProperty('test_metadata'); | |
var is_unit_test = node.resource_type === 'unit_test'; | |
return is_graph_type || is_singular_test || is_unit_test; | |
}), function(node) { | |
var node_obj = { | |
group: "nodes", | |
data: _.assign(node, { | |
parent: node.package_name, | |
id: node.unique_id, | |
is_group: 'false' | |
}) | |
} | |
service.graph.pristine.nodes[node.unique_id] = node_obj; | |
}); | |
_.each(service.manifest.parent_map, function(parents, child) { | |
_.each(parents, function(parent) { | |
var parent_node = service.manifest.nodes[parent]; | |
var child_node = service.manifest.nodes[child]; | |
if (!_.includes(['model', 'source', 'seed', 'snapshot', 'metric', 'semantic_model', 'saved_query'], parent_node.resource_type)) { | |
return; | |
} else if (child_node.resource_type == 'test' && child_node.hasOwnProperty('test_metadata')) { | |
return; | |
} | |
var unique_id = parent_node.unique_id + "|" + child_node.unique_id; | |
var edge = { | |
group: "edges", | |
data: { | |
source: parent_node.unique_id, | |
target: child_node.unique_id, | |
unique_id: unique_id, | |
} | |
}; | |
var edge_id = child_node.unique_id; | |
if (!service.graph.pristine.edges[edge_id]) { | |
service.graph.pristine.edges[edge_id] = []; | |
} | |
service.graph.pristine.edges[edge_id].push(edge); | |
}) | |
}); | |
var dag = new graphlib.Graph({directed: true}); | |
_.each(service.graph.pristine.nodes, function(node) { | |
dag.setNode(node.data.unique_id, node.data.name); | |
}); | |
_.each(service.graph.pristine.edges, function(edges) { | |
_.each(edges, function(edge) { | |
dag.setEdge(edge.data.source, edge.data.target); | |
}); | |
}); | |
service.graph.pristine.dag = dag; | |
service.graph.elements = _.flatten(_.values(service.graph.pristine.nodes).concat(_.values(service.graph.pristine.edges))); | |
setNodes(dag.nodes()) | |
}); | |
function updateGraphWithSelector(selected_spec, classes, should_highlight) { | |
var dag = service.graph.pristine.dag; | |
if (!dag) return; | |
// good: "+source:quickbooks.invoices+" | |
var pristine = service.graph.pristine.nodes; | |
var selected = selectorService.selectNodes(dag, pristine, selected_spec); | |
var highlight_nodes = should_highlight ? selected.matched : []; | |
return setNodes(selected.selected, highlight_nodes, classes); | |
} | |
service.hideGraph = function() { | |
service.orientation = 'sidebar'; | |
service.expanded = false; | |
} | |
service.showVerticalGraph = function(node_name, force_expand) { | |
service.orientation = 'sidebar' | |
if (force_expand) { | |
service.expanded = true; | |
} | |
var selected_spec = _.assign({}, selectorService.options, { | |
include: "+" + node_name + "+", | |
exclude: '', | |
hops: 1 | |
}); | |
var nodes = updateGraphWithSelector(selected_spec, 'vertical', true); | |
service.graph.layout = layouts.top_down; | |
service.graph.options = graph_options.vertical; | |
return nodes; | |
} | |
service.showFullGraph = function(node_name) { | |
service.orientation = 'fullscreen' | |
service.expanded = true; | |
var selected_spec = _.assign({}, selectorService.options); | |
if (node_name) { | |
selected_spec.include = "+" + node_name + "+"; | |
selected_spec.exclude = ""; | |
} else { | |
selected_spec.include = ""; | |
selected_spec.exclude = ""; | |
} | |
var nodes = updateGraphWithSelector(selected_spec, 'horizontal', true); | |
service.graph.layout = layouts.left_right; | |
service.graph.options = graph_options.horizontal; | |
// SWIM-LANES: Organize into swim-lanes after layout | |
if (service.graph_element) { | |
setTimeout(function() { | |
service.organizeIntoSwimLanes(); | |
}, 200); // Delay to ensure Dagre layout is complete | |
} | |
// update url with selection | |
locationService.setState(selected_spec); | |
return nodes; | |
} | |
service.updateGraph = function(selected_spec) { | |
service.orientation = 'fullscreen' | |
service.expanded = true; | |
var nodes = updateGraphWithSelector(selected_spec, 'horizontal', false); | |
service.graph.layout = layouts.left_right; | |
service.graph.options = graph_options.horizontal; | |
// SWIM-LANES: Organize into swim-lanes after layout | |
if (service.graph_element) { | |
setTimeout(function() { | |
service.organizeIntoSwimLanes(); | |
}, 200); // Delay to ensure Dagre layout is complete | |
} | |
// update url with selection | |
locationService.setState(selected_spec); | |
return nodes; | |
} | |
service.deselectNodes = function() { | |
if (service.orientation != 'fullscreen') { | |
return; | |
} | |
var g = service.graph_element; | |
g.elements().data('selected', 0); | |
} | |
service.selectNode = function(node_id) { | |
if (service.orientation != 'fullscreen') { | |
return; | |
} | |
var node = service.graph.pristine.nodes[node_id]; | |
// get all edges that pass through this node | |
var dag = service.graph.pristine.dag; | |
var parents = _.indexBy(selectorGraph.ancestorNodes(dag, node_id)); | |
var children = _.indexBy(selectorGraph.descendentNodes(dag, node_id)); | |
parents[node_id] = node_id; | |
children[node_id] = node_id; | |
var g = service.graph_element; | |
_.each(service.graph.elements, function(el) { | |
var graph_el = g.$id(el.data.id); | |
if (parents[el.data.source] && parents[el.data.target]) { | |
graph_el.data('selected', 1); | |
} else if (children[el.data.source] && children[el.data.target]) { | |
graph_el.data('selected', 1); | |
} else if (el.data.unique_id == node_id) { | |
graph_el.data('selected', 1); | |
} else { | |
graph_el.data('selected', 0); | |
} | |
}); | |
} | |
service.markDirty = function(node_ids) { | |
service.markAllClean(); | |
_.each(node_ids, function(node_id) { | |
service.graph_element.$id(node_id).addClass('dirty'); | |
}) | |
} | |
service.markAllClean = function() { | |
if (service.graph_element) { | |
service.graph_element.elements().removeClass('dirty'); | |
} | |
} | |
return service; | |
}]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment