diff --git a/components/VFBMain.js b/components/VFBMain.js index 9ae19034f..09abb64ed 100644 --- a/components/VFBMain.js +++ b/components/VFBMain.js @@ -47,6 +47,7 @@ export default class VFBMain extends React.Component { idSelected: undefined }; + this.addVfbId = this.addVfbId.bind(this); this.menuHandler = this.menuHandler.bind(this); this.setWrapperRef = this.setWrapperRef.bind(this); this.htmlToolbarRef = this.htmlToolbarRef.bind(this); @@ -786,7 +787,8 @@ export default class VFBMain extends React.Component { id="treeWidget" instance={this.instanceOnFocus} size={{ height: _height, width: _width }} - ref={ref => this.treeBrowserReference = ref}/> + ref={ref => this.treeBrowserReference = ref} + selectionHandler={this.addVfbId}/> ); } } diff --git a/components/configuration/treeWidgetConfiguration.js b/components/configuration/treeWidgetConfiguration.js new file mode 100644 index 000000000..37a299831 --- /dev/null +++ b/components/configuration/treeWidgetConfiguration.js @@ -0,0 +1,22 @@ +var restPostConfig = { + url: "http://pdb.virtualflybrain.org/db/data/transaction/commit", + contentType: "application/json" +}; + +var treeCypherQuery = instance => ({ + "statements": [ + { + "statement": "MATCH (root:Class)<-[:INSTANCEOF]-(t:Individual {short_form:'" + instance + "'})" + + "<-[:depicts]-(tc:Individual)<-[ie:in_register_with]-(c:Individual)-[:depicts]->(image:" + + "Individual)-[r:INSTANCEOF]->(anat:Class) WHERE has(ie.index) WITH root, anat,r,image" + + " MATCH p=allShortestPaths((root)<-[:SUBCLASSOF|part_of*..]-(anat:Class)) RETURN p,r,image", + "resultDataContents": ["graph"] + } + ] +}); + + +module.exports = { + restPostConfig, + treeCypherQuery +}; \ No newline at end of file diff --git a/components/interface/TreeWidget.js b/components/interface/TreeWidget.js index 9957f0291..db0a6df48 100644 --- a/components/interface/TreeWidget.js +++ b/components/interface/TreeWidget.js @@ -1,13 +1,19 @@ /* eslint-disable no-prototype-builtins */ import React from 'react'; +import CompactColor from 'react-color'; import Tree from 'geppetto-client/js/components/interface/tree/Tree'; import CircularProgress from '@material-ui/core/CircularProgress'; import Tooltip from '@material-ui/core/Tooltip'; +import { + createMuiTheme, + MuiThemeProvider +} from "@material-ui/core/styles"; import 'react-sortable-tree/style.css'; var $ = require('jquery'); -var GEPPETTO = require('geppetto'); +const restPostConfig = require('../configuration/treeWidgetConfiguration').restPostConfig; +const treeCypherQuery = require('../configuration/treeWidgetConfiguration').treeCypherQuery; export default class TreeWidget extends React.Component { @@ -21,9 +27,11 @@ export default class TreeWidget extends React.Component { loading: false, nodes: undefined, nodeSelected: undefined, + displayColorPicker: false }; this.initTree = this.initTree.bind(this); + this.getNodes = this.getNodes.bind(this); this.sortData = this.sortData.bind(this); this.restPost = this.restPost.bind(this); this.nodeClick = this.nodeClick.bind(this); @@ -35,21 +43,25 @@ export default class TreeWidget extends React.Component { this.findChildren = this.findChildren.bind(this); this.searchChildren = this.searchChildren.bind(this); this.insertChildren = this.insertChildren.bind(this); + this.monitorMouseClick = this.monitorMouseClick.bind(this); this.defaultComparator = this.defaultComparator.bind(this); this.convertDataForTree = this.convertDataForTree.bind(this); - this.customSearchMethod = this.customSearchMethod.bind(this); this.parseGraphResultData = this.parseGraphResultData.bind(this); + this.theme = createMuiTheme({ overrides: { MuiTooltip: { tooltip: { fontSize: "12px" } } } }); this.AUTHORIZATION = "Basic " + btoa("neo4j:vfb"); this.styles = { left_second_column: 395, column_width_small: 385, column_width_viewer: "calc(100% - 385px)", - row_height: 40, + row_height: 25, top: 0, height: this.props.size.height, width: this.props.size.width }; + + this.colorPickerNode = undefined; + this.colorPickerContainer = undefined; } isNumber (variable) { @@ -68,8 +80,8 @@ export default class TreeWidget extends React.Component { request.setRequestHeader("Authorization", this.AUTHORIZATION); } }, - url: "http://pdb.virtualflybrain.org/db/data/transaction/commit", - contentType: "application/json", + url: restPostConfig.url, + contentType: restPostConfig.contentType, data: strData }); } @@ -87,23 +99,23 @@ export default class TreeWidget extends React.Component { sortData (unsortedArray, key, comparator) { // Create a sortable array to return. const sortedArray = [ ...unsortedArray ]; - + // Recursively sort sub-arrays. const recursiveSort = (start, end) => { - + // If this sub-array is empty, it's sorted. if (end - start < 1) { return; } - + const pivotValue = sortedArray[end]; let splitIndex = start; for (let i = start; i < end; i++) { const sort = comparator(sortedArray[i], pivotValue, key); - + // This value is less than the pivot value. if (sort === -1) { - + /* * If the element just to the right of the split index, * isn't this element, swap them. @@ -113,29 +125,29 @@ export default class TreeWidget extends React.Component { sortedArray[splitIndex] = sortedArray[i]; sortedArray[i] = temp; } - + /* * Move the split index to the right by one, * denoting an increase in the less-than sub-array size. */ splitIndex++; } - + /* * Leave values that are greater than or equal to * the pivot value where they are. */ } - + // Move the pivot value to between the split. sortedArray[end] = sortedArray[splitIndex]; sortedArray[splitIndex] = pivotValue; - + // Recursively sort the less-than and greater-than arrays. recursiveSort(start, splitIndex - 1); recursiveSort(splitIndex + 1, end); }; - + // Sort the entire array. recursiveSort(0, unsortedArray.length - 1); return sortedArray; @@ -263,17 +275,32 @@ export default class TreeWidget extends React.Component { nodesList.push(edges[childrenList[i]].to) } var uniqNodes = [...new Set(nodesList)]; + + for (var j = uniqNodes.length - 1; j >= 0 ; j--) { + var node = nodes[this.findChildren({ id: uniqNodes[j] }, "id", nodes)[0]]; + if (node.instanceId.indexOf("VFB_") > -1) { + child.instanceId = node.instanceId; + // child.subtitle = child.subtitle + " " + node.instanceId; + uniqNodes.splice(j, 1); + } + } + for ( var j = 0; j < uniqNodes.length; j++) { var node = nodes[this.findChildren({ id: uniqNodes[j] }, "id", nodes)[0]]; - child.children.push({ - title: node.title, - subtitle: node.instanceId, - description: node.instanceId + " \n- " + node.info, - instanceId: node.instanceId, - id: node.id, - children: [] - }); - this.insertChildren(nodes, edges, child.children[j]) + if (node.instanceId.indexOf("VFB_") > -1) { + console.log("child.instanceId contains " + child.instanceId + "but I am overwriting it with " + node.instanceId); + child.instanceId = node.instanceId; + } else { + child.children.push({ + title: node.title, + subtitle: node.instanceId, + description: node.info, + instanceId: node.instanceId, + id: node.id, + children: [] + }); + this.insertChildren(nodes, edges, child.children[j]) + } } } @@ -284,10 +311,9 @@ export default class TreeWidget extends React.Component { refinedDataTree.push({ title: nodes[i].title, subtitle: nodes[i].instanceId, - description: "- " + nodes[i].instanceId + " \n- " + nodes[i].info, + description: nodes[i].info, instanceId: nodes[i].instanceId, id: nodes[i].id, - isSelected: true, children: [] }); break; @@ -300,9 +326,6 @@ export default class TreeWidget extends React.Component { selectNode (instance) { if (this.state.nodeSelected !== undefined && this.state.nodeSelected.instanceId !== instance.instanceId) { - let oldNode = this.state.nodeSelected; - oldNode.isSelected = false; - instance.isSelected = true; this.setState({ nodeSelected: instance, dataTree: this.state.dataTree }); } } @@ -315,7 +338,7 @@ export default class TreeWidget extends React.Component { innerInstance = instance; } - if (innerInstance.id !== this.state.instance.id) { + if (this.state.instance !== undefined && innerInstance.id !== this.state.instance.id) { if (innerInstance.id === window.templateID) { this.selectNode(this.state.dataTree[0]) return; @@ -338,24 +361,11 @@ export default class TreeWidget extends React.Component { initTree (instance) { this.setState({ loading: true }); - this.restPost({ - "statements": [ - { - "statement": "MATCH (root:Class)<-[:INSTANCEOF]-(t:Individual { short_form : '" + instance + "'})" - + "<-[:depicts]-(tc:Individual)<-[ie:in_register_with]-(c:Individual)-[:depicts]->" - + "(image:Individual)-[:INSTANCEOF]->(ac:Class) WHERE has(ie.index) WITH root, COLLECT" - + " (ac.short_form) as tree_nodes, COLLECT (DISTINCT{ image: image.short_form, anat_ind:" - + " image.short_form, type: ac.short_form}) AS domain_map MATCH p=allShortestPaths((root)" - + "<-[:SUBCLASSOF|part_of*..]-(anat:Class)) WHERE anat.short_form IN tree_nodes RETURN p," - + " domain_map", - "resultDataContents": ["graph"] - } - ] - }).done(data => { + this.restPost(treeCypherQuery(instance)).done(data => { // If I need to edit the data I can call this here and then assign it to this.state.dataTree if (data.results[0].data.length > 0) { var dataTree = this.parseGraphResultData(data); - var vertix = data.results[0].data[0].graph.nodes[0].id; + var vertix = this.findRoot(data.results[0].data[0].graph.nodes); var nodes = this.sortData(this.convertNodes(dataTree.nodes), "id", this.defaultComparator); var edges = this.sortData(this.convertEdges(dataTree.edges), "from", this.defaultComparator); var treeData = this.convertDataForTree(nodes, edges, vertix); @@ -365,7 +375,11 @@ export default class TreeWidget extends React.Component { root: vertix, loading: false, nodes: nodes, - nodeSelected: treeData[0] + nodeSelected: (this.props.instance === undefined + ? treeData[0] + : (this.props.instance.getParent() === null + ? { subtitle: this.props.instance.getId() } + : { subtitle: this.props.instance.getParent().getId() })) }); } else { var treeData = [{ @@ -383,53 +397,137 @@ export default class TreeWidget extends React.Component { }); } + findRoot (nodes) { + let min = nodes[0].id; + for ( let i = 1; i < nodes.length; i++) { + if (nodes[i].id < min) { + min = nodes[i].id; + } + } + return min; + } + componentWillMount () { if (window.templateID !== undefined) { this.initTree(window.templateID); } } + componentWillUnmount () { + document.removeEventListener('mousedown', this.monitorMouseClick, false); + } + nodeClick (event, rowInfo) { - console.log("clicked on the tree node"); - console.log(rowInfo); this.selectNode(rowInfo.node); } + monitorMouseClick (e) { + if (this.colorPickerContainer !== undefined && this.colorPickerContainer !== null && this.colorPickerContainer.contains(e.target)) { + return; + } else { + this.colorPickerContainer = undefined; + this.setState({ displayColorPicker: false }); + } + } + getButtons (rowInfo) { var buttons = []; - if (rowInfo.node.title !== "No data available.") { - buttons.push( -