Files
2025-08-27 17:10:05 +08:00

187 lines
4.3 KiB
JavaScript

import _ from 'lodash'
import { layout } from 'dagre-layout'
import positionNodes from './position-nodes'
import positionEdgeLabels from './position-edge-labels'
import positionClusters from './position-clusters'
import createNodes from './create-nodes'
import createClusters from './create-clusters'
import createEdgeLabels from './create-edge-labels'
import createEdgePaths from './create-edge-paths'
import shapes from './shapes'
import arrows from './arrows'
// This design is based on http://bost.ocks.org/mike/chart/.
function render () {
let _createNodes = createNodes
let _createClusters = createClusters
let _createEdgeLabels = createEdgeLabels
let _createEdgePaths = createEdgePaths
let _shapes = shapes
let _arrows = arrows
const fn = function (svg, g) {
preProcessGraph(g)
const outputGroup = createOrSelectGroup(svg, 'output')
const clustersGroup = createOrSelectGroup(outputGroup, 'clusters')
const edgePathsGroup = createOrSelectGroup(outputGroup, 'edgePaths')
const edgeLabels = _createEdgeLabels(createOrSelectGroup(outputGroup, 'edgeLabels'), g)
const nodes = _createNodes(createOrSelectGroup(outputGroup, 'nodes'), g, _shapes)
layout(g)
positionNodes(nodes, g)
positionEdgeLabels(edgeLabels, g)
_createEdgePaths(edgePathsGroup, g, _arrows)
const clusters = _createClusters(clustersGroup, g)
positionClusters(clusters, g)
postProcessGraph(g)
}
fn.createNodes = function (value) {
if (!arguments.length) {
return _createNodes
}
_createNodes = value
return fn
}
fn.createClusters = function (value) {
if (!arguments.length) {
return _createClusters
}
_createClusters = value
return fn
}
fn.createEdgeLabels = function (value) {
if (!arguments.length) {
return _createEdgeLabels
}
_createEdgeLabels = value
return fn
}
fn.createEdgePaths = function (value) {
if (!arguments.length) {
return _createEdgePaths
}
_createEdgePaths = value
return fn
}
fn.shapes = function (value) {
if (!arguments.length) {
return _shapes
}
_shapes = value
return fn
}
fn.arrows = function (value) {
if (!arguments.length) {
return _arrows
}
_arrows = value
return fn
}
return fn
}
const NODE_DEFAULT_ATTRS = {
paddingLeft: 10,
paddingRight: 10,
paddingTop: 10,
paddingBottom: 10,
rx: 0,
ry: 0,
shape: 'rect'
}
const EDGE_DEFAULT_ATTRS = {
arrowhead: 'normal',
lineInterpolate: 'linear'
}
function preProcessGraph (g) {
g.nodes().forEach(function (v) {
const node = g.node(v)
if (!_.has(node, 'label') && !g.children(v).length) { node.label = v }
if (_.has(node, 'paddingX')) {
_.defaults(node, {
paddingLeft: node.paddingX,
paddingRight: node.paddingX
})
}
if (_.has(node, 'paddingY')) {
_.defaults(node, {
paddingTop: node.paddingY,
paddingBottom: node.paddingY
})
}
if (_.has(node, 'padding')) {
_.defaults(node, {
paddingLeft: node.padding,
paddingRight: node.padding,
paddingTop: node.padding,
paddingBottom: node.padding
})
}
_.defaults(node, NODE_DEFAULT_ATTRS)
_.each(['paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom'], function (k) {
node[k] = Number(node[k])
})
// Save dimensions for restore during post-processing
if (_.has(node, 'width')) { node._prevWidth = node.width }
if (_.has(node, 'height')) { node._prevHeight = node.height }
})
g.edges().forEach(function (e) {
const edge = g.edge(e)
if (!_.has(edge, 'label')) { edge.label = '' }
_.defaults(edge, EDGE_DEFAULT_ATTRS)
})
}
function postProcessGraph (g) {
_.each(g.nodes(), function (v) {
const node = g.node(v)
// Restore original dimensions
if (_.has(node, '_prevWidth')) {
node.width = node._prevWidth
} else {
delete node.width
}
if (_.has(node, '_prevHeight')) {
node.height = node._prevHeight
} else {
delete node.height
}
delete node._prevWidth
delete node._prevHeight
})
}
function createOrSelectGroup (root, name) {
let selection = root.select('g.' + name)
if (selection.empty()) {
selection = root.append('g').attr('class', name)
}
return selection
}
export default render