feat: cleanup
This commit is contained in:
parent
4fddea47bf
commit
ec9dd815cc
92
README.md
92
README.md
|
@ -1,28 +1,14 @@
|
||||||
## About
|
## About
|
||||||
|
|
||||||
This repository creates a react webpage that allows to extract a utility function from possibly inconsistent binary comparisons.
|
This repository displays hierarchical estimates. It presents the user with a tree with nodes which can be clicked on and inspected, which visualizes the underlying squiggle calculation.
|
||||||
|
|
||||||
It presents the users with a series of elements to compare, using merge-sort in the background to cleverly minimize the number of choices needed.
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="50%" height="50%" src="./public/example-prompt.png">
|
<img width="50%" height="50%" src="./public/example-hierarchical.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Then, it cleverly aggregates them, on the one hand by producing a graphical representation:
|
So far, this mostly has visualization capabilities, meaning that edits are impermanent.
|
||||||
|
|
||||||
<p align="center">
|
**If you want to use the utility function extractor for a project, we are happy to add a page for your project, like `hierarchical-visualization.quantifieduncertainty.org/your-project`**.
|
||||||
<img width="50%" height="50%" src="./public/example-graph.png">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
and on the other hand doing some fast and clever mean aggregation [^1]:
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img width="50%" height="50%" src="./public/example-table.png">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
Initially, users could only input numbers, e.g., "A is `3` times better than B". But now, users can also input distributions, using the [squiggle](https://www.squiggle-language.com/) syntax, e.g., "A is `1 to 10` times better than B", or "A is `mm(normal(1, 10), uniform(0,100))` better than B".
|
|
||||||
|
|
||||||
**If you want to use the utility function extractor for a project, we are happy to add a page for your project, like `utility-function-extractor.quantifieduncertainty.org/your-project`**.
|
|
||||||
|
|
||||||
## Built with
|
## Built with
|
||||||
|
|
||||||
|
@ -30,56 +16,10 @@ Initially, users could only input numbers, e.g., "A is `3` times better than B".
|
||||||
- [Netlify](https://github.com/netlify/netlify-plugin-nextjs/#readme)
|
- [Netlify](https://github.com/netlify/netlify-plugin-nextjs/#readme)
|
||||||
- [React](https://reactjs.org/)
|
- [React](https://reactjs.org/)
|
||||||
- [Squiggle](https://www.squiggle-language.com/)
|
- [Squiggle](https://www.squiggle-language.com/)
|
||||||
- [Utility tools](https://github.com/quantified-uncertainty/utility-function-extractor/tree/master/packages/utility-tools)
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Navigate to [utility-function-extractor.quantifieduncertainty.org/](https://utility-function-extractor.quantifieduncertainty.org/), and start comparing objects.
|
Navigate to [hierarchical-visualization.quantifieduncertainty.org/](https://utility-function-extractor.quantifieduncertainty.org/) (to do: point to subdomain), and start visualizing.
|
||||||
|
|
||||||
You can change the list of objects to be compared by clicking on "advanced options".
|
|
||||||
|
|
||||||
After comparing objects for a while, you will get a table and a graph with results. You can also use the [utility tools](https://github.com/quantified-uncertainty/utility-function-extractor/tree/master/packages/utility-tools) package to process these results, for which you will need the json of comparisons, which can be found in "Advanced options" -> "Load comparisons"
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
The core structure is json array of objects. Only the "name" attribute is required. If there is a "url", it is displayed nicely.
|
|
||||||
|
|
||||||
```
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"name": "Peter Parker",
|
|
||||||
"someOptionalKey": "...",
|
|
||||||
"anotherMoreOptionalKey": "...",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Spiderman",
|
|
||||||
"someOptionalKey": "...",
|
|
||||||
"anotherMoreOptionalKey": "..."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
The core structure for links is as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"source": "Peter Parker",
|
|
||||||
"target": "Spiderman",
|
|
||||||
"squiggleString": "1 to 100",
|
|
||||||
"distance": 26.639800977355474
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Spiderman",
|
|
||||||
"target": "Jonah Jameson",
|
|
||||||
"squiggleString": "20 to 2000",
|
|
||||||
"distance": 6.76997149080232
|
|
||||||
},
|
|
||||||
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
A previous version of this webpage had a more complicated structure, but it has since been simplified.
|
|
||||||
|
|
||||||
## Contributions and help
|
## Contributions and help
|
||||||
|
|
||||||
|
@ -91,24 +31,4 @@ Distributed under the MIT License. See LICENSE.txt for more information.
|
||||||
|
|
||||||
## To do
|
## To do
|
||||||
|
|
||||||
- [x] Extract merge, findPath and aggregatePath functionality into different repos
|
- [ ] Allow for edits.
|
||||||
- [x] Send to mongo upon completion
|
|
||||||
- [x] Push to github
|
|
||||||
- [x] Push to netlify
|
|
||||||
- [x] Don't allow further comparisons after completion
|
|
||||||
- [x] Paths table
|
|
||||||
- [x] Add paths table
|
|
||||||
- [x] warn that the paths table is approximate.
|
|
||||||
- I really don't feel like re-adding this after having worked out the distribution rather than the mean aggregation
|
|
||||||
- On the other hand, I think it does make it more user to other users.
|
|
||||||
- [x] Change README.
|
|
||||||
- [ ] Add functionality like names, etc.
|
|
||||||
- I also don't feel like doing this
|
|
||||||
- [ ] Look back at Amazon thing which has been running
|
|
||||||
- [ ] Simplify Graph and DynamicSquiggleChart components
|
|
||||||
- [ ] Add squiggle component to initial comparison?
|
|
||||||
- [ ] Understand why the rewrite doesn't
|
|
||||||
- Maybe: When two elements are judged to be roughly equal
|
|
||||||
- Maybe: Slightly different merge-sort algorithm.
|
|
||||||
|
|
||||||
[^1]: The program takes each element as a reference point in turn, and computing the possible distances from that reference point to all other points, and taking the geometric mean of these distances. This produces a number representing the value of each element, such that the ratios between elements represent the user's preferences: a utility function. However, this isn't perfect; the principled approach woud be to aggregate the distributions rather than their means. But this principled approach is much more slowly. For the principled approach, see the `utility-tools` repository.
|
|
|
@ -1,85 +0,0 @@
|
||||||
import React, { useState } from "react";
|
|
||||||
import { ShowComparisons } from "./showComparisons.js";
|
|
||||||
import { ComparisonsChanger } from "./comparisonsChanger.js";
|
|
||||||
import { DataSetChanger } from "./datasetChanger.js";
|
|
||||||
import { setRevalidateHeaders } from "next/dist/server/send-payload/revalidate-headers.js";
|
|
||||||
|
|
||||||
const effectButtonStyle =
|
|
||||||
"bg-transparent m-2 hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded mt-5";
|
|
||||||
|
|
||||||
export function AdvancedOptions({
|
|
||||||
links,
|
|
||||||
setLinks,
|
|
||||||
listOfElements,
|
|
||||||
moveToNextStep,
|
|
||||||
onChangeOfDataset,
|
|
||||||
}) {
|
|
||||||
const [showAdvancedOptions, changeShowAdvanceOptions] = useState(false);
|
|
||||||
|
|
||||||
const [showComparisons, setShowComparisons] = useState(false);
|
|
||||||
const toggleShowComparisons = () => setShowComparisons(!showComparisons);
|
|
||||||
|
|
||||||
const [showLoadComparisons, setShowLoadComparisons] = useState(false);
|
|
||||||
const toggleShowLoadComparisons = () =>
|
|
||||||
setShowLoadComparisons(!showLoadComparisons);
|
|
||||||
|
|
||||||
const [showChangeDataset, setShowChangeDataset] = useState(false);
|
|
||||||
const toggleShowChangeDataset = () =>
|
|
||||||
setShowChangeDataset(!showChangeDataset);
|
|
||||||
|
|
||||||
const buttonNames = [
|
|
||||||
// "Show Comparisons",
|
|
||||||
"Load comparisons",
|
|
||||||
"Use your own data",
|
|
||||||
];
|
|
||||||
const buttonToggles = [
|
|
||||||
// toggleShowComparisons,
|
|
||||||
toggleShowLoadComparisons,
|
|
||||||
toggleShowChangeDataset,
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="">
|
|
||||||
{/* Show advanced options*/}
|
|
||||||
<button
|
|
||||||
key={"advancedOptionsButton-top"}
|
|
||||||
className="text-gray-500 text-sm "
|
|
||||||
onClick={() => changeShowAdvanceOptions(!showAdvancedOptions)}
|
|
||||||
>
|
|
||||||
Advanced options ▼
|
|
||||||
</button>
|
|
||||||
{/* Toggle buttons */}
|
|
||||||
<div className={showAdvancedOptions ? "" : "hidden"}>
|
|
||||||
{buttonNames.map((buttonName, i) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={effectButtonStyle}
|
|
||||||
onClick={() => buttonToggles[i]()}
|
|
||||||
key={`advancedOptionsButton-${i}`}
|
|
||||||
>
|
|
||||||
{buttonName}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{/* Element: Show comparisons */}
|
|
||||||
{/* <ShowComparisons links={links} show={showComparisons} /> */}
|
|
||||||
|
|
||||||
{/* Element: Change comparisons */}
|
|
||||||
<ComparisonsChanger
|
|
||||||
setLinks={setLinks}
|
|
||||||
listOfElements={listOfElements}
|
|
||||||
show={showLoadComparisons}
|
|
||||||
moveToNextStep={moveToNextStep}
|
|
||||||
links={links}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Element: Dataset changer */}
|
|
||||||
<DataSetChanger
|
|
||||||
onChangeOfDataset={onChangeOfDataset}
|
|
||||||
show={showChangeDataset}
|
|
||||||
listOfElements={listOfElements}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import { Separator } from "../separator.js";
|
|
||||||
|
|
||||||
const checkLinksAreOk = (links, listOfElements) => {
|
|
||||||
let linkSourceNames = links.map((link) => link.source.name);
|
|
||||||
let linkTargetNames = links.map((link) => link.target.name);
|
|
||||||
let allLinkNames = [...linkSourceNames, ...linkTargetNames];
|
|
||||||
let uniqueNames = [...new Set(allLinkNames)];
|
|
||||||
|
|
||||||
let listOfElementNames = listOfElements.map((element) => element.name);
|
|
||||||
let anyInvalidNames = uniqueNames.indexOf(
|
|
||||||
(name) => !listOfElementNames.includes(name)
|
|
||||||
);
|
|
||||||
let anyElementsWithoutDistances = links.indexOf(
|
|
||||||
(link) => !link.distance && link.distance != 0
|
|
||||||
);
|
|
||||||
if (anyInvalidNames == -1 && anyElementsWithoutDistances == -1) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ComparisonsChanger({
|
|
||||||
setLinks,
|
|
||||||
listOfElements,
|
|
||||||
show,
|
|
||||||
moveToNextStep,
|
|
||||||
links,
|
|
||||||
}) {
|
|
||||||
let [value, setValue] = useState(JSON.stringify(links, null, 4));
|
|
||||||
const [displayingDoneMessage, setDisplayingDoneMessage] = useState(false);
|
|
||||||
const [displayingDoneMessageTimer, setDisplayingDoneMessageTimer] =
|
|
||||||
useState(null);
|
|
||||||
|
|
||||||
let handleTextChange = (event) => {
|
|
||||||
setValue(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
let handleSubmitInner = (event) => {
|
|
||||||
clearTimeout(displayingDoneMessageTimer);
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
let newData = JSON.parse(value);
|
|
||||||
|
|
||||||
if (checkLinksAreOk(newData, listOfElements)) {
|
|
||||||
setLinks(newData);
|
|
||||||
moveToNextStep({
|
|
||||||
listOfElements,
|
|
||||||
whileChangingStuff: true,
|
|
||||||
newLinksFromChangingStuff: newData,
|
|
||||||
});
|
|
||||||
setDisplayingDoneMessage(true);
|
|
||||||
let timer = setTimeout(() => setDisplayingDoneMessage(false), 3000);
|
|
||||||
setDisplayingDoneMessageTimer(timer);
|
|
||||||
} else {
|
|
||||||
throw Error("Links are not ok");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setDisplayingDoneMessage(false);
|
|
||||||
alert(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setValue(JSON.stringify(links, null, 4));
|
|
||||||
}, [links]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form
|
|
||||||
onSubmit={handleSubmitInner}
|
|
||||||
className={`inline ${show ? "" : "hidden"}`}
|
|
||||||
>
|
|
||||||
<Separator />
|
|
||||||
<h3 className="text-lg mt-8">Load comparisons</h3>
|
|
||||||
<p>These can be edited, which will override your current comparisons.</p>
|
|
||||||
<br />
|
|
||||||
<textarea
|
|
||||||
value={value}
|
|
||||||
onChange={handleTextChange}
|
|
||||||
rows={4 + JSON.stringify(links, null, 4).split("\n").length}
|
|
||||||
cols={70}
|
|
||||||
className="text-left text-gray-600 bg-white rounded text-normal p-10 border-0 shadow outline-none focus:outline-none focus:ring "
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<button
|
|
||||||
className="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded mt-5 p-10"
|
|
||||||
onClick={handleSubmitInner}
|
|
||||||
>
|
|
||||||
Change comparisons
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
displayingDoneMessage
|
|
||||||
? "bg-transparent text-blue-700 font-semibold py-2 px-4 border border-blue-500 rounded mt-5 p-10"
|
|
||||||
: "hidden"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Done!
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
import React, { useState } from "react";
|
|
||||||
import { Separator } from "../separator.js";
|
|
||||||
|
|
||||||
export function DataSetChanger({ onChangeOfDataset, show, listOfElements }) {
|
|
||||||
/*let [value, setValue] = useState(`[
|
|
||||||
{
|
|
||||||
"name": "Some element. The name field is necessary",
|
|
||||||
"url": "http://www.example.com",
|
|
||||||
"somethirdfield": "a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Another element",
|
|
||||||
"url": "http://www.example1.com",
|
|
||||||
"somethirdfield": "b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "A third element",
|
|
||||||
"url": "http://www.example2.com",
|
|
||||||
"isReferenceValue": true,
|
|
||||||
"somethirdfield": "c"
|
|
||||||
}
|
|
||||||
]`);*/
|
|
||||||
let [value, setValue] = useState(JSON.stringify(listOfElements, null, 4));
|
|
||||||
|
|
||||||
const [displayingDoneMessage, setDisplayingDoneMessage] = useState(false);
|
|
||||||
const [displayingDoneMessageTimer, setDisplayingDoneMessageTimer] =
|
|
||||||
useState(null);
|
|
||||||
|
|
||||||
let handleChange = (event) => {
|
|
||||||
setValue(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
let handleSubmitInner = (event) => {
|
|
||||||
clearTimeout(displayingDoneMessageTimer);
|
|
||||||
event.preventDefault();
|
|
||||||
console.log("value@handleSubmitInner@DataSetChanger");
|
|
||||||
console.log(value);
|
|
||||||
try {
|
|
||||||
let newData = JSON.parse(value);
|
|
||||||
if (!newData.length || newData.length < 2) {
|
|
||||||
throw Error("Not enough objects");
|
|
||||||
}
|
|
||||||
onChangeOfDataset(newData);
|
|
||||||
setDisplayingDoneMessage(true);
|
|
||||||
let timer = setTimeout(() => setDisplayingDoneMessage(false), 3000);
|
|
||||||
setDisplayingDoneMessageTimer(timer);
|
|
||||||
} catch (error) {
|
|
||||||
setDisplayingDoneMessage(false);
|
|
||||||
alert(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div className={`${show ? "" : "hidden"} `}>
|
|
||||||
<Separator />
|
|
||||||
<form onSubmit={handleSubmitInner} className="inline mt-0">
|
|
||||||
<h3 className="text-lg mt-8 mb-4">Change dataset</h3>
|
|
||||||
<textarea
|
|
||||||
value={value}
|
|
||||||
onChange={handleChange}
|
|
||||||
rows={
|
|
||||||
1.2 * JSON.stringify(listOfElements, null, 4).split("\n").length
|
|
||||||
}
|
|
||||||
cols={70}
|
|
||||||
className="text-left text-gray-600 bg-white rounded text-normal p-10 border-0 shadow outline-none focus:outline-none focus:ring "
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<button
|
|
||||||
className="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded mt-5 p-10"
|
|
||||||
onClick={handleSubmitInner}
|
|
||||||
>
|
|
||||||
Change dataset
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
displayingDoneMessage
|
|
||||||
? "bg-transparent text-blue-700 font-semibold py-2 px-4 border border-blue-500 rounded mt-5 p-10"
|
|
||||||
: "hidden"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Done!
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
import React, { state } from "react";
|
|
||||||
import { CopyBlock, googlecode } from "react-code-blocks";
|
|
||||||
import { Separator } from "../separator.js";
|
|
||||||
|
|
||||||
export function ShowComparisons({ links, show }) {
|
|
||||||
return (
|
|
||||||
<div className={`text-left ${show ? "" : "hidden"}`}>
|
|
||||||
<Separator />
|
|
||||||
<h3 className="text-lg mt-8">Comparisons</h3>
|
|
||||||
<CopyBlock
|
|
||||||
text={JSON.stringify(links, null, 4)}
|
|
||||||
language={"js"}
|
|
||||||
showLineNumbers={false}
|
|
||||||
startingLineNumber={0}
|
|
||||||
wrapLines={true}
|
|
||||||
textColor={"black"}
|
|
||||||
theme={googlecode}
|
|
||||||
codeBlock={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
// import { SquiggleChart } from "@quri/squiggle-components";
|
// import { SquiggleChart } from "@quri/squiggle-components";
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
@ -10,20 +10,32 @@ const SquiggleChart = dynamic(
|
||||||
ssr: false,
|
ssr: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
/*
|
|
||||||
const SquiggleChart = dynamic(
|
const SquiggleEditor = dynamic(
|
||||||
() => import("@quri/squiggle-components").then((mod) => mod.SquiggleChart),
|
() => import("@quri/squiggle-components").then((mod) => mod.SquiggleEditor),
|
||||||
{
|
{
|
||||||
suspense: true,
|
loading: () => <p>Loading...</p>,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
*/
|
// ^ works, but updating the editor content from the outside would be tricky.
|
||||||
|
// and so instead we are hacking our own mini-editor.
|
||||||
|
|
||||||
const effectButtonStyle =
|
const effectButtonStyle =
|
||||||
"bg-transparent m-2 hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded mt-5";
|
"bg-transparent m-2 hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded mt-5";
|
||||||
|
|
||||||
|
const countNumberOfLines = string => {
|
||||||
|
return string.split("\n").length
|
||||||
|
}
|
||||||
|
|
||||||
export function DynamicSquiggleChart({ element, stopShowing }) {
|
export function DynamicSquiggleChart({ element, stopShowing }) {
|
||||||
|
const initialEditorState = !!element ? element.fn : ""
|
||||||
|
const [editorState, setEditorState] = useState(initialEditorState)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!!element && element.fn != "") {
|
||||||
|
setEditorState(element.fn)
|
||||||
|
}
|
||||||
|
}, [element]);
|
||||||
if (element == null) {
|
if (element == null) {
|
||||||
return "";
|
return "";
|
||||||
} else {
|
} else {
|
||||||
|
@ -32,43 +44,28 @@ export function DynamicSquiggleChart({ element, stopShowing }) {
|
||||||
squiggleString: element.fn,
|
squiggleString: element.fn,
|
||||||
binding: element.binding || null
|
binding: element.binding || null
|
||||||
};
|
};
|
||||||
console.log(element.binding)
|
|
||||||
// alert(usefulElement.squiggleString)
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
<h3 className="text-2xl font-bold mb-5">{usefulElement.name}</h3>
|
<div className="bg-white p-8">
|
||||||
|
<h3 className="text-2xl font-bold mb-4">{usefulElement.name}</h3>
|
||||||
<textarea
|
<textarea
|
||||||
value={usefulElement.squiggleString}
|
value={editorState}
|
||||||
//onChange={handleChange}
|
onChange={(event) => setEditorState(event.target.value)}
|
||||||
disabled={true}
|
// disabled={true}
|
||||||
rows={5} // could compute from usefulElement.squiggleString
|
rows={countNumberOfLines(editorState) + 1}
|
||||||
cols={30}
|
cols={30}
|
||||||
className="text-left text-gray-600 bg-white rounded text-normal p-8 m-8 border-0 shadow outline-none focus:outline-none focus:ring"
|
className="text-left text-gray-600 bg-white rounded text-normal p-5 border-0 shadow outline-none focus:outline-none focus:ring"
|
||||||
/>
|
/>
|
||||||
<SquiggleChart
|
<SquiggleChart
|
||||||
squiggleString={usefulElement.squiggleString}
|
squiggleString={editorState}
|
||||||
width={500}
|
width={500}
|
||||||
height={200}
|
height={200}
|
||||||
bindings={usefulElement.binding}
|
bindings={usefulElement.binding}
|
||||||
showSummary={true}
|
showSummary={true}
|
||||||
showTypes={true}
|
showTypes={true}
|
||||||
/>
|
/>
|
||||||
{/*
|
|
||||||
SquiggleChart props:
|
|
||||||
squiggleString?: string;
|
|
||||||
sampleCount?: number;
|
|
||||||
environment?: environment;
|
|
||||||
chartSettings?: FunctionChartSettings;
|
|
||||||
onChange?(expr: squiggleExpression): void;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
bindings?: bindings;
|
|
||||||
jsImports?: jsImports;
|
|
||||||
showSummary?: boolean;
|
|
||||||
showTypes?: boolean;
|
|
||||||
showControls?: boolean;
|
|
||||||
*/}
|
|
||||||
|
|
||||||
|
</div>
|
||||||
<button className={effectButtonStyle} onClick={() => stopShowing()}>
|
<button className={effectButtonStyle} onClick={() => stopShowing()}>
|
||||||
Hide chart
|
Hide chart
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { run, runPartial, mergeBindings } from "@quri/squiggle-lang";
|
import { run, runPartial, mergeBindings } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
|
||||||
function miniReducer(obj, parentName) {
|
function miniReducer(obj, parentName) {
|
||||||
let nodes = []
|
let nodes = []
|
||||||
let edges = []
|
let edges = []
|
||||||
|
@ -93,7 +92,6 @@ ${parentName}`;
|
||||||
|
|
||||||
let parentValue = run(parentResultSquiggleString, mergeBindings(bindings));
|
let parentValue = run(parentResultSquiggleString, mergeBindings(bindings));
|
||||||
if (parentValue.tag == "Error") {
|
if (parentValue.tag == "Error") {
|
||||||
// console.log(bindings)
|
|
||||||
return parentValue
|
return parentValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +104,6 @@ ${parentName}`;
|
||||||
})
|
})
|
||||||
|
|
||||||
nodes.push(resultNode)
|
nodes.push(resultNode)
|
||||||
// console.log("resultNode", resultNode)
|
|
||||||
let result = {
|
let result = {
|
||||||
...resultNode,
|
...resultNode,
|
||||||
value: parentValue,
|
value: parentValue,
|
||||||
|
@ -139,20 +136,10 @@ mean(r)`,
|
||||||
if (reducerResult.tag == "Error") {
|
if (reducerResult.tag == "Error") {
|
||||||
return reducerResult
|
return reducerResult
|
||||||
} else {
|
} else {
|
||||||
// console.log("reducerResult", reducerResult)
|
|
||||||
let {nodes, edges} = reducerResult
|
let {nodes, edges} = reducerResult
|
||||||
let nodeElements = nodes.map(node => ({ data: { ...node, name: node.id} }))
|
let nodeElements = nodes.map(node => ({ data: { ...node, name: node.id} }))
|
||||||
let edgeElements = edges.map(edge => ({ data: { ...edge, name: edge.id } }))
|
let edgeElements = edges.map(edge => ({ data: { ...edge, name: edge.id } }))
|
||||||
let answer = { nodeElements, edgeElements }
|
let answer = { nodeElements, edgeElements }
|
||||||
return answer
|
return answer
|
||||||
}
|
}
|
||||||
|
|
||||||
// return { nodeElements: [], edgeElements: [] }
|
|
||||||
/*
|
|
||||||
|
|
||||||
*/
|
|
||||||
// return (resultUtility)
|
|
||||||
|
|
||||||
// Then the rest should be doable without all that much work.
|
|
||||||
// Some duplication of efforts, but I don't really care:
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,9 @@ import { createGraph } from "./createGraph"
|
||||||
|
|
||||||
export function Graph({ }) {
|
export function Graph({ }) {
|
||||||
const containerRef = useRef("hello-world");
|
const containerRef = useRef("hello-world");
|
||||||
const [cs, setCs] = useState(null); /// useState("invisible");
|
const [cs, setCs] = useState(null);
|
||||||
const [selectedElement, setSelectedElement] = useState(null);
|
const [selectedElement, setSelectedElement] = useState(null);
|
||||||
const [selectedElementTimeout, setSelectedElementTimeout] = useState(null);
|
const [selectedElementTimeout, setSelectedElementTimeout] = useState(null);
|
||||||
//let { nodeElements, edgeElements } = createGraph()
|
|
||||||
const graphInit = createGraph()
|
const graphInit = createGraph()
|
||||||
const [nodeElements, setNodeElements] = useState(graphInit.nodeElements)
|
const [nodeElements, setNodeElements] = useState(graphInit.nodeElements)
|
||||||
const [edgeElements, setEdgeElements] = useState(graphInit.edgeElements)
|
const [edgeElements, setEdgeElements] = useState(graphInit.edgeElements)
|
||||||
|
@ -104,7 +103,6 @@ export function Graph({ }) {
|
||||||
// necessary for themes like spread, which have
|
// necessary for themes like spread, which have
|
||||||
// a confusing animation at the beginning
|
// a confusing animation at the beginning
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
callEffect({
|
callEffect({
|
||||||
|
@ -128,18 +126,17 @@ export function Graph({ }) {
|
||||||
cs.nodes().on("click", (event) => {
|
cs.nodes().on("click", (event) => {
|
||||||
clearTimeout(selectedElementTimeout);
|
clearTimeout(selectedElementTimeout);
|
||||||
let node = event.target;
|
let node = event.target;
|
||||||
console.log(JSON.stringify(node.json()));
|
// console.log(JSON.stringify(node.json()));
|
||||||
let newTimeout = setTimeout(() => {
|
let newTimeout = setTimeout(() => {
|
||||||
let selectedElementIncomplete = (JSON.parse(JSON.stringify(node.json())).data)
|
let selectedElementIncomplete = (JSON.parse(JSON.stringify(node.json())).data)
|
||||||
let selectedElementName = selectedElementIncomplete.name
|
let selectedElementName = selectedElementIncomplete.name
|
||||||
let selectedElementInFull = nodeElements.filter(node => node.data.name == selectedElementName)
|
let selectedElementInFull = nodeElements.filter(node => node.data.name == selectedElementName)
|
||||||
console.log("selectedElementInFull", selectedElementInFull)
|
// console.log("selectedElementInFull", selectedElementInFull)
|
||||||
if(selectedElementInFull.length == 1){
|
if(selectedElementInFull.length == 1){
|
||||||
let elementToBeSelected = selectedElementInFull[0]
|
let elementToBeSelected = selectedElementInFull[0]
|
||||||
console.log("elementToBeSelected", elementToBeSelected)
|
// console.log("elementToBeSelected", elementToBeSelected)
|
||||||
setSelectedElement(elementToBeSelected.data)
|
setSelectedElement(elementToBeSelected.data)
|
||||||
}
|
}
|
||||||
// setSelectedElement()
|
|
||||||
});
|
});
|
||||||
setSelectedElementTimeout(newTimeout)
|
setSelectedElementTimeout(newTimeout)
|
||||||
});
|
});
|
||||||
|
@ -148,31 +145,6 @@ export function Graph({ }) {
|
||||||
}
|
}
|
||||||
}, [cs]);
|
}, [cs]);
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
useEffect(() => {
|
|
||||||
if (cs != null) {
|
|
||||||
clearTimeout(selectedElementTimeout);
|
|
||||||
let newTimeout = setTimeout(() => {
|
|
||||||
cs.edges().on("mouseover", (event) => {
|
|
||||||
// on("click",
|
|
||||||
let edge = event.target;
|
|
||||||
// alert(JSON.stringify(edge.json()));
|
|
||||||
console.log(JSON.stringify(edge.json()));
|
|
||||||
setSelectedElement(JSON.parse(JSON.stringify(edge.json())).data);
|
|
||||||
});
|
|
||||||
cs.nodes().on("mouseover", (event) => {
|
|
||||||
// on("click",
|
|
||||||
let node = event.target;
|
|
||||||
// alert(JSON.stringify(edge.json()));
|
|
||||||
console.log(JSON.stringify(node.json()));
|
|
||||||
setSelectedElement(JSON.parse(JSON.stringify(node.json())).data);
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
setSelectedElementTimeout(newTimeout);
|
|
||||||
}
|
|
||||||
}, [cs]);
|
|
||||||
*/
|
|
||||||
return (
|
return (
|
||||||
<div className="grid place-items-center">
|
<div className="grid place-items-center">
|
||||||
<div
|
<div
|
||||||
|
@ -185,8 +157,8 @@ export function Graph({ }) {
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
style={{
|
style={{
|
||||||
height: "900px", // isListOrdered ? "900px" : "500px",
|
height: "900px",
|
||||||
width: "900px", // isListOrdered ? "900px" : "500px",
|
width: "900px",
|
||||||
}}
|
}}
|
||||||
className=""
|
className=""
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
export function Title() {
|
export function Title() {
|
||||||
return <h1 className="text-6xl font-bold ">Hierarchical Estimates Visualizer</h1>;
|
return <h1 className="text-6xl font-bold mb-8">Hierarchical Estimates Visualizer</h1>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
const CONNECTION_IS_ACTIVE = true;
|
|
||||||
|
|
||||||
export async function pushToMongo(data) {
|
|
||||||
if (CONNECTION_IS_ACTIVE) {
|
|
||||||
let response = await axios.post(
|
|
||||||
"https://server.loki.red/utility-function-extractor",
|
|
||||||
{
|
|
||||||
data: data,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
console.log(response);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { run } from "@quri/squiggle-lang";
|
|
||||||
|
|
||||||
export async function resolveToNumIfPossible(comparisonString) {
|
|
||||||
if (!isNaN(comparisonString) && comparisonString != "") {
|
|
||||||
let response = {
|
|
||||||
asNum: true,
|
|
||||||
num: Number(comparisonString),
|
|
||||||
};
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
let squiggleMeanCommand = `mean(${comparisonString})`;
|
|
||||||
let squiggleResponse = await run(squiggleMeanCommand);
|
|
||||||
console.log(squiggleResponse);
|
|
||||||
if (squiggleResponse.tag == "Ok") {
|
|
||||||
let responseAsNumber = squiggleResponse.value.value;
|
|
||||||
let response = {
|
|
||||||
asNum: true,
|
|
||||||
num: Number(responseAsNumber),
|
|
||||||
};
|
|
||||||
return response;
|
|
||||||
} else {
|
|
||||||
let errorMsg = squiggleResponse.value;
|
|
||||||
let response = {
|
|
||||||
asNum: false,
|
|
||||||
errorMsg: errorMsg,
|
|
||||||
};
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSquiggleSparkline(comparisonString) {
|
|
||||||
if (!isNaN(comparisonString) && comparisonString != "") {
|
|
||||||
let response = {
|
|
||||||
success: true,
|
|
||||||
sparkline: comparisonString,
|
|
||||||
};
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
let squiggleSparklineCommand = `sparkline(${comparisonString}, 20)`;
|
|
||||||
let squiggleResponse = await run(squiggleSparklineCommand);
|
|
||||||
console.log(squiggleResponse);
|
|
||||||
if (squiggleResponse.tag == "Ok") {
|
|
||||||
let responseAsNumber = squiggleResponse.value.value;
|
|
||||||
let response = {
|
|
||||||
success: true,
|
|
||||||
sparkline: responseAsNumber,
|
|
||||||
};
|
|
||||||
return response;
|
|
||||||
} else {
|
|
||||||
let errorMsg = squiggleResponse.value;
|
|
||||||
let response = {
|
|
||||||
success: false,
|
|
||||||
errorMsg: errorMsg,
|
|
||||||
};
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
export const cutOffLongNames = (string) => {
|
|
||||||
let maxLength = 40;
|
|
||||||
let result;
|
|
||||||
if (string.length < maxLength) {
|
|
||||||
result = string;
|
|
||||||
} else {
|
|
||||||
result = string.slice(0, maxLength - 4);
|
|
||||||
result = result + "...";
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
|
@ -1,39 +0,0 @@
|
||||||
let topOutAt100AndValidate = (x) => {
|
|
||||||
if (x == x) {
|
|
||||||
return x > 99 ? 99 : x < 0 ? 2 : x;
|
|
||||||
} else {
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const toLocale = (x) => Number(x).toLocaleString();
|
|
||||||
|
|
||||||
export const truncateValueForDisplay = (value) => {
|
|
||||||
let result;
|
|
||||||
if (value > 10) {
|
|
||||||
result = Number(Math.round(value).toPrecision(2));
|
|
||||||
} else if (value > 1) {
|
|
||||||
result = Math.round(value * 10) / 10;
|
|
||||||
} else if (value > 0) {
|
|
||||||
let candidateNumSignificantDigits =
|
|
||||||
-Math.floor(Math.log(value) / Math.log(10)) + 1;
|
|
||||||
let numSignificantDigits = topOutAt100AndValidate(
|
|
||||||
candidateNumSignificantDigits
|
|
||||||
);
|
|
||||||
result = value.toFixed(numSignificantDigits);
|
|
||||||
} else if (value == 0) {
|
|
||||||
return 0;
|
|
||||||
} else if (-1 < value) {
|
|
||||||
let candidateNumSignificantDigits =
|
|
||||||
-Math.floor(Math.log(Math.abs(value)) / Math.log(10)) + 1;
|
|
||||||
let numSignificantDigits = topOutAt100AndValidate(
|
|
||||||
candidateNumSignificantDigits
|
|
||||||
);
|
|
||||||
result = value.toFixed(numSignificantDigits);
|
|
||||||
} else if (value <= -1) {
|
|
||||||
result = "-" + toLocale(truncateValueForDisplay(-value));
|
|
||||||
} else {
|
|
||||||
result = toLocale(value); //return "~0"
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
Binary file not shown.
Before Width: | Height: | Size: 231 KiB |
Binary file not shown.
Before Width: | Height: | Size: 182 KiB |
Binary file not shown.
Before Width: | Height: | Size: 243 KiB |
BIN
public/example-hierarchical.png
Normal file
BIN
public/example-hierarchical.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 98 KiB |
Binary file not shown.
Before Width: | Height: | Size: 58 KiB |
Binary file not shown.
Before Width: | Height: | Size: 145 KiB |
Loading…
Reference in New Issue
Block a user