Skip to content

Commit

Permalink
added ability to control whether base pair lines are exported for a s…
Browse files Browse the repository at this point in the history
…elected strand when the other strand to which it is bound is not selected
  • Loading branch information
dave-doty committed Nov 6, 2024
1 parent f2e9db8 commit af47af7
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 20 deletions.
23 changes: 23 additions & 0 deletions lib/src/actions/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4398,6 +4398,29 @@ abstract class ShowBasePairLinesSet
int get hashCode;
}

abstract class ExportBasePairLinesIfOtherStrandNotSelectedSet
with
BuiltJsonSerializable
implements
Action,
Built<ExportBasePairLinesIfOtherStrandNotSelectedSet,
ExportBasePairLinesIfOtherStrandNotSelectedSetBuilder> {
bool get export_base_pair_lines_if_other_strand_not_selected;

/************************ begin BuiltValue boilerplate ************************/
factory ExportBasePairLinesIfOtherStrandNotSelectedSet(
{required bool export_base_pair_lines_if_other_strand_not_selected}) =
_$ExportBasePairLinesIfOtherStrandNotSelectedSet._;

ExportBasePairLinesIfOtherStrandNotSelectedSet._();

static Serializer<ExportBasePairLinesIfOtherStrandNotSelectedSet> get serializer =>
_$exportBasePairLinesIfOtherStrandNotSelectedSetSerializer;

@memoized
int get hashCode;
}

