Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possibility to add metadata overlay? #64

Closed
pschwientek opened this issue Apr 16, 2018 · 10 comments
Closed

Possibility to add metadata overlay? #64

pschwientek opened this issue Apr 16, 2018 · 10 comments
Assignees

Comments

@pschwientek
Copy link

Hi,
we've been working with phylotree for a while now and ran into the need to display metadata in addition to the phylogram. An initial hack got us 80% towards that goal (see below) but it is still very buggy and does not work on the circular tree at all. I was wondering if such a feature, or an interface/plugin to customize any sort of data overlay would a) be of interest to others and b) could be added to the code base. We are unfortunately not savvy enough to just fork and add the feature ourselves.

Example of our current hack, showing 5 metadata points overlaid with the sample in the tree. Other ideas like colored symbols or even pie charts per leaf would be great to have in the future.
image

Thanks,
Patrick

@spond
Copy link
Member

spond commented Apr 17, 2018

Dear @pschwien82,

Not the prettiest solution, but should get your started: http://bl.ocks.org/spond/707878838762cb57d892a5d8e774d4e0

The idea is to use the callback invoked when each node is drawn (node_styler) and add the desired graphical elements (in the example : 3 colored boxes) to the SVG element that is the node.

@stephenshank : if you look at the example, it exposes some of the limitations of the node styler callback, which we should discuss how to remedy. For example, there should be no need to manually handle rotations and shifts for radial layouts, and there needs to be a mechanism to request layout space for node labels to accommodate arbitrary elements.

Best,
Sergei

@spond spond self-assigned this Apr 17, 2018
@pschwientek
Copy link
Author

Thank you Sergei,
this was a super fast response, I appreciate it.
We will try the code sample out in the next few days!

Best,
Patrick

@carlotta-93
Copy link

I am working on this topic as well as part of a project for the university and I am interested in future updates.

what I managed to do so far is something like this:
linear_data
radial_data

I have three different categories of data and this is where I got so far.
I didn't use the node_styler but just append new svg elements at the end of the labels. I see now that the solution presented here might solve me a lot of effort actually.
So thank you very much :) and looking forward to new updates or examples

@carlotta-93
Copy link

