Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ function App() {
uniqMeasuredConcentrations.length >= 3
);
}, [hasAValueAboveKd, hasAValueBelowKd, uniqMeasuredConcentrations]);

const handleNewInputConcentration = useCallback(
(name: string, value: number) => {
if (value === 0) {
Expand All @@ -292,10 +293,10 @@ function App() {
name as keyof typeof LiveSimulationData.AVAILABLE_AGENTS;
const agentId = LiveSimulationData.AVAILABLE_AGENTS[agentName].id;
clientSimulator.changeConcentration(agentId, value);
simulariumController.gotoTime(time + 1);
simulariumController.gotoTime(1); // the number isn't used, but it triggers the update
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an ideal world, what would the API for this look like? Something like update() or advance() for simulations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way I was doing it before may be more ideal (ie "give me the next time step"). the issue was because of the async nature of the connection between the app and the viewer, sometimes what the app thought was the next frame, the viewer thought was the current frame, so it wouldn't update.

It might be nice to be able to pass something like "++1" to gotoTime meaning "whatever you think is the next frame, give it to me".

resetCurrentRunAnalysisState();
},
[clientSimulator, time, simulariumController]
[clientSimulator, simulariumController]
);
const totalReset = useCallback(() => {
setLiveConcentration({
Expand Down Expand Up @@ -348,6 +349,7 @@ function App() {
usePageNumber(
page,
(page) =>
currentModule === 1 &&
page === PROMPT_TO_ADJUST_B &&
isPlaying &&
recordedInputConcentration.length > 0 &&
Expand Down Expand Up @@ -417,12 +419,18 @@ function App() {

useEffect(() => {
const { section } = content[currentModule][page];
if (section === Section.Experiment) {
if (
section === Section.Experiment &&
timeFactor !== LiveSimulationData.DEFAULT_TIME_FACTOR
) {
setTimeFactor(LiveSimulationData.DEFAULT_TIME_FACTOR);
} else if (section === Section.Introduction) {
} else if (
section === Section.Introduction &&
timeFactor !== LiveSimulationData.INITIAL_TIME_FACTOR
) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these changes aren't directly connected, but I did realize i was doing more updates to state than I needed to, which became more of an issue when i'm pushing the rendering

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could alternatively pull the const { section } = content[currentModule][page] out of the useEffect and have the useEffect only trigger on changes to it?

setTimeFactor(LiveSimulationData.INITIAL_TIME_FACTOR);
}
}, [currentModule, page]);
}, [currentModule, page, timeFactor]);

const addProductionTrace = (previousConcentration: number) => {
const traces = productOverTimeTraces;
Expand All @@ -447,7 +455,6 @@ function App() {
const handleStartExperiment = () => {
simulariumController.pause();
totalReset();
setTimeFactor(LiveSimulationData.DEFAULT_TIME_FACTOR);
setPage(page + 1);
};

Expand Down
15 changes: 7 additions & 8 deletions src/components/AdminUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import React, { useContext, useEffect } from "react";
import Slider from "./shared/Slider";
import { BG_DARK, LIGHT_GREY } from "../constants/colors";
import { SimulariumContext } from "../simulation/context";
import { SliderSingleProps } from "antd";
import { InputNumber, SliderSingleProps } from "antd";
import { zStacking } from "../constants/z-stacking";
import { Module } from "../types";

interface AdminUIProps {
totalPages: number;
Expand Down Expand Up @@ -72,18 +73,15 @@ const AdminUI: React.FC<AdminUIProps> = ({
name="time factor (ns)"
/>
<h4>Module number</h4>
<Slider
<InputNumber
min={1}
max={totalNumberOfModules}
step={1}
initialValue={module}
onChange={(_, value): void => {
setModule(value);
value={Number(module)}
onChange={(value): void => {
setModule(value as Module);
}}
overrideValue={module}
marks={moduleMarks}
disabled={false}
name="time factor (ns)"
/>
</div>
<div style={{ padding: 12 }}>
Expand All @@ -94,6 +92,7 @@ const AdminUI: React.FC<AdminUIProps> = ({
max={100}
step={1}
initialValue={timeFactor}
overrideValue={timeFactor}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does overrideValue here do? Just keeping it synced with timeFactor until a user interacts with it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it's basically "value". it's because Slider itself is a wrapper on the Antd component that already stores it's own value on update

onChange={(_, value) => {
setTimeFactor(value);
}}
Expand Down
3 changes: 2 additions & 1 deletion src/components/ScaleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ interface ScaleBarProps {
const ScaleBar: React.FC<ScaleBarProps> = ({ productColor }) => {
const { maxConcentration } = useContext(SimulariumContext);
const labelArray = [];
for (let i = maxConcentration; i >= 0; i = i - 2) {
const interval = maxConcentration / 5;
for (let i = maxConcentration; i >= 0; i = i - interval) {
labelArray.push(i);
}
return (
Expand Down
15 changes: 9 additions & 6 deletions src/components/concentration-display/ConcentrationSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,21 @@ const ConcentrationSlider: React.FC<SliderProps> = ({
}) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
const disabledNumbers = [0];

const stepSize = useRef(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this just be, const stepSize = (max - min) / 5 instead of a ref? It looks like the useMemo updates whenever max and min do anyways.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to this; alternately, could become its own useMemo, or be returned from the useMemo for marks below in a 2-tuple or object

const marks = useMemo(() => {
stepSize.current = (max - min) / 5;
const marks: SliderSingleProps["marks"] = {};
for (let index = min; index <= max; index = index + 2) {
for (let index = min; index <= max; index = index + stepSize.current) {
marks[index] = {
label: (
<Mark
index={index}
disabledNumbers={disabledNumbers}
onMouseUp={() =>
onChangeComplete && onChangeComplete(name, index)
}
onMouseUp={() => {
if (onChangeComplete) {
onChangeComplete(name, index);
}
}}
/>
),
};
Expand All @@ -87,7 +90,7 @@ const ConcentrationSlider: React.FC<SliderProps> = ({
name={name}
min={min}
max={max}
step={2}
step={stepSize.current}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the second module has a much bigger range, so we're showing bigger jumps between markers

onChange={onChange}
onChangeComplete={onChangeComplete}
marks={marks}
Expand Down
9 changes: 5 additions & 4 deletions src/components/plots/EquilibriumPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const EquilibriumPlot: React.FC<PlotProps> = ({
getAgentColor,
adjustableAgentName,
} = useContext(SimulariumContext);

const xMax = Math.max(...x);
const xAxisMax = Math.max(kd * 2, xMax * 1.1);
const hintOverlay = (
<div
style={{
Expand All @@ -63,7 +64,7 @@ const EquilibriumPlot: React.FC<PlotProps> = ({
};

const horizontalLine = {
x: [0, kd * 2],
x: [0, xAxisMax],
y: [5, 5],
mode: "lines",
name: "50% bound",
Expand All @@ -74,7 +75,7 @@ const EquilibriumPlot: React.FC<PlotProps> = ({
line: lineOptions,
};
const horizontalLineMax = {
x: [0, kd * 2],
x: [0, xAxisMax],
y: [10, 10],
mode: "lines",
name: "Initial [A]",
Expand Down Expand Up @@ -108,7 +109,7 @@ const EquilibriumPlot: React.FC<PlotProps> = ({
height: Math.max(130, height),
xaxis: {
...AXIS_SETTINGS,
range: [0, kd * 2],
range: [0, xAxisMax],
title: `[${adjustableAgentName}] ${MICRO}M`,
titlefont: {
...AXIS_SETTINGS.titlefont,
Expand Down
50 changes: 38 additions & 12 deletions src/components/quiz-questions/KdQuestion.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useContext, useEffect, useState } from "react";
import { valueType } from "antd/es/statistic/utils";
import { Flex } from "antd";

Expand All @@ -8,6 +8,7 @@ import InputNumber from "../shared/InputNumber";
import { FormState } from "./types";
import styles from "./popup.module.css";
import { MICRO } from "../../constants";
import { SimulariumContext } from "../../simulation/context";

interface KdQuestionProps {
kd: number;
Expand All @@ -18,6 +19,41 @@ const KdQuestion: React.FC<KdQuestionProps> = ({ kd, canAnswer }) => {
const [selectedAnswer, setSelectedAnswer] = useState<number | null>(null);
const [formState, setFormState] = useState(FormState.Clear);

const { module } = useContext(SimulariumContext);

useEffect(() => {
setSelectedAnswer(null);
setFormState(FormState.Clear);
}, [module]);

const getSuccessMessage = (selectedAnswer: number) => {
if (selectedAnswer < 5) {
return (
Comment on lines +29 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: function could live outside component body

<>
{selectedAnswer} {MICRO}M is considered a{" "}
<strong>
low K<sub>d</sub>
</strong>
, which means A and B have a <strong>high affinity</strong>{" "}
for one another because it takes a low amount of B to create
the complex.
</>
);
} else {
return (
<>
{selectedAnswer} {MICRO}M is considered a{" "}
<strong>
high K<sub>d</sub>
</strong>
, which means A and C have a <strong>low affinity</strong>{" "}
for one another because it takes a lot of C to create the
complex.
</>
);
}
};

const handleAnswerSelection = (answer: valueType | null) => {
setSelectedAnswer(Number(answer));

Expand Down Expand Up @@ -77,17 +113,7 @@ const KdQuestion: React.FC<KdQuestionProps> = ({ kd, canAnswer }) => {
title="What is the binding affinity?"
formContent={formContent}
onSubmit={handleSubmit}
successMessage={
<>
{selectedAnswer} {MICRO}M is considered a{" "}
<strong>
low K<sub>d</sub>
</strong>
, which means A and B have a{" "}
<strong>high affinity</strong> for one another because
it takes a low amount of B to create the complex.
</>
}
successMessage={getSuccessMessage(selectedAnswer!)}
failureMessage="Visit the “Learn how to derive Kd” button above, then use the Equilibrium concentration plot to answer."
formState={formState}
id="Kd Value"
Expand Down
10 changes: 5 additions & 5 deletions src/simulation/BindingInstance.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Circle, Vector } from "detect-collisions";
import { Circle } from "detect-collisions";
import { random } from "lodash";
import { Vector } from "sat";

import LiveSimulationData from "./LiveSimulationData";

class BindingInstance extends Circle {
Expand Down Expand Up @@ -48,7 +50,6 @@ class BindingInstance extends Circle {
}

private releaseFromParent() {
this.isTrigger = false;
this.bound = false;
this.parent = null;
}
Expand All @@ -57,7 +58,6 @@ class BindingInstance extends Circle {
parent: BindingInstance,
overlapV: Vector
): BindingInstance {
this.isTrigger = true;
this.parent = parent;
// adjust the ligand to the exact edge of the parent
this.moveInstance(-overlapV.x, -overlapV.y);
Expand All @@ -68,6 +68,8 @@ class BindingInstance extends Circle {
/** PUBLIC METHODS BELOW */

public moveInstance(x: number, y: number) {
// if we're trying to move a bound instance, move the parent instead
// and then we'll resolve the child position
if (this.parent) {
this.parent.moveInstance(x, y);
} else {
Expand Down Expand Up @@ -161,7 +163,6 @@ class BindingInstance extends Circle {
return false;
}
this.child = null;
this.isTrigger = false;
ligand.releaseFromParent();
// QUESTION: should the ligand be moved to a random position?
return true;
Expand All @@ -180,7 +181,6 @@ class BindingInstance extends Circle {
if (!willBind) {
return false;
}
this.isTrigger = true;
this.child = ligand.bindToParent(this, overlapV);
return true;
}
Expand Down
Loading