aboutsummaryrefslogtreecommitdiffstats
path: root/graphgraph-fe/src
diff options
context:
space:
mode:
Diffstat (limited to 'graphgraph-fe/src')
-rw-r--r--graphgraph-fe/src/App.css2
-rw-r--r--graphgraph-fe/src/App.js9
-rw-r--r--graphgraph-fe/src/Graph.js76
-rw-r--r--graphgraph-fe/src/GraphHops.js6
-rw-r--r--graphgraph-fe/src/GraphInfoMenu.css4
-rw-r--r--graphgraph-fe/src/GraphInfoMenu.js26
-rw-r--r--graphgraph-fe/src/GraphSettings.js81
-rw-r--r--graphgraph-fe/src/GraphSettingsMenu.js2
-rw-r--r--graphgraph-fe/src/requests.js8
9 files changed, 169 insertions, 45 deletions
diff --git a/graphgraph-fe/src/App.css b/graphgraph-fe/src/App.css
index 53f375a..74c1327 100644
--- a/graphgraph-fe/src/App.css
+++ b/graphgraph-fe/src/App.css
@@ -49,5 +49,5 @@ left: 0;
border-width: 1px 0 1px 0;
position: relative;
width: 100%;
- height: 65%;
+ height: 70%;
}
diff --git a/graphgraph-fe/src/App.js b/graphgraph-fe/src/App.js
index 4573d79..788aca1 100644
--- a/graphgraph-fe/src/App.js
+++ b/graphgraph-fe/src/App.js
@@ -69,6 +69,10 @@ class App extends React.Component {
s['selectedSchema'] = selectedGraphSchema
s['graphFingerprint'] = graphFingerprint
s['graph'] = data
+ // TODO this should be handled more gracefully ...
+ if (_.isEmpty(data.edges)) {
+ alert('The graph has no edges, nothing to display')
+ }
s['displayedProperties'] = data.startNodeProperties
if (_.isArray(data.paths) && !_.isEmpty(data.paths)) {
s['paths'] = data.paths
@@ -101,9 +105,12 @@ class App extends React.Component {
}
render () {
+ let n = _.invert(this.state.nodeStates)[constants.CLICKED]
+
+ let selectedNode = _.isUndefined(n) ? '' : n
return (
<div className="App">
- <GraphSettingsMenu graphData={this.graphdata}/>
+ <GraphSettingsMenu graphData={this.graphdata} nodePropsLoader={this.loadNodeProperties} selectedNode={selectedNode}/>
<div className='graph-area'>
<Graph graphFingerprint={this.state.graphFingerprint} edgePropsLoader={this.loadEdgeProperties} selectedEdge={this.state.selectedEdge} nodes={this.state.graph.nodeNames} edges={this.state.graph.edges} nodeStates={this.state.nodeStates} nodePropsLoader={this.loadNodeProperties}/>
</div>
diff --git a/graphgraph-fe/src/Graph.js b/graphgraph-fe/src/Graph.js
index c6e5654..c4b1aa0 100644
--- a/graphgraph-fe/src/Graph.js
+++ b/graphgraph-fe/src/Graph.js
@@ -33,7 +33,7 @@ var mouseOverEdge = function (edge, div) {
var mouseOutEdge = function (edge, div) {
div.transition()
- .duration(4000)
+ .duration(6000)
.style('opacity', 0)
}
@@ -99,9 +99,54 @@ var addNodeLabels = function (nodes, g) {
.attr('fill', NODE_LABEL_COLOR)
.attr('font-size', '32px')
.text(d => d.id)
+ .on('mouseenter', onNodeLabelMouseOver)
+ .on('mouseout', onNodeLabelMouseOut)
}
var addLinks = function (links, g, div, edgePropsLoader) {
+ let ss = _.filter(links, l => l.source.id === l.target.id)
+
+ let selfLinks = _.isUndefined(ss) ? [] : ss
+
+ g.selectAll('ellipse')
+ .data(selfLinks)
+ .enter().append('ellipse')
+ .attr('fill-opacity', 0)
+ .attr('rx', d => 100)
+ .attr('ry', d => 16)
+ .attr('cx', d => d.target.x + 80)
+ .attr('cy', d => d.target.y)
+ .style('stroke', NODE_BORDER_COLOR)
+ .attr('stroke-width', 5)
+ .on('click', edge => edgePropsLoader(edge.source.id, edge.target.id))
+ .on('mouseenter', function (edge) {
+ mouseOverEdge(edge, div)
+ d3.select(this)
+ .transition()
+ .attr('oldStroke', EDGE_NORMAL_COLOR)
+ .duration(10)
+ .style('stroke', EDGE_MOUSE_OVER_COLOR)
+ })
+ .on('mouseleave', function (edge) {
+ mouseOutEdge(edge, div)
+ var strokeColor = d3.select(this).attr('oldStroke')
+ d3.select(this)
+ .transition()
+ .duration(300)
+ .style('stroke', strokeColor)
+ })
+
+ g.selectAll('.edgelooplabel')
+ .data(selfLinks)
+ .enter()
+ .append('text')
+ .attr('x', d => d.source.x + 35)
+ .attr('y', d => d.source.y + 50)
+ .attr('class', 'edgelooplabel')
+ .attr('fill', NODE_LABEL_COLOR)
+ .attr('font-size', '22px')
+ .text(d => d.type)
+
g.selectAll('line')
.data(links)
.enter().append('line')
@@ -147,10 +192,12 @@ var addMarkers = function (g, svg) {
.attr('fill', EDGE_NORMAL_COLOR)
.attr('stroke', EDGE_NORMAL_COLOR)
- var zoom_handler = d3.zoom()
+ var zoomHandler = d3.zoom()
.on('zoom', _ => g.attr('transform', d3.event.transform))
- zoom_handler(svg)
+ zoomHandler(svg)
+ zoomHandler.translateTo(svg, -7000, -4000)
+ zoomHandler.scaleTo(svg, 0.08)
}
var drawGraph = function (nodes, links, g, simulation, svg, addNodes, edgePropsLoader) {
@@ -189,8 +236,9 @@ var prepareLinks = function (nodes, links) {
var createSimulation = function (nodes, links) {
return d3.forceSimulation(nodes)
- .force('charge', d3.forceManyBody().strength(-1800))
- .force('link', d3.forceLink(links).distance(400).strength(1).iterations(100))
+ .force('charge', d3.forceManyBody().strength(-201))
+ .force('link', d3.forceLink(links).distance(1200).strength(1).iterations(400))
+ .force('collision', d3.forceCollide().radius(d => 310))
.force('x', d3.forceX())
.force('y', d3.forceY())
.stop()
@@ -198,13 +246,25 @@ var createSimulation = function (nodes, links) {
var createSvg = function () {
var svg = d3.select('#graph').append('svg').attr('height', '100%').attr('width', '100%')
- var width = 100
- var height = 100
- var g = svg.append('g').attr('transform', `translate(${(300 + width / 10)}, ${height / 10})`)
+ var g = svg.append('g')
return { 'g': g, 'svg': svg }
}
+var onNodeLabelMouseOut = function () {
+ d3.select(this)
+ .transition()
+ .duration(600)
+ .attr('font-size', '32px')
+}
+
+var onNodeLabelMouseOver = function () {
+ d3.select(this)
+ .transition()
+ .duration(200)
+ .attr('font-size', '232px')
+}
+
var onNodeMouseOver = function () {
var oldFill = d3.select(this).style('fill')
d3.select(this)
diff --git a/graphgraph-fe/src/GraphHops.js b/graphgraph-fe/src/GraphHops.js
index e835182..cd5763a 100644
--- a/graphgraph-fe/src/GraphHops.js
+++ b/graphgraph-fe/src/GraphHops.js
@@ -7,7 +7,7 @@ var createNumInput = function (label, callback, current) {
return (
<div>
<Label>{label}</Label>
- <NumericInput onChange={callback} min={0} max={500} value={current} className="hops-input-field" />
+ <NumericInput onChange={callback} min={1} max={500} value={current} className="hops-input-field" />
</div>
)
}
@@ -34,7 +34,6 @@ class GraphHops extends React.Component {
var s = this.state
s[hopsName] = num
this.setState(s)
-
this.props.updateHops(this.state.parentHops, this.state.cousinHops, this.state.childHops)
}
@@ -51,12 +50,11 @@ class GraphHops extends React.Component {
}
render () {
+ // {createNumInput('cousin hops', this.onChangeCousin, this.state.cousinHops)}
return (
<div className="hops-input">
{createNumInput('parent hops', this.onChangeParent, this.state.parentHops)}
- {createNumInput('cousin hops', this.onChangeCousin, this.state.cousinHops)}
{createNumInput('child hops', this.onChangeChild, this.state.childHops)}
-
</div>
)
}
diff --git a/graphgraph-fe/src/GraphInfoMenu.css b/graphgraph-fe/src/GraphInfoMenu.css
index 59ad6e4..24a2719 100644
--- a/graphgraph-fe/src/GraphInfoMenu.css
+++ b/graphgraph-fe/src/GraphInfoMenu.css
@@ -1,7 +1,7 @@
.node-property-list {
float:top;
display: flex;
- height: 250px;
+ height: 200px;
width: 100%;
}
@@ -12,7 +12,7 @@
.fixed-height-container {
overflow: scroll;
float:top;
- height: 250px;
+ height: 200px;
width:40%;
padding:3px;
background:white;
diff --git a/graphgraph-fe/src/GraphInfoMenu.js b/graphgraph-fe/src/GraphInfoMenu.js
index 52a081e..c22a504 100644
--- a/graphgraph-fe/src/GraphInfoMenu.js
+++ b/graphgraph-fe/src/GraphInfoMenu.js
@@ -4,9 +4,8 @@ import PathBreadCrumb from './PathBreadCrumb'
import _ from 'underscore'
import ReactBasicTable from 'react-basic-table'
-var generatePropertyTable = function (nodeProps) {
- var columns = ['Property Name', 'Value']
- var rows = _.map(nodeProps, path => {
+var getRows = function (nodeProps) {
+ return _.map(nodeProps, path => {
return (
[
<span>{path.propertyName}</span>,
@@ -14,15 +13,20 @@ var generatePropertyTable = function (nodeProps) {
]
)
})
-
- return (
- <div className="datatable">
- <ReactBasicTable pageSize={4} rows={rows} columns={columns} />
- </div>
- )
}
class GraphInfoMenu extends React.Component {
+ constructor (props) {
+ super(props)
+ this.basicTable = React.createRef()
+ }
+
+ // ReactBasicTable does not reset pagination after
+ // changing data, we need to do it manually
+ componentDidUpdate (prevProps) {
+ this.basicTable.current.setPage(1)
+ }
+
render () {
var paths = this.props.paths
var callback = this.props.pathCallback
@@ -34,8 +38,8 @@ class GraphInfoMenu extends React.Component {
<p className='path-heading'>Paths</p>
{breadcrumbs}
</div>
- <div className="kv-table">
- {generatePropertyTable(this.props.nodeProperties)}
+ <div className="kv-table datatable">
+ <ReactBasicTable ref={this.basicTable} pageSize={3} rows={getRows(this.props.nodeProperties)} columns={['Property Name', 'Value']} />
</div>
</div>
)
diff --git a/graphgraph-fe/src/GraphSettings.js b/graphgraph-fe/src/GraphSettings.js
index 94b3e31..54e2f4c 100644
--- a/graphgraph-fe/src/GraphSettings.js
+++ b/graphgraph-fe/src/GraphSettings.js
@@ -13,7 +13,9 @@ var emptyState = {
edges: []
},
showHops: false,
+ enableDestinationNode: false,
toNode: '',
+ edgeFilter: 'Edgerules',
hops: {
parents: 1,
cousin: 1,
@@ -25,30 +27,40 @@ var emptyState = {
class GraphSettings extends React.Component {
constructor (props, context) {
super(props, context)
-
this.onChangeStartNode = this.onChangeStartNode.bind(this)
+ this.onSelectNode = this.onSelectNode.bind(this)
this.selectSchema = this.selectSchema.bind(this)
this.onChangeToNode = this.onChangeToNode.bind(this)
this.loadInitialGraph = this.loadInitialGraph.bind(this)
this.updateHops = this.updateHops.bind(this)
+ this.changeEdgeFilter = this.changeEdgeFilter.bind(this)
this.graphFingerprint = this.graphFingerprint.bind(this)
this.state = emptyState
}
// this serves as a config 'fingerprint' to know if the d3 visualisation should be redrawn from scratch or just updated
- graphFingerprint (schema, from, to, parents, cousin, child) {
- return `${schema}:${from}:${to}:${parents}:${cousin}:${child}`
+ graphFingerprint (schema, from, to, parents, cousin, child, edgeFilter) {
+ return `${schema}:${from}:${to}:${parents}:${cousin}:${child}:${edgeFilter}`
}
- loadInitialGraph (startNode, endNode, parentHops, cousinHops, childHops) {
- let requestUri = endNode === 'none' ? basicGraph(this.state.selectedSchema, startNode, 0, 0, 1) : pathGraph(this.state.selectedSchema, startNode, endNode)
+ loadInitialGraph (startNode, endNode, parentHops, cousinHops, childHops, edgeFilter) {
+ if (this.state.selectedSchema === '' || startNode === 'none') {
+ return
+ }
+ if (startNode === 'all') {
+ endNode = 'none'
+ }
+
+ let requestUri = endNode === 'none'
+ ? basicGraph(this.state.selectedSchema, startNode, parentHops, cousinHops, childHops, edgeFilter) : pathGraph(this.state.selectedSchema, startNode, endNode, edgeFilter)
fetch(requestUri)
.then(response => response.json())
.then(g => {
let schema = this.state.selectedSchema
- let f = this.graphFingerprint(schema, startNode, endNode, parentHops, cousinHops, childHops)
+ let f = this.graphFingerprint(schema, startNode, endNode, parentHops, cousinHops, childHops, edgeFilter)
this.props.graphData(g, this.state.selectedSchema, f)
+ return g
})
.then(g => {
var s = this.state
@@ -58,8 +70,14 @@ class GraphSettings extends React.Component {
s['fromNode'] = startNode
s['toNode'] = endNode
s['graph'] = g
- s['showHops'] = endNode === 'none' && startNode !== 'none'
+ s['edgeFilter'] = edgeFilter
+ s['showHops'] = endNode === 'none' && startNode !== 'none' && startNode !== 'all'
+ s['enableDestinationNode'] = startNode !== 'none' && startNode !== 'all'
this.setState(s)
+
+ if (startNode !== 'all') {
+ this.onSelectNode(startNode)
+ }
})
}
@@ -75,13 +93,25 @@ class GraphSettings extends React.Component {
})
}
+ changeEdgeFilter (edgeFilter) {
+ this.loadInitialGraph(
+ this.state.fromNode,
+ this.state.toNode,
+ this.state.hops.parents,
+ this.state.hops.cousin,
+ this.state.hops.child,
+ edgeFilter
+ )
+ }
+
updateHops (parentHops, cousinHops, childHops) {
this.loadInitialGraph(
this.state.fromNode,
this.state.toNode,
parentHops,
cousinHops,
- childHops)
+ childHops,
+ this.state.edgeFilter)
}
onChangeToNode (eventKey) {
@@ -89,14 +119,20 @@ class GraphSettings extends React.Component {
eventKey,
this.state.hops.parents,
this.state.hops.cousin,
- this.state.hops.child)
+ this.state.hops.child,
+ this.state.edgeFilter)
+ }
+
+ onSelectNode (eventKey) {
+ this.props.nodePropsLoader(eventKey)
}
onChangeStartNode (eventKey) {
this.loadInitialGraph(eventKey, this.state.toNode,
this.state.hops.parents,
this.state.hops.cousin,
- this.state.hops.child)
+ this.state.hops.child,
+ this.state.edgeFilter)
}
componentDidMount () {
@@ -113,14 +149,21 @@ class GraphSettings extends React.Component {
var schemas = _.map(this.state.schemas, (x, k) => <MenuItem key={k} eventKey={x}>{x}</MenuItem>)
var items = _.map(this.state.nodeNames, (x, k) => <MenuItem key={k} eventKey={x.id}>{x.id}</MenuItem>)
+ let sortedNames = _.sortBy(this.state.graph.nodeNames, 'id')
+ var currentNodeNames = _.map(sortedNames, (x, k) => <MenuItem key={k} eventKey={x.id}>{x.id}</MenuItem>)
var fromItems = items.slice()
fromItems.unshift(<MenuItem key='divider' divider />)
- fromItems.unshift(<MenuItem key='none' eventKey='none'>none</MenuItem>)
+ fromItems.unshift(<MenuItem key='all' eventKey='all'>all</MenuItem>)
items.unshift(<MenuItem key='anotherdivider' divider />)
items.unshift(<MenuItem key='none' eventKey='none'>none</MenuItem>)
+ let edgeFilterItems = [
+ <MenuItem key='Edgerules' eventKey='Edgerules'>Edgerules</MenuItem>,
+ <MenuItem key='Parents' eventKey='Parents'>Parent-child (OXM structure)</MenuItem>,
+ <MenuItem key='Both' eventKey='Both'>Both</MenuItem>
+ ]
return (
<div>
<div className="graph-menu">
@@ -139,11 +182,23 @@ class GraphSettings extends React.Component {
</div>
<div>
<Label>Destination Node</Label>
- <DropdownButton className="node-dropdown" onSelect={this.onChangeToNode} id="namesTo" title={this.state.toNode}>
+ <DropdownButton disabled={!this.state.enableDestinationNode} className="node-dropdown" onSelect={this.onChangeToNode} id="namesTo" title={this.state.toNode}>
{items}
</DropdownButton>
-
</div>
+ <div className="source-dropdown-div">
+ <Label>Edge filter</Label>
+ <DropdownButton className="node-dropdown" onSelect={this.changeEdgeFilter} id="filterEdge" title={this.state.edgeFilter}>
+ {edgeFilterItems}
+ </DropdownButton>
+ </div>
+ <div className="source-dropdown-div">
+ <Label>Selected Node</Label>
+ <DropdownButton className="node-dropdown" onSelect={this.onSelectNode} id="selectedNode" title={this.props.selectedNode}>
+ {currentNodeNames}
+ </DropdownButton>
+ </div>
+
<Popup isDisabled={!this.state.showHops} parentHops={this.state.hops.parents} childHops={this.state.hops.child} cousinHops={this.state.hops.cousin} updateHops={this.updateHops}/>
</div>
diff --git a/graphgraph-fe/src/GraphSettingsMenu.js b/graphgraph-fe/src/GraphSettingsMenu.js
index f30fa29..d1b31ba 100644
--- a/graphgraph-fe/src/GraphSettingsMenu.js
+++ b/graphgraph-fe/src/GraphSettingsMenu.js
@@ -16,7 +16,7 @@ class GraphSettingsMenu extends React.Component {
<Navbar.Collapse>
<Navbar.Form pullLeft>
<FormGroup>
- <GraphSettings graphData={this.props.graphData}/>
+ <GraphSettings selectedNode={this.props.selectedNode} graphData={this.props.graphData} nodePropsLoader={this.props.nodePropsLoader} />
</FormGroup>{' '}
</Navbar.Form>
</Navbar.Collapse>
diff --git a/graphgraph-fe/src/requests.js b/graphgraph-fe/src/requests.js
index 9eaf3fe..a468d35 100644
--- a/graphgraph-fe/src/requests.js
+++ b/graphgraph-fe/src/requests.js
@@ -7,12 +7,12 @@ export function nodeNames (schema) {
return `http://localhost:8080/schemas/${schema}/nodes`
}
-export function basicGraph (schema, node, parentHops, cousinHops, childHops) {
- return `http://localhost:8080/schemas/${schema}/graph/basic?node=${node}&parentHops=${parentHops}&cousinHops=${cousinHops}&childHops=${childHops}`
+export function basicGraph (schema, node, parentHops, cousinHops, childHops, edgeFilter) {
+ return `http://localhost:8080/schemas/${schema}/graph/basic?node=${node}&parentHops=${parentHops}&cousinHops=${cousinHops}&childHops=${childHops}&edgeFilter=${edgeFilter}`
}
-export function pathGraph (schema, fromNode, toNode) {
- return `http://localhost:8080/schemas/${schema}/graph/paths?fromNode=${fromNode}&toNode=${toNode}`
+export function pathGraph (schema, fromNode, toNode, edgeFilter) {
+ return `http://localhost:8080/schemas/${schema}/graph/paths?fromNode=${fromNode}&toNode=${toNode}&edgeFilter=${edgeFilter}`
}
export function nodeProperty (schema, node) {