Hello again,
I used the code example for my project but I have some issues I cannot manage to solve.
My issue is on the resizing of the svg. More precisely, when I add "longer shapes" (so instead of the three squares presented in the code I want to bind "barplots", each bar on each node, showing a quantitative type of data) the issue I have is that on the linear layout the svg gets resized too small to show the whole "length" of the shapes.
I tried playing a bit with your example and maybe I didn't really understand how the
'left-right-spacing' and 'top-bottom-spacing' options work.
This is the code I am using:
`<script>
var example_tree = "(((glp1r_human:0.03295,g1sgd4_rabit:0.01369):0.30976,glr_human:0.27221):0.47235,(crfr1_human:0.64018,(grm1_human:0.10097,grm5_human:0.17333):3.05238):0.10081,calcr_human:0.71265);";
// tree from Yokoyama et al http://www.ncbi.nlm.nih.gov/pubmed/18768804
var tree = d3.layout.phylotree()
// create a tree layout object
.svg(d3.select("#tree_display")).align_tips(true).radial(true);
// render to this SVG element
var attribute_to_color = d3.scale.category10();
var standard_label = tree.branch_name();

tree.branch_name(function (node) {
    return standard_label(node) + "             ";
});
tree(d3.layout.newick_parser(example_tree));
tree.size([400, 400]);
var tree_attributes = {};

/* the following loop just populates an object with key : value pairs like
    leaf_name -> [a,b,c], where a,b,c are random numbers in {0,1,2,3,4}
*/

var maximum_length = 0;

tree.traverse_and_compute (function (node) {
    if (d3.layout.phylotree.is_leafnode (node)) {
        tree_attributes[node.name] = [0,0,0].map (function () {return Math.floor(Math.random() * 5);});
        maximum_length = maximum_length < node.name.length ? node.name.length : maximum_length;
    }
});


tree.style_nodes(function (element, node_data) {
    if (node_data.name in tree_attributes) {   // see if the node has attributes

        var node_label = element.select("text");
        var font_size  = parseFloat(node_label.style("font-size"));

        var annotation = element.selectAll("rect").data(tree_attributes[node_data.name]);
        annotation.enter().append("rect");
        annotation.attr ("width", font_size)
            .attr ("height", font_size)
            .attr ("y", -font_size/2).style ("fill", function(d, i) {
            return attribute_to_color(d);
        });

        var move_past_label =  maximum_length * 0.75 * font_size;

        if (tree.radial ()) {
            var shifter = tree.shift_tip(node_data)[0];
            annotation.attr("transform", "rotate (" + node_data.text_angle + ")")
                .attr ("x", function (d, i) { return   shifter > 0 ? shifter + font_size * i + move_past_label : shifter - font_size * (i+1) - move_past_label;})
        } else {
            var x_shift = tree.shift_tip (node_data)[0] + move_past_label;
            annotation.attr ("transform", null).attr ("x", function (d, i) { return  x_shift + font_size * i;});
        }

    }
});

tree.options({
    'show-scale': true
    'left-right-spacing': "fit-to-size",
    // fit to given size top-to-bottom
    'top-bottom-spacing': "fit-to-size"
    // fit to given size left-to-right
    //'selectable': false,
    // make nodes and branches not selectable
    //'collapsible': false,
    // turn off the menu on internal nodes
    //'transitions': false
    // turn off d3 animations
})

$("#layout").on ("click", function (e) {
    tree.radial ($(this).prop ("checked")).placenodes().update ();
});

// parse the Newick into a d3 hierarchy object with additional fields
tree.layout();`

The first output looks like this:
lay1
which is how the tree is supposed to look like.
When i switch view it looks like this:
lay2
Which is good. But when I switch again to radial, the tree changes and looks like this:
lay3
So, this is the issue I don't understand. Could you explain why this happens??

This project I am working on is part of an exam that I'll have in late June and if the output of the project will be functional then it might end up being used as an official tool on this website:
http://gpcrdb.org/
Therefore I am highly interested in solving the issue hereby presented.

Thanks for your help.

@spond
Copy link
Member

spond commented Jun 13, 2018

Dear @carlotta-93,

I think both radial trees are the same, just that the second one uses an "annular" display near the center to space things out a bit differently. I will make a note to track down why the layout changes between the two iterations (it should be stable), but the tree is not wrong in either case.

I'll update this thread when I have a solution for stable rendering.

Best,
Sergei

@spond spond added the bug label Jun 13, 2018
@carlotta-93
Copy link

Hi @spond,
Thanks for your reply. I understand that now but the weird part is that the "annular" is not added when "fixed-size" is chosen instead of "fit-to-size".

For stable rendering you mean when "longer" shapes are added ? Shall I add some pictured to make the problem more clear ?

Thank you very much for your help.

@spond
Copy link
Member

spond commented Jun 13, 2018

Dear @carlotta-93,

Yes, more pictures would be helpful. By stable rendering I mean that when you switch between radial and linear views, you should get the same renderings for each mode regardless of how many switches occur.

Best,
Sergei

@carlotta-93
Copy link

