fix: Formatting + avg => geomMean

This commit is contained in:
NunoSempere 2022-01-29 16:06:16 -05:00
parent e610d798d3
commit 5ec31fa365
3 changed files with 515 additions and 429 deletions

View File

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

View File

@ -1,145 +1,171 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import * as d3 from 'd3'; import * as d3 from "d3";
import { toLocale, truncateValueForDisplay, numToAlphabeticalString, formatLargeOrSmall } from "./utils.js" import {
toLocale,
truncateValueForDisplay,
numToAlphabeticalString,
formatLargeOrSmall,
} from "./utils.js";
let getlength = (number) => number.toString().length; let getlength = (number) => number.toString().length;
export function removeOldSvg() { export function removeOldSvg() {
d3.select("#graph").select("svg").remove(); d3.select("#graph").select("svg").remove();
} }
function drawGraphInner({ nodes, links }) { function drawGraphInner({ nodes, links }) {
// List of node ids for convenience
var nodeids = nodes.map((node) => node.id);
var positionById = {};
nodeids.forEach((nodeid, i) => (positionById[nodeid] = i));
console.log("NodeIds/positionById");
console.log(nodeids);
console.log(positionById);
// List of node ids for convenience // Calculate the dimensions
var nodeids = nodes.map(node => node.id) // let margin = { top: 0, right: 30, bottom: 20, left: 30 };
var positionById = {} // let width = 900 - margin.left - margin.right;
nodeids.forEach((nodeid, i) => positionById[nodeid] = i)
console.log("NodeIds/positionById")
console.log(nodeids)
console.log(positionById)
// Calculate the dimensions let initialWindowWidth = window.innerWidth;
// let margin = { top: 0, right: 30, bottom: 20, left: 30 }; let margin = { top: 0, right: 10, bottom: 30, left: 10 };
// let width = 900 - margin.left - margin.right; let width = initialWindowWidth * 0.8 - margin.left - margin.right;
let initialWindowWidth = window.innerWidth
let margin = { top: 0, right: 10, bottom: 30, left: 10 };
let width = initialWindowWidth*0.7 - margin.left - margin.right;
var x = d3.scalePoint()
.range([0, width])
.domain(nodeids)
let heights = links.map(link => { var x = d3.scalePoint().range([0, width]).domain(nodeids);
let start = x(positionById[link.source])
let end = x(positionById[link.target]) let heights = links.map((link) => {
return Math.abs(start - end) / 2 + 70 // Magic constant. let start = x(positionById[link.source]);
let end = x(positionById[link.target]);
return Math.abs(start - end) / 2 + 70; // Magic constant.
});
console.log(heights);
let maxheight = Math.max(...heights);
let height = maxheight - margin.top - margin.bottom;
console.log(`height: ${height}`);
// Build the d3 graph
removeOldSvg();
var svg = d3
.select("#graph")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// A linear scale to position the nodes on the X axis
// Add the circle for the nodes
svg
.selectAll("mynodes")
.data(nodes)
.enter()
.append("circle")
.attr("cx", function (d) {
return x(d.id);
}) })
console.log(heights) .attr("cy", height - 30)
let maxheight = Math.max(...heights) .attr("r", 8)
let height = maxheight - margin.top - margin.bottom; .style("fill", "#69b3a2");
console.log(`height: ${height}`)
// Build the d3 graph // And give them a label
svg
.selectAll("mylabels")
.data(nodes)
.enter()
.append("text")
.attr("x", function (d) {
return x(d.id);
})
.attr("y", height - 10)
.text(function (d) {
return numToAlphabeticalString(d.position);
})
.style("text-anchor", "middle");
removeOldSvg() // Add the links
var svg = d3.select("#graph") svg
.append("svg") .selectAll("mylinks")
.attr("width", width + margin.left + margin.right) .data(links)
.attr("height", height + margin.top + margin.bottom) .enter()
.append("g") .append("path")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); .attr("d", function (d) {
let start = x(d.source);
// X position of start node on the X axis
let end = x(d.target);
// X position of end node
return (
[
"M",
start,
height - 30,
// the arc starts at the coordinate x=start, y=height-30 (where the starting node is)
"A",
// This means we're gonna build an elliptical arc
(start - end) / 2,
",",
// Next 2 lines are the coordinates of the inflexion point. Height of this point is proportional with start - end distance
(start - end) / 2,
0,
0,
",",
start < end ? 1 : 0,
end,
",",
height - 30,
]
// We always want the arc on top. So if end is before start, putting 0 here turn the arc upside down.
.join(" ")
);
})
.style("fill", "none")
.attr("stroke", "black");
// A linear scale to position the nodes on the X axis // labels for links
svg
.selectAll("mylinks")
// Add the circle for the nodes .data(links)
svg .enter()
.selectAll("mynodes") .append("text")
.data(nodes) .attr("x", function (d) {
.enter() let start = x(d.source);
.append("circle") // X position of start node on the X axis
.attr("cx", function (d) { return (x(d.id)) }) let end = x(d.target);
.attr("cy", height - 30) // X position of end node
.attr("r", 8) return start + (end - start) / 2; //-4*getlength(d.distance)
.style("fill", "#69b3a2") })
.attr("y", function (d) {
// And give them a label let start = x(d.source);
svg // X position of start node on the X axis
.selectAll("mylabels") let end = x(d.target);
.data(nodes) // X position of end node
.enter() return height - 32 - Math.abs(start - end) / 2; //height-30
.append("text") })
.attr("x", function (d) { return (x(d.id)) }) .text(function (d) {
.attr("y", height - 10) return formatLargeOrSmall(Number(d.distance));
.text(function (d) { return numToAlphabeticalString(d.position) }) // return (truncateValueForDisplay(Number(d.distance)))
.style("text-anchor", "middle") //return(Number(d.distance).toPrecision(2).toString())
})
// Add the links .style("text-anchor", "middle");
svg
.selectAll('mylinks')
.data(links)
.enter()
.append('path')
.attr('d', function (d) {
let start = x(d.source)
// X position of start node on the X axis
let end = x(d.target)
// X position of end node
return ['M',
start,
height - 30,
// the arc starts at the coordinate x=start, y=height-30 (where the starting node is)
'A',
// This means we're gonna build an elliptical arc
(start - end) / 2, ',',
// Next 2 lines are the coordinates of the inflexion point. Height of this point is proportional with start - end distance
(start - end) / 2, 0, 0, ',',
start < end ? 1 : 0, end, ',', height - 30]
// We always want the arc on top. So if end is before start, putting 0 here turn the arc upside down.
.join(' ');
})
.style("fill", "none")
.attr("stroke", "black")
// labels for links
svg
.selectAll('mylinks')
.data(links)
.enter()
.append("text")
.attr("x", function (d) {
let start = x(d.source)
// X position of start node on the X axis
let end = x(d.target)
// X position of end node
return start + (end - start) / 2 //-4*getlength(d.distance)
})
.attr("y", function (d) {
let start = x(d.source)
// X position of start node on the X axis
let end = x(d.target)
// X position of end node
return height - 32 - (Math.abs(start - end) / 2)//height-30
})
.text(function (d) {
return formatLargeOrSmall(Number(d.distance))
// return (truncateValueForDisplay(Number(d.distance)))
//return(Number(d.distance).toPrecision(2).toString())
})
.style("text-anchor", "middle")
} }
export function DrawGraph({ isListOrdered, orderedList, listOfElements, links }) { export function DrawGraph({
if (isListOrdered) { isListOrdered,
let nodes = orderedList.map((id, pos) => ({ ...listOfElements[id], position: pos })) orderedList,
drawGraphInner({ nodes, links }); listOfElements,
} links,
return ( }) {
<div> if (isListOrdered) {
<div id="graph"> let nodes = orderedList.map((id, pos) => ({
</div> ...listOfElements[id],
</div> position: pos,
); }));
drawGraphInner({ nodes, links });
}
return (
<div>
<div id="graph"></div>
</div>
);
}
}

