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,71 +1,85 @@
/* Imports*/
import React from 'react';
import { numToAlphabeticalString, formatLargeOrSmall, avg } from "../lib/utils.js"
import React from "react";
import {
numToAlphabeticalString,
formatLargeOrSmall,
avg,
geomMean,
} from "../lib/utils.js";
/* Functions */
const pathPlusLink = (pathSoFar, link) => {
return [...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
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 paths = [];
/* Path traversing */
if (maxLengthOfPath > 0) {
for (let link of links) { // vs let link of linksNow in findPaths
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)) {
(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),
maxLengthOfPath: maxLengthOfPath - 1,
sourceElementId: link.target,
targetElementId,
links: links, // vs let link of linksInner in findPaths
nodes
})
nodes,
});
if (newPaths.length != 0) {
paths.push(...newPaths)
paths.push(...newPaths);
}
} else if ((link.target == sourceElementId)) {
} else if (link.target == sourceElementId) {
let newPaths = await findPaths({
pathSoFar: pathPlusLink(pathSoFar, link),
maxLengthOfPath: (maxLengthOfPath - 1),
maxLengthOfPath: maxLengthOfPath - 1,
sourceElementId: link.source,
targetElementId,
links: links, // vs let link of linksInner in findPaths
nodes
})
nodes,
});
if (newPaths.length != 0) {
paths.push(...newPaths)
paths.push(...newPaths);
}
}
}
}
return paths
return paths;
}
async function findPaths({
sourceElementId, sourceElementPosition,
targetElementId, targetElementPosition,
maxLengthOfPath, pathSoFar,
links, nodes
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
@ -74,117 +88,133 @@ async function findPaths({
// traverse only those which are between the origin and target links
// this requires us to have a notion of "between"
let paths = []
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))
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)) {
(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),
maxLengthOfPath: maxLengthOfPath - 1,
sourceElementPosition: link.sourceElementPosition,
sourceElementId: link.target,
targetElementId, targetElementPosition,
targetElementId,
targetElementPosition,
links: linksInner,
nodes
})
nodes,
});
if (newPaths.length != 0) {
paths.push(...newPaths)
paths.push(...newPaths);
}
} else if ((link.target == sourceElementId)) {
} else if (link.target == sourceElementId) {
let newPaths = await findPaths({
pathSoFar: pathPlusLink(pathSoFar, link),
maxLengthOfPath: (maxLengthOfPath - 1),
maxLengthOfPath: maxLengthOfPath - 1,
sourceElementPosition: link.sourceElementPosition,
sourceElementId: link.source,
targetElementPosition,
targetElementId,
links: linksInner,
nodes
})
nodes,
});
if (newPaths.length != 0) {
paths.push(...newPaths)
paths.push(...newPaths);
}
}
}
}
return paths
return paths;
}
async function findDistance({
sourceElementId, sourceElementPosition,
targetElementId, targetElementPosition,
nodes, links
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}`)
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({
sourceElementId, sourceElementPosition,
targetElementId, targetElementPosition,
links, nodes,
maxLengthOfPath, pathSoFar: []
sourceElementId,
sourceElementPosition,
targetElementId,
targetElementPosition,
links,
nodes,
maxLengthOfPath,
pathSoFar: [],
});
let weights = []
let weights = [];
for (let path of paths) {
let currentSource = sourceElementId
let weight = 1
let currentSource = sourceElementId;
let weight = 1;
for (let element of path) {
let distance = 0
let distance = 0;
if (element.source == currentSource) {
distance = element.distance
currentSource = element.target
distance = element.distance;
currentSource = element.target;
} else if (element.target == currentSource) {
distance = 1 / Number(element.distance)
currentSource = element.source
distance = 1 / Number(element.distance);
currentSource = element.source;
}
weight = weight * distance
weight = weight * distance;
}
weights.push(weight)
weights.push(weight);
}
return weights
return weights;
}
async function findDistancesForAllElements({ nodes, links }) {
// Simple wrapper function around findDistance
// Needs to find the reference point first
console.log("findDistancesForAllElements@findPaths.js")
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}`)
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 distances = nodes.map(node => {
if (node.isReferenceValue || (node.id == referenceElement.id)) {
return [1]
let distances = nodes.map((node) => {
if (node.isReferenceValue || node.id == referenceElement.id) {
return [1];
} else {
console.log("node")
console.log(node)
console.log("node");
console.log(node);
let distance = findDistance({
sourceElementId: referenceElement.id,
sourceElementPosition: referenceElement.position,
@ -192,59 +222,70 @@ async function findDistancesForAllElements({ nodes, links }) {
targetElementPosition: node.position,
nodes: nodes,
links: links,
})
return distance
});
return distance;
}
})
distances = await Promise.all(distances)
return distances
});
distances = await Promise.all(distances);
return distances;
}
export async function buildRows({ isListOrdered, orderedList, listOfElements, links, rows, setTableRows }) {
console.log("buildRows@findPaths.js")
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 = ({})
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 => ({
nodes.push({ ...listOfElements[id], position: pos });
positionDictionary[id] = pos;
});
links = links.map((link) => ({
...link,
sourceElementPosition: positionDictionary[link.source],
targetElementPosition: positionDictionary[link.target]
}))
targetElementPosition: positionDictionary[link.target],
}));
let distances = await findDistancesForAllElements({ nodes, links })
let distances = await 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)
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
let newArray;
let formatForDisplay;
if (array.length > 10) {
newArray = array.slice(0, 9)
formatForDisplay = newArray.map(d => formatLargeOrSmall(d))
formatForDisplay[9] = "..."
newArray = array.slice(0, 9);
formatForDisplay = newArray.map((d) => formatLargeOrSmall(d));
formatForDisplay[9] = "...";
} else {
newArray = array
formatForDisplay = newArray.map(d => formatLargeOrSmall(d))
newArray = array;
formatForDisplay = newArray.map((d) => formatLargeOrSmall(d));
}
let result = JSON.stringify(formatForDisplay, null, 2).replaceAll(`"`, "")
return result
let result = JSON.stringify(formatForDisplay, null, 2).replaceAll(`"`, "");
return result;
}
return (
<div>
@ -263,7 +304,8 @@ export function CreateTable({ tableRows }) {
</tr>
</thead>
<tbody>
{tableRows.map(row => <tr key={row.id}>
{tableRows.map((row) => (
<tr key={row.id}>
<td className="">{row.id}</td>
<td>&nbsp;&nbsp;&nbsp;</td>
<td className="">{row.position}</td>
@ -272,14 +314,15 @@ export function CreateTable({ tableRows }) {
<td>&nbsp;&nbsp;&nbsp;</td>
<td className="">{abridgeArrayAndDisplay(row.distances)}</td>
<td>&nbsp;&nbsp;&nbsp;</td>
<td className="">{formatLargeOrSmall(avg(row.distances))}</td>
<td className="">
{formatLargeOrSmall(geomMean(row.distances))}
</td>
</tr>
)}
))}
</tbody>
</table>
</div>
)
);
}
/* Testing */
@ -301,3 +344,4 @@ console.log(JSON.stringify(paths, null, 2))
let distances = findDistance({sourceElementId:2, targetElementId:4, links, nodes})
console.log(distances)
*/

