// Participant enters continuation probability class BasicNodeChoice extends React.Component { constructor(props) { super(props); this.state = { choiceOption: "", // Save whether path dependent or randomized choice } } radioChoice = (e) => { this.setState({ choiceOption: e.target.value, }) this.submitForm(e); } submitForm = (e) => { e.preventDefault(); // Define variables to be submitted const choiceOption = e.target.value let probContinue if (choiceOption == "continue") { probContinue = 100 } else if (choiceOption == "stop") { probContinue = 0 } this.props.submitMe(probContinue) } render() { return ( <>
My instruction at the selected ball


If this ball is reached, please
Stop Continue
risk-taking.
) } } class PathNodeChoice extends React.Component { // Decision panel for path-dependent stopping decision constructor(props) { super(props), this.reachableNodes = this.reachableNodes.bind(this); this.myRef = React.createRef(); } componentDidMount() { // Scroll component into view if needed //this.myRef.current.scrollIntoViewIfNeeded(false) // Check radio buttons if they exists const allPaths = js_vars.allPaths[this.props.currentNode] for( let i=0; i < allPaths.length; i++){ let currentPath = allPaths[i][0] if (currentPath in this.props.pdStop){ let value = this.props.pdStop[currentPath] document.getElementById(currentPath + "_" + value).checked = true; } } } reachableNodes() { // Function gives all nodes reachable to current node const allPaths = js_vars.allPaths[this.props.currentNode] let layerDict = {} for (let pathNum=0; pathNum < allPaths.length; pathNum++ ){ let thisPath = allPaths[pathNum][1] for (let layer=0; layer < thisPath.length; layer++) { let layArray = [] let elem = thisPath[layer] // Test if array exists already if (layerDict[layer]) { layArray = layerDict[layer] if (layArray.indexOf(elem) === -1) { layArray.push(elem); } } else { layArray = [elem] } layerDict[layer] = layArray } } return layerDict } render() { let outputArray = [] // push output into this array const allPaths = js_vars.allPaths[this.props.currentNode] if (allPaths.length == 1) { // Only one path outputArray.push( <>
If this ball is reached, please
Continue Stop
risk-taking.
) } else { outputArray.push( // Push header to array <>
If this ball is reached according to the respective path, please
Path
Continue
Stop
) for(let row=0; row < allPaths.length; row++){ let thisPath = allPaths[row] outputArray.push( <>
{/* Column showing path */}
{/* Column with continue choice button */}
{/* Column with stop choice button */}

) } // end of for loop outputArray.push(
risk-taking.
) } return <>
{this.props.header}
{outputArray}
} } class PathVisualization extends React.Component { // Component draws paths for path-dependent stopping decisions constructor(props) { super(props), this.buttonStyle = this.buttonStyle.bind(this); this.drawLines = this.drawLines.bind(this); } componentDidMount(){ // Draw lines between paths const path = this.props.pathsToPlot for (let col=1; col < path.length; col++) { // Must loop over all nodes to get rows for alignment below let prevCol = col - 1 let prevId = path[prevCol] let currentId = path[col] const previousId = "button_" + this.props.pathNum + "_" + prevId const thisId = "button_" + this.props.pathNum + "_" + currentId if (currentId == this.props.currentNode) { document.getElementById(thisId).style.background = "darkblue" } this.drawLines(previousId, thisId) } } buttonStyle(thisCol, maxLayer, thisRow, maxRow){ let style = { background: "white", width: "20px", height: "20px", borderRadius: "50%", border: "0.5px solid black", display: "block", position: 'absolute', } const colSpacing = 100 / (maxLayer + 1) // Percentage distance between nodes const rowSpacing = 70 / maxRow // Due to the height of the balls, we do not stretch to 100% height let left = (thisCol+1) * colSpacing let top = (thisRow+1) * rowSpacing style.left = left.toString() + '%'; style.top = top.toString() + '%'; return style } drawTriangle() { let buttonArray = [] // Push buttons here const path = this.props.pathsToPlot const layer = path.length // Column in ball triangle // Determine rows of nodes for plotting const reachableNodes = this.props.reachableNodes let plotDict = {0:[0]} let minRow = 0 let maxRow = 0 for (let tmpNum = 1; tmpNum < layer; tmpNum++) { let prevNum = tmpNum-1 let previousLayer = reachableNodes[prevNum] let prevColumns = plotDict[prevNum] let currentLayer = reachableNodes[tmpNum] plotDict[tmpNum] = [] for (let layerElem = 0; layerElem < currentLayer.length; layerElem++ ){ let elem = currentLayer[layerElem] let up = previousLayer.indexOf(elem - tmpNum) let down = previousLayer.indexOf(elem - tmpNum - 1) if (up != -1){ let rowIndex = prevColumns[up] - 1 if (plotDict[tmpNum].indexOf(rowIndex) == -1) { plotDict[tmpNum].push(rowIndex) } minRow = Math.min(rowIndex, minRow) maxRow = Math.max(rowIndex, maxRow) } else if (down != -1) { let rowIndex = prevColumns[down] + 1 if (plotDict[tmpNum].indexOf(rowIndex) == -1) { plotDict[tmpNum].push(rowIndex) } minRow = Math.min(rowIndex, minRow) maxRow = Math.max(rowIndex, maxRow) } } } const numRows = maxRow - minRow + 1 for (let col=0; col < layer; col++){ let numBalls = reachableNodes[col].length // Number of balls per column for (let ballInCol=0; ballInCol < numBalls; ballInCol ++){ let currentRow = plotDict[col][ballInCol] - minRow let nodeId = reachableNodes[col][ballInCol] buttonArray.push( ) } } return buttonArray } drawLines(id1, id2) { let start = document.getElementById(id1) let end = document.getElementById(id2) // add blue border around elems start.style.border = "2px solid #2E8BC0" end.style.border = "2px solid #2E8BC0" let x_start = parseInt(window.getComputedStyle(start).left, 10)+21 let y_start = parseInt(window.getComputedStyle(start).top, 10) let x_end = parseInt(window.getComputedStyle(end).left, 10) let y_end = parseInt(window.getComputedStyle(end).top, 10) let color = "black" if (y_start > y_end) { //Up move y_start = y_start + 4 y_end = y_end + 10 color = "#0f5132" } else { // Down mode y_start = y_start + 10 y_end = y_end + 4 //y_start = parseInt(window.getComputedStyle(start).top, 10) + 12.5 color = "#842029" } var newLine = document.createElementNS('http://www.w3.org/2000/svg','line'); //newLine.setAttribute('id',"line_"+move); newLine.setAttribute('x1',x_start ); newLine.setAttribute('y1',y_start); newLine.setAttribute('x2',x_end); newLine.setAttribute('y2',y_end); newLine.setAttribute("stroke", color) newLine.setAttribute("stroke-width", 2) $("#lineSVG_"+this.props.pathNum).append(newLine); } render () { return ( <> {this.drawTriangle()} ) } } class RandNodeChoice extends React.Component { constructor(props) { super(props), this.state={ currentValue: "", }, this.rangeInput = this.rangeInput.bind(this); this.numbersOnly = this.numbersOnly.bind(this); this.deleteTimer = this.deleteTimer.bind(this); this.componentDidMount = this.componentDidMount.bind(this); } timer; // Timer for rounding submitTimer; // Timer for automatic submission componentDidMount() { if (this.props.isClicked) { document.getElementById("amountInput").value = 100 - this.props.continuation document.getElementById("amountRange").value = 100 - this.props.continuation this.setState({currentValue: 100 - this.props.continuation}) //document.getElementById("amountRange").value = 100 - this.props.continuation } } componentWillUnmount() { if (this.submitTimer){ clearTimeout(this.submitTimer); } } rangeInput(inputVal) { document.getElementById("amountInput").value = inputVal this.setState({currentValue: inputVal}) if (this.submitTimer){ clearTimeout(this.submitTimer); } if (! this.props.walkingTask) { this.submitTimer = setTimeout( () => {this.props.submitMe(100 - this.state.currentValue)}, 5000 ) } } numbersOnly(inputVal) { let regex = new RegExp("^[0-9][0-9]?$|^100$") if (regex.test(inputVal) || inputVal.length == 0) { let numValue = parseInt(inputVal) // Need delay before rounding if (this.timer) { clearTimeout(this.timer); } this.timer = setTimeout( () => { let usedValue = "" if ( inputVal.length > 0) { usedValue = Math.round(numValue/10) * 10 } document.getElementById("amountInput").value = usedValue document.getElementById("amountRange").value = usedValue this.setState({currentValue: usedValue}) },500) if (! this.props.walkingTask) { this.submitTimer = setTimeout( () => {this.props.submitMe(100 - this.state.currentValue)}, 5000 ) } } else { document.getElementById("amountInput").value= ((this.state.currentValue !== "") ? this.state.currentValue : "") document.getElementById("amountRange").value= ((this.state.currentValue !== "") ? this.state.currentValue : "") } } deleteTimer(){ if (this.submitTimer){ clearTimeout(this.submitTimer); } } render () { return (
{this.props.header}
At this ball, please stop risk-taking with probability
this.rangeInput(e.target.value)} // using onclick allows for direct click on default /> this.numbersOnly(e.target.value)} style={{marginLeft:"10%"}}/>%.
{!this.props.walkingTask ?
: <> }
) } }