Merge branch master into topic/phenotype_heatmap_multiple_trials
[sgn.git] / js / source / entries / fieldmap.js
blobbf52a26ba852bb97dc9aa1040dfb71769ed6b209
1 import "../legacy/d3/d3v4Min.js";
2 import "../legacy/jquery.js";
3 import "../legacy/brapi/BrAPI.js";
5 // Colors to use when labelling multiple trials
6 const trial_colors = [
7     "#2f4f4f",
8     "#ff8c00",
9     "#ffff00",
10     "#00ff00",
11     "#9400d3",
12     "#00ffff",
13     "#1e90ff",
14     "#ff1493",
15     "#ffdab9",
16     "#228b22",
18 const trial_colors_text = [
19     "#ffffff",
20     "#000000",
21     "#000000",
22     "#000000",
23     "#ffffff",
24     "#000000",
25     "#ffffff",
26     "#ffffff",
27     "#000000",
28     "#ffffff",
31 export function init() {
32     class FieldMap {
33         constructor(trial_id) {
34             this.trial_id = String;
35             this.plot_arr = Array;
36             this.plot_object = Object;
37             this.meta_data = {};
38             this.brapi_plots = Object;
39             this.heatmap_queried = false;
40             this.heatmap_selected = false;
41             this.heatmap_selection = String;
42             this.heatmap_object = Object;
43             this.display_borders = true;
44             this.linked_trials = {};
45         }
47         set_id(trial_id) {
48             this.trial_id = trial_id;
49         }
51         set_linked_trials(trials = []) {
52             this.linked_trials = {};
53             trials.forEach((t, i) => {
54                 const index = i % trial_colors.length;
55                 this.linked_trials[t.trial_name] = {
56                     id: t.trial_id,
57                     name: t.trial_name,
58                     bg: trial_colors[index],
59                     fg: trial_colors_text[index],
60                 };
61             });
62         }
64         get_linked_trials() {
65             return this.linked_trials;
66         }
68         format_brapi_post_object() {
69             let brapi_post_plots = [];
70             let count = 1;
71             for (let plot of this.plot_arr.filter((plot) => plot.type == "filler")) {
72                 brapi_post_plots.push({
73                     additionalInfo: {
74                         invert_row_checkmark: document.getElementById(
75                             "invert_row_checkmark"
76                         ).checked,
77                         top_border_selection: this.meta_data.top_border_selection || false,
78                         left_border_selection:
79                             this.meta_data.left_border_selection || false,
80                         right_border_selection:
81                             this.meta_data.right_border_selection || false,
82                         bottom_border_selection:
83                             this.meta_data.bottom_border_selection || false,
84                         plot_layout: this.meta_data.plot_layout || "serpentine",
85                     },
86                     germplasmDbId: this.meta_data.filler_accession_id,
87                     germplasmName: this.meta_data.filler_accession_name,
88                     observationUnitName:
89                         this.trial_id +
90                         " filler " +
91                         (parseInt(this.meta_data.max_level_code) + count),
92                     observationUnitPosition: {
93                         observationLevel: {
94                             levelCode: parseInt(this.meta_data.max_level_code) + count,
95                             levelName: "plot",
96                             levelOrder: 2,
97                         },
98                         positionCoordinateX:
99                             plot.observationUnitPosition.positionCoordinateX,
100                         positionCoordinateY:
101                             plot.observationUnitPosition.positionCoordinateY,
102                     },
103                     trialDbId: this.trial_id,
104                     studyDbId: this.trial_id,
105                 });
106                 count++;
107             }
108             return brapi_post_plots;
109         }
111         format_brapi_put_object() {
112             let brapi_plots = {};
113             for (let plot of this.plot_arr.filter((plot) => plot.type == "data")) {
114                 brapi_plots[plot.observationUnitDbId] = {
115                     additionalInfo: {
116                         invert_row_checkmark: document.getElementById(
117                             "invert_row_checkmark"
118                         ).checked,
119                         top_border_selection: this.meta_data.top_border_selection || false,
120                         left_border_selection:
121                             this.meta_data.left_border_selection || false,
122                         right_border_selection:
123                             this.meta_data.right_border_selection || false,
124                         bottom_border_selection:
125                             this.meta_data.bottom_border_selection || false,
126                         plot_layout: this.meta_data.plot_layout || "serpentine",
127                     },
128                     germplasmDbId: plot.germplasmDbId,
129                     germplasmName: plot.gerplasmName,
130                     observationUnitName: plot.observationUnitName,
131                     observationUnitPosition: {
132                         observationLevel: {
133                             levelCode:
134                                 plot.observationUnitPosition.observationLevel.levelCode,
135                             levelName: "plot",
136                             levelOrder: 2,
137                         },
138                         positionCoordinateX:
139                             plot.observationUnitPosition.positionCoordinateX,
140                         positionCoordinateY:
141                             plot.observationUnitPosition.positionCoordinateY,
142                     },
143                     trialDbId: this.trial_id,
144                 };
145             }
146             return brapi_plots;
147         }
149         filter_data(data) {
150             var pseudo_layout = {};
151             var plot_object = {};
152             for (let plot of data) {
153                 plot.type = "data";
154                 if (isNaN(parseInt(plot.observationUnitPosition.positionCoordinateY))) {
155                     plot.observationUnitPosition.positionCoordinateY = isNaN(
156                         parseInt(
157                             plot.observationUnitPosition.observationLevelRelationships[1]
158                                 .levelCode
159                         )
160                     )
161                         ? plot.observationUnitPosition.observationLevelRelationships[0]
162                             .levelCode
163                         : plot.observationUnitPosition.observationLevelRelationships[1]
164                             .levelCode;
165                     if (
166                         plot.observationUnitPosition.positionCoordinateY in pseudo_layout
167                     ) {
168                         pseudo_layout[
169                             plot.observationUnitPosition.positionCoordinateY
170                         ] += 1;
171                         plot.observationUnitPosition.positionCoordinateX =
172                             pseudo_layout[plot.observationUnitPosition.positionCoordinateY];
173                     } else {
174                         pseudo_layout[plot.observationUnitPosition.positionCoordinateY] = 1;
175                         plot.observationUnitPosition.positionCoordinateX = 1;
176                     }
177                 }
178                 var obs_level = plot.observationUnitPosition.observationLevel;
179                 if (obs_level.levelName == "plot") {
180                     plot.observationUnitPosition.positionCoordinateX = parseInt(
181                         plot.observationUnitPosition.positionCoordinateX
182                     );
183                     plot.observationUnitPosition.positionCoordinateY = parseInt(
184                         plot.observationUnitPosition.positionCoordinateY
185                     );
186                     // if (plot.additionalInfo && plot.additionalInfo.type == "filler") {
187                     //     plot.type = "filler";
188                     // } else {
189                     //     plot.type = "data";
190                     // }
191                     plot_object[plot.observationUnitDbId] = plot;
192                 }
193             }
194             this.plot_object = plot_object;
195         }
197         filter_heatmap(observations) {
198             this.heatmap_object = {};
199             for (let observation of observations) {
200                 let trait_name = observation.observationVariableName;
201                 if (!this.heatmap_object[trait_name]) {
202                     this.heatmap_object[trait_name] = {
203                         [observation.observationUnitDbId]: {
204                             val: observation.value,
205                             plot_name: observation.observationUnitName,
206                             id: observation.observationDbId,
207                         },
208                     };
209                 } else {
210                     this.heatmap_object[trait_name][observation.observationUnitDbId] = {
211                         val: observation.value,
212                         plot_name: observation.observationUnitName,
213                         id: observation.observationDbId,
214                     };
215                 }
216             }
217         }
219         get_plot_order(
220             type,
221             order,
222             start,
223             include_borders,
224             include_gaps,
225             additional_properties
226         ) {
227             let q = new URLSearchParams({
228                 trial_ids: [
229                     this.trial_id,
230                     ...Object.keys(this.linked_trials).map(
231                         (e) => this.linked_trials[e].id
232                     ),
233                 ].join(","),
234                 type: type,
235                 order: order,
236                 start: start,
237                 top_border: !!include_borders && !!this.meta_data.top_border_selection,
238                 right_border:
239                     !!include_borders && !!this.meta_data.right_border_selection,
240                 bottom_border:
241                     !!include_borders && !!this.meta_data.bottom_border_selection,
242                 left_border:
243                     !!include_borders && !!this.meta_data.left_border_selection,
244                 gaps: !!include_gaps,
245                 ...additional_properties,
246             }).toString();
247             window.open(`/ajax/breeders/trial_plot_order?${q}`, "_blank");
248         }
250         set_meta_data() {
251             this.plot_arr = Object.values(this.plot_object);
252             var min_col = 100000;
253             var min_row = 100000;
254             var max_col = 0;
255             var max_row = 0;
256             var max_level_code = 0;
257             this.plot_arr.forEach((plot) => {
258                 max_col =
259                     plot.observationUnitPosition.positionCoordinateX > max_col
260                         ? plot.observationUnitPosition.positionCoordinateX
261                         : max_col;
262                 min_col =
263                     plot.observationUnitPosition.positionCoordinateX < min_col
264                         ? plot.observationUnitPosition.positionCoordinateX
265                         : min_col;
266                 max_row =
267                     plot.observationUnitPosition.positionCoordinateY > max_row
268                         ? plot.observationUnitPosition.positionCoordinateY
269                         : max_row;
270                 min_row =
271                     plot.observationUnitPosition.positionCoordinateY < min_row
272                         ? plot.observationUnitPosition.positionCoordinateY
273                         : min_row;
274                 max_level_code =
275                     parseInt(plot.observationUnitPosition.observationLevel.levelCode) >
276                         max_level_code
277                         ? plot.observationUnitPosition.observationLevel.levelCode
278                         : max_level_code;
279             });
280             this.meta_data.min_row = min_row;
281             this.meta_data.max_row = max_row;
282             this.meta_data.min_col = min_col;
283             this.meta_data.max_col = max_col;
284             this.meta_data.num_rows = max_row - min_row + 1;
285             this.meta_data.num_cols = max_col - min_col + 1;
286             this.meta_data.max_level_code = max_level_code;
287             this.meta_data.display_borders = !jQuery(
288                 "#include_linked_trials_checkmark"
289             ).is(":checked");
290             this.meta_data.overlapping_plots = {};
291         }
293         fill_holes() {
294             var fieldmap_hole_fillers = [];
295             let last_coord;
296             for (let plot of this.plot_arr) {
297                 if (last_coord === undefined) {
298                     last_coord = [0, 1];
299                 }
300                 if (plot === undefined) {
301                     if (last_coord[0] < this.meta_data.max_col) {
302                         fieldmap_hole_fillers.push(
303                             this.get_plot_format(
304                                 `Empty_Space_(${last_coord[0] + 1}_${last_coord[1]})`,
305                                 last_coord[0] + 1,
306                                 last_coord[1]
307                             )
308                         );
309                         last_coord = [last_coord[0] + 1, last_coord[1]];
310                         this.plot_object[
311                             "Empty Space" + String(last_coord[0]) + String(last_coord[1])
312                         ] = this.get_plot_format(
313                             "empty_space",
314                             last_coord[0] + 1,
315                             last_coord[1]
316                         );
317                     } else {
318                         fieldmap_hole_fillers.push(
319                             this.get_plot_format(
320                                 `Empty_Space_${this.meta_data.min_col}_${last_coord[1] + 1}`,
321                                 this.meta_data.min_col,
322                                 last_coord[1] + 1
323                             )
324                         );
325                         last_coord = [this.meta_data.min_col, last_coord[1]];
326                         this.plot_object[
327                             "Empty Space" + String(last_coord[0]) + String(last_coord[1])
328                         ] = this.get_plot_format(
329                             "empty_space",
330                             this.meta_data.min_col,
331                             last_coord[1] + 1
332                         );
333                     }
334                 } else {
335                     last_coord = [
336                         plot.observationUnitPosition.positionCoordinateX,
337                         plot.observationUnitPosition.positionCoordinateY,
338                     ];
339                 }
340             }
341             this.plot_arr = [
342                 ...this.plot_arr.filter((plot) => plot !== undefined),
343                 ...fieldmap_hole_fillers,
344             ];
345         }
347         check_element(selection, element_id) {
348             document.getElementById(element_id).checked = selection;
349         }
351         check_elements(additionalInfo) {
352             var elements = [
353                 "top_border_selection",
354                 "left_border_selection",
355                 "right_border_selection",
356                 "bottom_border_selection",
357                 "invert_row_checkmark",
358             ];
359             for (let element of elements) {
360                 this.check_element(additionalInfo[element], element);
361                 this.meta_data[element] = additionalInfo[element];
362             }
363         }
365         get_plot_format(type, x, y) {
366             // Use the first plot from the trial to get trial-level metadata to give to a border plot
367             // NOTE: this will break if plots from multiple trials are loaded
368             let p = this.plot_arr[0];
369             return {
370                 type: type,
371                 observationUnitName: this.trial_id + " " + type,
372                 observationUnitPosition: {
373                     positionCoordinateX: x,
374                     positionCoordinateY: y,
375                 },
376                 locationName: p.locationName,
377                 studyName: p.studyName,
378             };
379         }
381         change_dimensions(cols, rows) {
382             var cols = parseInt(cols);
383             var rows = parseInt(rows);
384             this.meta_data.post = false;
385             this.meta_data.num_cols = cols;
386             this.meta_data.num_rows = rows;
387             this.plot_arr = [
388                 ...this.plot_arr.slice(0, Object.entries(this.plot_object).length),
389             ];
391             if (this.meta_data.retain_layout == false) {
392                 this.meta_data.max_row = rows + this.meta_data.min_row - 1;
393                 this.meta_data.max_col = cols + this.meta_data.min_col - 1;
394                 this.meta_data.plot_layout = this.meta_data.plot_layout
395                     ? this.meta_data.plot_layout
396                     : "serpentine";
398                 this.plot_arr = this.plot_arr.filter((plot) => plot.type == "data");
399                 this.plot_arr.sort(function (a, b) {
400                     return (
401                         parseFloat(a.observationUnitPosition.observationLevel.levelCode) -
402                         parseFloat(b.observationUnitPosition.observationLevel.levelCode)
403                     );
404                 });
406                 var plot_count = 0;
407                 var row_count = 0;
408                 for (
409                     let j = this.meta_data.min_row;
410                     j < this.meta_data.min_row + rows;
411                     j++
412                 ) {
413                     row_count++;
414                     var swap_columns =
415                         this.meta_data.plot_layout == "serpentine" && j % 2 === 0;
416                     var col_count = 0;
417                     for (
418                         let i = this.meta_data.min_col;
419                         i < this.meta_data.min_col + cols;
420                         i++
421                     ) {
422                         col_count++;
423                         var row = j;
424                         var col = swap_columns ? this.meta_data.max_col - col_count + 1 : i;
425                         if (
426                             plot_count >= this.plot_arr.length &&
427                             this.meta_data.filler_accession_id
428                         ) {
429                             this.meta_data.post = true;
430                             this.plot_arr[plot_count] = this.get_plot_format(
431                                 "filler",
432                                 col,
433                                 row
434                             );
435                         } else if (
436                             plot_count < this.plot_arr.length &&
437                             this.plot_arr[plot_count].observationUnitPosition
438                         ) {
439                             this.plot_arr[
440                                 plot_count
441                             ].observationUnitPosition.positionCoordinateX = col;
442                             this.plot_arr[
443                                 plot_count
444                             ].observationUnitPosition.positionCoordinateY = row;
445                         }
446                         plot_count++;
447                     }
448                 }
449             }
450         }
452         add_corners() {
453             var add_corner = (condition_1, condition_2, x, y) => {
454                 if (condition_1 && condition_2) {
455                     this.plot_arr.push(this.get_plot_format("border", x, y));
456                 }
457             };
458             add_corner(
459                 this.meta_data.top_border_selection,
460                 this.meta_data.left_border_selection,
461                 this.meta_data.min_col - 1,
462                 this.meta_data.min_row - 1
463             );
464             add_corner(
465                 this.meta_data.top_border_selection,
466                 this.meta_data.right_border_selection,
467                 this.meta_data.max_col + 1,
468                 this.meta_data.min_row - 1
469             );
470             add_corner(
471                 this.meta_data.bottom_border_selection,
472                 this.meta_data.left_border_selection,
473                 this.meta_data.min_col - 1,
474                 this.meta_data.max_row + 1
475             );
476             add_corner(
477                 this.meta_data.bottom_border_selection,
478                 this.meta_data.right_border_selection,
479                 this.meta_data.max_col + 1,
480                 this.meta_data.max_row + 1
481             );
482         }
483         add_border(border_element, row_or_col, min_or_max) {
484             var start_iter;
485             var end_iter;
486             if (row_or_col == "row") {
487                 start_iter = this.meta_data.min_col;
488                 end_iter = this.meta_data.max_col;
489             } else if (row_or_col == "col") {
490                 start_iter = this.meta_data.min_row;
491                 end_iter = this.meta_data.max_row;
492             }
494             if (this.meta_data[border_element]) {
495                 for (let i = start_iter; i <= end_iter; i++) {
496                     this.plot_arr.push(
497                         this.get_plot_format(
498                             "border",
499                             row_or_col == "row" ? i : min_or_max,
500                             row_or_col == "row" ? min_or_max : i
501                         )
502                     );
503                 }
504             }
505         }
507         add_borders() {
508             if (this.meta_data.display_borders) {
509                 this.add_border(
510                     "left_border_selection",
511                     "col",
512                     this.meta_data.min_col - 1
513                 );
514                 this.add_border(
515                     "top_border_selection",
516                     "row",
517                     this.meta_data.min_row - 1
518                 );
519                 this.add_border(
520                     "right_border_selection",
521                     "col",
522                     this.meta_data.max_col + 1
523                 );
524                 this.add_border(
525                     "bottom_border_selection",
526                     "row",
527                     this.meta_data.max_row + 1
528                 );
529                 this.add_corners();
530             }
531         }
533         transpose() {
534             this.plot_arr = this.plot_arr.filter((plot) => plot.type != "border");
535             this.plot_arr.map((plot) => {
536                 let tempX = plot.observationUnitPosition.positionCoordinateX;
537                 plot.observationUnitPosition.positionCoordinateX =
538                     plot.observationUnitPosition.positionCoordinateY;
539                 plot.observationUnitPosition.positionCoordinateY = tempX;
540             });
542             let tempMaxCol = this.meta_data.max_col;
543             this.meta_data.max_col = this.meta_data.max_row;
544             this.meta_data.max_row = tempMaxCol;
546             let tempMinCol = this.meta_data.min_col;
547             this.meta_data.min_col = this.meta_data.min_row;
548             this.meta_data.min_row = tempMinCol;
550             let tempNumCols = this.meta_data.num_cols;
551             this.meta_data.num_cols = this.meta_data.num_rows;
552             this.meta_data.num_rows = tempNumCols;
553             d3.select("svg").remove();
554             this.add_borders();
555             this.render();
556         }
558         clickcancel() {
560             var event = d3.dispatch("click", "dblclick");
561             function cc(selection) {
562                 var down,
563                     tolerance = 5,
564                     last,
565                     wait = null;
566                 function dist(a, b) {
567                     return Math.sqrt(Math.pow(a[0] - b[0], 2), Math.pow(a[1] - b[1], 2));
568                 }
570                 selection.on("mousedown", function () {
571                     down = d3.mouse(document.body);
572                     last = +new Date();
573                 });
574                 selection.on("mouseup", function () {
576                     if (dist(down, d3.mouse(document.body)) > tolerance) {
577                         return;
578                     } else {
579                         if (wait) {
580                             window.clearTimeout(wait);
581                             wait = null;
582                             event.call("dblclick", this, d3.event);
583                         } else {
585                             wait = window.setTimeout(
586                                 (function (e) {
587                                     return function () {
588                                         event.call("click", this, e);
589                                         wait = null;
590                                     };
591                                 })(d3.event),
592                                 300
593                             );
594                         }
595                     }
596                 });
597             }
598             // return d3.rebind(cc, event, 'on');
599             return _rebind(cc, event, "on");
601             // Copies a variable number of methods from source to target.
602             function _rebind(target, source) {
604                 var i = 1,
605                     n = arguments.length,
606                     method;
607                 while (++i < n)
608                     target[(method = arguments[i])] = d3_rebind(
609                         target,
610                         source,
611                         source[method]
612                     );
613                 return target;
614             }
616             // Method is assumed to be a standard D3 getter-setter:
617             // If passed with no arguments, gets the value.
618             // If passed with arguments, sets the value and returns the target.
619             function d3_rebind(target, source, method) {
621                 return function () {
622                     var value = method.apply(source, arguments);
623                     return arguments.length ? target : value;
624                 };
625             }
626         }
628         heatmap_plot_click(plot, heatmap_object, trait_name) {
629             if (d3.event && d3.event.detail > 1) {
630                 return;
631             } else if (
632                 trait_name in heatmap_object &&
633                 heatmap_object[trait_name][plot.observationUnitDbId]
634             ) {
635                 let val, plot_name, pheno_id;
636                 val = heatmap_object[trait_name][plot.observationUnitDbId].val;
637                 plot_name =
638                     heatmap_object[trait_name][plot.observationUnitDbId].plot_name;
639                 pheno_id = heatmap_object[trait_name][plot.observationUnitDbId].id;
640                 jQuery("#suppress_plot_pheno_dialog").modal("show");
641                 jQuery("#myplot_name").html(plot_name);
642                 jQuery("#pheno_value").html(val);
643                 jQuery("#mytrait_id").html(trait_name);
644                 jQuery("#mypheno_id").html(pheno_id);
645             }
646         }
648         fieldmap_plot_click(plot) {
649             if (d3.event && d3.event.detail > 1) {
650                 return;
651             } else {
652                 function btnClick(n) {
653                     if (n.length == 0) {
654                         jQuery("#hm_view_plot_image_submit").addClass("disabled");
655                     } else {
656                         jQuery("#hm_view_plot_image_submit").removeClass("disabled");
657                     }
658                     return true;
659                 }
660                 if (plot.type == "data") {
661                     var image_ids = plot.plotImageDbIds || [];
662                     var replace_accession = plot.germplasmName;
663                     var replace_plot_id = plot.observationUnitDbId;
664                     var replace_plot_name = plot.observationUnitName;
665                     plot;
666                     var replace_plot_number =
667                         plot.observationUnitPosition.observationLevel.levelCode;
669                     jQuery("#plot_image_ids").html(image_ids);
670                     jQuery("#hm_replace_accessions_link").find("button").trigger("click");
671                     jQuery("#hm_replace_accessions_link").on("click", function () {
672                         btnClick(image_ids);
673                     });
674                     jQuery("#hm_edit_plot_information").html(
675                         "<b>Selected Plot Information: </b>"
676                     );
677                     jQuery("#hm_edit_plot_name").html(replace_plot_name);
678                     jQuery("#hm_edit_plot_number").html(replace_plot_number);
679                     var old_plot_id = jQuery("#hm_edit_plot_id").html(replace_plot_id);
680                     var old_plot_accession = jQuery("#hm_edit_plot_accession").html(
681                         replace_accession
682                     );
683                     jQuery("#hm_replace_plot_accessions_dialog").modal("show");
685                     new jQuery.ajax({
686                         type: "POST",
687                         url: "/ajax/breeders/trial/" + trial_id + "/retrieve_plot_images",
688                         dataType: "json",
689                         data: {
690                             image_ids: JSON.stringify(image_ids),
691                             plot_name: replace_plot_name,
692                             plot_id: replace_plot_id,
693                         },
694                         success: function (response) {
695                             jQuery("#working_modal").modal("hide");
696                             var images = response.image_html;
697                             if (response.error) {
698                                 alert("Error Retrieving Plot Images: " + response.error);
699                             } else {
700                                 jQuery("#show_plot_image_ids").html(images);
702                                 // jQuery('#view_plot_image_dialog').modal("show");
703                             }
704                         },
705                         error: function () {
706                             jQuery("#working_modal").modal("hide");
707                             alert("An error occurred retrieving plot images");
708                         },
709                     });
710                 }
711             }
712         }
714         addEventListeners() {
715             let LocalThis = this;
716             let transposeBtn = document.getElementById("transpose_fieldmap");
717             transposeBtn.onclick = function () {
718                 LocalThis.transpose();
719             };
720         }
722         FieldMap() {
723             this.addEventListeners();
724             var cc = this.clickcancel();
725             const colors = [
726                 "#ffffd9",
727                 "#edf8b1",
728                 "#c7e9b4",
729                 "#7fcdbb",
730                 "#41b6c4",
731                 "#1d91c0",
732                 "#225ea8",
733                 "#253494",
734                 "#081d58",
735             ];
736             var trait_name = this.heatmap_selection;
737             var heatmap_object = this.heatmap_object;
738             var plot_click = !this.heatmap_selected
739                 ? this.fieldmap_plot_click
740                 : this.heatmap_plot_click;
741             var trait_vals = [];
742             var local_this = this;
744             if (this.heatmap_selected) {
745                 let plots_with_selected_trait = heatmap_object[trait_name];
746                 for (let obs_unit of Object.values(plots_with_selected_trait)) {
747                     trait_vals.push(obs_unit.val);
748                 }
750                 var colorScale = d3.scaleQuantile().domain(trait_vals).range(colors);
751             }
753             var is_plot_overlapping = function (plot) {
754                 if (plot.observationUnitPosition) {
755                     let k = `${plot.observationUnitPosition.positionCoordinateX}-${plot.observationUnitPosition.positionCoordinateY}`;
756                     return Object.keys(local_this.meta_data.overlapping_plots).includes(
757                         k
758                     );
759                 }
760                 return false;
761             };
763             var get_fieldmap_plot_color = function (plot) {
764                 var color;
765                 if (plot.observationUnitPosition.observationLevelRelationships) {
766                     if (is_plot_overlapping(plot)) {
767                         color = "#000";
768                     } else if (plot.observationUnitPosition.entryType == "check") {
769                         color = "#6a5acd";
770                     } else if (
771                         plot.observationUnitPosition.observationLevelRelationships[1]
772                             .levelCode %
773                         2 ==
774                         0
775                     ) {
776                         color = "#c7e9b4";
777                     } else if (
778                         plot.observationUnitName.includes(local_this.trial_id + " filler")
779                     ) {
780                         color = "lightgrey";
781                     } else {
782                         color = "#41b6c4";
783                     }
784                 } else {
785                     color = "lightgrey";
786                 }
787                 return color;
788             };
790             var get_heatmap_plot_color = function (plot) {
791                 var color;
792                 if (is_plot_overlapping(plot)) {
793                     color = "#000";
794                 } else if (!plot.observationUnitPosition.observationLevel) {
795                     color = "lightgrey";
796                 } else {
797                     var cs = heatmap_object.hasOwnProperty(trait_name) && heatmap_object[trait_name].hasOwnProperty(plot.observationUnitDbId)
798                         ? colorScale(heatmap_object[trait_name][plot.observationUnitDbId].val)
799                         : "white";
800                     color = cs ? cs : "lightgrey";
801                 }
802                 return color;
803             };
804             var get_stroke_color = function (plot) {
805                 var stroke_color;
806                 if (plot.observationUnitPosition.observationLevel) {
807                     if (
808                         plot.observationUnitPosition.observationLevelRelationships[0]
809                             .levelCode %
810                         2 ==
811                         0
812                     ) {
813                         stroke_color = "red";
814                     } else {
815                         stroke_color = "green";
816                     }
817                 } else {
818                     stroke_color = "#666";
819                 }
820                 return stroke_color;
821             };
823             var get_plot_message = function (plot) {
824                 let html = "";
825                 if (is_plot_overlapping(plot)) {
826                     let k = `${plot.observationUnitPosition.positionCoordinateX}-${plot.observationUnitPosition.positionCoordinateY}`;
827                     let plots = local_this.meta_data.overlapping_plots[k];
828                     html += `<strong>Overlapping Plots:</strong> ${plots.join(", ")}`;
829                 } else {
830                     html += jQuery("#include_linked_trials_checkmark").is(":checked")
831                         ? `<strong>Trial Name:</strong> <span style='padding: 1px 2px; border-radius: 4px; color: ${local_this.linked_trials[plot.studyName].fg
832                         }; background-color: ${local_this.linked_trials[plot.studyName].bg
833                         }'>${plot.studyName}</span><br />`
834                         : "";
835                     html += `<strong>Plot Name:</strong> ${plot.observationUnitName}<br />`;
836                     if (plot.type == "data") {
837                         html += `<strong>Plot Number:</strong> ${plot.observationUnitPosition.observationLevel.levelCode}<br />
838                             <strong>Block Number:</strong> ${plot.observationUnitPosition.observationLevelRelationships[1].levelCode}<br />
839                             <strong>Rep Number:</strong> ${plot.observationUnitPosition.observationLevelRelationships[0].levelCode}<br />
840                             <strong>Accession Name:</strong> ${plot.germplasmName}`;
841                         if ( local_this.heatmap_selected ) {
842                             let v = '<em>NA</em>';
843                             if ( heatmap_object.hasOwnProperty(trait_name) && heatmap_object[trait_name].hasOwnProperty(plot.observationUnitDbId) ) {
844                                 v = heatmap_object[trait_name][plot.observationUnitDbId].val;
845                                 v = isNaN(v) ? v : Math.round((parseFloat(v) + Number.EPSILON) * 100) / 100;
846                             }
847                             html += `<br /><strong>Trait Value:</strong> ${v}`;
848                         }
849                     }
850                 }
851                 return html;
852             };
854             var handle_mouseover = function (d) {
855                 if (d.observationUnitPosition.observationLevel) {
856                     d3.select(`#fieldmap-plot-${d.observationUnitDbId}`)
858                         .style("fill", "green")
859                         .style("cursor", "pointer")
861                         .style("stroke-width", 3)
862                         .style("stroke", "#000000");
863                     tooltip
864                         .style("opacity", 0.9)
865                         .style("left", window.event.clientX + 25 + "px")
866                         .style("top", window.event.clientY + "px")
867                         .html(get_plot_message(d));
868                 }
869             };
872             var handle_mouseout = function (d) {
873                 d3.select(`#fieldmap-plot-${d.observationUnitDbId}`)
874                     .style(
875                         "fill",
876                         !isHeatMap ? get_fieldmap_plot_color(d) : get_heatmap_plot_color(d)
877                     )
878                     .style("cursor", "default")
879                     .style("stroke-width", 2)
880                     .style("stroke", get_stroke_color);
881                 tooltip.style("opacity", 0);
882                 plots.exit().remove();
883             };
885             var plot_x_coord = function (plot) {
886                 return (
887                     plot.observationUnitPosition.positionCoordinateX -
888                     min_col +
889                     col_increment +
890                     1
891                 );
892             };
894             var plot_y_coord = function (plot) {
895                 let y =
896                     plot.observationUnitPosition.positionCoordinateY -
897                     min_row +
898                     row_increment;
899                 if (
900                     plot.type !== "border" &&
901                     document.getElementById("invert_row_checkmark").checked !== true
902                 ) {
903                     y = num_rows - y - 1;
904                 }
905                 return y;
906             };
908             var width =
909                 this.meta_data.display_borders && this.meta_data.left_border_selection
910                     ? this.meta_data.num_cols + 3
911                     : this.meta_data.num_cols + 2;
912             width =
913                 this.meta_data.display_borders && this.meta_data.right_border_selection
914                     ? width + 1
915                     : width;
916             var height =
917                 this.meta_data.display_borders && this.meta_data.top_border_selection
918                     ? this.meta_data.num_rows + 3
919                     : this.meta_data.num_rows + 2;
920             height =
921                 this.meta_data.display_borders && this.meta_data.bottom_border_selection
922                     ? height + 1
923                     : height;
924             var row_increment = this.meta_data.invert_row_checkmark ? 1 : 0;
925             row_increment =
926                 this.meta_data.display_borders &&
927                     this.meta_data.top_border_selection &&
928                     this.meta_data.invert_row_checkmark
929                     ? row_increment + 1
930                     : row_increment;
931             var y_offset =
932                 this.meta_data.display_borders &&
933                     this.meta_data.top_border_selection &&
934                     !this.meta_data.invert_row_checkmark
935                     ? 50
936                     : 0;
937             var col_increment =
938                 this.meta_data.display_borders && this.meta_data.left_border_selection
939                     ? 1
940                     : 0;
942             // Check the fieldmap for any overlapping plots (plots that share the same x/y coordinates)
943             this.meta_data.overlapping_plots = {};
944             let plot_positions = {};
945             this.plot_arr.forEach((plot) => {
946                 if (plot.observationUnitPosition) {
947                     let x = plot.observationUnitPosition.positionCoordinateX;
948                     let y = plot.observationUnitPosition.positionCoordinateY;
949                     let p = plot.observationUnitPosition.observationLevel
950                         ? plot.observationUnitPosition.observationLevel.levelCode
951                         : "";
952                     let t = plot.studyName;
953                     if (x && y) {
954                         let k = `${x}-${y}`;
955                         if (!plot_positions.hasOwnProperty(k)) plot_positions[k] = [];
956                         plot_positions[k].push(
957                             jQuery("#include_linked_trials_checkmark").is(":checked")
958                                 ? `${p} (${t})`
959                                 : p
960                         );
961                         if (plot_positions[k].length > 1) {
962                             this.meta_data.overlapping_plots[k] = plot_positions[k];
963                         }
964                     }
965                 }
966             });
968             var min_row = this.meta_data.min_row;
969             var max_row = this.meta_data.max_row;
970             var min_col = this.meta_data.min_col;
971             var max_col = this.meta_data.max_col;
972             var num_rows = this.meta_data.num_rows;
973             var isHeatMap = this.heatmap_selected;
976             var grid = d3
977                 .select("#fieldmap_chart")
978                 .append("svg")
979                 .attr("width", width * 50 + 20 + "px")
980                 .attr("height", height * 50 + 20 + "px");
983             var tooltip = d3
984                 .select("#fieldmap_chart")
985                 .append("rect")
986                 .attr("id", "tooltip")
987                 .attr("class", "tooltip")
988                 .style("position", "fixed")
989                 .style("opacity", 0);
991             var plots = grid.selectAll("plots").data(this.plot_arr);
992             plots.append("title");
993             plots
994                 .enter()
995                 .append("rect")
996                 .attr("x", (d) => {
997                     return plot_x_coord(d) * 50;
998                 })
999                 .attr("y", (d) => {
1000                     return plot_y_coord(d) * 50 + 15 + y_offset;
1001                 })
1002                 .attr("rx", 2)
1003                 .attr("id", (d) => {
1004                     return `fieldmap-plot-${d.observationUnitDbId}`;
1005                 })
1006                 .attr("class", "col bordered")
1007                 .attr("width", 48)
1008                 .attr("height", 48)
1009                 .style("stroke-width", 2)
1010                 .style("stroke", get_stroke_color)
1011                 .style(
1012                     "fill",
1013                     !isHeatMap ? get_fieldmap_plot_color : get_heatmap_plot_color
1014                 )
1015                 .on("mouseover", handle_mouseover)
1016                 .on("mouseout", handle_mouseout)
1017                 .call(cc);
1020             cc.on("click", (el) => {
1021                 var plot = d3.select(el.srcElement).data()[0];
1022                 plot_click(plot, heatmap_object, trait_name);
1023             });
1024             cc.on("dblclick", (el) => {
1025                 var me = d3.select(el.srcElement);
1026                 var d = me.data()[0];
1027                 if (d.observationUnitDbId) {
1028                     window.open("/stock/" + d.observationUnitDbId + "/view");
1029                 }
1030             });
1032             // Add a colored band to the bottom of the plot box to indicate different trials
1033             if (jQuery("#include_linked_trials_checkmark").is(":checked")) {
1034                 plots
1035                     .enter()
1036                     .append("rect")
1037                     .attr("x", (d) => {
1038                         return plot_x_coord(d) * 50 + 4;
1039                     })
1040                     .attr("y", (d) => {
1041                         return plot_y_coord(d) * 50 + 54 + y_offset;
1042                     })
1043                     .attr("rx", 2)
1044                     .attr("width", 40)
1045                     .attr("height", 6)
1046                     .style("fill", (d) => {
1047                         return local_this.linked_trials[d.studyName].bg;
1048                     })
1049                     .style("opacity", (d) => {
1050                         return is_plot_overlapping(d) ? "0" : "100";
1051                     });
1052             }
1054             plots.append("text");
1055             plots
1056                 .enter()
1057                 .append("text")
1058                 .attr("x", (d) => {
1059                     return plot_x_coord(d) * 50 + 10;
1060                 })
1061                 .attr("y", (d) => {
1062                     return plot_y_coord(d) * 50 + 50 + y_offset;
1063                 })
1064                 .text((d) => {
1065                     if (
1066                         !d.observationUnitName.includes(local_this.trial_id + " filler") &&
1067                         d.type == "data" &&
1068                         !is_plot_overlapping(d)
1069                     ) {
1070                         return d.observationUnitPosition.observationLevel.levelCode;
1071                     }
1072                 })
1073                 .on("mouseover", handle_mouseover)
1074                 .on("mouseout", handle_mouseout);
1076             var image_icon = function (d) {
1077                 var image = d.plotImageDbIds || [];
1078                 var plot_image;
1079                 if (image.length > 0) {
1080                     plot_image = "/static/css/images/plot_images.png";
1081                 } else {
1082                     plot_image = "";
1083                 }
1084                 return plot_image;
1085             };
1087             plots
1088                 .enter()
1089                 .append("image")
1090                 .attr("xlink:href", image_icon)
1091                 .attr("x", (d) => {
1092                     return plot_x_coord(d) * 50 + 5;
1093                 })
1094                 .attr("y", (d) => {
1095                     return plot_y_coord(d) * 50 + 15 + y_offset;
1096                 })
1097                 .attr("width", 20)
1098                 .attr("height", 20)
1099                 .on("mouseover", handle_mouseover)
1100                 .on("mouseout", handle_mouseout);
1102             plots.exit().remove();
1104             var row_label_arr = [];
1105             var col_label_arr = [];
1106             for (let i = min_row; i <= max_row; i++) {
1107                 row_label_arr.push(i);
1108             }
1109             for (let i = min_col; i <= max_col; i++) {
1110                 col_label_arr.push(i);
1111             }
1113             var row_labels_col = 1;
1114             var col_labels_row = 0;
1115             if (!this.meta_data.invert_row_checkmark) {
1116                 col_labels_row =
1117                     this.meta_data.display_borders &&
1118                         this.meta_data.bottom_border_selection
1119                         ? num_rows + 1
1120                         : num_rows;
1121                 row_label_arr.reverse();
1122             }
1124             grid
1125                 .selectAll(".rowLabels")
1126                 .data(row_label_arr)
1127                 .enter()
1128                 .append("text")
1129                 .attr("x", row_labels_col * 50 - 25)
1130                 .attr("y", (label, i) => {
1131                     let y = this.meta_data.invert_row_checkmark ? i + 1 : i;
1132                     y =
1133                         this.meta_data.display_borders &&
1134                             this.meta_data.top_border_selection &&
1135                             this.meta_data.invert_row_checkmark
1136                             ? y + 1
1137                             : y;
1138                     return y * 50 + 45 + y_offset;
1139                 })
1140                 .text((label) => {
1141                     return label;
1142                 });
1144             grid
1145                 .selectAll(".colLabels")
1146                 .data(col_label_arr)
1147                 .enter()
1148                 .append("text")
1149                 .attr("x", (label, i) => {
1150                     let x = label - min_col + col_increment + 2;
1151                     return x * 50 - 30;
1152                 })
1153                 .attr("y", col_labels_row * 50 + 45 + y_offset)
1154                 .text((label) => {
1155                     return label;
1156                 });
1157         }
1159         load() {
1160             d3.select("svg").remove();
1161             this.change_dimensions(this.meta_data.num_cols, this.meta_data.num_rows);
1162             this.add_borders();
1163             this.render();
1164         }
1166         render() {
1167             jQuery("#working_modal").modal("hide");
1168             jQuery("#fieldmap_chart").css({ display: "inline-block" });
1169             jQuery("#container_fm").css({
1170                 display: "inline-block",
1171                 overflow: "auto",
1172             });
1173             jQuery("#trait_heatmap").css("display", "none");
1174             jQuery("#container_heatmap").css("display", "none");
1175             jQuery("#trait_heatmap").css("display", "none");
1176             this.FieldMap();
1177         }
1178     }
1180     const mapObj = new FieldMap();
1181     return mapObj;