Skip to content

Commit

Permalink
Merge branch 'maintenance/2020.02.15' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
tomka committed Mar 15, 2020
2 parents e34f711 + e6cf2cf commit 7082147
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 20 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
confirmation dialog is shown), removal buttons are now only shown if the "Show
removal options" checkbox is enabled in the Main tab.

- Neuron search: expanded elements (e.g. annotations) are now sorted correctly
within their expansion group. This also fixes the out-of-order expansion
display.

## 2020.02.15

Contributors: Chris Barnes, Andrew Champion, Stephan Gerhard, Pat Gunn, Tom Kazimiers
Expand Down
78 changes: 78 additions & 0 deletions django/applications/catmaid/static/js/extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,84 @@ $.fn.dataTable.ext.oSort['hslcolor-desc'] = function(a, b) {
return -1 * CATMAID.tools.compareHSLColors(a, b);
};

let compareHierarchicalName = function(a, b, reverse=false) {
let samePath = 0;
let [aPath, aNamePath] = a;
let [bPath, bNamePath] = b;

// Find the first unequal path element and compare only it.
let minPathLength = Math.min(aPath.length, bPath.length);
let firstDifferentIndex = -1;
for (let i=0; i<minPathLength; ++i) {
if (aPath[i] !== bPath[i]) {
firstDifferentIndex = i;
break;
}
}

if (firstDifferentIndex === -1) {
if (aPath.length > bPath.length) return 1;
if (aPath.length < bPath.length) return -1;
return 0;
}
return reverse ?
CATMAID.tools.compareStrings(bNamePath[firstDifferentIndex], aNamePath[firstDifferentIndex]) :
CATMAID.tools.compareStrings(aNamePath[firstDifferentIndex], bNamePath[firstDifferentIndex]);
};

/**
* We assume we sort search entities.
*/
$.fn.dataTable.ext.oSort['hierarchical-search-name-asc'] = function(a, b) {
return compareHierarchicalName(a, b);
};

/**
* We assume we sort search entities.
*/
$.fn.dataTable.ext.oSort['hierarchical-search-name-desc'] = function(a, b) {
return compareHierarchicalName(a, b, true);
};

let compareHierarchicalType = function(a, b, reverse=false) {
let samePath = 0;
let [aPath, aTypePath] = a;
let [bPath, bTypePath] = b;

// Find the first unequal path element and compare only it.
let minPathLength = Math.min(aPath.length, bPath.length);
let firstDifferentIndex = -1;
for (let i=0; i<minPathLength; ++i) {
if (aPath[i] !== bPath[i]) {
firstDifferentIndex = i;
break;
}
}

if (firstDifferentIndex === -1) {
if (aPath.length > bPath.length) return 1;
if (aPath.length < bPath.length) return -1;
return 0;
}
return reverse ?
CATMAID.tools.compareStrings(bTypePath[firstDifferentIndex], aTypePath[firstDifferentIndex]) :
CATMAID.tools.compareStrings(aTypePath[firstDifferentIndex], bTypePath[firstDifferentIndex]);
};

/**
* We assume we sort search entities.
*/
$.fn.dataTable.ext.oSort['hierarchical-search-type-asc'] = function(a, b) {
return compareHierarchicalType(a, b);
};

/**
* We assume we sort search entities.
*/
$.fn.dataTable.ext.oSort['hierarchical-search-type-desc'] = function(a, b) {
return compareHierarchicalType(a, b, true);
};

