Skip to content

Commit 713b0c1

Browse files
committed
Initial commit. 🌅
0 parents  commit 713b0c1

14 files changed

+52679
-0
lines changed

.babelrc

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"presets": [
3+
"es2015",
4+
"stage-0",
5+
"react"
6+
]
7+
}

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
.idea
3+
build
4+
lib

.npmignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

.storybook/config.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { configure } from '@kadira/storybook'
2+
3+
function loadStories () {
4+
require('../stories')
5+
}
6+
7+
configure(loadStories, module)

package.json

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "@buggyorg/graphify-react",
3+
"version": "0.1.0",
4+
"description": "React components to display buggy graphs.",
5+
"main": "lib/index.js",
6+
"scripts": {
7+
"build": "rm -rf lib && babel src -d lib",
8+
"prepublish": "rm -rf lib && babel src -d lib",
9+
"storybook": "start-storybook -p 6006",
10+
"deploy-storybook": "storybook-to-ghpages",
11+
"test": "jest"
12+
},
13+
"repository": {
14+
"type": "git",
15+
"url": "git+https://github.com/BuggyOrg/graphify-react.git"
16+
},
17+
"author": "",
18+
"license": "MIT",
19+
"bugs": {
20+
"url": "https://github.com/BuggyOrg/graphify-react/issues"
21+
},
22+
"homepage": "https://github.com/BuggyOrg/graphify-react#readme",
23+
"devDependencies": {
24+
"@kadira/storybook": "^2.35.3",
25+
"@kadira/storybook-deployer": "^1.2.0",
26+
"babel-cli": "^6.14.0",
27+
"babel-core": "^6.8.0",
28+
"babel-plugin-transform-object-assign": "^6.8.0",
29+
"babel-preset-es2015": "^6.14.0",
30+
"babel-preset-react": "^6.11.1",
31+
"babel-preset-stage-0": "^6.5.0",
32+
"jest": "^19.0.2",
33+
"react": "^15.5.4",
34+
"react-dom": "^15.5.4",
35+
"react-test-renderer": "^15.5.4",
36+
"storyshots": "^3.2.2"
37+
},
38+
"peerDependencies": {
39+
"react": "^15.5.4",
40+
"react-dom": "^15.5.4"
41+
},
42+
"dependencies": {
43+
"calculate-size": "^1.1.1",
44+
"graphify-node": "^1.0.0",
45+
"prop-types": "^15.5.8"
46+
}
47+
}

