diff --git a/lib/comparisonView.js b/lib/comparisonView.js index f3c4259..968a096 100644 --- a/lib/comparisonView.js +++ b/lib/comparisonView.js @@ -1,7 +1,7 @@ /* Imports */ import Head from 'next/head' import React, { useState } from "react"; -import { DrawGraph, removeOldSvg } from './labeledgraph'; +import { DrawGraph, removeOldSvg } from './labeledGraph'; import { SubmitSliderButton } from "./slider"; import { DisplayElement } from './displayElement' import { DisplayAsMarkdown } from './displayAsMarkdown' diff --git a/lib/findPaths.js b/lib/findPaths.js index f5878ee..77b348e 100644 --- a/lib/findPaths.js +++ b/lib/findPaths.js @@ -1,49 +1,55 @@ /* Imports*/ -import React, { useState, useEffect } from 'react'; -import { toLocale, truncateValueForDisplay, numToAlphabeticalString, formatLargeOrSmall } from "../lib/utils.js" +import React from 'react'; +import { numToAlphabeticalString, formatLargeOrSmall, avg } from "../lib/utils.js" -/* Utilities */ -let avg = arr => arr.reduce((a, b) => (a + b), 0) / arr.length +/* Functions */ -/* Main function */ +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 findPathsInner({ - sourceElementId, sourceElementPosition, - targetElementId, targetElementPosition, - links, nodes, - maxLengthOfPath, pathSoFar +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 = [] - 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) { + 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)) - ) { - paths.push(pathSoFar.concat(link).flat()) + ) { // direct Path + let newPath = pathPlusLink(pathSoFar, link) + paths.push(newPath) } else if ((link.source == sourceElementId)) { - let newPaths = await findPathsInner({ - sourceElementId: link.target, sourceElementPosition: link.sourceElementPosition, - targetElementId, targetElementPosition, - pathSoFar: pathSoFar.concat(link).flat(), - links: linksInner, nodes, maxLengthOfPath: (maxLengthOfPath - 1) + 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) { + if (newPaths.length != 0) { paths.push(...newPaths) } } else if ((link.target == sourceElementId)) { - let newPaths = await findPathsInner({ - sourceElementId: link.source, sourceElementPosition: link.sourceElementPosition, - targetElementId, targetElementPosition, - pathSoFar: pathSoFar.concat(link).flat(), - links: linksInner, nodes, maxLengthOfPath: (maxLengthOfPath - 1) + 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) @@ -54,40 +60,93 @@ async function findPathsInner({ return paths } -/* -function findPaths({ + +async function findPaths({ sourceElementId, sourceElementPosition, targetElementId, targetElementPosition, - nodes, links, direction + maxLengthOfPath, pathSoFar, + links, nodes }) { - let positionSourceElement = nodes.map((element, i) => (element.id)).indexOf(sourceElementId) - let positionTargetElement = nodes.map((element, i) => (element.id)).indexOf(targetElementId) - let maxLengthOfPath = Math.abs(positionSourceElement - positionTargetElement) - return findPathsInner({ - sourceElementId, sourceElementPosition, - targetElementId, targetElementPosition, - links, nodes, direction, - maxLengthOfPath, pathSoFar: [] - }) + // 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 findDistance({ sourceElementId, sourceElementPosition, targetElementId, targetElementPosition, - nodes, links, direction + 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 findPathsInner({ + let paths = await findPaths({ sourceElementId, sourceElementPosition, targetElementId, targetElementPosition, - links, nodes, direction, + links, nodes, maxLengthOfPath, pathSoFar: [] }); - console.log(`findDistance from ${sourceElementPosition} to ${targetElementPosition}`) - console.log(targetElementId) - console.log(direction) - console.log(paths) + let weights = [] for (let path of paths) { let currentSource = sourceElementId @@ -106,25 +165,26 @@ async function findDistance({ } weights.push(weight) } - //let sum = JSON.stringify(weights)//weights.length > 0 ? weights.reduce((a,b) => (a+b)) / weights.length : "Not found" - //return sum return weights - //return weights.map(weight => Math.round(weight*100)/100) } async function findDistancesForAllElements({ nodes, links }) { + // 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(nodes) console.log(`referenceElement.position: ${referenceElement.position}`) + + /* Get distances. */ let distances = nodes.map(node => { if (node.isReferenceValue || (node.id == referenceElement.id)) { return [1] } else { console.log("node") console.log(node) - let isUpwardsDirection = referenceElement.position < node.position let distance = findDistance({ sourceElementId: referenceElement.id, sourceElementPosition: referenceElement.position, @@ -132,7 +192,6 @@ async function findDistancesForAllElements({ nodes, links }) { targetElementPosition: node.position, nodes: nodes, links: links, - direction: isUpwardsDirection ? "upwards" : "downwards" }) return distance } @@ -141,23 +200,11 @@ async function findDistancesForAllElements({ nodes, links }) { return distances } -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 -} - export async function buildRows({ isListOrdered, orderedList, listOfElements, links, rows, setTableRows }) { - // Not used yet. + 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 = ({}) @@ -165,8 +212,6 @@ export async function buildRows({ isListOrdered, orderedList, listOfElements, li nodes.push({ ...listOfElements[id], position: pos }) positionDictionary[id] = pos }) - // let nodes = orderedList.map((id, pos) => ({ ...listOfElements[id], position: pos })) - /* Pre-process links to talk in terms of distances */ links = links.map(link => ({ ...link, sourceElementPosition: positionDictionary[link.source], @@ -180,161 +225,63 @@ export async function buildRows({ isListOrdered, orderedList, listOfElements, li name: element.name, distances: distances[i] })) - console.log("rows@CreateTableWithDistances") console.log(rows) setTableRows(rows) - } else { - // rows = [] - // Do nothing } - // return rows } -export function CreateTableWithDistancesWithUseEffect({ isListOrdered, orderedList, listOfElements, links, tableRows, setTableRows }) { - - useEffect(async () => { - await buildRows({ isListOrdered, orderedList, listOfElements, links, rows: tableRows, setTableRows }) - /* - // https://stackoverflow.com/questions/57847626/using-async-await-inside-a-react-functional-component - // https://stackoverflow.com/questions/54936559/using-async-await-in-react-component - // https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects - 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 - }) - // let nodes = orderedList.map((id, pos) => ({ ...listOfElements[id], position: pos })) - // Pre-process links to talk in terms of distances - links = links.map(link => ({...link, - sourceElementPosition: positionDictionary[link.source], - targetElementPosition: positionDictionary[link.target] - })) - - let distances = await findDistancesForAllElements({ nodes, links }) - setRows(nodes.map((element, i) => ({ - id: numToAlphabeticalString(element.position), - position: element.position, - name: element.name, - distances: distances[i] - }))) - console.log("rows@CreateTableWithDistances") - console.log(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)) } - */ - }); // this useEffect should ideally only work when isListOrdered changes, but I haven't bothered to program that. - - - return ( -
- - - - - - - - - - - - - - - - {tableRows.map(row => - - - - - - - - - - - )} - -
Id   Position   Element    Possible relative values    Average relative value
{row.id}   {row.position}   {row.name}   {abridgeArrayAndDisplay(row.distances)}   {formatLargeOrSmall(avg(row.distances))}
-
- ) - -} - -export function CreateTableWithDistances({ tableRows }) { - return ( -
- - - - - - - - - - - - - - - - {tableRows.map(row => - - - - - - - - - - - )} - -
Id   Position   Element    Possible relative values    Average relative value
{row.id}   {row.position}   {row.name}   {abridgeArrayAndDisplay(row.distances)}   {formatLargeOrSmall(avg(row.distances))}
-
- ) - -} - -function CreateTableWithoutDistances({ isListOrdered, orderedList, listOfElements, links }) { - if (!isListOrdered || orderedList.length < listOfElements.length) { - return (
{""}
) - } else { - let nodes = orderedList.map(i => listOfElements[i]) - let rows = nodes.map((element, i) => ({ id: numToAlphabeticalString(element.id), name: element.name })) - console.log("rows@CreateTableWithoutDistances") - console.log(rows) - return ( -
- - - - - - - - - - - {rows.map(row => - - - - - )} - { } - -
Id   Element    
{row.id}   {row.name}
-
- ) + let result = JSON.stringify(formatForDisplay, null, 2).replaceAll(`"`, "") + return result } + return ( +
+ + + + + + + + + + + + + + + + {tableRows.map(row => + + + + + + + + + + + )} + +
Id   Position   Element    Possible relative values    Average relative value
{row.id}   {row.position}   {row.name}   {abridgeArrayAndDisplay(row.distances)}   {formatLargeOrSmall(avg(row.distances))}
+
+ ) } -export const CreateTable = CreateTableWithDistances // CreateTableWithoutDistances; - /* Testing */ //import fs from 'fs'; //import path from 'path'; @@ -343,7 +290,7 @@ 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 = findPathsInner({sourceElementId:2, targetElementId:0, pathSoFar: [], links, nodes, maxLengthOfPath: 2}) +let paths = findPaths({sourceElementId:2, targetElementId:0, pathSoFar: [], links, nodes, maxLengthOfPath: 2}) console.log(JSON.stringify(paths, null, 2)) */ /* diff --git a/lib/labeledgraph.js b/lib/labeledGraph.js similarity index 98% rename from lib/labeledgraph.js rename to lib/labeledGraph.js index 3259dd3..011adc5 100644 --- a/lib/labeledgraph.js +++ b/lib/labeledGraph.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import * as d3 from 'd3'; -import { toLocale, truncateValueForDisplay, numToAlphabeticalString, formatLargeOrSmall } from "../lib/utils.js" +import { toLocale, truncateValueForDisplay, numToAlphabeticalString, formatLargeOrSmall } from "./utils.js" let getlength = (number) => number.toString().length; diff --git a/lib/utils.js b/lib/utils.js index 2e4f9a0..f186e57 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -67,4 +67,6 @@ export function expectedNumMergeSortSteps(n) { } else { return Math.ceil((n ** 2) / (n + 2)) + expectedNumMergeSortSteps(Math.ceil(n / 2)) } -} \ No newline at end of file +} + +export const avg = arr => arr.reduce((a, b) => (a + b), 0) / arr.length