diff --git a/frontend/src/components/CsetComparisonPage.jsx b/frontend/src/components/CsetComparisonPage.jsx index f82d91dc..f8c061ae 100644 --- a/frontend/src/components/CsetComparisonPage.jsx +++ b/frontend/src/components/CsetComparisonPage.jsx @@ -712,8 +712,8 @@ function getCollapseIconAndName( let direction; if ( (graphOptions.expandAll && - graphOptions.specificPaths[row.rowPath] !== 'collapse' - ) || graphOptions.specificPaths[row.rowPath] === 'expand' + graphOptions.expandStateByPath[row.rowPath] !== 'collapse' + ) || graphOptions.expandStateByPath[row.rowPath] === 'expand' ) { Component = RemoveCircleOutline; direction = 'collapse'; diff --git a/frontend/src/state/AppState.jsx b/frontend/src/state/AppState.jsx index 0a5cb742..675f90ce 100644 --- a/frontend/src/state/AppState.jsx +++ b/frontend/src/state/AppState.jsx @@ -180,11 +180,11 @@ export function graphOptionsReducer(state, action) { if (!validValues.includes(action.direction)) { console.error(`Invalid direction for TOGGLE_NODE_EXPANDED: ${action.direction}`); } - let specificPaths = {...graphOptions.specificPaths}; - let current = specificPaths[rowPath]; + let expandStateByPath = {...graphOptions.expandStateByPath}; + let current = expandStateByPath[rowPath]; if (typeof(current) === 'undefined') { // if no current expand/collapse for path, set it - specificPaths[rowPath] = action.direction; + expandStateByPath[rowPath] = action.direction; } else { // path has current state -- should be opposite of action.direction // so just delete it (to unexpand/uncollapse) @@ -194,9 +194,9 @@ export function graphOptionsReducer(state, action) { if (current === action.direction) { console.error(`Trying to ${action.direction} ${rowPath} but is already`); } - delete specificPaths[rowPath]; + delete expandStateByPath[rowPath]; } - graphOptions = { ...graphOptions, specificPaths}; + graphOptions = { ...graphOptions, expandStateByPath}; break; } case 'TOGGLE_OPTION': @@ -206,7 +206,7 @@ export function graphOptionsReducer(state, action) { break; case 'TOGGLE_EXPAND_ALL': graphOptions = {...graphOptions, expandAll:!graphOptions.expandAll}; - graphOptions.specificPaths = {}; + graphOptions.expandStateByPath = {}; // just start over when expandAll flips // could have two sets of specificPaths, one for expandAll, one for not break; @@ -231,10 +231,19 @@ if (process.env.NODE_ENV !== 'production') { // window.appStateW = {}; // playwright complaining that window isn't defined let appStateW = {}; +/* makeProvider() +Makes provider to manage both a regular reducer and a storage provider. +I think the idea was to put update logic into reducers and try to have storage providers. +just emulate localStorage (whether for localStorage, sessionStorage, or querystring). + +Returns: + Provider, useReducerWithStorage + +Side effects: + - Updates global `resetFuncs` obj w/ the `resetFunc` for the given `stateName`. + +*/ function makeProvider({stateName, reducer, initialSettings, storageProviderGetter, jsonify=false, }) { - // makes provider to manage both a regular reducer and a storage provider - // I think the idea was to put update logic into reducers and try to have storage providers - // just emulate localStorage (whether for localStorage, sessionStorage, or querystring) /* const regularReducer = (state, action) => { const newState = reducer(state, action); @@ -273,9 +282,8 @@ function makeProvider({stateName, reducer, initialSettings, storageProviderGette storageProvider.setItem(stateName, newState); }, [stateName, state]); */ - - const resetFunc = () => dispatch({type: 'reset', resetValue: initialSettings}); - resetFuncs[stateName] = resetFunc; + + resetFuncs[stateName] = () => dispatch({type: 'reset', resetValue: initialSettings}); return ( // diff --git a/frontend/src/state/GraphState.jsx b/frontend/src/state/GraphState.jsx index eace63bb..04c742b8 100644 --- a/frontend/src/state/GraphState.jsx +++ b/frontend/src/state/GraphState.jsx @@ -80,16 +80,21 @@ export class GraphContainer { } getDisplayedRows(graphOptions) { - /* + /* Terms + STC: showThoughCollapsed + HTE: hideThoughExpanded + SNC: specificPathsCollapsed (N=Nodes/Paths) + SNE: specificPathsExpanded (N=Nodes/Paths) + New algorithm - Special classes Action Default + Special classes Action Default concepts expandAll false TODO: make the expandAll default depend on number of concepts rather than being always false standard nothing classification nothing - specificPaths (expanded/collapsed) // rename -- chosenPaths? + expandStateByPath (expanded/collapsed) addedCids showThoughCollapsed true definitionConcepts showThoughCollapsed false @@ -201,7 +206,7 @@ export class GraphContainer { if (graphOptions.expandAll) { // 3.... no need to expand STC, because nothing collapsed except SNC } else { - // 4. Hide non-root rows; just hide depth > 0; + // 4. Hide non-root rows (depth > 0) for (let row of allRows) { if (row.depth > 0) { row.display.hideReasons.nonRoot = true; @@ -231,32 +236,32 @@ export class GraphContainer { this.insertShowThoughCollapsed([nodeIdToShow], shown, nodeRows); }); */ - // 5a. Expand children of specificPaths: expand, but only for displayed rows + // 5a. Expand children of expandStateByPath: expand, but only for displayed rows const hideThoughExpanded = new StringSet(); } - // Expand and collapse children based on user having clicked +/- on row + // expandStateByPath: Expand and collapse children based on user having clicked +/- on row allRows.forEach((row, rowIdx) => { if (row.display.result === 'hide') return; - if (graphOptions.specificPaths[row.rowPath]) { - this.rowDisplay(rowIdx, graphOptions.specificPaths[row.rowPath], 'specific', allRows) + let expandState = graphOptions.expandStateByPath[row.rowPath]; + if (expandState) { + this.rowDisplay(rowIdx, expandState, 'specific', allRows); } }); // hide all HTE (non-standard, zero pt, expansion only) for (let type in graphOptions.specialConceptTreatment) { if (type === 'allButFirstOccurrence') continue; // handle this differently - if (get(this, ['graphDisplayConfig', type, 'specialTreatmentRule']) - === 'hide though expanded' && - graphOptions.specialConceptTreatment[type]) { + let optionIsHTE = get(this, ['graphDisplayConfig', type, 'specialTreatmentRule']) === 'hide though expanded'; + if(optionIsHTE && graphOptions.specialConceptTreatment[type]) { // gather all the hideThoughExpanded ids this.gd.specialConcepts[type].forEach(id => { const rowsToHide = allRowsById.get(id) || []; for (const rowToHide of rowsToHide) { const rowToHideIdx = rowToHide.allRowsIdx; - this.rowDisplay(rowToHideIdx, graphOptions.specificPaths[rowToHide.rowPath], type, allRows) + this.rowDisplay(rowToHideIdx, graphOptions.expandStateByPath[rowToHide.rowPath], type, allRows) } }) } @@ -286,6 +291,7 @@ export class GraphContainer { return displayedRows; // return this.getDisplayedRowsOLD(graphOptions); } + insertShowThoughCollapsed(path, shown, nodeRows) { // moved out of getDisplayedRows where shown was a closure var // path starts with the nodeIdToShow and recurses up, prepending parents @@ -334,9 +340,23 @@ export class GraphContainer { }); shown.add(nodeIdToShow); }; + + /* rowDisplay() + + Sets properties regarding whether or not a row should be displayed, and why. + + Nomenclature: + specific: Means that a row is being shown or hidden not because of a general rules, but a special circumstance + particular to that and only that row, typically because of a user action. + + * :param rowIdx (Int): index of row in allRows + * :param showHide (String): Factor of 'expand' or 'collapse' + * :param reason (String): Factor variable; reason for showing or hiding + * :param allRows (Array[Row]): list of all rows + * */ rowDisplay(rowIdx, showHide, reason, allRows) { - // this.rowDisplay(row, graphOptions.specificPaths[row.rowPath], 'specific') - // this.rowDisplay(rowToHide, graphOptions.specificPaths[rowToHide.rowPath], type) + // this.rowDisplay(row, graphOptions.expandStateByPath[row.rowPath], 'specific') + // this.rowDisplay(rowToHide, graphOptions.expandStateByPath[rowToHide.rowPath], type) // TODO: don't hide if it has children that should be shown if (reason === 'specific') { if (showHide === 'expand') { @@ -362,6 +382,7 @@ export class GraphContainer { } } } + getDescendantRows(parentRowIdx, allRows, howDeep=Infinity) { // sort of a fragile way to do it, but will get all rows deeper // than current row until the next row of the same depth @@ -379,30 +400,36 @@ export class GraphContainer { } return rows; } + + /* setupAllRows + * Nomenclature: Rows, nodes, &concepts are all the same thing; just using term that fits purpose at the moment. */ setupAllRows(rootNodes) { let allRows = []; let allRowsById = new StringKeyMap(); // start by getting lists of rowIdx by concept_id - // rows and nodes and concepts are all the same thing, I just use the term - // that fits the purpose at the moment const addRows = (nodeIds, parentPath = '', depth = 0) => { let nodes = nodeIds.map(id => this.nodes[id]); nodes = sortBy(nodes, this.sortFunc); for (let node of nodes) { - let nodeId = node.concept_id; - let row = {...node, depth, rowPath: `${parentPath}/${nodeId}` }; + // Create `row`: `node` props, plus `depth`, `rowPath`, `display`, index + let row = {...node, depth, rowPath: `${parentPath}/${node.concept_id}` }; row.display = { hideReasons: {}, showReasons: {}, result: '', } row.allRowsIdx = allRows.length; + + // Add row + // - to rows array allRows.push(row); + // - too lookup if (allRowsById.has(row.concept_id)) { allRowsById.get(row.concept_id).push(row); } else { allRowsById.set(row.concept_id, [row]); } - + + // If children/descendants, add rows for them, too if (node.childIds && node.childIds.length) { addRows(node.childIds, row.rowPath, depth + 1); } @@ -489,9 +516,31 @@ export class GraphContainer { return this.graph.copy(); } + /* `setGraphDisplayConfig()`: These are all options that appear in Show Stats/Options + * + * Returns: + * graphOptions: Object + * + * Side effects: + * Sets: this.graphDisplayConfig (Object) + * Sets: this.graphDisplayConfigList (Array) + * Sets: graphOptions.specialConceptTreatment[type] + * + * displayOptions logic + * See code for hidden-rows column in CsetComparisonPage StatsAndOptions table. + * + * If specialTreatmentRule is 'show though collapsed', then what we care + * about are how many currently hidden rows will be shown if option is + * turned on and how many currently shown rows will be hidden if option + * is turned off. + * + * If specialTreatmentRule is 'hide though expanded', then what we care + * about are how many currently visible rows will be hidden if option is + * turned on and how many currently hidden rows will be unhidden if option + * is turned off. + * + * */ setGraphDisplayConfig(graphOptions) { - // these are all options that appear in Show Stats/Options - const displayedConcepts = this.displayedRows || []; // first time through, don't have displayed rows yet const displayedConceptIds = displayedConcepts.map(r => r.concept_id); let displayOrder = 0; @@ -503,22 +552,7 @@ export class GraphContainer { nested: true, }; } - let displayOptions = { - /* - displayOptions logic - See code for hidden-rows column in CsetComparisonPage StatsAndOptions - table. - - If specialTreatmentRule is 'show though collapsed', then what we care - about are how many currently hidden rows will be shown if option is - turned on and how many currently shown rows will be hidden if option - is turned off. - - If specialTreatmentRule is 'hide though expanded', then what we care - about are how many currently visible rows will be hidden if option is - turned on and how many currently hidden rows will be unhidden if option - is turned off. - */ + let displayOptions= { displayedRows: { name: "Visible rows", displayOrder: displayOrder++, value: displayedConcepts.length, @@ -606,8 +640,7 @@ export class GraphContainer { : 0, /* special_v_displayed: () => { let special = this.gd.specialConcepts.allButFirstOccurrence.map(p => p.join('/')); - let displayed = flatten(Object.values(this.displayedNodePaths) - .map(paths => paths.map(path => path.join('/')))) + let displayed = flatten(Object.values(this.displayedNodePaths).map(paths => paths.map(path => path.join('/')))) return [special, displayed]; }, */ specialTreatmentDefault: true,