Skip to content

Commit

Permalink
Merge pull request #387 from vgteam/fix_left_right_buttons
Browse files Browse the repository at this point in the history
Fix left right buttons
  • Loading branch information
adamnovak authored Jan 24, 2024
2 parents 17c69e5 + 256454c commit ca4f248
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/common.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const removeCommas = (input) => {
// or
// { contig, start, distance }
//
// a region string could look like: "17:1-100"
//
// For distance, + is used as the coordinate separator. For start/end ranges, - is used.
// The coordinates are set off from the contig by the last colon.
// Commas in coordinates are removed.
Expand Down
4 changes: 4 additions & 0 deletions src/components/DataPositionFormRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class DataPositionFormRow extends Component {
color="primary"
id="goLeftButton"
onClick={this.props.handleGoLeft}
disabled={this.props.uploadInProgress || !this.props.canGoLeft}
>
<FontAwesomeIcon icon={faStepBackward} size="lg" />
</Button>
Expand All @@ -97,6 +98,7 @@ class DataPositionFormRow extends Component {
color="primary"
id="goRightButton"
onClick={this.props.handleGoRight}
disabled={this.props.uploadInProgress || !this.props.canGoRight}
>
<FontAwesomeIcon icon={faStepForward} size="lg" />
</Button>
Expand Down Expand Up @@ -125,6 +127,8 @@ DataPositionFormRow.propTypes = {
uploadInProgress: PropTypes.bool.isRequired,
getCurrentViewTarget: PropTypes.func.isRequired,
viewTargetHasChange: PropTypes.bool.isRequired,
canGoLeft: PropTypes.bool.isRequired,
canGoRight: PropTypes.bool.isRequired,
};

export default DataPositionFormRow;
146 changes: 143 additions & 3 deletions src/components/HeaderForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,25 @@ const CLEAR_STATE = {
// Description for the selected region, is not displayed when empty
desc: "",

// This tracks several arrays of BED region data, stored by data type, with
// This tracks several arrays (desc, chr, start, end) of BED region data, with
// one entry in each array per region.
// desc: description of region, i.e. "region with no source graph available"
// chr: path in graph where the region is on, i.e. in ref:2000-3000, "ref" is the chr
// start: start of the region, i.e. in ref:2000-3000, 2000 is the start
// end: end of the region, i.e. in ref:2000-3000, 3000 is the end
// chunk: url/directory for preexisting cached chunk, or empty string if not available
// tracks: object full of tracks to apply when user selects region, or null
// so regionInfo might look like:
/*
{
chr: [ '17', '17' ],
start: [ '1', '1000' ],
end: [ '100', '1200' ],
desc: [ '17_1_100', '17_1000_1200' ],
chunk: [ '', '' ],
tracks: [ null, null ]
}
*/
regionInfo: {},

pathNames: [],
Expand Down Expand Up @@ -171,6 +188,77 @@ function viewTargetsEqual(currViewTarget, nextViewTarget) {
return true;
}

/* determine the current region: accepts a region string and returns the region index
example of regionInfo:
{
chr: [ '17', '17' ],
start: [ '1', '1000' ],
end: [ '100', '1200' ],
desc: [ '17_1_100', '17_1000_1200' ],
chunk: [ '', '' ],
tracks: [ null, null ]
}
examples:
if the regionString is "17:1-100", it would be parsed into {contig: "17", start: 1, end: 100} -> 0
if the regionString is "17:1000-1200", it would be parsed into {contig: "17", start: 1000, end: 1200} -> 1
if the regionString is "17:2000-3000", it cannot be found - return null
The function uses this approach to find the regionIndex given regionString and regionInfo:
function (region string){
parse(region string) -> return {contig, start, end}
loop over chr in region info
determine if contig, start, end are present at the current index
if present: return index
return null
}
*/
export const determineRegionIndex = (regionString, regionInfo) => {
let parsedRegion;
try {
parsedRegion = parseRegion(regionString);
} catch(error) {
return null;
}
if (!regionInfo["chr"]){
return null;
}
for (let i = 0; i < regionInfo["chr"].length; i++){
if ((parseInt(regionInfo["start"][i]) === parsedRegion.start)
&& (parseInt(regionInfo["end"][i]) === parsedRegion.end)
&& (regionInfo["chr"][i] === parsedRegion.contig)){
return i;
}
}
return null;
}

/*
This function takes in a regionIndex and regionInfo, and reconstructs a regionString from them
assumes that index is valid in regionInfo
example of regionInfo:
{
chr: [ '17', '17' ],
start: [ '1', '1000' ],
end: [ '100', '1200' ],
desc: [ '17_1_100', '17_1000_1200' ],
chunk: [ '', '' ],
tracks: [ null, null ]
}
example of regionIndex: 0
example of regionString: "17:1-100"
*/
export const regionStringFromRegionIndex = (regionIndex, regionInfo) => {
let regionStart = regionInfo["start"][regionIndex];
let regionEnd = regionInfo["end"][regionIndex];
let regionContig = regionInfo["chr"][regionIndex];
return regionContig + ":" + regionStart + "-" + regionEnd;
}

class HeaderForm extends Component {
state = EMPTY_STATE;
componentDidMount() {
Expand Down Expand Up @@ -504,6 +592,9 @@ class HeaderForm extends Component {
return regionString;
};




// In addition to a new region value, also takes tracks and chunk associated with the region
// Update current track if the new tracks are valid
// Otherwise check if the current bed file is a url, and if tracks can be fetched from said url
Expand Down Expand Up @@ -627,12 +718,57 @@ class HeaderForm extends Component {
);
}


/* Offset the region left or right by the given negative or positive fraction*/
// offset: +1 or -1
jumpRegion(offset) {
let regionIndex = determineRegionIndex(this.state.region, this.state.regionInfo) ?? 0;
if ((offset === -1 && this.canGoLeft(regionIndex)) || (offset === 1 && this.canGoRight(regionIndex))){
regionIndex += offset;
}
let regionString = regionStringFromRegionIndex(regionIndex, this.state.regionInfo);
this.setState(
(state) => ({
region: regionString,
}),
() => this.handleGoButton()
);
}

canGoLeft = (regionIndex) => {
if (this.state.bedFile){
return (regionIndex > 0);
} else {
return true;
}
}

canGoRight = (regionIndex) => {
if (this.state.bedFile){
if (!this.state.regionInfo["chr"]){
return false;
}
return (regionIndex < ((this.state.regionInfo["chr"].length) - 1));
} else {
return true;
}
}


handleGoRight = () => {
this.budgeRegion(0.5);
if (this.state.bedFile){
this.jumpRegion(1);
} else {
this.budgeRegion(0.5);
}
};

handleGoLeft = () => {
this.budgeRegion(-0.5);
if (this.state.bedFile){
this.jumpRegion(-1);
} else {
this.budgeRegion(-0.5);
}
};

showFileSizeAlert = () => {
Expand Down Expand Up @@ -736,9 +872,13 @@ class HeaderForm extends Component {
uploadInProgress={this.state.uploadInProgress}
getCurrentViewTarget={this.props.getCurrentViewTarget}
viewTargetHasChange={viewTargetHasChange}
canGoLeft={this.canGoLeft(determineRegionIndex(this.state.region, this.state.regionInfo))}
canGoRight={this.canGoRight(determineRegionIndex(this.state.region, this.state.regionInfo))}
/>
);



return (
<div>
<Container>
Expand Down
64 changes: 64 additions & 0 deletions src/components/HeaderForm.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { determineRegionIndex, regionStringFromRegionIndex } from "./HeaderForm.js";


// test for determineRegionIndex and regionStringFromRegionIndex
describe("determine regionIndex and corresponding region strings for various region inputs", () => {
// TEST #1
it("determine regionIndex and regionString for smaller regionInfo lists", async () => {
let regionInfo = {
chr: [ 'ref', 'ref' ],
start: [ '1000', '2000' ],
end: [ '2000', '3000' ],
desc: [ '17_1_100', 'ref_2000_3000' ],
chunk: [ '', '' ],
tracks: [ null, null ]
}
let regionString = "ref:2000-3000";
let regionIndex = determineRegionIndex(regionString, regionInfo);
expect(regionIndex).toBe(1);
expect(regionStringFromRegionIndex(regionIndex, regionInfo)).toBe("ref:2000-3000");
})
// TEST #2
it("determine regionIndex and regionString for larger regionInfo lists", async () => {
let regionInfo = {
chr: [ '17', 'ref', '17', 'ref', '17', 'ref' ],
start: [ '100', '200', '2000', '3000', '4000', '5000' ],
end: [ '200', '300', '3000', '4000', '5000', '6000' ],
desc: [ '17_100_200', '17_200_300', 'ref_2000_3000', 'ref_3000_4000', 'ref_4000_5000', 'ref_5000_6000' ],
chunk: [ '', '', '', '', '', '', '' ],
tracks: [ null, null, null, null, null, null ]
}
let regionString = "17:4000-5000";
let regionIndex = determineRegionIndex(regionString, regionInfo);
expect(regionIndex).toBe(4);
expect(regionStringFromRegionIndex(regionIndex, regionInfo)).toBe("17:4000-5000");
})
// TEST #3
it("determine regionIndex to be null for input of region not found in regionInfo", async () => {
let regionInfo = {
chr: [ '17', 'ref', '17', 'ref', '17', 'ref' ],
start: [ '100', '200', '2000', '3000', '4000', '5000' ],
end: [ '200', '300', '3000', '4000', '5000', '6000' ],
desc: [ '17_100_200', '17_200_300', 'ref_2000_3000', 'ref_3000_4000', 'ref_4000_5000', 'ref_5000_6000' ],
chunk: [ '', '', '', '', '', '', '' ],
tracks: [ null, null, null, null, null, null ]
}
let regionString = "17:5000-7000";
let regionIndex = determineRegionIndex(regionString, regionInfo);
expect(regionIndex).toBe(null);
})
// TEST #4
it("determine regionIndex and regionString to be null given empty regionInfo", async () => {
let regionInfo = {
chr: [],
start: [],
end: [],
desc: [],
chunk: [],
tracks: []
}
let regionString = "17:5000-7000";
let regionIndex = determineRegionIndex(regionString, regionInfo);
expect(regionIndex).toBe(null);
})
});

0 comments on commit ca4f248

Please sign in to comment.