/**
* Add case insensitive :contains content filter.
* Based on: https://stackoverflow.com/questions/187537
Expand Down
130 changes: 110 additions & 20 deletions django/applications/catmaid/static/js/widgets/neuron-search.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
this.expansions = new Map();
// Map entity IDs to entities
this.entityMap = {};
// A map of path IDs vs. entity ID path lists
this.paths = {};

this.entity_selection_map = {};
this.pid = project.id;
Expand Down Expand Up @@ -223,12 +225,6 @@
// Replace {{NA-ID}} with the actual widget ID
content.innerHTML = container_html.replace(/{{NA-ID}}/g, this.widgetID);

// Add a header click handler to know when a sorting event happens
$("thead th:nth-child(-n+2)", content).on('click', (function() {
// Remove any expansions before sorting
this.removeAllExpansions();
}).bind(this));

// Add a container that gets displayed if no results could be found
var no_results = document.createElement('div');
no_results.setAttribute('id', 'neuron_annotations_query_no_results' + this.widgetID);
Expand Down Expand Up @@ -574,7 +570,19 @@
var requestAnnotationUpdate = false;

return function() {
let widget = this;
var selector = 'table#neuron_annotations_query_results_table' + this.widgetID;

let nns = CATMAID.NeuronNameService.getInstance();
let asName = entityId => {
let entity = widget.entityMap[entityId];
return entity.type === 'neuron' ? nns.getName(entity.skeleton_ids[0]) : entity.name;
};
let asType = entityId => {
let entity = widget.entityMap[entityId];
return entity.type;
};

var datatable = $(selector).DataTable({
destroy: true,
dom: "lrptip",
Expand All @@ -586,8 +594,51 @@
order: this.order,
processing: true,
columns: [
{ "orderable": true },
{ "orderable": true },
{
"type": "hierarchical-search-name",
"orderable": true,
"render": function(data, type, row, meta) {
// For sorting, append the expansion information
if (type === 'display') {
return data;
} else {
let keyMatch = new RegExp('key="([0-9\-]\+)"').exec(data);
if (!keyMatch || !keyMatch[1]) return data;
let key = keyMatch[1];
let entityIdMatch = new RegExp('entity_id="([0-9]\+)"').exec(data);
if (!entityIdMatch || !entityIdMatch[1]) return data;
let entityId = entityIdMatch[1];
let name = asName(entityId);
let entity = widget.entityMap[entityId];
if (type === "sort") {
let path = widget.paths[key];
return [path, path.map(asName)];
} else {
return name;
}
}
},
},
{
"type": "hierarchical-search-type",
"orderable": true,
"render": function(data, type, row, meta) {
// For sorting, append the expansion information
if (type === 'display') {
return data;
} else {
let keyMatch = new RegExp('key="([0-9\-]\+)"').exec(data);
if (!keyMatch || !keyMatch[1]) return data;
let key = keyMatch[1];
if (type === "sort") {
let path = widget.paths[key];
return [path, path.map(asType)];
} else {
return data;
}
}
},
},
{ "orderable": false, "visible": this.displayAnnotations }
],
language: {
Expand All @@ -604,6 +655,30 @@
// After every page chage, annotations should be updated. This can't be
// done directly, because this event happens before redrawing.
requestAnnotationUpdate = true;
}).on('order.dt', this, function(e) {
this.order = datatable.order();
// Update header sort icon. For some reason this doesn't change
// automatically with DOM data.
let [sortCol, sortDir] = this.order && this.order.length > 0 ? this.order[0] : [null, null];
$(`${selector} th`).each((i, element) => {
if (element.classList.contains('sorting_asc')) {
element.classList.remove('sorting_asc');
element.classList.add("sorting");
} else if (element.classList.contains('sorting_desc')) {
element.classList.remove('sorting_desc');
element.classList.add("sorting");
}
if (i === sortCol) {
if (sortDir == 'asc') {
element.classList.remove('sorting');
element.classList.add("sorting_asc");
}
else {
element.classList.remove('sorting');
element.classList.add("sorting_desc");
}
}
});
});
};
})();
Expand All @@ -614,10 +689,12 @@
* avoid repetitions for cycling annotations.
*/
NeuronSearch.prototype.appendEntities = function(entities, appender, indent,
expandedIds, sourceSlot) {
expandedIds, sourceSlot, path = []) {
// Mark entities as unselected and create result table rows
entities.forEach(function(entity) {
var tr = this.add_result_table_row(entity, appender, indent);
var tr = this.add_result_table_row(entity, appender, indent, undefined, path);
let newPath = [...path, entity.id];
this.paths[newPath.join('-')] = newPath;
// Add source information, if this entry resulted from expansion
if (sourceSlot) {
tr.setAttribute('expansion', sourceSlot);
Expand All @@ -631,7 +708,7 @@
// Add entity ID to stack to not expand it twice
expandedIds.push(entity.id);
this.appendEntities(expandedEntities, appender, indent + 1, expandedIds,
expansionSlot);
expansionSlot, newPath);
// Remove ID from expansion stack, now that it is expanded
expandedIds.pop();
}
Expand All @@ -644,14 +721,15 @@
* steps that should be used.
*/
NeuronSearch.prototype.add_result_table_row = function(entity, add_row_fn,
indent, selected)
indent, selected, path=[])
{
// Build table row
var tr = document.createElement('tr');
tr.setAttribute('class', 'neuron_annotation_result_row' +
this.widgetID + '_' + entity.id);
tr.setAttribute('type', entity.type);
tr.dataset.entityId = entity.id;
tr.dataset.key = [...path, entity.id].join('-');
tr.entity = entity;

// Checkbox & name column, potentially indented
Expand All @@ -664,6 +742,7 @@
var cb = document.createElement('input');
cb.setAttribute('type', 'checkbox');
cb.setAttribute('entity_id', entity.id);
cb.dataset.key = [...path, entity.id].join('-');
cb.setAttribute('class', 'result' + this.widgetID + '_' +
entity.id);
cb.checked = !!selected;
Expand All @@ -683,8 +762,10 @@

// Type column
var td_type = document.createElement('td');
td_type.appendChild(document.createTextNode(
entity.type));
var span_type = document.createElement('span');
span_type.dataset.key = [...path, entity.id].join('-');
span_type.appendChild(document.createTextNode(entity.type));
td_type.appendChild(span_type);
tr.appendChild(td_type);

// Annotations column
Expand Down Expand Up @@ -943,6 +1024,7 @@
this.entityMap = {};
this.expansions.clear();
this.queryResults = [];
this.paths = {};
this.queryResults[0] = e.entities;
this.total_n_results = e.entities.length;
// Get new models for notification
Expand Down Expand Up @@ -1147,6 +1229,8 @@
var aID = CATMAID.annotations.getID(annotation);
var tr = $(this).closest('tr');
var entity = $(tr)[0].entity;
var key = $(tr)[0].dataset.key;


// If expanded, collapse it. Expand it otherwise.
if (tr.is('[expanded]')) {
Expand Down Expand Up @@ -1180,6 +1264,7 @@

// Update current result table classes
self.update_result_row_classes();
self.refresh();

// Find all unique skeletons that now are not available anymore from
// this widget (i.e. that are not part of any other expansion or the
Expand Down Expand Up @@ -1225,6 +1310,7 @@
'annotated_with': aID,
'with_annotations': self.displayAnnotations
};
let path = [];
CATMAID.fetch(project.id + '/annotations/query-targets', 'POST', query_data)
.then(function(e) {
// Register search results with neuron name service and rebuild
Expand Down Expand Up @@ -1252,13 +1338,16 @@

// Mark entities as selected if they are markes as such in the
// widget.
e.entities.filter(function(entity, i, a) {
let selected = !!self.entity_selection_map[entity.id];
self.entity_selection_map[entity.id] = selected;
if(!(entity.id in self.entityMap)) {
self.entityMap[entity.id] = entity;
let path = self.paths[$(tr)[0].dataset.key];
e.entities.filter(function(subEntity, i, a) {
let selected = !!self.entity_selection_map[subEntity.id];
self.entity_selection_map[subEntity.id] = selected;
if(!(subEntity.id in self.entityMap)) {
self.entityMap[subEntity.id] = subEntity;
}
self.add_result_table_row(entity, appender, indent + 1, selected);
let newPath = [...path, subEntity.id];
self.paths[newPath.join('-')] = newPath;
self.add_result_table_row(subEntity, appender, indent + 1, selected, newPath);
});

// The order of the query result array doesn't matter.
Expand All @@ -1267,6 +1356,7 @@
self.expansions.set(entity, sub_id);
// Update current result table classes
self.update_result_row_classes();
self.refresh();
// Announce new models, if any
if (newSkeletons.length > 0) {
var newModels = newSkeletons.reduce(function(o, skid) {
Expand Down

0 comments on commit 7082147

Please sign in to comment.