-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simple Squiggle Calculators #2265
Changes from 1 commit
f15ac43
eeb8b94
7609814
7c36e5e
22e8227
eddf7b4
002d216
167e84e
a231ae4
8a5d26a
77cfa6a
c4a0db4
e2a4033
0c85e45
6abecfe
f931ba7
7b4adfc
5ba152b
248d6ac
dd068db
dcb32e6
c3fd9fc
c030b44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import React, { FC, ReactNode, useState, useEffect } from "react"; | ||
|
||
import { | ||
SqValue, | ||
SqCalculator, | ||
SqError, | ||
SqProject, | ||
result, | ||
} from "@quri/squiggle-lang"; | ||
import { Env } from "@quri/squiggle-lang"; | ||
|
||
import { PlaygroundSettings } from "../PlaygroundSettings.js"; | ||
import { SqValueWithContext, valueHasContext } from "../../lib/utility.js"; | ||
|
||
type Props = { | ||
value: SqCalculator; | ||
environment: Env; | ||
settings: PlaygroundSettings; | ||
renderValue: ( | ||
value: SqValueWithContext, | ||
settings: PlaygroundSettings | ||
) => ReactNode; | ||
}; | ||
//calc.run([output.value.result], environment); | ||
const runSquiggleCode = async ( | ||
calc: SqCalculator, | ||
code: string, | ||
environment: Env | ||
): Promise<result<SqValue, SqError>> => { | ||
const project = SqProject.create(); | ||
if (environment) { | ||
project.setEnvironment(environment); | ||
} | ||
const sourceId = "test"; | ||
project.setSource(sourceId, code); | ||
await project.run(sourceId); | ||
const output = project.getOutput(sourceId); | ||
if (output.ok) { | ||
const result: result<SqValue, SqError> = { | ||
ok: true, | ||
value: output.value.result, | ||
}; | ||
return result; | ||
} else { | ||
return output; | ||
} | ||
}; | ||
|
||
export const Calculator: FC<Props> = ({ | ||
value, | ||
environment, | ||
settings, | ||
renderValue, | ||
}) => { | ||
const [codes, setCodes] = useState<Record<string, string>>(() => { | ||
const initialCodes: Record<string, string> = {}; | ||
value.names.forEach((name) => { | ||
initialCodes[name] = "40"; // Initial code value. This can be changed as needed. | ||
}); | ||
return initialCodes; | ||
}); | ||
|
||
const [cachedResults, setCachedResults] = useState< | ||
Record<string, result<SqValue, SqError> | null> | ||
>({}); | ||
|
||
const [finalResult, setFinalResult] = useState<result< | ||
SqValue, | ||
SqError | ||
> | null>(); | ||
|
||
useEffect(() => { | ||
const fetchResults = async () => { | ||
const newResults: Record<string, result<SqValue, SqError> | null> = {}; | ||
|
||
// Fetch all results | ||
for (const name of value.names) { | ||
const code = codes[name]; | ||
const res = await runSquiggleCode(value, code, environment); | ||
newResults[name] = res; | ||
} | ||
|
||
// Once all results are fetched, update the state | ||
setCachedResults(newResults); | ||
|
||
// Check validity | ||
const allCodesAreValid = value.names.every((name) => { | ||
const result = newResults[name]; | ||
return result && result.ok; | ||
}); | ||
|
||
// If all codes are valid, calculate the final result | ||
if (allCodesAreValid) { | ||
const results: SqValue[] = value.names.map((name) => { | ||
const res = newResults[name]; | ||
if (res && res.ok) { | ||
return res.value; | ||
} else { | ||
// This shouldn't happen since we've already checked if all codes are valid. | ||
// Just a fallback. | ||
throw new Error("Invalid result encountered."); | ||
} | ||
}); | ||
const finalResult: result<SqValue, SqError> = value.run( | ||
results, | ||
environment | ||
); | ||
setFinalResult(finalResult); | ||
} | ||
}; | ||
|
||
fetchResults(); | ||
}, [value, codes, environment]); | ||
|
||
const handleChange = | ||
(name: string) => (e: React.ChangeEvent<HTMLInputElement>) => { | ||
const newCode = e.target.value; | ||
setCodes((prevCodes) => ({ | ||
...prevCodes, | ||
[name]: newCode, | ||
})); | ||
}; | ||
|
||
const showItem = ( | ||
item: result<SqValue, SqError>, | ||
settings: PlaygroundSettings | ||
) => { | ||
if (item.ok) { | ||
const value = item.value; | ||
if (valueHasContext(value)) { | ||
return renderValue(value, settings); | ||
} else { | ||
return value.toString(); | ||
} | ||
} else { | ||
return item.value.toString(); | ||
} | ||
}; | ||
|
||
const chartHeight = 50; | ||
const distributionChartSettings = { | ||
...settings.distributionChartSettings, | ||
showSummary: false, | ||
}; | ||
const adjustedSettings: PlaygroundSettings = { | ||
...settings, | ||
distributionChartSettings, | ||
chartHeight, | ||
}; | ||
|
||
return ( | ||
<div className="relative rounded-sm overflow-hidden border border-slate-200"> | ||
{value.names.map((name) => ( | ||
<div key={name}> | ||
<input | ||
value={codes[name] || ""} | ||
onChange={handleChange(name)} | ||
placeholder={`Enter code for ${name}`} | ||
className="my-2 p-2 border rounded" | ||
/> | ||
{cachedResults[name] | ||
? showItem(cachedResults[name]!, adjustedSettings) | ||
: "Loading..."} | ||
</div> | ||
))} | ||
{finalResult && finalResult.ok && showItem(finalResult, adjustedSettings)} | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
|
||
import { SquiggleChart } from "../../components/SquiggleChart.js"; | ||
|
||
const meta = { | ||
component: SquiggleChart, | ||
} satisfies Meta<typeof SquiggleChart>; | ||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Basic: Story = { | ||
args: { | ||
code: ` | ||
Calculator.make( | ||
{ | ||
fn: {|a,b,c|a+b+c}, | ||
rows: ["a", "b", "c"] | ||
} | ||
) | ||
OAGr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
`, | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { makeDefinition } from "../library/registry/fnDefinition.js"; | ||
import { | ||
frDict, | ||
frLambda, | ||
frArray, | ||
frString, | ||
} from "../library/registry/frTypes.js"; | ||
import { FnFactory } from "../library/registry/helpers.js"; | ||
import { vCalculator } from "../value/index.js"; | ||
|
||
const maker = new FnFactory({ | ||
nameSpace: "Calculator", | ||
requiresNamespace: true, | ||
}); | ||
|
||
export const library = [ | ||
maker.make({ | ||
name: "make", | ||
output: "Calculator", | ||
examples: [], | ||
definitions: [ | ||
makeDefinition( | ||
[frDict(["fn", frLambda], ["rows", frArray(frString)])], | ||
([{ fn, rows }]) => { | ||
return vCalculator({ | ||
fn, | ||
inputs: rows.map((name) => ({ name })), | ||
}); | ||
} | ||
), | ||
], | ||
}), | ||
]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { Calculator } from "../../value/index.js"; | ||
import { Env } from "../../dist/env.js"; | ||
import * as Result from "../../utility/result.js"; | ||
|
||
import { SqError, SqOtherError } from "../SqError.js"; | ||
import { SqValueContext } from "../SqValueContext.js"; | ||
import { SqLambda } from "./SqLambda.js"; | ||
import { SqValue, wrapValue } from "./index.js"; | ||
import { Lambda } from "../../reducer/lambda.js"; | ||
|
||
const wrapFn = ({ fn }: { fn: Lambda }): SqLambda => { | ||
return new SqLambda(fn, undefined); | ||
}; | ||
OAGr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
export class SqCalculator { | ||
constructor(private _value: Calculator, public context?: SqValueContext) {} | ||
|
||
run(items: SqValue[], env: Env): Result.result<SqValue, SqError> { | ||
const response = wrapFn({ fn: this._value.fn }).call(items, env); | ||
const context = this.context; | ||
|
||
const newContext: SqValueContext | undefined = context; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something like This should be enough; we don't want to add argument values there, since we want to keep local settings for calculator outputs when arguments change. You'll also have to patch |
||
|
||
if (response.ok && context) { | ||
return Result.Ok(wrapValue(response.value._value, newContext)); | ||
} else if (response.ok) { | ||
return Result.Err(new SqOtherError("Context creation for table failed.")); | ||
OAGr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} else { | ||
return response; | ||
} | ||
} | ||
|
||
get names(): string[] { | ||
return this._value.inputs.map((x) => x.name || ""); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this file is messier than it could be. That said, I think cleaning it up could be a fair bit of work. Maybe a good fit for a few hooks or something.