-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #186 from CODEX-CELIDA/feature/execution-graph-plot
Feature/execution graph plot
- Loading branch information
Showing
19 changed files
with
536 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import os | ||
|
||
from dotenv import load_dotenv | ||
from fastapi import FastAPI, Request | ||
from fastapi.responses import HTMLResponse | ||
from fastapi.staticfiles import StaticFiles | ||
from fastapi.templating import Jinja2Templates | ||
|
||
load_dotenv() | ||
|
||
app = FastAPI() | ||
|
||
templates = Jinja2Templates(directory="templates") | ||
app.mount("/static", StaticFiles(directory="static"), name="static") | ||
|
||
EE_API_URL = os.getenv("EE_API_URL") | ||
|
||
|
||
@app.get("/", response_class=HTMLResponse) | ||
async def get_index(request: Request) -> HTMLResponse: | ||
""" | ||
Render the index page. | ||
""" | ||
return templates.TemplateResponse( | ||
"index.html", {"request": request, "ee_api_url": EE_API_URL} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
fastapi[all]==0.105.0 | ||
uvicorn==0.20.0 | ||
python-dotenv==0.21.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
// script.js | ||
|
||
async function loadRecommendations() { | ||
const response = await fetch(`${eeApiUrl}/recommendation/list`); | ||
const recommendations = await response.json(); | ||
const recommendationList = document.getElementById('recommendation-list'); | ||
recommendationList.innerHTML = ''; | ||
recommendations.forEach(rec => { | ||
const div = document.createElement('div'); | ||
div.className = 'recommendation-item'; | ||
div.innerHTML = ` | ||
<div class="recommendation-title">${rec.recommendation_id}: ${rec.recommendation_name}</div> | ||
<div class="recommendation-detail">Version: ${rec.recommendation_version}</div> | ||
<div class="recommendation-detail">Package Version: ${rec.recommendation_package_version}</div> | ||
`; | ||
div.onclick = () => loadGraph(rec.recommendation_id); | ||
recommendationList.appendChild(div); | ||
}); | ||
} | ||
|
||
async function loadGraph(recommendationId) { | ||
const response = await fetch(`${eeApiUrl}/recommendation/${recommendationId}/execution_graph`); | ||
const data = await response.json(); | ||
const graphData = data.recommendation_execution_graph; | ||
|
||
// Extract unique node types | ||
const nodeTypes = [...new Set(graphData.nodes.map(node => node.data.type))]; | ||
const nodeCategories = [...new Set(graphData.nodes.map(node => node.data.category))]; | ||
|
||
// Generate colors for each type | ||
const nodeColors = { | ||
"BASE": "#ff0000", | ||
"POPULATION": "#00ff00", | ||
"INTERVENTION": "#9999ff", | ||
"POPULATION_INTERVENTION": "#ff00ff", | ||
}; | ||
const nodeShapes = { | ||
"Symbol": "round-rectangle", | ||
"&": "rhomboid", | ||
"|": "diamond", | ||
"Not": "triangle", | ||
"NoDataPreservingAnd": "rhomboid", | ||
"NoDataPreservingOr": "diamond", | ||
"NonSimplifiableAnd": "rhomboid", | ||
"NonSimplifiableOr": "diamond", | ||
"LeftDependentToggle": "octagon", | ||
} | ||
|
||
// Initialize Cytoscape | ||
var cy = cytoscape({ | ||
container: document.getElementById('cy'), | ||
elements: [...graphData.nodes, ...graphData.edges], | ||
style: [ | ||
{ | ||
selector: 'node', | ||
style: { | ||
'label': function(ele) { | ||
if (ele.data('type') === 'Symbol') { | ||
if (ele.data('category') == 'BASE') { | ||
return ele.data('class') | ||
} | ||
var label; | ||
label = ele.data('concept')["concept_name"]; | ||
var value = ele.data('value'); | ||
var dosage = ele.data('dosage'); | ||
var timing = ele.data('timing'); | ||
var route = ele.data('route'); | ||
|
||
if (value) { | ||
label += " " + value; | ||
} | ||
if (dosage) { | ||
label += "\n" + dosage; | ||
} | ||
if (timing) { | ||
label += "\n" + timing; | ||
} | ||
if (route) { | ||
label += "\n[" + route + "]"; | ||
} | ||
return label; | ||
|
||
} | ||
if (ele.data("is_sink")) { | ||
return ele.data('category') + " [SINK]" | ||
} | ||
return ele.data('class') | ||
}, | ||
'background-color': function(ele) { | ||
return nodeColors[ele.data('category')] || '#666'; // Assign color based on 'type', with a default | ||
}, | ||
'shape': function(ele) { | ||
return nodeShapes[ele.data('type')] || 'star'; // Assign color based on 'type', with a default | ||
}, | ||
'text-valign': 'center', | ||
'color': '#000000', | ||
'width': function(ele) { | ||
return ele.data('type') === 'Symbol' ? '120px': '40px'; | ||
}, | ||
'height': function(ele) { | ||
return ele.data('type') === 'Symbol' ? '80px': '40px'; | ||
}, | ||
'font-size': '10px', | ||
'text-wrap': 'wrap', | ||
'text-max-width': '120px' // Adjust width as needed | ||
} | ||
}, | ||
{ | ||
selector: 'edge', | ||
style: { | ||
'width': 2, | ||
'target-arrow-shape': 'triangle', // Set arrow shape to triangle | ||
'curve-style': 'bezier' // Makes the edge curved for better visibility of direction | ||
} | ||
} | ||
], | ||
layout: { | ||
name: 'klay', // Use 'klay' layout for better visualization | ||
nodeDimensionsIncludeLabels: true, | ||
fit: true, | ||
padding: 20, | ||
animate: true, | ||
animationDuration: 500, | ||
klay: { | ||
spacing: 20, | ||
direction: 'DOWN', | ||
} | ||
} | ||
}); | ||
|
||
// Add event listener for node click | ||
cy.on('tap', 'node', function(evt) { | ||
hideTippys(cy); | ||
const node = evt.target; | ||
if (!node.tippy) { | ||
node.tippy = createTippy(node); | ||
} | ||
node.tippy.show(); | ||
}); | ||
|
||
// Hide popper when clicking on the canvas | ||
cy.on('tap', function(evt) { | ||
if (evt.target === cy) { | ||
hideTippys(cy); | ||
} | ||
}); | ||
} | ||
|
||
function createTippy(node) { | ||
let content = ''; | ||
|
||
function formatData(data, prefix = '') { | ||
for (let key in data) { | ||
if (data.hasOwnProperty(key)) { | ||
if (Array.isArray(data[key])) { | ||
content += `<strong>${prefix}${key}:</strong><br>`; | ||
data[key].forEach((item, index) => { | ||
content += `<strong>${prefix}${key}[${index}]:</strong><br>`; | ||
formatData(item, prefix + ' '); | ||
}); | ||
} else if (typeof data[key] === 'object' && data[key] !== null) { | ||
content += `<strong>${prefix}${key}:</strong><br>`; | ||
formatData(data[key], prefix + ' '); | ||
} else { | ||
content += `<strong>${prefix}${key}:</strong> ${data[key]}<br>`; | ||
} | ||
} | ||
} | ||
} | ||
|
||
formatData(node.data()); | ||
|
||
let ref = node.popperRef(); // used only for positioning | ||
let dummyDomEle = document.createElement('div'); | ||
document.body.appendChild(dummyDomEle); // Ensure dummyDomEle has a parent | ||
|
||
return tippy(dummyDomEle, { | ||
content: () => { | ||
let div = document.createElement('div'); | ||
div.innerHTML = content; | ||
return div; | ||
}, | ||
placement: 'top', | ||
hideOnClick: true, | ||
interactive: true, | ||
trigger: 'manual', | ||
allowHTML: true, | ||
getReferenceClientRect: () => ref.getBoundingClientRect() | ||
}); | ||
} | ||
|
||
function hideTippys(cy) { | ||
cy.elements().forEach(ele => { | ||
if (ele.tippy) { | ||
ele.tippy.hide(); | ||
} | ||
}); | ||
} | ||
|
||
loadRecommendations(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<title>CELIDA Execution Graphs</title> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.21.0/cytoscape.min.js"></script> | ||
<script src="https://unpkg.com/[email protected]/klay.js"></script> | ||
<script src="https://unpkg.com/[email protected]/dist/dagre.js"></script> | ||
<script src="https://unpkg.com/[email protected]/cytoscape-dagre.js"></script> | ||
<script src="https://unpkg.com/[email protected]/cytoscape-klay.js"></script> | ||
<script src="https://unpkg.com/@popperjs/[email protected]/dist/umd/popper.min.js"></script> | ||
<script src="https://unpkg.com/[email protected]/dist/tippy-bundle.umd.min.js"></script> | ||
<!-- cy libs --> | ||
<script src="https://unpkg.com/[email protected]/cytoscape-popper.js"></script> | ||
<link rel="stylesheet" href="https://unpkg.com/tippy.js@6/dist/tippy.css" /> | ||
<style> | ||
body { | ||
display: flex; | ||
height: 100vh; | ||
margin: 0; | ||
font-family: Arial, sans-serif; | ||
} | ||
#recommendation-list { | ||
width: 250px; /* Adjust width as necessary */ | ||
overflow-y: auto; | ||
border-right: 1px solid #ccc; | ||
padding: 10px; | ||
} | ||
.recommendation-item { | ||
cursor: pointer; | ||
padding: 10px; | ||
border-bottom: 1px solid #ccc; | ||
display: flex; | ||
flex-direction: column; | ||
} | ||
.recommendation-item:hover { | ||
background-color: #f0f0f0; | ||
} | ||
#cy { | ||
flex-grow: 1; | ||
display: block; | ||
} | ||
.recommendation-title { | ||
font-weight: bold; | ||
} | ||
.recommendation-detail { | ||
font-size: 0.9em; | ||
color: #555; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div id="recommendation-list"></div> | ||
<div id="cy"></div> | ||
<div class="popover" id="popover"></div> | ||
<script> | ||
const eeApiUrl = "{{ ee_api_url }}"; | ||
</script> | ||
<script src="/static/script.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.