1 import '../legacy/d3/d3Min.js';
2 import '../legacy/jquery.js';
3 import '../legacy/brapi/BrAPI.js';
5 // Colors to use when labelling multiple trials
6 const trial_colors = ['#2f4f4f', '#ff8c00', '#ffff00', '#00ff00', '#9400d3', '#00ffff', '#1e90ff', '#ff1493', '#ffdab9', '#228b22'];
7 const trial_colors_text = ['#ffffff', '#000000', '#000000', '#000000', '#ffffff', '#000000', '#ffffff', '#ffffff', '#000000', '#ffffff'];
9 export function init() {
11 constructor(trial_id) {
12 this.trial_id = String;
13 this.plot_arr = Array;
14 this.plot_object = Object;
16 this.brapi_plots = Object;
17 this.heatmap_queried = false;
18 this.heatmap_selected = false;
19 this.heatmap_selection = String;
20 this.heatmap_object = Object;
21 this.display_borders = true;
22 this.linked_trials = {};
26 this.trial_id = trial_id;
29 set_linked_trials(trials = []) {
30 this.linked_trials = {};
31 trials.forEach((t, i) => {
32 const index = i % trial_colors.length;
33 this.linked_trials[t.trial_name] = {
36 bg: trial_colors[index],
37 fg: trial_colors_text[index]
43 return this.linked_trials;
46 format_brapi_post_object() {
47 let brapi_post_plots = [];
49 for (let plot of this.plot_arr.filter(plot => plot.type == "filler")) {
50 brapi_post_plots.push({
52 "invert_row_checkmark": document.getElementById("invert_row_checkmark").checked,
53 "top_border_selection": this.meta_data.top_border_selection || false,
54 "left_border_selection": this.meta_data.left_border_selection || false,
55 "right_border_selection": this.meta_data.right_border_selection || false,
56 "bottom_border_selection": this.meta_data.bottom_border_selection || false,
57 "plot_layout": this.meta_data.plot_layout || "serpentine",
59 "germplasmDbId": this.meta_data.filler_accession_id,
60 "germplasmName": this.meta_data.filler_accession_name,
61 "observationUnitName": this.trial_id + " filler " + (parseInt(this.meta_data.max_level_code) + count),
62 "observationUnitPosition": {
64 "levelCode": parseInt(this.meta_data.max_level_code) + count,
68 "positionCoordinateX": plot.observationUnitPosition.positionCoordinateX,
69 "positionCoordinateY": plot.observationUnitPosition.positionCoordinateY,
71 "trialDbId": this.trial_id,
72 "studyDbId": this.trial_id,
76 return brapi_post_plots;
79 format_brapi_put_object() {
81 for (let plot of this.plot_arr.filter(plot => plot.type == "data")) {
82 brapi_plots[plot.observationUnitDbId] = {
84 "invert_row_checkmark": document.getElementById("invert_row_checkmark").checked,
85 "top_border_selection": this.meta_data.top_border_selection || false,
86 "left_border_selection": this.meta_data.left_border_selection || false,
87 "right_border_selection": this.meta_data.right_border_selection || false,
88 "bottom_border_selection": this.meta_data.bottom_border_selection || false,
89 "plot_layout": this.meta_data.plot_layout || "serpentine",
91 "germplasmDbId": plot.germplasmDbId,
92 "germplasmName": plot.gerplasmName,
93 "observationUnitName": plot.observationUnitName,
94 "observationUnitPosition": {
96 "levelCode": plot.observationUnitPosition.observationLevel.levelCode,
100 "positionCoordinateX": plot.observationUnitPosition.positionCoordinateX,
101 "positionCoordinateY": plot.observationUnitPosition.positionCoordinateY,
103 "trialDbId": this.trial_id,
110 var pseudo_layout = {};
111 var plot_object = {};
112 for (let plot of data) {
114 if (isNaN(parseInt(plot.observationUnitPosition.positionCoordinateY))) {
115 plot.observationUnitPosition.positionCoordinateY = isNaN(parseInt(plot.observationUnitPosition.observationLevelRelationships[1].levelCode)) ? plot.observationUnitPosition.observationLevelRelationships[0].levelCode : plot.observationUnitPosition.observationLevelRelationships[1].levelCode;
116 if (plot.observationUnitPosition.positionCoordinateY in pseudo_layout) {
117 pseudo_layout[plot.observationUnitPosition.positionCoordinateY] += 1;
118 plot.observationUnitPosition.positionCoordinateX = pseudo_layout[plot.observationUnitPosition.positionCoordinateY];
120 pseudo_layout[plot.observationUnitPosition.positionCoordinateY] = 1;
121 plot.observationUnitPosition.positionCoordinateX = 1;
124 var obs_level = plot.observationUnitPosition.observationLevel;
125 if (obs_level.levelName == "plot") {
126 plot.observationUnitPosition.positionCoordinateX = parseInt(plot.observationUnitPosition.positionCoordinateX);
127 plot.observationUnitPosition.positionCoordinateY = parseInt(plot.observationUnitPosition.positionCoordinateY);
128 // if (plot.additionalInfo && plot.additionalInfo.type == "filler") {
129 // plot.type = "filler";
131 // plot.type = "data";
133 plot_object[plot.observationUnitDbId] = plot;
136 this.plot_object = plot_object;
139 filter_heatmap(observations) {
140 this.heatmap_object = {};
141 for (let observation of observations) {
142 let trait_name = observation.observationVariableName;
143 if (!this.heatmap_object[trait_name]) {
144 this.heatmap_object[trait_name] = {[observation.observationUnitDbId]: {val: observation.value, plot_name: observation.observationUnitName, id: observation.observationDbId }};
146 this.heatmap_object[trait_name][observation.observationUnitDbId] = {val: observation.value, plot_name: observation.observationUnitName, id: observation.observationDbId };
152 * This function has been deprecated! It is not performed by the backend function CXGN::Trial->get_sorted_plots and
153 * the AJAX call to /ajax/breeders/trial_plot_order
155 // traverse_map(plot_arr, planting_or_harvesting_order_layout) {
156 // var local_this = this;
157 // let coord_matrix = [];
158 // var row = this.meta_data[planting_or_harvesting_order_layout].includes('row') ? "positionCoordinateY" : "positionCoordinateX";
159 // var col = this.meta_data[planting_or_harvesting_order_layout].includes('row') ? "positionCoordinateX" : "positionCoordinateY";
161 // for (let plot of plot_arr) {
162 // if (!coord_matrix[plot.observationUnitPosition[row]]) {
163 // coord_matrix[plot.observationUnitPosition[row]] = [];
164 // coord_matrix[plot.observationUnitPosition[row]][plot.observationUnitPosition[col]] = plot;
166 // coord_matrix[plot.observationUnitPosition[row]][plot.observationUnitPosition[col]] = plot;
170 // coord_matrix = coord_matrix.filter(plot_arr => Array.isArray(plot_arr));
171 // if (!document.getElementById("invert_row_checkmark").checked && this.meta_data[planting_or_harvesting_order_layout].includes('row') && planting_or_harvesting_order_layout.includes('planting')) {
172 // if ((this.meta_data.top_border_selection && !this.meta_data.bottom_border_selection) || (!this.meta_data.top_border_selection && this.meta_data.bottom_border_selection)) {
173 // if (this.meta_data.top_border_selection) {
174 // var top_borders = coord_matrix.shift();
175 // coord_matrix.push(top_borders);
176 // } else if (this.meta_data.bottom_border_selection) {
177 // var bottom_borders = coord_matrix.pop();
178 // coord_matrix.unshift(bottom_borders);
184 // if (this.meta_data[planting_or_harvesting_order_layout].includes('serpentine')) {
185 // for (let i = 0; i < coord_matrix.length; i++) {
187 // coord_matrix[i].reverse();
192 // var final_arr = [];
193 // for (let plot_arr of coord_matrix) {
194 // plot_arr = plot_arr.filter(plot => plot !== undefined);
195 // if (!document.getElementById("invert_row_checkmark").checked && local_this.meta_data[planting_or_harvesting_order_layout].includes('col') && planting_or_harvesting_order_layout.includes('planting')) {
196 // if ((local_this.meta_data.top_border_selection && !local_this.meta_data.bottom_border_selection) || (!local_this.meta_data.top_border_selection && local_this.meta_data.bottom_border_selection)) {
197 // if (local_this.meta_data.top_border_selection) {
198 // var top_border_plot = plot_arr.shift();
199 // plot_arr.push(top_border_plot);
200 // } else if (local_this.meta_data.bottom_border_selection) {
201 // var bottom_border_plot = plot_arr.pop();
202 // plot_arr.unshift(bottom_border_plot);
206 // final_arr.push(...plot_arr);
210 // planting_or_harvesting_order_layout == "planting_order_layout" ? 'planting_order': "harvesting_order",
219 // final_arr = final_arr.filter(plot => plot !== undefined);
220 // let order_number = 1;
221 // final_arr.forEach(function(plot) {
224 // "\"" + plot.locationName + "\"",
226 // plot.observationUnitPosition.observationLevel ? plot.observationUnitPosition.observationLevel.levelCode : "N/A",
227 // plot.observationUnitName,
228 // plot.germplasmName,
229 // plot.seedLotName ? plot.seedLotName : ''
234 // var hiddenElement = document.createElement('a');
235 // hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
236 // hiddenElement.target = '_blank';
237 // hiddenElement.download = `Trial_${this.trial_id}_${this.meta_data[planting_or_harvesting_order_layout]}_${planting_or_harvesting_order_layout}.csv`;
238 // hiddenElement.click();
241 get_plot_order(type, order, start, include_borders) {
242 // TODO: The backend function doesn't yet take into account the borders defined in the frontend
243 let q = new URLSearchParams({
244 'trial_ids': [this.trial_id, ...Object.keys(this.linked_trials).map((e) => x[e].id)].join(','),
249 window.open(`/ajax/breeders/trial_plot_order?${q}`, '_blank');
253 this.plot_arr = Object.values(this.plot_object);
254 var min_col = 100000;
255 var min_row = 100000;
258 var max_level_code = 0;
259 this.plot_arr.forEach((plot) => {
260 max_col = plot.observationUnitPosition.positionCoordinateX > max_col ? plot.observationUnitPosition.positionCoordinateX : max_col;
261 min_col = plot.observationUnitPosition.positionCoordinateX < min_col ? plot.observationUnitPosition.positionCoordinateX : min_col;
262 max_row = plot.observationUnitPosition.positionCoordinateY > max_row ? plot.observationUnitPosition.positionCoordinateY : max_row;
263 min_row = plot.observationUnitPosition.positionCoordinateY < min_row ? plot.observationUnitPosition.positionCoordinateY : min_row;
264 max_level_code = parseInt(plot.observationUnitPosition.observationLevel.levelCode) > max_level_code ? plot.observationUnitPosition.observationLevel.levelCode : max_level_code;
266 this.meta_data.min_row = min_row;
267 this.meta_data.max_row = max_row;
268 this.meta_data.min_col = min_col;
269 this.meta_data.max_col = max_col;
270 this.meta_data.num_rows = max_row - min_row + 1;
271 this.meta_data.num_cols = max_col - min_col + 1;
272 this.meta_data.max_level_code = max_level_code;
273 this.meta_data.display_borders = !jQuery("#include_linked_trials_checkmark").is(":checked");
274 this.meta_data.overlapping_plots = {};
278 var fieldmap_hole_fillers = [];
280 for (let plot of this.plot_arr) {
281 if (last_coord === undefined) {
284 if (plot === undefined) {
285 if (last_coord[0] < this.meta_data.max_col) {
286 fieldmap_hole_fillers.push(this.get_plot_format(`Empty_Space_(${last_coord[0] + 1}_${last_coord[1]})`, last_coord[0] + 1, last_coord[1]));
287 last_coord = [last_coord[0] + 1, last_coord[1]];
288 this.plot_object['Empty Space' + String(last_coord[0]) + String(last_coord[1])] = this.get_plot_format('empty_space', last_coord[0] + 1, last_coord[1]);
290 fieldmap_hole_fillers.push(this.get_plot_format(`Empty_Space_${this.meta_data.min_col}_${last_coord[1] + 1}`, this.meta_data.min_col, last_coord[1] + 1));
291 last_coord = [this.meta_data.min_col, last_coord[1]];
292 this.plot_object['Empty Space' + String(last_coord[0]) + String(last_coord[1])] = this.get_plot_format('empty_space', this.meta_data.min_col, last_coord[1] + 1);
295 last_coord = [plot.observationUnitPosition.positionCoordinateX, plot.observationUnitPosition.positionCoordinateY];
298 this.plot_arr = [...this.plot_arr.filter(plot => plot !== undefined), ...fieldmap_hole_fillers];
301 check_element(selection, element_id) {
302 document.getElementById(element_id).checked = selection;
305 check_elements(additionalInfo) {
306 var elements = ["top_border_selection", "left_border_selection", "right_border_selection", "bottom_border_selection", "invert_row_checkmark"];
307 for (let element of elements) {
308 this.check_element(additionalInfo[element], element);
309 this.meta_data[element] = additionalInfo[element];
313 get_plot_format(type, x, y) {
314 // Use the first plot from the trial to get trial-level metadata to give to a border plot
315 // NOTE: this will break if plots from multiple trials are loaded
316 let p = this.plot_arr[0];
319 observationUnitName: this.trial_id + ' ' + type,
320 observationUnitPosition: {
321 positionCoordinateX: x,
322 positionCoordinateY: y
324 locationName: p.locationName,
325 studyName: p.studyName
329 change_dimensions(cols, rows) {
330 var cols = parseInt(cols);
331 var rows = parseInt(rows);
332 this.meta_data.post = false;
333 this.meta_data.num_cols = cols;
334 this.meta_data.num_rows = rows;
336 ...this.plot_arr.slice(0, Object.entries(this.plot_object).length),
339 if (this.meta_data.retain_layout == false) {
340 this.meta_data.max_row = rows + this.meta_data.min_row - 1;
341 this.meta_data.max_col = cols + this.meta_data.min_col - 1;
342 this.meta_data.plot_layout = this.meta_data.plot_layout ? this.meta_data.plot_layout : "serpentine";
344 this.plot_arr = this.plot_arr.filter(plot => plot.type == "data");
345 this.plot_arr.sort(function(a,b) { return parseFloat(a.observationUnitPosition.observationLevel.levelCode) - parseFloat(b.observationUnitPosition.observationLevel.levelCode) });
349 for (let j = this.meta_data.min_row; j < (this.meta_data.min_row+rows); j++) {
351 var swap_columns = this.meta_data.plot_layout == "serpentine" && j % 2 === 0;
353 for (let i = this.meta_data.min_col; i < (this.meta_data.min_col+cols); i++) {
356 var col = swap_columns ? this.meta_data.max_col - col_count + 1 : i;
357 if (plot_count >= this.plot_arr.length && this.meta_data.filler_accession_id) {
358 this.meta_data.post = true;
359 this.plot_arr[plot_count] = this.get_plot_format('filler', col, row);
360 } else if (plot_count < this.plot_arr.length && this.plot_arr[plot_count].observationUnitPosition) {
361 this.plot_arr[plot_count].observationUnitPosition.positionCoordinateX = col;
362 this.plot_arr[plot_count].observationUnitPosition.positionCoordinateY = row;
371 var add_corner = (condition_1, condition_2, x,y) => {
372 if (condition_1 && condition_2) {
373 this.plot_arr.push(this.get_plot_format("border", x, y));
376 add_corner(this.meta_data.top_border_selection, this.meta_data.left_border_selection, this.meta_data.min_col - 1, this.meta_data.min_row - 1);
377 add_corner(this.meta_data.top_border_selection, this.meta_data.right_border_selection, this.meta_data.max_col + 1, this.meta_data.min_row - 1);
378 add_corner(this.meta_data.bottom_border_selection, this.meta_data.left_border_selection, this.meta_data.min_col - 1, this.meta_data.max_row + 1);
379 add_corner(this.meta_data.bottom_border_selection, this.meta_data.right_border_selection, this.meta_data.max_col + 1, this.meta_data.max_row + 1);
382 add_border(border_element, row_or_col, min_or_max) {
385 if (row_or_col == "row") {
386 start_iter = this.meta_data.min_col;
387 end_iter = this.meta_data.max_col;
388 } else if (row_or_col == "col") {
389 start_iter = this.meta_data.min_row;
390 end_iter = this.meta_data.max_row;
393 if (this.meta_data[border_element]) {
394 for (let i = start_iter; i <= end_iter; i++) {
395 this.plot_arr.push(this.get_plot_format("border", row_or_col == "row" ? i : min_or_max, row_or_col == "row" ? min_or_max : i));
401 if ( this.meta_data.display_borders ) {
402 this.add_border("left_border_selection", "col", this.meta_data.min_col - 1);
403 this.add_border("top_border_selection", "row", this.meta_data.min_row - 1);
404 this.add_border("right_border_selection", "col", this.meta_data.max_col + 1);
405 this.add_border("bottom_border_selection", "row", this.meta_data.max_row + 1);
412 this.plot_arr = this.plot_arr.filter((plot) => plot.type != "border")
413 this.plot_arr.map((plot) => {
414 let tempX = plot.observationUnitPosition.positionCoordinateX;
415 plot.observationUnitPosition.positionCoordinateX = plot.observationUnitPosition.positionCoordinateY;
416 plot.observationUnitPosition.positionCoordinateY = tempX;
419 let tempMaxCol = this.meta_data.max_col;
420 this.meta_data.max_col = this.meta_data.max_row;
421 this.meta_data.max_row = tempMaxCol;
423 let tempMinCol = this.meta_data.min_col;
424 this.meta_data.min_col = this.meta_data.min_row;
425 this.meta_data.min_row = tempMinCol;
427 let tempNumCols = this.meta_data.num_cols;
428 this.meta_data.num_cols = this.meta_data.num_rows;
429 this.meta_data.num_rows = tempNumCols;
431 d3.select("svg").remove();
438 var event = d3.dispatch('click', 'dblclick');
439 function cc(selection) {
444 function dist(a, b) {
445 return Math.sqrt(Math.pow(a[0] - b[0], 2), Math.pow(a[1] - b[1], 2));
447 selection.on('mousedown', function() {
448 down = d3.mouse(document.body);
451 selection.on('mouseup', function() {
452 if (dist(down, d3.mouse(document.body)) > tolerance) {
456 window.clearTimeout(wait);
458 event.dblclick(d3.event);
460 wait = window.setTimeout((function(e) {
470 return d3.rebind(cc, event, 'on');
473 heatmap_plot_click(plot, heatmap_object, trait_name) {
474 if (d3.event && d3.event.detail > 1) {
476 } else if (trait_name in heatmap_object && heatmap_object[trait_name][plot.observationUnitDbId]) {
477 let val, plot_name, pheno_id;
478 val = heatmap_object[trait_name][plot.observationUnitDbId].val;
479 plot_name = heatmap_object[trait_name][plot.observationUnitDbId].plot_name;
480 pheno_id = heatmap_object[trait_name][plot.observationUnitDbId].id;
481 jQuery("#suppress_plot_pheno_dialog").modal("show");
482 jQuery("#myplot_name").html(plot_name);
483 jQuery("#pheno_value").html(val);
484 jQuery("#mytrait_id").html(trait_name);
485 jQuery("#mypheno_id").html(pheno_id);
489 fieldmap_plot_click(plot) {
490 if (d3.event && d3.event.detail > 1) {
493 function btnClick(n){
495 jQuery("#hm_view_plot_image_submit").addClass("disabled");
497 jQuery("#hm_view_plot_image_submit").removeClass("disabled");
501 if (plot.type == "data") {
502 var image_ids = plot.plotImageDbIds || [];
503 var replace_accession = plot.germplasmName;
504 var replace_plot_id = plot.observationUnitDbId;
505 var replace_plot_name = plot.observationUnitName;plot
506 var replace_plot_number = plot.observationUnitPosition.observationLevel.levelCode;
508 jQuery('#plot_image_ids').html(image_ids);
509 jQuery('#hm_replace_accessions_link').find('button').trigger('click');
510 jQuery("#hm_replace_accessions_link").on("click", function(){ btnClick(image_ids); });
511 jQuery('#hm_edit_plot_information').html('<b>Selected Plot Information: </b>');
512 jQuery('#hm_edit_plot_name').html(replace_plot_name);
513 jQuery('#hm_edit_plot_number').html(replace_plot_number);
514 var old_plot_id = jQuery('#hm_edit_plot_id').html(replace_plot_id);
515 var old_plot_accession = jQuery('#hm_edit_plot_accession').html(replace_accession);
516 jQuery('#hm_replace_plot_accessions_dialog').modal('show');
520 url: '/ajax/breeders/trial/'+ trial_id +'/retrieve_plot_images',
523 'image_ids': JSON.stringify(image_ids),
524 'plot_name': replace_plot_name,
525 'plot_id': replace_plot_id,
527 success: function (response) {
528 jQuery('#working_modal').modal("hide");
529 var images = response.image_html;
530 if (response.error) {
531 alert("Error Retrieving Plot Images: "+response.error);
534 jQuery("#show_plot_image_ids").html(images);
536 // jQuery('#view_plot_image_dialog').modal("show");
540 jQuery('#working_modal').modal("hide");
541 alert('An error occurred retrieving plot images');
548 addEventListeners() {
549 let LocalThis = this;
550 let transposeBtn = document.getElementById("transpose_fieldmap");
551 transposeBtn.onclick = function() {
552 LocalThis.transpose();
557 this.addEventListeners();
558 var cc = this.clickcancel();
559 const colors = ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"];
560 var trait_name = this.heatmap_selection;
561 var heatmap_object = this.heatmap_object;
562 var plot_click = !this.heatmap_selected ? this.fieldmap_plot_click : this.heatmap_plot_click;
564 var local_this = this;
566 if (this.heatmap_selected) {
567 let plots_with_selected_trait = heatmap_object[trait_name];
568 for (let obs_unit of Object.values(plots_with_selected_trait)) {
569 trait_vals.push(obs_unit.val);
571 var colorScale = d3.scale.quantile()
576 var is_plot_overlapping = function(plot) {
577 if ( plot.observationUnitPosition ) {
578 let k = `${plot.observationUnitPosition.positionCoordinateX}-${plot.observationUnitPosition.positionCoordinateY}`;
579 return Object.keys(local_this.meta_data.overlapping_plots).includes(k);
584 var get_fieldmap_plot_color = function(plot) {
586 if (plot.observationUnitPosition.observationLevelRelationships) {
587 if ( is_plot_overlapping(plot) ) {
590 else if (plot.observationUnitPosition.entryType == "check") {
592 } else if (plot.observationUnitPosition.observationLevelRelationships[1].levelCode % 2 == 0) {
594 } else if (plot.observationUnitName.includes(local_this.trial_id + " filler")) {
605 var get_heatmap_plot_color = function(plot) {
607 if ( is_plot_overlapping(plot) ) {
610 else if (!plot.observationUnitPosition.observationLevel) {
613 color = heatmap_object[trait_name][plot.observationUnitDbId] ? colorScale(heatmap_object[trait_name][plot.observationUnitDbId].val) : "white";
617 var get_stroke_color = function(plot) {
619 if (plot.observationUnitPosition.observationLevel) {
620 if (plot.observationUnitPosition.observationLevelRelationships[0].levelCode % 2 == 0) {
623 stroke_color = "green";
626 stroke_color = "#666";
631 var get_plot_message = function(plot) {
633 if ( is_plot_overlapping(plot) ) {
634 let k = `${plot.observationUnitPosition.positionCoordinateX}-${plot.observationUnitPosition.positionCoordinateY}`;
635 let plots = local_this.meta_data.overlapping_plots[k];
636 html += `<strong>Overlapping Plots:</strong> ${plots.join(', ')}`;
639 html += jQuery("#include_linked_trials_checkmark").is(":checked") ?
640 `<strong>Trial Name:</strong> <span style='padding: 1px 2px; border-radius: 4px; color: ${local_this.linked_trials[plot.studyName].fg}; background-color: ${local_this.linked_trials[plot.studyName].bg}'>${plot.studyName}</span><br />` :
642 html += `<strong>Plot Name:</strong> ${plot.observationUnitName}<br />`;
643 if ( plot.type == "data" ) {
644 html += `<strong>Plot Number:</strong> ${plot.observationUnitPosition.observationLevel.levelCode}<br />
645 <strong>Block Number:</strong> ${plot.observationUnitPosition.observationLevelRelationships[1].levelCode}<br />
646 <strong>Rep Number:</strong> ${plot.observationUnitPosition.observationLevelRelationships[0].levelCode}<br />
647 <strong>Accession Name:</strong> ${plot.germplasmName}`;
648 if ( local_this.heatmap_selected ) {
649 let v = heatmap_object[trait_name][plot.observationUnitDbId].val;
650 v = Math.round((parseFloat(v) + Number.EPSILON) * 100) / 100
651 html += `<br /><strong>Trait Value:</strong> ${v}`;
658 var handle_mouseover = function(d) {
659 if (d.observationUnitPosition.observationLevel) {
660 d3.select(`#fieldmap-plot-${d.observationUnitDbId}`)
661 .style('fill', 'green')
662 .style('cursor', 'pointer')
663 .style("stroke-width", 3)
664 .style("stroke", '#000000');
665 tooltip.style('opacity', .9)
666 .style("left", (window.event.clientX+25) + "px")
667 .style("top", window.event.clientY + "px")
668 .html(get_plot_message(d));
672 var handle_mouseout = function(d) {
673 d3.select(`#fieldmap-plot-${d.observationUnitDbId}`)
674 .style('fill', !isHeatMap ? get_fieldmap_plot_color(d) : get_heatmap_plot_color(d))
675 .style('cursor', 'default')
676 .style("stroke-width", 2)
677 .style("stroke", get_stroke_color);
678 tooltip.style('opacity', 0);
679 plots.exit().remove();
682 var plot_x_coord = function(plot) {
683 return plot.observationUnitPosition.positionCoordinateX - min_col + col_increment + 1
686 var plot_y_coord = function(plot) {
687 let y = plot.observationUnitPosition.positionCoordinateY - min_row + row_increment;
688 if ( plot.type !== "border" && document.getElementById("invert_row_checkmark").checked !== true ) {
689 y = num_rows - y - 1;
694 var width = this.meta_data.display_borders && this.meta_data.left_border_selection ?
695 this.meta_data.num_cols + 3 :
696 this.meta_data.num_cols + 2;
697 width = this.meta_data.display_borders && this.meta_data.right_border_selection ?
700 var height = this.meta_data.display_borders && this.meta_data.top_border_selection ?
701 this.meta_data.num_rows + 3 :
702 this.meta_data.num_rows + 2;
703 height = this.meta_data.display_borders && this.meta_data.bottom_border_selection ?
706 var row_increment = this.meta_data.invert_row_checkmark ?
709 row_increment = this.meta_data.display_borders && this.meta_data.top_border_selection && this.meta_data.invert_row_checkmark ?
712 var y_offset = this.meta_data.display_borders && this.meta_data.top_border_selection && !this.meta_data.invert_row_checkmark ?
715 var col_increment = this.meta_data.display_borders && this.meta_data.left_border_selection ?
719 // Check the fieldmap for any overlapping plots (plots that share the same x/y coordinates)
720 this.meta_data.overlapping_plots = {};
721 let plot_positions = {};
722 this.plot_arr.forEach((plot) => {
723 if ( plot.observationUnitPosition ) {
724 let x = plot.observationUnitPosition.positionCoordinateX;
725 let y = plot.observationUnitPosition.positionCoordinateY;
726 let p = plot.observationUnitPosition.observationLevel ? plot.observationUnitPosition.observationLevel.levelCode : '';
727 let t = plot.studyName;
730 if ( !plot_positions.hasOwnProperty(k) ) plot_positions[k] = [];
731 plot_positions[k].push(jQuery("#include_linked_trials_checkmark").is(":checked") ? `${p} (${t})` : p);
732 if ( plot_positions[k].length > 1 ) {
733 this.meta_data.overlapping_plots[k] = plot_positions[k];
739 var min_row = this.meta_data.min_row;
740 var max_row = this.meta_data.max_row;
741 var min_col = this.meta_data.min_col;
742 var max_col = this.meta_data.max_col;
743 var num_rows = this.meta_data.num_rows;
744 var isHeatMap = this.heatmap_selected;
746 var grid = d3.select("#fieldmap_chart")
748 .attr("width", width * 50 + 20 + "px")
749 .attr("height", height * 50 + 20 + "px")
751 var tooltip = d3.select("#fieldmap_chart")
753 .attr("id", "tooltip")
754 .attr("class", "tooltip")
755 .style("position", "fixed")
756 .style("opacity", 0);
758 var plots = grid.selectAll("plots").data(this.plot_arr);
759 plots.append("title");
760 plots.enter().append("rect")
761 .attr("x", (d) => { return plot_x_coord(d) * 50 })
762 .attr("y", (d) => { return plot_y_coord(d) * 50 + 15 + y_offset })
764 .attr("id", (d) => { return `fieldmap-plot-${d.observationUnitDbId}` })
765 .attr("class", "col bordered")
768 .style("stroke-width", 2)
769 .style("stroke", get_stroke_color)
770 .style("fill", !isHeatMap ? get_fieldmap_plot_color : get_heatmap_plot_color)
771 .on("mouseover", handle_mouseover)
772 .on("mouseout", handle_mouseout)
775 cc.on("click", (el) => {
776 var plot = d3.select(el.srcElement).data()[0];
777 plot_click(plot, heatmap_object, trait_name)
779 cc.on("dblclick", (el) => {
780 var me = d3.select(el.srcElement);
781 var d = me.data()[0];
782 if (d.observationUnitDbId) {
783 window.open('/stock/'+d.observationUnitDbId+'/view');
787 // Add a colored band to the bottom of the plot box to indicate different trials
788 if ( jQuery("#include_linked_trials_checkmark").is(":checked") ) {
789 plots.enter().append("rect")
790 .attr("x", (d) => { return plot_x_coord(d) * 50 + 4 })
791 .attr("y", (d) => { return plot_y_coord(d) * 50 + 54 + y_offset })
795 .style("fill", (d) => { return local_this.linked_trials[d.studyName].bg })
796 .style("opacity", (d) => { return is_plot_overlapping(d) ? '0' : '100' })
799 plots.append("text");
800 plots.enter().append("text")
801 .attr("x", (d) => { return plot_x_coord(d) * 50 + 10 })
802 .attr("y", (d) => { return plot_y_coord(d) * 50 + 50 + y_offset})
804 if (!(d.observationUnitName.includes(local_this.trial_id + " filler")) && d.type == "data" && !is_plot_overlapping(d) ) {
805 return d.observationUnitPosition.observationLevel.levelCode;
808 .on("mouseover", handle_mouseover)
809 .on("mouseout", handle_mouseout);
811 var image_icon = function(d) {
812 var image = d.plotImageDbIds || [];
814 if (image.length > 0){
815 plot_image = "/static/css/images/plot_images.png";
822 plots.enter().append("image")
823 .attr("xlink:href", image_icon)
824 .attr("x", (d) => { return plot_x_coord(d) * 50 + 5 })
825 .attr("y", (d) => { return plot_y_coord(d) * 50 + 15 + y_offset })
828 .on("mouseover", handle_mouseover)
829 .on("mouseout", handle_mouseout)
831 plots.exit().remove();
833 var row_label_arr = [];
834 var col_label_arr = [];
835 for (let i = min_row; i <= max_row; i++) {
836 row_label_arr.push(i);
838 for (let i = min_col; i <= max_col; i++) {
839 col_label_arr.push(i);
842 var row_labels_col = 1;
843 var col_labels_row = 0;
844 if (!this.meta_data.invert_row_checkmark) {
845 col_labels_row = this.meta_data.display_borders && this.meta_data.bottom_border_selection ? num_rows + 1 : num_rows;
846 row_label_arr.reverse();
849 grid.selectAll(".rowLabels")
851 .enter().append("text")
852 .attr("x", (row_labels_col * 50 - 25))
853 .attr("y", (label, i) => {
854 let y = this.meta_data.invert_row_checkmark ? i+1 : i;
855 y = this.meta_data.display_borders && this.meta_data.top_border_selection && this.meta_data.invert_row_checkmark ? y+1 : y;
856 return y * 50 + 45 + y_offset;
858 .text((label) => { return label });
860 grid.selectAll(".colLabels")
862 .enter().append("text")
863 .attr("x", (label, i) => {
864 let x = label-min_col+col_increment+2;
867 .attr("y", (col_labels_row * 50) + 45 + y_offset)
868 .text((label) => { return label });
873 d3.select("svg").remove();
874 this.change_dimensions(this.meta_data.num_cols, this.meta_data.num_rows);
880 jQuery("#working_modal").modal("hide");
881 jQuery("#fieldmap_chart").css({ "display": "inline-block" });
882 jQuery("#container_fm").css({ "display": "inline-block", "overflow": "auto" });
883 jQuery("#trait_heatmap").css("display", "none");
884 jQuery("#container_heatmap").css("display", "none");
885 jQuery("#trait_heatmap").css("display", "none");
890 const mapObj = new FieldMap();