frogpot
Version:
A library to work with directed graphs that can be projected into trees.
902 lines (890 loc) • 27.3 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
Graph: () => Graph,
Hierarchy: () => Hierarchy,
HierarchyTree: () => Hierarchy_default,
OBOGraphLoader: () => OBOGraphLoader,
Path: () => Path,
useNodeSearch: () => useNodeSearch
});
module.exports = __toCommonJS(index_exports);
// src/components/Hierarchy.tsx
var import_react = __toESM(require("react"), 1);
// src/path.ts
var Path = class _Path {
steps;
constructor(steps) {
this.steps = steps;
if (this.steps.length === 0) {
throw new Error("Cannot create an empty path.");
}
}
get key() {
return JSON.stringify(this.steps);
}
static fromKey(k) {
return new _Path(JSON.parse(k));
}
depth() {
return this.steps.length;
}
startsWith(other) {
return other.isAncestorOf(this) || other.equals(this);
}
isAncestorOf(other) {
if (this.steps.length >= other.steps.length) return false;
return this.steps.every((val, i) => val === other.steps[i]);
}
equals(other) {
if (other.steps.length !== this.steps.length) return false;
return this.steps.every((v, i) => v === other.steps[i]);
}
parent() {
if (this.steps.length <= 1) return null;
return new _Path(this.steps.slice(0, -1));
}
child(uri) {
return new _Path([...this.steps, uri]);
}
leaf() {
return this.steps.at(-1);
}
};
// src/util.ts
function takeWhile(arr, testFn) {
const ret = [];
let i = 0;
for (const item of arr) {
i += 1;
if (!testFn(item, i)) {
break;
}
ret.push(item);
}
return ret;
}
function sortByLabel(a, b) {
const aLabel = a.label;
const bLabel = b.label;
if (aLabel === bLabel) return 0;
if (!aLabel) return -1;
if (!bLabel) return 1;
return aLabel.localeCompare(bLabel);
}
// src/components/Hierarchy.tsx
var import_jsx_runtime = require("react/jsx-runtime");
var d = {
// width: 1000,
tree: {
itemHeight: 24,
depthIndent: 16
}
};
function drawHierarchyPath(tree, el) {
const ctx = el.getContext("2d");
if (!ctx) return;
const dpr = window.devicePixelRatio || 1;
const cssHeight = tree.length * d.tree.itemHeight;
el.style.width = "100%";
el.style.height = `${cssHeight}px`;
const cssWidth = el.clientWidth;
el.width = cssWidth * dpr;
el.height = cssHeight * dpr;
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, cssWidth, cssHeight);
ctx.strokeStyle = "#666";
ctx.lineWidth = 1;
ctx.setLineDash([1, 1]);
ctx.beginPath();
tree.forEach((originItem, i) => {
const childItems = takeWhile(
tree.slice(i + 1),
(item) => item.depth > originItem.depth
);
if (childItems.length === 0) return;
const x0 = originItem.depth * d.tree.depthIndent + 5;
const y0 = i * d.tree.itemHeight + d.tree.itemHeight / 2;
const lastDirectChildIdx = childItems.findLastIndex((item) => item.depth === originItem.depth + 1) + 1;
ctx.moveTo(x0, y0);
ctx.lineTo(x0, y0 + lastDirectChildIdx * d.tree.itemHeight);
childItems.forEach((childItem, i2) => {
if (childItem.depth === originItem.depth + 1) {
const tickWidth = d.tree.depthIndent;
const y = y0 + (i2 + 1) * d.tree.itemHeight;
ctx.moveTo(x0, y);
ctx.lineTo(x0 + tickWidth, y);
}
});
});
ctx.stroke();
}
function HierarchyTree(props, ref) {
const { hierarchy, itemURI } = props;
const canvasRef = (0, import_react.useRef)(null);
const [treeState, setTreeState] = (0, import_react.useState)({
expandPaths: /* @__PURE__ */ new Set(),
showPaths: /* @__PURE__ */ new Set(),
selectedPath: hierarchy.getPathsForNode(props.rootURI)[0]
});
(0, import_react.useEffect)(() => {
const expandPaths = /* @__PURE__ */ new Set();
let showPaths = null;
if (itemURI) {
const node = hierarchy.getNode(itemURI);
const paths = hierarchy.getPathsForNode(node);
showPaths = new Set(paths.map((path) => path.key));
}
setTreeState((prev) => ({
...prev,
expandPaths,
showPaths: showPaths ? showPaths : prev.showPaths
}));
}, [itemURI]);
const tree = hierarchy.projectFlatView({
showPaths: [...treeState.showPaths].map((key) => Path.fromKey(key)),
expandPaths: [...treeState.expandPaths].map((key) => Path.fromKey(key))
});
(0, import_react.useImperativeHandle)(
ref,
() => ({
openAndFocusNode(uri) {
const node = hierarchy.getNode(uri);
const paths = hierarchy.getPathsForNode(node);
const parents = paths.map((p) => p.parent()).filter((p) => p !== null);
setTreeState((prev) => {
const expandPaths = /* @__PURE__ */ new Set([
...prev.expandPaths,
...parents.map((p) => p.key)
]);
const showPaths = /* @__PURE__ */ new Set([...paths.map((path) => path.key)]);
const selectedPath = paths[0];
return {
expandPaths,
showPaths,
selectedPath
};
});
}
}),
[]
);
(0, import_react.useEffect)(() => {
const canvasEl = canvasRef.current;
if (!canvasEl) return;
drawHierarchyPath(tree, canvasEl);
}, [tree]);
(0, import_react.useEffect)(() => {
const uri = treeState.selectedPath.leaf();
if (props.onSelectNode) {
props.onSelectNode(hierarchy.getNode(uri));
}
}, [treeState.selectedPath]);
const expandPath = (pathKey) => {
setTreeState((prev) => ({
...prev,
expandPaths: /* @__PURE__ */ new Set([...prev.expandPaths, pathKey])
}));
};
const unexpandPath = (pathKey) => {
const path = Path.fromKey(pathKey);
setTreeState((prev) => {
const nextExpandPaths = [...prev.expandPaths].filter((_path) => {
return !Path.fromKey(_path).startsWith(path);
});
const nextShowPaths = [...prev.showPaths].filter((_path) => {
return !Path.fromKey(_path).startsWith(path);
});
let nextSelectedPath = treeState.selectedPath;
if (treeState.selectedPath.startsWith(path) && !treeState.selectedPath.equals(path)) {
nextSelectedPath = path;
}
return {
selectedPath: nextSelectedPath,
showPaths: new Set(nextShowPaths),
expandPaths: new Set(nextExpandPaths)
};
});
};
const togglePathExpansion = (path) => {
if (treeState.expandPaths.has(path)) {
unexpandPath(path);
} else {
expandPath(path);
}
};
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"div",
{
tabIndex: 0,
onKeyDown: (e) => {
const curIdx = tree.findIndex(
({ path }) => path.equals(treeState.selectedPath)
);
const curItem = tree[curIdx];
if (e.key === "ArrowDown") {
e.preventDefault();
if (curIdx !== -1) {
const nextIdx = curIdx + 1;
const nextItem = tree[nextIdx];
if (nextItem) {
setTreeState((prev) => ({
...prev,
selectedPath: nextItem.path
}));
}
}
} else if (e.key === "ArrowUp") {
e.preventDefault();
if (curIdx > 0) {
const nextIdx = curIdx - 1;
const nextItem = tree[nextIdx];
if (nextItem) {
setTreeState((prev) => ({
...prev,
selectedPath: nextItem.path
}));
}
}
} else if (e.key === "ArrowRight") {
e.preventDefault();
if (curItem) {
expandPath(curItem.path.key);
}
} else if (e.key === "ArrowLeft") {
e.preventDefault();
if (curItem) {
if (treeState.expandPaths.has(curItem.path.key)) {
unexpandPath(curItem.path.key);
} else {
for (let i = curIdx; i >= 0; i--) {
if (tree[i]?.depth === curItem.depth - 1) {
setTreeState((prev) => ({
...prev,
selectedPath: tree[i].path
}));
break;
}
}
}
}
}
},
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
"div",
{
style: {
position: "relative",
border: "1px solid #ccc",
width: props.width
},
children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"canvas",
{
ref: canvasRef,
style: {
pointerEvents: "none",
position: "absolute",
top: 0,
left: 0
}
}
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "relative" }, children: tree.map(({ item, depth, path }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
"div",
{
"data-path": path.key,
style: {
display: "flex",
alignItems: "center",
lineHeight: `${d.tree.itemHeight}px`,
height: `${d.tree.itemHeight}px`,
paddingLeft: `${depth * d.tree.depthIndent}px`,
userSelect: "none",
cursor: "pointer"
},
children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"span",
{
onClick: () => togglePathExpansion(path.key),
style: {
display: "inline-block",
width: "18px"
},
children: hierarchy.graph.childrenByURI[item.uri].length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"span",
{
style: {
display: "inline-flex",
width: 10,
height: 10,
border: "2px solid #666",
background: treeState.expandPaths.has(path.key) ? "#ccc" : "#fff",
alignItems: "center",
justifyContent: "center"
},
children: treeState.expandPaths.has(path.key) ? "" : ""
}
)
}
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"span",
{
style: {
flex: 1,
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
backgroundColor: treeState.selectedPath.equals(path) ? "#f0f0f0" : "transparent"
},
title: itemURI,
onMouseDown: (e) => {
if (e.detail === 1) {
setTreeState((prev) => ({
...prev,
selectedPath: path
}));
} else if (e.detail === 2) {
togglePathExpansion(path.key);
}
},
children: item.label
}
)
]
},
path.key
)) })
]
}
)
}
);
}
var Hierarchy_default = import_react.default.forwardRef(HierarchyTree);
// src/search.ts
var import_react2 = require("react");
var import_minisearch = __toESM(require("minisearch"), 1);
var import_react_highlight_words = __toESM(require("react-highlight-words"), 1);
var SearchEngine = class {
items;
itemsByURI;
miniSearch;
constructor(items) {
this.items = items;
this.itemsByURI = new Map(items.map((item) => [item.uri, item]));
this.miniSearch = new import_minisearch.default({
idField: "uri",
fields: ["label", "synonyms", "definitions"],
extractField: (doc, fieldName) => {
if (fieldName === "synonyms" || fieldName === "definitions") {
return doc[fieldName].map((x) => x.value).join(" ");
}
return doc[fieldName];
}
});
}
search(text, options) {
const results = this.miniSearch.search(text, options);
return results.map((res) => ({
...res,
node: this.itemsByURI.get(res.id)
}));
}
buildIndex() {
this.miniSearch.addAll(this.items);
}
};
function useSearchEngine(items) {
const engineRef = (0, import_react2.useRef)(null);
const rebuild = (items2) => {
const engine = new SearchEngine(items2);
engine.buildIndex();
engineRef.current = engine;
};
if (engineRef.current === null) {
rebuild(items);
}
(0, import_react2.useEffect)(() => {
rebuild(items);
}, [items]);
return { engine: engineRef.current };
}
function useNodeSearch(nodes) {
const { engine } = useSearchEngine(nodes);
const [query, setQuery] = (0, import_react2.useState)("");
const [results, setResults] = (0, import_react2.useState)(
null
);
(0, import_react2.useEffect)(() => {
if (query.trim() === "") {
setResults(null);
return;
}
const results2 = engine.search(query, {
prefix: true,
boost: { label: 2 },
combineWith: "and"
});
setResults(results2);
}, [query]);
const searchWords = query.split(" ").map((word) => new RegExp("\\b" + word));
const highlightText = (text, props) => (0, import_react2.createElement)(import_react_highlight_words.default, {
textToHighlight: text,
searchWords,
...props
});
return { engine, results, query, setQuery, highlightText };
}
// src/graph.ts
var import_treeverse2 = __toESM(require("treeverse"), 1);
// src/hierarchy.ts
var import_treeverse = __toESM(require("treeverse"), 1);
var Hierarchy = class {
root;
graph;
nodesByURI;
nodesByPathKey;
pathsByURI;
constructor(root, graph) {
this.root = root;
this.graph = graph;
this.nodesByURI = /* @__PURE__ */ new Map();
this.nodesByPathKey = /* @__PURE__ */ new Map();
this.pathsByURI = /* @__PURE__ */ new Map();
import_treeverse.default.depth({
tree: {
item: this.root,
path: new Path([this.root.uri])
},
visit: (node) => {
const { item, path } = node;
if (!this.pathsByURI.has(item.uri)) {
this.pathsByURI.set(item.uri, []);
}
this.nodesByURI.set(item.uri, item);
this.pathsByURI.get(item.uri).push(path);
this.nodesByPathKey.set(path.key, item);
},
getChildren: (node) => {
const { item, path } = node;
const childRels = this.graph.childrenByURI[item.uri] ?? [];
return childRels.map((rel) => {
const child = this.graph.getItem(rel.to);
return {
item: child,
path: path.child(child.uri)
};
});
}
});
}
getNode(node) {
if (typeof node === "string") {
const ret = this.nodesByURI.get(node);
if (!ret) {
throw new Error(`No item in hierarchy with URI ${node}`);
}
return ret;
} else if (node instanceof Path) {
const ret = this.nodesByPathKey.get(node.key);
if (!ret) {
throw new Error(`No item in hierarchy at path ${node.key}`);
}
return ret;
} else {
return node;
}
}
items() {
return [...this.nodesByURI.values()].sort(sortByLabel);
}
getPathsForNode(node) {
const _node = this.getNode(node);
return this.pathsByURI.get(_node.uri) ?? [];
}
// Produce a projection of this hierarchy, with only certain nodes shown or
// expanded. Meant for producing a portion of the hierarchy suitable for
// rendering in a UI.
//
// Returns a one-dimensional array of HierarchyRow objects annotated by their
// depth.
projectFlatView(opts = {}) {
const showPaths = opts.showPaths ?? [];
const expandPaths = opts.expandPaths ?? [];
const showKeys = new Set(showPaths.map((p) => p.key));
const expandKeys = new Set(expandPaths.map((p) => p.key));
const rows = [];
const shouldIterateChildren = (path) => {
if (expandKeys.has(path.key)) return true;
if (showPaths.some((showPath) => path.isAncestorOf(showPath))) return true;
if (expandPaths.some((expandPath) => path.isAncestorOf(expandPath)))
return true;
return false;
};
const shouldShowPath = (path) => {
const pathKey = path.key;
if (path.depth() === 1) return true;
if (expandKeys.has(pathKey)) return true;
if (showKeys.has(pathKey)) return true;
for (const showPath of showPaths) {
if (path.isAncestorOf(showPath)) return true;
}
for (const expandPath of expandPaths) {
if (path.isAncestorOf(expandPath)) return true;
if (path.parent()?.equals(expandPath)) return true;
}
return false;
};
import_treeverse.default.depth({
tree: {
item: this.root,
path: new Path([this.root.uri]),
depth: 0,
relToParent: null
},
visit(node) {
const { item, path, depth, relToParent } = node;
if (shouldShowPath(path)) {
rows.push({ item, path, depth, relToParent });
}
},
getChildren: (node) => {
const { item, path, depth } = node;
if (!shouldIterateChildren(path)) {
return [];
}
const childRels = this.graph.childrenByURI[item.uri] ?? [];
const children = childRels.map((rel) => {
const item2 = this.graph.getItem(rel.to);
const relPredicate = !rel.inverse ? `^${rel.predicate}` : rel.predicate;
return {
item: item2,
path: path.child(item2.uri),
depth: depth + 1,
relToParent: relPredicate
};
});
children.sort((a, b) => sortByLabel(b.item, a.item));
return children;
}
});
return rows;
}
};
// src/graph.ts
var Graph = class {
roots;
nodes;
nodesByURI;
childrenByURI;
parentsByURI;
constructor(nodes) {
const nodesByURI = {};
const parentsByURI = {};
const childrenByURI = {};
for (const node of nodes) {
nodesByURI[node.uri] = node;
for (const [relURI, termURIs] of Object.entries(node.children)) {
termURIs.forEach((childURI) => {
if (!Object.hasOwn(childrenByURI, node.uri)) {
childrenByURI[node.uri] = [];
}
if (!Object.hasOwn(parentsByURI, childURI)) {
parentsByURI[childURI] = [];
}
childrenByURI[node.uri].push({
to: childURI,
predicate: relURI,
inverse: false
});
parentsByURI[childURI].push({
to: node.uri,
predicate: relURI,
inverse: true
});
});
}
for (const [relURI, termURIs] of Object.entries(node.parents)) {
termURIs.forEach((parentURI) => {
if (!Object.hasOwn(parentsByURI, node.uri)) {
parentsByURI[node.uri] = [];
}
if (!Object.hasOwn(childrenByURI, parentURI)) {
childrenByURI[parentURI] = [];
}
parentsByURI[node.uri].push({
to: parentURI,
predicate: relURI,
inverse: false
});
childrenByURI[parentURI].push({
to: node.uri,
predicate: relURI,
inverse: true
});
});
}
if (!Object.hasOwn(childrenByURI, node.uri)) {
childrenByURI[node.uri] = [];
}
if (!Object.hasOwn(parentsByURI, node.uri)) {
parentsByURI[node.uri] = [];
}
}
const roots = nodes.filter(
(item) => parentsByURI[item.uri].length === 0 && childrenByURI[item.uri].length > 0
);
this.roots = roots;
this.nodes = nodes;
this.nodesByURI = nodesByURI;
this.parentsByURI = parentsByURI;
this.childrenByURI = childrenByURI;
}
items() {
return this.nodes;
}
getHierarchy(rootURI) {
const item = this.getItem(rootURI);
return new Hierarchy(item, this);
}
getRootHierarchies() {
const ret = /* @__PURE__ */ new Map();
this.roots.forEach((root) => {
ret.set(root.uri, this.getHierarchy(root.uri));
});
return ret;
}
getItem(uri) {
const item = this.nodesByURI[uri];
if (item === void 0) {
throw new Error(`No item in graph with URI ${uri}`);
}
return item;
}
findAllParents(item) {
const parents = [];
import_treeverse2.default.breadth({
tree: item,
visit(item2) {
parents.push(item2);
},
getChildren: (node) => this.parentsByURI[node.uri].map((rel) => this.getItem(rel.to)).sort(
sortByLabel
)
});
parents.shift();
return parents;
}
findAllChildren(item) {
const children = [];
import_treeverse2.default.breadth({
tree: item,
visit(item2) {
children.push(item2);
},
getChildren: (node) => this.childrenByURI[node.uri].map((rel) => this.getItem(rel.to)).sort(
sortByLabel
)
});
children.shift();
return children;
}
};
// src/loaders/obograph/schema.ts
var z = __toESM(require("zod"), 1);
var OBOMeta = z.object({
definition: z.optional(
z.object({
val: z.string(),
pred: z.optional(z.string()),
xrefs: z.optional(z.array(z.string())),
get meta() {
return z.optional(OBOMeta);
}
})
),
comments: z.optional(z.array(z.string())),
subsets: z.optional(z.array(z.string())),
synonyms: z.optional(
z.array(
z.object({
synonymType: z.optional(z.string()),
pred: z.optional(z.string()),
val: z.string(),
xrefs: z.optional(z.array(z.string())),
get meta() {
return z.optional(OBOMeta);
}
})
)
),
xrefs: z.optional(
z.array(
z.object({
lbl: z.optional(z.string()),
pred: z.optional(z.string()),
val: z.string(),
xrefs: z.optional(z.array(z.string())),
get meta() {
return z.optional(OBOMeta);
}
})
)
),
basicPropertyValues: z.optional(
z.array(
z.object({
pred: z.string(),
val: z.string(),
xrefs: z.optional(z.array(z.string())),
get meta() {
return z.optional(OBOMeta);
}
})
)
),
version: z.optional(z.string()),
deprecatd: z.optional(z.boolean())
});
var OBOGraph = z.object({
id: z.string(),
lbl: z.optional(z.string()),
meta: z.optional(OBOMeta),
nodes: z.array(
z.object({
id: z.string(),
lbl: z.optional(z.string()),
type: z.literal(["CLASS", "INDIVIDUAL", "PROPERTY"]),
propertyType: z.optional(z.literal(["ANNOTATION", "OBJECT", "DATA"])),
meta: z.optional(OBOMeta)
})
),
edges: z.array(
z.object({
sub: z.string(),
pred: z.string(),
obj: z.string(),
meta: z.optional(OBOMeta)
})
)
// logicalDefinitionAxioms,
// domainRangeAxioms,
// propertyChainAxioms,
});
var OBOGraphsSchema = z.object({
meta: z.optional(OBOMeta),
graphs: z.array(OBOGraph)
});
// src/loaders/index.ts
var GraphLoader = class {
fromString(str) {
const graph = this.loadGraphFromString(str);
return this.parseGraph(graph);
}
async fromURI(uri, options) {
const resp = await fetch(uri, options);
if (!resp.ok) {
throw Error(`Error requesting ${uri}: ${resp.status} ${resp.statusText}`);
}
const str = await resp.text();
const graph = await this.loadGraphFromString(str);
return this.parseGraph(graph);
}
};
// src/loaders/obograph/index.ts
var parentProperties = {
is_a: "rdfs:subClassOf",
"http://purl.obolibrary.org/obo/BFO_0000050": "BFO:0000050"
};
var OBOGraphLoader = class extends GraphLoader {
parseGraph(graph) {
const terms = /* @__PURE__ */ new Map();
for (const node of graph.nodes) {
if (node.type !== "CLASS") continue;
const bpvs = node.meta?.basicPropertyValues || [];
const replaced = !!bpvs.some(
({ pred }) => pred === "http://purl.obolibrary.org/obo/IAO_0100001"
);
if (replaced) continue;
const synonyms = node.meta?.synonyms || [];
const definition = node.meta?.definition;
terms.set(node.id, {
uri: node.id,
label: node.lbl || node.id,
definitions: definition ? [{ value: definition.val }] : [],
synonyms: synonyms.map((syn) => ({
value: syn.val
})),
parents: {},
children: {},
meta: node.meta,
edges: []
});
}
for (const edge of graph.edges) {
if (edge.pred in parentProperties) {
const predID = parentProperties[edge.pred];
const parents = terms.get(edge.sub)?.parents;
if (!parents) continue;
if (!Object.hasOwn(parents, predID)) {
parents[predID] = [];
}
parents[predID].push(edge.obj);
} else if (terms.has(edge.sub)) {
terms.get(edge.sub).edges.push(edge);
}
}
return new Graph([...terms.values()]);
}
loadGraphFromString(str) {
const result = OBOGraphsSchema.safeParse(JSON.parse(str));
if (!result.success) {
console.log(result.error.issues);
throw Error();
}
const graph = result.data.graphs[0];
return graph;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Graph,
Hierarchy,
HierarchyTree,
OBOGraphLoader,
Path,
useNodeSearch
});
//# sourceMappingURL=index.cjs.map