diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9b239..466db43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,21 @@ # ArborView: Changelog + ## Unreleased ### `Changed` - - Made the scale bar draggable and keyboard-movable for dendrogram layouts. Introduced support for ultrametric trees v [PR 14](https://github.com/phac-nml/ArborView/pull/14) - Added an alternative distance display for ultrametric and ML trees. [PR 13](https://github.com/phac-nml/ArborView/pull/13) - Tree layout selection is now a drop down menu and not a slider. [PR 2](https://github.com/phac-nml/ArborView/pull/2) - Changed the `IDs` button hoverover tip message to reflect the new copy leaf node identifiers to the clipboard functionality [PR 11](https://github.com/phac-nml/ArborView/pull/11) - Tree layout selection is now a drop down menu and not a slider. [PR 2](https://github.com/phac-nml/ArborView/pull/2) - Text selection cursor symbol changes from hand to a pipe symbol (`|`) when mouse over the text in both the webapp tree and SVG exported image. [PR 8](https://github.com/phac-nml/ArborView/pull/8) -- TreeSVG overflow is now visible preventing cutoff labels, fixing ENHC0010243 [PR 19](https://github.com/phac-nml/ArborView/pull/19) ### `Added` - -- Added duplicated `` tags removal during SVG image export from the tree leaf nodes for accurate keyword searches on an resulting SVG image [PR 10] () +- Added duplicated `` tags removal during SVG image export from the tree leaf nodes for accurate keyword searches on an resulting SVG image [PR 10] (https://github.com/phac-nml/ArborView/pull/10) - Added text selection of tree text including distance values and leaf nodes text in both webapp and SVG image exports [PR 8](https://github.com/phac-nml/ArborView/pull/8) - Metadata fields have been added to inner and outer node labels. [PR 4](https://github.com/phac-nml/ArborView/pull/3) - Cladeogram tree layout. [PR 2](https://github.com/phac-nml/ArborView/pull/2) - Added slider for adjusting line thickness. [PR 5](https://github.com/phac-nml/ArborView/pull/5) - Added copy tree nodes to the clipboard [PR 11](https://github.com/phac-nml/ArborView/pull/11) -### `Fixed` - -- Stopped tree from "wandering" after redraws, due to incorrect setting of left padding. [PR 19](https://github.com/phac-nml/ArborView/pull/19) diff --git a/LOGO.png b/LOGO.png new file mode 100644 index 0000000..ce10c16 Binary files /dev/null and b/LOGO.png differ diff --git a/README.md b/README.md index 9d816ea..3bb716a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-green)](https://www.apache.org/licenses/LICENSE-2.0) +

+ ArborView Logo +