View File

@ -1,78 +1,94 @@
import crypto from "crypto" import crypto from "crypto";
export const hashString = (string) => crypto.createHash('md5').update(string).digest('hex'); export const hashString = (string) =>
const id = x => x crypto.createHash("md5").update(string).digest("hex");
export const transformSliderValueToActualValue = id const id = (x) => x;
export const transformSliderValueToPracticalValue = id export const transformSliderValueToActualValue = id;
export const transformSliderValueToPracticalValue = id;
export const _transformSliderValueToActualValue = value => 10 ** value //>= 2 ? Math.round(10 ** value) : Math.round(10 * 10 ** value) / 10 export const _transformSliderValueToActualValue = (value) => 10 ** value; //>= 2 ? Math.round(10 ** value) : Math.round(10 * 10 ** value) / 10
export const toLocale = x => Number(x).toLocaleString() export const toLocale = (x) => Number(x).toLocaleString();
export const truncateValueForDisplay = value => { export const truncateValueForDisplay = (value) => {
if (value > 10) { if (value > 10) {
return Number(Math.round(value).toPrecision(2)) return Number(Math.round(value).toPrecision(2));
} else if (value > 1) { } else if (value > 1) {
return Math.round(value * 10) / 10 return Math.round(value * 10) / 10;
} else if (value < 1) { } else if (value < 1) {
}
} };
} export const _transformSliderValueToPracticalValue = (value) =>
export const _transformSliderValueToPracticalValue = value => truncateValueForDisplay(transformSliderValueToActualValue(value)) truncateValueForDisplay(transformSliderValueToActualValue(value));
export function sleep(ms) { export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
export function numToAlphabeticalString(num) { export function numToAlphabeticalString(num) {
// https://stackoverflow.com/questions/45787459/convert-number-to-alphabet-string-javascript/45787487 // https://stackoverflow.com/questions/45787459/convert-number-to-alphabet-string-javascript/45787487
num = num + 1 num = num + 1;
var s = '', t; var s = "",
while (num > 0) { t;
t = (num - 1) % 26; while (num > 0) {
s = String.fromCharCode(65 + t) + s; t = (num - 1) % 26;
num = (num - t) / 26 | 0; s = String.fromCharCode(65 + t) + s;
} num = ((num - t) / 26) | 0;
return `#${s}` || undefined; }
return `#${s}` || undefined;
} }
export function formatLargeOrSmall(num) { export function formatLargeOrSmall(num) {
if (num > 1) { if (num > 1) {
return toLocale(truncateValueForDisplay(num)) return toLocale(truncateValueForDisplay(num));
} else if (num > 0) { } else if (num > 0) {
return num.toFixed(-Math.floor(Math.log(num) / Math.log(10)) + 1); return num.toFixed(-Math.floor(Math.log(num) / Math.log(10)) + 1);
} else if (num < -1) { } else if (num < -1) {
return num.toFixed(-Math.floor(Math.log(-num) / Math.log(10)) + 1); return num.toFixed(-Math.floor(Math.log(-num) / Math.log(10)) + 1);
} else { } else {
return toLocale(num)//return "~0" return toLocale(num); //return "~0"
}
}
} }
const firstFewMaxMergeSortSequence = [0, 0, 1, 3, 5, 8, 11, 14, 17, 21, 25, 29, 33, 37, 41, 45, 49, 54, 59, 64, 69, 74, 79, 84, 89, 94, 99, 104, 109, 114, 119, 124, 129, 135, 141, 147, 153, 159, 165, 171, 177, 183, 189, 195, 201, 207, 213, 219, 225, 231, 237, 243, 249, 255, 261, 267, 273, 279, 285] const firstFewMaxMergeSortSequence = [
0, 0, 1, 3, 5, 8, 11, 14, 17, 21, 25, 29, 33, 37, 41, 45, 49, 54, 59, 64, 69,
74, 79, 84, 89, 94, 99, 104, 109, 114, 119, 124, 129, 135, 141, 147, 153, 159,
165, 171, 177, 183, 189, 195, 201, 207, 213, 219, 225, 231, 237, 243, 249,
255, 261, 267, 273, 279, 285,
];
export function maxMergeSortSteps(n) { export function maxMergeSortSteps(n) {
if (n < firstFewMaxMergeSortSequence.length) { if (n < firstFewMaxMergeSortSequence.length) {
return firstFewMaxMergeSortSequence[n] return firstFewMaxMergeSortSequence[n];
} else { } else {
return maxMergeSortSteps(Math.floor(n / 2)) + maxMergeSortSteps(Math.ceil(n / 2)) + n - 1 return (
} maxMergeSortSteps(Math.floor(n / 2)) +
maxMergeSortSteps(Math.ceil(n / 2)) +
n -
1
);
}
} }
export function expectedNumMergeSortSteps(n) { export function expectedNumMergeSortSteps(n) {
// https://cs.stackexchange.com/questions/82862/expected-number-of-comparisons-in-a-merge-step // https://cs.stackexchange.com/questions/82862/expected-number-of-comparisons-in-a-merge-step
// n-2 for each step, so (n-2) + (n-2)/2 + (n-2)/4 + ... // n-2 for each step, so (n-2) + (n-2)/2 + (n-2)/4 + ...
// ~ 2*(n-2) -1 = 2*n - 3 // ~ 2*(n-2) -1 = 2*n - 3
if (n == 0) { if (n == 0) {
return 0 return 0;
} else if (n == 1) { } else if (n == 1) {
return 0 return 0;
} else if (n == 2) { } else if (n == 2) {
return 1 return 1;
} else if (n == 3) { } else if (n == 3) {
return 2 return 2;
} else { } else {
return Math.ceil((n ** 2) / (n + 2)) + expectedNumMergeSortSteps(Math.ceil(n / 2)) return (
} Math.ceil(n ** 2 / (n + 2)) + expectedNumMergeSortSteps(Math.ceil(n / 2))
);
}
} }
export const avg = arr => arr.reduce((a, b) => (a + b), 0) / arr.length export const avg = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
export const increasingList = (n) => Array.from(Array(n).keys()) export const geomMean = (arr) =>
arr.reduce((a, b) => a * b, 1) ^ (1 / arr.length);
export const increasingList = (n) => Array.from(Array(n).keys());