src/Graph.js

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import React from 'react'
2+
import { isCompound } from './graphUtil'
3+
4+
function isOutputPort ({ id }) {
5+
return /.+_out$/.test(id)
6+
}
7+
8+
function Port ({ port }) {
9+
return (
10+
<rect
11+
x={port.x}
12+
y={port.y}
13+
width={port.width}
14+
height={port.height}
15+
stroke='#000'
16+
fill={isOutputPort(port) ? 'black' : 'white'}
17+
/>
18+
)
19+
}
20+
21+
function getPath (e, padding, startsAtParent = null) {
22+
let path
23+
if (startsAtParent) {
24+
path = 'M ' + e.sourcePoint.x + ' ' + e.sourcePoint.y + ' '
25+
} else {
26+
path = 'M ' + (e.sourcePoint.x + padding.left) + ' ' + (e.sourcePoint.y + padding.top) + ' '
27+
}
28+
var bendPoints = e.bendPoints || []
29+
bendPoints.forEach((bp, i) => {
30+
path += 'L ' + (bp.x + padding.left) + ' ' + (bp.y + padding.top) + ' '
31+
})
32+
path += 'L ' + (e.targetPoint.x + padding.left) + ' ' + (e.targetPoint.y + padding.top - 10) + ' '
33+
return path
34+
}
35+
36+
function Edge ({ parentNode, edge, padding = { top: 0, left: 0 }, ...other }) {
37+
let endPoint = edge.targetPoint
38+
let previousPoint = edge.bendPoints != null && edge.bendPoints.length > 0 ? edge.bendPoints[edge.bendPoints.length - 1] : edge.sourcePoint
39+
const angle = Math.atan2(endPoint.y - previousPoint.y, endPoint.x - previousPoint.x) * 180 / Math.PI
40+
const color = (edge.meta && edge.meta.style && edge.meta.style.color) || '#333333'
41+
42+
return (
43+
<g>
44+
<path
45+
strokeWidth={3}
46+
opacity={0.8}
47+
fill='none'
48+
stroke={color}
49+
d={getPath(edge, padding, edge.source === parentNode.id)}
50+
{...other}
51+
/>
52+
<path
53+
strokeWidth={3}
54+
opacity={0.8}
55+
fill={color}
56+
d='M0,0 L0,3 L3,1.5 L0,0'
57+
transform={`translate(${edge.targetPoint.x + padding.left + 4.5} ${edge.targetPoint.y + padding.top - 10}) rotate(${angle}) scale(3)`}
58+
/>
59+
</g>
60+
)
61+
}
62+
63+
function Node ({ graph, node, ...other }) {
64+
const transform = `translate(${(node.x || 0) + (node.padding ? node.padding.left : 0)} ${(node.y || 0) + (node.padding ? node.padding.top : 0)})`
65+
66+
if (isCompound(node, graph)) {
67+
return (
68+
<g
69+
transform={transform}
70+
>
71+
<rect
72+
{...other}
73+
strokeWidth={1}
74+
width={node.width}
75+
height={node.height}
76+
strokeOpacity={0.5}
77+
strokeDasharray='10 5'
78+
fillOpacity={0}
79+
transform='translate(0.5 0.5)'
80+
stroke={(node.meta && node.meta.style && node.meta.style.color) || '#000'}
81+
/>
82+
<text
83+
x={5}
84+
y={5}
85+
alignmentBaseline='hanging'
86+
fontFamily='sans-serif'
87+
fontSize={14}
88+
fill={(node.meta && node.meta.style && node.meta.style.color) || null}
89+
opacity={0.5}
90+
>
91+
{node.text}
92+
</text>
93+
{Array.isArray(node.edges) && node.edges.map((edge) => (
94+
<Edge
95+
key={`e-${edge.source}-${edge.target}`}
96+
graph={graph}
97+
parentNode={node}
98+
edge={edge}
99+
padding={node.padding}
100+
/>
101+
))}
102+
{Array.isArray(node.ports) && node.ports.map((port) => (
103+
<Port
104+
key={`p-${port.id}`}
105+
graph={graph}
106+
port={port}
107+
/>
108+
))}
109+
{Array.isArray(node.children) && node.children.map((child) => (
110+
<Node
111+
key={`c-${child.id}`}
112+
graph={graph}
113+
node={child}
114+
/>
115+
))}
116+
</g>
117+
)
118+
} else {
119+
return (
120+
<g
121+
transform={transform}
122+
>
123+
<rect
124+
{...other}
125+
strokeWidth={3}
126+
width={node.width}
127+
height={node.height}
128+
stroke={(node.meta && node.meta.style && node.meta.style.color) || '#000'}
129+
fill='#fff'
130+
/>
131+
<text
132+
x={node.width / 2}
133+
y={node.height / 2}
134+
textAnchor='middle'
135+
alignmentBaseline='central'
136+
fontFamily='sans-serif'
137+
fontSize={14}
138+
fill={(node.meta && node.meta.style && node.meta.style.color) || null}
139+
>
140+
{node.text}
141+
</text>
142+
{Array.isArray(node.ports) && node.ports.map((port) => (
143+
<Port
144+
key={`p-${port.id}`}
145+
graph={graph}
146+
port={port}
147+
/>
148+
))}
149+
</g>
150+
)
151+
}
152+
}
153+
154+
function RootNode ({ graph, node }) {
155+
return (
156+
<g transform={`translate(${-node.padding.left} ${-node.padding.top})`}>
157+
{Array.isArray(node.edges) && node.edges.map((edge) => (
158+
<Edge
159+
key={`e-${edge.source}-${edge.target}`}
160+
graph={graph}
161+
parentNode={node}
162+
edge={edge}
163+
/>
164+
))}
165+
{Array.isArray(node.ports) && node.ports.map((port) => (
166+
<Port
167+
key={`p-${port.id}`}
168+
graph={graph}
169+
port={port}
170+
/>
171+
))}
172+
{Array.isArray(node.children) && node.children.map((child) => (
173+
<Node
174+
key={`c-${child.id}`}
175+
graph={graph}
176+
node={child}
177+
/>
178+
))}
179+
</g>
180+
)
181+
}
182+
183+
export default function Graph ({ graph }) {
184+
return (
185+
<svg
186+
width={graph.width - graph.padding.left - graph.padding.right}
187+
height={graph.height - graph.padding.top - graph.padding.bottom}
188+
>
189+
<RootNode
190+
graph={graph}
191+
node={graph}
192+
/>
193+
</svg>
194+
)
195+
}

