From 4899ef47029603f37acb64296e06d0491219cbeb Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Mon, 23 Apr 2018 11:58:21 -0700 Subject: [PATCH 1/2] modify temporal color scale & legend to focus on tip dates --- src/util/colorScale.js | 81 ++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/src/util/colorScale.js b/src/util/colorScale.js index 71ab57ef4..a01c36f1c 100644 --- a/src/util/colorScale.js +++ b/src/util/colorScale.js @@ -10,18 +10,10 @@ import { setGenotype, orderOfGenotypeAppearance } from "./setGenotype"; const unknownColor = "#DDDDDD"; -const continuousScale = (x0, x1) => { - const scale = scaleLinear() - .domain(genericDomain.map((d) => x0 + d * (x1 - x0))) - .range(colors[9]); - return (val) => (val === undefined || val === false) ? unknownColor : scale(val); -}; - -const getMinMaxFromTree = (nodes, nodesToo, attr) => { - const vals = nodes.map((n) => n.attr[attr]); - if (nodesToo) { - nodesToo.forEach((n) => vals.push(n.attr[attr])); - } +const getMinMaxFromTree = (nodes, nodesToo, attr, tipsOnly) => { + let arr = nodesToo ? nodes.concat(nodesToo) : nodes.slice(); + if (tipsOnly) arr = arr.filter((n) => !n.hasChildren); + const vals = arr.map((n) => n.attr[attr]); vals.filter((n) => n !== undefined) .filter((item, i, ar) => ar.indexOf(item) === i); return [min(vals), max(vals)]; @@ -75,10 +67,19 @@ const createDiscreteScale = (domain) => { return (val) => (val === undefined || val === false) ? unknownColor : scale(val); }; -const valBetween = (x0, x1) => { - return x0 + 0.5*(x1-x0); +const createLegendBounds = (legendValues) => { + const valBetween = (x0, x1) => x0 + 0.5*(x1-x0); + const len = legendValues.length; + const legendBounds = {}; + legendBounds[legendValues[0]] = [0, valBetween(legendValues[0], legendValues[1])]; + for (let i = 1; i < len - 1; i++) { + legendBounds[legendValues[i]] = [valBetween(legendValues[i-1], legendValues[i]), valBetween(legendValues[i], legendValues[i+1])]; + } + legendBounds[legendValues[len-1]] = [valBetween(legendValues[len-2], legendValues[len-1]), 10000]; + return legendBounds; }; + export const calcColorScale = (colorBy, controls, tree, treeToo, metadata) => { // console.log(`calcColorScale for ${colorBy}. TreeToo? ${!!treeToo}`) let genotype; @@ -128,24 +129,42 @@ export const calcColorScale = (colorBy, controls, tree, treeToo, metadata) => { } else if (colorOptions && colorOptions[colorBy].type === "continuous") { // console.log("making a continuous color scale for ", colorBy) continuous = true; - const minMax = colorBy === "lbi" ? - [0, 0.7] : - getMinMaxFromTree(tree.nodes, treeTooNodes, colorBy, colorOptions[colorBy]); - colorScale = continuousScale(...minMax); - const spread = minMax[1] - minMax[0]; - const dp = spread > 5 ? 2 : 3; - legendValues = colorBy === "lbi" ? - [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7] : - genericDomain.map((d) => parseFloat((minMax[0] + d*spread).toFixed(dp))); - if (legendValues[0] === -0) legendValues[0] = 0; /* hack to avoid bugs */ - /* sort out ranges */ - const len = legendValues.length; - legendBounds = {}; - legendBounds[legendValues[0]] = [0, valBetween(legendValues[0], legendValues[1])]; - for (let i = 1; i < len - 1; i++) { - legendBounds[legendValues[i]] = [valBetween(legendValues[i-1], legendValues[i]), valBetween(legendValues[i], legendValues[i+1])]; + let minMax; + switch (colorBy) { + case "lbi": + minMax = [0, 0.7]; + break; + case "num_date": + minMax = getMinMaxFromTree(tree.nodes, treeTooNodes, colorBy, colorOptions[colorBy], true); + break; + default: + minMax = getMinMaxFromTree(tree.nodes, treeTooNodes, colorBy, colorOptions[colorBy], false); + } + + /* make the continuous scale */ + let domain = genericDomain.map((d) => minMax[0] + d * (minMax[1] - minMax[0])); + const cols = colors[9]; + /* for num_date we need to extend the domain to the root, even tho we don't want the colorScale to "focus" on that */ + if (colorBy === "num_date") { + let rootDate = tree.nodes[0].attr.num_date; + if (treeTooNodes && treeTooNodes[0].attr.num_date < rootDate) rootDate = treeTooNodes[0].attr.num_date; + domain = [rootDate].concat(domain); } - legendBounds[legendValues[len-1]] = [valBetween(legendValues[len-2], legendValues[len-1]), 10000]; + const scale = scaleLinear().domain(domain).range(cols); + colorScale = (val) => (val === undefined || val === false) ? unknownColor : scale(val); + + /* construct the legend values & their respective bounds */ + switch (colorBy) { + case "lbi": + legendValues = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]; + break; + default: + const spread = minMax[1] - minMax[0]; + const dp = spread > 5 ? 2 : 3; + legendValues = genericDomain.map((d) => parseFloat((minMax[0] + d*spread).toFixed(dp))); + } + if (legendValues[0] === -0) legendValues[0] = 0; /* hack to avoid bugs */ + legendBounds = createLegendBounds(legendValues); } } else { error = true; From b7e1760e3179a9c42b9b276217c41e6b5adb0727 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Mon, 23 Apr 2018 13:52:24 -0700 Subject: [PATCH 2/2] space colorScale according to tip sampling --- src/util/colorScale.js | 43 ++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/util/colorScale.js b/src/util/colorScale.js index a01c36f1c..1214a529d 100644 --- a/src/util/colorScale.js +++ b/src/util/colorScale.js @@ -10,9 +10,8 @@ import { setGenotype, orderOfGenotypeAppearance } from "./setGenotype"; const unknownColor = "#DDDDDD"; -const getMinMaxFromTree = (nodes, nodesToo, attr, tipsOnly) => { - let arr = nodesToo ? nodes.concat(nodesToo) : nodes.slice(); - if (tipsOnly) arr = arr.filter((n) => !n.hasChildren); +const getMinMaxFromTree = (nodes, nodesToo, attr) => { + const arr = nodesToo ? nodes.concat(nodesToo) : nodes.slice(); const vals = arr.map((n) => n.attr[attr]); vals.filter((n) => n !== undefined) .filter((item, i, ar) => ar.indexOf(item) === i); @@ -135,22 +134,35 @@ export const calcColorScale = (colorBy, controls, tree, treeToo, metadata) => { minMax = [0, 0.7]; break; case "num_date": - minMax = getMinMaxFromTree(tree.nodes, treeTooNodes, colorBy, colorOptions[colorBy], true); - break; + break; /* minMax not needed for num_date */ default: - minMax = getMinMaxFromTree(tree.nodes, treeTooNodes, colorBy, colorOptions[colorBy], false); + minMax = getMinMaxFromTree(tree.nodes, treeTooNodes, colorBy, colorOptions[colorBy]); } /* make the continuous scale */ - let domain = genericDomain.map((d) => minMax[0] + d * (minMax[1] - minMax[0])); - const cols = colors[9]; - /* for num_date we need to extend the domain to the root, even tho we don't want the colorScale to "focus" on that */ - if (colorBy === "num_date") { - let rootDate = tree.nodes[0].attr.num_date; - if (treeTooNodes && treeTooNodes[0].attr.num_date < rootDate) rootDate = treeTooNodes[0].attr.num_date; - domain = [rootDate].concat(domain); + let domain, range; + switch (colorBy) { + case "num_date": + /* we want the colorScale to "focus" on the tip dates, and be spaced according to sampling */ + let rootDate = tree.nodes[0].attr.num_date; + let vals = tree.nodes.filter((n) => !n.hasChildren).map((n) => n.attr.num_date); + if (treeTooNodes) { + if (treeTooNodes[0].attr.num_date < rootDate) rootDate = treeTooNodes[0].attr.num_date; + vals.concat(treeTooNodes.filter((n) => !n.hasChildren).map((n) => n.attr.num_date)); + } + vals = vals.sort(); + domain = [rootDate]; + const n = 10; + const spaceBetween = parseInt(vals.length / (n - 1), 10); + for (let i = 0; i < (n-1); i++) domain.push(vals[spaceBetween*i]); + domain.push(vals[vals.length-1]); + range = colors[n]; /* contains n+1 values */ + break; + default: + range = colors[9]; + domain = genericDomain.map((d) => minMax[0] + d * (minMax[1] - minMax[0])); } - const scale = scaleLinear().domain(domain).range(cols); + const scale = scaleLinear().domain(domain).range(range); colorScale = (val) => (val === undefined || val === false) ? unknownColor : scale(val); /* construct the legend values & their respective bounds */ @@ -158,6 +170,9 @@ export const calcColorScale = (colorBy, controls, tree, treeToo, metadata) => { case "lbi": legendValues = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]; break; + case "num_date": + legendValues = domain.slice(1); + break; default: const spread = minMax[1] - minMax[0]; const dp = spread > 5 ? 2 : 3;