| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- import _ from 'lodash'
- import dagre from 'dagre-layout'
- import * as d3 from 'd3'
- 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)
- svg.selectAll('*').remove()
- 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)
- dagre.layout(g)
- let minX = 1000
- let minY = 1000
- let maxX = -1000
- let maxY = -1000
- const graph = g
- graph.nodes().map(n => graph.node(n)).forEach(node => {
- minX = Math.min(minX, node.x - node.width / 2)
- minY = Math.min(minY, node.y - node.height / 2)
- maxX = Math.max(maxX, node.x + node.width / 2)
- maxY = Math.max(maxY, node.y + node.height / 2)
- })
- graph.edges().forEach(e => {
- const edge = graph.edge(e)
- if (edge.label !== undefined && edge.x !== undefined && edge.y !== undefined) {
- minX = Math.min(minX, edge.x - edge.width / 2)
- minY = Math.min(minY, edge.y - edge.height / 2)
- maxX = Math.max(maxX, edge.x + edge.width / 2)
- maxY = Math.max(maxY, edge.y + edge.height / 2)
- }
- const points = edge.points.slice(1, edge.points.length - 1) // intersetion points don't matter
- for (let i = 0; i < points.length; i++) {
- const point = points[i]
- minX = Math.min(minX, point.x)
- minY = Math.min(minY, point.y)
- maxX = Math.max(maxX, point.x)
- maxY = Math.max(maxY, point.y)
- }
- })
- graph.minX = minX
- graph.minY = minY
- graph.maxX = maxX
- graph.maxY = maxY
- 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',
- curve: d3.curveLinear
- }
- 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
|