/* Imports*/ import React from "react"; import { numToAlphabeticalString, formatLargeOrSmall, avg, hackyGeomMean, getCoefficientOfVariation, } from "../lib/utils.js"; /* Functions */ const pathPlusLink = (pathSoFar, link) => { return [...pathSoFar, link]; // previously: pathSoFar.concat(link).flat() // Note that concat is not destructive // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat }; async function findPathsWithoutPrunning({ sourceElementId, targetElementId, maxLengthOfPath, pathSoFar, links, nodes, }) { // This is an un-used function which might make findPaths more understandable // It uses the same recursive functionality // but has no path prunning let paths = []; /* Path traversing */ if (maxLengthOfPath > 0) { for (let link of links) { // vs let link of linksNow in findPaths if ( (link.source == sourceElementId && link.target == targetElementId) || (link.source == targetElementId && link.target == sourceElementId) ) { // direct Path let newPath = pathPlusLink(pathSoFar, link); paths.push(newPath); } else if (link.source == sourceElementId) { let newPaths = await findPaths({ pathSoFar: pathPlusLink(pathSoFar, link), maxLengthOfPath: maxLengthOfPath - 1, sourceElementId: link.target, targetElementId, links: links, // vs let link of linksInner in findPaths nodes, }); if (newPaths.length != 0) { paths.push(...newPaths); } } else if (link.target == sourceElementId) { let newPaths = await findPaths({ pathSoFar: pathPlusLink(pathSoFar, link), maxLengthOfPath: maxLengthOfPath - 1, sourceElementId: link.source, targetElementId, links: links, // vs let link of linksInner in findPaths nodes, }); if (newPaths.length != 0) { paths.push(...newPaths); } } } } return paths; } async function findPaths({ sourceElementId, sourceElementPosition, targetElementId, targetElementPosition, maxLengthOfPath, pathSoFar, links, nodes, }) { // This is the key path finding function // It finds the path from one element to another, recursively // It used to be very computationally expensive until I added // the path prunning step: Instead of traversing all links, // traverse only those which are between the origin and target links // this requires us to have a notion of "between" let paths = []; /* Path prunning*/ let minPos = Math.min(sourceElementPosition, targetElementPosition); let maxPos = Math.max(sourceElementPosition, targetElementPosition); let linksInner = links.filter( (link) => minPos <= link.sourceElementPosition && link.sourceElementPosition <= maxPos && minPos <= link.targetElementPosition && link.targetElementPosition <= maxPos ); let linksNow = linksInner.filter( (link) => link.source == sourceElementId || link.target == sourceElementId ); /* Path traversing */ if (maxLengthOfPath > 0) { for (let link of linksNow) { if ( (link.source == sourceElementId && link.target == targetElementId) || (link.source == targetElementId && link.target == sourceElementId) ) { // direct Path let newPath = pathPlusLink(pathSoFar, link); paths.push(newPath); } else if (link.source == sourceElementId) { let newPaths = await findPaths({ pathSoFar: pathPlusLink(pathSoFar, link), maxLengthOfPath: maxLengthOfPath - 1, sourceElementPosition: link.sourceElementPosition, sourceElementId: link.target, targetElementId, targetElementPosition, links: linksInner, nodes, }); if (newPaths.length != 0) { paths.push(...newPaths); } } else if (link.target == sourceElementId) { let newPaths = await findPaths({ pathSoFar: pathPlusLink(pathSoFar, link), maxLengthOfPath: maxLengthOfPath - 1, sourceElementPosition: link.sourceElementPosition, sourceElementId: link.source, targetElementPosition, targetElementId, links: linksInner, nodes, }); if (newPaths.length != 0) { paths.push(...newPaths); } } } } return paths; } async function findDistancesForElement({ sourceElementId, sourceElementPosition, targetElementId, targetElementPosition, nodes, links, }) { // This function gets all possible paths using findPaths // then orders them correctly in the for loop // (by flipping the distance to 1/distance when necessary) // and then gets the array of weights for the different paths. console.log( `findDistance@findPaths.js from ${sourceElementPosition} to ${targetElementPosition}` ); let maxLengthOfPath = Math.abs(sourceElementPosition - targetElementPosition); let paths = await findPaths({ sourceElementId, sourceElementPosition, targetElementId, targetElementPosition, links, nodes, maxLengthOfPath, pathSoFar: [], }); let weights = []; for (let path of paths) { let currentSource = sourceElementId; let weight = 1; for (let element of path) { let distance = 0; if (element.source == currentSource) { distance = element.distance; currentSource = element.target; } else if (element.target == currentSource) { distance = 1 / Number(element.distance); currentSource = element.source; } weight = weight * distance; } weights.push(weight); } return weights; } async function findDistancesForAllElements({ nodes, links }) { // legacy. I used to need a somewhat hardcoded reference point. Now that's not necessary. // Simple wrapper function around findDistance // Needs to find the reference point first console.log("findDistancesForAllElements@findPaths.js"); /* Get or build reference element */ let referenceElements = nodes.filter((x) => x.isReferenceValue); let midpoint = Math.round(nodes.length / 2); let referenceElement = referenceElements.length > 0 ? referenceElements[0] : nodes[midpoint]; console.log(`referenceElement.position: ${referenceElement.position}`); /* Get distances. */ let distancesArray = nodes.map((node) => { if (node.isReferenceValue || node.id == referenceElement.id) { return [1]; } else { console.log("node"); console.log(node); let distancesForElement = findDistancesForElement({ sourceElementId: referenceElement.id, sourceElementPosition: referenceElement.position, targetElementId: node.id, targetElementPosition: node.position, nodes: nodes, links: links, }); return distancesForElement; } }); distancesArray = await Promise.all(distancesArray); return distancesArray; } async function findDistancesFromAllElementsToReferencePoint({ nodes, links, referenceElement, }) { // Simple wrapper function around findDistance // Needs to find the reference point first console.log("findDistancesForAllElements@findPaths.js"); /* Get or build reference element */ let midpoint = Math.round(nodes.length / 2); referenceElement = referenceElement || nodes[midpoint]; console.log(`referenceElement.position: ${referenceElement.position}`); /* Get distances. */ let distancesArray = nodes.map((node) => { if (node.id == referenceElement.id) { return [1]; } else { console.log("node"); console.log(node); let distancesForElement = findDistancesForElement({ sourceElementId: referenceElement.id, sourceElementPosition: referenceElement.position, targetElementId: node.id, targetElementPosition: node.position, nodes: nodes, links: links, }); return distancesForElement; } }); distancesArray = await Promise.all(distancesArray); return distancesArray; } async function findDistancesFromAllElementsToAllReferencePoints({ nodes, links, }) { let nodes2 = nodes.map((node) => ({ ...node, isReferenceValue: false })); let distancesForAllElements = Array(nodes.length); for (let i = 0; i < nodes.length; i++) { distancesForAllElements[i] = []; } for (let node of nodes2) { let distancesFromNode = await findDistancesFromAllElementsToReferencePoint({ nodes, links, referenceElement: node, }); // alert(`distancesFromNode.length: ${distancesFromNode.length}`); distancesForAllElements = distancesForAllElements.map((arr, i) => { return !!arr && arr.length > 0 ? [...arr, ...distancesFromNode[i]] : distancesFromNode[i]; }); } return distancesForAllElements; } export async function buildRows({ isListOrdered, orderedList, listOfElements, links, rows, setTableRows, }) { console.log("buildRows@findPaths.js"); // This function is used in pages/comparisonView.js to create the rows that will be displayed. // it is in there because it needs to be deployed after isListOrdered becomes true, // and using an useEffect inside CreateTable was too messy. if ( isListOrdered && !(orderedList.length < listOfElements.length) && rows.length == 0 ) { let nodes = []; let positionDictionary = {}; orderedList.forEach((id, pos) => { nodes.push({ ...listOfElements[id], position: pos }); positionDictionary[id] = pos; }); links = links.map((link) => ({ ...link, sourceElementPosition: positionDictionary[link.source], targetElementPosition: positionDictionary[link.target], })); let distances = await findDistancesFromAllElementsToAllReferencePoints({ nodes, links, }); //findDistancesForAllElements({ nodes, links }); rows = nodes.map((element, i) => ({ id: numToAlphabeticalString(element.position), position: element.position, name: element.name, distances: distances[i], })); console.log(rows); setTableRows(rows); } } export function CreateTable({ tableRows }) { /* This function receives a list of rows, and displays them nicely. */ function abridgeArrayAndDisplay(array) { let newArray; let formatForDisplay; if (array.length > 10) { newArray = array.slice(0, 9); formatForDisplay = newArray.map((d) => formatLargeOrSmall(d)); formatForDisplay[9] = "..."; } else { newArray = array; formatForDisplay = newArray.map((d) => formatLargeOrSmall(d)); } let result = JSON.stringify(formatForDisplay, null, 2).replaceAll(`"`, ""); return result; } return (
{tableRows.map((row) => ( ))}
Id     Position     Element     Possible relative values     geomMean(relative values) Coefficient of variation
{row.id}     {row.position}     {row.name}     {abridgeArrayAndDisplay(row.distances)}     {formatLargeOrSmall(hackyGeomMean(row.distances))} {formatLargeOrSmall(getCoefficientOfVariation(row.distances))}
); } /* Testing */ //import fs from 'fs'; //import path from 'path'; /* const directory = path.join(process.cwd(),"pages") let links = JSON.parse(fs.readFileSync(path.join(directory, 'distancesExample.json'), 'utf8')); let nodes = JSON.parse(fs.readFileSync(path.join(directory, 'listOfPosts.json'), 'utf8')); let paths = findPaths({sourceElementId:2, targetElementId:0, pathSoFar: [], links, nodes, maxLengthOfPath: 2}) console.log(JSON.stringify(paths, null, 2)) */ /* let paths = findPaths({sourceElementId:2, targetElementId:0, links, nodes}) console.log(JSON.stringify(paths, null, 2)) */ /* let distances = findDistance({sourceElementId:2, targetElementId:4, links, nodes}) console.log(distances) */