diff --git a/src/App.js b/src/App.js
index 4af5cd2..724c959 100644
--- a/src/App.js
+++ b/src/App.js
@@ -18,7 +18,6 @@ import { NodeViewWidget } from './app/widgets';
import { addWidget } from '@metacell/geppetto-meta-client/common/layout/actions';
import { WidgetStatus } from "@metacell/geppetto-meta-client/common/layout/model";
import DatasetsListSplinter from "./components/DatasetsListViewer/DatasetsListSplinter";
-
import config from './config/app.json';
const App = () => {
@@ -132,6 +131,7 @@ const App = () => {
const splinter = new DatasetsListSplinter(undefined, file.data);
let graph = await splinter.getGraph();
let datasets = graph.nodes.filter((node) => node?.attributes?.hasDoi);
+ let version = graph.nodes.find( node => node?.attributes?.versionInfo)?.attributes?.versionInfo
const match = datasets.find( node => node.attributes?.hasDoi?.[0]?.includes(doi));
if ( match ) {
const datasetID = match.name;
@@ -140,6 +140,20 @@ const App = () => {
setLoading(false);
setInitialised(false);
}
+
+ let datasetStorage = {};
+ if ( version !== undefined && localStorage.getItem(config.datasetsStorage)?.version !== version[0] ) {
+ let parsedDatasets = []
+ datasets.forEach( node => {
+ parsedDatasets.push({ name : node.name , doi : node.attributes?.hasDoi?.[0], label : node.attributes ? node.attributes?.label?.[0]?.toLowerCase() : null});
+ });
+ datasetStorage = {
+ version : version[0],
+ datasets : parsedDatasets
+ }
+
+ localStorage.setItem(config.datasetsStorage, JSON.stringify(datasetStorage));
+ }
};
useEffect(() => {
@@ -149,9 +163,21 @@ const App = () => {
if (doi && doi !== "" ) {
if ( doiMatch ){
- const fileHandler = new FileHandler();
- const summaryURL = config.repository_url + config.available_datasets;
- fileHandler.get_remote_file(summaryURL, loadDatsetFromDOI);
+ if ( localStorage.getItem(config.datasetsStorage) ) {
+ let storedDatasetsInfo = JSON.parse(localStorage.getItem(config.datasetsStorage));
+ const match = storedDatasetsInfo.datasets.find( node => node?.doi.includes(doi));
+ if ( match ) {
+ const datasetID = match.name;
+ loadFiles(datasetID);
+ } else {
+ setLoading(false);
+ setInitialised(false);
+ }
+ } else {
+ const fileHandler = new FileHandler();
+ const summaryURL = config.repository_url + config.available_datasets;
+ fileHandler.get_remote_file(summaryURL, loadDatsetFromDOI);
+ }
}
}
}, []);
diff --git a/src/components/DatasetsListViewer/DatasetsListDialog.js b/src/components/DatasetsListViewer/DatasetsListDialog.js
index b63ef04..7cc464e 100644
--- a/src/components/DatasetsListViewer/DatasetsListDialog.js
+++ b/src/components/DatasetsListViewer/DatasetsListDialog.js
@@ -106,8 +106,24 @@ const DatasetsListDialog = (props) => {
let datasets = graph.nodes.filter((node) => node?.attributes?.hasUriApi);
datasets.forEach( node => node.attributes ? node.attributes.lowerCaseLabel = node.attributes?.label?.[0]?.toLowerCase() : null );
datasets = datasets.filter( node => node?.attributes?.statusOnPlatform?.[0]?.includes(PUBLISHED) );
- dispatch(setDatasetsList(datasets));
- setFilteredDatasets(datasets);
+
+
+ let version = graph.nodes.find( node => node?.attributes?.versionInfo)?.attributes?.versionInfo
+ let datasetStorage = {};
+ if ( version !== undefined && localStorage.getItem(config.datasetsStorage)?.version !== version[0] ) {
+ let parsedDatasets = []
+ datasets.forEach( node => {
+ parsedDatasets.push({ name : node.name , doi : node.attributes?.hasDoi?.[0], label : node.attributes ? node.attributes.lowerCaseLabel : null});
+ });
+ datasetStorage = {
+ version : version[0],
+ datasets : parsedDatasets
+ }
+
+ localStorage.setItem(config.datasetsStorage, JSON.stringify(datasetStorage));
+ dispatch(setDatasetsList(datasetStorage.datasets));
+ setFilteredDatasets(datasetStorage.datasets);
+ }
};
const summaryURL = config.repository_url + config.available_datasets;
fileHandler.get_remote_file(summaryURL, callback);
@@ -116,7 +132,7 @@ const DatasetsListDialog = (props) => {
const handleChange = (event) => {
const lowerCaseSearch = event.target.value.toLowerCase();
let filtered = datasets.filter((dataset) =>
- dataset.attributes.lowerCaseLabel.includes(lowerCaseSearch) || dataset.name.includes(lowerCaseSearch)
+ dataset.label?.includes(lowerCaseSearch) || dataset.name?.includes(lowerCaseSearch)
);
setSearchField(lowerCaseSearch);
setFilteredDatasets(filtered);
@@ -139,7 +155,15 @@ const DatasetsListDialog = (props) => {
}
useEffect(() => {
- open && datasets.length === 0 && loadDatasets();
+ if ( open && datasets.length === 0 ) {
+ if ( localStorage.getItem(config.datasetsStorage) ) {
+ let storedDatasetsInfo = JSON.parse(localStorage.getItem(config.datasetsStorage));
+ dispatch(setDatasetsList(storedDatasetsInfo.datasets));
+ setFilteredDatasets(storedDatasetsInfo.datasets);
+ } else {
+ loadDatasets();
+ }
+ }
});
return (
@@ -202,7 +226,7 @@ const DatasetsListDialog = (props) => {
className="dataset_list_text"
dangerouslySetInnerHTML={{
__html:
- getFormattedListTex(dataset.attributes?.label[0])
+ getFormattedListTex(dataset.label)
}}
/>
}
diff --git a/src/components/DatasetsListViewer/DatasetsListSplinter.js b/src/components/DatasetsListViewer/DatasetsListSplinter.js
index 553ae3d..c3284f0 100644
--- a/src/components/DatasetsListViewer/DatasetsListSplinter.js
+++ b/src/components/DatasetsListViewer/DatasetsListSplinter.js
@@ -254,7 +254,7 @@ class Splinter {
dataset_node.proxies = dataset_node.proxies.concat(ontology_node.proxies);
dataset_node.level = 1;
this.nodes.set(dataset_node.id, dataset_node);
- this.nodes.delete(ontology_node.id);
+ // this.nodes.delete(ontology_node.id);
// fix links that were pointing to the ontology
let temp_edges = this.edges.map(link => {
if (link.source === ontology_node.id) {
diff --git a/src/components/GraphViewer/GraphViewer.js b/src/components/GraphViewer/GraphViewer.js
index a347bf9..f07b6a2 100644
--- a/src/components/GraphViewer/GraphViewer.js
+++ b/src/components/GraphViewer/GraphViewer.js
@@ -45,9 +45,11 @@ const GraphViewer = (props) => {
const [loading, setLoading] = React.useState(false);
const [data, setData] = React.useState({ nodes : [], links : []});
const nodeSelected = useSelector(state => state.sdsState.instance_selected.graph_node);
+ const nodeClickSource = useSelector(state => state.sdsState.instance_selected.source);
const groupSelected = useSelector(state => state.sdsState.group_selected.graph_node);
const [collapsed, setCollapsed] = React.useState(true);
const [previouslySelectedNodes, setPreviouslySelectedNodes] = useState(new Set());
+ let triggerCenter = false;
const handleLayoutClick = (event) => {
setLayoutAnchorEl(event.currentTarget);
@@ -70,7 +72,11 @@ const GraphViewer = (props) => {
if ( node.type === rdfTypes.Subject.key || node.type === rdfTypes.Sample.key || node.type === rdfTypes.Collection.key ) {
node.collapsed = !node.collapsed;
collapseSubLevels(node, node.collapsed, { links : 0 });
- const updatedData = getPrunedTree(props.graph_id, selectedLayout.layout);
+ let updatedData = getPrunedTree(props.graph_id, selectedLayout.layout);
+ setData(updatedData);
+
+ collapseSubLevels(node, true, { links : 0 });
+ updatedData = getPrunedTree(props.graph_id, selectedLayout.layout);
setData(updatedData);
}
handleNodeHover(node);
@@ -115,7 +121,7 @@ const GraphViewer = (props) => {
setCollapsed(!collapsed)
setTimeout( () => {
resetCamera();
- },100)
+ },200)
}
/**
@@ -168,6 +174,10 @@ const GraphViewer = (props) => {
const onEngineStop = () => {
setForce();
+ if ( triggerCenter ) {
+ graphRef?.current?.ggv?.current.centerAt(selectedNode.x, selectedNode.y, ONE_SECOND);
+ triggerCenter = false;
+ }
}
useEffect(() => {
@@ -209,7 +219,7 @@ const GraphViewer = (props) => {
});
useEffect(() => {
- if ( groupSelected ) {
+ if ( groupSelected && groupSelected?.dataset_id?.includes(props.graph_id)) {
setSelectedNode(groupSelected);
handleNodeHover(groupSelected);
graphRef?.current?.ggv?.current.centerAt(groupSelected.x, groupSelected.y, ONE_SECOND);
@@ -224,26 +234,46 @@ const GraphViewer = (props) => {
}, [selectedNode]);
useEffect(() => {
- if ( nodeSelected ) {
+ if ( nodeSelected && ( nodeSelected?.tree_reference?.dataset_id?.includes(props.graph_id) ||
+ nodeSelected?.dataset_id?.includes(props.graph_id) )) {
if ( nodeSelected?.id !== selectedNode?.id ){
let node = nodeSelected;
- let collapsed = nodeSelected.collapsed
- while ( node?.parent && !collapsed ) {
- node = node.parent;
- collapsed = node.collapsed
+ let collapsed = node.collapsed
+ let parent = node.parent;
+ let prevNode = node;
+ while ( parent && parent?.collapsed ) {
+ prevNode = parent;
+ parent = parent.parent;
}
- if ( collapsed ) {
- node.collapsed = !node.collapsed;
- collapseSubLevels(node, node.collapsed, { links : 0 });
- const updatedData = getPrunedTree(props.graph_id, selectedLayout.layout);
- setData(updatedData);
+
+ if ( prevNode && nodeSelected.collapsed && nodeClickSource === "TREE") {
+ if ( prevNode.type == rdfTypes.Subject.key || prevNode.type == rdfTypes.Sample.key ||
+ prevNode.type == rdfTypes.Collection.key ) {
+ prevNode.collapsed = false;
+ collapseSubLevels(prevNode, false, { links : 0 });
+ let updatedData = getPrunedTree(props.graph_id, selectedLayout.layout);
+ setData(updatedData);
+ }
+ if ( node.parent?.type == rdfTypes.Subject.key || node.parent?.type == rdfTypes.Sample.key ||
+ node.parent?.type == rdfTypes.Collection.key ) {
+ collapseSubLevels(node.parent, true, { links : 0 });
+ let updatedData = getPrunedTree(props.graph_id, selectedLayout.layout);
+ setData(updatedData);
+ } else {
+ collapseSubLevels(node, true, { links : 0 });
+ let updatedData = getPrunedTree(props.graph_id, selectedLayout.layout);
+ setData(updatedData);
+ }
}
setSelectedNode(nodeSelected);
handleNodeHover(nodeSelected);
- graphRef?.current?.ggv?.current.centerAt(nodeSelected.x, nodeSelected.y, 10);
+ graphRef?.current?.ggv?.current.centerAt(nodeSelected.x, nodeSelected.y, ONE_SECOND);
} else {
handleNodeHover(nodeSelected);
+ graphRef?.current?.ggv?.current.centerAt(nodeSelected.x, nodeSelected.y, ONE_SECOND);
}
+ const divElement = document.getElementById(nodeSelected.id + detailsLabel);
+ divElement?.scrollIntoView({ behavior: 'smooth' });
}
},[nodeSelected])
diff --git a/src/components/NodeDetailView/Details/DatasetDetails.js b/src/components/NodeDetailView/Details/DatasetDetails.js
index c2bfe4a..5e9d0a0 100644
--- a/src/components/NodeDetailView/Details/DatasetDetails.js
+++ b/src/components/NodeDetailView/Details/DatasetDetails.js
@@ -64,7 +64,9 @@ const DatasetDetails = (props) => {
-
+
+ { property.link?.asText ? {value} : }
+
)
}
diff --git a/src/components/Sidebar/TreeView/InstancesTreeView.js b/src/components/Sidebar/TreeView/InstancesTreeView.js
index 5738f0f..65da03d 100644
--- a/src/components/Sidebar/TreeView/InstancesTreeView.js
+++ b/src/components/Sidebar/TreeView/InstancesTreeView.js
@@ -31,7 +31,7 @@ const InstancesTreeView = (props) => {
} else {
dispatch(selectInstance({
dataset_id: dataset_id,
- graph_node: node?.graph_reference?.id,
+ graph_node: node?.graph_reference?.id || node?.id,
tree_node: node?.id,
source: TREE_SOURCE
}));
diff --git a/src/config/app.json b/src/config/app.json
index 761251d..13eab97 100644
--- a/src/config/app.json
+++ b/src/config/app.json
@@ -17,5 +17,6 @@
"datasetsButtonText" : "Import a new dataset",
"datasetsDialogSearchText" : "Search datasets by label or id",
"datasetsButtonSubtitleText" : "Select a dataset to load"
- }
+ },
+ "datasetsStorage" : "publishedDatasets"
}
\ No newline at end of file
diff --git a/src/redux/initialState.js b/src/redux/initialState.js
index ce740f1..ccf8d90 100644
--- a/src/redux/initialState.js
+++ b/src/redux/initialState.js
@@ -132,9 +132,6 @@ export default function sdsClientReducer(state = {}, action) {
if (a.visible === b.visible) {
// Preserve the original order for items with the same visibility
return updatedMetadataModel[groupTitle].indexOf(a) - updatedMetadataModel[groupTitle].indexOf(b);
- } else {
- // Move visible items to the top
- return a.visible ? -1 : 1;
}
});
}
diff --git a/src/utils/GraphViewerHelper.js b/src/utils/GraphViewerHelper.js
index f7109d6..5dad9cf 100644
--- a/src/utils/GraphViewerHelper.js
+++ b/src/utils/GraphViewerHelper.js
@@ -1,6 +1,7 @@
import React, {useCallback} from 'react';
import { rdfTypes } from './graphModel';
import { current } from '@reduxjs/toolkit';
+import * as d3 from 'd3';
export const NODE_FONT = '500 5px Inter, sans-serif';
export const ONE_SECOND = 1000;
@@ -156,7 +157,7 @@ export const paintNode = (node, ctx, hoverNode, selectedNode, nodeSelected, prev
ctx.fillText(...textProps);
if ( node.childLinks?.length && node.collapsed ) {
let children = { links : 0 };
- collapseSubLevels(node, undefined, children)
+ collapseSubLevels(node, true, children)
const collapsedNodes = [children.links, node.x, textHoverPosition[1]];
ctx.fillStyle = GRAPH_COLORS.collapsedFolder;
ctx.textAlign = 'center';
@@ -168,207 +169,28 @@ export const paintNode = (node, ctx, hoverNode, selectedNode, nodeSelected, prev
export const collapseSubLevels = (node, collapsed, children) => {
node?.childLinks?.forEach( n => {
- if ( collapsed !== undefined ) n.target.collapsed = collapsed;
- collapseSubLevels(n.target, collapsed, children);
- children.links = children.links + 1;
+ if ( collapsed !== undefined ) {
+ n.target.collapsed = collapsed;
+ collapseSubLevels(n.target, collapsed, children);
+ children.links = children.links + 1;
+ }
});
}
-export const determineNodePosition = (positionsMap, levels, n, targetCoord) => {
- let nearestParentNeighbor = null;
- let nearestParentNeighborIndex = 0;
- let leftParentMatch = false;
- let firstParentCollection = null;
- let firstParentCollectionIndex = 0;
- sortArray(levels[n.level - 1])
- levels[n.level-1]?.forEach( ( node, index ) => {
- if ( !leftParentMatch && (node.type == "Collection" || node.type == "Sample") && node.id !== n.parent.id && !node.collapsed ) {
- firstParentCollection = node;
- firstParentCollectionIndex = index;
- }
-
- if ( !leftParentMatch && node.id === n.parent?.id) {
- leftParentMatch = true;
- nearestParentNeighbor = node;
- nearestParentNeighborIndex = index;
- }
- })
-
- let nodesInBetween = Math.floor((( firstParentCollection?.neighbors?.length - 1) + ( nearestParentNeighbor?.neighbors?.length - 1)) /2 ) - 1;
- if ( firstParentCollection === nearestParentNeighbor ) {
- nodesInBetween = Math.floor((( nearestParentNeighbor?.neighbors?.length - 1)) /2 );
- } else if ( firstParentCollection == null ) {
- nodesInBetween = Math.floor((( nearestParentNeighbor?.neighbors?.length - 1)) /2 );
- }
-
- let spacesNeeded = nearestParentNeighborIndex - firstParentCollectionIndex;
-
- let nearestNeighbor = null;
- let nearestNeighborIndex = 0;
- let leftMatch = false;
- let leftMatchIndex = 0;
- let firstCollection = null;
- levels[n.level]?.forEach( ( node, index ) => {
- if ( leftMatch && nearestNeighbor == null && ( node.type == "Collection" || node.type == "Sample" )&& !node.collapsed) {
- nearestNeighbor = node;
- nearestNeighborIndex = index;
- }
-
- if ( !leftMatch ) {
- firstCollection = n;
- }
-
- if ( !leftMatch && node.id === n.id ) {
- leftMatch = true;
- leftMatchIndex = index - 1;
- }
- })
-
- let position = positionsMap[n.level];
- if ( !isNaN(nodesInBetween) && spacesNeeded - 1 > nodesInBetween) {
- let neighbors = n?.parent?.neighbors;
- let matchNeighbor = neighbors.findIndex( (node) => node.id === n.id ) ;;
- if ( matchNeighbor === 1 && n.level === Object.keys(levels)?.length ) {
- position = position + (Math.abs( spacesNeeded - nodesInBetween) * nodeSpace)
- } else {
- position = position + nodeSpace
- }
- } else if ( nearestNeighborIndex > 0 ) {
- if ( nearestNeighbor != null && !n.collapsed ){
- let middleNode = nearestNeighbor.neighbors?.[Math.floor(nearestNeighbor.neighbors?.length / 2)]?.[targetCoord];
- if ( middleNode ) {
- position = middleNode
- }
- position = position - ( (nearestNeighborIndex - leftMatchIndex - 1) * nodeSpace)
- } else if ( n.collapsed ) {
- position = nearestNeighborIndex?.[targetCoord] ? nearestNeighborIndex?.[targetCoord] - ( -1 * nodeSpace) : position - ( -1 * nodeSpace)
- } else if ( nearestNeighborIndex - leftMatchIndex > 0 ) {
- position = position - ( nodeSpace)
- } else {
- position = (position + nodeSpace)
- }
- } else if ( n.collapsed) {
- position = nearestNeighborIndex?.[targetCoord] ? nearestNeighborIndex?.[targetCoord] - ( -1 * nodeSpace): position - ( -1 * nodeSpace)
- } else {
- position = position + nodeSpace
- }
- positionsMap[n.level] = position;
- return position
-}
-const sortArray = (arrayToSort) => {
- arrayToSort?.sort( (a, b) => {
- if ( a?.attributes?.relativePath && b?.attributes?.relativePath) {
- let aParent = a;
- let aPath= "";
- while ( aParent?.type != "Subject" ){
- aParent = aParent?.parent;
- if ( aParent?.attributes?.relativePath ){
- aPath = aPath + "/" + aParent?.attributes?.relativePath
- }
- }
- let bParent = b;
- let bPath = ""
- while ( bParent?.type != "Subject"){
- bParent = bParent?.parent;
- if ( bParent?.attributes?.relativePath ){
- bPath = bPath + "/" +bParent?.attributes?.relativePath
- }
- }
- aPath = (aParent?.id ) + "/" + aPath
- bPath = (bParent?.id ) + "/" + bPath
- return aPath.localeCompare(bPath);
- } else {
- return a?.id.localeCompare(b.id);
- }
- });
+const hierarchy = (data) =>{
+ return d3.hierarchy(data);
}
-/**
- * Algorithm used to position nodes in a Tree. Position depends on the layout,
- * either Tree or Vertical Layout views.
- * @param {*} levels - How many levels we need for this tree. This depends on the dataset subjects/samples/folders/files.
- * @param {*} layout
- * @param {*} furthestLeft
- */
-export const algorithm = (levels, layout, furthestLeft) => {
- let positionsMap = {};
- let levelsMapKeys = Object.keys(levels);
-
- levelsMapKeys.forEach( level => {
- furthestLeft = 0 - (Math.ceil(levels[level].length)/2 * nodeSpace );
- positionsMap[level] = furthestLeft + nodeSpace;
- sortArray(levels[level]);
- });
-
- // Start assigning the graph from the bottom up
- let neighbors = 0;
- levelsMapKeys.reverse().forEach( level => {
- let collapsedInLevel = levels[level].filter( n => n.collapsed);
- let notcollapsedInLevel = levels[level].filter( n => !n.collapsed);
- levels[level].forEach ( (n, index) => {
- neighbors = n?.neighbors?.filter(neighbor => { return neighbor.level > n.level });
- if ( !n.collapsed ) {
- if ( neighbors?.length > 0 ) {
- let max = Number.MIN_SAFE_INTEGER, min = Number.MAX_SAFE_INTEGER;
- neighbors.forEach( neighbor => {
- if ( layout === TOP_DOWN.layout ) {
- if ( neighbor.xPos > max ) { max = neighbor.xPos };
- if ( neighbor.xPos <= min ) { min = neighbor.xPos };
- } else if ( layout === LEFT_RIGHT.layout ) {
- if ( neighbor.yPos > max ) { max = neighbor.yPos };
- if ( neighbor.yPos <= min ) { min = neighbor.yPos };
- }
- });
- if ( layout === TOP_DOWN.layout ) {
- n.xPos = min === max ? min : min + ((max - min) * .5);
- } else if ( layout === LEFT_RIGHT.layout ) {
- n.yPos = min === max ? min : min + ((max - min) * .5);
- }
- if ( notcollapsedInLevel?.length > 0 && collapsedInLevel.length > 0) {
- if ( n.type === "Subject" || n.parent?.type === "Subject" || n.parent?.parent?.type === "Subject" ) {
- updateConflictedNodes(levels[level], n, positionsMap, level, index, layout);
- }
- }
-
- if ( layout === TOP_DOWN.layout ) {
- n.fx = n.xPos;
- n.fy = 50 * n.level;
- positionsMap[n.level] = n.xPos + nodeSpace;
- } else if ( layout === LEFT_RIGHT.layout ) {
- n.fy = n.yPos;
- n.fx = 50 * n.level;
- positionsMap[n.level] = n.yPos + nodeSpace;
- }
- } else {
- if ( layout === TOP_DOWN.layout ) {
- let position = determineNodePosition(positionsMap, levels, n, "xPos");
- n.xPos = position;
- n.fx = n.xPos;
- n.fy = 50 * n.level;
- } else if ( layout === LEFT_RIGHT.layout ) {
- let position = determineNodePosition(positionsMap, levels, n, "yPos");
- n.yPos = position;
- n.fy = n.yPos;
- n.fx = 50 * n.level;
- }
- }
- }else {
- if ( layout === TOP_DOWN.layout ) {
- let position = determineNodePosition(positionsMap, levels, n, "xPos");
- n.xPos = position;
- n.fx = n.xPos;
- n.fy = 50 * n.level;
- } else if ( layout === LEFT_RIGHT.layout ) {
- let position = determineNodePosition(positionsMap, levels, n, "yPos");
- n.yPos = position;
- n.fy = n.yPos;
- n.fx = 50 * n.level;
- }
- }
- })
+const dendrogram = (data) => {
+ const dendrogramGenerator = d3.cluster().nodeSize([1, 100])
+ .separation(function(a,b){
+ return 1 + d3.sum([a,b].map(function(d){
+ return 15
+ }))
});
- }
+ return dendrogramGenerator(hierarchy(data));
+}
/**
* Create Graph ID
@@ -412,59 +234,72 @@ export const getPrunedTree = (graph_id, layout) => {
levels[n.level] = [n];
}
})
-
- // Calculate level with max amount of nodes
- let maxLevel = Object.keys(levels).reduce((a, b) => levels[a].length > levels[b].length ? a : b);
- let maxLevelNodes = levels[maxLevel];
- // The furthestLeft a node can be
- let furthestLeft = 0 - (Math.ceil(maxLevelNodes.length)/2 * nodeSpace );
+
+ // Calculate level with max amount of nodes
+ let maxLevel = parseInt(Object.keys(levels).reduce((a, b) => levels[a].length > levels[b].length ? a : b));
- algorithm(levels, layout, furthestLeft);
+
+ let root = levelsMap["1"]?.[0];
- const graph = { nodes : visibleNodes, links : visibleLinks, levelsMap : levelsMap, hierarchyVariant : maxLevel * 20 };
- return graph;
- };
+ let data = {
+ type : "node",
+ name : root?.id,
+ value : levelsMap["1"]?.[0]?.level - 1,
+ children : []
+ };
- /**
- * Update Nodes x and y position, used for vertical and tree view layouts.
- * @param {*} nodes - The nodes we have for the dataset
- * @param {*} conflictNode - Conflicting Node that needs re positioning
- * @param {*} positionsMap - Object keeping track of positions of nodes
- * @param {*} level - level of tree
- * @param {*} index - Index of conflict node in this tree level
- * @param {*} layout - The layout we are using to display these nodes
- */
- const updateConflictedNodes = (nodes, conflictNode, positionsMap, level, index, layout) => {
- let matchIndex = index;
- for ( let i = 0; i < index ; i++ ) {
- let conflict = nodes.find ( n => !n.collapsed && n?.parent?.id === nodes[i]?.parent?.id)
- if ( conflict === undefined ){
- conflict = nodes.find ( n => !n.collapsed )
- if ( conflict === undefined ){
- conflict = conflictNode;
- }
+ function traverse(node, data) {
+ if (node === null) {
+ return;
}
- matchIndex = nodes.findIndex( n => n.id === conflict.id );
+ node.neighbors?.forEach( n => {
+ if ( visibleNodes?.find( node => node.id === n.id ) ) {
+ if ( n.neighbors?.length > 1 ) {
+ if ( n?.level > node.level ) {
+ let node = {
+ type : "node",
+ name : n.id,
+ value : n?.level - 1,
+ children : []
+ }
+ data.children.push(node);
+ traverse(n, node)
+ }
+ } else {
+ data.children.push({type : "leaf",
+ name : n.id,
+ value : n?.level - 1})
+ }
+ }
+ });
+ }
+
+ traverse(root,data)
+
+ // Use D3 cluster to give position to nodes
+ const allNodes = dendrogram(data).descendants();
+ let mapNodes = {};
+ allNodes.forEach( n => mapNodes[n.data?.name] = n );
+
+ // Assign position of nodes
+ visibleNodes.forEach( n => {
if ( layout === TOP_DOWN.layout ) {
- let furthestLeft = conflict?.xPos;
- if ( nodes?.[i]?.collapsed ) {
- furthestLeft = conflict.xPos - ((((matchIndex - i )/2)) * nodeSpace );
- nodes[i].xPos =furthestLeft;
- positionsMap[level] = furthestLeft + nodeSpace;
+ if ( mapNodes[n.id] ) {
+ n.xPos = mapNodes[n.id].x
+ n.fx = n.xPos;
+ n.fy = 50 * n.level;
}
- nodes[i].fx = nodes[i].xPos;
- nodes[i].fy = 50 * nodes[i].level;
- } else if ( layout === LEFT_RIGHT.layout ) {
- let furthestLeft = conflict?.yPos;
- if ( nodes[i].collapsed ) {
- furthestLeft = conflict.yPos - ((((matchIndex - i )/2)) * nodeSpace );
- nodes[i].yPos =furthestLeft;
+ }
+ if ( layout === LEFT_RIGHT.layout ) {
+ if ( mapNodes[n.id] ) {
+ n.yPos = mapNodes[n.id].x
+ n.fy = n.yPos;
+ n.fx = 50 * n.level;
}
- positionsMap[level] = furthestLeft + nodeSpace;
- nodes[i].fy = nodes[i].yPos;
- positionsMap[level] = nodes[i].fy + nodeSpace;
- nodes[i].fx = 50 * nodes[i].level;
}
- }
- }
\ No newline at end of file
+ })
+
+ const graph = { nodes : visibleNodes, links : visibleLinks, levelsMap : levelsMap, hierarchyVariant : maxLevel * 20 };
+ return graph;
+ };
diff --git a/src/utils/Splinter.js b/src/utils/Splinter.js
index f68ca03..625e996 100644
--- a/src/utils/Splinter.js
+++ b/src/utils/Splinter.js
@@ -14,6 +14,7 @@ import {
protocols_key,
contributors_key, SUBJECTS_LEVEL, PROTOCOLS_LEVEL, CRONTRIBUTORS_LEVEL
} from '../constants';
+import * as d3 from 'd3';
const N3 = require('n3');
const ttl2jsonld = require('@frogcat/ttl2jsonld').parse;
@@ -231,7 +232,11 @@ class Splinter {
if ( !existingLing ) {
const a = this.nodes.get( link.source );
const b = this.nodes.get( link.target );
- if ( a && b && ( a?.type !== rdfTypes.Award.key && b?.type !== rdfTypes.Award.key ) &&!((a?.type === rdfTypes.Collection.key && a.children_counter < 1 ) || ( b?.type === rdfTypes.Collection.key && b.children_counter < 1))) {
+ const awardEmpty = ( a?.type !== rdfTypes.Award.key && b?.type !== rdfTypes.Award.key );
+ const collectionEmpty = ((a?.type === rdfTypes.Collection.key && a.children_counter < 1 ) || ( b?.type === rdfTypes.Collection.key && b.children_counter < 1));
+ const sampleEmpty = ((a?.type === rdfTypes.Sample.key && a.children_counter < 1 ) || ( b?.type === rdfTypes.Sample.key && b.children_counter < 1))
+ const sameLevels = a?.level === b?.level;
+ if ( a && b && awardEmpty && !collectionEmpty && !sampleEmpty && !sameLevels) {
!a.neighbors && (a.neighbors = []);
!b.neighbors && (b.neighbors = []);
if ( !a.neighbors.find( n => n.id === b.id )){
@@ -256,10 +261,18 @@ class Splinter {
}
}
});
- console.log("This levels map ", this.levelsMap)
+
+ let newCleanLinks = cleanLinks.filter(link => {
+
+ const collectionEmpty = ((link?.target?.type === rdfTypes.Collection.key && link?.target?.neighbors?.length <= 1 ) || ( link?.source?.type === rdfTypes.Collection.key && link?.source?.neighbors?.length <= 1));
+ if ( collectionEmpty ) {
+ return false;
+ }
+ return true;
+ });
return {
nodes: filteredNodes,
- links: cleanLinks,
+ links: newCleanLinks,
levelsMap : this.levelsMap
};
}
@@ -679,11 +692,11 @@ class Splinter {
target_node.level = protocols.level + 1;
target_node.parent = protocols;
this.nodes.set(target_node.id, target_node);
- } else if (link.source === id && target_node.type === rdfTypes.Sample.key) {
- // link.source = target_node.attributes.derivedFrom[0];
- // target_node.level = subjects.level + 2;
- // target_node.parent = this.nodes.get(target_node.attributes.derivedFrom[0]);
- // this.nodes.set(target_node.id, target_node);
+ } else if (link.source === id && target_node.type === rdfTypes.Sample.key ) {
+ link.source = target_node.attributes.derivedFrom[0];
+ target_node.level = subjects.level + 2;
+ target_node.parent = this.nodes.get(target_node.attributes.derivedFrom[0]);
+ this.nodes.set(target_node.id, target_node);
}
let source_node = this.nodes.get(link.source);
if ( source_node?.childLinks ) {
@@ -716,6 +729,27 @@ class Splinter {
}
}
}
+
+ if (node.type === rdfTypes.Sample.key) {
+ if (node.attributes.derivedFrom !== undefined) {
+ let source = this.nodes.get(node.attributes.derivedFrom[0]);
+ if ( source !== undefined ) {
+ source.children_counter++
+ array[index].level = source.level + 1;
+ this.forced_edges.push({
+ source: node.attributes.derivedFrom[0],
+ target: node.id
+ });
+ }
+ }
+
+ if (node.attributes?.hasFolderAboutIt !== undefined) {
+ node.attributes.hasFolderAboutIt =
+ [Array.from(this.nodes)[0][1].attributes.hasUriPublished[0] +
+ "?datasetDetailsTab=files&path=files/" +
+ node.tree_reference?.dataset_relative_path];
+ }
+ }
if (node.type === rdfTypes.Subject.key) {
if (node.attributes?.animalSubjectIsOfStrain !== undefined) {
@@ -863,59 +897,59 @@ class Splinter {
}
- mergeData() {
+ mergeData() {
this.nodes.forEach((value, key) => {
if (value.attributes !== undefined && value.attributes.hasFolderAboutIt !== undefined) {
value.attributes.hasFolderAboutIt.forEach(folder => {
let jsonNode = this.tree_map.get(folder);
const splitName = jsonNode.dataset_relative_path.split('/');
let newName = jsonNode.basename;
- if ( value.type == "Subject" && value.attributes?.localId?.[0] == splitName[splitName.length - 1] ) {
+ if ( value.type === rdfTypes.Subject.key && value.attributes?.localId?.[0] == splitName[splitName.length - 1] ) {
newName = splitName[0]
}
- let parentNode = value;
- if ( value.type == "Sample" && value.attributes?.localId?.[0] == splitName[splitName.length - 1] ) {
- newName = splitName[0] + "/" + newName
- parentNode = this.nodes.get(value.attributes.derivedFrom[0])
- if (parentNode?.attributes !== undefined && parentNode?.attributes?.hasFolderAboutIt !== undefined) {
- parentNode?.attributes?.hasFolderAboutIt.forEach(folder => {
- let jNode = this.tree_map.get(folder);
- this.tree_parents_map2.delete(jNode.remote_id);
- })
- }
+ if ( value.type === rdfTypes.Sample.key && value.attributes?.localId?.[0] == splitName[splitName.length - 1] ) {
+ newName = splitName[0] + "/" + newName
}
+ let parentNode = value;
let newNode = this.buildFolder(jsonNode, newName, parentNode);
- let folderChildren = this.tree_parents_map2.get(newNode.parent_id)?.map(child => {
- child.parent_id = newNode.uri_api
- return child;
- });
+ if ( value.type === rdfTypes.Sample.key) {
+ newNode.remote_id = jsonNode.basename + '_' + newName;
+ newNode.uri_api = newNode.remote_id
+ // this.tree_parents_map2.delete(jsonNode.remote_id);
+ }
- if (!this.filterNode(newNode) && (this.nodes.get(newNode.remote_id)) === undefined) {
- this.linkToNode(newNode, parentNode);
- }
- if (this.tree_parents_map2.get(newNode.uri_api) === undefined) {
- this.tree_parents_map2.set(newNode.uri_api, folderChildren);
- this.tree_parents_map2.delete(newNode.parent_id);
- folderChildren?.forEach(child => {
- if (!this.filterNode(child) ) {
- this.linkToNode(child, this.nodes.get(newNode.remote_id));
- }
- });
- } else {
- let tempChildren = folderChildren === undefined ? [...this.tree_parents_map2.get(newNode.uri_api)] : [...this.tree_parents_map2.get(newNode.uri_api), ...folderChildren];;
- this.tree_parents_map2.set(newNode.uri_api, tempChildren);
- this.tree_parents_map2.delete(newNode.parent_id);
- tempChildren?.forEach(child => {
- if (!this.filterNode(child) ) {
- this.linkToNode(child, this.nodes.get(newNode.remote_id));
- }
- });
- }
- //}
+ let folderChildren = this.tree_parents_map2.get(newNode.parent_id)?.map(child => {
+ child.parent_id = newNode.uri_api
+ child.collapsed = true;
+ return child;
+ });
+
+ if (!this.filterNode(newNode) && (this.nodes.get(newNode.remote_id)) === undefined) {
+ this.linkToNode(newNode, parentNode);
+ }
+
+ if (this.tree_parents_map2.get(newNode.uri_api) === undefined) {
+ this.tree_parents_map2.set(newNode.uri_api, folderChildren);
+ this.tree_parents_map2.delete(newNode.parent_id);
+ folderChildren?.forEach(child => {
+ if (!this.filterNode(child) ) {
+ this.linkToNode(child, this.nodes.get(newNode.remote_id));
+ }
+ });
+ } else {
+ let tempChildren = folderChildren === undefined ? [...this.tree_parents_map2.get(newNode.uri_api)] : [...this.tree_parents_map2.get(newNode.uri_api), ...folderChildren];;
+ this.tree_parents_map2.set(newNode.uri_api, tempChildren);
+ this.tree_parents_map2.delete(newNode.parent_id);
+ tempChildren?.forEach(child => {
+ if (!this.filterNode(child) ) {
+ this.linkToNode(child, this.nodes.get(newNode.remote_id));
+ }
+ });
+ }
})
}
});
@@ -937,17 +971,16 @@ class Splinter {
level = this.nodes.get(parent.attributes.derivedFrom[0])?.level + 1;
}
}
+ const new_node = this.buildNodeFromJson(node, level);
if ( parent ) {
parent.children_counter++;
- const new_node = this.buildNodeFromJson(node, level);
new_node.parent = parent;
-
+ new_node.id = parent.id + new_node.id;
this.forced_edges.push({
source: parent?.id,
target: new_node?.id
});
new_node.childLinks = [];
- new_node.collapsed = new_node.type === typesModel.NamedIndividual.subject.type
if ( !this.nodes.get(new_node.id) ) {
this.nodes.set(new_node.id, this.factory.createNode(new_node));
var children = this.tree_parents_map2.get(node.remote_id);
@@ -1091,4 +1124,4 @@ class Splinter {
}
}
-export default Splinter;
+export default Splinter;
\ No newline at end of file
diff --git a/src/utils/graphModel.js b/src/utils/graphModel.js
index b25d42e..7a1d2b8 100644
--- a/src/utils/graphModel.js
+++ b/src/utils/graphModel.js
@@ -22,6 +22,12 @@ export const rdfTypes = {
"key": "hasUriHuman",
"property": "hasUriHuman",
"label": "To be filled"
+ },
+ {
+ "type" : "owl",
+ "key" : "versionInfo",
+ "property" : "versionInfo",
+ "label" : "Version"
}
]
},
@@ -122,7 +128,8 @@ export const rdfTypes = {
"label": "Title",
"visible" : true,
"link" : {
- "property" : "hasUriPublished"
+ "property" : "hasUriPublished",
+ "asText" : true
}
},
{
@@ -202,7 +209,8 @@ export const rdfTypes = {
"label": "DOI",
"visible" : true,
"link" : {
- "property" : "hasUriPublished"
+ "property" : "hasUriPublished",
+ "asText" : true
}
},
{
@@ -714,7 +722,7 @@ export const rdfTypes = {
"type": "TEMP",
"key": "hasFolderAboutIt",
"property": "hasFolderAboutIt",
- "label": "Related Folder",
+ "label": "Find in SPARC Portal",
"visible" : true
},
{
@@ -722,7 +730,7 @@ export const rdfTypes = {
"key": "wasDerivedFromSubject",
"property": "derivedFrom",
"label": "Derived from Subject",
- "visible" : true
+ "visible" : false
},
{
"type": "TEMP",
@@ -743,7 +751,7 @@ export const rdfTypes = {
"key": "hasDerivedInformationAsParticipant",
"property": "hasDerivedInformationAsParticipant",
"label": "Derived Information as Participant",
- "visible" : true
+ "visible" : false
},
{
"type": "TEMP",
diff --git a/yarn.lock b/yarn.lock
index 1081921..c37197d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5321,6 +5321,13 @@ css-blank-pseudo@^0.1.4:
dependencies:
postcss "^7.0.5"
+css-box-model@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
+ integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
+ dependencies:
+ tiny-invariant "^1.0.6"
+
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
@@ -11733,7 +11740,7 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
-memoize-one@^5.0.0:
+memoize-one@^5.0.0, memoize-one@^5.1.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
@@ -14423,6 +14430,11 @@ quickselect@^2.0.0:
resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
+raf-schd@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
+ integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
+
raf@^3.2.0, raf@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
@@ -14494,6 +14506,19 @@ react-app-polyfill@^2.0.0:
regenerator-runtime "^0.13.7"
whatwg-fetch "^3.4.1"
+react-beautiful-dnd@^13.1.1:
+ version "13.1.1"
+ resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2"
+ integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+ css-box-model "^1.2.0"
+ memoize-one "^5.1.1"
+ raf-schd "^4.0.2"
+ react-redux "^7.2.0"
+ redux "^4.0.4"
+ use-memo-one "^1.1.1"
+
react-color@^2.17.3:
version "2.19.3"
resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d"
@@ -14792,6 +14817,18 @@ react-redux@^5.0.6:
react-is "^16.6.0"
react-lifecycles-compat "^3.0.0"
+react-redux@^7.2.0:
+ version "7.2.9"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d"
+ integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==
+ dependencies:
+ "@babel/runtime" "^7.15.4"
+ "@types/react-redux" "^7.1.20"
+ hoist-non-react-statics "^3.3.2"
+ loose-envify "^1.4.0"
+ prop-types "^15.7.2"
+ react-is "^17.0.2"
+
react-redux@^7.2.4:
version "7.2.8"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de"
@@ -17302,6 +17339,11 @@ tiny-invariant@^1.0.2:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
+tiny-invariant@^1.0.6:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
+ integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
+
tiny-warning@^1.0.0, tiny-warning@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
@@ -17929,6 +17971,11 @@ url@^0.11.0, url@~0.11.0:
punycode "1.3.2"
querystring "0.2.0"
+use-memo-one@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99"
+ integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==
+
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"