diff --git a/Capfile b/Capfile
index eb67ee33..c56719e1 100644
--- a/Capfile
+++ b/Capfile
@@ -12,10 +12,12 @@ require 'capistrano/rbenv'
require 'capistrano/puma'
install_plugin Capistrano::Puma
+install_plugin Capistrano::Puma::Systemd
install_plugin Capistrano::Puma::Monit
require 'capistrano/sidekiq'
-require 'capistrano/sidekiq/monit'
+install_plugin Capistrano::Sidekiq
+install_plugin Capistrano::Sidekiq::Monit
require 'whenever/capistrano'
diff --git a/app/assets/javascripts/datatable_initialisation.js b/app/assets/javascripts/datatable_initialisation.js
new file mode 100644
index 00000000..a661282b
--- /dev/null
+++ b/app/assets/javascripts/datatable_initialisation.js
@@ -0,0 +1,361 @@
+jQuery(function() {
+ // FREEZERS
+ $('#freezers').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#freezers').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 3 }
+ ],
+ "order": [ 2, 'desc' ]
+ } );
+
+ //HERBARIA
+ $('#herbaria').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#herbaria').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 3 }
+ ],
+ "order": [1, 'asc']
+ } );
+
+ // ISOLATES
+ $('#isolates').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#isolates').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 5 }
+ ],
+ "order": [ 4, 'desc' ]
+ } );
+
+ $('#isolates_no_specimen').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#isolates_no_specimen').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 5 }
+ ],
+ "order": [ 0, 'asc' ]
+ } );
+
+ $('#isolates_duplicates').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#isolates_duplicates').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 5 }
+ ],
+ "order": [ 0, 'desc' ]
+ } );
+
+ // LAB RACKS
+ $('#lab_racks').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#lab_racks').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 5 }
+ ],
+ "order": [ 0, 'desc' ]
+ } );
+
+ // MARKERS
+ $('#markers').DataTable( {
+ "columnDefs": [
+ { "orderable": false, "targets": 2 }
+ ],
+ "order": [ 7, 'desc' ]
+ } );
+
+ // MICRONIC PLATES
+ $('#micronic_plates').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#micronic_plates').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 6 }
+ ],
+ "order": [ 5, 'desc' ]
+ } );
+
+ // PLANT PLATES
+ $('#plant_plates').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#plant_plates').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 2 }
+ ],
+ "order": [ 1, 'desc' ]
+ } );
+
+ // PRIMERS
+ $('#primers').DataTable( {
+ "order": [ 4, 'desc' ],
+ "columnDefs": [
+ { "orderable": false, "targets": 5 }
+ ]
+ } );
+
+ // USERS
+ $('#users').DataTable( {
+ "columnDefs": [
+ { "orderable": false, "targets": 3 },
+ { "orderable": false, "targets": 7 }
+ ]
+ } );
+
+ // CLUSTERS
+ $('#clusters').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#clusters').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 5 }
+ ],
+ "order": [ 4, 'desc' ]
+ } );
+
+ // CONTIG SEARCHES
+ $('#contig_searches').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#contig_searches').data('source'),
+ "columnDefs": [{
+ "targets": 3,
+ "orderable": false
+ }],
+ "order": [ 2, 'desc' ]
+ } );
+
+ $('#contig_search_results').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#contig_search_results').data('source'),
+ "columnDefs": [{
+ "targets": [1, 2, 5],
+ "orderable": false
+ }],
+ "order": [ 0, 'asc' ]
+ } );
+
+ // CONTIGS
+ $('#contigs').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#contigs').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 5 }
+ ],
+ "order": [ 4, 'desc' ]
+ } );
+
+ $('#contigs-duplicates').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#contigs-duplicates').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 5 }
+ ],
+ "order": [ 0, 'asc' ]
+ } );
+
+ // INDIVIDUAL SEARCHES
+ $('#individual_searches').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#individual_searches').data('source'),
+ "columnDefs": [{
+ "orderable": false,
+ "targets": 3
+ }],
+ "order": [ 2, 'desc' ]
+ } );
+
+ $('#individual_search_results').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#individual_search_results').data('source'),
+ "columnDefs": [{
+ "orderable": false,
+ "targets": [1, 6]
+ }],
+ "order": [ 0, 'asc' ]
+ } );
+
+ // INDIVIDUALS
+ $('#individuals').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#individuals').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 6 }
+ ],
+ "order": [5, 'desc']
+ } );
+
+ // ISSUES
+ $('#issues').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#issues').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 1 },
+ { "orderable": false, "targets": 2 }
+ ],
+ "order": [ 3, 'desc' ]
+ } );
+
+ // MARKERS SEQUENCE SEARCHES
+ $('#marker_sequence_searches').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#marker_sequence_searches').data('source'),
+ "columnDefs": [{
+ "orderable": false,
+ "targets": 3
+ }],
+ "order": [ 2, 'desc' ]
+ } );
+
+ $('#marker_sequence_search_results').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#marker_sequence_search_results').data('source'),
+ "columnDefs": [{
+ "orderable": false,
+ "targets": [1, 3]
+ }],
+ "order": [ 0, 'asc' ]
+ } );
+
+ // MARKER SEQUENCES
+ $('#marker_sequences').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#marker_sequences').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 3 }
+ ],
+ "order": [ 2, 'desc' ]
+ } );
+
+ // MISLABEL ANALYSES
+ $('#mislabel_analyses').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#mislabel_analyses').data('source'),
+ "columnDefs": [{
+ "orderable": false,
+ "targets": [1, 4]
+ }],
+ "order": [3, 'desc']
+ } );
+
+ $('#mislabel_analysis_results').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#mislabel_analysis_results').data('source'),
+ "columnDefs": [{
+ "orderable": false,
+ "targets": 6
+ }],
+ "order": [0, 'asc']
+ } );
+
+ // NGS RUNS
+ $('#ngs_runs').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#ngs_runs').data('source'),
+ "columnDefs": [{
+ "targets": 2,
+ "orderable": false
+ }],
+ "order": [1, 'desc']
+ } );
+
+ $('#ngs_run_results').dataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#ngs_run_results').data('source'),
+ "sScrollY": document.body.clientHeight * 70 / 100,
+ "scrollX": true,
+ scroller: {
+ loadingIndicator: true,
+ displayBuffer: 2
+ },
+ deferRender: true,
+ "columnDefs": [{
+ "targets": 3,
+ "orderable": false
+ }],
+ "order": [0, 'desc']
+ } );
+
+ // PRIMER READS
+ $('#primer_reads').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#primer_reads').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 4 }
+ ],
+ "order": [ 3, 'desc' ]
+ } );
+
+ $('#primer_reads-duplicates').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#primer_reads-duplicates').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 4 }
+ ],
+ "order": [ 0, 'asc' ]
+ } );
+
+ $('#reads_without_contigs').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#reads_without_contigs').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 4 }
+ ],
+ "order": [ 3, 'desc' ]
+ } );
+
+ // TAXA
+ $('#associated_individuals').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#associated_individuals').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 6 }
+ ],
+ "order": [5, 'desc']
+ } );
+
+ $('#direct_children').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#direct_children').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 5 }
+ ],
+ "order": [ 0, 'asc' ]
+ } );
+
+ $('#orphans').DataTable( {
+ bProcessing: true,
+ bServerSide: true,
+ sAjaxSource: $('#orphans').data('source'),
+ "columnDefs": [
+ { "orderable": false, "targets": 5 }
+ ],
+ "order": [ 0, 'asc' ]
+ } );
+} );
diff --git a/app/assets/javascripts/progress_overview.js b/app/assets/javascripts/progress_overview.js
index 7f6704e3..1d17d7e3 100644
--- a/app/assets/javascripts/progress_overview.js
+++ b/app/assets/javascripts/progress_overview.js
@@ -50,16 +50,28 @@ function changeDownloadButtonStatus() {
function drawProgressTree(data) {
var parentDiv = document.getElementById("progress_tree");
- var width = parentDiv.clientWidth - 17, // subtract padding and border width
- height = 710,
- scale = 1,
- radius = Math.max(width/2 - 100, 500),
- nodeRadius = 2;
+ var width = parentDiv.clientWidth,
+ height = 710;
- var treeLayout = d3.cluster().size([2 * Math.PI, radius - 100]);
+ var root = d3.hierarchy(data)
+ .sort((a, b) => d3.ascending(a.data.scientific_name, b.data.scientific_name));
- var root = treeLayout(d3.hierarchy(data)
- .sort((a, b) => d3.ascending(a.data.scientific_name, b.data.scientific_name)));
+ var maxChildren = 0;
+ var leaveCnt = 0;
+ root.descendants().forEach(function(d) {
+ if (!d.children){
+ maxChildren = Math.max(maxChildren, d.data.size);
+ leaveCnt++;
+ }
+ })
+
+ var radius = Math.max(width/2, leaveCnt * 30 / (2 * Math.PI)); // Calculate tree radius from number of leave nodes with a minimum distance of 30
+ var scale = height / ( 2 * radius);
+
+ var treeLayout = d3.cluster()
+ .size([2 * Math.PI, radius - 150]);
+
+ treeLayout(root);
var svg = d3.select('#progress_tree')
.append("svg")
@@ -82,13 +94,13 @@ function drawProgressTree(data) {
svg.call(zoom);
// Trigger initial zoom with an initial transform
- zoom.transform(svg, d3.zoomIdentity.translate(width / 2, radius).scale(scale));
+ zoom.transform(svg, d3.zoomIdentity.translate($("#progress_svg").width() / 2, height/2).scale(scale)); // Initially zoom out to show full tree
// Button to reset zoom and position
d3.select("#reset_zoom")
.on("click", function() {
var current_width = $("#progress_svg").width();
- zoom.transform(svg, d3.zoomIdentity.translate(current_width / 2, radius).scale(scale));
+ zoom.transform(svg, d3.zoomIdentity.translate(current_width / 2, height/2).scale(scale));
});
enableButton($('#reset_zoom'));
@@ -152,11 +164,13 @@ function drawProgressTree(data) {
})
.on("mousemove", function(d) {
var finished_percent = d.data.size === 0 ? '' : " (" + ((d.data.finished_size / d.data.size) * 100).toFixed(2) + "%)";
+ var sidebar_width = $("#sidebar_progress_tree").width();
+
tooltip
.html(d.data.scientific_name + ":
" + d.data.size + " species (incl. subspecies)
" + d.data.finished_size
+ " finished" + finished_percent)
- .style("left", (d3.event.pageX - parentDiv.offsetLeft + 10) + "px")
- .style("top", (d3.event.pageY - parentDiv.offsetTop + 20) + "px");
+ .style("left", (d3.event.pageX - document.getElementById('chart_column').offsetLeft + 20) + "px")
+ .style("top", (d3.event.pageY - document.getElementById('chart_column').offsetTop - document.getElementById('progress_tree').offsetTop + 28) + "px");
})
.on("mouseout", function mouseout() {
tooltip.transition()
@@ -168,8 +182,14 @@ function drawProgressTree(data) {
.style("font-weight", 'normal');
});
+ var nodeRadiusMin = 4;
+ var nodeRadiusMax = 40;
+ var nodeSizeScale = d3.scaleLinear()
+ .domain([0, maxChildren])
+ .range([nodeRadiusMin, nodeRadiusMax]);
+
nodeEnter.append('circle')
- .attr('r', d => d.children ? nodeRadius : ((d.data.size / 5) + 1))
+ .attr('r', d => d.children ? nodeRadiusMin : nodeSizeScale(d.data.size))
.style("stroke", "#555")
.attr("fill", function(d) { return nodeColor((d.data.finished_size / d.data.size) * 100) })
.attr("transform", d => `
@@ -187,7 +207,7 @@ function drawProgressTree(data) {
`)
.attr("dy", "0.31em")
.attr("x", function(d) {
- r = d.children ? nodeRadius : ((d.data.size / 5) + 1);
+ r = d.children ? nodeRadiusMin : nodeSizeScale(d.data.size);
return d.x < Math.PI === !d.children ? (r + 6) : (0 - r - 6);
})
.attr("text-anchor", d => d.x < Math.PI === !d.children ? "start" : "end")
diff --git a/app/assets/javascripts/selects_with_select2.js b/app/assets/javascripts/selects_with_select2.js
new file mode 100644
index 00000000..3fbdc26b
--- /dev/null
+++ b/app/assets/javascripts/selects_with_select2.js
@@ -0,0 +1,168 @@
+jQuery(function() {
+ // CLUSTERS
+ $('#cluster_isolate_id').select2((select2AutocompleteOptions("/isolates/filter", "an isolate")));
+
+ // CONTIGS
+ $('#contig_isolate_id').select2((select2AutocompleteOptions("/isolates/filter", "an isolate")));
+ $('#contig_marker_id').select2((select2AutocompleteOptions("/markers/filter", "a marker")));
+ $('#contig_marker_sequence_id').select2((select2AutocompleteOptions("/marker_sequences/filter", "a marker sequence")));
+ $('#contig_project_ids').select2(select2MultiselectOptions);
+
+ // CONTIG SEARCHES
+ $('#contig_search_name').select2((select2AutocompleteOptions("/contigs/filter", "a contig")));
+ $('#contig_search_marker').select2(select2SingleSelectOptions('a marker'));
+ $('#contig_search_specimen').select2((select2AutocompleteOptions("/individuals/filter", "a specimen")));
+ $('#contig_search_taxon').select2((select2AutocompleteOptions("/taxa/filter", "a taxon")));
+ $('#contig_search_verified_by').select2(select2SingleSelectOptions('a user'));
+
+ // FREEZERS
+ $('#freezer_lab_id').select2(select2SingleSelectOptions('a lab'));
+ $('#freezer_project_ids').select2(select2MultiselectOptions);
+
+ // INDIVIDUALS
+ $('#individual_taxon_id').select2((select2AutocompleteOptions("/taxa/filter", "a taxon")));
+ $('#individual_herbarium_id').select2(select2SingleSelectOptions('an herbarium'));
+ $('#individual_tissue_id').select2(select2SingleSelectOptions('a tissue type'));
+ $('#individual_project_ids').select2(select2MultiselectOptions);
+
+ // INDIVIDUAL SEARCHES
+ $('#individual_search_specimen_id').select2((select2AutocompleteOptions("/individuals/filter", "a specimen")));
+ $('#individual_search_herbarium_id').select2(select2SingleSelectOptions('an herbarium'));
+ $('#individual_search_taxon').select2((select2AutocompleteOptions("/taxa/filter", "a taxon")));
+
+ // ISOLATES
+ $('#isolate_project_ids').select2(select2MultiselectOptions);
+ $('#isolate_individual_id').select2((select2AutocompleteOptions("/individuals/filter", "a specimen")));
+ $('#isolate_tissue_id').select2(select2SingleSelectOptions('a tissue type'));
+ $('#isolate_plant_plate_id').select2(select2SingleSelectOptions('a plate'));
+ $('#isolate_user_id').select2(select2SingleSelectOptions('a user'));
+
+ // ISSUES
+ $('#issue_project_ids').select2(select2MultiselectOptions);
+
+ // LAB RACKS
+ $('#lab_rack_shelf_id').select2(select2SingleSelectOptions('a shelf'));
+ $('#lab_rack_project_ids').select2(select2MultiselectOptions);
+
+ // LABS
+ $('#lab_project_ids').select2(select2MultiselectOptions);
+
+ // MARKERS
+ $('#marker_project_ids').select2(select2MultiselectOptions);
+
+ // MARKER SEQUENCES
+ $('#marker_sequence_isolate_id').select2((select2AutocompleteOptions("/isolates/filter", "an isolate")));
+ $('#marker_sequence_marker_id').select2(select2SingleSelectOptions('a marker'));
+ $('#marker_sequence_project_ids').select2(select2MultiselectOptions);
+
+ // MARKER SEQUENCE SEARCHES
+ $('#marker_sequence_search_name').select2((select2AutocompleteOptions("/marker_sequences/filter", "a marker sequence name")));
+ $('#marker_sequence_search_marker').select2(select2SingleSelectOptions('a marker'));
+ $('#marker_sequence_search_specimen').select2((select2AutocompleteOptions("/individuals/filter", "a specimen")));
+ $('#marker_sequence_search_taxon').select2((select2AutocompleteOptions("/taxa/filter", "a taxon")));
+ $('#marker_sequence_search_verified_by').select2(select2SingleSelectOptions('a user'));
+
+ // MICRONIC PLATES
+ $('#micronic_plate_lab_rack_id').select2(select2SingleSelectOptions('a rack'));
+ $('#micronic_plate_project_ids').select2(select2MultiselectOptions);
+
+ // NGS RUNS
+ $('#ngs_run_taxon_id').select2((select2AutocompleteOptions("/taxa/filter", "a taxon")));
+
+ // PLANT PLATES
+ $('#plant_plate_project_ids').select2(select2MultiselectOptions);
+
+ // PRIMER READS
+ $('#primer_read_contig_id').select2((select2AutocompleteOptions("/contigs/filter", "a contig")));
+ $('#primer_read_project_ids').select2(select2MultiselectOptions);
+
+ // PRIMERS
+ $('#primer_marker_id').select2(select2SingleSelectOptions('a marker'));
+ $('#primer_project_ids').select2(select2MultiselectOptions);
+
+ // PROJECTS
+ $('#project_user_ids').select2(select2MultiselectOptions);
+ $( '#associated_project_ids').select2(select2MultiselectOptions);
+ $('#query_associated_taxon').select2((select2AutocompleteOptions("/taxa/filter", "a taxon")));
+
+ // SHELVES
+ $('#shelf_project_ids').select2(select2MultiselectOptions);
+
+ // TAXA
+ $( "#taxon_taxonomic_rank" ).select2(select2SingleSelectOptions('a rank'));
+ $( "#taxon_parent_id" ).select2(select2AutocompleteOptions("/taxa/filter", "a taxon"));
+ $('#taxon_project_ids').select2(select2MultiselectOptions);
+
+ $("#taxonomy_root_select").select2({
+ theme: "bootstrap",
+ placeholder: 'Select a taxon',
+ minimumResultsForSearch: 15,
+ allowClear: false,
+ width: 'auto'
+ });
+
+ $('#taxon_search').select2(select2AutocompleteOptions("/taxa/filter", "a taxon", 'auto'));
+
+ // USERS
+ $("#user_lab_id").select2(select2SingleSelectOptions('a lab'));
+ $("#user_responsibility_ids").select2(select2MultiselectOptions);
+ $("#user_project_ids").select2(select2MultiselectOptions);
+
+ // DEPRECATED
+ $('#family_project_ids').select2(select2MultiselectOptions);
+
+ $('#order_project_ids').select2(select2MultiselectOptions);
+
+ $('#species_project_ids').select2(select2MultiselectOptions);
+
+ $('#higher_order_taxon_marker_ids').select2(select2MultiselectOptions);
+
+ $('#higher_order_taxon_project_ids').select2(select2MultiselectOptions);
+});
+
+let select2MultiselectOptions = {
+ theme: "bootstrap",
+ width: '190px'
+};
+
+function select2SingleSelectOptions(recordName, width='190px') {
+ return {
+ theme: "bootstrap",
+ placeholder: 'Select ' + recordName,
+ minimumResultsForSearch: 15,
+ allowClear: true,
+ width: width
+ }
+}
+
+function select2AutocompleteOptions(filter_url, recordName, width='190px') {
+ return {
+ theme: "bootstrap",
+ minimumResultsForSearch: 15,
+ placeholder: 'Select ' + recordName,
+ allowClear: true,
+ minimumInputLength: 2,
+ width: width,
+ ajax: {
+ url: filter_url,
+ delay: 400,
+ data: function (params) {
+ return {
+ term: params.term
+ };
+ },
+ processResults: function (data) {
+ var results = [];
+ $.each(data, function(index, item){
+ results.push({
+ id: item.id,
+ text: item.name
+ });
+ });
+ return {
+ results: results
+ };
+ },
+ },
+ };
+}
diff --git a/app/assets/javascripts/taxa.js b/app/assets/javascripts/taxa.js
index d9a9f038..6162a235 100644
--- a/app/assets/javascripts/taxa.js
+++ b/app/assets/javascripts/taxa.js
@@ -2,54 +2,35 @@ jQuery(function() {
if (document.getElementById("taxonomy_tree") != null) {
initialize_buttons();
- $("#taxonomy_root_select").on("change", () => $.ajax({
- type: "GET",
- contentType: "application/json; charset=utf-8",
- url: 'taxa/taxonomy_tree',
- dataType: 'json',
- data: {
- root_id: $('#taxonomy_root_select option:selected').val()
- },
- success: function (data) {
- remove_selected_taxon_info();
- deleteVisualization('#taxonomy_tree');
+ loadTaxonomy();
- drawTaxonomy(data[0]);
- },
- error: function (_result) {
- console.error("Error getting data.");
- }
- }));
+ $("#taxonomy_root_select").on("change", () => loadTaxonomy());
}
+});
- $('#taxon_parent_name').autocomplete({
- source: $('#taxon_parent_name').data('autocomplete-source')});
-
- $('#taxon_project_ids').chosen({
- allow_single_deselect: true,
- no_results_text: 'No results matched'
- });
-
- $('#taxon_search').autocomplete({
- source: $('#taxon_search').data('autocomplete-source')});
-
- $('#orphans').DataTable({
- bProcessing: true,
- bServerSide: true,
- sAjaxSource: $('#orphans').data('source'),
- "columnDefs": [
- { "orderable": false, "targets": 5 }
- ],
- "order": [ 0, 'asc' ]
+function loadTaxonomy() {
+ $.ajax({
+ type: "GET",
+ contentType: "application/json; charset=utf-8",
+ url: 'taxa/taxonomy_tree',
+ dataType: 'json',
+ data: {
+ root_id: $('#taxonomy_root_select option:selected').val()
+ },
+ success: function (data) {
+ remove_selected_taxon_info();
+ deleteVisualization('#taxonomy_tree');
+
+ drawTaxonomy(data[0]);
+ },
+ error: function (_result) {
+ console.error("Error getting data.");
+ }
});
-});
+}
function initialize_buttons() {
- disableButton($("#center_root"), "Please load a taxonomy first");
- disableButton($("#reset_tree_pos"), "Please load a taxonomy first");
-
disableButton($("#center_selected_node"), "Please select a taxon first");
-
disableButton($("#edit_taxon"), "Please select a taxon first");
disableButton($("#delete_taxon"), "Please select a taxon first");
}
@@ -72,6 +53,7 @@ function drawTaxonomy(data) {
scale = 1;
var selected_node = null;
+ var selected_circle = null;
// Append the SVG object to the parent div
var svg = d3.select('#taxonomy_tree')
@@ -101,8 +83,8 @@ function drawTaxonomy(data) {
duration = 750,
root;
- // // Declares a tree layout and assigns the size
- var treemap = d3.tree().size([width, height]);
+ // Declares a tree layout and assigns the size
+ var treemap = d3.tree().separation(function(a, b) { return a.parent == b.parent ? 2.5 : 2; }).nodeSize([2 * nodeRadius, 4 * nodeRadius]);
// Assigns the data to a hierarchy using parent-child relationships
root = d3.hierarchy(data, function(d) {
@@ -115,21 +97,12 @@ function drawTaxonomy(data) {
update(root);
- centerNode(root);
-
- enableButton($("#center_root"), "Center root node");
- enableButton($("#reset_tree_pos"), "Align tree to top left");
-
- // Button to reset zoom and reset tree to top left
- d3.select("#reset_tree_pos")
- .on("click", function() {
- zoom.transform(svg, d3.zoomIdentity.translate(margin.left, margin.top).scale(scale));
- });
+ alignTreeLeft(root);
// Button to reset zoom and center root node
- d3.select("#center_root")
+ d3.select("#reset_position")
.on("click", function() {
- centerNode(root);
+ alignTreeLeft(root);
});
// Button to reset zoom and center root node
@@ -140,18 +113,12 @@ function drawTaxonomy(data) {
}
});
- d3.select('#start_search')
- .on("click", function() { startTaxonSearch() });
-
- document.getElementById('taxon_search')
- .addEventListener("keydown", function (e) {
- if (e.code === "Enter") {
- startTaxonSearch();
- }
+ $('#taxon_search').on('select2:select', function (e) {
+ startTaxonSearch();
});
function startTaxonSearch() {
- var taxon_name = document.getElementById('taxon_search').value;
+ var taxon_name = $('#taxon_search').select2('data')[0].text;
$.ajax({
type: "GET",
@@ -170,23 +137,8 @@ function drawTaxonomy(data) {
}
function update(source) {
- var levelHeight = [1];
- var childCount = function(level, n) {
- if (n.children && n.children.length > 0) {
- if (levelHeight.length <= level + 1) levelHeight.push(0);
-
- levelHeight[level + 1] += n.children.length;
- n.children.forEach(function(d) {
- childCount(level + 1, d);
- });
- }
- };
- childCount(0, root);
- var newHeight = d3.max(levelHeight) === 2 ? 2 * 50 + (25 * levelHeight.length) : d3.max(levelHeight) * 50; // Account for diagonals in height calculation
-
- treemap = treemap.size([newHeight, width]);
-
- treeData = treemap(root);
+ // Assigns the x and y position for the nodes
+ var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
@@ -249,29 +201,7 @@ function drawTaxonomy(data) {
.attr("fill-opacity", 1)
.style('font', '14px sans-serif')
.on('click', function(d) {
- // Display taxon info in top left div
- var text = "Scientific name: " + htmlSafe(d.data.scientific_name) + "
";
- if (d.data.taxonomic_rank) text += "Taxonomic rank: " + htmlSafe(d.data.taxonomic_rank) + "
";
- if (d.data.synonym) text += "Synonym: " + htmlSafe(d.data.synonym) + "
";
- if (d.data.common_name) text += "Common name: " + htmlSafe(d.data.common_name) + "
";
- if (d.data.author) text += "Author: " + htmlSafe(d.data.author) + "
";
- if (d.data.comment) text += "Comment: " + htmlSafe(d.data.comment) + "
";
- taxon_text.html(text);
-
- // Set correct taxon edit and destroy links and enable buttons
- var taxon_edit_link = d3.select('#edit_taxon').attr('href').replace(/(.*\/)(\d+)(\/.*)/, "$1" + d.data.id + "$3");
- d3.select('#edit_taxon').attr('href', taxon_edit_link);
- enableButton($('#edit_taxon'), 'Edit entry in a new tab');
-
- var taxon_delete_link = d3.select('#delete_taxon').attr('href').replace(/(.*\/)(\d+)/, "$1" + d.data.id);
- d3.select('#delete_taxon').attr('href', taxon_delete_link);
- enableButton($('#delete_taxon'), 'Delete taxon entry');
-
- selected_node = d;
- enableButton($('#center_selected_node'), 'Center currently selected node');
-
- // Display list of specimen associated with this taxon
- display_specimen_Data(d);
+ selectNode(d, this);
});
// UPDATE
@@ -379,6 +309,16 @@ function drawTaxonomy(data) {
}.bind(this)) : toggle(d);
}
+ function alignTreeLeft(root) {
+ x = margin.left;
+ y = -root.x0 + height / 2;
+
+ d3.select('g').transition()
+ .duration(duration)
+ .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
+ zoom.transform(svg, d3.zoomIdentity.translate(x, y).scale(scale));
+ }
+
function centerNode(source) {
x = -source.y0;
y = -source.x0;
@@ -451,6 +391,52 @@ function drawTaxonomy(data) {
return promise; //return a promise if async. requests
}
+ function selectNode(d, current_circle) {
+ // Display taxon info in top left div
+ var text = "Scientific name: " + htmlSafe(d.data.scientific_name) + "
";
+ if (d.data.taxonomic_rank) text += "Taxonomic rank: " + htmlSafe(d.data.taxonomic_rank) + "
";
+ if (d.data.synonym) text += "Synonym: " + htmlSafe(d.data.synonym) + "
";
+ if (d.data.common_name) text += "Common name: " + htmlSafe(d.data.common_name) + "
";
+ if (d.data.author) text += "Author: " + htmlSafe(d.data.author) + "
";
+ if (d.data.comment) text += "Comment: " + htmlSafe(d.data.comment) + "
";
+ taxon_text.html(text);
+
+ // Set correct taxon edit and destroy links and enable buttons
+ var taxon_edit_link = d3.select('#edit_taxon').attr('href').replace(/(.*\/)(\d+)(\/.*)/, "$1" + d.data.id + "$3");
+ d3.select('#edit_taxon').attr('href', taxon_edit_link);
+ enableButton($('#edit_taxon'), 'Edit entry in a new tab');
+
+ var taxon_delete_link = d3.select('#delete_taxon').attr('href').replace(/(.*\/)(\d+)/, "$1" + d.data.id);
+ d3.select('#delete_taxon').attr('href', taxon_delete_link);
+ enableButton($('#delete_taxon'), 'Delete taxon entry');
+
+ selected_node = d;
+ enableButton($('#center_selected_node'), 'Center currently selected node');
+
+ // Reset previously selected circle to normal view if one was selected
+ if (selected_circle) {
+ d3.select(selected_circle)
+ .style("stroke-width", '1px')
+ .style("font-weight", 'normal');
+
+ d3.select(selected_circle.parentNode).selectAll("circle")
+ .style("fill", function (d) {
+ return d.data.has_children ? "lightgrey" : "#fff";
+ });
+ }
+
+ selected_circle = current_circle;
+ d3.select(selected_circle)
+ .style("stroke-width", '2px')
+ .style("font-weight", 'bold');
+
+ d3.select(selected_circle.parentNode).selectAll("circle")
+ .style("fill", '#616161');
+
+ // Display list of specimen associated with this taxon
+ display_specimen_Data(d);
+ }
+
function display_specimen_Data(d) {
$.ajax({
url: "taxa/" + d.data.id + "/associated_specimen",
diff --git a/app/controllers/taxa_controller.rb b/app/controllers/taxa_controller.rb
index 9e7f4678..45eaa215 100644
--- a/app/controllers/taxa_controller.rb
+++ b/app/controllers/taxa_controller.rb
@@ -32,17 +32,18 @@ def show_individuals
end
end
- def filter
- @taxa = Taxon.where('scientific_name ILIKE ?', "%#{params[:term]}%").in_project(current_project_id).order(:scientific_name).limit(100)
- size = Taxon.where('scientific_name ILIKE ?', "%#{params[:term]}%").in_project(current_project_id).order(:scientific_name).size
-
- if size > 100
- render json: @taxa.map(&:scientific_name).push("and #{size} more...")
- else
- render json: @taxa.map(&:scientific_name)
+ def show_children
+ respond_to do |format|
+ format.html
+ format.json { render json: TaxonDatatable.new(view_context, params[:id], current_project_id) }
end
end
+ def filter
+ @taxa = Taxon.where('scientific_name ILIKE ?', "%#{params[:term]}%").in_project(current_project_id).order(:scientific_name)
+ render json: @taxa.map{ |taxon| {:id=> taxon.id, :name => taxon.scientific_name }}
+ end
+
def export_as_csv
authorize! :export_as_csv, :taxon
@@ -54,7 +55,7 @@ def export_as_csv
def orphans
respond_to do |format|
format.html
- format.json { render json: OrphanedTaxaDatatable.new(view_context, current_project_id) }
+ format.json { render json: TaxonDatatable.new(view_context, nil, current_project_id) }
end
end
diff --git a/app/datatables/taxon_datatable.rb b/app/datatables/taxon_datatable.rb
new file mode 100644
index 00000000..8bfd8eae
--- /dev/null
+++ b/app/datatables/taxon_datatable.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+class TaxonDatatable
+ include Rails.application.routes.url_helpers
+
+ delegate :url_helpers, to: 'Rails.application.routes'
+ delegate :params, :link_to, :h, to: :@view
+
+ def initialize(view, parent_id, current_default_project)
+ @view = view
+ @parent_id = parent_id
+ @current_default_project = current_default_project
+ end
+
+ def as_json(_options = {})
+ {
+ sEcho: params[:sEcho].to_i,
+ iTotalRecords: Taxon.in_project(@current_default_project).count,
+ iTotalDisplayRecords: taxa.total_entries,
+ aaData: data
+ }
+ end
+
+ private
+
+ def data
+ taxa.map do |taxon|
+ [
+ link_to(taxon.scientific_name, edit_taxon_path(taxon)),
+ taxon.synonym,
+ taxon.common_name,
+ taxon.human_taxonomic_rank,
+ taxon.updated_at.in_time_zone('CET').strftime('%Y-%m-%d %H:%M:%S'),
+ link_to('Delete', taxon, method: :delete, data: { confirm: 'Are you sure?' })
+ ]
+ end
+ end
+
+ def taxa
+ @taxa ||= fetch_taxa
+ end
+
+ def fetch_taxa
+ if @parent_id
+ taxa = Taxon.find(@parent_id)&.children&.in_project(@current_default_project).order("#{sort_column} #{sort_direction}")
+ else
+ taxa = Taxon.orphans.in_project(@current_default_project).order("#{sort_column} #{sort_direction}")
+ end
+
+ taxa = taxa.page(page).per_page(per_page)
+
+ if params[:sSearch].present?
+ taxa = taxa.where('taxa.scientific_name ILIKE :search OR taxa.synonym ILIKE :search
+OR taxa.common_name ILIKE :search', search: "%#{params[:sSearch]}%")
+ end
+
+ taxa
+ end
+
+ def page
+ params[:iDisplayStart].to_i / per_page + 1
+ end
+
+ def per_page
+ params[:iDisplayLength].to_i.positive? ? params[:iDisplayLength].to_i : 10
+ end
+
+ def sort_column
+ columns = %w[taxa.scientific_name taxa.synonym taxa.common_name taxa.taxonomic_rank taxa.updated_at]
+ columns[params[:iSortCol_0].to_i]
+ end
+
+ def sort_direction
+ params[:sSortDir_0] == 'desc' ? 'desc' : 'asc'
+ end
+end
diff --git a/app/models/taxon.rb b/app/models/taxon.rb
index fecd802f..37264b60 100644
--- a/app/models/taxon.rb
+++ b/app/models/taxon.rb
@@ -2,10 +2,10 @@ class Taxon < ApplicationRecord
extend Import
include ProjectRecord
- has_many :individuals
- has_many :ngs_runs
+ has_many :individuals, dependent: :nullify
+ has_many :ngs_runs, dependent: :nullify
- has_ancestry cache_depth: true, counter_cache: true
+ has_ancestry cache_depth: true, counter_cache: true, orphan_strategy: :adopt
validates_presence_of :scientific_name
validates_uniqueness_of :scientific_name
@@ -33,7 +33,7 @@ def self.subtree_json(project_id, parent_id=nil, root_id=nil)
children: children}
end.to_json
else
- Taxon.find(root_id).subtree.to_depth(1).order(:position, :scientific_name)
+ Taxon.find(root_id).subtree.to_depth(3).order(:position, :scientific_name)
.in_project(project_id).arrange_serializable do |parent, children|
{ id: parent.id,
scientific_name: parent.scientific_name,
diff --git a/app/views/clusters/_form.html.erb b/app/views/clusters/_form.html.erb
index 4ca42be7..ec504f95 100644
--- a/app/views/clusters/_form.html.erb
+++ b/app/views/clusters/_form.html.erb
@@ -51,7 +51,7 @@
<% if @cluster.isolate %>
<%= link_to '', edit_isolate_path(@cluster.isolate), :class => "glyphicon glyphicon-share-alt" %>
<% end %>
- <%= f.text_field :isolate_name , data: { autocomplete_source: filter_isolates_path } , class: 'form-control'%>
+ <%= f.collection_select :isolate_id, Isolate.order(:display_name), :id, :display_name, { include_blank: true }, { class:"form-control" } %>
Name | -
---|
- <%= link_to c.name,edit_family_path(c) %> - | -
- <%= link_to '', 'javascript:history.go(-1)', :class => "glyphicon glyphicon-chevron-left" %> - | -- <%= link_to '', @order, method: :delete, data: { confirm: 'Are you sure?' }, :class => "glyphicon glyphicon-trash" %> - | -
Name | -Higher-order Taxon | -- |
---|---|---|
<%= link_to order.name, edit_order_path(order) %> | - -- <% if order.higher_order_taxon %> - <%= link_to order.higher_order_taxon.name, edit_higher_order_taxon_path(order.higher_order_taxon) %> - <% end %> - | -<%= link_to 'Delete', order, method: :delete, data: { confirm: 'Are you sure?' } %> | -
- Name: - <%= @order.name %> -
- -- Author: - <%= @order.author %> -
- -- Belongs to: - <%= @order.higher_order_taxon.name %> -
- -<%= link_to 'Edit', edit_order_path(@order) %> | -<%= link_to 'Back', orders_path %> diff --git a/app/views/orders/show.json.jbuilder b/app/views/orders/show.json.jbuilder deleted file mode 100644 index f182a27f..00000000 --- a/app/views/orders/show.json.jbuilder +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -json.extract! @order, :id, :name, :author, :created_at, :updated_at diff --git a/app/views/primer_reads/_form.html.erb b/app/views/primer_reads/_form.html.erb index 4064d90a..7af86ec1 100644 --- a/app/views/primer_reads/_form.html.erb +++ b/app/views/primer_reads/_form.html.erb @@ -39,11 +39,11 @@Select a taxon in order to associate it and all its descendants to the selected project:
<%= link_to subsp.name_for_display, edit_species_path(subsp) %> |
Specimen ID | -Species | -Herbarium | -Collector | -Collector's Field Number | -Last updated | -- |
---|
- <%= link_to '', 'javascript:history.go(-1)', :class => "glyphicon glyphicon-chevron-left" %> - | -- <%= link_to '', @species, method: :delete, data: { confirm: 'Are you sure?' }, :class => "glyphicon glyphicon-trash" %> - | -
Species | -Author | -Family | -Last Updated | -- |
---|
<%= notice %>
- -- Author: - <%= @species.author %> -
- -- Genus name: - <%= @species.genus_name %> -
- -- Species epithet: - <%= @species.species_epithet %> -
- -<%= link_to 'Edit', edit_species_path(@species) %> | -<%= link_to 'Back', species_index_path %> diff --git a/app/views/species/show.json.jbuilder b/app/views/species/show.json.jbuilder deleted file mode 100644 index 3825e2ac..00000000 --- a/app/views/species/show.json.jbuilder +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -json.extract! @species, :id, :author, :genus_name, :species_epithet, :created_at, :updated_at diff --git a/app/views/taxa/_form.html.erb b/app/views/taxa/_form.html.erb index fc1b56d7..d45109e2 100644 --- a/app/views/taxa/_form.html.erb +++ b/app/views/taxa/_form.html.erb @@ -11,7 +11,10 @@ @@ -43,12 +46,13 @@ <% if @taxon.parent %> <%= link_to '', edit_taxon_path(@taxon.parent), :class => "glyphicon glyphicon-share-alt" %> <% end %> - <%= f.text_field :parent_name , data: { autocomplete_source: filter_taxa_path } , class: 'form-control'%> +Specimen directly associated with this Taxon:
Specimen ID | @@ -88,6 +92,27 @@
---|
Direct children of this taxon:
+Scientific name | +Synonym | +Common name | +Taxonomic rank | +Last updated | ++ |
---|
- Select a root taxon to show a phylogenetic tree of all its descendants. + Select a root taxon to show a taxonomy of all its descendants. <%= select_tag('Root taxon', options_from_collection_for_select(Taxon.orphans.order(:taxonomic_rank).in_project(current_project_id), 'id', 'scientific_name'), - prompt: "Select a taxon", class: 'form-control root_select', id: 'taxonomy_root_select') %> + class: 'form-control root_select', id: 'taxonomy_root_select') %>
- ++ Select a taxon to open and center it in the tree. + <%= collection_select :taxon, :search, Taxon.order(:scientific_name), :id, :scientific_name, { include_blank: true }, { class:"form-control" } %> +
+