Files
enviPy-bayer/static/js/pps.js
Tobias O 1a2c9bb543 [Feature] Modern UI roll out (#236)
This PR moves all the collection pages into the new UI in a rough push.
I did not put the same amount of care into these as into search, index, and predict.

## Major changes

- All modals are now migrated to a state based alpine.js implementation.
- jQuery is no longer present in the base layout; ajax is replace by native fetch api
- most of the pps.js is now obsolte (as I understand it; the code is not referenced any more @jebus  please double check)
- in-memory pagination for large result lists (set to 50; we can make that configurable later; performance degrades at around 1k) stukk a bit rough tracked in #235

## Minor things

- Sarch and index also use alpine now
- The loading spinner is now CSS animated (not sure if it currently gets correctly called)

## Not done

- Ihave not even cheked the admin pages. Not sure If these need migrations
- The temporary migration pages still use the old template. Not sure what is supposed to happen with those? @jebus

## What I did to test

- opend all pages in browse, and user ; plus all pages reachable from there.
- Interacted and tested the functionality of each modal superfically with exception of the API key modal (no functional test).

---
This PR is massive sorry for that; just did not want to push half-brokenn state.
@jebus @liambrydon I would be glad if you could click around and try to break it :)

Finally closes #133

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#236
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
2025-11-26 23:16:44 +13:00

851 lines
30 KiB
JavaScript