All right,
Firstly, I am using these options:
tree.options({ 'show-scale': true //'left-right-spacing': "fixed-step", // fit to given size top-to-bottom //'top-bottom-spacing': "fixed-step" // fit to given size left-to-right //'selectable': false, // make nodes and branches not selectable //'collapsible': false, // turn off the menu on internal nodes //'transitions': false // turn off d3 animations })
so the spacings are default to "fixed-step". And the tree is built with the same code as the example for the metadata. (there is option for width neither in the HTML nor the .js)
The problem with the "longer shapes" I have is the following
First layout:
lay1
Here the size of the svg is "arbitrary chosen" and everything is fine.
When I add "barplots" to show quantitative data this is what happens in radial layout:
lay2
So, some of the shapes are cut out from the svg (which mantains the same size)
If I switch to linear layout, this is what happens:
lay3
And the size of the svg is "resized" but it is too small in width.
If I change the size from the "inspection" board I get to see my barplots entirely:
lay4

What I would like to understand is why this happens and if there is a way to solve it.
I tries using "fit-to-size" with tree.size([800, 800]) exactly the same happens.

Thanks for your time

@spond
Copy link
Member

spond commented Jun 13, 2018

Dear @carlotta-93,

It looks like you are toggling bar plots on and off. In order to understand what phylotree is doing at that moment, can you provide the code for your event handler for when the annotations are added?

Best,
Sergei

@carlotta-93
Copy link

carlotta-93 commented Jun 14, 2018

Hi @spond,
Here is the code:
` function draw_quantitative_data(select_data, data_type){

tree.align_tips(true);

var maximum_length = 0;

var receptor_coverage = {}; // create object of GPCR_classes to bind to element

tree.get_nodes().forEach(function (node) {
    if (d3.layout.phylotree.is_leafnode(node)) {
        select_data.forEach(function (receptor) {
            if(node.name === receptor.name){
                receptor_coverage[node.name] = [0].map(function () {
                    return receptor.coverage
                });
            }
        });
        maximum_length = maximum_length < node.name.length ? node.name.length : maximum_length;
    }
});

var coverage_tooltip = d3.select("body").append("div")
    .attr("class", "coverage_tooltip")
    .style("opacity", 0);

var max_rect_h = 30;

var yScale_radial = d3.scale.linear() // scale to draw the quantities relative to the max_rect_h
    .domain([1, 0]) // change to max and min values from the type
    .range([max_rect_h, 0]);

tree.style_nodes(function (element, node_data) {

    if (node_data.name in receptor_coverage) {   // see if the node has attributes
        var node_label = element.select("text");
        var font_size  = parseFloat(node_label.style("font-size"));

        var annotation = element.selectAll("rect").data(receptor_coverage[node_data.name]);

        annotation.enter().append("rect").attr("class", "receptor_coverage");
        annotation
            .attr ("height", font_size)
            .attr("width", function (d) {
                return yScale_radial(d)
            })
            .attr ("y", -font_size/2)
            .style("fill", "#DBB1CD")

            .on("mouseover", function(d) { // add tooltip
                coverage_tooltip.transition()
                    .style("opacity", .9);
                coverage_tooltip.html(function(){
                    return ("Coverage: " + d)
                })
                    .style("left", (d3.event.pageX) + "px")
                    .style("top", (d3.event.pageY - 28) + "px");
            })

            .on("mouseout", function() {
                coverage_tooltip.transition().duration(100)
                    .style("opacity", 0);
            });

        var move_past_label = maximum_length * 0.65 * font_size;

        if (tree.radial ()) {
            var shifter = tree.shift_tip(node_data)[0];
            annotation.attr("transform", "rotate (" + node_data.text_angle + ")")
                .attr ("x", function (d, i) { return   shifter > 0 ? shifter + font_size * i + move_past_label : shifter - font_size * (i+1) - move_past_label;})
        } else {
            var x_shift = tree.shift_tip (node_data)[0] + move_past_label;
            annotation.attr ("transform", null).attr ("x", function (d, i) { return  x_shift + font_size * i;});
        }

    }

});

tree.layout();`

The select_data arguments is an array of dictionaries as follows:
{ "name":"glr_human", "GPCR_class":"C (Glutamate)", "selectivity":["Gi/Go family", "Gq/G11 family", "Gs family"], "ligand_type":"Protein receptors", "coverage":0.6, "receptor_page":"http://gpcrdb.org/protein/glr_human/" },
Where coverage is a random number between 0 and 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants