Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class App extends Component {
alphaReadsByMappingQuality: false,
colorSchemes: getColorSchemesFromTracks(this.defaultViewTarget.tracks),
mappingQualityCutoff: 0,
hiddenTracks: [],
},
APIInterface: new ServerAPI(props.apiUrl)
};
Expand Down Expand Up @@ -95,6 +96,7 @@ class App extends Component {
visOptions: {
...state.visOptions,
colorSchemes: [],
hiddenTracks: [],
},
};
} else if (mode === "server") {
Expand All @@ -107,6 +109,7 @@ class App extends Component {
visOptions: {
...state.visOptions,
colorSchemes: getColorSchemesFromTracks(this.defaultViewTarget.tracks),
hiddenTracks: [],
},
};
} else {
Expand Down Expand Up @@ -199,6 +202,15 @@ class App extends Component {
}));
};

handleTrackVisibilityChanged = (hiddenTrackIds) => {
this.setState((state) => ({
visOptions: {
...state.visOptions,
hiddenTracks: hiddenTrackIds,
},
}));
};

// Set a color scheme setting for a particular track.
//
// key is the name of the setting to set, and may be "mainPalette", "auxPalette", or "colorByMappingQuality".
Expand Down Expand Up @@ -251,6 +263,7 @@ class App extends Component {
dataOrigin={this.state.dataOrigin}
visOptions={this.state.visOptions}
APIInterface={this.state.APIInterface}
onTrackVisibilityChanged={this.handleTrackVisibilityChanged}
/>
<CustomizationAccordion
enableCompressedNodes={this.state.viewTarget.removeSequences}
Expand Down
12 changes: 11 additions & 1 deletion src/components/TubeMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import isEqual from "react-fast-compare";

class TubeMap extends Component {
componentDidMount() {
this.setupCallback();
this.updateVisOptions();
this.createTubeMap();
}
Expand All @@ -16,13 +17,20 @@ class TubeMap extends Component {
console.log("Old read count:", (prevProps.reads || []).length);
if (!isEqual(this.props, prevProps)) {
console.log("Props have changed so re-creating tube map");
this.setupCallback();
this.updateVisOptions();
this.createTubeMap();
} else {
console.log("Props have not changed so leaving existing tube map");
}
}

setupCallback() {
if (this.props.onTrackVisibilityChanged) {
tubeMap.setVisibilityChangedCallback(this.props.onTrackVisibilityChanged);
}
}

createTubeMap = () => {
tubeMap.create({
svgID: "#svg",
Expand Down Expand Up @@ -50,6 +58,7 @@ class TubeMap extends Component {
tubeMap.setShowReadsFlag(visOptions.showReads);
tubeMap.setSoftClipsFlag(visOptions.showSoftClips);
tubeMap.setColoredNodes(visOptions.coloredNodes);
tubeMap.setHiddenTrackIds(visOptions.hiddenTracks || []);

for (let key of Object.keys(visOptions.colorSchemes)) {
// Apply color-by-mapping-quality parameter to all the schemes.
Expand All @@ -76,7 +85,8 @@ TubeMap.propTypes = {
reads: PropTypes.array.isRequired,
region: PropTypes.array.isRequired,
visOptions: PropTypes.object.isRequired,
nodeSequences: PropTypes.bool
nodeSequences: PropTypes.bool,
onTrackVisibilityChanged: PropTypes.func,
};

TubeMap.defaultProps = {
Expand Down
2 changes: 2 additions & 0 deletions src/components/TubeMapContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class TubeMapContainer extends Component {
...this.props.visOptions,
}}
nodeSequences={!this.props.viewTarget.removeSequences}
onTrackVisibilityChanged={this.props.onTrackVisibilityChanged}
/>
</div>
</div>
Expand Down Expand Up @@ -326,6 +327,7 @@ TubeMapContainer.propTypes = {
viewTarget: PropTypes.object.isRequired,
visOptions: PropTypes.object.isRequired,
APIInterface: PropTypes.object.isRequired,
onTrackVisibilityChanged: PropTypes.func,
};

export default TubeMapContainer;
55 changes: 45 additions & 10 deletions src/util/tubemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ const config = {
showInfoCallback: function (info) {
alert(info);
},
hiddenTrackIds: new Set(),
visibilityChangedCallback: null,
};

// variables for storing info which can be directly translated into drawing instructions
Expand Down Expand Up @@ -191,6 +193,13 @@ export function create(params) {
// And then leave a hole in the array at 0 which we won't iterate over.
delete inputNodes[0];
inputTracks = deepCopy(params.tracks); // deep copy
inputTracks.forEach((t) => {
if (!t.type) t.type = "haplotype";
});
config.hiddenTrackIds.forEach((id) => {
const track = inputTracks.find((t) => t.id === id);
if (track) track.hidden = true;
});
inputReads = params.reads || null;
inputRegion = params.region;
bed = params.bed || null;
Expand Down Expand Up @@ -311,19 +320,34 @@ export function changeTrackVisibility(trackID) {
} else {
inputTracks[i].hidden = true;
}
if (inputTracks[i].hidden) {
config.hiddenTrackIds.add(trackID);
} else {
config.hiddenTrackIds.delete(trackID);
}
if (config.visibilityChangedCallback) {
config.visibilityChangedCallback(Array.from(config.hiddenTrackIds));
}
}
createTubeMap();
}

// to select/deselect all
export function changeAllTracksVisibility(value) {
let i = 0;
config.hiddenTrackIds.clear();
while (i < inputTracks.length) {
inputTracks[i].hidden = !value;
if (!value) {
config.hiddenTrackIds.add(inputTracks[i].id);
}
var checkbox = document.getElementById(`showTrack${inputTracks[i].id}`);
checkbox.checked = value;
i += 1;
}
if (config.visibilityChangedCallback) {
config.visibilityChangedCallback(Array.from(config.hiddenTrackIds));
}
createTubeMap();
}

Expand Down Expand Up @@ -418,6 +442,18 @@ export function setMappingQualityCutoff(value) {
}
}

export function setHiddenTrackIds(ids) {
config.hiddenTrackIds = new Set(ids || []);
}

export function setVisibilityChangedCallback(callback) {
config.visibilityChangedCallback = callback;
}

export function getHiddenTrackIds() {
return Array.from(config.hiddenTrackIds);
}

// main
function createTubeMap() {
console.log("Recreating tube map in", svgID);
Expand Down Expand Up @@ -4224,24 +4260,23 @@ function drawLegend() {
content +=
'<table class="table-sm table-condensed table-nonfluid"><thead><tr><th>Color</th><th>Trackname</th><th>Show Track</th></tr></thead>';
const listeners = [];
// This is in terms of tracks, but when we change visibility we need to touch inputTracks, so we need to set up listeners by track ID.
for (let i = 0; i < tracks.length; i += 1) {
if (tracks[i].type === "haplotype") {
for (let i = 0; i < inputTracks.length; i += 1) {
if (inputTracks[i].type === "haplotype") {
content += `<tr><td style="text-align:right"><div class="color-box" style="background-color: ${generateTrackColor(
tracks[i],
inputTracks[i],
"exon"
)};"></div></td>`;
if (tracks[i].hasOwnProperty("name")) {
content += `<td>${tracks[i].name}</td>`;
if (inputTracks[i].hasOwnProperty("name")) {
content += `<td>${inputTracks[i].name}</td>`;
} else {
content += `<td>${tracks[i].id}</td>`;
content += `<td>${inputTracks[i].id}</td>`;
}
content += `<td><input type="checkbox" checked=true id="showTrack${tracks[i].id}"></td>`;
listeners.push(tracks[i].id);
const isChecked = !config.hiddenTrackIds.has(inputTracks[i].id);
content += `<td><input type="checkbox" ${isChecked ? 'checked=true' : ''} id="showTrack${inputTracks[i].id}"></td>`;
listeners.push(inputTracks[i].id);
}
}
content += "</table";
// $('#legendDiv').html(content);
document.getElementById("legendDiv").innerHTML = content;
listeners.forEach((id) => {
document
Expand Down
57 changes: 56 additions & 1 deletion src/util/tubemap.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { cigar_string, coverage, axisIntervals } from "./tubemap";
import {
cigar_string,
coverage,
axisIntervals,
setHiddenTrackIds,
getHiddenTrackIds,
setVisibilityChangedCallback,
} from "./tubemap";

// cigar string test
describe("cigar_string", () => {
Expand Down Expand Up @@ -865,6 +872,54 @@ describe("coverage", () => {
});


describe("hiddenTrackIds", () => {
it("starts empty", async () => {
expect(getHiddenTrackIds()).toStrictEqual([]);
});

it("can set and get hidden track IDs", async () => {
setHiddenTrackIds([1, 3, 5]);
expect(getHiddenTrackIds()).toStrictEqual([1, 3, 5]);
});

it("can set empty list", async () => {
setHiddenTrackIds([]);
expect(getHiddenTrackIds()).toStrictEqual([]);
});

it("can set null/undefined to empty", async () => {
setHiddenTrackIds(null);
expect(getHiddenTrackIds()).toStrictEqual([]);
setHiddenTrackIds(undefined);
expect(getHiddenTrackIds()).toStrictEqual([]);
});

it("deduplicates IDs via Set", async () => {
setHiddenTrackIds([1, 1, 2, 2, 3]);
const result = getHiddenTrackIds();
expect(result.sort((a, b) => a - b)).toStrictEqual([1, 2, 3]);
});

it("replaces previous values on set", async () => {
setHiddenTrackIds([1, 2]);
setHiddenTrackIds([3, 4]);
expect(getHiddenTrackIds()).toStrictEqual([3, 4]);
});
});

describe("visibilityChangedCallback", () => {
it("can set callback", async () => {
const cb = () => {};
setVisibilityChangedCallback(cb);
// No error thrown
});

it("can clear callback with null", async () => {
setVisibilityChangedCallback(null);
// No error thrown
});
});

describe("axisIntervals", () => {
// TEST 1
it("can handle an empty array", async () => {
Expand Down