From 9845db2d07c503ff4c6fba66c46afc54f847c881 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Fri, 16 Apr 2021 11:31:46 +0100 Subject: [PATCH 01/27] swapping everything to paths --- .../circuitBrowserConfiguration.js | 8 +++---- .../interface/VFBCircuitBrowser/Controls.js | 22 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js index 1b2ba5e29..f5417aa1f 100644 --- a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js +++ b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js @@ -1,4 +1,4 @@ -var locationCypherQuery = ( instances, hops, weight ) => ({ +var locationCypherQuery = ( instances, paths, weight ) => ({ "statements": [ { "statement" : "WITH [" + instances + "] AS neurons" @@ -10,7 +10,7 @@ var locationCypherQuery = ( instances, hops, weight ) => ({ + weight.toString() + " RETURN id(a) AS source, id(b) AS target, type(r) as type, 5000-r.weight[0] as weight_p'," + " sourceNode: id(source)," + " targetNode: id(target)," - + " k: " + hops.toString() + "," + + " k: " + pathss.toString() + "," + " relationshipWeightProperty: 'weight_p'," + " relationshipTypes: ['*']," + " path: true" @@ -41,9 +41,9 @@ var configuration = { "tooltip" : "label" } }, - // Minimum amount of hops allowed + // Minimum amount of paths allowed minHops : 1, - // Maximum amount of hops allowed + // Maximum amount of paths allowed maxHops : 6, // Minimum amount of neurons allowed minNeurons : 2, diff --git a/components/interface/VFBCircuitBrowser/Controls.js b/components/interface/VFBCircuitBrowser/Controls.js index 6a212b5b1..6bd868224 100644 --- a/components/interface/VFBCircuitBrowser/Controls.js +++ b/components/interface/VFBCircuitBrowser/Controls.js @@ -106,11 +106,11 @@ const searchConfiguration = require('./../../configuration/VFBMain/searchConfigu const datasourceConfiguration = require('./../../configuration/VFBMain/searchConfiguration').datasourceConfiguration; /** - * Create custom marks for Hops slider. - * Only show the label for the minimum and maximum hop, hide the rest + * Create custom marks for Paths slider. + * Only show the label for the minimum and maximum paths, hide the rest */ const customMarks = () => { - let marks = new Array(configuration.maxHops); + let marks = new Array(configuration.maxPaths); for ( var i = 0; i < marks.length; i++ ) { if ( i == 0 || i == marks.length - 1 ) { marks[i] = { value : i + 1, label : (i + 1).toString() }; @@ -192,7 +192,7 @@ class Controls extends Component { expanded : true }; this.weight = this.props.weight; - this.hops = this.props.hops; + this.paths = this.props.paths; this.addNeuron = this.addNeuron.bind(this); this.neuronTextfieldModified = this.neuronTextfieldModified.bind(this); this.typingTimeout = this.typingTimeout.bind(this); @@ -330,10 +330,10 @@ class Controls extends Component { } /** - * Hops slider has been dragged, value has changed + * Paths slider has been dragged, value has changed */ sliderChange (event, value ) { - this.hops = value; + this.paths = value; } weightChange (event ) { @@ -476,18 +476,18 @@ class Controls extends Component { - Hops + Paths @@ -504,7 +504,7 @@ class Controls extends Component { variant="contained" size="small" id="refreshCircuitBrowser" - onClick={() => this.props.updateGraph(this.neuronFields, this.hops, this.weight)} + onClick={() => this.props.updateGraph(this.neuronFields, this.paths, this.weight)} >Refresh Graph From dc1f254de4a2c5a26eaf230903e5172cb600cc13 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Mon, 26 Apr 2021 11:43:37 +0100 Subject: [PATCH 02/27] typo fix --- .../VFBCircuitBrowser/circuitBrowserConfiguration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js index f5417aa1f..0f1bcc332 100644 --- a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js +++ b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js @@ -10,7 +10,7 @@ var locationCypherQuery = ( instances, paths, weight ) => ({ + weight.toString() + " RETURN id(a) AS source, id(b) AS target, type(r) as type, 5000-r.weight[0] as weight_p'," + " sourceNode: id(source)," + " targetNode: id(target)," - + " k: " + pathss.toString() + "," + + " k: " + paths.toString() + "," + " relationshipWeightProperty: 'weight_p'," + " relationshipTypes: ['*']," + " path: true" From d93d8af7f90e09db9575539bf24170f218195418 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Mon, 26 Apr 2021 12:40:54 +0100 Subject: [PATCH 03/27] Swapping over Hops to Paths --- .../VFBCircuitBrowser/VFBCircuitBrowser.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js index ff59f498b..12ceec772 100644 --- a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js +++ b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js @@ -57,7 +57,7 @@ class VFBCircuitBrowser extends Component { queryLoaded : false, dropDownAnchorEl : null, neurons : [{ id : "", label : "" } , { id : "", label : "" }], - hops : Math.ceil((configuration.maxHops - configuration.minHops) / 2), + paths : Math.ceil((configuration.maxPaths - configuration.minPaths) / 2), weight : 70, reload : false } @@ -68,7 +68,7 @@ class VFBCircuitBrowser extends Component { this.zoomIn = this.zoomIn.bind(this); this.zoomOut = this.zoomOut.bind(this); this.queriesUpdated = this.queriesUpdated.bind(this); - this.updateHops = this.updateHops.bind(this); + this.updatePaths = this.updatePaths.bind(this); this.updateWeight = this.updateWeight.bind(this); this.resize = this.resize.bind(this); @@ -88,7 +88,7 @@ class VFBCircuitBrowser extends Component { componentDidMount () { let self = this; this.__isMounted = true; - this.updateGraph(this.state.neurons , Math.ceil((configuration.maxHops - configuration.minHops) / 2), this.state.weight); + this.updateGraph(this.state.neurons , Math.ceil((configuration.maxPaths - configuration.minPaths) / 2), this.state.weight); const { circuitQuerySelected } = this.props; this.circuitQuerySelected = circuitQuerySelected; } @@ -126,15 +126,15 @@ class VFBCircuitBrowser extends Component { // Request graph update if the list of new neurons is not the same if ( !this.state.loading && !matched ) { - this.updateGraph(neurons, this.state.hops, this.state.weight); + this.updateGraph(neurons, this.state.paths, this.state.weight); } } /** - * Hops in controls component have been updated, request new graph with updated amount of hops + * Paths in controls component have been updated, request new graph with updated amount of paths */ - updateHops (hops) { - this.setState({ hops : hops }); + updatePaths (paths) { + this.setState({ paths : paths }); } updateWeight (weight) { @@ -186,12 +186,12 @@ class VFBCircuitBrowser extends Component { /** * Re-render graph with a new instance */ - updateGraph (neurons, hops, weight) { + updateGraph (neurons, paths, weight) { if (this.__isMounted){ // Show loading spinner while cypher query search occurs - this.setState({ loading : true , neurons : neurons ? neurons : this.state.neurons, hops : hops ? hops : this.state.hops, weight : weight ? weight : this.state.weight, queryLoaded : false }); + this.setState({ loading : true , neurons : neurons ? neurons : this.state.neurons, paths : paths ? paths : this.state.paths, weight : weight ? weight : this.state.weight, queryLoaded : false }); // Perform cypher query. TODO: Remove hardcoded weight once edge weight is implemented - this.queryResults(cypherQuery(neurons ? neurons.map(a => `'${a.id}'`).join(",") : this.state.neurons, hops ? hops : this.state.hops, weight ? weight : this.state.weight)); + this.queryResults(cypherQuery(neurons ? neurons.map(a => `'${a.id}'`).join(",") : this.state.neurons, paths ? paths : this.state.paths, weight ? weight : this.state.weight)); } } @@ -296,11 +296,11 @@ class VFBCircuitBrowser extends Component {

{errorMessage}

this.state.graph.nodes.length > 0 } resetCamera={self.resetCamera} @@ -515,11 +515,11 @@ class VFBCircuitBrowser extends Component { controls = { this.state.graph.nodes.length > 0 } resetCamera={self.resetCamera} From 1e10e42507921427480802de4fd5a5c25f9d3bb0 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Sun, 2 May 2021 11:49:04 +0100 Subject: [PATCH 04/27] testing for meta rather than parent on selection. --- components/interface/VFBTermInfo/VFBTermInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/interface/VFBTermInfo/VFBTermInfo.js b/components/interface/VFBTermInfo/VFBTermInfo.js index 98c35936c..9e07d4405 100644 --- a/components/interface/VFBTermInfo/VFBTermInfo.js +++ b/components/interface/VFBTermInfo/VFBTermInfo.js @@ -732,12 +732,12 @@ class VFBTermInfoWidget extends React.Component { return; } var Query = require('@geppettoengine/geppetto-core/model/Query'); - var n = window[path]; var otherId; var otherName; var target = widget; var that = this; var meta = path + "." + path + "_meta"; + var n = window[meta]; if (n != undefined) { var metanode = Instances.getInstance(meta); if ((this.data.length > 0) && (this.data[0] == metanode)) { From 0ba856da3ef7a9ec25d26f5b1c42350a043f4dab Mon Sep 17 00:00:00 2001 From: Rob Court Date: Sun, 2 May 2021 13:08:55 +0100 Subject: [PATCH 05/27] Revert "updating help menu size" This reverts commit 04083920cb10c50f51dfc6fd8e97562ca74693d5. --- tests/jest/vfb/batch1/menu-component-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/jest/vfb/batch1/menu-component-tests.js b/tests/jest/vfb/batch1/menu-component-tests.js index 0c256e321..9f65e6a63 100644 --- a/tests/jest/vfb/batch1/menu-component-tests.js +++ b/tests/jest/vfb/batch1/menu-component-tests.js @@ -105,7 +105,7 @@ describe('VFB Menu Component Tests', () => { await wait4selector(page, "ul.MuiList-root", { visible: true, timeout : 120000 }) // Check there's four elements in the drop down menu of 'Help' const dropDownMenuItems = await page.evaluate(async () => document.getElementsByClassName("MuiListItem-root").length); - expect(dropDownMenuItems).toEqual(5); + expect(dropDownMenuItems).toEqual(4); }) it('Help Modal FAQ Tab Opened', async () => { From f5dd7a85c9ed4b5bf1f01c757304c69126fcdf7f Mon Sep 17 00:00:00 2001 From: Rob Court Date: Sun, 2 May 2021 13:09:06 +0100 Subject: [PATCH 06/27] Revert "changing image" This reverts commit de696518d8c0da02bc24c4dc6427833050f62e61. --- components/configuration/VFBOverview/quickHelp.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/configuration/VFBOverview/quickHelp.json b/components/configuration/VFBOverview/quickHelp.json index 189bf6731..57b2aeafc 100644 --- a/components/configuration/VFBOverview/quickHelp.json +++ b/components/configuration/VFBOverview/quickHelp.json @@ -4,7 +4,7 @@ "width": "900", "steps": [{ "title": "Welcome to Virtual Fly Brain", - "image" : "https://VirtualFlyBrain.org/data/VFB/screencaps/VFB_3D_400x400.jpg", + "image" : "geppetto/build/VFBqHelpMain.jpg", "instructions": [{ "icon" : "fa fa-question-circle-o", "label" : "Hi, we are currently running a VFB User Survey, once you have used the site please consider completing this survey to help us improve it by clicking User Survey in the Help menu above." From ffff90e74361afceb6b43f166566ba5f812d4611 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Sun, 2 May 2021 13:09:11 +0100 Subject: [PATCH 07/27] Revert "adding user survey" This reverts commit 9a1c2df4f221eca3d8ead8d48e55c7157c2f79af. --- .../VFBToolbar/vfbtoolbarMenuConfiguration.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js b/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js index 052786ed2..0412122a8 100644 --- a/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js +++ b/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js @@ -624,14 +624,6 @@ var toolbarMenu = { position: "bottom-start", list: [ { - label: "User Survey", - icon: "", - trailerIcon: "fa fa-external-link", - action: { - handlerAction: "openNewTab", - parameters: ["https://www.surveymonkey.co.uk/r/5HDZZRR"] - } - },{ label: "F.A.Q.", icon: "", trailerIcon: "fa fa-external-link", From a19d93684862e9a089382da18e97a48092adfbd7 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Sun, 2 May 2021 13:10:31 +0100 Subject: [PATCH 08/27] Revert "typo fix" This reverts commit 50db76e40d63ca32b8e6e849fbd08c56c5bf0862. --- components/configuration/VFBOverview/quickHelp.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/configuration/VFBOverview/quickHelp.json b/components/configuration/VFBOverview/quickHelp.json index 57b2aeafc..b79594539 100644 --- a/components/configuration/VFBOverview/quickHelp.json +++ b/components/configuration/VFBOverview/quickHelp.json @@ -10,7 +10,7 @@ "label" : "Hi, we are currently running a VFB User Survey, once you have used the site please consider completing this survey to help us improve it by clicking User Survey in the Help menu above." },{ "icon" : "fa fa-fast-forward", - "label" : "Just click NEXT below for handy tips on using VFB or SKIP INTRO to close this and jump right into VFB." + "label" : "Just click NEXT for handy tips on using VFB or SKIP INTO to close this and jump right into VFB." }] },{ "title": "Welcome to Virtual Fly Brain", From 394d7bca72aa0827bc4ef51599d4d7ee7f0b3c1a Mon Sep 17 00:00:00 2001 From: Rob Court Date: Sun, 2 May 2021 13:10:36 +0100 Subject: [PATCH 09/27] Revert "adding survey text" This reverts commit ebe39aefc9b7326c0ee86e00afb1f282269535cb. --- components/configuration/VFBOverview/quickHelp.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/components/configuration/VFBOverview/quickHelp.json b/components/configuration/VFBOverview/quickHelp.json index b79594539..75d0ade4d 100644 --- a/components/configuration/VFBOverview/quickHelp.json +++ b/components/configuration/VFBOverview/quickHelp.json @@ -3,16 +3,6 @@ "height": "575", "width": "900", "steps": [{ - "title": "Welcome to Virtual Fly Brain", - "image" : "geppetto/build/VFBqHelpMain.jpg", - "instructions": [{ - "icon" : "fa fa-question-circle-o", - "label" : "Hi, we are currently running a VFB User Survey, once you have used the site please consider completing this survey to help us improve it by clicking User Survey in the Help menu above." - },{ - "icon" : "fa fa-fast-forward", - "label" : "Just click NEXT for handy tips on using VFB or SKIP INTO to close this and jump right into VFB." - }] - },{ "title": "Welcome to Virtual Fly Brain", "image" : "geppetto/build/VFBqHelpMain.jpg", "instructions": [{ From 8509162b645addbbb41b4b90cb5e57480b1b2cff Mon Sep 17 00:00:00 2001 From: Rob Court Date: Sun, 2 May 2021 20:32:20 +0100 Subject: [PATCH 10/27] reverting to fixed query menus --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ab9df7a4e..aadfe94ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ARG geppettoDatasourceRelease=vfb_20200604_a ARG geppettoModelSwcRelease=v1.0.1 ARG geppettoFrontendRelease=development ARG geppettoClientRelease=VFBv2.2.0.7 -ARG ukAcVfbGeppettoRelease=pipeline2 +ARG ukAcVfbGeppettoRelease=2021-03-06 ARG mvnOpt="-Dhttps.protocols=TLSv1.2 -DskipTests --quiet -Pmaster" From af3b56f6e4858b24bc38f1845365d203a68e4c53 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Mon, 3 May 2021 08:36:40 +0100 Subject: [PATCH 11/27] swapping to neo NBLAST to GAL4 --- model/vfb.xmi | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index 2b95dbe72..423bd962d 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -150,6 +150,9 @@ + + + + + - - - + + + From 63e26396efcb6303ca939eea3dd20359352a4484 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Mon, 3 May 2021 11:11:56 +0100 Subject: [PATCH 12/27] enabling collapsable queries --- Dockerfile | 2 +- css/VFBTermInfo.less | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index aadfe94ae..81868d22f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ARG geppettoDatasourceRelease=vfb_20200604_a ARG geppettoModelSwcRelease=v1.0.1 ARG geppettoFrontendRelease=development ARG geppettoClientRelease=VFBv2.2.0.7 -ARG ukAcVfbGeppettoRelease=2021-03-06 +ARG ukAcVfbGeppettoRelease=2021-05-03 ARG mvnOpt="-Dhttps.protocols=TLSv1.2 -DskipTests --quiet -Pmaster" diff --git a/css/VFBTermInfo.less b/css/VFBTermInfo.less index 0d7e199ce..cf2356afb 100644 --- a/css/VFBTermInfo.less +++ b/css/VFBTermInfo.less @@ -934,3 +934,19 @@ transition: all 0.50s; } } + +details > summary > i.fa-chevron-circle-right { + display: inline-block; +} + +details > summary > i.fa-chevron-circle-down { + display: none; +} + +details[open] > summary > i.fa-chevron-circle-down { + display: inline-block; +} + +details[open] > summary > i.fa-chevron-circle-right { + display: none; +} \ No newline at end of file From 9515c964386609a615e28c7a2d094b91b3b80a01 Mon Sep 17 00:00:00 2001 From: Rob Court Date: Mon, 3 May 2021 11:34:01 +0100 Subject: [PATCH 13/27] update query for links to Ind Exp --- model/vfb.xmi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/model/vfb.xmi b/model/vfb.xmi index 423bd962d..f61b7ac31 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -553,8 +553,8 @@ name="NBLAST_anat_image_query" description="find has_similar_morphology_to relationships" returnType="//@libraries.3/@types.1" - query=""statement": "MATCH (n:Individual)-[nblast:has_similar_morphology_to_part_of]-(c:Class) WHERE n.short_form in [$id] WITH c, nblast OPTIONAL MATCH (c:Class)<-[:INSTANCEOF|SUBCLASSOF*..]-(primary:Individual)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, primary, nblast OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE collect ({ channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }}) END AS channel_image,primary, nblast OPTIONAL MATCH (primary)-[:INSTANCEOF]->(typ:Class) WITH CASE WHEN typ is null THEN [] ELSE collect ({ short_form: typ.short_form, label: coalesce(typ.label,''), iri: typ.iri, types: labels(typ), symbol: coalesce(typ.symbol[0], '')} ) END AS types,primary,channel_image, nblast RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, nblast.NBLAST_score[0] as score, 'm20210406' AS version, 'NBLASTexp_anat_image_query' AS query, channel_image, types", "parameters" : { "id" : "$ID" }" - countQuery=""statement": "MATCH (n:Individual)-[nblast:has_similar_morphology_to_part_of]-(primary:Class) WHERE n.short_form in [$id] RETURN count(primary) AS count", "parameters" : { "id" : "$ID" }"/> + query=""statement": "MATCH (n:Neuron)-[nblast:has_similar_morphology_to_part_of]->(primary:Expression_pattern) WHERE n.short_form in [$id] WITH primary, nblast OPTIONAL MATCH (c:Class)<-[:INSTANCEOF]-(primary) OPTIONAL MATCH (primary)<-[:depicts]-(channel:Individual)-[irw:in_register_with]->(template:Individual)-[:depicts]->(template_anat:Individual) WITH template, channel, template_anat, irw, primary, nblast OPTIONAL MATCH (channel)-[:is_specified_output_of]->(technique:Class) WITH CASE WHEN channel IS NULL THEN [] ELSE collect ({ channel: { short_form: channel.short_form, label: coalesce(channel.label,''), iri: channel.iri, types: labels(channel), symbol: coalesce(channel.symbol[0], '')} , imaging_technique: { short_form: technique.short_form, label: coalesce(technique.label,''), iri: technique.iri, types: labels(technique), symbol: coalesce(technique.symbol[0], '')} ,image: { template_channel : { short_form: template.short_form, label: coalesce(template.label,''), iri: template.iri, types: labels(template), symbol: coalesce(template.symbol[0], '')} , template_anatomy: { short_form: template_anat.short_form, label: coalesce(template_anat.label,''), iri: template_anat.iri, types: labels(template_anat), symbol: coalesce(template_anat.symbol[0], '')} ,image_folder: COALESCE(irw.folder[0], ''), index: coalesce(apoc.convert.toInteger(irw.index[0]), []) + [] }}) END AS channel_image,primary, nblast OPTIONAL MATCH (primary)-[:INSTANCEOF]->(typ:Class) WITH CASE WHEN typ is null THEN [] ELSE collect ({ short_form: typ.short_form, label: coalesce(typ.label,''), iri: typ.iri, types: labels(typ), symbol: coalesce(typ.symbol[0], '')} ) END AS types,primary,channel_image, nblast RETURN { core : { short_form: primary.short_form, label: coalesce(primary.label,''), iri: primary.iri, types: labels(primary), symbol: coalesce(primary.symbol[0], '')} , description : coalesce(primary.description, []), comment : coalesce(primary.comment, []) } AS term, nblast.NBLAST_score[0] as score, 'm20210503' AS version, 'NBLASTexp_anat_image_query' AS query, channel_image, types", "parameters" : { "id" : "$ID" }" + countQuery=""statement": "MATCH (n:Individual)-[nblast:has_similar_morphology_to_part_of]->(primary:Individual) WHERE n.short_form in [$id] RETURN count(primary) AS count", "parameters" : { "id" : "$ID" }"/> Date: Mon, 3 May 2021 15:13:51 +0100 Subject: [PATCH 14/27] swapping to latest --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 81868d22f..ab9df7a4e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ARG geppettoDatasourceRelease=vfb_20200604_a ARG geppettoModelSwcRelease=v1.0.1 ARG geppettoFrontendRelease=development ARG geppettoClientRelease=VFBv2.2.0.7 -ARG ukAcVfbGeppettoRelease=2021-05-03 +ARG ukAcVfbGeppettoRelease=pipeline2 ARG mvnOpt="-Dhttps.protocols=TLSv1.2 -DskipTests --quiet -Pmaster" From 3e490a86ddb1f4328315c243075f40e30babf918 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Mon, 10 May 2021 13:04:41 -0700 Subject: [PATCH 15/27] #1114 Arrange nodes by levels, levels calculated using relationshipY:maxHops --- .../VFBCircuitBrowser/QueryParser.js | 40 +++++++++ .../VFBCircuitBrowser/VFBCircuitBrowser.js | 87 ++++--------------- 2 files changed, 56 insertions(+), 71 deletions(-) diff --git a/components/interface/VFBCircuitBrowser/QueryParser.js b/components/interface/VFBCircuitBrowser/QueryParser.js index 27c5fb74b..56aa37ba7 100644 --- a/components/interface/VFBCircuitBrowser/QueryParser.js +++ b/components/interface/VFBCircuitBrowser/QueryParser.js @@ -4,12 +4,34 @@ export function queryParser (e) { let graphData = e.data.params.results; console.log("Results ", e); + // Reads graph data let data = graphData.results[0].data; + // Read source and target node ids + let sourceNode = data[0]?.row[4]; + let targetNode = data[0]?.row[5]; + // Read relationship max hops + let relationshipsHops = data[0]?.row[7]; + + // The nodes and links arrays used for the graph let nodes = [], links = []; + // Keeps track of links let linksMap = new Map(); + // Keeps track of reverse links let reverseMap = new Map(); + // Keeps track of nodes in map let nodesMap = new Map(); + + // Colors for labels let presentColorLabels = new Array(); + // maps of links with their max hop + let linksMaxHops = {}; + let levels = {}; + + relationshipsHops?.forEach( rel => { + let split = rel.split(":"); + // Map link id to highest hop + linksMaxHops[split[0]] = parseInt(split[1]) + 1; + }) // Creates links map from Relationships, avoid duplicates data.forEach(({ graph, row }) => { @@ -39,6 +61,8 @@ export function queryParser (e) { } reverseMap.get(startNode).push( { target : endNode, label : properties[e.data.params.configuration.resultsMapping.link.label], weight : properties[e.data.params.configuration.resultsMapping.link.weight] }); } + + linksMaxHops[id] ? levels[endNode] = linksMaxHops[id] : null; }); }); @@ -66,14 +90,30 @@ export function queryParser (e) { } let n = null; if (nodesMap.get(id) === undefined) { + let level = 1; + let positionX = 0; + + if ( id == targetNode ){ + level = 0; + positionX = 300; + } else if ( id == sourceNode ) { + level = 0; + positionX = -300; + } else { + level = levels[id]; + } + n = { path : label, id : parseInt(id), title : title, + level : level, + positionX : positionX, width : e.data.params.NODE_WIDTH, height : e.data.params.NODE_HEIGHT, color : color, }; + nodesMap.set(id, n); nodes.push(n); } diff --git a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js index a9f7c3ce1..712217735 100644 --- a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js +++ b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js @@ -238,8 +238,15 @@ class VFBCircuitBrowser extends Component { } }; + let params = { + results: response.data, + configuration : configuration, + styling : stylingConfiguration, + NODE_WIDTH : NODE_WIDTH, NODE_HEIGHT : NODE_HEIGHT + } + // Invoke web worker to perform conversion of graph data into format - worker.postMessage({ message: "refine", params: { results: response.data, configuration : configuration, styling : stylingConfiguration, NODE_WIDTH : NODE_WIDTH, NODE_HEIGHT : NODE_HEIGHT } }); + worker.postMessage({ message: "refine", params: params }); }) .catch( function (error) { self.setState( { loading : false } ); @@ -322,78 +329,13 @@ class VFBCircuitBrowser extends Component { linkLabel={link => link.label} // Width of links, log(weight) linkWidth={link => link.weight ? Math.log(link.weight) : 1 } - linkCurvature='curvature' - linkDirectionalArrowLength={link => link.weight ? Math.log(link.weight) * 3 : .5} + linkCurvature={.075} + linkDirectionalArrowLength={link => link.weight ? Math.log(link.weight) * 5 : 4} linkDirectionalArrowRelPos={.75} - linkCanvasObject={(link, ctx) => { - const MAX_FONT_SIZE = 5; - const LABEL_NODE_MARGIN = 1 * 1.5; - - const start = link.source; - const end = link.target; - - // ignore unbound links - if (typeof start !== 'object' || typeof end !== 'object') { - return; - } - - // calculate label positioning - let textPos = Object.assign({},...['x', 'y'].map(c => ({ [c]: start[c] + (end[c] - start[c]) / 2 }))); - - - if (link?.curvature && link?.__controlPoints ) { - // Get mid point of link, save as position of weight label text - textPos = this.getQuadraticXY( - .5, - start.x, - start.y, - link?.__controlPoints[0], - link?.__controlPoints[1], - end.x, - end.y - ); - } - const relLink = { x: end.x - start.x, y: end.y - start.y }; - - const maxTextLength = Math.sqrt(Math.pow(relLink.x, 2) + Math.pow(relLink.y, 2)) - LABEL_NODE_MARGIN * 2; - - let textAngle = Math.atan2(relLink.y, relLink.x); - // maintain label vertical orientation for legibility - if (textAngle > Math.PI / 2) { - textAngle = -(Math.PI - textAngle); - } - if (textAngle < -Math.PI / 2) { - textAngle = -(-Math.PI - textAngle); - } - - const label = link.weightLabel; - - // estimate fontSize to fit in link length - ctx.font = '1px Sans-Serif'; - const fontSize = Math.min(MAX_FONT_SIZE, maxTextLength / ctx.measureText(label).width); - ctx.font = `${fontSize}px Sans-Serif`; - const textWidth = ctx.measureText(label).width; - const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding - - // draw text label (with background rect) - ctx.save(); - ctx.translate(textPos.x,textPos.y); - ctx.rotate(textAngle); - // draw black rectangle - ctx.fillStyle = 'rgba(0, 0, 0, 1)'; - ctx.fillRect(- bckgDimensions[0] / 2, - bckgDimensions[1] / 2, ...bckgDimensions); - // draw weight label text - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillStyle = stylingConfiguration.defaultLinkColor; - ctx.setLineDash([5, 5]); - ctx.fillText(label, 0, 0); - ctx.restore(); - }} // Node label, used in tooltip when hovering over Node linkCanvasObjectMode={() => "after"} linkCanvasObject={(link, ctx) => { - const MAX_FONT_SIZE = 5; + const MAX_FONT_SIZE = 10; const LABEL_NODE_MARGIN = 1 * 1.5; const start = link.source; @@ -434,7 +376,6 @@ class VFBCircuitBrowser extends Component { const label = link.weightLabel; // estimate fontSize to fit in link length - ctx.font = '1px Sans-Serif'; const fontSize = Math.min(MAX_FONT_SIZE, maxTextLength / ctx.measureText(label).width); ctx.font = `${fontSize}px Sans-Serif`; const textWidth = ctx.measureText(label).width; @@ -501,7 +442,11 @@ class VFBCircuitBrowser extends Component { nodeCanvasObjectMode={node => 'replace'} // bu = Bottom Up, creates Graph with root at bottom dagMode="lr" - dagLevelDistance = {100} + nodeVal = { node => { + node.fx = node.level == 0 ? node.positionX : node.fx ? node.fx : 0 ; + node.fy = node.level > 0 ? -100 * node.level : node.fy ? node.fy : 0 ; + }} + dagLevelDistance = {25} onDagError={loopNodeIds => {}} // Handles clicking event on an individual node onNodeClick = { (node,event) => this.handleNodeLeftClick(node,event) } From 865ee516607c5691f341dc158ea042ff824c8c46 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Thu, 13 May 2021 14:53:23 -0700 Subject: [PATCH 16/27] #1114 circuit browser layout improvements --- .../VFBCircuitBrowser/QueryParser.js | 133 ++++++++++++------ .../VFBCircuitBrowser/VFBCircuitBrowser.js | 3 +- 2 files changed, 89 insertions(+), 47 deletions(-) diff --git a/components/interface/VFBCircuitBrowser/QueryParser.js b/components/interface/VFBCircuitBrowser/QueryParser.js index 56aa37ba7..ea4e10ee3 100644 --- a/components/interface/VFBCircuitBrowser/QueryParser.js +++ b/components/interface/VFBCircuitBrowser/QueryParser.js @@ -2,13 +2,14 @@ * Converts graph data received from cypher query into a readable format for react-force-graph-2d */ export function queryParser (e) { + var start = Date.now(); let graphData = e.data.params.results; console.log("Results ", e); // Reads graph data let data = graphData.results[0].data; // Read source and target node ids - let sourceNode = data[0]?.row[4]; - let targetNode = data[0]?.row[5]; + let sourceNodeID = data[0]?.row[4]; + let targetNodeID = data[0]?.row[5]; // Read relationship max hops let relationshipsHops = data[0]?.row[7]; @@ -25,47 +26,27 @@ export function queryParser (e) { let presentColorLabels = new Array(); // maps of links with their max hop let linksMaxHops = {}; - let levels = {}; + let nodesPerLevel = new Array(7).fill(0); + let maxHops = 2; + let levels = new Array(7).fill(0); relationshipsHops?.forEach( rel => { let split = rel.split(":"); + let level = parseInt(split[1]) + 1; + level > maxHops ? maxHops = level : null; // Map link id to highest hop - linksMaxHops[split[0]] = parseInt(split[1]) + 1; + linksMaxHops[split[0]] = level; }) + + console.log("Reverse Map : ", reverseMap) - // Creates links map from Relationships, avoid duplicates + // Creates links map from Relationships, avoid duplicates data.forEach(({ graph, row }) => { graph.relationships.forEach(({ startNode, endNode, properties, id }) => { - if ( row[3].includes(parseInt(id)) ) { - if (linksMap.get(startNode) === undefined) { - linksMap.set(startNode, new Array()); - } - - let newLink = true; - linksMap.get(startNode).find( function ( ele ) { - if ( ele.target !== endNode ) { - newLink = true; - } else { - newLink = false; - } - }); - - // Only keep track of new links, avoid duplicates - if ( newLink ) { - linksMap.get(startNode).push( { target : endNode, label : properties[e.data.params.configuration.resultsMapping.link.label], weight : properties[e.data.params.configuration.resultsMapping.link.weight] }); - } - } else { - // Keep track of reverse links - if (reverseMap.get(startNode) === undefined) { - reverseMap.set(startNode, new Array()); - } - reverseMap.get(startNode).push( { target : endNode, label : properties[e.data.params.configuration.resultsMapping.link.label], weight : properties[e.data.params.configuration.resultsMapping.link.weight] }); - } - - linksMaxHops[id] ? levels[endNode] = linksMaxHops[id] : null; + linksMaxHops[id] ? nodesPerLevel[endNode] = linksMaxHops[id] : null; }); }); - + // Loop through nodes from query and create nodes for graph data.forEach(({ graph }) => { graph.nodes.forEach(({ id, labels, properties }) => { @@ -90,25 +71,20 @@ export function queryParser (e) { } let n = null; if (nodesMap.get(id) === undefined) { - let level = 1; - let positionX = 0; - - if ( id == targetNode ){ - level = 0; - positionX = 300; - } else if ( id == sourceNode ) { - level = 0; - positionX = -300; - } else { - level = levels[id]; + let level = 0; + if ( id != targetNodeID && id != sourceNodeID){ + level = parseInt(nodesPerLevel[id]); } + console.log("Level ", level); + console.log("nodesPerLevel[level] ", levels[level]); + level > 0 ? levels[level] = levels[level] + 1 : null; + n = { path : label, id : parseInt(id), title : title, level : level, - positionX : positionX, width : e.data.params.NODE_WIDTH, height : e.data.params.NODE_HEIGHT, color : color, @@ -119,7 +95,50 @@ export function queryParser (e) { } }); }); + + // Creates links map from Relationships, avoid duplicates + data.forEach(({ graph, row }) => { + graph.relationships.forEach(({ startNode, endNode, properties, id }) => { + let matchingStartNode = nodes.find((node) => node.id === parseInt(startNode)); + let matchingEndNode = nodes.find((node) => node.id === parseInt(endNode)); + + console.log("matchingStartNode ", matchingStartNode); + console.log("matchingEndNode ", matchingEndNode); + if ( matchingStartNode?.level <= matchingEndNode?.level ) { + if (linksMap.get(startNode) === undefined) { + linksMap.set(startNode, new Array()); + } + + let newLink = true; + linksMap.get(startNode).find( function ( ele ) { + if ( ele.target !== endNode ) { + newLink = true; + } else { + newLink = false; + } + }); + // Only keep track of new links, avoid duplicates + if ( newLink ) { + linksMap.get(startNode).push( { target : endNode, label : properties[e.data.params.configuration.resultsMapping.link.label], weight : properties[e.data.params.configuration.resultsMapping.link.weight] }); + } + } else { + // Keep track of reverse links + if (reverseMap.get(startNode) === undefined) { + reverseMap.set(startNode, new Array()); + } + reverseMap.get(startNode).push( { target : endNode, label : properties[e.data.params.configuration.resultsMapping.link.label], weight : properties[e.data.params.configuration.resultsMapping.link.weight] }); + } + }); + }); + + console.log("Nodes per level ", levels); + let maxNodesPerLevel = levels.reduce(function (a, b) { + return Math.max(a, b); + }); + console.log("Max Nodes per level ", maxNodesPerLevel); + console.log("Hops ", maxHops); + // Creates Links array with nodes nodes.forEach( sourceNode => { let id = sourceNode.id; @@ -157,8 +176,32 @@ export function queryParser (e) { } } } + + let positionX = 0; + if ( sourceNode.level === 0 ){ + if ( sourceNode.id == targetNodeID ){ + sourceNode.positionX = maxNodesPerLevel * 200; + } else if ( sourceNode.id == sourceNodeID ) { + sourceNode.positionX = maxNodesPerLevel * -200; + } + } else if ( sourceNode.level > 1 ) { + levels[sourceNode.level] == 1 ? positionX = ((maxNodesPerLevel * -200) + 100) : positionX = (maxNodesPerLevel * -200) + levels[sourceNode.level] * 100; + levels[sourceNode.level]-- + sourceNode.positionX = positionX; + } + console.log("Source Node ", sourceNode); + console.log("PositonX ", sourceNode.positionX); + console.log("Level ", sourceNode.level); }); + var end = Date.now(); + var elapsed = end - start; + console.log("Elapse time ", elapsed); + // Worker is done, notify main thread this.postMessage({ resultMessage: "OK", params: { results: { nodes, links }, colorLabels : presentColorLabels } }); + + end = Date.now(); + elapsed = end - start; + console.log("Elapse time after post message ", elapsed); } \ No newline at end of file diff --git a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js index 712217735..4c2c5202b 100644 --- a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js +++ b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js @@ -329,7 +329,6 @@ class VFBCircuitBrowser extends Component { linkLabel={link => link.label} // Width of links, log(weight) linkWidth={link => link.weight ? Math.log(link.weight) : 1 } - linkCurvature={.075} linkDirectionalArrowLength={link => link.weight ? Math.log(link.weight) * 5 : 4} linkDirectionalArrowRelPos={.75} // Node label, used in tooltip when hovering over Node @@ -443,7 +442,7 @@ class VFBCircuitBrowser extends Component { // bu = Bottom Up, creates Graph with root at bottom dagMode="lr" nodeVal = { node => { - node.fx = node.level == 0 ? node.positionX : node.fx ? node.fx : 0 ; + node.fx = node.positionX ? node.positionX : node.fx ; node.fy = node.level > 0 ? -100 * node.level : node.fy ? node.fy : 0 ; }} dagLevelDistance = {25} From f55a45addd94abd0e4b00b8d7f12a75817936088 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Thu, 13 May 2021 15:03:28 -0700 Subject: [PATCH 17/27] #1114 Circuit browser layout improvements --- .../VFBCircuitBrowser/circuitBrowserConfiguration.js | 2 +- components/interface/VFBCircuitBrowser/QueryParser.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js index 90767375d..6e8fe40e5 100644 --- a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js +++ b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js @@ -110,7 +110,7 @@ var styling = { } var restPostConfig = { - url: "https://pdb.virtualflybrain.org/db/neo4j/tx/commit", + url: "https://pdb-dev.virtualflybrain.org/db/neo4j/tx/commit", contentType: "application/json" }; diff --git a/components/interface/VFBCircuitBrowser/QueryParser.js b/components/interface/VFBCircuitBrowser/QueryParser.js index ea4e10ee3..86661598b 100644 --- a/components/interface/VFBCircuitBrowser/QueryParser.js +++ b/components/interface/VFBCircuitBrowser/QueryParser.js @@ -3,6 +3,7 @@ */ export function queryParser (e) { var start = Date.now(); + console.log("START : ", start); let graphData = e.data.params.results; console.log("Results ", e); // Reads graph data @@ -195,6 +196,7 @@ export function queryParser (e) { }); var end = Date.now(); + console.log("END : ", end); var elapsed = end - start; console.log("Elapse time ", elapsed); From 86c1998a7387e4f213f2df54958547661ef56b95 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Thu, 13 May 2021 16:32:13 -0700 Subject: [PATCH 18/27] #1114 Circuit browser improvements --- .../VFBCircuitBrowser/QueryParser.js | 51 +++++++------------ .../VFBCircuitBrowser/VFBCircuitBrowser.js | 7 +-- 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/components/interface/VFBCircuitBrowser/QueryParser.js b/components/interface/VFBCircuitBrowser/QueryParser.js index 86661598b..50f189227 100644 --- a/components/interface/VFBCircuitBrowser/QueryParser.js +++ b/components/interface/VFBCircuitBrowser/QueryParser.js @@ -2,8 +2,6 @@ * Converts graph data received from cypher query into a readable format for react-force-graph-2d */ export function queryParser (e) { - var start = Date.now(); - console.log("START : ", start); let graphData = e.data.params.results; console.log("Results ", e); // Reads graph data @@ -39,9 +37,7 @@ export function queryParser (e) { linksMaxHops[split[0]] = level; }) - console.log("Reverse Map : ", reverseMap) - - // Creates links map from Relationships, avoid duplicates + // Creates links map from Relationships, avoid duplicates data.forEach(({ graph, row }) => { graph.relationships.forEach(({ startNode, endNode, properties, id }) => { linksMaxHops[id] ? nodesPerLevel[endNode] = linksMaxHops[id] : null; @@ -76,9 +72,7 @@ export function queryParser (e) { if ( id != targetNodeID && id != sourceNodeID){ level = parseInt(nodesPerLevel[id]); } - - console.log("Level ", level); - console.log("nodesPerLevel[level] ", levels[level]); + level > 0 ? levels[level] = levels[level] + 1 : null; n = { @@ -100,12 +94,17 @@ export function queryParser (e) { // Creates links map from Relationships, avoid duplicates data.forEach(({ graph, row }) => { graph.relationships.forEach(({ startNode, endNode, properties, id }) => { - let matchingStartNode = nodes.find((node) => node.id === parseInt(startNode)); - let matchingEndNode = nodes.find((node) => node.id === parseInt(endNode)); + let matchingStartNode = nodes.find(node => node.id === parseInt(startNode)); + let matchingEndNode = nodes.find(node => node.id === parseInt(endNode)); - console.log("matchingStartNode ", matchingStartNode); - console.log("matchingEndNode ", matchingEndNode); - if ( matchingStartNode?.level <= matchingEndNode?.level ) { + let reverseLink = false; + linksMap.get(endNode)?.find( function ( ele ) { + if ( ele.target === startNode ) { + reverseLink = true; + } + }); + + if ( matchingStartNode?.level <= matchingEndNode?.level && !reverseLink ) { if (linksMap.get(startNode) === undefined) { linksMap.set(startNode, new Array()); } @@ -118,7 +117,7 @@ export function queryParser (e) { newLink = false; } }); - + // Only keep track of new links, avoid duplicates if ( newLink ) { linksMap.get(startNode).push( { target : endNode, label : properties[e.data.params.configuration.resultsMapping.link.label], weight : properties[e.data.params.configuration.resultsMapping.link.weight] }); @@ -133,12 +132,10 @@ export function queryParser (e) { }); }); - console.log("Nodes per level ", levels); + // Calculate what's the maximum nodes a level has let maxNodesPerLevel = levels.reduce(function (a, b) { return Math.max(a, b); }); - console.log("Max Nodes per level ", maxNodesPerLevel); - console.log("Hops ", maxHops); // Creates Links array with nodes nodes.forEach( sourceNode => { @@ -178,32 +175,22 @@ export function queryParser (e) { } } + // Set the X position of each node, this will place them on their corresponding column depending on hops let positionX = 0; if ( sourceNode.level === 0 ){ if ( sourceNode.id == targetNodeID ){ - sourceNode.positionX = maxNodesPerLevel * 200; + sourceNode.positionX = maxNodesPerLevel > 0 ? maxNodesPerLevel * 200 : 100; } else if ( sourceNode.id == sourceNodeID ) { - sourceNode.positionX = maxNodesPerLevel * -200; + sourceNode.positionX = maxNodesPerLevel > 0 ? maxNodesPerLevel * -200 : -100; } } else if ( sourceNode.level > 1 ) { - levels[sourceNode.level] == 1 ? positionX = ((maxNodesPerLevel * -200) + 100) : positionX = (maxNodesPerLevel * -200) + levels[sourceNode.level] * 100; + let space = ((maxNodesPerLevel * 200) * 2) / (maxNodesPerLevel + 1); + levels[sourceNode.level] == 1 ? positionX = ((maxNodesPerLevel * -200) + space) : positionX = (maxNodesPerLevel * -200) + levels[sourceNode.level] * space; levels[sourceNode.level]-- sourceNode.positionX = positionX; } - console.log("Source Node ", sourceNode); - console.log("PositonX ", sourceNode.positionX); - console.log("Level ", sourceNode.level); }); - var end = Date.now(); - console.log("END : ", end); - var elapsed = end - start; - console.log("Elapse time ", elapsed); - // Worker is done, notify main thread this.postMessage({ resultMessage: "OK", params: { results: { nodes, links }, colorLabels : presentColorLabels } }); - - end = Date.now(); - elapsed = end - start; - console.log("Elapse time after post message ", elapsed); } \ No newline at end of file diff --git a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js index 12254e1bf..7be3c024e 100644 --- a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js +++ b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js @@ -338,12 +338,7 @@ class VFBCircuitBrowser extends Component { linkLabel={link => link.label} // Width of links, log(weight) linkWidth={link => link.weight ? Math.log(link.weight) : 1 } -<<<<<<< HEAD - linkDirectionalArrowLength={link => link.weight ? Math.log(link.weight) * 5 : 4} -======= - linkCurvature={.075} linkDirectionalArrowLength={link => link.weight ? Math.log(link.weight) * 5 : 2} ->>>>>>> refs/remotes/origin/development linkDirectionalArrowRelPos={.75} // Node label, used in tooltip when hovering over Node linkCanvasObjectMode={() => "after"} @@ -389,7 +384,7 @@ class VFBCircuitBrowser extends Component { const label = link.weightLabel; // estimate fontSize to fit in link length - const fontSize = Math.min(MAX_FONT_SIZE, maxTextLength / ctx.measureText(label).width); + const fontSize = MAX_FONT_SIZE; ctx.font = `${fontSize}px Sans-Serif`; const textWidth = ctx.measureText(label).width; const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding From ff575f5f49ba4cb1000d122ba60e52fe4b94be8d Mon Sep 17 00:00:00 2001 From: jrmartin Date: Thu, 20 May 2021 21:53:10 -0700 Subject: [PATCH 19/27] #1114 Circuit browser layout improvements, this has all requirements of #1114 except that the links labels are sometimes hidden behind other nodes or links. --- .../circuitBrowserConfiguration.js | 4 +- .../VFBCircuitBrowser/QueryParser.js | 85 ++++++++++++------- .../VFBCircuitBrowser/VFBCircuitBrowser.js | 15 ++-- 3 files changed, 66 insertions(+), 38 deletions(-) diff --git a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js index 6e8fe40e5..cc7676cb9 100644 --- a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js +++ b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js @@ -48,7 +48,9 @@ var configuration = { // Minimum amount of neurons allowed minNeurons : 2, // Maximum amount of neurons allowed - maxNeurons : 2 + maxNeurons : 2, + // Curvature of lines, 0 is a straight line + linkCurvature : 0 } var styling = { diff --git a/components/interface/VFBCircuitBrowser/QueryParser.js b/components/interface/VFBCircuitBrowser/QueryParser.js index 50f189227..25d9a5621 100644 --- a/components/interface/VFBCircuitBrowser/QueryParser.js +++ b/components/interface/VFBCircuitBrowser/QueryParser.js @@ -40,10 +40,10 @@ export function queryParser (e) { // Creates links map from Relationships, avoid duplicates data.forEach(({ graph, row }) => { graph.relationships.forEach(({ startNode, endNode, properties, id }) => { - linksMaxHops[id] ? nodesPerLevel[endNode] = linksMaxHops[id] : null; + linksMaxHops[id] ? nodesPerLevel[endNode] ? nodesPerLevel[endNode] = Math.max(linksMaxHops[id], nodesPerLevel[endNode]) : nodesPerLevel[endNode] = linksMaxHops[id] : null; }); }); - + // Loop through nodes from query and create nodes for graph data.forEach(({ graph }) => { graph.nodes.forEach(({ id, labels, properties }) => { @@ -90,27 +90,55 @@ export function queryParser (e) { } }); }); + + // Calculate what's the maximum nodes a level has + let maxNodesPerLevel = levels.reduce( function (a, b) { + return Math.max(a, b); + }); + + + // Creates Links array with nodes + nodes.forEach( sourceNode => { + let id = sourceNode.id; + if ( typeof id === "number" ) { + id = sourceNode.id.toString(); + } + let n = linksMap.get(id); + + // Set the X position of each node, this will place them on their corresponding column depending on hops + let positionX = 0; + if ( sourceNode.level === 0 ){ + if ( sourceNode.id == targetNodeID ){ + sourceNode.positionX = maxNodesPerLevel > 0 ? maxNodesPerLevel * 200 : 100; + } else if ( sourceNode.id == sourceNodeID ) { + sourceNode.positionX = maxNodesPerLevel > 0 ? maxNodesPerLevel * -200 : -100; + } + } else if ( sourceNode.level >= 1 ) { + let space = ((maxNodesPerLevel * 200) * 2) / (maxNodesPerLevel + 1); + levels[sourceNode.level] == 1 ? positionX = ((maxNodesPerLevel * -200) + space) : positionX = (maxNodesPerLevel * -200 ) + (levels[sourceNode.level] * space); + levels[sourceNode.level]-- + sourceNode.positionX = positionX; + } + }); // Creates links map from Relationships, avoid duplicates data.forEach(({ graph, row }) => { graph.relationships.forEach(({ startNode, endNode, properties, id }) => { let matchingStartNode = nodes.find(node => node.id === parseInt(startNode)); let matchingEndNode = nodes.find(node => node.id === parseInt(endNode)); - let reverseLink = false; - linksMap.get(endNode)?.find( function ( ele ) { - if ( ele.target === startNode ) { - reverseLink = true; - } - }); - - if ( matchingStartNode?.level <= matchingEndNode?.level && !reverseLink ) { + + if ( matchingStartNode.positionX >= matchingEndNode.positionX ){ + reverseLink = true + } + + if ( !reverseLink ) { if (linksMap.get(startNode) === undefined) { linksMap.set(startNode, new Array()); } let newLink = true; - linksMap.get(startNode).find( function ( ele ) { + linksMap.get(startNode).find( ele => { if ( ele.target !== endNode ) { newLink = true; } else { @@ -132,11 +160,23 @@ export function queryParser (e) { }); }); - // Calculate what's the maximum nodes a level has - let maxNodesPerLevel = levels.reduce(function (a, b) { - return Math.max(a, b); + reverseMap.forEach( (value, key) => { + let linkInMap = value.find( valueNode => { + let found = linksMap?.get(valueNode.target)?.find( ele => { + if ( ele.target === key ) { + return ele; + } + }); + + if ( !found ) { + if (linksMap.get(key) === undefined) { + linksMap.set(key, new Array()); + } + linksMap?.get(key)?.push( { target : valueNode?.target, label : valueNode?.label, weight : valueNode?.weight }); + } + }) }); - + // Creates Links array with nodes nodes.forEach( sourceNode => { let id = sourceNode.id; @@ -174,21 +214,6 @@ export function queryParser (e) { } } } - - // Set the X position of each node, this will place them on their corresponding column depending on hops - let positionX = 0; - if ( sourceNode.level === 0 ){ - if ( sourceNode.id == targetNodeID ){ - sourceNode.positionX = maxNodesPerLevel > 0 ? maxNodesPerLevel * 200 : 100; - } else if ( sourceNode.id == sourceNodeID ) { - sourceNode.positionX = maxNodesPerLevel > 0 ? maxNodesPerLevel * -200 : -100; - } - } else if ( sourceNode.level > 1 ) { - let space = ((maxNodesPerLevel * 200) * 2) / (maxNodesPerLevel + 1); - levels[sourceNode.level] == 1 ? positionX = ((maxNodesPerLevel * -200) + space) : positionX = (maxNodesPerLevel * -200) + levels[sourceNode.level] * space; - levels[sourceNode.level]-- - sourceNode.positionX = positionX; - } }); // Worker is done, notify main thread diff --git a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js index 7be3c024e..e3999e37c 100644 --- a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js +++ b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js @@ -338,7 +338,8 @@ class VFBCircuitBrowser extends Component { linkLabel={link => link.label} // Width of links, log(weight) linkWidth={link => link.weight ? Math.log(link.weight) : 1 } - linkDirectionalArrowLength={link => link.weight ? Math.log(link.weight) * 5 : 2} + linkCurvature={configuration.linkCurvature} + linkDirectionalArrowLength={link => link.weight ? Math.max(10, Math.log(link.weight) * 5) : 2} linkDirectionalArrowRelPos={.75} // Node label, used in tooltip when hovering over Node linkCanvasObjectMode={() => "after"} @@ -451,7 +452,7 @@ class VFBCircuitBrowser extends Component { // bu = Bottom Up, creates Graph with root at bottom dagMode="lr" nodeVal = { node => { - node.fx = node.positionX ? node.positionX : node.fx ; + node.fx = node.positionX; node.fy = node.level > 0 ? -100 * node.level : node.fy ? node.fy : 0 ; }} dagLevelDistance = {25} @@ -487,15 +488,15 @@ class VFBCircuitBrowser extends Component { // Function triggered when hovering over a nodeoptions onNodeHover={node => { // Reset maps of hover nodes and links - self.highlightNodes.clear(); - self.highlightLinks.clear(); + self.highlightNodes?.clear(); + self.highlightLinks?.clear(); // We found the node that we are hovering over if (node) { // Keep track of hover node, its neighbors and links - self.highlightNodes.add(node); - node.neighbors.forEach(neighbor => self.highlightNodes.add(neighbor)); - node.links.forEach(link => self.highlightLinks.add(link)); + self.highlightNodes?.add(node); + node?.neighbors?.forEach(neighbor => self?.highlightNodes?.add(neighbor)); + node?.links?.forEach(link => self?.highlightLinks?.add(link)); } // Keep track of hover node From 6f7e184f1ed27a3b0e1ed907546b68aef1a5d9b9 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Fri, 21 May 2021 19:51:51 -0700 Subject: [PATCH 20/27] #1114 Improve circuit browser layout --- .../VFBCircuitBrowser/QueryParser.js | 75 ++++++++++++------- .../VFBCircuitBrowser/VFBCircuitBrowser.js | 2 +- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/components/interface/VFBCircuitBrowser/QueryParser.js b/components/interface/VFBCircuitBrowser/QueryParser.js index 25d9a5621..a5c7992ae 100644 --- a/components/interface/VFBCircuitBrowser/QueryParser.js +++ b/components/interface/VFBCircuitBrowser/QueryParser.js @@ -9,6 +9,7 @@ export function queryParser (e) { // Read source and target node ids let sourceNodeID = data[0]?.row[4]; let targetNodeID = data[0]?.row[5]; + let maxHops = Math.ceil(data[0]?.row[6] / 2) + 1; // Read relationship max hops let relationshipsHops = data[0]?.row[7]; @@ -16,6 +17,7 @@ export function queryParser (e) { let nodes = [], links = []; // Keeps track of links let linksMap = new Map(); + let allRelationships = new Map(); // Keeps track of reverse links let reverseMap = new Map(); // Keeps track of nodes in map @@ -25,14 +27,11 @@ export function queryParser (e) { let presentColorLabels = new Array(); // maps of links with their max hop let linksMaxHops = {}; - let nodesPerLevel = new Array(7).fill(0); - let maxHops = 2; - let levels = new Array(7).fill(0); + let nodesInLevel = new Array(); relationshipsHops?.forEach( rel => { let split = rel.split(":"); let level = parseInt(split[1]) + 1; - level > maxHops ? maxHops = level : null; // Map link id to highest hop linksMaxHops[split[0]] = level; }) @@ -40,7 +39,11 @@ export function queryParser (e) { // Creates links map from Relationships, avoid duplicates data.forEach(({ graph, row }) => { graph.relationships.forEach(({ startNode, endNode, properties, id }) => { - linksMaxHops[id] ? nodesPerLevel[endNode] ? nodesPerLevel[endNode] = Math.max(linksMaxHops[id], nodesPerLevel[endNode]) : nodesPerLevel[endNode] = linksMaxHops[id] : null; + linksMaxHops[id] ? nodesInLevel[endNode] ? nodesInLevel[endNode] = Math.max(linksMaxHops[id], nodesInLevel[endNode]) : nodesInLevel[endNode] = linksMaxHops[id] : null; + if (allRelationships.get(parseInt(startNode)) === undefined) { + allRelationships.set(parseInt(startNode), new Array()); + } + allRelationships?.get(parseInt(startNode))?.push( { target : parseInt(endNode), label : properties[e.data.params.configuration.resultsMapping.link.label], weight : properties[e.data.params.configuration.resultsMapping.link.weight] }); }); }); @@ -69,17 +72,24 @@ export function queryParser (e) { let n = null; if (nodesMap.get(id) === undefined) { let level = 0; - if ( id != targetNodeID && id != sourceNodeID){ - level = parseInt(nodesPerLevel[id]); + let parseID = parseInt(id); + if ( parseID != targetNodeID && parseID != sourceNodeID){ + level = parseInt(nodesInLevel[id]); + } + + let hop = 1; + if ( parseID === targetNodeID ) { + hop = maxHops + 1; + } else if ( parseID === sourceNodeID) { + hop = 0; } - - level > 0 ? levels[level] = levels[level] + 1 : null; n = { path : label, - id : parseInt(id), + id : parseID, title : title, level : level, + hop : hop, width : e.data.params.NODE_WIDTH, height : e.data.params.NODE_HEIGHT, color : color, @@ -90,12 +100,22 @@ export function queryParser (e) { } }); }); - - // Calculate what's the maximum nodes a level has - let maxNodesPerLevel = levels.reduce( function (a, b) { - return Math.max(a, b); - }); + function hopAssignment (startNodeID, endNodeID, relationshipsMap, currentHops, nodesHopsMap){ + let neighbors = relationshipsMap?.get(startNodeID); + + for ( let i = 0; i < neighbors?.length; i++ ) { + let currentID = neighbors[i]?.target; + if ( currentID != endNodeID && currentID != sourceNodeID && nodesHopsMap[currentID] === undefined ) { + nodesHopsMap[currentID] = currentHops + 1; + hopAssignment(currentID, endNodeID, relationshipsMap, currentHops + 1, nodesHopsMap) + } + } + + return nodesHopsMap; + } + + let hopsMap = hopAssignment(sourceNodeID, targetNodeID, allRelationships, 0, {}); // Creates Links array with nodes nodes.forEach( sourceNode => { @@ -109,14 +129,14 @@ export function queryParser (e) { let positionX = 0; if ( sourceNode.level === 0 ){ if ( sourceNode.id == targetNodeID ){ - sourceNode.positionX = maxNodesPerLevel > 0 ? maxNodesPerLevel * 200 : 100; + sourceNode.positionX = maxHops > 0 ? maxHops * 100 : 75; } else if ( sourceNode.id == sourceNodeID ) { - sourceNode.positionX = maxNodesPerLevel > 0 ? maxNodesPerLevel * -200 : -100; + sourceNode.positionX = maxHops > 0 ? maxHops * -100 : -75; } } else if ( sourceNode.level >= 1 ) { - let space = ((maxNodesPerLevel * 200) * 2) / (maxNodesPerLevel + 1); - levels[sourceNode.level] == 1 ? positionX = ((maxNodesPerLevel * -200) + space) : positionX = (maxNodesPerLevel * -200 ) + (levels[sourceNode.level] * space); - levels[sourceNode.level]-- + sourceNode.hop = hopsMap[sourceNode?.id]; + let space = ((maxHops * 100 * 2) / (maxHops + 1)) * sourceNode.hop; + positionX = (maxHops * -100 ) + space sourceNode.positionX = positionX; } }); @@ -128,7 +148,7 @@ export function queryParser (e) { let matchingEndNode = nodes.find(node => node.id === parseInt(endNode)); let reverseLink = false; - if ( matchingStartNode.positionX >= matchingEndNode.positionX ){ + if ( matchingStartNode.positionX > matchingEndNode.positionX ){ reverseLink = true } @@ -155,11 +175,13 @@ export function queryParser (e) { if (reverseMap.get(startNode) === undefined) { reverseMap.set(startNode, new Array()); } + reverseMap.get(startNode).push( { target : endNode, label : properties[e.data.params.configuration.resultsMapping.link.label], weight : properties[e.data.params.configuration.resultsMapping.link.weight] }); } }); }); + // Loop through reverse map and find reverse links reverseMap.forEach( (value, key) => { let linkInMap = value.find( valueNode => { let found = linksMap?.get(valueNode.target)?.find( ele => { @@ -177,7 +199,7 @@ export function queryParser (e) { }) }); - // Creates Links array with nodes + // Creates Links array with nodes, assign node neighbors that are connected by links nodes.forEach( sourceNode => { let id = sourceNode.id; if ( typeof id === "number" ) { @@ -193,10 +215,10 @@ export function queryParser (e) { if ( !match ) { // Create tooltip label for link and weight const tooltip = "Label : " + n[i].label + '
' - + "Weight : " + (reverse ? n[i].weight + " [" + reverse.weight + "]" : n[i].weight); - const weightLabel = reverse ? n[i].weight + " [" + reverse.weight + "]" : n[i].weight; + + "Weight : " + (reverse ? n[i].weight + " [" + reverse.weight + "]" : n[i].weight + "[0]"); + const weightLabel = reverse ? n[i].weight + " [" + reverse.weight + "]" : n[i].weight + "[0]"; // Create new link for graph - let link = { source: sourceNode, label : tooltip, weightLabel : weightLabel, weight : n[i].weight, target: targetNode, targetNode: targetNode, curvature: .75 }; + let link = { source: sourceNode, label : tooltip, weightLabel : weightLabel, weight : n[i].weight, target: targetNode, targetNode: targetNode }; links.push( link ); // Assign neighbors to nodes and links @@ -216,6 +238,9 @@ export function queryParser (e) { } }); + console.log("Nodes ", nodes); + console.log("Links ", links); + // Worker is done, notify main thread this.postMessage({ resultMessage: "OK", params: { results: { nodes, links }, colorLabels : presentColorLabels } }); } \ No newline at end of file diff --git a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js index e3999e37c..aaf2b0aa8 100644 --- a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js +++ b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js @@ -338,7 +338,7 @@ class VFBCircuitBrowser extends Component { linkLabel={link => link.label} // Width of links, log(weight) linkWidth={link => link.weight ? Math.log(link.weight) : 1 } - linkCurvature={configuration.linkCurvature} + linkCurvature={ configuration.linkCurvature } linkDirectionalArrowLength={link => link.weight ? Math.max(10, Math.log(link.weight) * 5) : 2} linkDirectionalArrowRelPos={.75} // Node label, used in tooltip when hovering over Node From 412b4ec7d248857705cb3be981a213996d9019e2 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Sat, 22 May 2021 09:36:29 -0700 Subject: [PATCH 21/27] #1114 Fixing bug with circuit browser --- .../VFBCircuitBrowser/QueryParser.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/components/interface/VFBCircuitBrowser/QueryParser.js b/components/interface/VFBCircuitBrowser/QueryParser.js index a5c7992ae..59b09555d 100644 --- a/components/interface/VFBCircuitBrowser/QueryParser.js +++ b/components/interface/VFBCircuitBrowser/QueryParser.js @@ -27,19 +27,21 @@ export function queryParser (e) { let presentColorLabels = new Array(); // maps of links with their max hop let linksMaxHops = {}; + // Keeps track of what level nodes belong let nodesInLevel = new Array(); + // Read relationshipY:index values relationshipsHops?.forEach( rel => { let split = rel.split(":"); - let level = parseInt(split[1]) + 1; - // Map link id to highest hop - linksMaxHops[split[0]] = level; + // Map link id to highest index + linksMaxHops[split[0]] = parseInt(split[1]) + 1; }) - // Creates links map from Relationships, avoid duplicates + // Creates map from Relationships for easy access. data.forEach(({ graph, row }) => { graph.relationships.forEach(({ startNode, endNode, properties, id }) => { - linksMaxHops[id] ? nodesInLevel[endNode] ? nodesInLevel[endNode] = Math.max(linksMaxHops[id], nodesInLevel[endNode]) : nodesInLevel[endNode] = linksMaxHops[id] : null; + // Keep track of the level where the node will be placed + linksMaxHops[id] ? nodesInLevel[endNode] ? nodesInLevel[endNode] = linksMaxHops[id] : nodesInLevel[endNode] = linksMaxHops[id] : null; if (allRelationships.get(parseInt(startNode)) === undefined) { allRelationships.set(parseInt(startNode), new Array()); } @@ -77,6 +79,7 @@ export function queryParser (e) { level = parseInt(nodesInLevel[id]); } + // Assign hop to source and target node let hop = 1; if ( parseID === targetNodeID ) { hop = maxHops + 1; @@ -101,6 +104,7 @@ export function queryParser (e) { }); }); + // Helper function to assign hops to nodes, this will determine the X position function hopAssignment (startNodeID, endNodeID, relationshipsMap, currentHops, nodesHopsMap){ let neighbors = relationshipsMap?.get(startNodeID); @@ -115,9 +119,10 @@ export function queryParser (e) { return nodesHopsMap; } + // assign hops to nodes let hopsMap = hopAssignment(sourceNodeID, targetNodeID, allRelationships, 0, {}); - // Creates Links array with nodes + // Loop through nodes and assign X position based on hops nodes.forEach( sourceNode => { let id = sourceNode.id; if ( typeof id === "number" ) { @@ -129,9 +134,9 @@ export function queryParser (e) { let positionX = 0; if ( sourceNode.level === 0 ){ if ( sourceNode.id == targetNodeID ){ - sourceNode.positionX = maxHops > 0 ? maxHops * 100 : 75; + sourceNode.positionX = maxHops > 0 ? maxHops * 100 : 100; } else if ( sourceNode.id == sourceNodeID ) { - sourceNode.positionX = maxHops > 0 ? maxHops * -100 : -75; + sourceNode.positionX = maxHops > 0 ? maxHops * -100 : -100; } } else if ( sourceNode.level >= 1 ) { sourceNode.hop = hopsMap[sourceNode?.id]; @@ -148,7 +153,7 @@ export function queryParser (e) { let matchingEndNode = nodes.find(node => node.id === parseInt(endNode)); let reverseLink = false; - if ( matchingStartNode.positionX > matchingEndNode.positionX ){ + if ( matchingStartNode.positionX >= matchingEndNode.positionX ){ reverseLink = true } From 0efd6e5a375c82bcf8b1e0a1f1648d5fed7ddf53 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Wed, 26 May 2021 14:55:27 -0700 Subject: [PATCH 22/27] #1114 Adjust destination spacing --- .../interface/VFBCircuitBrowser/QueryParser.js | 12 +++++++----- .../interface/VFBCircuitBrowser/VFBCircuitBrowser.js | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/components/interface/VFBCircuitBrowser/QueryParser.js b/components/interface/VFBCircuitBrowser/QueryParser.js index 59b09555d..d161ca2dd 100644 --- a/components/interface/VFBCircuitBrowser/QueryParser.js +++ b/components/interface/VFBCircuitBrowser/QueryParser.js @@ -120,7 +120,8 @@ export function queryParser (e) { } // assign hops to nodes - let hopsMap = hopAssignment(sourceNodeID, targetNodeID, allRelationships, 0, {}); + let hopsMap = hopAssignment(sourceNodeID, targetNodeID, allRelationships, 0, {}); + maxHops = Math.max.apply(null, Object.keys(hopsMap)?.map( key => parseInt(hopsMap[key]))); // Loop through nodes and assign X position based on hops nodes.forEach( sourceNode => { @@ -132,16 +133,17 @@ export function queryParser (e) { // Set the X position of each node, this will place them on their corresponding column depending on hops let positionX = 0; + let spaceBetween = maxHops > 2 ? 100 : 200; if ( sourceNode.level === 0 ){ if ( sourceNode.id == targetNodeID ){ - sourceNode.positionX = maxHops > 0 ? maxHops * 100 : 100; + sourceNode.positionX = maxHops > 0 ? maxHops * spaceBetween : spaceBetween; } else if ( sourceNode.id == sourceNodeID ) { - sourceNode.positionX = maxHops > 0 ? maxHops * -100 : -100; + sourceNode.positionX = maxHops > 0 ? maxHops * (-1 * spaceBetween) : (-1 * spaceBetween); } } else if ( sourceNode.level >= 1 ) { sourceNode.hop = hopsMap[sourceNode?.id]; - let space = ((maxHops * 100 * 2) / (maxHops + 1)) * sourceNode.hop; - positionX = (maxHops * -100 ) + space + let space = ((maxHops * spaceBetween * 2) / (maxHops + 1)) * sourceNode.hop; + positionX = (maxHops * (-1 * spaceBetween) ) + space sourceNode.positionX = positionX; } }); diff --git a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js index aaf2b0aa8..1eaab1ed8 100644 --- a/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js +++ b/components/interface/VFBCircuitBrowser/VFBCircuitBrowser.js @@ -249,6 +249,7 @@ class VFBCircuitBrowser extends Component { results: response.data, configuration : configuration, styling : stylingConfiguration, + hops : self.state.hops, NODE_WIDTH : NODE_WIDTH, NODE_HEIGHT : NODE_HEIGHT } @@ -344,7 +345,7 @@ class VFBCircuitBrowser extends Component { // Node label, used in tooltip when hovering over Node linkCanvasObjectMode={() => "after"} linkCanvasObject={(link, ctx) => { - const MAX_FONT_SIZE = 10; + const MAX_FONT_SIZE = 8; const LABEL_NODE_MARGIN = 1 * 1.5; const start = link.source; @@ -360,7 +361,7 @@ class VFBCircuitBrowser extends Component { if (link?.__controlPoints ) { textPos = this.getQuadraticXY( - .5, + .3, start.x, start.y, link?.__controlPoints[0], From 5c0fc5a6ec39d1ae07be2fb226ff00d84977d2ef Mon Sep 17 00:00:00 2001 From: Rob Court Date: Thu, 27 May 2021 13:42:54 +0100 Subject: [PATCH 23/27] fix to avoid extraneous relationships --- .../VFBCircuitBrowser/circuitBrowserConfiguration.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js index cc7676cb9..36363f6b4 100644 --- a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js +++ b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js @@ -10,17 +10,16 @@ var locationCypherQuery = ( instances, hops, weight ) => ({ + weight.toString() + " RETURN id(a) AS source, id(b) AS target, type(r) as type, 5000-r.weight[0] as weight_p'," + " sourceNode: id(source)," + " targetNode: id(target)," - + " k: " + hops.toString() + "," + + " k: " + hops.toString() + "," + " relationshipWeightProperty: 'weight_p'," + " relationshipTypes: ['*']," + " path: true" + "})" + " YIELD index, sourceNode, targetNode, nodeIds, path" + " WITH * ORDER BY index DESC" - + " OPTIONAL MATCH fp=(source)-[r:synapsed_to*..]->(target) WHERE ALL(n in nodes(fp) WHERE id(n) IN nodeIds)" - + " UNWIND r as sr WITH *, collect(id(sr)) as ids, toString(id(sr))+':'+toString(index) as relY OPTIONAL MATCH cp=(source)-[r:synapsed_to*..]-(target)" - + " WHERE ALL(n in nodes(cp) WHERE id(n) IN nodeIds) UNWIND ids as id" - + " RETURN distinct a as root, collect(distinct fp) as pp, collect(distinct cp) as p, collect(distinct id) as fr, sourceNode as source, targetNode as target, max(length(fp)) as maxHops, collect(distinct relY) as relationshipY ", + + " UNWIND relationships(path) as sr" + + " OPTIONAL MATCH cp=(x)-[:synapsed_to]-(y) WHERE x=apoc.rel.startNode(sr) AND y=apoc.rel.endNode(sr) OPTIONAL MATCH fp=(x)-[r:synapsed_to]->(y)" + + " RETURN distinct a as root, collect(distinct fp) as pp, collect(distinct cp) as p, collect(distinct id(r)) as fr, sourceNode as source, targetNode as target, max(length(path)) as maxHops, collect(distinct toString(id(r))+':'+toString(index)) as relationshipY ", "resultDataContents": ["row", "graph"] } ] From 65437b522834d04e39942d0b40c5171d3645b119 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Thu, 3 Jun 2021 07:00:26 -0700 Subject: [PATCH 24/27] #1119 If circuit browser is launched form term info, the full label is displayed and not just the ID. Improves autocomplete. --- components/interface/VFBCircuitBrowser/Controls.js | 4 ++-- components/interface/VFBTermInfo/VFBTermInfo.js | 7 ++++--- reducers/generals.js | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/components/interface/VFBCircuitBrowser/Controls.js b/components/interface/VFBCircuitBrowser/Controls.js index 6eb154c34..13e54ac7f 100644 --- a/components/interface/VFBCircuitBrowser/Controls.js +++ b/components/interface/VFBCircuitBrowser/Controls.js @@ -330,7 +330,7 @@ class Controls extends Component { clearTimeout(this.typingTimeout); } // Create a setTimeout interval, to avoid performing searches on every stroke - setTimeout(this.typingTimeout, 500, event.target); + setTimeout(this.typingTimeout, 10, event.target); } /** @@ -580,7 +580,7 @@ function mapStateToProps (state) { } function mapDispatchToProps (dispatch) { - return { vfbCircuitBrowser: (type, path) => dispatch ( { type : type, data : { instance : path } }), } + return { vfbCircuitBrowser: (type, neurons) => dispatch ( { type : type, data : { instance : neurons } }), } } export default connect(mapStateToProps, mapDispatchToProps, null, { forwardRef : true } )(withStyles(styles)(Controls)); diff --git a/components/interface/VFBTermInfo/VFBTermInfo.js b/components/interface/VFBTermInfo/VFBTermInfo.js index 72381feaa..9542c1e67 100644 --- a/components/interface/VFBTermInfo/VFBTermInfo.js +++ b/components/interface/VFBTermInfo/VFBTermInfo.js @@ -336,7 +336,7 @@ class VFBTermInfo extends React.Component { let graphs = new Array(); for (var j = 0; j < values.length; j++) { graphs.push(
- + { "Show Circuit Browser for " + values[j].instance.parent.name }
@@ -723,10 +723,11 @@ class VFBTermInfoWidget extends React.Component { if (path.indexOf(CIRCUIT_BROWSER) === 0 ) { // Show Circuit Browser const { vfbCircuitBrowser } = this.props; + const selectedQuery = { label : path.split(',')[1] + "(" + path.split(',')[2] + ")" , id : path.split(',')[2] }; /* * Path contains the instancE ID passed to the circuit browser */ - vfbCircuitBrowser(UPDATE_CIRCUIT_QUERY, path.split(',')[1], true); + vfbCircuitBrowser(UPDATE_CIRCUIT_QUERY, selectedQuery, true); // Notify VFBMain UI needs to be updated this.props.uiUpdated(); @@ -886,7 +887,7 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { - vfbCircuitBrowser: (type, path, visible) => dispatch ( { type : type, data : { instance : path, visible : visible } }), + vfbCircuitBrowser: (type, instance, visible) => dispatch ( { type : type, data : { instance : instance, visible : visible } }), vfbGraph: (type, path, index, visible, sync) => dispatch ( { type : type, data : { instance : path, queryIndex : index, visible : visible, sync : sync } }) } } diff --git a/reducers/generals.js b/reducers/generals.js index 207cf62b5..ad5fa81d4 100644 --- a/reducers/generals.js +++ b/reducers/generals.js @@ -242,9 +242,11 @@ function generalReducer (state, action) { }; case UPDATE_CIRCUIT_QUERY: var newQueryMap = []; + // Instance is array if ( Array.isArray(action.data.instance) ) { newQueryMap = action.data.instance; } else { + // Instance is object !state.ui.circuitBrowser.circuitQuerySelected.includes(action.data.instance) ? newQueryMap = [...state.ui.circuitBrowser.circuitQuerySelected, action.data.instance] : newQueryMap = [...state.ui.circuitBrowser.circuitQuerySelected]; } From 68b028b15f4cefa31aede9497d89a87fa24a8850 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Thu, 3 Jun 2021 10:13:58 -0700 Subject: [PATCH 25/27] #1119 space --- components/interface/VFBTermInfo/VFBTermInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/interface/VFBTermInfo/VFBTermInfo.js b/components/interface/VFBTermInfo/VFBTermInfo.js index 9542c1e67..e9221f535 100644 --- a/components/interface/VFBTermInfo/VFBTermInfo.js +++ b/components/interface/VFBTermInfo/VFBTermInfo.js @@ -723,7 +723,7 @@ class VFBTermInfoWidget extends React.Component { if (path.indexOf(CIRCUIT_BROWSER) === 0 ) { // Show Circuit Browser const { vfbCircuitBrowser } = this.props; - const selectedQuery = { label : path.split(',')[1] + "(" + path.split(',')[2] + ")" , id : path.split(',')[2] }; + const selectedQuery = { label : path.split(',')[1] + " (" + path.split(',')[2] + ")" , id : path.split(',')[2] }; /* * Path contains the instancE ID passed to the circuit browser */ From 3fbfb776c2c61c42e21de5166fb311dc00d84c7f Mon Sep 17 00:00:00 2001 From: jrmartin Date: Fri, 4 Jun 2021 08:22:24 -0700 Subject: [PATCH 26/27] #1114 Fix eslint --- .../VFBCircuitBrowser/circuitBrowserConfiguration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js index 07a11f2cb..30a1c60a0 100644 --- a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js +++ b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js @@ -10,7 +10,7 @@ var locationCypherQuery = ( instances, paths, weight ) => ({ + weight.toString() + " RETURN id(a) AS source, id(b) AS target, type(r) as type, 5000-r.weight[0] as weight_p'," + " sourceNode: id(source)," + " targetNode: id(target)," - + " k: " + hops.toString() + "," + + " k: " + paths.toString() + "," + " relationshipWeightProperty: 'weight_p'," + " relationshipTypes: ['*']," + " path: true" From 15f7cfcb455c34c7555e9d5cea2e6eedd91b7f51 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Wed, 9 Jun 2021 16:17:35 -0700 Subject: [PATCH 27/27] #1138 Check for undefined objects to avoid crashes --- .../VFBCircuitBrowser/circuitBrowserConfiguration.js | 8 ++++---- .../interface/VFBCircuitBrowser/QueryParser.js | 12 +++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js index 30a1c60a0..456b5dda7 100644 --- a/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js +++ b/components/configuration/VFBCircuitBrowser/circuitBrowserConfiguration.js @@ -7,10 +7,10 @@ var locationCypherQuery = ( instances, paths, weight ) => ({ + " CALL gds.beta.shortestPath.yens.stream({" + " nodeQuery: 'MATCH (n:Neuron) RETURN id(n) AS id'," + " relationshipQuery: 'MATCH (a:Neuron:has_neuron_connectivity)-[r:synapsed_to]->(b:Neuron) WHERE exists(r.weight) AND r.weight[0] >= " - + weight.toString() + " RETURN id(a) AS source, id(b) AS target, type(r) as type, 5000-r.weight[0] as weight_p'," + + weight?.toString() + " RETURN id(a) AS source, id(b) AS target, type(r) as type, 5000-r.weight[0] as weight_p'," + " sourceNode: id(source)," + " targetNode: id(target)," - + " k: " + paths.toString() + "," + + " k: " + paths?.toString() + "," + " relationshipWeightProperty: 'weight_p'," + " relationshipTypes: ['*']," + " path: true" @@ -41,9 +41,9 @@ var configuration = { } }, // Minimum amount of paths allowed - minHops : 1, + minPaths : 1, // Maximum amount of paths allowed - maxHops : 6, + maxPaths : 6, // Minimum amount of neurons allowed minNeurons : 2, // Maximum amount of neurons allowed diff --git a/components/interface/VFBCircuitBrowser/QueryParser.js b/components/interface/VFBCircuitBrowser/QueryParser.js index d161ca2dd..3b657930c 100644 --- a/components/interface/VFBCircuitBrowser/QueryParser.js +++ b/components/interface/VFBCircuitBrowser/QueryParser.js @@ -2,10 +2,18 @@ * Converts graph data received from cypher query into a readable format for react-force-graph-2d */ export function queryParser (e) { + // The nodes and links arrays used for the graph + let nodes = [], links = []; let graphData = e.data.params.results; console.log("Results ", e); // Reads graph data - let data = graphData.results[0].data; + let data = graphData?.results[0]?.data; + + if (data === undefined) { + this.postMessage({ resultMessage: "OK", params: { results: { nodes, links } } }); + return; + } + // Read source and target node ids let sourceNodeID = data[0]?.row[4]; let targetNodeID = data[0]?.row[5]; @@ -13,8 +21,6 @@ export function queryParser (e) { // Read relationship max hops let relationshipsHops = data[0]?.row[7]; - // The nodes and links arrays used for the graph - let nodes = [], links = []; // Keeps track of links let linksMap = new Map(); let allRelationships = new Map();