UNPKG

gazeplotter

Version:

Gazeplotter is a Svelte application for visualizing eye-tracking data.

331 lines (330 loc) 13.7 kB
import { writable, get } from 'svelte/store'; import { demoData } from '../const/demoDataTwo.ts'; export const getDemoDataWritable = () => { return writable(demoData); }; export const data = getDemoDataWritable(); export const setData = (newData) => { data.set(newData); }; export const getData = () => { return get(data); }; export const getNumberOfStimuli = () => { return getData().stimuli.data.length; }; export const getNumberOfParticipants = () => { return getData().participants.data.length; }; /** * Returns the stimulus with the given id * @param id - id of the stimulus * @returns BaseInterpretedDataType * @throws Error if stimulus with given id does not exist */ export const getStimulus = (id) => { const stimulusArray = getData().stimuli.data[id]; if (stimulusArray === undefined) throw new Error('Stimulus with this id does not exist'); const originalName = stimulusArray[0]; const displayedName = stimulusArray[1] ?? originalName; return { id, originalName, displayedName, }; }; export const getStimulusHighestEndTime = (stimulusIndex) => { let max = 0; for (let participantIndex = 0; participantIndex < getNumberOfParticipants(); participantIndex++) { const lastSegmentEndTime = getParticipantEndTime(stimulusIndex, participantIndex); max = lastSegmentEndTime > max ? (max = lastSegmentEndTime) : max; } return max; }; export const getParticipantEndTime = (stimulusIndex, particIndex) => { const segmentsInfo = getData().segments[stimulusIndex][particIndex]; return segmentsInfo === undefined ? 0 : segmentsInfo.length > 0 ? segmentsInfo[segmentsInfo.length - 1][1] : 0; }; export const getParticipant = (id) => { const participantArray = getData().participants.data[id]; if (participantArray === undefined) throw new Error('Participant with this id does not exist'); const originalName = participantArray[0]; const displayedName = participantArray[1] ?? originalName; return { id, displayedName, originalName, }; }; const getDefaultColor = (index) => { const COLORS = ['#66c5cc', '#f6cf71', '#f89c74', '#dcb0f2', '#87c55f']; return COLORS[index % COLORS.length]; }; export const getAoi = (stimulusId, aoiId) => { const aoiArray = getData().aois.data[stimulusId][aoiId]; if (aoiArray === undefined) throw new Error(`AOI with id ${aoiId} does not exist in stimulus with id ${stimulusId}`); const originalName = aoiArray[0]; const displayedName = aoiArray[1] ?? originalName; const color = aoiArray[2] ?? getDefaultColor(aoiId); return { id: aoiId, originalName, displayedName, color, }; }; export const getCategory = (id) => { const categoryArray = getData().categories.data[id]; if (categoryArray === undefined) throw new Error(`Category with id ${id} does not exist`); const originalName = categoryArray[0]; const displayedName = categoryArray[1] ?? originalName; const color = categoryArray[2] ?? '#626262'; return { id, originalName, displayedName, color, }; }; export const getSegment = (stimulusId, participantId, id) => { const segmentArray = getData().segments[stimulusId][participantId][id]; if (segmentArray === undefined) throw new Error(`Segment with id ${id} does not exist in stimulus with id ${stimulusId} and participant with id ${participantId}`); const start = segmentArray[0]; const end = segmentArray[1]; const aoiIds = getSortedAoiIdsByOrderVector(stimulusId, segmentArray.slice(3)); const aoi = aoiIds.map((aoiId) => getAoi(stimulusId, aoiId)); const categoryId = segmentArray[2]; const category = getCategory(categoryId); return { id, start, end, aoi, category, }; }; /** * Returns given array of aoi ids sorted by its order vector. * @param stimulusId - id of the stimulus as AOIs are stimulus specific (thus, order vectors too) * @param aoiIds - array of aoi ids to be sorted */ const getSortedAoiIdsByOrderVector = (stimulusId, aoiIds) => { const orderVector = getAoiOrderVector(stimulusId); return aoiIds.sort((a, b) => orderVector.indexOf(a) - orderVector.indexOf(b)); }; export const getNumberOfSegments = (stimulusId, participantId) => { return getData().segments[stimulusId][participantId]?.length ?? 0; }; export const getAoiOrderVector = (stimulusId) => { const order = getData().aois.orderVector?.[stimulusId]; // if it does not exist in const, return array of sequence from 0 to N // N ... number of aoi categories for given stimulus if (order == null) { const noOfAois = getData().aois.data[stimulusId].length; return [...Array(noOfAois).keys()]; } return order; }; export const getStimuliOrderVector = () => { const order = getData().stimuli.orderVector; // if it does not exist in const, return array of sequence from 0 to N // N ... number of aoi categories for given stimulus if (order.length === 0) { const noOfStimuli = getData().stimuli.data.length; return [...Array(noOfStimuli).keys()]; } return order; }; export const getParticipantOrderVector = () => { const order = getData().participants.orderVector; // if it does not exist in const, return array of sequence from 0 to N // N ... number of aoi categories for given stimulus if (order.length === 0) { const noOfParticipants = getData().participants.data.length; return [...Array(noOfParticipants).keys()]; } return order; }; export const getAois = (stimulusId) => { const aoiIds = getAoiOrderVector(stimulusId); return aoiIds.map((aoiId) => getAoi(stimulusId, aoiId)); }; export const getStimuli = () => { const stimuliIds = getStimuliOrderVector(); return stimuliIds.map((stimulusId) => getStimulus(stimulusId)); }; /** * Get all participants of given group ID. * Only groups with non-negative ID truly exist. * Values -1 and -2 are used for "All participants" and "Non-empty participants" default groups. * @param groupId Id of participants group. If not provided, -1 is used as default value * @returns participants of given group * @throws Error if group with given id does not exist and is not -1 or -2 */ export const getParticipants = (groupId = -1, stimulusId = 0) => { if (groupId === -1) { return getAllParticipants(); } if (groupId === -2) { console.log('non empty'); return getNonEmptyParticipants(stimulusId); } const group = getParticipantsGroup(groupId); const participantsIds = getParticipantOrderVector(); const groupParticipantsIds = participantsIds.filter(participantId => group.participantsIds.includes(participantId)); return groupParticipantsIds.map(participantId => getParticipant(participantId)); }; export const getAllParticipants = () => { const participantsIds = getParticipantOrderVector(); return participantsIds.map((participantId) => getParticipant(participantId)); }; export const getNonEmptyParticipants = (stimulusId) => { const participantsIds = getParticipantOrderVector(); const nonEmptyParticipantsIds = participantsIds.filter(participantId => getNumberOfSegments(stimulusId, participantId) > 0); return nonEmptyParticipantsIds.map(participantId => getParticipant(participantId)); }; export const getParticipantsGroups = (isDefault = false, stimulusId = 0) => { const defaultGroups = []; if (isDefault) { defaultGroups.push({ id: -1, name: 'All participants', participantsIds: getParticipantOrderVector(), }); defaultGroups.push({ id: -2, name: 'Non-empty', participantsIds: getNonEmptyParticipants(stimulusId).map(participant => participant.id), }); } return [...defaultGroups, ...getData().participantsGroups]; }; export const getParticipantsGroup = (groupId) => { const group = getParticipantsGroups().find(group => group.id === groupId); if (group === undefined) { throw new Error(`Participants group with id ${groupId} does not exist`); } return group; }; export const updateParticipantsGroups = (groups) => { data.update(data => { data.participantsGroups = groups; return data; }); }; /** * Returns the visibility of the AOI for the given stimulus and participant. * Remember the AOI dynamic visibility are stored under the key "stimulusId_aoiId_participantId" * @param stimulusId numeric id of the stimulus * @param aoiId numeric id of the AOI for the given stimulus (notice that AOI ids are stimulus specific) * @param participantId numeric id of the participant (these are global for all stimuli) * @returns */ export const getAoiVisibility = (stimulusId, aoiId, participantId = null) => { const baseKey = `${stimulusId}_${aoiId}`; let result = getData().aois.dynamicVisibility[baseKey] ?? null; if (participantId != null) { const extendedKey = `${baseKey}_${participantId}`; result = getData().aois.dynamicVisibility[extendedKey] ?? result; } return result; }; /** * Returns boolean value indicating if the AOI has any visibility set for the given stimulus. * Remember the AOI dynamic visibility are stored under the key "stimulusId_aoiId_participantId". * Thus, we need to check if there is any key starting with "stimulusId_". * @param stimulusId numeric id of the stimulus * @returns boolean value indicating if the AOI has any visibility set for the given stimulus */ export const hasStimulusAoiVisibility = (stimulusId) => { return Object.keys(getData().aois.dynamicVisibility).some(key => key.startsWith(`${stimulusId}_`)); }; export const updateMultipleAoiVisibility = (stimulusId, aoiNames, visibilityArr, participantId = null) => { data.update(currentData => { const aoiData = currentData.aois.data[stimulusId]; if (aoiData === undefined) { console.error(`No AOI data found for stimulusId: ${stimulusId}`); return currentData; // No update if no AOI data found } aoiNames.forEach((aoiName, index) => { const aoiId = aoiData.findIndex(el => el[0] === aoiName); if (aoiId === -1) { console.warn(`AOI with name ${aoiName} not found for stimulusId: ${stimulusId}`); return; // Continue to next AOI name if current one not found } let key = `${stimulusId}_${aoiId}`; if (participantId != null) { key += `_${participantId}`; } currentData.aois.dynamicVisibility[key] = visibilityArr[index]; }); return currentData; }); }; export const updateMultipleAoi = (aoi, stimulusId, applyTo) => { console.log('updateMultipleAoi', aoi); data.update(data => { if (data.aois.data[stimulusId] === undefined) { throw new Error(`Stimulus with id ${stimulusId} does not exist in AOIs data`); } if (aoi.length !== data.aois.data[stimulusId].length) { throw new Error(`Number of AOIs in stimulus with id ${stimulusId} does not match number of AOIs in given array`); } aoi.forEach(aoi => { const aoiArray = [aoi.originalName, aoi.displayedName, aoi.color]; if (data.aois.data[stimulusId][aoi.id] === undefined) { throw new Error(`AOI with id ${aoi.id} does not exist in stimulus with id ${stimulusId}`); } data.aois.data[stimulusId][aoi.id] = aoiArray; }); // Update order vector, only for this stimulus, it is too hard to guess what to do in the other stimuli data.aois.orderVector[stimulusId] = aoi.map(aoi => aoi.id); if (applyTo === 'all_by_original_name') { const originalNames = aoi.map(aoi => aoi.originalName); const displayedNames = aoi.map(aoi => aoi.displayedName); const colors = aoi.map(aoi => aoi.color); const noOfStimuli = data.stimuli.data.length; for (let stimulusId = 0; stimulusId < noOfStimuli; stimulusId++) { const aois = data.aois.data[stimulusId]; for (const aoi of aois) { if (aoi === undefined) continue; const originalName = aoi[0]; if (originalNames.includes(originalName)) { const index = originalNames.indexOf(originalName); aoi[1] = displayedNames[index]; aoi[2] = colors[index]; } } } } if (applyTo === 'all_by_displayed_name') { const displayedNames = aoi.map(aoi => aoi.displayedName); const colors = aoi.map(aoi => aoi.color); const noOfStimuli = data.stimuli.data.length; for (let stimulusId = 0; stimulusId < noOfStimuli; stimulusId++) { const aois = data.aois.data[stimulusId]; for (const aoi of aois) { if (aoi === undefined) continue; const displayedName = aoi[1]; if (displayedNames.includes(displayedName)) { const index = displayedNames.indexOf(displayedName); aoi[1] = displayedNames[index]; aoi[2] = colors[index]; } } } } return data; }); };