Permalink
Browse files

Merge pull request #106 from nextstrain/fix_legend_zoom

Fix legend zoom
  • Loading branch information...
2 parents f95e32c + ce73826 commit 049937139090c28d9e62b0a82fef0a072930caae @colinmegill colinmegill committed on GitHub Jan 3, 2017
@@ -80,7 +80,8 @@ class App extends React.Component {
componentWillReceiveProps(nextProps) {
const tmpQuery = queryString.parse(window.location.search);
- const cScale = getColorScale(tmpQuery.colorBy, nextProps.tree, nextProps.sequences);
+ const cScale = getColorScale(tmpQuery.colorBy, nextProps.tree, nextProps.sequences,
+ this.props.metadata.metadata ? this.props.metadata.metadata.color_options : null);
this.setState({
colorScale: cScale
});
@@ -196,7 +197,8 @@ class App extends React.Component {
}
updateColorScale(colorBy) {
- const cScale = getColorScale(colorBy, this.props.tree, this.props.sequences);
+ const cScale = getColorScale(colorBy, this.props.tree, this.props.sequences,
+ this.props.metadata.metadata ? this.props.metadata.metadata.color_options : null);
let gts = null;
if (colorBy.slice(0,3) === "gt-" && this.props.sequences.geneLength) {
gts = parseGenotype(colorBy, this.props.sequences.geneLength);
@@ -4,6 +4,10 @@ import titleCase from "title-case";
import { connect } from "react-redux";
import { LEGEND_ITEM_MOUSEENTER, LEGEND_ITEM_MOUSELEAVE } from "../../actions/controls";
+function isNumeric(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+}
+
@connect()
@Radium
class LegendItem extends React.Component {
@@ -22,6 +26,11 @@ class LegendItem extends React.Component {
if (d) {
label = titleCase(d.toString());
}
+ if (isNumeric(d)){
+ const val = parseFloat(d);
+ const magnitude = Math.ceil(Math.log10(Math.abs(val)+1e-10));
+ label = val.toFixed(5-magnitude);
+ }
if (this.props.dFreq) {
label += "\u00D7";
@@ -63,7 +63,8 @@ class TreeView extends React.Component {
previously in componentDidMount, but we don't mount immediately anymore -
need to wait for browserDimensions
*/
- if (this.Viewer) {
+ const good_tree = (this.state.tree && (nextProps.datasetGuid === this.props.datasetGuid));
+ if (this.Viewer && !good_tree) {
this.Viewer.fitToViewer();
const tree = (this.state.tree)
? this.state.tree
@@ -72,7 +73,7 @@ class TreeView extends React.Component {
}
/* Do we have a tree to draw? if yes, check whether it needs to be redrawn */
- const tree = ((nextProps.datasetGuid === this.props.datasetGuid) && this.state.tree)
+ const tree = good_tree
? this.state.tree
: this.makeTree(nextProps.nodes, this.props.layout, this.props.distance);
@@ -187,7 +188,8 @@ class TreeView extends React.Component {
this.props.distanceMeasure,
{
/* options */
- grid: true
+ grid: true,
+ confidence: true
},
{
/* callbacks */
@@ -37,9 +37,9 @@ export const regionColorScale = d3.scale.ordinal()
.domain(globals.regions.map((d) => { return d[0]; }))
.range(globals.regions.map((d) => { return d[1]; }));
- export const countryColorScale = d3.scale.ordinal()
- .domain(globals.countries.map((d) => { return d[0]; }))
- .range(globals.countries.map((d) => { return d[1]; }));
+export const countryColorScale = d3.scale.ordinal()
+ .domain(globals.countries.map((d) => { return d[0]; }))
+ .range(globals.countries.map((d) => { return d[1]; }));
export const dateColorScale = d3.scale.linear().clamp([true])
.domain(globals.dateColorDomain)
@@ -43,7 +43,7 @@ const discreteAttributeScale = (nodes, attr) => {
return d3.scale.ordinal().domain(domain);
};
-const getColorScale = (colorBy, tree, sequences) => {
+const getColorScale = (colorBy, tree, sequences, colorOptions) => {
const cScaleTypes = {ep: "integer", ne: "integer", rb: "integer",
lbi: "continuous", fitness: "continuous", num_date: "continuous",
region: "discrete", country: "discrete"};
@@ -71,24 +71,28 @@ const getColorScale = (colorBy, tree, sequences) => {
colorScale = d3.scale.ordinal().domain(domain).range(genotypeColors);
}
}
- } else if (colorBy === "region") {
- continuous = false;
- colorScale = scales.regionColorScale;
- } else if (colorBy === "country") {
- continuous = false;
- colorScale = scales.countryColorScale;
- } else if (cScaleTypes[colorBy] === "continuous") {
- continuous = true;
- colorScale = minMaxAttributeScale(tree.nodes, colorBy);
- } else if (cScaleTypes[colorBy] === "integer") {
- continuous = true;
- colorScale = integerAttributeScale(tree.nodes, colorBy);
- } else if (cScaleTypes[colorBy] === "discrete") {
- continuous = false;
- colorScale = discreteAttributeScale(tree.nodes, colorBy);
+ } else if (colorOptions && colorOptions[colorBy]){
+ if (colorOptions[colorBy].color_map){
+ continuous=false;
+ colorScale = d3.scale.ordinal()
+ .domain(colorOptions[colorBy].color_map.map((d) => { return d[0]; }))
+ .range(colorOptions[colorBy].color_map.map((d) => { return d[1]; }));
+ }
+ else if (colorOptions && colorOptions[colorBy].type === "discrete") {
+ continuous = false;
+ colorScale = discreteAttributeScale(tree.nodes, colorBy);
+ }
+ else if (colorOptions && colorOptions[colorBy].type === "integer") {
+ continuous = false;
+ colorScale = integerAttributeScale(tree.nodes, colorBy);
+ }
+ else if (colorOptions && colorOptions[colorBy].type === "continuous") {
+ continuous = true;
+ colorScale = minMaxAttributeScale(tree.nodes, colorBy);
+ }
} else {
continuous = true;
- colorScale = genericScale(0, 1);
+ colorScale = minMaxAttributeScale(tree.nodes, colorBy);
}
return {"scale": colorScale, "continuous": continuous, "colorBy": colorBy,
"legendBoundsMap": createLegendMatchBound(colorScale)};
@@ -17,7 +17,7 @@ export const addAllTipsToMap = (nodes, metadata, colorScale, map) => {
metadata.geo.country[key].longitude
], {
stroke: false,
- radius: value * 2,
+ radius: 2 + Math.sqrt(value) * 4,
// color: ""
// weight: 5 Stroke width in pixels.
View
@@ -54,6 +54,7 @@ var PhyloTree = function(treeJson) {
// remember the range of children subtending a node (i.e. the range of yvalues)
// and create children structure
this.nodes.forEach(function(d) {
+ d.parent = d.n.parent.shell;
if (d.terminal) {
d.yRange = [d.n.yvalue, d.n.yvalue];
d.children=null;
@@ -103,6 +104,11 @@ PhyloTree.prototype.setDistance = function(attr) {
const tmp_dist = this.distance;
this.nodes.forEach(function(d) {
d.depth = d.n.attr[tmp_dist];
+ if (d.n.attr[tmp_dist+"_confidence"]){
+ d.conf = d.n.attr[tmp_dist+"_confidence"];
+ }else{
+ d.conf = [d.depth, d.depth];
+ }
});
this.nodes.forEach(function(d) {
d.pDepth = d.n.parent.attr[tmp_dist];
@@ -113,6 +119,7 @@ PhyloTree.prototype.rectangularLayout = function() {
this.nodes.forEach(function(d) {
d.y = d.n.yvalue;
d.x = d.depth;
+ d.x_conf = d.conf;
d.px = d.pDepth;
d.py = d.y;
});
@@ -181,7 +188,7 @@ PhyloTree.prototype.radialLayout = function() {
d.xCBarStart = (d.depth - offset) * Math.sin(angleCBar1);
d.yCBarEnd = (d.depth - offset) * Math.cos(angleCBar2);
d.xCBarEnd = (d.depth - offset) * Math.sin(angleCBar2);
- d.smallBigArc = Math.abs(angleCBar2 - angleCBar1) > Math.Pi * 0.5;
+ d.smallBigArc = Math.abs(angleCBar2 - angleCBar1) > Math.PI * 1.0;
});
};
@@ -227,6 +234,12 @@ PhyloTree.prototype.zoomIntoClade = function(clade, dt) {
}
}
};
+ // zooming into terminal node doesn't make sense, presumably the parent is meant
+ if (clade.terminal){
+ kidsVisible(clade.parent);
+ }else{
+ kidsVisible(clade);
+ }
kidsVisible(clade);
this.mapToScreen();
this.updateGeometry(dt);
@@ -263,6 +276,9 @@ PhyloTree.prototype.mapToScreen = function(){
this.nodes.forEach(function(d){d.yTip = tmp_yScale(d.y)});
this.nodes.forEach(function(d){d.xBase = tmp_xScale(d.px)});
this.nodes.forEach(function(d){d.yBase = tmp_yScale(d.py)});
+ if (this.params.confidence && this.layout==="rectangular"){
+ this.nodes.forEach(function(d){d.xConf = [tmp_xScale(d.conf[0]), tmp_xScale(d.conf[1])];});
+ }
if (this.layout==="rootToTip" || this.layout==="unrooted"){
this.nodes.forEach(function(d){d.branch =" M "+d.xBase.toString()+","+d.yBase.toString()+
@@ -274,6 +290,10 @@ PhyloTree.prototype.mapToScreen = function(){
" L "+d.xTip.toString()+","+d.yTip.toString()+
" M "+d.xTip.toString()+","+d.cBarStart.toString()+
" L "+d.xTip.toString()+","+d.cBarEnd.toString();});
+ if (this.params.confidence){
+ this.nodes.forEach(function(d){d.confLine =" M "+d.xConf[0].toString()+","+d.yBase.toString()+
+ " L "+d.xConf[1].toString()+","+d.yTip.toString();});
+ }
} else if (this.layout==="radial"){
const offset = this.nodes[0].depth;
this.nodes.forEach(function(d){d.cBarStart = tmp_yScale(d.yRange[0])});
@@ -545,6 +565,13 @@ PhyloTree.prototype.updateGeometryFade = function(dt) {
};
};
setTimeout(fadeBack(this.svg, 0.2 * dt), 1.5 * dt);
+
+ this.svg.selectAll('.conf')
+ .transition().duration(dt)
+ .attr("visibility", this.layout==="rectangular"?"visible":"hidden")
+ .attr("d", function(d) {
+ return d.confLine;
+ });
};
PhyloTree.prototype.updateGeometry = function(dt) {
@@ -566,6 +593,13 @@ PhyloTree.prototype.updateGeometry = function(dt) {
.attr("d", function(d) {
return d.branch;
});
+
+ this.svg.selectAll('.conf')
+ .transition().duration(dt)
+ .attr("visibility", this.layout==="rectangular"?"visible":"hidden")
+ .attr("d", function(d) {
+ return d.confLine;
+ });
};
PhyloTree.prototype.selectBranch = function(node) {
@@ -725,7 +759,7 @@ PhyloTree.prototype.clearSVG = function() {
this.svg.selectAll('.branch').remove();
};
-PhyloTree.prototype.tips = function() {
+PhyloTree.prototype.makeTips = function() {
this.tipElements = this.svg.append("g").selectAll(".tip")
.data(this.nodes.filter(function(d) {
return d.terminal;
@@ -764,7 +798,7 @@ PhyloTree.prototype.tips = function() {
.style("cursor", "pointer");
};
-PhyloTree.prototype.branches = function() {
+PhyloTree.prototype.makeBranches = function() {
this.branches = this.svg.append("g").selectAll('.branch')
.data(this.nodes)
.enter()
@@ -793,6 +827,29 @@ PhyloTree.prototype.branches = function() {
.style("cursor", "pointer");
};
+PhyloTree.prototype.makeConfidence = function() {
+ this.confidence = this.svg.append("g").selectAll('.conf')
+ .data(this.nodes)
+ .enter()
+ .append("path")
+ .attr("class", "conf")
+ .attr("id", function(d) {
+ return "conf_" + d.n.clade;
+ })
+ .attr("d", function(d) {
+ return d.conf;
+ })
+ .style("stroke", function(d) {
+ return d.stroke || "#888";
+ })
+ .style("opacity", 0.5)
+ .style("fill", "none")
+ .style("stroke-width", function(d) {
+ return d.strokeWidth*2 || 4;
+ });
+};
+
+
PhyloTree.prototype.render = function(svg, layout, distance, options, callbacks) {
this.svg = svg;
this.params = Object.assign(this.params, options);
@@ -805,8 +862,11 @@ PhyloTree.prototype.render = function(svg, layout, distance, options, callbacks)
if (this.params.showGrid){
this.addGrid();
}
- this.branches();
- this.tips();
+ if (this.params.confidence){
+ this.makeConfidence();
+ }
+ this.makeBranches();
+ this.makeTips();
this.updateGeometry(10);
this.svg.selectAll(".regression").remove();
if (layout==="rootToTip") this.drawRegression();

0 comments on commit 0499371

Please sign in to comment.