Skip to content

Commit

Permalink
Merge pull request #307 from phyloref/modify-tunits-on-phylogenies
Browse files Browse the repository at this point in the history
This PR adds support for modifying the taxonomic units represented by a node in a phylogeny. This functionality has existed in Phyx.js for a while now, so it's really just a case of surfacing it in the UI with minimum work but in a way that's easy to work with.

Closes #228.
  • Loading branch information
gaurav authored Nov 15, 2023
2 parents 842617a + de5a5d1 commit 1e576d2
Show file tree
Hide file tree
Showing 3 changed files with 385 additions and 48 deletions.
158 changes: 136 additions & 22 deletions src/components/phylogeny/PhylogenyView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@

<div class="card-footer">
<div
class="btn-group"
role="group"
area-label="Phylogeny management"
class="btn-group"
role="group"
area-label="Phylogeny management"
>
<button
class="btn btn-danger"
@click="deleteThisPhylogeny()"
class="btn btn-danger"
@click="deleteThisPhylogeny()"
>
Delete phylogeny
</button>
Expand Down Expand Up @@ -144,6 +144,66 @@
/>
</div>
</div>

<!-- Display taxonomic units in this phylogeny -->
<div class="card mt-2">
<h5 class="card-header">
Taxonomic units in this phylogeny
</h5>
<div class="card-body">
To add additional taxonomic units to this list, please label the corresponding node on the phylogeny.
</div>

<!--
<b-table-simple striped hover>
<b-thead>
<b-tr>
<b-th>Node label</b-th>
<b-th>Node type</b-th>
</b-tr>
</b-thead>
</b-table-simple>
-->

<b-table
striped
hover
:items="taxonomicUnitsTable"
:fields="['node_label', 'node_type', 'additional_taxonomic_units']"
:primary-key="node_label"
show-empty
>
<template #empty="scope">
<h4>No labels found in this phylogeny.</h4>
</template>
<template #emptyfiltered="scope">
<h4>No labels found after filtering.</h4>
</template>

<template #cell(additional_taxonomic_units)="row">
{{row.item.additional_taxonomic_units}} taxonomic units <b-button variant="primary" @click="addTUnitForNodeLabel(row.item.node_label)" class="float-right" size="sm">Add</b-button>
</template>

<template #row-details="row">
<b-card>
<b-row
v-for="(tunit, index) in getExplicitTUnitsForLabel(row.item.node_label)"
:key="row.item.node_label"
class="mb-12"
>
<Specifier
:key="'tunit_' + row.item.node_label + '_' + index"
:phylogeny="selectedPhylogeny"
:node-label="row.item.node_label"
:remote-specifier="tunit"
:remote-specifier-id="'tunit_' + row.item.node_label + '_' + index"
/>
</b-row>
</b-card>
</template>
</b-table>
</div>
</div>
</template>