View File

@ -1,6 +1,11 @@
import React, { useState, useEffect } from "react";
import * as d3 from 'd3';
import { toLocale, truncateValueForDisplay, numToAlphabeticalString, formatLargeOrSmall } from "./utils.js"
import * as d3 from "d3";
import {
toLocale,
truncateValueForDisplay,
numToAlphabeticalString,
formatLargeOrSmall,
} from "./utils.js";
let getlength = (number) => number.toString().length;
@ -9,41 +14,39 @@ export function removeOldSvg() {
}
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)
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);
// Calculate the dimensions
// let margin = { top: 0, right: 30, bottom: 20, left: 30 };
// let width = 900 - margin.left - margin.right;
let initialWindowWidth = window.innerWidth
let initialWindowWidth = window.innerWidth;
let margin = { top: 0, right: 10, bottom: 30, left: 10 };
let width = initialWindowWidth*0.7 - margin.left - margin.right;
let width = initialWindowWidth * 0.8 - margin.left - margin.right;
var x = d3.scalePoint()
.range([0, width])
.domain(nodeids)
var x = d3.scalePoint().range([0, width]).domain(nodeids);
let heights = links.map(link => {
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 heights = links.map((link) => {
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}`)
console.log(`height: ${height}`);
// Build the d3 graph
removeOldSvg()
var svg = d3.select("#graph")
removeOldSvg();
var svg = d3
.select("#graph")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
@ -52,17 +55,18 @@ function drawGraphInner({ nodes, links }) {
// 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)) })
.attr("cx", function (d) {
return x(d.id);
})
.attr("cy", height - 30)
.attr("r", 8)
.style("fill", "#69b3a2")
.style("fill", "#69b3a2");
// And give them a label
svg
@ -70,76 +74,98 @@ function drawGraphInner({ nodes, links }) {
.data(nodes)
.enter()
.append("text")
.attr("x", function (d) { return (x(d.id)) })
.attr("x", function (d) {
return x(d.id);
})
.attr("y", height - 10)
.text(function (d) { return numToAlphabeticalString(d.position) })
.style("text-anchor", "middle")
.text(function (d) {
return numToAlphabeticalString(d.position);
})
.style("text-anchor", "middle");
// Add the links
svg
.selectAll('mylinks')
.selectAll("mylinks")
.data(links)
.enter()
.append('path')
.attr('d', function (d) {
let start = x(d.source)
.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)
let end = x(d.target);
// X position of end node
return ['M',
return (
[
"M",
start,
height - 30,
// the arc starts at the coordinate x=start, y=height-30 (where the starting node is)
'A',
"A",
// This means we're gonna build an elliptical arc
(start - end) / 2, ',',
(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]
(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(' ');
.join(" ")
);
})
.style("fill", "none")
.attr("stroke", "black")
.attr("stroke", "black");
// labels for links
svg
.selectAll('mylinks')
.selectAll("mylinks")
.data(links)
.enter()
.append("text")
.attr("x", function (d) {
let start = x(d.source)
let start = x(d.source);
// X position of start node on the X axis
let end = x(d.target)
let end = x(d.target);
// X position of end node
return start + (end - start) / 2 //-4*getlength(d.distance)
return start + (end - start) / 2; //-4*getlength(d.distance)
})
.attr("y", function (d) {
let start = x(d.source)
let start = x(d.source);
// X position of start node on the X axis
let end = x(d.target)
let end = x(d.target);
// X position of end node
return height - 32 - (Math.abs(start - end) / 2)//height-30
return height - 32 - Math.abs(start - end) / 2; //height-30
})
.text(function (d) {
return formatLargeOrSmall(Number(d.distance))
return formatLargeOrSmall(Number(d.distance));
// return (truncateValueForDisplay(Number(d.distance)))
//return(Number(d.distance).toPrecision(2).toString())
})
.style("text-anchor", "middle")
.style("text-anchor", "middle");
}
export function DrawGraph({ isListOrdered, orderedList, listOfElements, links }) {
export function DrawGraph({
isListOrdered,
orderedList,
listOfElements,
links,
}) {
if (isListOrdered) {
let nodes = orderedList.map((id, pos) => ({ ...listOfElements[id], position: pos }))
let nodes = orderedList.map((id, pos) => ({
...listOfElements[id],
position: pos,
}));
drawGraphInner({ nodes, links });
}
return (
<div>
<div id="graph">
</div>
<div id="graph"></div>
</div>
);
}

View File

@ -1,58 +1,69 @@
import crypto from "crypto"
import crypto from "crypto";
export const hashString = (string) => crypto.createHash('md5').update(string).digest('hex');
const id = x => x
export const transformSliderValueToActualValue = id
export const transformSliderValueToPracticalValue = id
export const hashString = (string) =>
crypto.createHash("md5").update(string).digest("hex");
const id = (x) => x;
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 toLocale = x => Number(x).toLocaleString()
export const truncateValueForDisplay = value => {
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 truncateValueForDisplay = (value) => {
if (value > 10) {
return Number(Math.round(value).toPrecision(2))
return Number(Math.round(value).toPrecision(2));
} else if (value > 1) {
return Math.round(value * 10) / 10
return Math.round(value * 10) / 10;
} else if (value < 1) {
}
}
export const _transformSliderValueToPracticalValue = value => truncateValueForDisplay(transformSliderValueToActualValue(value))
};
export const _transformSliderValueToPracticalValue = (value) =>
truncateValueForDisplay(transformSliderValueToActualValue(value));
export function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function numToAlphabeticalString(num) {
// https://stackoverflow.com/questions/45787459/convert-number-to-alphabet-string-javascript/45787487
num = num + 1
var s = '', t;
num = num + 1;
var s = "",
t;
while (num > 0) {
t = (num - 1) % 26;
s = String.fromCharCode(65 + t) + s;
num = (num - t) / 26 | 0;
num = ((num - t) / 26) | 0;
}
return `#${s}` || undefined;
}
export function formatLargeOrSmall(num) {
if (num > 1) {
return toLocale(truncateValueForDisplay(num))
return toLocale(truncateValueForDisplay(num));
} else if (num > 0) {
return num.toFixed(-Math.floor(Math.log(num) / Math.log(10)) + 1);
} else if (num < -1) {
return num.toFixed(-Math.floor(Math.log(-num) / Math.log(10)) + 1);
} 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) {
if (n < firstFewMaxMergeSortSequence.length) {
return firstFewMaxMergeSortSequence[n]
return firstFewMaxMergeSortSequence[n];
} 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
);
}
}
@ -61,18 +72,23 @@ export function expectedNumMergeSortSteps(n) {
// n-2 for each step, so (n-2) + (n-2)/2 + (n-2)/4 + ...
// ~ 2*(n-2) -1 = 2*n - 3
if (n == 0) {
return 0
return 0;
} else if (n == 1) {
return 0
return 0;
} else if (n == 2) {
return 1
return 1;
} else if (n == 3) {
return 2
return 2;
} 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());