diff --git a/site/css/style.css b/site/css/style.css index dba0412f0aaf9d803f67db8e611af9adb2ae5b02..552724cadacdd25b04b4bb91433b233e7deb0329 100644 --- a/site/css/style.css +++ b/site/css/style.css @@ -17,5 +17,4 @@ } */ .vizbody{ max-height: 50vh; - overflow-y: scroll; } diff --git a/site/graph.html b/site/graph.html index 6002e904ddae7e8821ffc52ae6bf0e30f43aeded..08f331daba90135674879efcfea4548704169b90 100644 --- a/site/graph.html +++ b/site/graph.html @@ -46,7 +46,7 @@ </form> <div class="btn-group" style="max-height: 3em"> - <button id="metadata-manager-button" class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#metadatamanagerholder" style="max-height:3em;" type="button">Metadata</button> + <button id="metadata_manager_button" class="btn btn-primary" style="max-height:3em;" type="button">Metadata</button> <button id="filter_manager_button" class="btn btn-primary" style="max-height:3em;" type="button">Filters</button> <button id="export_button" class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#exportmanagerholder" style="max-height:3em;" type="button">Export</button> </div> @@ -59,8 +59,6 @@ <button id="add_filter_button" type="button" class="col-1 btn btn-primary">Add</button> </div> </div> - <div id="metadatamanagerholder" class="col collapse"> - </div> <div id="exportmanagerholder" class="col collapse"> <form id="export_form" class="form row"> <div class="row container-fluid"> @@ -84,7 +82,10 @@ </div> </div> + + </nav> + <main class="container-fluid py-2 mx-0 px-0"> <div class="row w-100 mx-0 px-1 gx-3" id="main"></div> </main> diff --git a/site/js/application.js b/site/js/application.js index 46e1e71ffb8bdd3a4cd7232c49cd5221c5a26bab..3f7e8ee1e8d39699776a16a6fff64e94e0625332 100644 --- a/site/js/application.js +++ b/site/js/application.js @@ -246,9 +246,15 @@ export class Graph{ _node_satisfy(ast, nodeid){ let that = this; let keys = Object.keys(ast); - if (keys.length > 1) - throw new Error("AST should have at most one key"); - let key = keys[0].toLowerCase(); + let key; + let name = undefined; + if (keys.length > 1){ + if (ast.name == undefined || ast.type == undefined) + throw new Error("AST should have at most one key"); + name = ast.name.toLowerCase(); + key = ast.type.toLowerCase(); + } else + key = keys[0].toLowerCase(); if (key == "land" ) return ast.land.map((e) => that._node_satisfy(e, nodeid)).every(e=>e); if (key == "lor") @@ -261,16 +267,17 @@ export class Graph{ return true; if (key == "selection") return this.#selection.has(nodeid); + let data = this.#nodes.get(nodeid); - if (data == PARTIALLY) - return key == "partial"; - if (key == "degree") return Object.keys(data.neighbors).length == ast[key]; - - for (const metadata of data.metadatas) - if (metadata.type.toLowerCase() == key && metadata.id.toLowerCase() == ast[key].toLowerCase()) + if (key == "partial") + return data == PARTIALLY; + if (name == undefined) + name = ast[key].toLowerCase() + for (const metadata of this.#nodes.get(nodeid).metadatas) + if (metadata.type.toLowerCase() == key && metadata.id.toLowerCase() == name) return true; return false; } diff --git a/site/js/metadata.js b/site/js/metadata.js new file mode 100644 index 0000000000000000000000000000000000000000..205a89f6a088a8ac79073105ca28e5587019c9c3 --- /dev/null +++ b/site/js/metadata.js @@ -0,0 +1,129 @@ +import { BaseViz } from "./visualisation.js"; +import {autoResizeQueryField} from "./ui.js"; +let debounceTimer; +let meta_id=0; +export class MetaViz extends BaseViz{ + + constructor(graph){ + super(); + this.graph = graph; + this.container = main; + this.id = meta_id; + meta_id += 1; + } + + + build(){ + let G = this.graph + queryField.addEventListener("input", function() { + autoResizeQueryField(); + }); + + let holder = document.createElement("div"); + holder.classList.add("col-5", "card", "overflow-hidden", "p-0", "m-1"); + holder.style = "height: 45vh; resize: both;"; + holder.innerHTML = ` +<div class="vizhead card-header p-1"> + <div class="p-0 vizmenu_main"> + <div class="container-fluid d-flex "> + <span> + <h5>Metadata</h5> + </span> + <span class="header-buttons ms-auto d-flex"> + <button class="viz-close btn-close my-auto" aria-label="Close"></button> + </span> + </div> + <div class="vizbody"> + <input type="text" id="metadata_field_${this.id}" class="form-control" placeholder="Filter metadata here"> + <div class="form-floating"> + <select name="metadata" id="metadata_selector_${this.id}" class="form-select metadata_selector"> + <option value="">--Please choose a metadata type to explore--</option> + </select> + <label for="metadata_selector" class="form-label">Select a metadata type:</label> + </div> + </div> + <div class="vizfooter mt-3"></div> +</div> + `; + this.holder = holder; + let that = this; + holder.querySelector('.viz-close').addEventListener("click", function(){ + G.delete_onready_callback(that.draw_callback); + holder.remove(); + }); + // closing the viz + //holder.querySelector('button[class="btn-close"]').addEventListener("click", function(){ + // that.graph.delete_onready_callback(that.draw_callback); + // holder.remove(); + // holder.remove(); + //}); + + + this.container.appendChild(holder); + + let buttons_holder = document.createElement("div"); + buttons_holder.classList.add("p-2", "overflow-y-auto"); + holder.appendChild(buttons_holder); + + G.metadata_types_list.forEach(function(type){ + const option = document.createElement('option'); + const metadataList = G.metadata_vars_values[type]; + option.value = type; + option.innerHTML = type; + + option.addEventListener("click", function() { + buttons_holder.innerHTML = ""; + build_metadata_buttons(buttons_holder, metadataList) + + }); + document.querySelector(".metadata_selector").appendChild(option); + }); + + document.getElementById(`metadata_field_${this.id}`).addEventListener('input', function() { + clearTimeout(debounceTimer); + + const filterText = this.value; // Get the current value of the textarea + // Rebuild the buttons with the filter applied + let type = document.querySelector(".metadata_selector").value; + const metadataList = G.metadata_vars_values[type]; + debounceTimer = setTimeout(function() { + // Rebuild the buttons with the filter applied + build_metadata_buttons(buttons_holder, metadataList, filterText); + }, 200); // 1000 milliseconds = 1 second + }); + } +} + + +// function build_metadata_buttons(container, metadataList) { +// for (const [key, element ] of Object.entries(metadataList)) { +// let button = document.createElement("button"); +// button.classList.add("btn", "btn-primary"); +// button.innerHTML = key; +// button.addEventListener("click", function(){ +// queryField.value += element.type + '(' + key + ') '; +// autoResizeQueryField(); +// }) +// container.appendChild(button); +// } +// } + +function build_metadata_buttons(container, metadataList, filterText = "") { + // Clear the container before adding buttons + container.innerHTML = ""; + + // Loop through the metadataList and create buttons for keys that match the filterText + for (const [key, element] of Object.entries(metadataList)) { + // Check if the key includes the filter text (case-insensitive) + if (key.toLowerCase().includes(filterText.toLowerCase())) { + let button = document.createElement("button"); + button.classList.add("btn", "btn-outline-primary", "btn-sm", "m-1"); + button.innerHTML = key; + button.addEventListener("click", function() { + queryField.value += element.type + `(${key})`; + autoResizeQueryField(); + }); + container.appendChild(button); + } + } +} \ No newline at end of file diff --git a/site/js/ui.js b/site/js/ui.js index 9f88b6637a616e2bab08508db6727965d9857a84..8d587d1ec44ab68835d6f4bf5cd3a93ea0ffdbe9 100644 --- a/site/js/ui.js +++ b/site/js/ui.js @@ -1,3 +1,5 @@ +import { MetaViz } from "./metadata.js"; + export function set_page(G){ for (const el of document.querySelectorAll("[gname]")){ el.innerHTML += G.gname; @@ -48,12 +50,8 @@ export function set_page(G){ link.remove(); }) }); - const metadataselector = document.getElementById("metadata-manager-button"); - metadataselector.addEventListener("click", function(event) { - console.log("Adding metadata event listener"); - event.preventDefault(); - add_viz(G, metadatamanagerholder, "metadata"); - }) + const metadataselector = document.getElementById("metadata_manager_button"); + metadataselector.onclick = () => (new MetaViz(G)).build(); add_filter_button.addEventListener("click", function() { G.add_filter_str(filterName.value, queryField.value.trim()); diff --git a/site/js/visualisation.js b/site/js/visualisation.js index b69a7fa200f611e3689e6565532ca8bae95f6193..4f4d7016417237076509f45ca3027361dbe49b0a 100644 --- a/site/js/visualisation.js +++ b/site/js/visualisation.js @@ -68,7 +68,7 @@ export class BaseViz { Show actions </button> <button class="viz-close btn-close my-auto" aria-label="Close"></button> - <span> + </span> </div> <div id="vizmenu_${this.viz_id}_actions" class="vizmenu setup_action_list container-fluid collapse border-top"> <form class="row py-1 list_action form"> diff --git a/site/js/viz/metadata.js b/site/js/viz/metadata.js deleted file mode 100644 index deb713d0c6dd8567820f43aae099d3940c70a9c5..0000000000000000000000000000000000000000 --- a/site/js/viz/metadata.js +++ /dev/null @@ -1,178 +0,0 @@ -import { BaseViz } from "../visualisation.js"; -import { modal } from "../ui.js"; -import {autoResizeQueryField} from "../ui.js"; - -export class MetaViz extends BaseViz{ - - get vizname(){ - return "Metadata"; - } - - get vizlocation(){ - return "." - } - - draw(){ - this.holder.classList.add("text-white"); - this.body.innerHTML = ` -<div class="card text-white" > - <label for="metadata_selector">Select a metadata type:</label> - - <select name="metadata" id="metadata_selector"> - <option value="">--Please choose a metadata type to explore--</option> - </select> - <div id = "metadata_table_container"> - <table id="metadata_table"> - <thead> - <tr> - <th>Metadata</th> - </tr> - </thead> - <tbody> - <!-- --> - </tbody> - </table> - </div> -</div>`; - - const metadataSelector = this.body.querySelector('#metadata_selector'); - const metadata_table = document.querySelector("#metadata_table tbody"); - metadata_table.innerHTML = ''; // Clear existing data - let G = this.graph; - G.metadata_types_list.forEach(function(type){ - const option = document.createElement('option'); - const metadataList = G.metadata_vars_values[type]; - option.value = type; - option.innerHTML = type; - // option.addEventListener('click',function(){ - // for (const [key, element ] of Object.entries(metadataList)) { - // const tr = document.createElement('tr'); - // const td = document.createElement('td'); - // td.textContent = key; - // td.value = element; - // tr.appendChild(td); - // }; - // }); - option.addEventListener("click", function() { - let container = document.getElementById("metadatamanagerholder"); - try { - document.getElementById("metadataButtonHolder").remove(); - } - catch (error) { - ; - } - - let holder = document.createElement("div"); - holder.id = "metadataButtonHolder" - holder.style = "max-height: 208px;"; - holder.classList.add("overflow-auto", "d-flex", "align-content-start", "flex-wrap", "mb-3", "col-12", "col-md-6","white-text", "m-2", "border"); - - const metadataValues = G.metadata_vars - for (const [key, element ] of Object.entries(metadataList)) { - let button_container = document.createElement("div"); - button_container.classList.add("w-25"); - let button = document.createElement("button"); - button.classList.add("p-2", "btn", "btn-primary", "w-100"); - button.innerHTML = key; - button.addEventListener("click", function(){ - queryField.value += element.type + '(' + key + ') '; - autoResizeQueryField(); - }) - button_container.appendChild(button); - holder.appendChild(button_container); - } - container.appendChild(holder); - }); - metadataSelector.appendChild(option); - }); - - queryField.addEventListener("input", function() { - autoResizeQueryField(); - }); - } - - - - build(){ - let holder = document.createElement("div"); - holder.classList.add("col-12", "col-md-5", "vizholder", "m-2", "border"); - holder.innerHTML = ` -<div class="vizhead"> - <h2> - ${this.vizname} - <button class="btn btn-danger"> Close </button> - </h2> - <div class="viz_menu"></div> - <div> - <form class="add_action"> - <label> Apply a filter : </label> - <select class="filter_list" name="filter"> - </select> - <label> action </label> - <select class="action_list"> - </select> - <span class="action_params"></span> - <button type="submit"> apply </button> - </form> - </div> - <div class="filter_list"> - <label> List of actions</label> - <ul></ul> - </div> -</div> -<div class="vizbody"></div> -<div class="vizfooter"></div> -`; - this.container.appendChild(holder); - this.holder = holder; - let that = this; - // build the filter list - this.update_filter(); - - // build the action list - let action_list = holder.querySelector(".action_list"); - for (const act of this._actions.values()){ - let opt = document.createElement("option"); - opt.value = opt.innerHTML = act.name; - action_list.appendChild(opt); - } - - // logic to expose form of each action - let action_params = this.holder.querySelector(".action_params"); - action_list.onchange = function(){ - action_params.innerHTML = ""; - let act = that._actions.filter(e => e.name == this.value)[0] - that.current_action = act; - action_params.appendChild(act.form()); - } - action_list.onchange(); - - // add_action logic - let add_action = document.querySelector(".add_action"); - add_action.addEventListener("submit", function(event){ - event.preventDefault(); - const formData = new FormData(this); - let act_inst = new that.current_action(that.graph, formData); - that.add_action(act_inst); - }); - - // linking field with html nodes - this.head = holder.querySelector(".vizhead"); - this.body = holder.querySelector(".vizbody"); - this.footer= holder.querySelector(".vizfooter"); - - // closing the viz - holder.querySelector('button[class="btn btn-danger"]').addEventListener("click", function(){ - // that.graph.delete_onready_callback(that.draw_callback); - // holder.remove(); - document.getElementById("metadatamanagerholder").innerHTML = ""; - }); - - // callback logic - this.draw_callback = () => that.draw(); - this.update_filter_callback = () => that.update_filter(); - this.graph.add_onready_callback(this.draw_callback); - this.graph.add_onupdate_filter_callback(this.update_filter_callback); - this.draw(); - } -} diff --git a/vizitig/query/query.py b/vizitig/query/query.py index 69d19298723c305a992c7af777a477ad16e43c1b..fcc874e9441847910ca9e19a4fdd0b612e5a3694 100644 --- a/vizitig/query/query.py +++ b/vizitig/query/query.py @@ -156,7 +156,7 @@ GT : ">" acgt : /[ACGT]+/i integer : INT -ident : CNAME +ident : /[a-zA-Z_][\w\.\-\_]*/i %import common.CNAME %import common.INT