Expand All @@ -156,30 +216,21 @@ import { has } from 'lodash';
import { mapState } from 'vuex';
import { parse as parseNewick } from 'newick-js';
import {PhylogenyWrapper, TaxonomicUnitWrapper} from '@phyloref/phyx';
import ModifiedCard from '../cards/ModifiedCard.vue';
import Phylotree from './Phylotree.vue';
import Citation from '../citations/Citation.vue';
import Specifier from "@/components/specifiers/Specifier.vue";
export default {
name: 'PhylogenyView',
components: { ModifiedCard, Phylotree, Citation },
components: {Specifier, ModifiedCard, Phylotree, Citation },
data() {
return {
// Errors in the phylogenyId field.
phylogenyIdError: undefined,
};
},
methods: {
deleteThisPhylogeny() {
// Delete this phylogeny, and unset the selected phylogeny so we return to the summary page.
if(confirm('Are you sure you wish to delete this phylogeny? This cannot be undone!')) {
this.$store.commit('deletePhylogeny', {
phylogeny: this.selectedPhylogeny,
});
this.$store.commit('changeDisplay', {});
}
},
},
computed: {
/*
* The following properties allow you to get or set the phylogeny label,
Expand Down Expand Up @@ -246,11 +297,11 @@ export default {
if (parenLevels !== 0) {
errors.push({
title: 'Unbalanced parentheses in Newick string',
message: (parenLevels > 0
? `You have ${parenLevels} too many open parentheses`
: `You have ${-parenLevels} too few open parentheses`
),
title: "Unbalanced parentheses in Newick string",
message:
parenLevels > 0
? `You have ${parenLevels} too many open parentheses`
: `You have ${-parenLevels} too few open parentheses`,
});
}
Expand All @@ -266,11 +317,74 @@ export default {
return errors;
},
terminalLabelsSorted() {
// Return a list of terminal (i.e. leaf node) labels sorted alphabetically.
return new PhylogenyWrapper(this.selectedPhylogeny).getNodeLabels('terminal').sort();
},
internalLabelsSorted() {
// Return a list of internal (i.e. non-leaf node) labels sorted alphabetically.
return new PhylogenyWrapper(this.selectedPhylogeny).getNodeLabels('internal').sort();
},
taxonomicUnitsTable() {
// Create a table of taxonomic units and their additional taxonomic units found in this phylogeny.
const terminalLabels = this.terminalLabelsSorted;
const internalLabels = this.internalLabelsSorted;
return terminalLabels
.map((nodeLabel) => ({
node_label: nodeLabel,
node_type: "Terminal node",
additional_taxonomic_units:
this.getExplicitTUnitsForLabel(nodeLabel).length,
_showDetails: this.getExplicitTUnitsForLabel(nodeLabel).length > 0,
}))
.concat(
internalLabels.map((nodeLabel) => ({
node_label: nodeLabel,
node_type: "Internal node",
additional_taxonomic_units:
this.getExplicitTUnitsForLabel(nodeLabel).length,
_showDetails: this.getExplicitTUnitsForLabel(nodeLabel).length > 0,
}))
);
},
...mapState({
currentPhyx: state => state.phyx.currentPhyx,
loadedPhyx: state => state.phyx.loadedPhyx,
selectedPhylogeny: state => state.ui.display.phylogeny,
}),
},
methods: {
deleteThisPhylogeny() {
// Delete this phylogeny, and unset the selected phylogeny so we return to the summary page.
if (confirm('Are you sure you wish to delete this phylogeny? This cannot be undone!')) {
this.$store.commit('deletePhylogeny', {
phylogeny: this.selectedPhylogeny,
});
this.$store.commit('changeDisplay', {});
}
},
getExplicitTUnitsForLabel(nodeLabel) {
// Return the list of "explicit" taxonomic units for a phylogeny node, which is the list of
// taxonomic units listed in the additionalNodeProperties.
//
// This will not include "implicit" taxonomic units, which we generate from the node label.
return this.$store.getters.getExplicitTaxonomicUnitsForPhylogenyNode(
this.selectedPhylogeny,
nodeLabel
);
},
addTUnitForNodeLabel(nodeLabel) {
// Add a taxonomic unit to a node label.
this.$store.commit('addTaxonomicUnitToPhylogenyNode', {
phylogeny: this.selectedPhylogeny,
nodeLabel,
tunit: TaxonomicUnitWrapper.fromLabel(
"",
this.$store.getters.getDefaultNomenCodeURI
),
});
},
},
};
</script>
88 changes: 73 additions & 15 deletions src/components/specifiers/Specifier.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@
Specifier details
</h5>

<!-- Specifier type: internal or external -->
<div class="form-group row">
<!-- Specifier type: internal or external. Only applies to phylorefs! -->
<div v-if="phyloref" class="form-group row">
<label
class="col-form-label col-md-2"
for="specifier-type"
Expand Down Expand Up @@ -424,9 +424,26 @@ export default {
required: false,
default: () => uniqueId('remoteSpecifierId'),
},
phyloref: { /* The phyloreference containing this specifier */
/*
* There are two ways of visualizing a specifier:
* - The specifier may be a part of a phyloref, or
* - The specifier (really a taxonomic unit) may be a part of a phylogeny via the `representsTaxonomicUnits`
* property of the `additionalNodeProperties` dictionary. This is specified in two ways: a phylogeny as
* well as a nodeLabel.
*/
/* The phyloreference containing this specifier */
phyloref: {
type: Object,
required: true,
required: false,
},
/* The phylogeny and nodeLabel containing this specifier (really a taxonomic unit). */
phylogeny: {
type: Object,
required: false,
},
nodeLabel: {
type: String,
required: false,
},
},
data() {
Expand Down Expand Up @@ -489,9 +506,12 @@ export default {
},
specifierType: {
get() {
if (!this.phyloref) return undefined;
return new PhylorefWrapper(this.phyloref).getSpecifierType(this.remoteSpecifier);
},
set(type) {
if (!this.phyloref) return undefined;
this.$store.commit(
'setSpecifierType',
{
Expand Down Expand Up @@ -573,6 +593,12 @@ export default {
phyloref() {
this.recalculateEntered();
},
phylogeny() {
this.recalculateEntered();
},
nodeLabel() {
this.recalculateEntered();
},
remoteSpecifier() {
this.recalculateEntered();
},
Expand Down Expand Up @@ -645,24 +671,56 @@ export default {
// Update remoteSpecifier to what we've got currently entered.
const confirmed = confirm('Are you sure you want to delete this specifier?');
if (confirmed) {
console.log('Deleting specifier: ', this.phyloref, this.remoteSpecifier);
this.$store.commit('deleteSpecifier', {
phyloref: this.phyloref,
specifier: this.remoteSpecifier,
});
if (this.phyloref) {
console.log("Deleting specifier from phyloref: ", this.phyloref, this.remoteSpecifier);
this.$store.commit('deleteSpecifier', {
phyloref: this.phyloref,
specifier: this.remoteSpecifier,
});
} else if (this.phylogeny && this.nodeLabel) {
console.log("Deleting taxonomic unit from phylogeny: ", this.phylogeny, this.nodeLabel, this.remoteSpecifier);
this.$store.commit('replaceTUnitForPhylogenyNode', {
phylogeny: this.phylogeny,
nodeLabel: this.nodeLabel,
tunit: this.remoteSpecifier,
delete: true,
});
}
}
},
updateSpecifier() {
const result = this.specifier;
// If our local specifier differs from the remoteSpecifier, update it.
if (isEqual(result, this.remoteSpecifier)) return;
if (this.$store.getters.areTUnitsIdentical(result, this.remoteSpecifier))
return;
console.log('Updating specifier as ', result, ' differs from ', this.remoteSpecifier);
this.$store.commit('setSpecifierProps', {
specifier: this.remoteSpecifier,
props: result,
});
if (this.phyloref) {
console.log('Updating specifier in ', this.phyloref, ' as ', result, ' differs from ', this.remoteSpecifier);
this.$store.commit('setSpecifierProps', {
specifier: this.remoteSpecifier,
props: result,
});
} else if (this.phylogeny && this.nodeLabel) {
console.log(
"Updating tunit in ",
this.phylogeny,
" with node label ",
this.nodeLabel,
" as ",
result,
" differs from ",
this.remoteSpecifier
);
this.$store.commit('replaceTUnitForPhylogenyNode', {
phylogeny: this.phylogeny,
nodeLabel: this.nodeLabel,
tunit: this.remoteSpecifier,
tunit_new: result,
});
} else {
console.error("Specifier has neither phyloref nor phylogeny/nodeLabel combination: ", this);
}
},
},
};
Expand Down
Loading

0 comments on commit 1e576d2

Please sign in to comment.