An easy-to-use platform for EEG experimentation in the classroom
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fixed all of the typescript errors while listening to mouth noises

+325 -282
+71 -69
app/components/AnalyzeComponent.tsx
··· 9 9 Button, 10 10 Checkbox, 11 11 Sidebar, 12 + DropdownProps, 12 13 } from 'semantic-ui-react'; 13 - import { isNil } from 'lodash'; 14 + import { isNil, isArray, isString } from 'lodash'; 14 15 import Plot from 'react-plotly.js'; 15 16 import styles from './styles/common.css'; 16 17 import { ··· 50 51 51 52 interface Props { 52 53 title: string; 53 - type: EXPERIMENTS | null | undefined; 54 + type: EXPERIMENTS; 54 55 deviceType: DEVICES; 55 56 isEEGEnabled: boolean; 56 - kernel: Kernel | null | undefined; 57 + kernel: Kernel; 57 58 kernelStatus: KERNEL_STATUS; 58 - mainChannel: any | null | undefined; 59 - epochsInfo: 60 - | Array<{ 61 - [key: string]: number | string; 62 - }> 63 - | null 64 - | undefined; 65 - channelInfo: Array<string> | null | undefined; 66 - psdPlot: 67 - | { 68 - [key: string]: string; 69 - } 70 - | null 71 - | undefined; 72 - topoPlot: 73 - | { 74 - [key: string]: string; 75 - } 76 - | null 77 - | undefined; 78 - erpPlot: 79 - | { 80 - [key: string]: string; 81 - } 82 - | null 83 - | undefined; 59 + mainChannel: any; 60 + epochsInfo: Array<{ 61 + [key: string]: number | string; 62 + }>; 63 + 64 + channelInfo: Array<string>; 65 + psdPlot: { 66 + [key: string]: string; 67 + }; 68 + 69 + topoPlot: { 70 + [key: string]: string; 71 + }; 72 + 73 + erpPlot: { 74 + [key: string]: string; 75 + }; 76 + 84 77 JupyterActions: typeof JupyterActions; 85 78 } 86 79 87 80 interface State { 88 81 activeStep: string; 89 82 selectedChannel: string; 90 - eegFilePaths: Array< 91 - | { 92 - key: string; 93 - text: string; 94 - value: { name: string; dir: string }; 95 - } 96 - | null 97 - | undefined 98 - >; 99 - behaviorFilePaths: Array< 100 - | { 101 - key: string; 102 - text: string; 103 - value: string; 104 - } 105 - | null 106 - | undefined 107 - >; 108 - selectedFilePaths: Array<string | null | undefined>; 109 - selectedBehaviorFilePaths: Array<string | null | undefined>; 110 - selectedSubjects: Array<string | null | undefined>; 83 + eegFilePaths: Array<{ 84 + key: string; 85 + text: string; 86 + value: { name: string; dir: string }; 87 + }>; 88 + behaviorFilePaths: Array<{ 89 + key: string; 90 + text: string; 91 + value: string; 92 + }>; 93 + selectedFilePaths: Array<string>; 94 + selectedBehaviorFilePaths: Array<string>; 95 + selectedSubjects: Array<string>; 111 96 selectedDependentVariable: string; 112 97 removeOutliers: boolean; 113 98 showDataPoints: boolean; ··· 118 103 dataToPlot: number[]; 119 104 layout: Record<string, any>; 120 105 helpMode: string; 121 - dependentVariables: Array< 122 - | { 123 - key: string; 124 - text: string; 125 - value: string; 126 - } 127 - | null 128 - | undefined 129 - >; 106 + dependentVariables: Array<{ 107 + key: string; 108 + text: string; 109 + value: string; 110 + }>; 130 111 } 131 112 // TODO: Add a channel callback from reading epochs so this screen can be aware of which channels are 132 113 // available in dataset 114 + // TODO: Refactor component to DRY up handler functions 133 115 export default class Analyze extends Component<Props, State> { 134 116 constructor(props: Props) { 135 117 super(props); ··· 212 194 return subjects.reduce((acc, curr) => `${acc}-${curr}`); 213 195 }; 214 196 215 - handleDatasetChange(event: Record<string, any>, data: Record<string, any>) { 216 - this.setState({ 217 - selectedFilePaths: data.value, 218 - selectedSubjects: getSubjectNamesFromFiles(data.value), 219 - }); 220 - this.props.JupyterActions.LoadCleanedEpochs(data.value); 197 + handleDatasetChange(event: Record<string, any>, data: DropdownProps) { 198 + if (isStringArray(data.value)) { 199 + this.setState({ 200 + selectedFilePaths: data.value, 201 + selectedSubjects: getSubjectNamesFromFiles(data.value), 202 + }); 203 + this.props.JupyterActions.LoadCleanedEpochs(data.value); 204 + } 221 205 } 222 206 223 207 handleBehaviorDatasetChange( ··· 279 263 } 280 264 281 265 handleRemoveOutliers(event: Record<string, any>, data: Record<string, any>) { 282 - const { dataToPlot, layout } = aggregateDataForPlot( 266 + const aggregatedData = aggregateDataForPlot( 283 267 readBehaviorData(this.state.selectedBehaviorFilePaths), 284 268 this.state.selectedDependentVariable, 285 269 !this.state.removeOutliers, 286 270 this.state.showDataPoints, 287 271 this.state.displayMode 288 272 ); 273 + if (!aggregatedData) { 274 + return; 275 + } 276 + const { dataToPlot, layout } = aggregatedData; 289 277 this.setState({ 290 278 removeOutliers: !this.state.removeOutliers, 291 279 dataToPlot, ··· 295 283 } 296 284 297 285 handleDataPoints(event: Record<string, any>, data: Record<string, any>) { 298 - const { dataToPlot, layout } = aggregateDataForPlot( 286 + const aggregatedData = aggregateDataForPlot( 299 287 readBehaviorData(this.state.selectedBehaviorFilePaths), 300 288 this.state.selectedDependentVariable, 301 289 this.state.removeOutliers, 302 290 !this.state.showDataPoints, 303 291 this.state.displayMode 304 292 ); 293 + if (!aggregatedData) { 294 + return; 295 + } 296 + const { dataToPlot, layout } = aggregatedData; 305 297 this.setState({ 306 298 showDataPoints: !this.state.showDataPoints, 307 299 dataToPlot, ··· 314 306 this.state.selectedBehaviorFilePaths && 315 307 this.state.selectedBehaviorFilePaths.length > 0 316 308 ) { 317 - const { dataToPlot, layout } = aggregateDataForPlot( 309 + const aggregatedData = aggregateDataForPlot( 318 310 readBehaviorData(this.state.selectedBehaviorFilePaths), 319 311 this.state.selectedDependentVariable, 320 312 this.state.removeOutliers, 321 313 this.state.showDataPoints, 322 314 displayMode 323 315 ); 316 + if (!aggregatedData) { 317 + return; 318 + } 319 + const { dataToPlot, layout } = aggregatedData; 324 320 this.setState({ 325 321 dataToPlot, 326 322 layout, ··· 475 471 selection 476 472 closeOnChange 477 473 value={this.state.selectedFilePaths} 478 - options={this.state.eegFilePaths} 474 + options={this.state.eegFilePaths.map( 475 + (eegFilePath) => eegFilePath.value 476 + )} 479 477 onChange={this.handleDatasetChange} 480 478 /> 481 479 {this.renderEpochLabels()} ··· 659 657 ); 660 658 } 661 659 } 660 + 661 + function isStringArray(data: any): data is string[] { 662 + return isArray(data.value) && data.value.every(isString); 663 + }
+1 -1
app/components/CleanComponent/index.tsx
··· 248 248 <Button 249 249 primary 250 250 disabled={isNil(this.props.epochsInfo)} 251 - onClick={this.props.JupyterActions.CleanEpochs()} 251 + onClick={this.props.JupyterActions.CleanEpochs} 252 252 > 253 253 Clean Data 254 254 </Button>
+3 -3
app/components/CollectComponent/ConnectModal.tsx
··· 10 10 SCREENS, 11 11 } from '../../constants/constants'; 12 12 import styles from '../styles/collect.css'; 13 - import { SignalQualityData, PipesEpoch } from '../../constants/interfaces'; 13 + import { SignalQualityData } from '../../constants/interfaces'; 14 14 import { DeviceActions } from '../../actions'; 15 15 16 16 interface Props { ··· 18 18 open: boolean; 19 19 onClose: () => void; 20 20 connectedDevice: Record<string, any>; 21 - signalQualityObservable?: Observable<PipesEpoch>; 21 + signalQualityObservable?: Observable<SignalQualityData>; 22 22 deviceType: DEVICES; 23 23 deviceAvailability: DEVICE_AVAILABILITY; 24 24 connectionStatus: CONNECTION_STATUS; ··· 27 27 } 28 28 29 29 interface State { 30 - selectedDevice: any | null | undefined; 30 + selectedDevice: any; 31 31 instructionProgress: INSTRUCTION_PROGRESS; 32 32 } 33 33
+2
app/components/CollectComponent/index.tsx
··· 1 1 import { Observable } from 'rxjs'; 2 2 import React, { Component } from 'react'; 3 3 import { isNil } from 'lodash'; 4 + import { History } from 'history'; 4 5 import { 5 6 EXPERIMENTS, 6 7 DEVICES, ··· 11 12 Trial, 12 13 ExperimentParameters, 13 14 SignalQualityData, 15 + PipesEpoch, 14 16 } from '../../constants/interfaces'; 15 17 import PreTestComponent from './PreTestComponent'; 16 18 import ConnectModal from './ConnectModal';
+31 -16
app/components/DesignComponent/CustomDesignComponent.tsx
··· 9 9 Image, 10 10 Table, 11 11 } from 'semantic-ui-react'; 12 - import { isNil } from 'lodash'; 12 + import { isNil, isString } from 'lodash'; 13 13 import { History } from 'history'; 14 14 15 15 import styles from '../styles/common.css'; ··· 158 158 label={FIELDS.QUESTION} 159 159 value={this.state.description.question} 160 160 placeholder="Explain your research question here." 161 - onChange={(event, data) => 161 + onChange={(event, data) => { 162 + if (!isString(data.value)) { 163 + return; 164 + } 162 165 this.setState({ 163 166 description: { 164 167 ...this.state.description, 165 168 question: data.value, 166 169 }, 167 170 saved: false, 168 - }) 169 - } 171 + }); 172 + }} 170 173 /> 171 174 </Form> 172 175 </Grid.Column> ··· 185 188 label={FIELDS.HYPOTHESIS} 186 189 value={this.state.description.hypothesis} 187 190 placeholder="Describe your hypothesis here." 188 - onChange={(event, data) => 191 + onChange={(event, data) => { 192 + if (!isString(data.value)) { 193 + return; 194 + } 189 195 this.setState({ 190 196 description: { 191 197 ...this.state.description, 192 198 hypothesis: data.value, 193 199 }, 194 200 saved: false, 195 - }) 196 - } 201 + }); 202 + }} 197 203 /> 198 204 </Form> 199 205 </Grid.Column> ··· 212 218 label={FIELDS.METHODS} 213 219 value={this.state.description.methods} 214 220 placeholder="Explain how you will design your experiment to answer the question here." 215 - onChange={(event, data) => 221 + onChange={(event, data) => { 222 + if (!isString(data.value)) { 223 + return; 224 + } 216 225 this.setState({ 217 226 description: { 218 227 ...this.state.description, 219 228 methods: data.value, 220 229 }, 221 230 saved: false, 222 - }) 223 - } 231 + }); 232 + }} 224 233 /> 225 234 </Form> 226 235 </Grid.Column> ··· 581 590 autoHeight 582 591 value={this.state.params.intro} 583 592 placeholder="e.g., You will view a series of faces and houses. Press 1 when a face appears and 9 for a house. Press the the space bar on your keyboard to start doing the practice trials. If you want to skip the practice trials and go directly to the task, press the 'q' button on your keyboard." 584 - onChange={(event, data) => 593 + onChange={(event, data) => { 594 + if (!isString(data.value)) { 595 + return; 596 + } 585 597 this.setState({ 586 598 params: { ...this.state.params, intro: data.value }, 587 599 saved: false, 588 - }) 589 - } 600 + }); 601 + }} 590 602 /> 591 603 </Form> 592 604 </Segment> ··· 608 620 autoHeight 609 621 value={this.state.params.taskHelp} 610 622 placeholder="e.g., Press 1 for a face and 9 for a house" 611 - onChange={(event, data) => 623 + onChange={(event, data) => { 624 + if (!isString(data.value)) { 625 + return; 626 + } 612 627 this.setState({ 613 628 params: { ...this.state.params, taskHelp: data.value }, 614 629 saved: false, 615 - }) 616 - } 630 + }); 631 + }} 617 632 /> 618 633 </Form> 619 634 </Segment>
+3 -3
app/components/DesignComponent/ParamSlider.tsx
··· 1 1 import { Segment } from 'semantic-ui-react'; 2 - import React, { PureComponent } from 'react'; 2 + import React from 'react'; 3 3 import Slider from 'rc-slider'; 4 4 import styles from '../styles/common.css'; 5 5 ··· 26 26 <Slider 27 27 dots 28 28 marks={marks} 29 - min={Math.min(...Object.keys(marks))} 30 - max={Math.max(...Object.keys(marks))} 29 + min={Math.min(...Object.keys(marks).map(parseInt))} 30 + max={Math.max(...Object.keys(marks).map(parseInt))} 31 31 value={value / parseInt(msConversion, 10)} 32 32 onChange={(val) => onChange(val * parseInt(msConversion, 10))} 33 33 defaultValue={1}
+21 -15
app/components/DesignComponent/StimuliRow.tsx
··· 2 2 3 3 import React from 'react'; 4 4 import { Segment, Form, Button, Table, Dropdown } from 'semantic-ui-react'; 5 + import { isString } from 'lodash'; 5 6 import styles from '../styles/common.css'; 6 7 7 8 interface Props { ··· 21 22 value: i.toString(), 22 23 })); 23 24 24 - export const StimuliRow: React.FC<Props> = (props) => { 25 + export const StimuliRow: React.FC<Props> = ({ 26 + name, 27 + num, 28 + response, 29 + dir, 30 + condition, 31 + phase, 32 + onChange, 33 + onDelete, 34 + }) => { 25 35 return ( 26 36 <Table.Row className={styles.trialsRow}> 27 37 <Table.Cell className={styles.conditionsNameRow}> 28 - <div style={{ alignSelf: 'center' }}>{props.num + 1}.</div> 29 - <div>{props.name}</div> 38 + <div style={{ alignSelf: 'center' }}>{num + 1}.</div> 39 + <div>{name}</div> 30 40 </Table.Cell> 31 41 32 42 <Table.Cell className={styles.experimentRowName}> 33 - <div>{props.condition}</div> 43 + <div>{condition}</div> 34 44 </Table.Cell> 35 45 36 46 <Table.Cell className={styles.experimentRowName}> 37 47 <Form.Select 38 48 fluid 39 49 selection 40 - value={props.response} 50 + value={response} 41 51 onChange={(event, data) => 42 - props.onChange(props.num, 'response', data.value) 52 + onChange(num, 'response', isString(data.value) ? data.value : '') 43 53 } 44 54 placeholder="Response" 45 55 options={RESPONSE_OPTIONS} ··· 51 61 <div 52 62 className={styles.trialsTrialTypeRowSelector} 53 63 style={{ 54 - backgroundColor: props.phase === 'main' ? '#1AC4EF' : '#EB1B66', 64 + backgroundColor: phase === 'main' ? '#1AC4EF' : '#EB1B66', 55 65 }} 56 66 > 57 - {props.phase === 'main' ? 'Experimental' : 'Practice'} 67 + {phase === 'main' ? 'Experimental' : 'Practice'} 58 68 </div> 59 69 <Dropdown 60 70 fluid ··· 65 75 }} 66 76 > 67 77 <Dropdown.Menu> 68 - <Dropdown.Item 69 - onClick={() => props.onChange(props.num, 'phase', 'main')} 70 - > 78 + <Dropdown.Item onClick={() => onChange(num, 'phase', 'main')}> 71 79 <div>Experimental</div> 72 80 </Dropdown.Item> 73 - <Dropdown.Item 74 - onClick={() => props.onChange(props.num, 'phase', 'practice')} 75 - > 81 + <Dropdown.Item onClick={() => onChange(num, 'phase', 'practice')}> 76 82 <div>Practice</div> 77 83 </Dropdown.Item> 78 84 </Dropdown.Menu> ··· 82 88 <Button 83 89 secondary 84 90 onClick={() => { 85 - props.onDelete(props.num); 91 + onDelete(num); 86 92 }} 87 93 > 88 94 Delete
+2 -2
app/components/EEGExplorationComponent.tsx
··· 21 21 import ConnectModal from './CollectComponent/ConnectModal'; 22 22 import styles from './styles/common.css'; 23 23 import { DeviceActions } from '../actions'; 24 - import { SignalQualityData, PipesEpoch } from '../constants/interfaces'; 24 + import { SignalQualityData } from '../constants/interfaces'; 25 25 26 26 interface Props { 27 27 history: History; 28 28 connectedDevice: Record<string, any>; 29 - signalQualityObservable: Observable<PipesEpoch>; 29 + signalQualityObservable: Observable<SignalQualityData>; 30 30 deviceType: DEVICES; 31 31 deviceAvailability: DEVICE_AVAILABILITY; 32 32 connectionStatus: CONNECTION_STATUS;
+2 -2
app/components/SignalQualityIndicatorComponent.tsx
··· 4 4 import * as d3 from 'd3'; 5 5 import { Observable, Subscription } from 'rxjs'; 6 6 import SignalQualityIndicatorSVG from './svgs/SignalQualityIndicatorSVG'; 7 - import { PipesEpoch } from '../constants/interfaces'; 7 + import { PipesEpoch, SignalQualityData } from '../constants/interfaces'; 8 8 9 9 interface Props { 10 - signalQualityObservable: Observable<PipesEpoch>; 10 + signalQualityObservable: Observable<SignalQualityData>; 11 11 plottingInterval: number; 12 12 } 13 13
+2 -2
app/components/ViewerComponent.tsx
··· 8 8 DEVICES, 9 9 VIEWER_DEFAULTS, 10 10 } from '../constants/constants'; 11 - import { PipesEpoch } from '../constants/interfaces'; 11 + import { PipesEpoch, SignalQualityData } from '../constants/interfaces'; 12 12 13 13 const Mousetrap = require('mousetrap'); 14 14 15 15 interface Props { 16 - signalQualityObservable: Observable<PipesEpoch>; 16 + signalQualityObservable: Observable<SignalQualityData>; 17 17 deviceType: DEVICES; 18 18 plottingInterval: number; 19 19 }
-1
app/epics/index.ts
··· 3 3 import device from './deviceEpics'; 4 4 import experiment from './experimentEpics'; 5 5 6 - // TODO: Fix issue: https://github.com/piotrwitek/typesafe-actions/issues/174 7 6 export default combineEpics(device, experiment, jupyter);
+149 -149
app/utils/behavior/compute.js
··· 3 3 import * as path from 'path'; 4 4 5 5 export const aggregateBehaviorDataToSave = (data, removeOutliers) => { 6 - const processedData = data.map(result => { 6 + const processedData = data.map((result) => { 7 7 if (path.basename(result.meta.datafile).includes('aggregated')) { 8 8 return transformAggregated(result); 9 9 } 10 10 return filterData(result, removeOutliers); 11 11 }); 12 - const aggregatedData = processedData.map(e => { 13 - const conditionsArray = e.map(row => row.condition); 12 + const aggregatedData = processedData.map((e) => { 13 + const conditionsArray = e.map((row) => row.condition); 14 14 const unsortedConditions = [...new Set(conditionsArray)].sort(); 15 15 const conditions = unsortedConditions.sort( 16 16 (a, b) => parseInt(a) - parseInt(b) ··· 19 19 accuracyPercent = {}; 20 20 for (const condition of conditions) { 21 21 const rt = e 22 - .filter(row => row.response_given === 'yes') 23 - .filter(row => row.correct_response === 'true') 24 - .filter(row => row.condition === condition) 25 - .map(row => row.reaction_time) 26 - .map(value => parseFloat(value)); 22 + .filter((row) => row.response_given === 'yes') 23 + .filter((row) => row.correct_response === 'true') 24 + .filter((row) => row.condition === condition) 25 + .map((row) => row.reaction_time) 26 + .map((value) => parseFloat(value)); 27 27 rtMean[condition] = Math.round(ss.mean(rt)); 28 28 const accuracy = e.filter( 29 - row => 29 + (row) => 30 30 row.condition === condition && 31 31 row.correct_response === 'true' && 32 32 row.response_given === 'yes' ··· 35 35 ? Math.round( 36 36 100 * 37 37 (accuracy.length / 38 - e.filter(r => r.condition === condition).length) 38 + e.filter((r) => r.condition === condition).length) 39 39 ) 40 40 : ss.mean( 41 - e.filter(r => r.condition === condition).map(r => r.accuracy) 41 + e.filter((r) => r.condition === condition).map((r) => r.accuracy) 42 42 ); 43 43 } 44 44 const row = { 45 - subject: e.map(r => r.subject)[0], 46 - group: e.map(r => r.group)[0], 47 - session: e.map(r => r.session)[0] 45 + subject: e.map((r) => r.subject)[0], 46 + group: e.map((r) => r.group)[0], 47 + session: e.map((r) => r.session)[0], 48 48 }; 49 49 for (const condition of conditions) { 50 50 row[`RT_${condition}`] = rtMean[condition]; ··· 62 62 showDataPoints, 63 63 displayMode 64 64 ) => { 65 - if (data && data.length > 0) { 66 - const processedData = data.map(result => { 67 - if (path.basename(result.meta.datafile).includes('aggregated')) { 68 - return transformAggregated(result); 69 - } 70 - return filterData(result, removeOutliers); 71 - }); 72 - const colors = ['#28619E', '#3DBBDB']; 73 - const unsortedConditions = [ 74 - ...new Set(processedData[0].map(row => row.condition)) 75 - ].sort(); 76 - const conditions = unsortedConditions.sort( 77 - (a, b) => parseInt(a) - parseInt(b) 78 - ); 79 - switch (dependentVariable) { 80 - case 'RT': 81 - default: 82 - return computeRT( 83 - processedData, 84 - dependentVariable, 85 - conditions, 86 - showDataPoints, 87 - colors, 88 - displayMode 89 - ); 90 - case 'Accuracy': 91 - return computeAccuracy( 92 - processedData, 93 - dependentVariable, 94 - conditions, 95 - showDataPoints, 96 - colors, 97 - displayMode 98 - ); 65 + if (!data || data.length < 1) { 66 + return; 67 + } 68 + const processedData = data.map((result) => { 69 + if (path.basename(result.meta.datafile).includes('aggregated')) { 70 + return transformAggregated(result); 99 71 } 72 + return filterData(result, removeOutliers); 73 + }); 74 + const colors = ['#28619E', '#3DBBDB']; 75 + const conditions = [ 76 + ...new Set(processedData[0].map((row) => row.condition)), 77 + ].sort((a, b) => parseInt(a) - parseInt(b)); 78 + 79 + switch (dependentVariable) { 80 + case 'RT': 81 + default: 82 + return computeRT( 83 + processedData, 84 + dependentVariable, 85 + conditions, 86 + showDataPoints, 87 + colors, 88 + displayMode 89 + ); 90 + case 'Accuracy': 91 + return computeAccuracy( 92 + processedData, 93 + dependentVariable, 94 + conditions, 95 + showDataPoints, 96 + colors, 97 + displayMode 98 + ); 100 99 } 101 100 }; 102 101 103 - const transformAggregated = result => { 102 + const transformAggregated = (result) => { 104 103 const unsortedConditions = result.meta.fields 105 - .filter(field => field.startsWith('RT_')) 106 - .map(c => c.split('RT_')[1]) 104 + .filter((field) => field.startsWith('RT_')) 105 + .map((c) => c.split('RT_')[1]) 107 106 .sort(); 108 107 const conditions = unsortedConditions.sort( 109 108 (a, b) => parseInt(a) - parseInt(b) 110 109 ); 111 - const transformed = conditions.map(condition => 112 - result.data.map(e => ({ 110 + const transformed = conditions.map((condition) => 111 + result.data.map((e) => ({ 113 112 reaction_time: parseFloat(e[`RT_${condition}`]), 114 113 subject: path.parse(result.meta.datafile).name, 115 114 condition, ··· 117 116 session: e.session, 118 117 accuracy: parseFloat(e[`Accuracy_${condition}`]), 119 118 response_given: 'yes', 120 - correct_response: 'true' 119 + correct_response: 'true', 121 120 })) 122 121 ); 123 122 const data = transformed.reduce((acc, item) => acc.concat(item), []); ··· 126 125 127 126 const filterData = (data, removeOutliers) => { 128 127 let filteredData = data.data 129 - .filter(row => row.trial_number && row.phase !== 'practice') 130 - .map(row => ({ 128 + .filter((row) => row.trial_number && row.phase !== 'practice') 129 + .map((row) => ({ 131 130 condition: row.condition, 132 131 subject: path.parse(data.meta.datafile).name.split('-')[0], 133 132 group: path.parse(data.meta.datafile).name.split('-')[1], ··· 135 134 reaction_time: Math.round(parseFloat(row.reaction_time)), 136 135 correct_response: row.correct_response, 137 136 trial_number: row.trial_number, 138 - response_given: row.response_given 137 + response_given: row.response_given, 139 138 })); 140 139 if (removeOutliers) { 141 140 try { 142 141 const mean = ss.mean( 143 142 filteredData 144 143 .filter( 145 - r => r.response_given === 'yes' && r.correct_response === 'true' 144 + (r) => r.response_given === 'yes' && r.correct_response === 'true' 146 145 ) 147 - .map(r => r.reaction_time) 146 + .map((r) => r.reaction_time) 148 147 ); 149 148 const standardDeviation = ss.sampleStandardDeviation( 150 149 filteredData 151 150 .filter( 152 - r => r.response_given === 'yes' && r.correct_response === 'true' 151 + (r) => r.response_given === 'yes' && r.correct_response === 'true' 153 152 ) 154 - .map(r => r.reaction_time) 153 + .map((r) => r.reaction_time) 155 154 ); 156 155 const upperBoarder = mean + 2 * standardDeviation; 157 156 const lowerBoarder = mean - 2 * standardDeviation; 158 157 filteredData = filteredData.filter( 159 - r => 158 + (r) => 160 159 (r.reaction_time > lowerBoarder && r.reaction_time < upperBoarder) || 161 160 isNaN(r.reaction_time) 162 161 ); ··· 188 187 const xRaw = data 189 188 .reduce((a, b) => a.concat(b), []) 190 189 .filter( 191 - r => r.response_given === 'yes' && r.correct_response === 'true' 190 + (r) => r.response_given === 'yes' && r.correct_response === 'true' 192 191 ) 193 - .filter(e => e.condition === condition) 194 - .map(r => r.subject); 192 + .filter((e) => e.condition === condition) 193 + .map((r) => r.subject); 195 194 const y = data 196 195 .reduce((a, b) => a.concat(b), []) 197 196 .filter( 198 - r => r.response_given === 'yes' && r.correct_response === 'true' 197 + (r) => r.response_given === 'yes' && r.correct_response === 'true' 199 198 ) 200 - .filter(e => e.condition === condition) 201 - .map(r => r.reaction_time); 199 + .filter((e) => e.condition === condition) 200 + .map((r) => r.reaction_time); 202 201 maxValue = Math.max(...y) > maxValue ? Math.max(...y) : maxValue; 203 202 const subjects = Array.from(new Set(xRaw)); 204 203 const x = xRaw.map( 205 - x => subjects.indexOf(x) + 1 + i / 4 + (Math.random() - 0.5) / 5 204 + (x) => subjects.indexOf(x) + 1 + i / 4 + (Math.random() - 0.5) / 5 206 205 ); 207 - tickValuesX = subjects.map(x => subjects.indexOf(x) + 1 + 1 / 8); 206 + tickValuesX = subjects.map((x) => subjects.indexOf(x) + 1 + 1 / 8); 208 207 tickTextX = subjects; 209 208 obj[condition] = { x, y }; 210 209 return obj; ··· 226 225 const xRaw = data 227 226 .reduce((a, b) => a.concat(b), []) 228 227 .filter( 229 - r => r.response_given === 'yes' && r.correct_response === 'true' 228 + (r) => r.response_given === 'yes' && r.correct_response === 'true' 230 229 ) 231 - .filter(e => e.condition === condition) 232 - .map(r => r.subject); 230 + .filter((e) => e.condition === condition) 231 + .map((r) => r.subject); 233 232 const x = Array.from(new Set(xRaw)); 234 - const data_condition = data.map(d => 233 + const data_condition = data.map((d) => 235 234 d 236 235 .filter( 237 - r => r.response_given === 'yes' && r.correct_response === 'true' 236 + (r) => r.response_given === 'yes' && r.correct_response === 'true' 238 237 ) 239 - .filter(e => e.condition == condition) 238 + .filter((e) => e.condition == condition) 240 239 ); 241 - const y_bars_prep = x.map(a => 240 + const y_bars_prep = x.map((a) => 242 241 data_condition 243 - .map(d => d.filter(e => e.subject === a)) 244 - .filter(d => d.length > 0) 242 + .map((d) => d.filter((e) => e.subject === a)) 243 + .filter((d) => d.length > 0) 245 244 ); 246 245 const y = y_bars_prep 247 - .map(y => 246 + .map((y) => 248 247 ss.mean( 249 - y.reduce((a, b) => a.concat(b), []).map(r => r.reaction_time) 248 + y.reduce((a, b) => a.concat(b), []).map((r) => r.reaction_time) 250 249 ) 251 250 ) 252 - .map(v => Math.round(v)); 251 + .map((v) => Math.round(v)); 253 252 maxValue = Math.max(...y) > maxValue ? Math.max(...y) : maxValue; 254 - const stErrorFunction = array => 253 + const stErrorFunction = (array) => 255 254 ss.sampleStandardDeviation(array) / Math.sqrt(array.length); 256 255 const stErrors = data_condition 257 - .map(a => 258 - a.length > 1 ? stErrorFunction(a.map(r => r.reaction_time)) : 0 256 + .map((a) => 257 + a.length > 1 ? stErrorFunction(a.map((r) => r.reaction_time)) : 0 259 258 ) 260 - .map(v => Math.round(v)); 259 + .map((v) => Math.round(v)); 261 260 maxValueSE = 262 261 Math.max(...stErrors) > maxValueSE 263 262 ? Math.max(...stErrors) ··· 275 274 const x = data 276 275 .reduce((a, b) => a.concat(b), []) 277 276 .filter( 278 - r => r.response_given === 'yes' && r.correct_response === 'true' 277 + (r) => r.response_given === 'yes' && r.correct_response === 'true' 279 278 ) 280 - .filter(e => e.condition === condition) 281 - .map(r => r.subject); 279 + .filter((e) => e.condition === condition) 280 + .map((r) => r.subject); 282 281 const y = data 283 282 .reduce((a, b) => a.concat(b), []) 284 283 .filter( 285 - r => r.response_given === 'yes' && r.correct_response === 'true' 284 + (r) => r.response_given === 'yes' && r.correct_response === 'true' 286 285 ) 287 - .filter(e => e.condition === condition) 288 - .map(r => r.reaction_time); 286 + .filter((e) => e.condition === condition) 287 + .map((r) => r.reaction_time); 289 288 maxValue = Math.max(...y) > maxValue ? Math.max(...y) : maxValue; 290 289 obj[condition] = { x, y }; 291 290 return obj; ··· 311 310 default: 312 311 let tickValuesX, tickTextX; 313 312 dataToPlot = conditions.reduce((obj, condition, i) => { 314 - const correctDataForCondition = data.map(d => 315 - d.filter(e => e.condition == condition) 313 + const correctDataForCondition = data.map((d) => 314 + d.filter((e) => e.condition == condition) 316 315 ); 317 316 318 317 const y = correctDataForCondition 319 - .map(d => { 320 - if (d.filter(l => l.accuracy).length > 0) { 321 - return d.map(l => l.accuracy); 318 + .map((d) => { 319 + if (d.filter((l) => l.accuracy).length > 0) { 320 + return d.map((l) => l.accuracy); 322 321 } 323 322 const c = d.filter( 324 - e => e.response_given === 'yes' && e.correct_response === 'true' 323 + (e) => e.response_given === 'yes' && e.correct_response === 'true' 325 324 ); 326 325 return Math.round((c.length / d.length) * 100); 327 326 }) 328 - .reduce((acc, item) => acc.concat(item), []); 327 + // TODO: Remove these useless reduce steps, but confirm it doesn't break anything 328 + .reduce((acc, item) => acc.concat(item), []); // ? 329 329 330 330 const xRaw = correctDataForCondition 331 - .map(d => { 332 - if (d.filter(l => l.accuracy).length > 0) { 333 - return d.map(l => l.subject); 331 + .map((d) => { 332 + if (d.filter((l) => l.accuracy).length > 0) { 333 + return d.map((l) => l.subject); 334 334 } 335 - return d.map(r => r.subject)[0]; 335 + return d.map((r) => r.subject)[0]; 336 336 }) 337 - .reduce((acc, item) => acc.concat(item), []); 337 + .reduce((acc, item) => acc.concat(item), []); // ? 338 338 const subjects = Array.from(new Set(xRaw)); 339 339 const x = xRaw.map( 340 - x => subjects.indexOf(x) + 1 + i / 4 + (Math.random() - 0.5) / 5 340 + (x) => subjects.indexOf(x) + 1 + i / 4 + (Math.random() - 0.5) / 5 341 341 ); 342 - tickValuesX = subjects.map(x => subjects.indexOf(x) + 1 + 1 / 8); 342 + tickValuesX = subjects.map((x) => subjects.indexOf(x) + 1 + 1 / 8); 343 343 tickTextX = subjects; 344 344 obj[condition] = { x, y }; 345 345 return obj; ··· 357 357 358 358 case 'errorbars': 359 359 dataToPlot = conditions.reduce((obj, condition, i) => { 360 - const correctDataForCondition = data.map(d => 361 - d.filter(e => e.condition == condition) 360 + const correctDataForCondition = data.map((d) => 361 + d.filter((e) => e.condition == condition) 362 362 ); 363 363 const transformedData = correctDataForCondition 364 - .map(d => { 365 - if (d.filter(l => l.accuracy).length > 0) { 366 - return d.map(l => ({ 364 + .map((d) => { 365 + if (d.filter((l) => l.accuracy).length > 0) { 366 + return d.map((l) => ({ 367 367 accuracy: l.accuracy, 368 - subject: l.subject 368 + subject: l.subject, 369 369 })); 370 370 } 371 371 const c = d.filter( 372 - e => e.response_given === 'yes' && e.correct_response === 'true' 372 + (e) => e.response_given === 'yes' && e.correct_response === 'true' 373 373 ); 374 374 return { 375 375 accuracy: Math.round((c.length / d.length) * 100), 376 - subject: d.map(r => r.subject)[0] 376 + subject: d.map((r) => r.subject)[0], 377 377 }; 378 378 }) 379 379 .reduce((acc, item) => acc.concat(item), []); 380 380 const subjects = Array.from( 381 - new Set(transformedData.map(e => e.subject)) 381 + new Set(transformedData.map((e) => e.subject)) 382 382 ); 383 - const y = subjects.map(subject => 383 + const y = subjects.map((subject) => 384 384 ss.mean( 385 385 transformedData 386 - .filter(e => e.subject === subject) 387 - .map(d => d.accuracy) 386 + .filter((e) => e.subject === subject) 387 + .map((d) => d.accuracy) 388 388 ) 389 389 ); 390 - const stErrorFunction = array => 390 + const stErrorFunction = (array) => 391 391 ss.sampleStandardDeviation(array) / Math.sqrt(array.length); 392 - const stErrors = subjects.map(subject => { 392 + const stErrors = subjects.map((subject) => { 393 393 const array = transformedData 394 - .filter(e => e.subject === subject) 395 - .map(d => d.accuracy); 394 + .filter((e) => e.subject === subject) 395 + .map((d) => d.accuracy); 396 396 if (array.length > 1) { 397 397 return stErrorFunction(array); 398 398 } ··· 407 407 408 408 case 'whiskers': 409 409 dataToPlot = conditions.reduce((obj, condition, i) => { 410 - const correctDataForCondition = data.map(d => 411 - d.filter(e => e.condition == condition) 410 + const correctDataForCondition = data.map((d) => 411 + d.filter((e) => e.condition == condition) 412 412 ); 413 413 const y = correctDataForCondition 414 - .map(d => { 415 - if (d.filter(l => l.accuracy).length > 0) { 416 - return d.map(l => l.accuracy); 414 + .map((d) => { 415 + if (d.filter((l) => l.accuracy).length > 0) { 416 + return d.map((l) => l.accuracy); 417 417 } 418 418 const c = d.filter( 419 - e => e.response_given === 'yes' && e.correct_response === 'true' 419 + (e) => e.response_given === 'yes' && e.correct_response === 'true' 420 420 ); 421 421 return Math.round((c.length / d.length) * 100); 422 422 }) 423 423 .reduce((acc, item) => acc.concat(item), []); 424 424 const xRaw = correctDataForCondition 425 - .map(d => { 426 - if (d.filter(l => l.accuracy).length > 0) { 427 - return d.map(l => l.subject); 425 + .map((d) => { 426 + if (d.filter((l) => l.accuracy).length > 0) { 427 + return d.map((l) => l.subject); 428 428 } 429 - return d.map(r => r.subject)[0]; 429 + return d.map((r) => r.subject)[0]; 430 430 }) 431 431 .reduce((acc, item) => acc.concat(item), []); 432 432 obj[condition] = { x: xRaw, y }; ··· 452 452 marker: { 453 453 color: colors[i], 454 454 size: 7, 455 - symbol: symbols[i] 455 + symbol: symbols[i], 456 456 }, 457 - mode: 'markers' 457 + mode: 'markers', 458 458 }; 459 459 }); 460 460 const layout = { 461 461 xaxis: { 462 462 tickvals: data.tickvals, 463 - ticktext: data.ticktext 463 + ticktext: data.ticktext, 464 464 }, 465 465 yaxis: { 466 466 title: `${ ··· 468 468 ? 'Response Time (milliseconds)' 469 469 : '% correct' 470 470 }`, 471 - range: [data.lowerLimit, data.upperLimit] 471 + range: [data.lowerLimit, data.upperLimit], 472 472 }, 473 - title: `${dependentVariable}` 473 + title: `${dependentVariable}`, 474 474 }; 475 475 return { 476 476 dataToPlot, 477 - layout 477 + layout, 478 478 }; 479 479 }; 480 480 ··· 488 488 type: 'bar', 489 489 marker: { 490 490 color: colors[i], 491 - size: 7 491 + size: 7, 492 492 }, 493 493 error_y: { 494 494 type: 'data', 495 495 array: dataForCondition.stErrors, 496 - visible: true 497 - } 496 + visible: true, 497 + }, 498 498 }; 499 499 }); 500 500 const layout = { ··· 505 505 : '% correct' 506 506 }`, 507 507 zeroline: false, 508 - range: [data.lowerLimit, data.upperLimit] 508 + range: [data.lowerLimit, data.upperLimit], 509 509 }, 510 510 barmode: 'group', 511 - title: `${dependentVariable}` 511 + title: `${dependentVariable}`, 512 512 }; 513 513 return { 514 514 dataToPlot, 515 - layout 515 + layout, 516 516 }; 517 517 }; 518 518 ··· 528 528 marker: { 529 529 color: colors[i], 530 530 size: 7, 531 - symbol: symbols[i] 531 + symbol: symbols[i], 532 532 }, 533 533 boxpoints: 'false', 534 - pointpos: 0 534 + pointpos: 0, 535 535 }; 536 536 }); 537 537 const layout = { ··· 542 542 : '% correct' 543 543 }`, 544 544 zeroline: false, 545 - range: [data.lowerLimit, data.upperLimit] 545 + range: [data.lowerLimit, data.upperLimit], 546 546 }, 547 547 boxmode: 'group', 548 - title: `${dependentVariable}` 548 + title: `${dependentVariable}`, 549 549 }; 550 550 return { 551 551 dataToPlot, 552 - layout 552 + layout, 553 553 }; 554 554 };
+1 -1
app/utils/labjs/functions.ts
··· 7 7 import { buildCustomTimeline } from './protocols/custom'; 8 8 9 9 // loads a protocol of the experiment 10 - // TODOL refactor this experiment description system to be much more predictable 10 + // TODO refactor this experiment description system to be much more predictable 11 11 export const loadProtocol = (paradigm: EXPERIMENTS) => { 12 12 let protocol; 13 13 switch (paradigm) {
+1 -1
package.json
··· 294 294 "muse-js": "^3.1.0", 295 295 "papaparse": "^5.2.0", 296 296 "plotly.js": "^1.54.2", 297 - "rc-slider": "^9.2.4", 297 + "rc-slider": "9.2.4", 298 298 "react": "^16.13.1", 299 299 "react-dom": "^16.12.0", 300 300 "react-hot-loader": "^4.12.21",
+36 -17
yarn.lock
··· 2659 2659 resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" 2660 2660 integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== 2661 2661 2662 + add-dom-event-listener@^1.1.0: 2663 + version "1.1.0" 2664 + resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" 2665 + integrity sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw== 2666 + dependencies: 2667 + object-assign "4.x" 2668 + 2662 2669 add-line-numbers@^1.0.1: 2663 2670 version "1.0.1" 2664 2671 resolved "https://registry.yarnpkg.com/add-line-numbers/-/add-line-numbers-1.0.1.tgz#48dbbdea47dbd234deafeac6c93cea6f70b4b7e3" ··· 3970 3977 mkdirp "^0.5.1" 3971 3978 source-map-support "^0.4.15" 3972 3979 3973 - babel-runtime@^5.8.34: 3974 - version "5.8.38" 3975 - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-5.8.38.tgz#1c0b02eb63312f5f087ff20450827b425c9d4c19" 3976 - integrity sha1-HAsC62MxL18If/IEUIJ7QlydTBk= 3977 - dependencies: 3978 - core-js "^1.0.0" 3979 - 3980 - babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.25.0, babel-runtime@^6.26.0, babel-runtime@^6.6.1: 3980 + babel-runtime@6.x, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.25.0, babel-runtime@^6.26.0, babel-runtime@^6.6.1: 3981 3981 version "6.26.0" 3982 3982 resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" 3983 3983 integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= 3984 3984 dependencies: 3985 3985 core-js "^2.4.0" 3986 3986 regenerator-runtime "^0.11.0" 3987 + 3988 + babel-runtime@^5.8.34: 3989 + version "5.8.38" 3990 + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-5.8.38.tgz#1c0b02eb63312f5f087ff20450827b425c9d4c19" 3991 + integrity sha1-HAsC62MxL18If/IEUIJ7QlydTBk= 3992 + dependencies: 3993 + core-js "^1.0.0" 3987 3994 3988 3995 babel-template@^6.24.1, babel-template@^6.26.0: 3989 3996 version "6.26.0" ··· 12690 12697 resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" 12691 12698 integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== 12692 12699 12693 - object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: 12700 + object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: 12694 12701 version "4.1.1" 12695 12702 resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 12696 12703 integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= ··· 14092 14099 object.assign "^4.1.0" 14093 14100 reflect.ownkeys "^0.2.0" 14094 14101 14095 - prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: 14102 + prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: 14096 14103 version "15.7.2" 14097 14104 resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" 14098 14105 integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== ··· 14370 14377 raf "^3.4.0" 14371 14378 rc-util "^5.0.1" 14372 14379 14373 - rc-slider@^9.2.4: 14374 - version "9.3.1" 14375 - resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-9.3.1.tgz#444012f3b4847d592b167a9cee6a1a46779a6ef4" 14376 - integrity sha512-c52PWPyrfJWh28K6dixAm0906L3/4MUIxqrNQA4TLnC/Z+cBNycWJUZoJerpwSOE1HdM3XDwixCsmtFc/7aWlQ== 14380 + rc-slider@9.2.4: 14381 + version "9.2.4" 14382 + resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-9.2.4.tgz#92e2b58c53def9921ae0fc2822727ab5785b9ed0" 14383 + integrity sha512-wSr7vz+WtzzGqsGU2rTQ4mmLz9fkuIDMPYMYm8ygYFvxQ2Rh4uRhOWHYI0R8krNK5k1bGycckYxmQqUIvLAh3w== 14377 14384 dependencies: 14378 - "@babel/runtime" "^7.10.1" 14385 + babel-runtime "6.x" 14379 14386 classnames "^2.2.5" 14380 14387 rc-tooltip "^4.0.0" 14381 - rc-util "^5.0.0" 14388 + rc-util "^4.0.4" 14382 14389 shallowequal "^1.1.0" 14390 + warning "^4.0.3" 14383 14391 14384 14392 rc-tooltip@^4.0.0: 14385 14393 version "4.2.1" ··· 14400 14408 rc-animate "^3.0.0" 14401 14409 rc-util "^5.0.1" 14402 14410 14403 - rc-util@^5.0.0, rc-util@^5.0.1: 14411 + rc-util@^4.0.4: 14412 + version "4.21.1" 14413 + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.21.1.tgz#88602d0c3185020aa1053d9a1e70eac161becb05" 14414 + integrity sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg== 14415 + dependencies: 14416 + add-dom-event-listener "^1.1.0" 14417 + prop-types "^15.5.10" 14418 + react-is "^16.12.0" 14419 + react-lifecycles-compat "^3.0.4" 14420 + shallowequal "^1.1.0" 14421 + 14422 + rc-util@^5.0.1: 14404 14423 version "5.0.4" 14405 14424 resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.0.4.tgz#297bd719b1bd00b3c947a884ab7ef0a07c55dce6" 14406 14425 integrity sha512-cd19RCrE0DJH6UcJ9+V3eaXA/5sNWyVKOKkWl8ZM2OqgNzVb8fv0obf/TkuvSN43tmTsgqY8k7OqpFYHhmef8g==