abstract class ShowBasePairLinesWithMismatchesSet
with BuiltJsonSerializable
implements Action, Built<ShowBasePairLinesWithMismatchesSet, ShowBasePairLinesWithMismatchesSetBuilder> {
Expand Down
6 changes: 4 additions & 2 deletions lib/src/middleware/export_svg.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,11 @@ List<Element> get_selected_svg_elements(AppState state) {
BuiltSet<Strand> selected_strands = state.ui_state.selectables_store.selected_strands;
List<Element> selected_elts = [];
if (app.state.ui_state.base_pair_display_type != BasePairDisplayType.none) {
bool export_bp_if_other_not_selected =
app.state.ui_state.export_base_pair_lines_if_other_strand_not_selected;
var base_pairs = state.ui_state.show_base_pair_lines_with_mismatches
? state.design.selected_base_pairs_with_mismatches(selected_strands)
: state.design.selected_base_pairs(selected_strands);
? state.design.selected_base_pairs_with_mismatches(selected_strands, export_bp_if_other_not_selected)
: state.design.selected_base_pairs(selected_strands, export_bp_if_other_not_selected);
selected_elts.addAll(get_svg_elements_of_base_pairs(base_pairs));
}
selected_elts.addAll(get_svg_elements_of_strands(selected_strands));
Expand Down
3 changes: 2 additions & 1 deletion lib/src/middleware/oxdna_export.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ String to_oxview_format(Design design, List<Strand> strands_to_export) {
}

//TODO: this hasn't been tested well
var base_pairs_map = design.base_pairs_with_domain_strand(false, true, strands_to_export.toSet().build());
var base_pairs_map =
design.base_pairs_with_domain_strand(false, true, strands_to_export.toSet().build(), true);
for (int helix in base_pairs_map.keys) {
for (var offset_dom_strands in base_pairs_map[helix]!) {
int offset = offset_dom_strands.item1;
Expand Down
8 changes: 8 additions & 0 deletions lib/src/reducers/app_ui_state_reducer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ BasePairDisplayType base_pair_type_idx_reducer(

bool show_base_pair_lines_reducer(bool _, actions.ShowBasePairLinesSet action) => action.show_base_pair_lines;

bool export_base_pair_lines_if_other_strand_not_selected_reducer(
bool _, actions.ExportBasePairLinesIfOtherStrandNotSelectedSet action) =>
action.export_base_pair_lines_if_other_strand_not_selected;

bool show_base_pair_lines_with_mismatches_reducer(
bool _, actions.ShowBasePairLinesWithMismatchesSet action) =>
action.show_base_pair_lines_with_mismatches;
Expand Down Expand Up @@ -531,6 +535,10 @@ AppUIStateStorables app_ui_state_storable_local_reducer(AppUIStateStorables stor
storables.base_pair_display_type, action)
..show_base_pair_lines = TypedReducer<bool, actions.ShowBasePairLinesSet>(show_base_pair_lines_reducer)(
storables.show_base_pair_lines, action)
..export_base_pair_lines_if_other_strand_not_selected =
TypedReducer<bool, actions.ExportBasePairLinesIfOtherStrandNotSelectedSet>(
export_base_pair_lines_if_other_strand_not_selected_reducer)(
storables.export_base_pair_lines_if_other_strand_not_selected, action)
..show_base_pair_lines_with_mismatches = TypedReducer<bool, actions.ShowBasePairLinesWithMismatchesSet>(
show_base_pair_lines_with_mismatches_reducer)(storables.show_base_pair_lines_with_mismatches, action)
..export_svg_text_separately =
Expand Down
1 change: 1 addition & 0 deletions lib/src/serializers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ part 'serializers.g.dart';
SubstrandLabelSet,
DomainNameMismatch,
ShowBasePairLinesSet,
ExportBasePairLinesIfOtherStrandNotSelectedSet,
ShowBasePairLinesWithMismatchesSet,
ShowDomainNameMismatchesSet,
ShowUnpairedInsertionDeletionsSet,
Expand Down
3 changes: 3 additions & 0 deletions lib/src/state/app_ui_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ abstract class AppUIState with BuiltJsonSerializable implements Built<AppUIState

bool get show_base_pair_lines => storables.show_base_pair_lines;

bool get export_base_pair_lines_if_other_strand_not_selected =>
storables.export_base_pair_lines_if_other_strand_not_selected;

bool get show_base_pair_lines_with_mismatches => storables.show_base_pair_lines_with_mismatches;

double get strand_name_font_size => storables.strand_name_font_size;
Expand Down
3 changes: 3 additions & 0 deletions lib/src/state/app_ui_state_storables.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ abstract class AppUIStateStorables

bool get show_base_pair_lines;

bool get export_base_pair_lines_if_other_strand_not_selected;

bool get show_base_pair_lines_with_mismatches;

double get strand_name_font_size;
Expand Down Expand Up @@ -147,6 +149,7 @@ abstract class AppUIStateStorables
b.show_domain_labels = false;
b.base_pair_display_type = BasePairDisplayType.none;
b.show_base_pair_lines = false;
b.export_base_pair_lines_if_other_strand_not_selected = false;
b.show_base_pair_lines_with_mismatches = false;
b.strand_name_font_size = constants.default_strand_name_font_size;
b.strand_label_font_size = constants.default_strand_label_font_size;
Expand Down
40 changes: 28 additions & 12 deletions lib/src/state/design.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2152,21 +2152,23 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
/// maps each helix_idx to a list of offsets where there is a complementary base pair on each strand,
/// or when at least one of the strands lacks DNA (is null or is `?` wildcard)
@memoized
BuiltMap<int, BuiltList<int>> get base_pairs => this._base_pairs(false, strands.toBuiltSet());
BuiltMap<int, BuiltList<int>> get base_pairs => this._base_pairs(false, strands.toBuiltSet(), true);

/// maps each helix_idx to a list of offsets where there is a base on each strand,
/// NOT necessarily complementary
@memoized
BuiltMap<int, BuiltList<int>> get base_pairs_with_mismatches =>
this._base_pairs(true, strands.toBuiltSet());
this._base_pairs(true, strands.toBuiltSet(), true);

// returns a subset of base_pairs that is connected to selected_strands
BuiltMap<int, BuiltList<int>> selected_base_pairs(BuiltSet<Strand> selected_strands) =>
this._base_pairs(false, selected_strands);
BuiltMap<int, BuiltList<int>> selected_base_pairs(
BuiltSet<Strand> selected_strands, bool include_base_pair_lines_if_other_strand_not_selected) =>
this._base_pairs(false, selected_strands, include_base_pair_lines_if_other_strand_not_selected);

// returns a subset of base_pairs_with_mismatches that is connected to selected_strands
BuiltMap<int, BuiltList<int>> selected_base_pairs_with_mismatches(BuiltSet<Strand> selected_strands) =>
this._base_pairs(true, selected_strands);
BuiltMap<int, BuiltList<int>> selected_base_pairs_with_mismatches(
BuiltSet<Strand> selected_strands, bool include_base_pair_lines_if_other_strand_not_selected) =>
this._base_pairs(true, selected_strands, include_base_pair_lines_if_other_strand_not_selected);

/// Teturns a list of tuples (offset, d1, d2, s1, s2), where offset is the offset on the helix
/// of a base pair, and d1/d2/s1/s2 are the domains/strands in the base pair.
Expand All @@ -2175,7 +2177,10 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
/// Otherwise we only consider them paired if they both have DNA; [allow_mismatches] then determines
/// whether they need to be complementary to be considered a base pair.
BuiltMap<int, BuiltList<Tuple5<int, Domain, Domain, Strand, Strand>>> base_pairs_with_domain_strand(
bool allow_mismatches, bool allow_unassigned_dna, BuiltSet<Strand> selected_strands) {
bool allow_mismatches,
bool allow_unassigned_dna,
BuiltSet<Strand> selected_strands,
bool include_base_pair_lines_if_other_strand_not_selected) {
var base_pairs_with_domain_strand = Map<int, BuiltList<Tuple5<int, Domain, Domain, Strand, Strand>>>();
BuiltSet<Domain> selected_domains = selected_strands
.map((s) => s.substrands)
Expand All @@ -2193,8 +2198,18 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
// If not allowing unassigned DNA, then skip if either has no DNA sequence
continue;
}
if (!selected_domains.contains(dom1) || !selected_domains.contains(dom2)) {
continue;
if (include_base_pair_lines_if_other_strand_not_selected) {
// If we include base pair lines when one strand is not selected, we still need to check
// that the OTHER strand is selected.
if (!selected_domains.contains(dom1) && !selected_domains.contains(dom2)) {
continue;
}
} else {
// If we only include base pair lines when both strands are selected, then skip if either
// strand is not selected.
if (!selected_domains.contains(dom1) || !selected_domains.contains(dom2)) {
continue;
}
}
// We already checked that these domains are overlapping by calling
// `find_overlapping_domains_on_helix`, so the next call should not return null.
Expand Down Expand Up @@ -2234,9 +2249,10 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
return base_pairs_with_domain_strand.build();
}

BuiltMap<int, BuiltList<int>> _base_pairs(bool allow_mismatches, BuiltSet<Strand> selected_strands) {
var base_pairs_with_domain_strand =
this.base_pairs_with_domain_strand(allow_mismatches, true, selected_strands);
BuiltMap<int, BuiltList<int>> _base_pairs(bool allow_mismatches, BuiltSet<Strand> selected_strands,
bool include_base_pair_lines_if_other_strand_not_selected) {
var base_pairs_with_domain_strand = this.base_pairs_with_domain_strand(
allow_mismatches, true, selected_strands, include_base_pair_lines_if_other_strand_not_selected);
var base_pairs = Map<int, BuiltList<int>>();
for (int idx in base_pairs_with_domain_strand.keys) {
base_pairs[idx] = base_pairs_with_domain_strand[idx]!.map((x) => x.item1).toList().build();
Expand Down
4 changes: 2 additions & 2 deletions lib/src/view/design_main_base_pair_lines.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ class DesignMainBasePairLinesComponent extends UiComponent2<DesignMainBasePairLi
List<ReactElement> create_base_pair_lines_components(BuiltSet<Strand> strands) {
List<ReactElement> base_pair_lines_components = [];
BuiltMap<int, BuiltList<int>> base_pairs = props.with_mismatches
? props.design.selected_base_pairs_with_mismatches(strands)
: props.design.selected_base_pairs(strands);
? props.design.selected_base_pairs_with_mismatches(strands, true)
: props.design.selected_base_pairs(strands, true);

for (int helix_idx in base_pairs.keys) {
if (!props.only_display_selected_helices || props.side_selected_helix_idxs.contains(helix_idx)) {
Expand Down
4 changes: 2 additions & 2 deletions lib/src/view/design_main_base_pair_rectangle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class DesignMainBasePairRectangleComponent extends UiComponent2<DesignMainBasePa
List<ReactElement> create_base_pair_lines_components(BuiltSet<Strand> strands) {
List<ReactElement> base_pair_lines_components = [];
BuiltMap<int, BuiltList<int>> base_pairs = props.with_mismatches
? props.design.selected_base_pairs_with_mismatches(strands)
: props.design.selected_base_pairs(strands);
? props.design.selected_base_pairs_with_mismatches(strands, true)
: props.design.selected_base_pairs(strands, true);

for (int helix_idx in base_pairs.keys) {
if (!props.only_display_selected_helices || props.side_selected_helix_idxs.contains(helix_idx)) {
Expand Down
16 changes: 15 additions & 1 deletion lib/src/view/menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@ or real coordinates in nanometers, depending on whether a grid is selected).'''
..key = 'view_menu_base_pairs-dropdown'
..className = 'submenu_item')([
(MenuBoolean()
..value = props.state.ui_state.base_pair_display_type.toIndex() == 1
..value = props.state.ui_state.base_pair_display_type == BasePairDisplayType.lines
..display = 'Display as ${BasePairDisplayType.lines.display_name()}'
..key = 'base-pair-display-lines'
..on_change = (_) {
Expand All @@ -919,6 +919,20 @@ or real coordinates in nanometers, depending on whether a grid is selected).'''
app.dispatch(actions.BasePairTypeSet(selected_idx: BasePairDisplayType.lines.toIndex()));
}
})(),
(MenuBoolean()
..value = props.state.ui_state.export_base_pair_lines_if_other_strand_not_selected
..hide = props.state.ui_state.base_pair_display_type != BasePairDisplayType.lines
..display = 'Export base pair lines if other strand not selected'
..tooltip = '''\
When exporting an image of strands while base pair lines are displayed, either through
Edit-->Copy/Paste/Select-->Copy image (Ctrl+I), or through
Export-->SVG of selected strands, where it is possible that some strands are not selected,
but are bound to strands that are selected, this determines whether to export the base pair
lines connecting the exported strand to the other (non-exported) strand.'''
..on_change = ((_) => app.dispatch(actions.ExportBasePairLinesIfOtherStrandNotSelectedSet(
export_base_pair_lines_if_other_strand_not_selected:
!props.state.ui_state.export_base_pair_lines_if_other_strand_not_selected)))
..key = 'display-major-tick-offsets-on-all-helices')(),
(MenuBoolean()
..value = props.state.ui_state.base_pair_display_type.toIndex() == 2
..display = 'Display as ${BasePairDisplayType.rectangle.display_name()}'
Expand Down

0 comments on commit af47af7

Please sign in to comment.