+ # ArborView - [Introduction](#introduction) diff --git a/html/table.html b/html/table.html index 1f84939..16977ec 100644 --- a/html/table.html +++ b/html/table.html @@ -16,15 +16,7 @@ position: relative; overflow: auto } - - .tree-svg { - height: auto; - font 10px sans-serif; - user-select: auto; - overflow: visible; - margin-left: 1em; - } .row.no-wrap { flex-wrap: nowrap; /*prevents the TreeData div with the svg tree to wrap around*/ @@ -225,7 +217,6 @@ */ var TREE_ULTRAMETRIC_P = false; - var LABEL_SAFETY_FACTOR = 200 var collapse_subtree = false; // can think of another way to signal state without our larger refactor var focused_element = null; // Focused element to have its colour reset var RADIUS_INCREASED = false; @@ -614,7 +605,8 @@ const marginBottom = 30; // updated for viewing const marginLeft = 40; - const inner_radius = width - LABEL_SAFETY_FACTOR; + const label_safety_factor = 300; + const inner_radius = width - label_safety_factor; function maxLength(d) { @@ -661,7 +653,7 @@ const svg = d3.create("svg") .attr("id", "TreeSVG") .attr("width", width) //makes sure all tree nodes are within view - .attr("class", "tree-svg") + .attr("style", `height: auto; font: 10px sans-serif; user-select: auto;`) svg.append("defs").append("style").attr("type", "text/css").text("svg text {cursor: text}"); @@ -854,12 +846,14 @@ var ele = document.getElementById("TreeData"); var ele_style = window.getComputedStyle(ele); const width = parseInt(ele_style.width)-TREE_OFFSET; + //var width = window.innerWidth; const marginTop = 40; const marginRight = 10; const marginBottom = 30; // updated for viewing const marginLeft = 40; - const inner_radius = width - LABEL_SAFETY_FACTOR; + let label_safety_factor = 300; + const inner_radius = width - label_safety_factor; function setDistance(d, y0, k) { // From the tree of life code @@ -878,11 +872,13 @@ const leaves_in_tree = root.leaves(); const pixels_per_label = 15; + const dx = 20; const root_node_height = dx * 0.5; + //const dy = (width - marginRight - marginLeft) / (1 + root.height); const dy = inner_radius / root.height; - // Test Scale drawing + tree = d3.cluster() .nodeSize([dx, dy]) .separation( (a, b) => { return 1;}) // Can pass in a custom function to alter distance between labels, 1 means all labels are spaced the same distance @@ -897,7 +893,7 @@ const svg = d3.create("svg") .attr("id", "TreeSVG") .attr("width", width) //makes sure all tree nodes are within view - .attr("class", "tree-svg") + .attr("style", `height: auto; font: 10px sans-serif; user-select: auto;`) svg.append("defs").append("style").attr("type", "text/css").text("svg text {cursor: text}"); @@ -922,7 +918,7 @@ let below_root = 0; function update(event, source) { - console.debug("Performing tree update") + console.log("Performing tree update") // Recalculate tree lengths on collapse let max_length = maxLength(root) setDistance(root, root.data.d = 0, (inner_radius / max_length)) // Third value is a scaling factor @@ -1041,7 +1037,7 @@ nodeEnter.append("text") .text((d) => { if(!d.data.leaf || !TREE_ULTRAMETRIC_P){ - return Number((d.display_dist).toFixed(DECIMAL_PLACES)); + return Number((d.display_dist).toFixed(DECIMAL_PLACES)) } }) .attr("class", "branch-length") @@ -1213,7 +1209,9 @@ const svg = d3.create("svg") .attr("id", "TreeSVG") .attr("viewBox", [0, 0, width, height]) - .attr("class", "tree-svg") + .attr("font-family", "sans-serif") + .attr("font-size", 10) + .attr("style", `height: auto; font: 10px sans-serif; user-select: auto;`); svg.append("defs").append("style").attr("type", "text/css").text("svg text {cursor: text}"); @@ -1483,7 +1481,8 @@ .attr("id", "TreeSVG") .attr("width", width + 20) //makes sure all tree nodes are within view .attr("height", dx) - .attr("class", "tree-svg") + .attr("style", `height: auto; font: 10px sans-serif; user-select: auto;`) + svg.append("defs").append("style").attr("type", "text/css").text("svg text {cursor: text}"); @@ -2069,7 +2068,7 @@ // Create a subtree from the inner node selected in a tree let CustomSubTree = (event, data) => { - console.debug("Subtree is being rendered") + console.log("Subtree is being rendered") $("#legend_toggle").bootstrapToggle('off') SelectedNodes.drawSelectedNodes(); $('#colour-legend').empty() @@ -2083,9 +2082,9 @@ let copy_head_node=structuredClone(data.data) const totalNodes = countNodes(copy_head_node) - console.debug(`New head node of the tree with ${totalNodes} nodes at distance ${copy_head_node.max_length} and ultrametric=${TREE_ULTRAMETRIC_P}`); + console.log(`New head node of the tree with ${totalNodes} nodes at distance ${copy_head_node.max_length} and ultrametric=${TREE_ULTRAMETRIC_P}`); //if a tree is non-ultrametric then create a subtree with subtracted distances from the head node selected as root now - console.debug(copy_head_node) + console.log(copy_head_node) if (!TREE_ULTRAMETRIC_P){ const distanceToSubtract = copy_head_node.max_length; const subtractDistance = (node, amount) => { @@ -2102,15 +2101,13 @@ // Apply the annotated distance subtraction to the copied head node and all its descendants. subtractDistance(copy_head_node, distanceToSubtract); } - console.debug(`Head node after subtraction ${copy_head_node.max_length}`); + console.log(`Head node after subtraction ${copy_head_node.max_length}`); - console.debug(copy_head_node) + console.log(copy_head_node) //RENDER tree based on the selected layout function let svg_chart = TREE_SWITCH.get(TREE_VAL)(copy_head_node); + svg_chart.style.paddingLeft = svg_chart.style.paddingLeft + TREE_OFFSET; - let renderedTreePadding = svg_chart.style.paddingLeft || 0 - svg_chart.style.paddingLeft = `${renderedTreePadding + TREE_OFFSET}px` - $("#TreeData").append(svg_chart); $(".leaf-node").each((i, elm) => { // Maintain colour of already selected nodes @@ -3040,8 +3037,7 @@ let chart_ = null; if(tree_root !== null){ chart_ = TREE_SWITCH.get(TREE_VAL)(tree_root); - let renderedTreePadding = chart_.style.paddingLeft || 0 - chart_.style.paddingLeft = `${renderedTreePadding + TREE_OFFSET}px` + chart_.style.paddingLeft = chart_.style.paddingLeft + TREE_OFFSET; $("#TreeData").append(chart_); $(".leaf-node").each((i, elm) => { @@ -3579,8 +3575,7 @@ if(text_nodes_leafs.length === 2){ text_nodes_leafs[0].remove() } - }) - svg.querySelector("#TreeSVG").style.overflow = "visible" + }) const blob = serialize2svg(svg)[0]; downloadLink.href = window.URL.createObjectURL(blob); downloadLink.click(); //Trigger a click on the element @@ -3589,70 +3584,39 @@ /*Adapted from https://gist.github.com/tatsuyasusukida/1261585e3422da5645a1cbb9cf8813d6 and https://zooper.pages.dev/articles/how-to-convert-a-svg-to-png-using-canvas*/ export_tree_to_png = function(){ + let svg = document.getElementById("TreeSVG") + let svg_width = svg.getBoundingClientRect().width; + let svg_height = svg.getBoundingClientRect().height; + //extract SVG element + const svgData = new XMLSerializer().serializeToString(svg); + + //create canvas to the size of the SVG + let canvas = document.createElement('canvas') + canvas.width = svg_width+svg_width*0.05; + canvas.height = svg_height+svg_width*0.15; - let svg = document.getElementById("TreeSVG").cloneNode(true); + //create SVG image element + let img = new Image(); + img.src = "data:image/svg+xml;base64," + btoa(svgData); + + img.onload = function () { //must be inside this image onload event function due to async image load nature + //render SVG image onto canvas element + let ctx = canvas.getContext('2d'); + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(img, 0, 0); + //create PNG image from the canvas + let pngUrl = canvas.toDataURL('image/png').replace('image/png', 'octet/stream'); + //create download link + let downloadLink = document.createElement("a"); + downloadLink.download = 'tree_snapshot.png'; + downloadLink.href = pngUrl; + downloadLink.click(); + downloadLink.remove(); + } - let {x, y, width, height} = svg.viewBox.baseVal; - let svg_width = width; - let svg_height = height; - let modified_width = svg_width + (svg_width * 0.15); - let modified_height = svg_height + (svg_height * 0.15); - - // Change the width of the svg element allowing for more of the viewbox to show up - svg.setAttribute("width", modified_width) - - /* - Create a temporary svg expanding increasing the size of the final - canvas the elelment is rendered in. Having this seperate wrapper - also allows for seperate modifications of the PNG before export allowing - for translationst to be performed. - */ - let wrapping_svg = d3.select("body") - .append("svg") - .attr("id", "temporary_svg") - .attr("height", modified_height) - .attr("width", modified_width) - .append('g') - .attr("id", "translation_element") - .attr("height", modified_height) - .attr("width", modified_width) - .append(() => { - return svg - }) - - //extract SVG element - const svgData = new XMLSerializer().serializeToString(document.getElementById("temporary_svg")); - - //create canvas to the size of the SVG - let canvas = document.createElement('canvas') - canvas.width = modified_width; - canvas.height = modified_height; - - //create SVG image element - let img = new Image(); - img.src = "data:image/svg+xml;base64," + btoa(svgData); - - img.onload = function () { //must be inside this image onload event function due to async image load nature - //render SVG image onto canvas element - let ctx = canvas.getContext('2d'); - ctx.fillStyle = "white"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - ctx.drawImage(img, 0, 0, modified_width, modified_height); - - //create PNG image from the canvas - let pngUrl = canvas.toDataURL('image/png').replace('image/png', 'octet/stream'); - //create download link - let downloadLink = document.createElement("a"); - downloadLink.download = 'tree_snapshot.png'; - downloadLink.href = pngUrl; - downloadLink.click(); - downloadLink.remove(); - } - - // Remove temporary elements - document.getElementById("temporary_svg").remove(); + }