diff options
author | Pavel Paroulek <pavel.paroulek@orange.com> | 2019-04-23 15:27:28 +0200 |
---|---|---|
committer | Pavel Paroulek <pavel.paroulek@orange.com> | 2019-05-08 13:30:00 +0200 |
commit | 1722ddc1b782c79d881a11eb06df66c6e2ab6029 (patch) | |
tree | db8850cd3733b6301c90ebeef64b31b3ca8a3304 /graphgraph-fe/src | |
parent | c90203a311c4c214eb0ea74bd83461ab7ee17d94 (diff) |
Adding schema ingestor and backend
Adding backend for schema visualization, bugfixes, UI changes
Change-Id: I830c4e5566806f14ff609e8784cd1ed2f54ba4ac
Issue-ID: AAI-531
Signed-off-by: Pavel Paroulek <pavel.paroulek@orange.com>
Diffstat (limited to 'graphgraph-fe/src')
-rw-r--r-- | graphgraph-fe/src/App.css | 2 | ||||
-rw-r--r-- | graphgraph-fe/src/App.js | 9 | ||||
-rw-r--r-- | graphgraph-fe/src/Graph.js | 76 | ||||
-rw-r--r-- | graphgraph-fe/src/GraphHops.js | 6 | ||||
-rw-r--r-- | graphgraph-fe/src/GraphInfoMenu.css | 4 | ||||
-rw-r--r-- | graphgraph-fe/src/GraphInfoMenu.js | 26 | ||||
-rw-r--r-- | graphgraph-fe/src/GraphSettings.js | 81 | ||||
-rw-r--r-- | graphgraph-fe/src/GraphSettingsMenu.js | 2 | ||||
-rw-r--r-- | graphgraph-fe/src/requests.js | 8 |
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) { |