diff --git a/frontend/dist_widgets/README.md b/frontend/dist_widgets/README.md index c89f6f756..dec0d7e92 100644 --- a/frontend/dist_widgets/README.md +++ b/frontend/dist_widgets/README.md @@ -19,7 +19,7 @@ After installation, you can use OLS4 Widgets in your project by including the ne initializing the widget with a simple javaScript command. Here's a quick example of how to display the chebi tree in a **React** application: -1. Import the dependencies in the js file you want to render the component: +## 1. Import the dependencies in the js file you want to render the component: ```javascript import '@ebi-ols/ols4-widgets/treestyles.css' @@ -27,7 +27,7 @@ import { useEffect, useRef } from 'react'; import { createEntityTree } from '@ebi-ols/ols4-widgets/ols4_widgets'; ``` -2. Create the function to render the tree: +## 2. Create the function to render the tree: ```javascript function EntityTree() { @@ -49,7 +49,24 @@ function EntityTree() { ``` **NOTE:** The main point to notice here is the ontologyId and the apiUrl. The ontologyId is the id of the ontology you want to display and the apiUrl is the base url of the OLS4 API. -3. Add the element where you want the tree to appear in your HTML file: +### Partial rendering of the tree + +You can specify a term uri e.g. water (http://purl.obolibrary.org/obo/CHEBI_15377) as a prop to the `createEntityTree` function to render a partial tree. If you specify this prop i.e. `specifiedRootIri`, +the tree will render starting from the specified term. + +```javascript +useEffect(() => { + if(div.current) { + createEntityTree({ + ontologyId: "chebi", + specifiedRootIri: "http://purl.obolibrary.org/obo/CHEBI_15377", + apiUrl: "https://www.ebi.ac.uk/ols4/" + }, div.current); + } + }, [div]) +``` + +## 3. Add the element where you want the tree to appear in your HTML file: ```javascript function App() { @@ -60,12 +77,17 @@ return ( ); } ``` -### Example of the rendered tree +### Example of the complete ontology rendered tree ![chebi-tree-render](https://github.com/EBISPOT/ols4/assets/13108541/b9e14c70-6be0-4007-8311-91605087d5ad) +### Example of the partial ontology rendered tree by providing a specified uri of water (http://purl.obolibrary.org/obo/CHEBI_15377) + +![Screenshot 2024-04-23 at 13 25 11](https://github.com/EBISPOT/ols4/assets/13108541/78a09a8b-8b25-49ff-8b2e-7469c811eaee) + ## Features - Easy to integrate with any web application. +- Full and partial ontology tree rendering. - Lightweight and fast. \ No newline at end of file diff --git a/frontend/dist_widgets/package.json b/frontend/dist_widgets/package.json index f394e9fda..ed3d3a93c 100644 --- a/frontend/dist_widgets/package.json +++ b/frontend/dist_widgets/package.json @@ -1,7 +1,7 @@ { "name": "@ebi-ols/ols4-widgets", - "version": "1.0.2", + "version": "1.1.0", "type": "module", "main": "ols4_widgets.js", "types": "manually_maintained_types.d.ts" -} +} \ No newline at end of file diff --git a/frontend/src/pages/ontologies/entities/EntityTree.tsx b/frontend/src/pages/ontologies/entities/EntityTree.tsx index 36f0a2236..0ae34afed 100644 --- a/frontend/src/pages/ontologies/entities/EntityTree.tsx +++ b/frontend/src/pages/ontologies/entities/EntityTree.tsx @@ -31,11 +31,14 @@ import { showCounts, showObsolete, showSiblings, + setSpecificRootIri, + getEntity, } from "../ontologiesSlice"; export default function EntityTree({ ontology, entityType, + specifiedRootIri, selectedEntity, lang, onNavigateToEntity, @@ -45,6 +48,7 @@ export default function EntityTree({ ontology: Ontology; selectedEntity?: Entity; entityType: "entities" | "classes" | "properties" | "individuals"; + specifiedRootIri?: string; lang: string; onNavigateToEntity: (ontology: Ontology, entity: Entity) => void; onNavigateToOntology: (ontologyId: string, entity: Entity) => void; @@ -136,6 +140,19 @@ export default function EntityTree({ }) ); return () => promise.abort(); // component was unmounted + } else if (specifiedRootIri) { + let promise = dispatch( + getAncestors({ + ontologyId: ontology.getOntologyId(), + entityType, + entityIri: specifiedRootIri, + lang, + showObsoleteEnabled, + showSiblingsEnabled, + apiUrl, + }) + ); + return () => promise.abort(); // component was unmounted } else { let promise = dispatch( getRootEntities({ @@ -157,8 +174,27 @@ export default function EntityTree({ preferredRoots, lang, showObsoleteEnabled, + specifiedRootIri, ]); + useEffect(() => { + if (specifiedRootIri) { + dispatch( + getEntity({ + ontologyId: ontology.getOntologyId(), + entityIri: specifiedRootIri, + apiUrl: apiUrl, + }) + ); + } + }, [dispatch, specifiedRootIri, ontology.getOntologyId(),]); + + useEffect(() => { + if (specifiedRootIri) { + dispatch(setSpecificRootIri(specifiedRootIri)); + } + }, [dispatch, specifiedRootIri]); + const prevNodeChildrenRef = useRef(nodeChildren); const haveRelevantPartsChanged = (prev, next) => { @@ -350,7 +386,7 @@ export default function EntityTree({ ) : null}
{entityType === "classes" && - ontology.getPreferredRoots().length > 0 && ( + ontology.getPreferredRoots().length > 0 && !specifiedRootIri && (
, nodeChildren: any } { let { rootEntities, parentToChildRelations } = extractEntityHierarchy(entities); - console.log('rootEntities') - console.dir(rootEntities) - console.log('parentToChildRelations') - console.dir(parentToChildRelations) + if(specificRootIri) { + let specificRootEntity = entities.find(entity => entity.getIri() === specificRootIri) + if (specificRootEntity) { + rootEntities = [specificRootEntity]; + } + } - if (preferredRoots) { + if (preferredRoots && !specificRootIri) { let preferred = ontology.getPreferredRoots(); if (preferred.length > 0) { let preferredRootEntities = preferred.map( diff --git a/frontend/src/pages/ontologies/ontologiesSlice.ts b/frontend/src/pages/ontologies/ontologiesSlice.ts index 9314b7fe4..91baabbf5 100644 --- a/frontend/src/pages/ontologies/ontologiesSlice.ts +++ b/frontend/src/pages/ontologies/ontologiesSlice.ts @@ -34,6 +34,7 @@ export interface OntologiesState { displaySiblings: boolean; displayCounts: boolean; errorMessage: string; + specificRootIri: string; } export interface TreeNode { absoluteIdentity: string; // the IRIs of this node and its ancestors delimited by a ; @@ -70,6 +71,7 @@ const initialState: OntologiesState = { displaySiblings: false, displayCounts: true, errorMessage: "", + specificRootIri: "", }; export const resetTreeContent = createAction("ontologies_tree_reset_content"); @@ -95,6 +97,7 @@ export const hideSiblings = createAction("ontologies_hide_siblings"); export const showCounts = createAction("ontologies_show_counts"); export const hideCounts = createAction("ontologies_hide_counts"); +export const setSpecificRootIri = createAction("ontologies_set_specific_root_iri"); export const getOntology = createAsyncThunk( "ontologies_ontology", @@ -171,9 +174,11 @@ export const getEntity = createAsyncThunk( { ontologyId, entityIri, + apiUrl, }: { ontologyId: string; entityIri: string; + apiUrl?: string; }, { rejectWithValue } ) => { @@ -183,7 +188,7 @@ export const getEntity = createAsyncThunk( let path = ""; try { path = `api/v2/ontologies/${ontologyId}/entities/${doubleEncodedTermUri}`; - const entityJsonProperties = await get(path); + const entityJsonProperties = await get(path, undefined, apiUrl); return thingFromJsonProperties(entityJsonProperties); } catch (error: any) { return rejectWithValue(`Error accessing: ${path}; ${error.message}`); @@ -550,7 +555,8 @@ const ontologiesSlice = createSlice({ createTreeFromEntities( [state.entity!, ...action.payload], state.preferredRoots, - state.ontology! + state.ontology!, + state.specificRootIri ); state.rootNodes = rootNodes; state.nodeChildren = nodeChildren; @@ -607,7 +613,8 @@ const ontologiesSlice = createSlice({ ...(orphanedIndividuals || []), ], state.preferredRoots, - state.ontology! + state.ontology!, + state.specificRootIri ); state.rootNodes = rootNodes; @@ -726,6 +733,9 @@ const ontologiesSlice = createSlice({ builder.addCase(hideCounts, (state: OntologiesState) => { state.displayCounts = false; }); + builder.addCase(setSpecificRootIri, (state: OntologiesState, action: PayloadAction) => { + state.specificRootIri = action.payload; + }); builder.addCase( openNode, (state: OntologiesState, action: PayloadAction) => { diff --git a/frontend/src/widgets/EntityTreeWidget.tsx b/frontend/src/widgets/EntityTreeWidget.tsx index 22e5715db..863d3a727 100644 --- a/frontend/src/widgets/EntityTreeWidget.tsx +++ b/frontend/src/widgets/EntityTreeWidget.tsx @@ -12,6 +12,7 @@ export interface EntityTreeWidgetProps { iri?: string; ontologyId: string; apiUrl: string; + specifiedRootIri?: string; entityType?: "entities" | "classes" | "properties" | "individuals"; lang?: string; onNavigateToEntity?: (ontology: Ontology, entity: Entity) => void; @@ -33,6 +34,7 @@ function EntityTreeWidget(props: EntityTreeWidgetProps) { ontologyId={props.ontologyId} iri={props.iri} apiUrl={props.apiUrl} + specifiedRootIri={props.specifiedRootIri} /> ); @@ -64,6 +66,7 @@ function EntityTreeWidgetInner(props: EntityTreeWidgetProps) {