/////////////////////////////////////////
// Functions to keep after refactoring //
/////////////////////////////////////////
var syncKetcherLock = 0;
var ignoreKetcherUpdates = 0
var ignoreTextFieldUpdates = 0
// The SMILES generated by Ketcher might look different than the one inserted
// however we need this for internal comparison to check whether the SMILES taken
// from the TextInput generates the same smiles.
var SMILES = "";
// FIXME: marked for removal
// function syncTextInputToKetcher(textInputId, ketcherId) {
// var ketcher = getKetcher(ketcherId);
// var textInputSMILES = $('#' + textInputId).val();
// data = {
// "struct": textInputSMILES,
// "output_format": "chemical/x-mdl-molfile",
// "options": {
// "smart-layout": true,
// "ignore-stereochemistry-errors": true,
// "mass-skip-error-on-pseudoatoms": false,
// "gross-formula-add-rsites": true
// }
// }
// ketcher.server.layout(data).then(
// function(response) {
// if(response.struct != '') {
// console.log("Writing " + textInputSMILES + " to Ketcher");
// ketcher.setMolecule(textInputSMILES);
// // Wait for ketcher to accept the change and set global SMILES
// setTimeout(function() {
// SMILES = ketcher.getSmiles();
// }, 250);
// }
// }
// ).catch(
// function(res) {
// console.log("Promise failed" + res);
// }
// );
// }
// FIXME: marked for removal
// function syncKetcherToTextInput(ketcherId, textInputId) {
// // Obtain values
// var ketcher = getKetcher(ketcherId);
// // Get the SMILES of the molecule currently drawn in Ketcher
// var tempKetcherSMILES;
// try {
// tempKetcherSMILES = ketcher.getSmiles();
// } catch(err) {
// console.log(err);
// return;
// }
// // compare to SMILES from global scope
// if(SMILES == tempKetcherSMILES) {
// // There was no change originating from TextInput
// return
// } else {
// SMILES = tempKetcherSMILES;
// $("#" + textInputId).val(SMILES);
// }
// }
// FIXME: marked for removal
// function syncKetcherAndTextInput(origin, ketcherId, textInputId) {
// // check if function is "locked"
// if(origin == 'ketcher') {
// // Early exit - no op
// if (ignoreKetcherUpdates == 1) {
// return;
// }
// // Lock updates triggered by textInput
// ignoreTextFieldUpdates = 1;
// // Do the sync
// syncKetcherToTextInput(ketcherId, textInputId);
// ignoreTextFieldUpdates = 0;
// } else {
// // Early exit - no op
// if(ignoreTextFieldUpdates == 1) {
// return;
// }
// ignoreKetcherUpdates = 1;
// // Do the sync
// syncTextInputToKetcher(textInputId, ketcherId);
// ignoreKetcherUpdates = 0;
// }
// }
// Returns the ketcher object usually located in an iframe
function getKetcher(idToUse){
if (idToUse === undefined) {
console.log("getKetcher() called without id!");
idToUse = 'ifKetcher';
}
var ketcherFrame = document.getElementById(idToUse);
if ('contentDocument' in ketcherFrame) {
return ketcherFrame.contentWindow.ketcher;
} else { // IE7
return document.frames[idToUse].window.ketcher;
}
}
// FIXME: marked for removal
// function enterKeyPressed(event){
// // 13 means return/enter
// if (event.which == 13 || event.keyCode == 13) {
// return false;
// }
// return true
// };
// FIXME: marked for removal
// Function to attach an RSS feed available at feedUrl and attaches it to attachElement
// async function loadRSSFeed(attachElement, feedUrl) {
// try {
// const response = await fetch(feedUrl, {
// headers: {
// 'Accept': 'application/rss+xml'
// }
// });
// const text = await response.text();
// const parser = new DOMParser();
// const data = parser.parseFromString(text, 'application/xml');
// const items = data.querySelectorAll('item');
// items.forEach(item => {
// const link = item.querySelector('link')?.textContent || '';
// const title = item.querySelector('title')?.textContent || '';
// const pubDate = item.querySelector('pubDate')?.textContent || '';
// const description = item.querySelector('description')?.textContent || '';
// document.getElementById(attachElement).innerHTML +=
// '<li class="list-group-item">' +
// '<h4 class="list-group-item-heading">' +
// '<a target="blank" href="' + link + '">' +
// title +
// '</a></h4><h6>' +
// pubDate +
// '</h6><p>' +
// description +
// '</p></li>';
// });
// } catch (error) {
// console.error('Error loading RSS feed:', error);
// }
// }
// FIXME: marked for removal
// function hasValue(id) {
// var element = document.getElementById(id);
// if(element.value === ""
// || element.value === undefined
// || element.value === null
// || element.value === "undefined"){
// return false;
// }
// return true;
// }
// FIXME: marked for removal
// async function beforePredict(){
// // TODO make it a parameter
// if(!hasValue("smilesinput")) {
// return;
// }
// const smilesInput = document.getElementById('smilesinput');
// const encodedSMILES = encodeURIComponent(smilesInput.value);
// // TODO Fix via templating to set host
// const url = "/pathway?rootNode="+ encodedSMILES;
// try {
// const response = await fetch(url);
// const result = await response.json();
// // If something is found the returned dict contains the key "pathway" if not it contains "object"
// if(result.pathway){
// // TODO set as param
// attachObjectsAsList("foundPathways", result.pathway);
// } else {
// document.getElementById("index-form").submit();
// }
// } catch (error) {
// console.error('Error in beforePredict:', error);
// }
// }
// FIXME: marked for removal
// function attachObjectsAsList(elementId, objects){
// var content ='<ul class="list-group">';
// var foundone = false;
// for (var obj in objects) {
// if(objects[obj].reviewStatus == "reviewed"){
// content += '<a class="list-group-item" href="' + objects[obj].id + '">';
// content += objects[obj].name;
// content += '</a>';
// foundone = true;
// }
// }
// content += '</ul>';
// console.log(foundone);
// if(foundone){
// document.getElementById("foundPathways").innerHTML += content;
// // Show the modal using native dialog API
// const modal = document.getElementById("foundMatching");
// if (modal && modal.showModal) {
// modal.showModal();
// }
// } else {
// document.getElementById("index-form").submit();
// }
// }
// FIXME: marked for removal
// class Setting {
// constructor(nameInputId, selectedPackagesId, relativeReasoningSelectId, cutoffInputId, evaluationTypeSelectId,
// availableTSSelectId, formsId, tableId, summaryTableId) {
// this.nameInputId = nameInputId;
// this.selectedPackagesId = selectedPackagesId;
// this.relativeReasoningSelectId = relativeReasoningSelectId;
// this.cutoffInputId = cutoffInputId;
// this.evaluationTypeSelectId = evaluationTypeSelectId;
// this.availableTSSelectId = availableTSSelectId;
// this.formsId = formsId;
// this.tableId = tableId;
// this.summaryTableId = summaryTableId;
// // General settings
// this.name = null;
// this.selectedPackages = [];
// // Relative Reasoning related
// this.selectedRelativeReasoning = null;
// this.cutoff = null;
// this.evaluationType = null;
// // parameters such as { "lowPH": 7, "highPH": 8 }
// this.tsParams = {};
// }
// FIXME: marked for removal
// extractName() {
// var tempName = $('#' + this.nameInputId).val()
// if (tempName == '') {
// console.log("Name was empty...");
// return;
// }
// this.name = tempName;
// }
// extractSelectedPackages() {
// var selPacks = $("#" + this.selectedPackagesId + " :selected");
// var ref = this;
// ref.selectedPackages = [];
// selPacks.each(function () {
// var obj = {}
// obj['id'] = this.value;
// obj['name'] = this.text;
// ref.selectedPackages.push(obj);
// });
// }
// extractRelativeReasoning() {
// var tempRR = $('#' + this.relativeReasoningSelectId + " :selected").val()
// if (tempRR == '') {
// console.log("RR was empty...");
// return;
// }
// var obj = {}
// obj['id'] = $('#' + this.relativeReasoningSelectId + " :selected").val()
// obj['name'] = $('#' + this.relativeReasoningSelectId + " :selected").text()
// this.selectedRelativeReasoning = obj;
// }
// extractCutoff() {
// var tempCutoff = $('#' + this.cutoffInputId).val()
// if (tempCutoff == '') {
// console.log("Cutoff was empty...");
// return;
// }
// this.cutoff = tempCutoff;
// }
// extractEvaluationType() {
// var tempEvaluationType = $('#' + this.evaluationTypeSelectId).val()
// if (tempEvaluationType == '') {
// console.log("EvaluationType was empty...");
// return;
// }
// this.evaluationType = tempEvaluationType;
// }
// addTruncator() {
// // triggered by "Add"
// // will extract values and afterwards updates table + summary
// var type = $("#" + this.availableTSSelectId + " :selected").val();
// var text = $("#" + this.availableTSSelectId + " :selected").text();
// var form = $("#" + type + "_form > :input")
// // flag to check whether at least one input had a valid value
// var addedValue = false;
// // reference being used in each
// var ref = this;
// form.each(function() {
// if(this.value == "" || this.value === "undefined"){
// console.log(this);
// console.log("Skipping " + this.name);
// } else {
// var obj = {}
// obj[this.name] = this.value;
// obj['text'] = text;
// ref.tsParams[type] = obj
// }
// });
// this.updateTable();
// this.updateSummaryTable();
// }
// removeTruncator(rowId) {
// var summary = rowId.startsWith("sum") ? true : false;
// // plain key
// var key = rowId.replace(summary ? "sum" : "trunc", "").replace("row", "");
// console.log("Removing " + key);
// // remove the rows
// $("#trunc"+ key + "row").remove();
// if($("#sum"+ key + "row").length > 0) {
// $("#sum"+ key + "row").remove();
// }
// delete this.tsParams[key];
// }
// updateTable() {
// // remove all children
// $('#'+this.tableId + "Body").empty()
// var innerHTML = "<tr>" +
// "<td>Name</td>" +
// "<td>Value</td>" +
// "<td width='10%'>Action</td>" +
// "</tr>";
// for (var x in this.tsParams) {
// var val = "";
// for (var y in this.tsParams[x]){
// if (y == 'text') {
// continue;
// }
// val += this.tsParams[x][y]
// }
// innerHTML += "<tr id='trunc" + x + "row'>" +
// "<td>" + this.tsParams[x]['text'] + "</td>" +
// "<td>" + val + "</td>" +
// "<td width='10%'>"+
// "<button type='button' id='" + x + "button' class='form-control' onclick='s.removeTruncator(\"trunc" + x + "row\")'>Remove</button>" +
// "</td>" +
// "</tr>";
// }
// $('#'+this.tableId + "Body").append(innerHTML);
// }
// packageRows() {
// var res = '';
// for(var p in this.selectedPackages) {
// var obj = this.selectedPackages[p];
// res += "<tr>" +
// "<td>Package</td>" +
// "<td>" + obj['name'] + "</td>" +
// "<td width='10%'>" +
// // "<button type='button' id='relativereasoningbutton' class='form-control' onclick='s.removeTruncator(\"Relative Reasoning\")'>Remove</button>" +
// "</td>" +
// "</tr>";
// }
// return res;
// }
// modelRow() {
// if(this.selectedRelativeReasoning == null) {
// return '';
// }
// return "<tr>" +
// "<td>Relative Reasoning</td>" +
// "<td>" + this.selectedRelativeReasoning['name'] + " " + (this.evaluationType == "singleGen" ? "SG" : "MG") + " with t=" + this.cutoff + " </td>" +
// "<td width='10%'>" +
// // "<button type='button' id='relativereasoningbutton' class='form-control' onclick='s.removeTruncator(\"Relative Reasoning\")'>Remove</button>" +
// "</td>" +
// "</tr>";
// }
// updateSummaryTable() {
// // remove all children
// $('#'+this.summaryTableId + "Body").empty()
// var innerHTML = "<tr>" +
// "<td>Name</td>" +
// "<td>Value</td>" +
// "<td width='10%'>Action</td>" +
// "</tr>";
// innerHTML += this.packageRows();
// innerHTML += this.modelRow();
// // var innerHTML = ''
// for (var x in this.tsParams) {
// var val = "";
// for (var y in this.tsParams[x]){
// if (y == 'text') {
// continue;
// }
// val += this.tsParams[x][y]
// }
// innerHTML += "<tr id='sum" + x + "row'>" +
// "<td>" + this.tsParams[x]['text'] + "</td>" +
// "<td>" + val + "</td>" +
// "<td width='10%'>"+
// "<button type='button' id='" + x + "button' class='form-control' onclick='s.removeTruncator(\"sum" + x + "row\")'>Remove</button>" +
// "</td>" +
// "</tr>";
// }
// $('#'+this.summaryTableId + "Body").append(innerHTML);
// }
// }
// TODO
function queryResultToTable(appendId, result) {
const container = document.getElementById(appendId);
container.innerHTML = '';
var table = '<table class="table">' +
'<thead class="thead-dark">' +
'<tr>';
var header = '';
for (var r in result['result']) {
var keys = Object.keys(result['result'][r]);
for (var k in keys) {
header += '<th>' + keys[k] + '</th>';
}
break;
}
table += header;
table += '</tr>';
table += '</thead>';
table += '<tbody>';
var body = '';
for (var r in result['result']) {
body += '<tr>';
for (var k in result['result'][r]) {
body += '<td>' + result['result'][r][k] + '</td>';
}
body += '</tr>';
}
table += body;
table += '</tbody>';
table += '</table>';
container.innerHTML = table;
}
function showLoadingSpinner(attachOb) {
// Benzene ring spinner - hexagon with rotating double bonds
const spinnerHtml = `<div class="loading-spinner">
<svg viewBox="0 0 58 66">
<!-- Outer hexagon (static) -->
<path class="hexagon" d="M1 49v-32l28-16 28 16v32l-28 16z" fill="none" stroke="currentColor"/>
<!-- Inner double bonds (rotating) -->
<g class="double-bonds">
<path d="M52.7 19.5 L29 6" fill="none" stroke="currentColor"/>
<path d="M29 60 L52.7 46.5" fill="none" stroke="currentColor"/>
<path d="M5.5 19.5 v27" fill="none" stroke="currentColor"/>
</g>
</svg>
</div>`;
// Handle both ID selectors and DOM elements
let element;
if (typeof attachOb === 'string') {
element = attachOb.startsWith('#')
? document.querySelector(attachOb)
: document.getElementById(attachOb);
} else {
element = attachOb;
}
if (element) {
element.innerHTML = spinnerHtml;
}
}
// Keep old function name for backwards compatibility
function makeLoadingGif(attachOb) {
showLoadingSpinner(attachOb);
}
// FIXME: marked for removal
// function cleanIdName(idName) {
// return idName.replace(/[ \-()]/g, "")
// }
// FIXME: marked for removal
// function makeAccordionHead(accordionId, accordionTitle, reviewStatus) {
// return "<div class='panel-group' id='" + accordionId + "'>"
// + "<div class='panel panel-default'>"
// + "<div class='panel-heading' id='headingPanel' style='font-size:2rem;height: 46px'>"
// + accordionTitle + reviewStatus
// + "</div><div id='descDiv'></div>"
// + "</div>";
// }
// FIXME: marked for removal
// function makeAccordionPanel(accordionId, panelName, panelContent, collapsed, id) {
// if(panelContent == "" || panelContent == "no description") {
// return "";
// }
// if(collapsed == true) {
// collapsed = "in";
// } else {
// collapsed = "";
// }
// var panelId = typeof id === "undefined" ? cleanIdName(panelName) : id;
// return "<div class='panel panel-default panel-heading list-group-item' style='background-color:silver'>"
// + "<h4 class='panel-title'>"
// + "<a id=" + panelId + "Link" + " data-toggle='collapse' data-parent='#" + accordionId + "' href='#" + panelId + "'>"
// + panelName
// + "</a>"
// + "</h4>"
// + "</div>"
// + "<div id='" + panelId + "' class='panel-collapse collapse " + collapsed + "'>"
// + "<div class='panel-body list-group-item' id='"+panelId+"Content'>"
// + panelContent
// + "</div>"
// + "</div>";
// }
// FIXME: marked for removal
// async function fillPRCurve(modelUri, onclick){
// if (modelUri == '') {
// return;
// }
// // clear div
// const plotDiv = document.getElementById('plotDiv');
// plotDiv.innerHTML = '';
// // for the time being add animation
// makeLoadingGif('#plotDiv');
// try {
// const response = await fetch(modelUri + "?prcurve");
// const result = await response.json();
// // remove loading gif
// plotDiv.innerHTML = '';
// // check if model is in a proper state
// if(result.hasOwnProperty('error')){
// var content = '<div>'+result.error+' \nYou can check the model <a href="' + modelUri + '">here</a>. </div>';
// plotDiv.innerHTML += content;
// return
// }
// var chartPanel = '<div id="chart" class="panel-group"></div>';
// plotDiv.innerHTML += chartPanel;
// // Labels
// var x = ['Recall'];
// var y = ['Precision'];
// var thres = ['threshold'];
// // Compare function for the given array
// function compare(a, b) {
// if (a.threshold < b.threshold)
// return -1;
// else if (a.threshold > b.threshold)
// return 1;
// else
// return 0;
// }
// // gets the index for a certain value
// function getIndexForValue(data, val, val_name) {
// for(var idx in data) {
// if(data[idx][val_name] == val) {
// return idx;
// }
// }
// return -1;
// }
// var data = result.prdata;
// var dataLength = Object.keys(data).length;
// data.sort(compare);
// // collect data in 3 individual arrays
// for (var idx in data) {
// var d = data[idx];
// x.push(d.recall);
// y.push(d.precision);
// thres.push(d.threshold);
// }
// // generate the actual chart with values collected above
// var chart = c3.generate({
// bindto: '#chart',
// data: {
// onclick: function (d, e) {
// var idx = d.index;
// var thresh = data[dataLength-idx-1].threshold;
// onclick(thresh)
// },
// x: 'Recall',
// y: 'Precision',
// columns: [
// x,
// y,
// //thres
// ]
// },
// size: {
// height: 400, // TODO: Make variable to current modal width
// width: 480
// },
// axis: {
// x: {
// max: 1,
// min: 0,
// label: 'Recall',
// padding: 0,
// tick: {
// fit: true,
// values: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
// }
// },
// y: {
// max: 1,
// min: 0,
// label: 'Precision',
// padding: 0,
// tick: {
// fit: true,
// values: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
// }
// }
// },
// point: {
// r: 4
// },
// tooltip: {
// format: {
// title: function (recall) {
// idx = getIndexForValue(data, recall, "recall");
// if(idx != -1) {
// return "Threshold: " + data[idx].threshold;
// }
// return "";
// },
// value: function (precision, ratio, id) {
// return undefined;
// }
// }
// },
// zoom: {
// enabled: true
// }
// });
// } catch (error) {
// console.error('Error loading PR curve:', error);
// const plotDiv = document.getElementById('plotDiv');
// plotDiv.innerHTML = '<div>Error loading PR curve data.</div>';
// }
// }
function handleAssessmentResponse(depict_url, data) {
var inside_app_domain = "<a class='list-group-item'>This compound is " + (data["assessment"]["inside_app_domain"] ? "inside" : "outside") + " the Applicability Domain derived from the chemical (PCA) space constructed using the training data." + "</a>";
var functionalGroupsImgSrc = null;
var reactivityCentersImgSrc = null;
if (data['assessment']['node'] !== undefined) {
functionalGroupsImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "&highlight=true'>";
reactivityCentersImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "&highlightReactivity=true'>"
} else {
functionalGroupsImgSrc = "<img width='400' src=\"" + depict_url + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "\">";
reactivityCentersImgSrc = "<img width='400' src=\"" + depict_url + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "\">"
}
tpl = `<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="app-domain-assessment-functional-groups-link" data-toggle="collapse" data-parent="#app-domain-assessment" href="#app-domain-assessment-functional-groups">Functional Groups Covered by Model</a>
</h4>
</div>
<div id="app-domain-assessment-functional-groups" class="panel-collapse collapse">
<div class="panel-body list-group-item">
${inside_app_domain}
<p></p>
<div id="image-div" align="center">
${functionalGroupsImgSrc}
</div>
</div>
</div>
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="app-domain-assessment-reactivity-centers-link" data-toggle="collapse" data-parent="#app-domain-assessment" href="#app-domain-assessment-reactivity-centers">Reactivity Centers</a>
</h4>
</div>
<div id="app-domain-assessment-reactivity-centers" class="panel-collapse collapse">
<div class="panel-body list-group-item">
<div id="image-div" align="center">
${reactivityCentersImgSrc}
</div>
</div>
</div>`
var transformations = '';
for (t in data['assessment']['transformations']) {
transObj = data['assessment']['transformations'][t];
var neighbors = '';
for (n in transObj['neighbors']) {
neighObj = transObj['neighbors'][n];
var neighImg = "<img width='100%' src='" + transObj['rule']['url'] + "?smiles=" + encodeURIComponent(neighObj['smiles']) + "'>";
var objLink = `<a class='list-group-item' href="${neighObj['url']}">${neighObj['name']}</a>`
var neighPredProb = "<a class='list-group-item'>Predicted probability: " + neighObj['probability'].toFixed(2) + "</a>";
var pwLinks = '';
for (pw in neighObj['related_pathways']) {
var pwObj = neighObj['related_pathways'][pw];
pwLinks += "<a class='list-group-item' href=" + pwObj['url'] + ">" + pwObj['name'] + "</a>";
}
var expPathways = `
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="transformation-${t}-neighbor-${n}-exp-pathway-link" data-toggle="collapse" data-parent="#transformation-${t}-neighbor-${n}" href="#transformation-${t}-neighbor-${n}-exp-pathway">Experimental Pathways</a>
</h4>
</div>
<div id="transformation-${t}-neighbor-${n}-exp-pathway" class="panel-collapse collapse">
<div class="panel-body list-group-item">
${pwLinks}
</div>
</div>
`
if (pwLinks === '') {
expPathways = ''
}
neighbors += `
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="transformation-${t}-neighbor-${n}-link" data-toggle="collapse" data-parent="#transformation-${t}" href="#transformation-${t}-neighbor-${n}">Analog Transformation on ${neighObj['name']}</a>
</h4>
</div>
<div id="transformation-${t}-neighbor-${n}" class="panel-collapse collapse">
<div class="panel-body list-group-item">
${objLink}
${neighPredProb}
${expPathways}
<p></p>
<div id="image-div" align="center">
${neighImg}
</div>
</div>
</div>
`
}
var panelName = null;
var objLink = null;
if (transObj['is_predicted']) {
panelName = `Predicted Transformation by ${transObj['rule']['name']}`;
for (e in transObj['edges']) {
objLink = `<a class='list-group-item' href="${transObj['edges'][e]['url']}">${transObj['edges'][e]['name']}</a>`
break;
}
} else {
panelName = `Potential Transformation by applying ${transObj['rule']['name']}`;
objLink = `<a class='list-group-item' href="${transObj['rule']['url']}">${transObj['rule']['name']}</a>`
}
var predProb = "<a class='list-group-item'>Predicted probability: " + transObj['probability'].toFixed(2) + "</a>";
var timesTriggered = "<a class='list-group-item'>This rule has triggered " + transObj['times_triggered'] + " times in the training set</a>";
var reliability = "<a class='list-group-item'>Reliability: " + transObj['reliability'].toFixed(2) + " (" + (transObj['reliability'] > data['ad_params']['reliability_threshold'] ? "&gt" : "&lt") + " Reliability Threshold of " + data['ad_params']['reliability_threshold'] + ") </a>";
var localCompatibility = "<a class='list-group-item'>Local Compatibility: " + transObj['local_compatibility'].toFixed(2) + " (" + (transObj['local_compatibility'] > data['ad_params']['local_compatibility_threshold'] ? "&gt" : "&lt") + " Local Compatibility Threshold of " + data['ad_params']['local_compatibility_threshold'] + ")</a>";
var transImg = "<img width='100%' src='" + transObj['rule']['url'] + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "'>";
var transformation = `
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="transformation-${t}-link" data-toggle="collapse" data-parent="#transformation-${t}" href="#transformation-${t}">${panelName}</a>
</h4>
</div>
<div id="transformation-${t}" class="panel-collapse collapse">
<div class="panel-body list-group-item">
${objLink}
${predProb}
${timesTriggered}
${reliability}
${localCompatibility}
<p></p>
<div id="image-div" align="center">
${transImg}
</div>
<p></p>
${neighbors}
</div>
</div>
`
transformations += transformation;
}
res = tpl + transformations;
document.getElementById('appDomainAssessmentResultTable').innerHTML += res;
}