src/GraphLayouter.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React, { Component } from 'react'
2+
import calculateSize from 'calculate-size'
3+
import graphify from 'graphify-node'
4+
import Graph from './Graph'
5+
6+
export default class GraphLayouter extends Component {
7+
constructor (props) {
8+
super(props)
9+
this.state = {
10+
graph: null
11+
}
12+
this.updateGraph(this.props.kgraph)
13+
}
14+
15+
componentWillReceiveProps (nextProps) {
16+
if (nextProps.kgraph !== this.props.kgraph) {
17+
this.setState({ graph: null })
18+
this.updateGraph(nextProps.kgraph)
19+
} else if (nextProps.options !== this.props.options) {
20+
this.updateGraph(nextProps.kgraph)
21+
}
22+
}
23+
24+
updateGraph (kgraph) {
25+
if (kgraph == null) return
26+
27+
layout(kgraph, calculateSize, this.props.options).then((graph) => {
28+
if (this.props.kgraph === kgraph) {
29+
this.setState({ graph })
30+
}
31+
})
32+
.catch((e) => console.error(e))
33+
}
34+
35+
render () {
36+
let { graph } = this.state
37+
return graph != null ? (
38+
<Graph
39+
graph={graph}
40+
/>
41+
) : (
42+
<svg />
43+
)
44+
}
45+
}
46+
47+
export function layout (graph, calculateSize, options = {}) {
48+
// wrap the given kgraph in another graph for layouting to support ports on the original root node
49+
graph = {
50+
id: `graphify_root_${Date.now()}`,
51+
children: [ graph ],
52+
edges: []
53+
}
54+
return graphify.layout(graph, calculateSize, options)
55+
.then((graph) => graph.children[0]) // unwrap the graph
56+
}

src/graphUtil.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export function getNode (id, graph) {
2+
if (graph.id === id) {
3+
return graph
4+
}
5+
6+
const node = (graph.children || []).find((n) => n.id === id)
7+
if (node) {
8+
return node
9+
} else {
10+
for (let i = 0; i < (graph.children || []).length; i++) {
11+
const result = getNode(id, graph.children[i])
12+
if (result) {
13+
return result
14+
}
15+
}
16+
}
17+
}
18+
19+
export function hasChildren (node, graph) {
20+
node = getNode(node.id, graph)
21+
return node.children && node.children.length > 0
22+
}
23+
24+
export function hasEdges (node, graph) {
25+
node = getNode(node.id, graph)
26+
return node.edges && node.edges.length > 0
27+
}
28+
29+
export function isCompound (node, graph) {
30+
return hasChildren(node, graph) || hasEdges(node, graph)
31+
}

src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as Graph } from './Graph'
2+
export { default as GraphLayouter, layout } from './GraphLayouter'

0 commit comments

Comments
 (0)