Skip to content

Commit

Permalink
- Fixes #993, fixes #995
Browse files Browse the repository at this point in the history
- Can now click on row to get info about where else a concept appears.
- Fixed an edge case bug so newCset concept_ids will now be included
  when generating concept graph
- Replaced hard-coded unicode characters with constants defined in
  utils.jsx:
  - export const NO_BREAK_SPACE = '\u00a0';
    export const CHECKMARK = '\u2713';
    export const RIGHT_ARROW = '\u2192';
  • Loading branch information
Sigfried committed Nov 27, 2024
1 parent aabf1c6 commit 570f278
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 119 deletions.
6 changes: 3 additions & 3 deletions frontend/src/components/AddConcepts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { countBy, sum, set, uniq, flatten, debounce, isEmpty, union, difference,

import { useDataGetter, DataGetter } from '../state/DataGetter';
import { setColDefDimensions } from './dataTableUtils';
import { useWindowSize } from '../utils';
import { useWindowSize, NO_BREAK_SPACE } from '../utils';
import { useCids, useCodesetIds } from '../state/AppState';
import Button from '@mui/material/Button';
import { TextField } from '@mui/material';
Expand Down Expand Up @@ -182,8 +182,8 @@ function ConceptStringSearch() {
value={searchText}
autoFocus={true}
/>
{'\u00A0'}{'\u00A0'}{'\u00A0'}{found_concept_ids.length ? found_concept_ids.length.toLocaleString() + ` concepts match "${searchText}"` : ''}
{'\u00A0'}{hiddenMatches.length ? hiddenMatches.length.toLocaleString() + ' already included and not listed' : ''}
{NO_BREAK_SPACE}{NO_BREAK_SPACE}{NO_BREAK_SPACE}{found_concept_ids.length ? found_concept_ids.length.toLocaleString() + ` concepts match "${searchText}"` : ''}
{NO_BREAK_SPACE}{hiddenMatches.length ? hiddenMatches.length.toLocaleString() + ' already included and not listed' : ''}
<TextField fullWidth multiline
style={{marginTop: 15, }}
label={"Enter concept_ids to add separated by spaces, commas, or newlines and click button below"}
Expand Down
189 changes: 98 additions & 91 deletions frontend/src/components/CsetComparisonPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ import {

import {dfs, dfsFromNode} from 'graphology-traversal/dfs';

import {fmt, saveCsv, useWindowSize} from '../utils';
import {
fmt,
saveCsv,
useWindowSize,
RIGHT_ARROW,
NO_BREAK_SPACE,
} from '../utils';
import {Info} from "@mui/icons-material";
import {iconStyle, setColDefDimensions} from './dataTableUtils';
import {ConceptSetCard} from './ConceptSetCard';
Expand Down Expand Up @@ -71,15 +77,15 @@ export async function fetchGraphData(props) {
dataGetter.fetchAndCacheItems(dataGetter.apiCalls.csets, codeset_ids),
];
// have to get concept_ids before fetching concepts
let newCsetCids = [];
if (!isEmpty(newCset)) {
newCsetCids = Object.keys(newCset.definitions).map(d => parseInt(d));
}
cids = union(cids, newCsetCids);
const graphData = await dataGetter.fetchAndCacheItems(
dataGetter.apiCalls.concept_graph_new, {codeset_ids, cids});
let {concept_ids} = graphData;

if (!isEmpty(newCset)) {
concept_ids = union(concept_ids,
Object.keys(newCset.definitions).map(d => parseInt(d))
);
}
promises.push(
dataGetter.fetchAndCacheItems(dataGetter.apiCalls.concepts, concept_ids));

Expand Down Expand Up @@ -170,7 +176,7 @@ export function CsetComparisonPage() {
const [cids, cidsDispatch] = useCids();
const [newCset, newCsetDispatch] = useNewCset();
const [api_call_group_id, setApiCallGroupId] = useState();
const [showInfoAbout, setShowInfoAbout] = useState();
const [showRowInfo, setShowRowInfo] = useState();
let [graphOptions, graphOptionsDispatch] = useGraphOptions();

const editingCset = !isEmpty(newCset);
Expand Down Expand Up @@ -246,9 +252,7 @@ export function CsetComparisonPage() {
* from defaults
* 2) figure out displayedRows accordingly
* 3) set counts for StatsAndOptions table accordingly
*
*/

let displayedRows = [];
_gc.setGraphDisplayConfig(graphOptions, allRows, displayedRows);

Expand Down Expand Up @@ -327,7 +331,6 @@ export function CsetComparisonPage() {
csmi,
newCset, newCsetDispatch,
setShowCsetCodesetId,
setShowInfoAbout,
});

let csetCard = null;
Expand Down Expand Up @@ -501,45 +504,89 @@ export function CsetComparisonPage() {
</FlexibleContainer>,
);

if (showInfoAbout) {
const item = {...csmi[showInfoAbout.codeset_id][showInfoAbout.concept_id], ...showInfoAbout};
const descendantOf = csmi[item.codeset_id][item.descendantOf];
const cset = selected_csets.find(d => d.codeset_id === item.codeset_id);
const concept = conceptLookup[item.concept_id];
const dconcept = conceptLookup[item.descendantOf];
let paths = displayedRows
.filter(d => d.concept_id == item.concept_id)
.map(d => d.rowPath.split('/')
.map(p => conceptLookup[p]?.concept_name || p).join('/'));
console.log(displayedRows);
if (showRowInfo) {
const row = showRowInfo;
console.log(row);
const concept = conceptLookup[row.concept_id];

// paths = all paths to concept in displayedRows
let conceptPaths = displayedRows
.filter(d => d.concept_id == row.concept_id)
// remove initial /, split into concept_ids
.map(d => d.rowPath.slice(1).split('/'));

let paths = conceptPaths
// replace concept_ids with names, join with →
.map(path => path.map(c => conceptLookup[c]?.concept_name || c).join(RIGHT_ARROW))
// add spaces at end so window border doesn't touch text
.map(path => path + NO_BREAK_SPACE + NO_BREAK_SPACE);

console.log(selected_csets);
let items = selected_csets.map(
cset => {
let item = csmi[cset.codeset_id][row.concept_id];
if (!item) return;
item = {
...item,
codesetName: cset.concept_set_version_title,
};
console.log(item);
if (item.descendantOf) {
if (Number.isInteger(item.descendantOf)) {
let ancestor = csmi[cset.codeset_id][item.descendantOf];
if (ancestor?.includeDescendants) {
item.descendantOf = {...ancestor, ...conceptLookup[ancestor.concept_id] };
}
} else {
throw new Error("wasn't expecting this");
}
} else {
// let's try to find definition this item descended from
for (const path of conceptPaths) {
for (const concept_id of path.reverse()) {
let ancestor = csmi[cset.codeset_id][concept_id];
if (ancestor?.includeDescendants) {
item.descendantOf = ancestor;
break;
}
}
if (item.descendantOf) break;
}
}
return item;
}).filter(d => d);

infoPanels.push(
<FlexibleContainer id="infoAbout" key="infoAbout" title={<span>Last clicked <Info sx={iconStyle}/></span>}
closeAction={() => setShowInfoAbout(undefined)}
<FlexibleContainer id="rowInfo" key="rowInfo" title={concept.concept_name}
closeAction={() => setShowRowInfo(undefined)}
startHidden={false}
hideShowPrefix={true}
style={{midWidth: '500px', height: '300px', resize: 'both'}}
style={{midWidth: '500px', minHeight: '200px', resize: 'both'}}
position={panelPosition}
countRef={countRef}>
<ul style={{listStyleType: 'none', paddingLeft: '2em'}}>
<li>In{' '} {cset.concept_set_version_title}</li>
<ul style={{listStyleType: 'none', paddingLeft: '2em'}}>
<li>Concept <strong>{concept.concept_id} {concept.concept_name}</strong>
</li>
<li><em>{textCellForItem(descendantOf, true, true)}</em></li>
</ul>
<li>is descended from</li>
<ul style={{listStyleType: 'none', paddingLeft: '2em'}}>
<li><strong>{dconcept.concept_id} {dconcept.concept_name}</strong>
</li>
<li><em>{textCellForItem(descendantOf, true, true)}</em></li>
</ul>
<li>and appears at these paths</li>
<ul>
<li>Concept <strong>{concept.concept_id}</strong> appears at these paths</li>
<ul>
{ paths.map((p,i) => <li key={i}>{p}</li>) }
{paths.map((p, i) => <li key={i}>{p}</li>)}
</ul>
{items.filter(d => d).map((item, i) => (
<li key={i}>
In {item.codesetName} this concept is
<ul style={{paddingLeft: '2em'}}>
<li>{textCellForItem(item, true)}</li>
{
item.descendantOf
? <li>Descended from{' '}
<strong>{item.descendantOf.concept_id} {item.descendantOf.concept_name}</strong>
{' '}<em>{textCellForItem(item.descendantOf, true, true)}</em>
</li>
: null
}
</ul>
</li>
))}
</ul>
</FlexibleContainer>
,
);
}

Expand All @@ -548,6 +595,7 @@ export function CsetComparisonPage() {
columns: colDefs,
selected_csets,
customStyles,
setShowRowInfo,
};
return (
<div style={{margin: 10}}>
Expand Down Expand Up @@ -684,53 +732,6 @@ function nodeToTree(node) { // Not using
return [node, ...subTrees];
}

function getCollapseIconAndNameOLD(
row, name, sizes, graphOptions, graphOptionsDispatch, gc) {
let Component;
let direction;
if (
(graphOptions.expandAll &&
graphOptions.specificPaths[row.rowPath] !== 'collapse'
) || graphOptions.specificPaths[row.rowPath] === 'expand'
) {
Component = RemoveCircleOutline;
direction = 'collapse';
} else {
Component = AddCircle;
direction = 'expand';
}
return (
<span
className="toggle-collapse concept-name-row"
onClick={
(evt) => {
graphOptionsDispatch({
gc,
type: 'TOGGLE_NODE_EXPANDED',
rowPath: row.rowPath,
direction,
});
}
}
onDoubleClick={() => {
console.log('got a double click');
}}
// TODO: capture long click or double click or something to expand descendants
// onDoubleClick={() => graphOptionsDispatch({type: "TOGGLE_NODE_EXPANDED", payload: {expandDescendants: true, nodeId: row.concept_id}})}
>
<Component
sx={{
fontSize: sizes.collapseIcon,
display: 'inline-flex',
marginRight: '0.15rem',
marginTop: '0.05rem',
verticalAlign: 'top',
}}
/>
<span className="concept-name-text">{name}</span>
</span>
);
}
function getCollapseIconAndName(
row, name, sizes, graphOptions, graphOptionsDispatch, gc) {
let Component;
Expand Down Expand Up @@ -833,7 +834,6 @@ function getColDefs(props) {
csmi,
newCset, newCsetDispatch,
setShowCsetCodesetId,
setShowInfoAbout,
} = props;
const {nested, } = graphOptions;

Expand All @@ -857,8 +857,6 @@ function getColDefs(props) {
<span className="concept-name-text">{name}</span>
</span>)
) : (
// this is for non-nested which is not currently implemented (no button for it)
// it allowed sorting rows... TODO: figure out if bringing it back
<span className="concept-name-text">{name}</span>
);
return content;
Expand Down Expand Up @@ -1145,7 +1143,6 @@ function getColDefs(props) {
...props,
row,
cset_col,
setShowInfoAbout,
});
},
conditionalCellStyles: [
Expand Down Expand Up @@ -1186,6 +1183,12 @@ function getColDefs(props) {

coldefs = [...coldefs, ...cset_cols];

// add pointer style to everything so users know they can click anywhere
// on row for row info
coldefs = coldefs.map(d => ({
...d, style: {...(d.style ?? {}), cursor: 'pointer' }
}));

// concept name takes up remaining window width after all other columns
const totalWidthOfOthers = sum(coldefs.map(d => d.width));

Expand Down Expand Up @@ -1214,6 +1217,7 @@ function ComparisonDataTable(props) {
newCset = {},
customStyles,
rowData,
setShowRowInfo,
/* squishTo = 1, cset_data, displayedRows, selected_csets */
} = props;
const infoPanelRef = useRef();
Expand Down Expand Up @@ -1252,6 +1256,9 @@ function ComparisonDataTable(props) {
theme="custom-theme" // theme="light"
columns={columns}
data={rowData}
onRowClicked={row => {
setShowRowInfo(row);
}}
dense
fixedHeader
fixedHeaderScrollHeight={() => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/Csets.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useEffect, } from 'react';
import { CsetsDataTable } from './CsetsDataTable';
import {pct_fmt} from "../utils";
import {NO_BREAK_SPACE, pct_fmt} from '../utils';
import ConceptSetCards from './ConceptSetCard';
import Alert from '@mui/material/Alert';
import AlertTitle from '@mui/material/AlertTitle';
Expand Down Expand Up @@ -285,7 +285,7 @@ export function ConceptSetsPage () {
cset['intersecting_concepts'] = val;
} else {
val = Number(val).toLocaleString(undefined, {style:'percent', maximumFractionDigits: 2});
vocabs.push(`${val}\u00A0${key}`);
vocabs.push(`${val}${NO_BREAK_SPACE}${key}`);
}
});
cset['vocabs'] = vocabs.join(', ');
Expand Down
Loading

0 comments on commit 570f278

Please sign in to comment.