diff --git a/components/annot-tool-group.vue b/components/annot-tool-group.vue deleted file mode 100644 index 92bf9598..00000000 --- a/components/annot-tool-group.vue +++ /dev/null @@ -1,321 +0,0 @@ - - - \ No newline at end of file diff --git a/components/categ-tool-group.vue b/components/categ-tool-group.vue deleted file mode 100644 index 951b2504..00000000 --- a/components/categ-tool-group.vue +++ /dev/null @@ -1,486 +0,0 @@ - - - - - \ No newline at end of file diff --git a/components/column-linking-table.vue b/components/column-linking-table.vue index 4cffb175..b0311af8 100644 --- a/components/column-linking-table.vue +++ b/components/column-linking-table.vue @@ -55,14 +55,14 @@ ...mapState([ "colorInfo", - "columnToCategoryMapping" + "columnToCategoryMap" ]), tableRows() { return this.getColumnNames.map(column => ({ - category: this.columnToCategoryMapping[column], + category: this.columnToCategoryMap[column], column: column, description: this.getColumnDescription(column) })); @@ -85,7 +85,7 @@ styleTableRow(p_row, p_rowType) { // Check to see what category has been assigned to this row's column, if any - const assignedCategory = this.columnToCategoryMapping[p_row.column]; + const assignedCategory = this.columnToCategoryMap[p_row.column]; return ( null === assignedCategory ) ? "" : this.colorInfo.categoryClasses[assignedCategory]; } diff --git a/cypress/component/column-linking-table.cy.js b/cypress/component/column-linking-table.cy.js index 2b1f5d10..5fc41019 100644 --- a/cypress/component/column-linking-table.cy.js +++ b/cypress/component/column-linking-table.cy.js @@ -63,7 +63,7 @@ describe("The column-linking-table component", () => { return "descriptions help"; }, - columnToCategoryMapping: () => { + columnToCategoryMap: () => { return { diff --git a/cypress/unit/store-getter-getCategoricalOptions.cy.js b/cypress/unit/store-getter-getCategoricalOptions.cy.js index 7669b490..5e2a6405 100644 --- a/cypress/unit/store-getter-getCategoricalOptions.cy.js +++ b/cypress/unit/store-getter-getCategoricalOptions.cy.js @@ -14,7 +14,7 @@ let store = { ] }, - columnToCategoryMapping: { + columnToCategoryMap: { "column1": "category1" } diff --git a/cypress/unit/store-getter-getMappedCategories.cy.js b/cypress/unit/store-getter-getMappedCategories.cy.js index e6500d3f..191bb8bc 100644 --- a/cypress/unit/store-getter-getMappedCategories.cy.js +++ b/cypress/unit/store-getter-getMappedCategories.cy.js @@ -4,7 +4,7 @@ const store = { state: { - columnToCategoryMapping: { + columnToCategoryMap: { column1: "category1", column2: "category2", diff --git a/cypress/unit/store-getter-getMappedColumns.cy.js b/cypress/unit/store-getter-getMappedColumns.cy.js index 40278ffe..bd8ccdf7 100644 --- a/cypress/unit/store-getter-getMappedColumns.cy.js +++ b/cypress/unit/store-getter-getMappedColumns.cy.js @@ -4,7 +4,7 @@ const store = { state: { - columnToCategoryMapping: { + columnToCategoryMap: { "column1": "category1", "column2": "category2", diff --git a/cypress/unit/store-getter-getMissingValues.cy.js b/cypress/unit/store-getter-getMissingValues.cy.js index 443478d0..1de1072e 100644 --- a/cypress/unit/store-getter-getMissingValues.cy.js +++ b/cypress/unit/store-getter-getMissingValues.cy.js @@ -5,7 +5,7 @@ const store = { getters: getters, state: { - columnToCategoryMapping: { + columnToCategoryMap: { column1: "category1", column2: "category1", diff --git a/cypress/unit/store-getter-getUniqueValues.cy.js b/cypress/unit/store-getter-getUniqueValues.cy.js index a2e98d23..5858a360 100644 --- a/cypress/unit/store-getter-getUniqueValues.cy.js +++ b/cypress/unit/store-getter-getUniqueValues.cy.js @@ -10,7 +10,7 @@ describe("getUniqueValues getter", () => { state: { - columnToCategoryMapping: { + columnToCategoryMap: { column1: "Diagnosis", column2: "Age", @@ -50,7 +50,7 @@ describe("getUniqueValues getter", () => { it("Retrieves unique values of a multiple columns", () => { // Setup - store.state.columnToCategoryMapping.column2 = "Diagnosis"; + store.state.columnToCategoryMap.column2 = "Diagnosis"; // Act const diagnosisUniqueValues = getters.getUniqueValues(store.state)("Diagnosis"); diff --git a/cypress/unit/store-getter-isPageAccessible.cy.js b/cypress/unit/store-getter-isPageAccessible.cy.js index 582220b5..6c9519d4 100644 --- a/cypress/unit/store-getter-isPageAccessible.cy.js +++ b/cypress/unit/store-getter-isPageAccessible.cy.js @@ -11,7 +11,7 @@ describe("isPageAccessible", () => { annotationCount: 1, - columnToCategoryMapping: { + columnToCategoryMap: { "column1Name": "Subject ID" }, @@ -47,13 +47,13 @@ describe("isPageAccessible", () => { // Setup let nextPage = "annotation"; - state.columnToCategoryMapping["column2Name"] = "Sex"; + state.columnToCategoryMap["column2Name"] = "Sex"; // Assert expect(getters.isPageAccessible(state)(nextPage)).to.be.true; // Setup - state.columnToCategoryMapping = {}; + state.columnToCategoryMap = {}; // Assert expect(getters.isPageAccessible(state)(nextPage)).to.be.false; diff --git a/cypress/unit/store-mutation-alterColumnCategoryMapping.cy.js b/cypress/unit/store-mutation-alterColumnCategoryMap.cy.js similarity index 87% rename from cypress/unit/store-mutation-alterColumnCategoryMapping.cy.js rename to cypress/unit/store-mutation-alterColumnCategoryMap.cy.js index f5071ce9..81de42aa 100644 --- a/cypress/unit/store-mutation-alterColumnCategoryMapping.cy.js +++ b/cypress/unit/store-mutation-alterColumnCategoryMap.cy.js @@ -16,7 +16,7 @@ describe("alterColumnCategoryMapping", () => { state: { - columnToCategoryMapping : { + columnToCategoryMap : { "column1": "Age", "column2": null, @@ -63,7 +63,7 @@ describe("alterColumnCategoryMapping", () => { mutations.alterColumnCategoryMapping(store.state, { category: "Sex", column: "column3" }); // Assert - expect(store.state.columnToCategoryMapping.column3).to.equal(null); + expect(store.state.columnToCategoryMap.column3).to.equal(null); }); it("Changes the mapping of column to category if they're not already mapped", () => { @@ -72,14 +72,14 @@ describe("alterColumnCategoryMapping", () => { mutations.alterColumnCategoryMapping(store.state, { category: "someCategory", column: "column1" }); // Assert - expect(store.state.columnToCategoryMapping.column1).to.equal("someCategory"); + expect(store.state.columnToCategoryMap.column1).to.equal("someCategory"); expect(store.state.dataDictionary.annotated.column1).to.deep.equal(reinitializedAnnotatedColumn); // Act mutations.alterColumnCategoryMapping(store.state, { category: "someCategory", column: "column2" }); // Assert - expect(store.state.columnToCategoryMapping.column2).to.equal("someCategory"); + expect(store.state.columnToCategoryMap.column2).to.equal("someCategory"); expect(store.state.dataDictionary.annotated.column2).to.deep.equal(reinitializedAnnotatedColumn); }); }); diff --git a/cypress/unit/store-mutation-initializeColumnToCategoryMap.cy.js b/cypress/unit/store-mutation-initializeColumnToCategoryMap.cy.js index 2fe7f9ee..d0b7211c 100644 --- a/cypress/unit/store-mutation-initializeColumnToCategoryMap.cy.js +++ b/cypress/unit/store-mutation-initializeColumnToCategoryMap.cy.js @@ -9,7 +9,7 @@ describe("initializeColumnToCategoryMap", () => { // Setup state = { - columnToCategoryMapping: {}, + columnToCategoryMap: {}, dataTable: [ @@ -29,19 +29,19 @@ describe("initializeColumnToCategoryMap", () => { mutations.initializeColumnToCategoryMap(state, ["column1Name", "column2Name"]); // Assert - expect(state.columnToCategoryMapping).to.eql({"column1Name": null, "column2Name": null}); + expect(state.columnToCategoryMap).to.eql({"column1Name": null, "column2Name": null}); }); it("Make sure that old columns are removed from newly updated map", () => { // Setup - state.columnToCategoryMapping["oldColumn1"] = "category1"; - state.columnToCategoryMapping["oldColumn2"] = "category2"; + state.columnToCategoryMap["oldColumn1"] = "category1"; + state.columnToCategoryMap["oldColumn2"] = "category2"; // Act mutations.initializeColumnToCategoryMap(state, ["column1Name", "column2Name"]); // Assert - expect(state.columnToCategoryMapping).to.eql({"column1Name": null, "column2Name": null}); + expect(state.columnToCategoryMap).to.eql({"column1Name": null, "column2Name": null}); }); }); \ No newline at end of file diff --git a/store/index-old.js b/store/index-old.js deleted file mode 100644 index 43a203c7..00000000 --- a/store/index-old.js +++ /dev/null @@ -1,848 +0,0 @@ - -// Facilitate Vue reactivity via 'Vue.set' and 'Vue.delete' -import Vue from "vue"; - -// Root state - Stores state data -export const state = () => ({ - - // Page-related data - - currentPage: "home", - - columns: [], - - pageData: { - - home: { - - accessible: true, - fullName: "Home", - location: "/", - pageName: "index" - }, - - categorization: { - - accessible: false, - fullName: "Categorization", - location: "categorization", - pageName: "categorization" - }, - - annotation: { - - accessible: false, - fullName: "Annotation", - location: "annotation", - pageName: "annotation" - }, - - download: { - - accessible: false, - fullName: "Download", - location: "download", - pageName: "download" - } - }, - - pageOrder: [ - - "home", - "categorization", - "annotation", - "download" - ], - - // Data table (i.e. participants.tsv file) - - dataTable: { - - // List of data table's columns - columns: [], - - // File name of the data table tsv file - filename: "", - - // File type of the original data table file - fileType: "", - - // Participants.tsv file data - // For format see 'convertTsvLinesToTableData' in index.js - original: null, - - // Version of table data for annotation page - annotated: null - }, - - // Data dictionary (i.e. participants.json) - - dataDictionary: { - - // File names of the data dictionary json file - filename: "", - - // File type of the original data dictionary file - fileType: "", - - // Original data dictionary file data - original: null, - - // User-amended data dictionary file data - amended: {} - }, - - // Stores table data in format ready for Bootstrap table - // This is an array of objects. See the mutation - // 'setupColumnToCategoryMap' for exact format - columnToCategoryMap: { - - }, - - // Hardcoded list of categories used on the categorization page - // and possibly elsewhere in the tool - categories: [], - - // This is a computed direct map between current categories and CSS classes - // See getter 'categoryClasses' - categoryClasses: null, - - // The following fields are only accessed by store methods - - // Maps our categories in 'categories' to colors in 'toolColorPalette' - // (Final class names pending). This way colors can be swappable and - // rearrangeable for categories. - categoryToColorMap: {}, - - // Map of the tools colors to CSS classes containing color (and possibly - // other style) values. More palettes could be defined here, either out of - // user preference or if we ever decided to code a light/dark mode feature - toolColorPalette: { - - color1: "category-style-0", - color2: "category-style-1", - color3: "category-style-2", - color4: "category-style-3", - color5: "category-style-4", - colorDefault: "category-style-default" - }, - - // Annotation page-specific fields - - // The string label applied to values designated as "missing values" when the data are annotated. - missingValueLabel: "missing value", - - // Counter for the number of annotations that have been done - annotationCount: 0, - - // Keeps track of textual- and component-related information for the annotation of each category - // See action nuxtServerInit() for initialization code - annotationDetails: [], - - // Stores a list of (potentially) missing values for each column. This is determined in the missing-values - // components on the annotation page, and then amended by the user as they see fit - missingColumnValues: {}, - - // Keeps track of named assessment tool groups and their associated tools (e.g. columns in the data table) - toolGroups: {} -}); - -// Actions - Call mutations to change state data in order to maintain trace of -// what component changed state data and when -export const actions = { - - // Initializations - - initializeAnnotationDetails(p_context, p_details) { - - p_context.commit("setupAnnotationDetails", p_details); - }, - - initializeCategories(p_context, p_categories) { - - p_context.commit("setupCategories", p_categories); - }, - - nuxtServerInit({ commit }) { - - // This function is called on Nuxt server startup - - // 0. This list is default but we can swap out and reinitialize category - // data structures by calling store action 'initializeCategories' with - // a new list of categories - const categories = [ - - "Subject ID", - "Age", - "Sex", - "Diagnosis", - "Assessment Tool" - ]; - - // 0. This annotation information is default but we can swap out and reinitialize - // annotation data structures by calling 'initializeAnnotationDetails' with a new - // object containing annotation information for each category - const annotationDetails = [ - - { - id: 0, - category: "Age", - dataType: "continuous", - explanation: "This is an explanation for how to annotate age.", - options: {}, - specializedComponent: "annot-age-values" - }, - { - id: 1, - category: "Sex", - dataType: "categorical", - explanation: "This is an explanation for how to annotate sex.", - options: ["male", "female", "other"], - specializedComponent: "annot-discrete-choices" - }, - { - id: 2, - category: "Diagnosis", - dataType: "string", - explanation: "This is an explanation for how to annotate diagnosis.", - options: {}, - specializedComponent: "annot-vocabulary" - } - - // NOTE: Assessment tools are now only added to annotationDetails when grouped - ]; - - // 1. Setup category-related data structures based on the given categories - commit("setupCategories", categories); - - // 2. Setup annotation-related data structures based on the given categories\ - commit("setupAnnotationDetails", annotationDetails); - - // 3. Set the current page as the landing page - commit("setCurrentPage", "home"); - }, - - // Tool navigation - - // Landing page actions - - saveDataDictionary(p_context, p_newFileData) { - - // 1. Attempt to transform the string data into JSON if valid data given - if ( "json" === p_newFileData.fileType ) { - - if ( null !== p_newFileData.data ) - p_newFileData.formattedData = JSON.parse(p_newFileData.data); - } - - // 2. Save either an empty object or the JSON dict to state data - p_context.commit("setDataDictionary", p_newFileData); - }, - - saveDataTable(p_context, p_newFileData) { - - // 1. Attempt to convert the tsv lines into a dict for each line if valid data given - if ( "tsv" === p_newFileData.fileType ) { - - // 1. Save new table data, formatted for the Vue table element - if ( null !== p_newFileData.data ) - p_newFileData.formattedData = convertTsvLinesToTableData(p_newFileData.data); - - // 2. Save a list of the columns of this new table data - if ( p_newFileData.formattedData.length > 0 ) - p_newFileData.columns = Object.keys(p_newFileData.formattedData[0]); - } - - // 2. Save either an empty array or array of tsv dictionaries to state data - p_context.commit("setDataTable", p_newFileData); - }, - - // Categorization page actions - - alterColumnCategoryRelation(p_context, p_relationData) { - - // Category not set or categor is not the same as input category - if ( null === p_context.state.columnToCategoryMap[p_relationData.column] || - p_relationData.category !== p_context.state.columnToCategoryMap[p_relationData.column] ) { - - p_context.commit("addColumnCategorization", p_relationData.column, p_relationData.category); - } - // Else, category is the same as input category - else { - - p_context.commit("removeColumnCategorization", p_relationData.column); - } - }, - - createToolGroup(p_context, p_toolGroupData) { - - p_context.commit("saveToolGroup", p_toolGroupData); - }, - - modifyToolGroup(p_context, p_toolGroupData) { - - p_context.commit("changeToolGroup", p_toolGroupData); - }, - - removeToolGroup(p_context, p_toolGroupData) { - - p_context.commit("deleteToolGroup", p_toolGroupData); - }, - - // Annotation page actions - - revertColumnToOriginal(p_context, p_column) { - - // NOTE: Reverts a column of annotated data to its original set of values - // Currently used when a user decouples a column from a category, - // but could also have use if an 'Undo Annotation' button is implemented - // on the annotation page. - - // Gather original table column values in row-order - const originalValues = []; - for ( let index = 0; index < p_context.state.dataTable.original.length; index++ ){ - originalValues.push(p_context.state.dataTable.original[index][p_column]); - } - - p_context.commit("changeColumnValues", { - - column: p_column, - tableToChange: p_context.state.dataTable.annotated, - newValues: originalValues - }); - }, - - saveMissingColumnValues(p_context, p_missingColumnValues) { - - p_context.commit("setMissingColumnValues", p_missingColumnValues); - } -}; - -// Mutations - Change state data, as called by Actions -export const mutations = { - - // Initialization - - createColumnToCategoryMap(p_state) { - - // Column to category map lists all columns as keys with default value of null - p_state.columnToCategoryMap = - Object.fromEntries(p_state.dataTable.columns.map((column) => [column, null])); - }, - - setupAnnotationDetails(p_state, p_details) { - - p_state.annotationDetails = p_details; - }, - - setupCategories(p_state, p_categories) { - - // 1. Save the given category list - p_state.categories = p_categories; - - // 2. Get color keys from tool color palette - const colorKeys = Object.keys(p_state.toolColorPalette); - - // 3. Create the category to color map - let assignedCategories = 0; - for ( let index = 0; index < p_categories.length && - index < colorKeys.length; index++ ) { - - // A. Stop when the default color key has been reached - if ( "colorDefault" === colorKeys[index] ) - break; - - // B. Map this category to color key - p_state.categoryToColorMap[p_categories[index]] = colorKeys[index]; - - // C. Keep track of how many categories have been assigned color keys - assignedCategories += 1; - } - // D. Issue warning if there are not enough color keys for the given category set - if ( p_categories.length > assignedCategories ) { - console.log("WARNING: Not all categories have been assigned color keys!"); - } - - // 4. Set up the category to CSS class map - - // A. Create a map between category names and color classes - const mapArray = []; - for ( let index = 0; index < p_state.categories.length; index++ ) { - - const category = p_state.categories[index]; - const colorID = p_state.categoryToColorMap[category]; - const colorClass = p_state.toolColorPalette[colorID]; - - mapArray.push([category, colorClass]); - } - - // B. Save the new category to class map - p_state.categoryClasses = Object.fromEntries(mapArray); - }, - - // Tool navigation - - setCurrentPage(p_state, p_pageName) { - - // Set the current page for the layout navbar - p_state.currentPage = p_pageName; - }, - - // Landing page - - setDataDictionary(p_state, p_newFileData) { - - // 1. Save the new data dictionary to state data - p_state.dataDictionary.original = p_newFileData.formattedData; - - // 2. Save the file name of the data dictionary json file - p_state.dataDictionary.filename = p_newFileData.filename; - - // 3. Save the file type of the new data dictionary - p_state.dataDictionary.fileType = p_newFileData.fileType; - }, - - setDataTable(p_state, p_newFileData) { - - // 1. Save the new tsv row dictionary list to state data - p_state.dataTable.original = p_newFileData.formattedData; - - // 2. Save the file name of the tsv file - p_state.dataTable.filename = p_newFileData.filename; - - // 3. Save the file type of the new data table - p_state.dataTable.fileType = p_newFileData.fileType; - - // 4. Save a list of the columns of this data table - p_state.dataTable.columns = p_newFileData.columns; - - // 5. Make the annotated data a copy of the original - p_state.dataTable.annotated = structuredClone(p_state.dataTable.original); - }, - - // Categorization page - - addColumnCategorization(p_state, p_column, p_category) { - - // Save the categorization-column link in the annotated table - p_state.columnToCategoryMap[p_column] = p_category; - }, - - changeToolGroup(p_state, p_toolGroupData) { - - // 1. Remove the old group from the tool group object - Vue.delete(p_state.toolGroups, p_toolGroupData.previousName); - - // 2. Add the new group to the tool group object - Vue.set(p_state.toolGroups, p_toolGroupData.name, p_toolGroupData.tools); - - // 3. Alter the annotation details to reflect this change - const detailIndex = p_state.annotationDetails.findIndex( - detail => p_toolGroupData.previousName === detail.groupName); - p_state.annotationDetails[detailIndex].groupName = p_toolGroupData.name; - p_state.annotationDetails[detailIndex].tools = p_toolGroupData.tools; - }, - - deleteToolGroup(p_state, p_toolGroupData) { - - // 1. Remove this tool group from the list - Vue.delete(p_state.toolGroups, p_toolGroupData.name); - - // 2. Remove the toolgroup from the annotation details - const groupIndex = p_state.annotationDetails.findIndex(detail => - p_toolGroupData.name === detail?.groupName); - p_state.annotationDetails.splice(groupIndex, 1); - }, - - removeColumnCategorization(p_state, p_column) { - - // Disassociate the column with this category it was linked to - p_state.columnToCategoryMap[p_column] = null; - }, - - deleteToolFromGroup(p_state, p_data) { - - // Remove the tool from the given tool group - p_state.toolGroups[p_data.group].splice( - p_state.toolGroups[p_data.group].findIndex(element => element === p_data.tool), 1); - }, - - saveToolGroup(p_state, p_toolGroupData) { - - // 1. Save this group to the tool group map - // p_state.set(p_state.toolGroups, p_toolGroupData.name, p_toolGroupData.tools); - Vue.set(p_state.toolGroups, p_toolGroupData.name, [...p_toolGroupData.tools]); - - // 2. Add a new assessment tool item to the annotation details list for this tool group - p_state.annotationDetails.push({ - - id: p_state.annotationDetails.length, - category: "Assessment Tool", - dataType: "string", - explanation: "This is an explanation for how to annotate assessments.", - groupName: p_toolGroupData.name, - options: {}, - specializedComponent: "annot-tool-group", - tools: p_state.toolGroups[p_toolGroupData.name] - }); - }, - - // Annotation page - - changeColumnValues(p_state, p_changeInfo) { - - // Change the values in the given table's column - for ( let index = 0; index < p_changeInfo.tableToChange.length; index++ ) { - - p_changeInfo.tableToChange[index][p_changeInfo.column] = p_changeInfo.newValues[index]; - } - }, - - setAnnotatedDataTable(p_state, p_newTable) { - - p_state.dataTable.annotated = p_newTable; - }, - - setMissingColumnValues(p_state, p_missingColumnValues) { - - // This method merges incoming updated missingColumnValues records with the missingColumnValues - // object in the store. Because the incoming changes can be incomplete (e.g. only contain updated - // records of a single column), we cannot just overwrite the store object with them. - // However, because of how reactivity in Vue works, we can also not simply overwrite the affected columns - // (i.e. keys) in the object, because that will break reactivity. - // The below pattern via assign sovles this problem. See here: https://v2.vuejs.org/v2/guide/reactivity.html - - const missingColumnKey = Object.keys(p_missingColumnValues)[0]; - if ( 0 === p_missingColumnValues[missingColumnKey].length ) { - Vue.delete(p_state.missingColumnValues, missingColumnKey); - } else { - p_state.missingColumnValues = Object.assign({}, p_state.missingColumnValues, p_missingColumnValues); - } - } -}; - -// Getters - Give access to state data -export const getters = { - - columnDescription: (p_state) => (p_column) => { - - // 0. If we do not have a data dictionary then the column description is undefined (e.g. 'null') - let columnDescription = null; - - // 1. Find the description for this column in the data dictionary - if ( null !== p_state.dataDictionary.original && Object.keys(p_state.dataDictionary.original).includes(p_column) ) { - - // A. Get dictionary's description string for this column - const dictionaryDescStr = Object.keys(p_state.dataDictionary.original[p_column]).find( - (key) => key.toLowerCase() === "description"); - - // B. Get the column description if the description key was found - if ( dictionaryDescStr ) { - columnDescription = p_state.dataDictionary.original[p_column][dictionaryDescStr]; - } - } - - return columnDescription; - }, - - columns(p_state, p_getters) { - - return p_state.dataTable.columns.map(column => ({ - - name: column, - description: p_getters.columnDescription(column) - })); - }, - - getColumnsOfCategory: (p_state) => (p_category) => { - - // If it exists in the map, retrieve the column(s) assigned this category - let columns = []; - - for ( const column in p_state.columnToCategoryMap ) { - if ( p_category === p_state.columnToCategoryMap[column] ) { - columns.push(column); - } - } - - return columns; - }, - - getGroupOfTool: (p_state) => (p_tool) => { - - // Look for the group of the given tool - let toolGroup = null; - for ( const groupName in p_state.toolGroups ) { - if ( p_state.toolGroups[groupName].includes(p_tool) ) { - toolGroup = groupName; - } - } - - return toolGroup; - }, - - getOriginalColumnValue: (p_state) => (p_subjectID, p_column) => { - - for ( let index = 0; index < p_state.dataTable.original.length; index++ ) { - - if ( p_subjectID === p_state.dataTable.original[index]["participant_id"] ) { - - return p_state.dataTable.original[index][p_column]; - } - } - - return null; - }, - - isColumnLinkedToCategory: (p_state) => (p_matchData) => { - - // Check to see if the given column has been linked to the given category - return ( p_matchData.category === p_state.columnToCategoryMap[p_matchData.column] ); - }, - - isDataAnnotated(p_state) { - - // 1. Check to see if the annotated data table is different from the original data table - const tablesAreEqual = p_state.dataTable.original.every((originalTableRow, index) => { - - let allValuesEqual = true; - - // A. Tables are different if column sizes for rows are different - if ( Object.keys(originalTableRow).length !== Object.keys(p_state.dataTable.annotated[index]).length ) { - allValuesEqual = false; - } - // B. Or, check to see if values in the respective rows are different - else { - - for ( const column in originalTableRow ) { - if ( originalTableRow[column] !== p_state.dataTable.annotated[index][column] ) { - allValuesEqual = false; - break; - } - } - } - - return allValuesEqual; - }); - - // Annotation has not occurred if both tables are equal - return !tablesAreEqual; - }, - - isDataDictionaryLoaded(p_state) { - - return ( null !== p_state.dataDictionary.original ); - }, - - isDataTableLoaded(p_state) { - - return ( null !== p_state.dataTable.original ); - }, - - isMissingValue: (p_state) => (p_column, p_value) => { - - // Checks if a column-value combination is stored in the missingColumnValues object - // and returns true if it is, false otherwise - // if no records are stored for the entire p_column, then also returns false - - if ( !Object.keys(p_state.missingColumnValues).includes(p_column) ) { - - return false; - } - - return ( p_state.missingColumnValues[p_column].includes(p_value) ); - }, - - isToolGrouped: (p_state) => (p_column) => { - - let foundTool = false; - - // Look for tool name in the saved tool groups - for ( const groupName in p_state.toolGroups ) { - - if ( p_state.toolGroups[groupName].includes(p_column) ) { - foundTool = true; - break; - } - } - - return foundTool; - }, - - getMissingValuesColumn: (p_state) => (p_column) => { - - // For a given column name returns the array of missing values the state knows about - // or returns null if no missing values are stored for this column name - - if ( !Object.keys(p_state.missingColumnValues).includes(p_column) ) { - return null; - } else { - return p_state.missingColumnValues[p_column]; - } - }, - - nextPage(p_state) { - - let nextPage = ""; - - switch ( p_state.currentPage ) { - - case "home": - nextPage = "categorization"; - break; - case "categorization": - nextPage = "annotation"; - break; - case "annotation": - nextPage = "download"; - break; - } - - return nextPage; - }, - - pageAccessible: (p_state, p_getters) => (p_pageName) => { - - let pageAccessible = false; - - switch ( p_pageName ) { - - case "home": - - // Landing page is always accessible - pageAccessible = true; - break; - - case "categorization": - - // Categorization page is accessible if a data table has been uploaded - pageAccessible = p_getters.isDataTableLoaded; - - break; - - case "annotation": { - - // 1. Determine if at least one column has been linked to a category - const linkCount = Object.values(p_state.columnToCategoryMap).filter( - category => ( null !== category )).length; - const categorizationStatus = linkCount > 0; - - // 2. Determine if all columns assigned the 'Assessment Tool' category have been grouped - const assessmentToolColumns = []; - for ( const column in p_state.columnToCategoryMap ) { - if ( "Assessment Tool" === p_state.columnToCategoryMap[column] ) { - assessmentToolColumns.push(column); - } - } - - // 3. Make sure all assessment tool columns are grouped - for ( const toolGroup in p_state.toolGroups ) { - for ( const tool of p_state.toolGroups[toolGroup] ) { - const columnIndex = assessmentToolColumns.indexOf(tool); - assessmentToolColumns.splice(columnIndex, 1); - } - } - const toolGroupingStatus = ( 0 === assessmentToolColumns.length ); - - // 4. Make sure one (and only one) column has been categorized as 'Subject ID' - let subjectIDFound = 0; - for ( const column in p_state.columnToCategoryMap ) { - if ( "Subject ID" === p_state.columnToCategoryMap[column] ) { - subjectIDFound += 1; - } - } - const singleSubjectIDColumn = ( 1 === subjectIDFound ); - - // Annotation page is only accessible if at least one column has - // been categorized and all assessment tools have been grouped - // and if one (and only one) column has been categorized as 'Subject ID' - pageAccessible = categorizationStatus && toolGroupingStatus && singleSubjectIDColumn; - - break; - } - - case "download": - - pageAccessible = p_state.annotationCount > 1; - - break; - } - - return pageAccessible; - }, - - valueDescription: (p_state) => (p_column, p_value) => { - - // 0. If we do not have a data dictionary then the value description is undefined (e.g. "") - let valueDescription = ""; - - // 1. Find the description for this column's value in the data dictionary - if ( null !== p_state.dataDictionary.original && Object.keys(p_state.dataDictionary.original).includes(p_column) ) { - - // A. Get dictionary's levels string for this column - const dictionaryLevelsStr = Object.keys(p_state.dataDictionary.original[p_column]).find((key) => key.toLowerCase() === "levels"); - - // B. Attempt to get the value string in this 'levels' object - if ( dictionaryLevelsStr ) { - - // I. Get the dictionary's value string for this column's value - const dictionaryValueStr = Object.keys(p_state.dataDictionary.original[p_column][dictionaryLevelsStr]).find( - (key) => key.toLowerCase() === p_value.toLowerCase()); - - // II. Get the value description - if ( dictionaryValueStr ) { - valueDescription = p_state.dataDictionary.original[p_column][dictionaryLevelsStr][dictionaryValueStr]; - } - } - } - - return valueDescription; - } -}; - - -// Action helpers -function convertTsvLinesToTableData(p_tsvLines){ - - // 0. Data structure for table will be stored here - const tsvRowDictArray = []; - - // 1. First tsv line contains column headers - const columnHeaders = p_tsvLines[0]; - - // 2. Create dictionaries for each tsv row keyed on the column headers - for ( let index = 1; index < p_tsvLines.length; index++ ){ - - const tsvRowDict = {}; - - // A. Loop through the tsv row, matching entries with the tsv column headers - for ( let index2 = 0; index2 < columnHeaders.length; index2++ ) { - - // Skip blank lines - if ( "" === p_tsvLines[index].join().trim() ) - continue; - - // I. Potential warning in case file is malformed. - // NOTE: Graceful handling of this will be required - if ( p_tsvLines[index].length !== columnHeaders.length ){ - console.log("WARNING: tsv row " + parseInt(index) + " has different size than tsv header."); - console.log("Row size: " + parseInt(p_tsvLines[index].length)); - console.log("Row: '" + p_tsvLines[index] + "'"); - console.log("Header fields: " + columnHeaders.length); - } - - // II. Save the field for this row, keyed by the current column header - tsvRowDict[columnHeaders[index2]] = p_tsvLines[index][index2]; - } - - // B. Save the row dictionary - // NOTE: Conditional here is to account for the possibility of a blank line - // among the tsv lines input to this function. We may want to readdress this - // via the Papa parse file input method or just leave as is - if ( Object.keys(tsvRowDict).length > 0 ) { - tsvRowDictArray.push(tsvRowDict); - } - } - - return tsvRowDictArray; -} \ No newline at end of file diff --git a/store/index.js b/store/index.js index 116425eb..c2d4c853 100644 --- a/store/index.js +++ b/store/index.js @@ -71,7 +71,7 @@ export const state = () => ({ } }, - columnToCategoryMapping: {}, + columnToCategoryMap: {}, currentPage: "home", @@ -141,7 +141,7 @@ export const getters = { // Return the options for this column listed in the current (hardcoded) // options for each categorical data-based category - return p_state.categoricalOptions[p_state.columnToCategoryMapping[p_column]] ?? []; + return p_state.categoricalOptions[p_state.columnToCategoryMap[p_column]] ?? []; }, getCategoryNames (p_state) { @@ -249,7 +249,7 @@ export const getters = { getMappedCategories: (p_state) => (p_categorySkipList=[]) => { // 1. Remove unmapped (null) columns and skipped categories - const currentCategories = Object.values(p_state.columnToCategoryMapping) + const currentCategories = Object.values(p_state.columnToCategoryMap) .filter(category => null !== category && !p_categorySkipList.includes(category)); // 2. Create a set of the unique mapped categories @@ -262,9 +262,9 @@ export const getters = { getMappedColumns: (p_state) => (p_category) => { const mappedColumns = []; - for ( const column in p_state.columnToCategoryMapping ) { + for ( const column in p_state.columnToCategoryMap ) { - if ( p_category === p_state.columnToCategoryMapping[column] ) { + if ( p_category === p_state.columnToCategoryMap[column] ) { mappedColumns.push(column); } @@ -277,9 +277,9 @@ export const getters = { // 1. Retrieve all columns linked with the given category const mappedColumns = []; - for ( const columnName in p_state.columnToCategoryMapping ) { + for ( const columnName in p_state.columnToCategoryMap ) { - if ( p_category === p_state.columnToCategoryMapping[columnName] ) { + if ( p_category === p_state.columnToCategoryMap[columnName] ) { mappedColumns.push(columnName); } @@ -334,10 +334,10 @@ export const getters = { // 1. Construct an object containing a list of unique values for each column const uniqueValues = {}; - for ( const columnName in p_state.columnToCategoryMapping ) { + for ( const columnName in p_state.columnToCategoryMap ) { // A. Create a new list for values for each column linked to the given category - if ( p_category === p_state.columnToCategoryMapping[columnName] ) { + if ( p_category === p_state.columnToCategoryMap[columnName] ) { // I. Save unique values for each column uniqueValues[columnName] = new Set(); @@ -400,12 +400,12 @@ export const getters = { case "annotation": { // 1. Make sure one (and only one) column has been categorized as 'Subject ID' - const singleSubjectIDColumn = ( 1 === Object.values(p_state.columnToCategoryMapping) + const singleSubjectIDColumn = ( 1 === Object.values(p_state.columnToCategoryMap) .filter(category => "Subject ID" === category) .length ); // 2. Make sure at least one other category other than 'Subject ID' has been linked to a column - const notOnlySubjectIDCategorized = ( Object.values(p_state.columnToCategoryMapping) + const notOnlySubjectIDCategorized = ( Object.values(p_state.columnToCategoryMap) .filter(category => "Subject ID" !== category && null !== category) .length >= 1 ); @@ -460,15 +460,15 @@ export const mutations = { */ alterColumnCategoryMapping(p_state, { category, column }) { - if ( category === p_state.columnToCategoryMapping[column] ) { + if ( category === p_state.columnToCategoryMap[column] ) { // 1. Unlink the column from the category - p_state.columnToCategoryMapping[column] = null; + p_state.columnToCategoryMap[column] = null; } else { // 1. Link the column to the category - p_state.columnToCategoryMapping[column] = category; + p_state.columnToCategoryMap[column] = category; } // 2. Re-initialize the annotated data dictionary column @@ -499,7 +499,7 @@ export const mutations = { initializeColumnToCategoryMap(p_state, p_columns) { // Column to category map lists all columns as keys with default value of null - p_state.columnToCategoryMapping = + p_state.columnToCategoryMap = Object.fromEntries(p_columns.map((column) => [column, null])); },