Trial Layout: download plot order using new backend function
[sgn.git] / js / source / entries / fieldmap.js
blob80b65d25186c2a3906776e8df955a77af65e4b85
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() {
10     class FieldMap {
11         constructor(trial_id) {
12             this.trial_id = String;
13             this.plot_arr = Array;
14             this.plot_object = Object;
15             this.meta_data = {};
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 = {};
23         }
25         set_id(trial_id) {
26             this.trial_id = trial_id;
27         }
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] = {
34                     id: t.trial_id,
35                     name: t.trial_name,
36                     bg: trial_colors[index],
37                     fg: trial_colors_text[index]
38                 };
39             });
40         }
42         get_linked_trials() {
43             return this.linked_trials;
44         }
46         format_brapi_post_object() {
47             let brapi_post_plots = [];
48             let count = 1;
49             for (let plot of this.plot_arr.filter(plot => plot.type == "filler")) {
50                 brapi_post_plots.push({
51                     "additionalInfo": {
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",
58                     },
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": {
63                         "observationLevel": {
64                             "levelCode": parseInt(this.meta_data.max_level_code) + count,
65                             "levelName": "plot",
66                             "levelOrder": 2
67                         },
68                         "positionCoordinateX": plot.observationUnitPosition.positionCoordinateX,
69                         "positionCoordinateY": plot.observationUnitPosition.positionCoordinateY,
70                     },
71                     "trialDbId": this.trial_id,
72                     "studyDbId": this.trial_id,
73                 });
74                 count++;
75             }
76             return brapi_post_plots;
77         }
79         format_brapi_put_object() {
80             let brapi_plots = {};
81             for (let plot of this.plot_arr.filter(plot => plot.type == "data")) {
82                 brapi_plots[plot.observationUnitDbId] = {
83                     "additionalInfo": {
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",
90                     },
91                     "germplasmDbId": plot.germplasmDbId,
92                     "germplasmName": plot.gerplasmName,
93                     "observationUnitName": plot.observationUnitName,
94                     "observationUnitPosition": {
95                         "observationLevel": {
96                             "levelCode": plot.observationUnitPosition.observationLevel.levelCode,
97                             "levelName": "plot",
98                             "levelOrder": 2
99                         },
100                         "positionCoordinateX": plot.observationUnitPosition.positionCoordinateX,
101                         "positionCoordinateY": plot.observationUnitPosition.positionCoordinateY,
102                     },
103                     "trialDbId": this.trial_id,
104                 }
105             }
106             return brapi_plots;
107         }
109         filter_data(data) {
110             var pseudo_layout = {};
111             var plot_object = {};
112             for (let plot of data) {
113                 plot.type = "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];
119                     } else {
120                         pseudo_layout[plot.observationUnitPosition.positionCoordinateY] = 1;
121                         plot.observationUnitPosition.positionCoordinateX = 1;
122                     }
123                 }
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";
130                     // } else {
131                     //     plot.type = "data";
132                     // }
133                     plot_object[plot.observationUnitDbId] = plot;
134                 }   
135             }
136             this.plot_object = plot_object;
137         }
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 }};
145                 } else {
146                     this.heatmap_object[trait_name][observation.observationUnitDbId] = {val: observation.value, plot_name: observation.observationUnitName, id: observation.observationDbId };
147                 }
148             }
149         }
151         /**
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
154          */
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";
160             
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;
165         //         } else {
166         //             coord_matrix[plot.observationUnitPosition[row]][plot.observationUnitPosition[col]] = plot;
167         //         }
168         //     }
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);
179         //             }
180         //         }
181         //     }
183             
184         //     if (this.meta_data[planting_or_harvesting_order_layout].includes('serpentine')) {
185         //         for (let i = 0; i < coord_matrix.length; i++) {
186         //             if (i % 2 == 1) {
187         //                 coord_matrix[i].reverse();
188         //             }
189         //         }
190         //     }
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);
203         //                 }
204         //             }
205         //         }
206         //         final_arr.push(...plot_arr);
207         //     }
209         //     var csv = [
210         //         planting_or_harvesting_order_layout == "planting_order_layout" ? 'planting_order': "harvesting_order",
211         //         'location_name',
212         //         'trial_name',
213         //         'plot_number',
214         //         'plot_name',
215         //         'accession_name',
216         //         'seedlot_name',
217         //     ].join(',');
218         //     csv += "\n";
219         //     final_arr = final_arr.filter(plot => plot !== undefined);
220         //     let order_number = 1;
221         //     final_arr.forEach(function(plot) {
222         //         csv += [
223         //             order_number++,
224         //             "\"" + plot.locationName + "\"",
225         //             plot.studyName,
226         //             plot.observationUnitPosition.observationLevel ? plot.observationUnitPosition.observationLevel.levelCode : "N/A",
227         //             plot.observationUnitName,
228         //             plot.germplasmName,
229         //             plot.seedLotName ? plot.seedLotName : ''
230         //         ].join(',');
231         //         csv += "\n";
232         //     });
233     
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();    
239         // }
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(','),
245                 'type': type,
246                 'order': order,
247                 'start': start,
248             }).toString();
249             window.open(`/ajax/breeders/trial_plot_order?${q}`, '_blank');
250         }
252         set_meta_data() {
253             this.plot_arr = Object.values(this.plot_object);
254             var min_col = 100000;
255             var min_row = 100000;
256             var max_col = 0;
257             var max_row = 0;
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;
265             });
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 = {};
275         }
277         fill_holes() {
278             var fieldmap_hole_fillers = [];
279             let last_coord;
280             for (let plot of this.plot_arr) {
281                 if (last_coord === undefined) {
282                     last_coord = [0,1];
283                 }
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]);
289                     } else {
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);
293                     }
294                 } else {
295                     last_coord = [plot.observationUnitPosition.positionCoordinateX, plot.observationUnitPosition.positionCoordinateY];
296                 }
297             }
298             this.plot_arr = [...this.plot_arr.filter(plot => plot !== undefined), ...fieldmap_hole_fillers];
299         }
301         check_element(selection, element_id) {
302             document.getElementById(element_id).checked = selection;
303         }
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];
310             }
311         }
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];
317             return {
318                 type: type,
319                 observationUnitName: this.trial_id + ' ' + type,
320                 observationUnitPosition: {
321                     positionCoordinateX: x,
322                     positionCoordinateY: y
323                 },
324                 locationName: p.locationName,
325                 studyName: p.studyName
326             }
327         }
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;
335             this.plot_arr = [
336                 ...this.plot_arr.slice(0, Object.entries(this.plot_object).length),
337             ];
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) });
347                 var plot_count = 0;
348                 var row_count = 0;
349                 for (let j = this.meta_data.min_row; j < (this.meta_data.min_row+rows); j++) {
350                     row_count++;
351                     var swap_columns = this.meta_data.plot_layout == "serpentine" && j % 2 === 0;
352                     var col_count = 0;
353                     for (let i = this.meta_data.min_col; i < (this.meta_data.min_col+cols); i++) {
354                         col_count++;
355                         var row = j;
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;
363                         }
364                         plot_count++;
365                     }
366                 }
367             }
368         }
370         add_corners() {
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));
374                 }
375             }
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);
381         }
382         add_border(border_element, row_or_col, min_or_max) {
383             var start_iter;
384             var end_iter;
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;
391             }
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));
396                 }
397             }
398         }
400         add_borders() {
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);
406                 this.add_corners();
407             }
408         }
411         transpose() {
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;
417             });
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;
430         
431             d3.select("svg").remove();
432             this.add_borders();
433             this.render();
434           
435         }
437         clickcancel() {
438             var event = d3.dispatch('click', 'dblclick');
439             function cc(selection) {
440                 var down,
441                     tolerance = 5,
442                     last,
443                     wait = null;
444                 function dist(a, b) {
445                     return Math.sqrt(Math.pow(a[0] - b[0], 2), Math.pow(a[1] - b[1], 2));
446                 }
447                 selection.on('mousedown', function() {
448                     down = d3.mouse(document.body);
449                     last = +new Date();
450                 });
451                 selection.on('mouseup', function() {
452                     if (dist(down, d3.mouse(document.body)) > tolerance) {
453                         return;
454                     } else {
455                         if (wait) {
456                             window.clearTimeout(wait);
457                             wait = null;
458                             event.dblclick(d3.event);
459                         } else {
460                             wait = window.setTimeout((function(e) {
461                                 return function() {
462                                     event.click(e);
463                                     wait = null;
464                                 };
465                             })(d3.event), 300);
466                         }
467                     }
468                 });
469             };
470             return d3.rebind(cc, event, 'on');
471         }
473         heatmap_plot_click(plot, heatmap_object, trait_name) {
474             if (d3.event && d3.event.detail > 1) {
475                 return;
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);
486             }
487         }
489         fieldmap_plot_click(plot) {
490             if (d3.event && d3.event.detail > 1) {
491                 return;
492             } else {
493                 function btnClick(n){
494                     if (n.length == 0){
495                         jQuery("#hm_view_plot_image_submit").addClass("disabled");
496                     } else {
497                         jQuery("#hm_view_plot_image_submit").removeClass("disabled");
498                     }
499                     return true; 
500                 }
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');
518                     new jQuery.ajax({
519                         type: 'POST',
520                         url: '/ajax/breeders/trial/'+ trial_id +'/retrieve_plot_images',
521                         dataType: "json",
522                         data: {
523                                 'image_ids': JSON.stringify(image_ids),
524                                 'plot_name': replace_plot_name,
525                                 'plot_id': replace_plot_id,
526                         },
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);
532                         }
533                         else {
534                             jQuery("#show_plot_image_ids").html(images);
536                         // jQuery('#view_plot_image_dialog').modal("show"); 
537                         }
538                         },
539                         error: function () {
540                             jQuery('#working_modal').modal("hide");
541                             alert('An error occurred retrieving plot images');
542                         }
543                     });
544                 }
545             }
546         }
548         addEventListeners() {
549             let LocalThis = this;
550             let transposeBtn = document.getElementById("transpose_fieldmap");
551             transposeBtn.onclick = function() {
552                 LocalThis.transpose();
553             }
554         }
555         
556         FieldMap() {
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;
563             var trait_vals = [];
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);
570                 }
571                 var colorScale = d3.scale.quantile()
572                 .domain(trait_vals)
573                 .range(colors);
574             }
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);
580                 }
581                 return false;
582             }
584             var get_fieldmap_plot_color = function(plot) {
585                 var color;
586                 if (plot.observationUnitPosition.observationLevelRelationships) {
587                     if ( is_plot_overlapping(plot) ) {
588                         color = "#000";
589                     }
590                     else if (plot.observationUnitPosition.entryType == "check") {
591                         color = "#6a5acd";
592                     } else if (plot.observationUnitPosition.observationLevelRelationships[1].levelCode % 2 == 0) {
593                         color = "#c7e9b4";
594                     } else if (plot.observationUnitName.includes(local_this.trial_id + " filler")) {
595                         color = "lightgrey";    
596                     } else {
597                         color = "#41b6c4";
598                     }
599                 } else {
600                     color = "lightgrey";
601                 }
602                 return color;
603             }
604             
605             var get_heatmap_plot_color = function(plot) {
606                 var color;
607                 if ( is_plot_overlapping(plot) ) {
608                     color = "#000";
609                 }
610                 else if (!plot.observationUnitPosition.observationLevel) {
611                     color = "lightgrey";
612                 } else {
613                     color = heatmap_object[trait_name][plot.observationUnitDbId] ? colorScale(heatmap_object[trait_name][plot.observationUnitDbId].val) : "white";
614                 }
615                 return color;
616             }
617             var get_stroke_color = function(plot) {
618                 var stroke_color;
619                 if (plot.observationUnitPosition.observationLevel) {
620                     if (plot.observationUnitPosition.observationLevelRelationships[0].levelCode % 2 == 0) {
621                         stroke_color = "red"
622                     } else {
623                         stroke_color = "green";
624                     }
625                 } else {
626                     stroke_color = "#666";
627                 }
628                 return stroke_color;
629             }
631             var get_plot_message = function(plot) {
632                 let html = '';
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(', ')}`;
637                 }
638                 else {
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 />` :
641                         "";
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}`;
652                         }
653                     }
654                 }
655                 return html;
656             }
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));
669                 }
670             }
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();
680             }
682             var plot_x_coord = function(plot) {
683                 return plot.observationUnitPosition.positionCoordinateX - min_col + col_increment + 1
684             }
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;
690                 }
691                 return y;
692             }
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 ? 
698                 width + 1 : 
699                 width;
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 ? 
704                 height + 1 : 
705                 height;
706             var row_increment = this.meta_data.invert_row_checkmark ?
707                 1 : 
708                 0;
709             row_increment = this.meta_data.display_borders && this.meta_data.top_border_selection && this.meta_data.invert_row_checkmark ?
710                 row_increment + 1 : 
711                 row_increment;
712             var y_offset = this.meta_data.display_borders && this.meta_data.top_border_selection && !this.meta_data.invert_row_checkmark ? 
713                 50 : 
714                 0;
715             var col_increment = this.meta_data.display_borders && this.meta_data.left_border_selection ? 
716                 1 : 
717                 0;
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;
728                     if ( x && y ) {
729                         let k = `${x}-${y}`;
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];
734                         }
735                     }
736                 }
737             });
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")
747                 .append("svg")
748                 .attr("width", width * 50 + 20 + "px")
749                 .attr("height", height * 50 + 20 + "px")
751             var tooltip = d3.select("#fieldmap_chart")
752                 .append("rect")
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 })
763                 .attr("rx", 2)
764                 .attr("id", (d) => { return `fieldmap-plot-${d.observationUnitDbId}` })
765                 .attr("class", "col bordered")
766                 .attr("width", 48)
767                 .attr("height", 48)
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)
773                 .call(cc);
775             cc.on("click", (el) => { 
776                 var plot = d3.select(el.srcElement).data()[0];
777                 plot_click(plot, heatmap_object, trait_name)
778             });
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');        
784                 }
785             });
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 })
792                     .attr("rx", 2)
793                     .attr("width", 40)
794                     .attr("height", 6)
795                     .style("fill", (d) => { return local_this.linked_trials[d.studyName].bg })
796                     .style("opacity", (d) => { return is_plot_overlapping(d) ? '0' : '100' })
797             }
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})
803                 .text((d) => {
804                     if (!(d.observationUnitName.includes(local_this.trial_id + " filler")) && d.type == "data" && !is_plot_overlapping(d) ) { 
805                         return d.observationUnitPosition.observationLevel.levelCode;
806                     }
807                 })
808                 .on("mouseover", handle_mouseover)
809                 .on("mouseout", handle_mouseout);
811             var image_icon = function(d) {
812                 var image = d.plotImageDbIds || []; 
813                 var plot_image;
814                 if (image.length > 0){
815                     plot_image = "/static/css/images/plot_images.png"; 
816                 }else{
817                     plot_image = "";
818                 }
819                 return plot_image;
820             }
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 })
826                 .attr("width", 20)
827                 .attr("height", 20)
828                 .on("mouseover", handle_mouseover)
829                 .on("mouseout", handle_mouseout)
830                                       
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);
837             }
838             for (let i = min_col; i <= max_col; i++) {
839                 col_label_arr.push(i);
840             }
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();
847             }
849             grid.selectAll(".rowLabels") 
850                 .data(row_label_arr)
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;
857                 })
858                 .text((label) => { return label });
860             grid.selectAll(".colLabels") 
861                 .data(col_label_arr)
862                 .enter().append("text")
863                 .attr("x", (label, i) => {
864                     let x = label-min_col+col_increment+2;
865                     return x * 50 - 30;
866                 })
867                 .attr("y", (col_labels_row * 50) + 45 + y_offset)
868                 .text((label) => { return label });
869         }
872         load() {
873             d3.select("svg").remove();
874             this.change_dimensions(this.meta_data.num_cols, this.meta_data.num_rows);
875             this.add_borders();
876             this.render();
877         }
879         render() {
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");
886             this.FieldMap();
887         }
888     }
890     const mapObj = new FieldMap();
891     return mapObj;