1595e97504deb202b96a3e4bd5efe94f4b70c898 ceisenhart Wed Jan 13 13:31:36 2016 -0800 The new home for the visuzlization javascript code, refs# 16468, 16216,16341 diff --git src/hg/js/d3.dendrograms.js src/hg/js/d3.dendrograms.js new file mode 100644 index 0000000..a99f7c4 --- /dev/null +++ src/hg/js/d3.dendrograms.js @@ -0,0 +1,578 @@ +/* +d3.dendrogram.js + +Copyright (c) 2013, Ken-ichi Ueda +2015 UCSC Chris Eisenhart + +DOCUEMENTATION +d3.dendrogram.makeCartesianDendrogram(selector, nodes, options) + Generates a cartesian (flat) dendrogram from a set of .json data. The branch lengths are + scaled acording to the 'length' field in the .json file, buttons are automatically generated + for every .json field, in the leaf nodes only, not in this list, [x, y, name, kids, length, + colorGroup, parent, depth, rootDist]. + Arguments: + selector: selector of an element that will contain the SVG (a div id). + nodes: A Javascript object of nodes, most commonly a .json file parsed into javascript. + options: + width + Width of the vis, will attempt to set a default based on the width of + the container. + height + Height of the vis, will attempt to set a default based on the height + of the container. + vis + Pre-constructed d3 vis. + tree + Pre-constructed d3 tree layout. + children + Function for retrieving an array of children given a node. Default is + to assume each node has an attribute called "children" that contains the + nodes children. + diagonal + Function that creates the d attribute for an svg:path. Defaults to a + right-angle diagonal. + skipTicks + Skip the background lines for context. + skipBranchLengthScaling + Make a dendrogram instead of a dendrogram. + +d3.dendrogram.makeRadialDendrogram(selector, nodes, options) + Generates a radial (circular) dendrogram from a set of .json data. The dendrogram displays + internal node size based on the number of children nodes and intenal node color based of the + .json field "whiteToBlack" which is expected to be a rgb color. Buttons are automatically + generated to control internal node color, these buttons correspond to the .json fields + [whiteToBlack, whiteToBlackSqrt, whiteToBlackQuad]. Additionally buttons are automatically + generated for every .json field, in the leaf nodes only, not in this list, [x, y, name, kids, + length, colorGroup, parent, depth, rootDist]. + Arguments: + selector: selector of an element that will contain the SVG (a div id). + nodes: A Javascript object of nodes, most commonly a .json file parsed into javascript. + options: + width + Width of the vis, will attempt to set a default based on the width of + the container. + height + Height of the vis, will attempt to set a default based on the height + of the container. + vis + Pre-constructed d3 vis. + tree + Pre-constructed d3 tree layout. + children + Function for retrieving an array of children given a node. Default is + to assume each node has an attribute called "children" that contains the + nodes children. + diagonal + Function that creates the d attribute for an svg:path. Defaults to a + right-angle diagonal. + skipTicks + Skip the background lines for context. + skipBranchLengthScaling + Make a dendrogram instead of a dendrogram. + +d3.dendrogram.rightAngleDiagonal() + Computes the angles for the graph, the user can override this if needed. +d3.dendrogram.leafColors(val, layer, shift, selector) + This function is used by the buttons to color the leaf nodes. It should not be + modified. +d3.dendrogram.nodeColors(val, selector) + This function is used by the buttons in the radial dendrogram to color the + internal nodes. It should not be modified. + +Internal functions: + updateLegend(val, layer, vis, shift) + Update the legend to correspond to the current set of meta data. + scaleBranchLengths(nodes, w) + Scale the branch lengths to correspond to the 'length' field in the json data. + addLeafButton(title, count, layer, shift, dropdownSelector, graph) + Adds a leaf button to the dropdown skeleton. + addNodeButton(dropdownSelector, val, graph, title) + Adds a node button to the dropdown skeleton. + makeDropdownSkeleton(treeType) + Builds a bootstrap dropdown menu skeleton. + +*/ + +if (!d3) { throw "d3 wasn't included!";} +(function(){ + d3.dendrogram = {}; + + var colors=d3.scale.category20(); + var legendRectSize=20; + var legendSpacing=4; + var currentLegend=-1; + var radius=0; + + d3.dendrogram.rightAngleDiagonal=function (){ + var projection = function(d) { return [d.y, d.x]; }; + + var path = function(pathData) { + return "M" + pathData[0] + ' ' + pathData[1] + " " + pathData[2]; + }; + + function diagonal(diagonalPath, i) { + var source = diagonalPath.source, + target = diagonalPath.target, + midpointX = (source.x + target.x) / 2, + midpointY = (source.y + target.y) / 2, + pathData = [source, {x: target.x, y: source.y}, target]; + pathData = pathData.map(projection); + return path(pathData); + } + + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + + diagonal.path = function(x) { + if (!arguments.length) return path; + path = x; + return diagonal; + }; + return diagonal; + }; + + d3.dendrogram.leafColors = function(val, layer, shift, selector){ + colors = d3.scale.category20(); + var vis = d3.select(selector).select("svg"); + var first = 1; + var node = vis.selectAll("g."+ layer + "Leaf"); + + node.selectAll("circle") + .style("fill", function(d){ + // This block links the extra leaf json fields to a specific value (count). This value + // is provided when the buttons are written in the main function. + if (d.name != " "){ + var count = 0; + for (var key in d) { + if (d.hasOwnProperty(key)){ + if (key != "x" && key!="y" && key!= "name" && key!="kids" && key!="length" && key!="colorGroup" && key!="parent" && key!="depth" && key!="rootDist"){ + if (count == val){ + count += 1; + return colors(d[key]); + } + } + count += 1; + } + } + } + else{return 'none';} + }) + .attr("transform","translate(" + shift + "," + 0 + ")"); + + var legend=vis.selectAll('g.' + layer + 'Legend').remove(); + updateLegend(colors.domain(), layer, vis, shift); + colors=d3.scale.category20(); + }; + + d3.dendrogram.nodeColors = function(val, selector){ + var vis = d3.select(selector).selectAll("svg"); + var node=vis.selectAll("g.internalNode").selectAll("circle") + .style("fill",function(d){ + if (d.name === " "){ + if (val==1) return d3.rgb(d.colorGroup); + if (val==2) return d3.rgb(d.whiteToBlack); + if (val==3) return d3.rgb(d.whiteToBlackSqrt); + if (val==4) return d3.rgb(d.whiteToBlackQuad); + } + }); + }; + + updateLegend = function(val, layer, vis, shift){ + var legend=vis.selectAll('g.'+layer+'Legend').data(val); + + legend.enter() + .append('g') + .attr('class',layer+ 'Legend') + .attr('transform', function (d, i){ + var height=legendRectSize + legendSpacing; + var offset= height * colors.domain().length / 2; + var horz, vert; + if (layer=="inner"){ + horz=(-2 * legendRectSize) + radius; + vert=-(i * height - offset) + (radius*(1/2)); + } + if (layer=="middle"){ + horz=(-2 * legendRectSize) + radius + 75; + vert=-(i * height - offset) + (radius*(1/2)); + } + if (layer=="outer"){ + horz=(-2 * legendRectSize) + radius + 150; + vert=-(i * height - offset)+ (radius*(1/2)); + } + return 'translate(' + horz + ',' + vert + ')'; + }); + + legend.append('rect') + .attr('width', legendRectSize) + .attr('height', legendRectSize) + .style('fill', colors) + .style('stroke', colors); + + legend.append('text') + .attr('x', legendRectSize + legendSpacing) + .attr('y', legendRectSize - legendSpacing) + .text(function (d){return d;}); + }; + + scaleBranchLengths = function (nodes, w) { + // Visit all nodes and adjust y pos width distance metric + var visitPreOrder = function(root, callback) { + callback(root); + if (root.children) { + for (var i = parseInt(root.children.length) - 1; i >= 0; i--){ + visitPreOrder(root.children[i], callback); + } + } + }; + + visitPreOrder(nodes[0], function(node) { + node.rootDist = (node.parent ? node.parent.rootDist : 0) + (parseInt(node.length) || 0); + }); + + var rootDists = nodes.map(function(n) { return n.rootDist; }); + var yscale = d3.scale.linear() + .domain([0, d3.max(rootDists)]) + .range([0, w]); + visitPreOrder(nodes[0], function(node) { + node.y = yscale(node.rootDist); + }); + return yscale; + }; + + addLeafButton = function (title, count, layer, shift, dropdownSelector, graph) { + d3.select(dropdownSelector).append("li").html(""); + }; + + addNodeButton = function (dropdownSelector, val, graph, title) { + d3.select(dropdownSelector).append("li").html(""); + }; + + makeDropdownSkeleton = function (treeType){ + var dropdown = d3.select("#dropdown").append("ul").attr("style","list-style-type:none;display:inline-flex"); + + if (treeType == "Dendro"){ + var interiorNodeDropdown = dropdown.append("li").attr("class","dropdown"); + interiorNodeDropdown.append("button").attr("class","btn btn-default dropdown-toggle") + .attr("type", "button").attr("data-toggle","dropdown").html("Inner nodes").append("span").attr("class","caret"); + interiorNodeDropdown.append("ul").attr("class","dropdown-menu").html("