From 3b2ebb648d8d7df7406de98ef8df3af5f9c2cf5b Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Wed, 14 Feb 2018 15:10:36 -0800 Subject: [PATCH 01/25] basic idea of new react-phylotree interface --- src/components/tree/phyloTree/change.js | 80 +++++++++++++++++++ src/components/tree/phyloTree/phyloTree.js | 4 +- src/components/tree/reactD3Interface/index.js | 14 ++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/components/tree/phyloTree/change.js diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js new file mode 100644 index 000000000..8cc3911bf --- /dev/null +++ b/src/components/tree/phyloTree/change.js @@ -0,0 +1,80 @@ + +const updateNodesWithNewData = (nodes, data) => { + console.log("update nodes with data for these keys:", Object.keys(data)); + let tmp = 0; + nodes.forEach((d, i) => { + d.update = false; + for (let key in data) { // eslint-disable-line + const val = data[key][i]; + if (val !== d[key]) { + d[key] = val; + d.update = true; + tmp++; + } + } + }); + console.log("changed ", tmp, " things") +}; + +const isValid = { + ".branch": ["stroke", "path"], + ".tip": ["stroke", "fill"] +}; + +const createUpdateCall = (treeElem, attrs, styles) => (selection) => { + [...attrs].filter((x) => isValid[treeElem].indexOf(x) !== -1) + .forEach((attrName) => { + selection.attr(attrName, (d) => d[attrName]); + }); + [...styles].filter((x) => isValid[treeElem].indexOf(x) !== -1) + .forEach((styleName) => { + selection.style(styleName, (d) => d[styleName]); + }); +}; + +export const change = function change({ + colorBy = false, + stroke = undefined, + fill = undefined +}) { + console.log("\n** change **\n\n"); + const data = {}; + + const toChange = { + elems: new Set(), + styles: new Set(), + attrs: new Set() + }; + + console.log(colorBy) + + /* the logic of converting what react is telling us to change + and what we actually change */ + if (colorBy) { + toChange.styles.add("stroke").add("fill"); + data.stroke = stroke; + data.fill = fill; + toChange.elems.add(".branch").add(".tip"); + } + + /* run calculations as needed */ + + /* change nodes as necessary */ + updateNodesWithNewData(this.nodes, data); + + /* calculate dt */ + const dt = 700; + + /* strip out toChange.elems that are not necesary */ + + /* svg change elements */ + toChange.elems.forEach((treeElem) => { + console.log("updating treeElem", treeElem); + const updateCall = createUpdateCall(treeElem, toChange.attrs, toChange.styles); + this.svg.selectAll(treeElem) + .filter((d) => d.update) + .transition().duration(dt) + .call(updateCall); + }); + +}; diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js index 2eddd8af6..b213e7cf6 100644 --- a/src/components/tree/phyloTree/phyloTree.js +++ b/src/components/tree/phyloTree/phyloTree.js @@ -3,6 +3,7 @@ import { max } from "d3-array"; import { scaleLinear } from "d3-scale"; import { defaultParams } from "./defaultParams"; import { addLeafCount, createChildrenAndParents } from "./helpers"; +import { change } from "./change"; /* PROTOTYPES */ import * as renderers from "./renderers"; @@ -43,12 +44,13 @@ const PhyloTree = function PhyloTree(reduxNodes) { this.yScale = scaleLinear(); this.zoomNode = this.nodes[0]; addLeafCount(this.nodes[0]); - /* debounced functions (AFAIK you can't define these as normal prototypes as they need "this") */ this.debouncedMapToScreen = _debounce(this.mapToScreen, this.params.mapToScreenDebounceTime, {leading: false, trailing: true, maxWait: this.params.mapToScreenDebounceTime}); }; +PhyloTree.prototype.change = change; + /* I N I T I A L R E N D E R E T C */ PhyloTree.prototype.render = renderers.render; PhyloTree.prototype.rerenderAllElements = renderers.rerenderAllElements; diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index b9323add0..6f0d8f0c4 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -55,6 +55,17 @@ export const salientPropChanges = (props, nextProps, tree) => { }; }; +const tempWrapper = (changes, nextProps, tree) => { + /* change salientPropChanges to output this! */ + if (changes.colorBy === true) { + tree.change({ + colorBy: nextProps.colorBy, + stroke: calcStrokeCols(nextProps.tree, nextProps.colorByConfidence, nextProps.colorBy), + fill: nextProps.tree.nodeColors.map((col) => rgb(col).brighter([0.65]).toString()) + }); + } +}; + /** * effect (in phyloTree) the necessary style + attr updates * @param {obj} changes see salientPropChanges above @@ -63,6 +74,9 @@ export const salientPropChanges = (props, nextProps, tree) => { * @return {null} causes side-effects via phyloTree object */ export const updateStylesAndAttrs = (that, changes, nextProps, tree) => { + + tempWrapper(changes, nextProps, tree); + changes.colorBy = false; /* the objects storing the changes to make to the tree */ const tipAttrToUpdate = {}; const tipStyleToUpdate = {}; From b2c541a86842b7932bf3e1ca8d29f63f11c43a82 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Wed, 14 Feb 2018 15:50:18 -0800 Subject: [PATCH 02/25] initial render displays colours without extra phylotree call --- src/components/tree/index.js | 9 +++++---- src/components/tree/phyloTree/renderers.js | 8 +++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/tree/index.js b/src/components/tree/index.js index 7b51b2dba..0537ea0dd 100644 --- a/src/components/tree/index.js +++ b/src/components/tree/index.js @@ -2,6 +2,7 @@ import React from "react"; import PropTypes from 'prop-types'; import { connect } from "react-redux"; import { select } from "d3-selection"; +import { rgb } from "d3-color"; import { ReactSVGPanZoom } from "react-svg-pan-zoom"; import Card from "../framework/card"; import Legend from "./legend/legend"; @@ -14,6 +15,7 @@ import TipClickedPanel from "./infoPanels/click"; import computeResponsive from "../../util/computeResponsive"; import { updateStylesAndAttrs, salientPropChanges } from "./reactD3Interface"; import * as callbacks from "./reactD3Interface/callbacks"; +import { calcStrokeCols } from "./treeHelpers"; /* this.props.tree contains the nodes etc used to build the PhyloTree @@ -80,8 +82,6 @@ class Tree extends React.Component { for (const k in changes) { // eslint-disable-line changes[k] = false; } - changes.colorBy = true; - updateStylesAndAttrs(this, changes, nextProps, tree); this.setState({tree}); if (this.Viewer) { this.Viewer.fitToViewer(); @@ -96,7 +96,6 @@ class Tree extends React.Component { componentDidMount() { const tree = this.makeTree(this.props); - updateStylesAndAttrs(this, {colorBy: true}, this.props, tree); this.setState({tree}); if (this.Viewer) { this.Viewer.fitToViewer(); @@ -156,7 +155,9 @@ class Tree extends React.Component { nextProps.tree.branchThickness, /* guarenteed to be in redux by now */ nextProps.tree.visibility, nextProps.temporalConfidence.on, /* drawConfidence? */ - nextProps.tree.vaccines + nextProps.tree.vaccines, + calcStrokeCols(nextProps.tree, nextProps.colorByConfidence, nextProps.colorBy), + nextProps.tree.nodeColors.map((col) => rgb(col).brighter([0.65]).toString()) ); return myTree; } diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index a40ed71a1..f0bbdc308 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -11,7 +11,7 @@ import { timerStart, timerEnd } from "../../../util/perf"; * @param visibility (OPTIONAL) -- array of "visible" or "hidden" * @return {null} */ -export const render = function render(svg, layout, distance, options, callbacks, branchThickness, visibility, drawConfidence, vaccines) { +export const render = function render(svg, layout, distance, options, callbacks, branchThickness, visibility, drawConfidence, vaccines, stroke, fill) { timerStart("phyloTree render()"); if (branchThickness) { this.nodes.forEach((d, i) => {d["stroke-width"] = branchThickness[i];}); @@ -27,6 +27,12 @@ export const render = function render(svg, layout, distance, options, callbacks, this.setLayout(layout); this.mapToScreen(); + /* set nodes stroke / fill */ + this.nodes.forEach((d, i) => { + d.stroke = stroke[i]; + d.fill = fill[i]; + }); + /* draw functions */ if (this.params.showGrid) this.addGrid(); if (this.params.branchLabels) this.drawBranches(); From b2883c7e46a70fe1527d24d94448c674a7045f8e Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Wed, 14 Feb 2018 15:57:12 -0800 Subject: [PATCH 03/25] visibility & tipRadii --- src/components/tree/index.js | 3 +- src/components/tree/phyloTree/change.js | 81 ++++++++++++------- src/components/tree/reactD3Interface/index.js | 80 ++++++++++-------- 3 files changed, 99 insertions(+), 65 deletions(-) diff --git a/src/components/tree/index.js b/src/components/tree/index.js index 0537ea0dd..b9bc909c4 100644 --- a/src/components/tree/index.js +++ b/src/components/tree/index.js @@ -13,7 +13,7 @@ import { mediumTransitionDuration } from "../../util/globals"; import HoverInfoPanel from "./infoPanels/hover"; import TipClickedPanel from "./infoPanels/click"; import computeResponsive from "../../util/computeResponsive"; -import { updateStylesAndAttrs, salientPropChanges } from "./reactD3Interface"; +import { updateStylesAndAttrs, salientPropChanges, changePhyloTreeViaPropsComparison } from "./reactD3Interface"; import * as callbacks from "./reactD3Interface/callbacks"; import { calcStrokeCols } from "./treeHelpers"; @@ -67,6 +67,7 @@ class Tree extends React.Component { works out what to update, based upon changes to redux.control */ let tree = this.state.tree; const changes = salientPropChanges(this.props, nextProps, tree); + changePhyloTreeViaPropsComparison(this.props, nextProps, tree); /* usefull for debugging: */ // console.log("CWRP Changes:", // Object.keys(changes).filter((k) => !!changes[k]).reduce((o, k) => { diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 8cc3911bf..9d12bf8c5 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -1,11 +1,11 @@ -const updateNodesWithNewData = (nodes, data) => { - console.log("update nodes with data for these keys:", Object.keys(data)); +const updateNodesWithNewData = (nodes, newNodeProps) => { + console.log("update nodes with data for these keys:", Object.keys(newNodeProps)); let tmp = 0; nodes.forEach((d, i) => { d.update = false; - for (let key in data) { // eslint-disable-line - const val = data[key][i]; + for (let key in newNodeProps) { // eslint-disable-line + const val = newNodeProps[key][i]; if (val !== d[key]) { d[key] = val; d.update = true; @@ -13,64 +13,83 @@ const updateNodesWithNewData = (nodes, data) => { } } }); - console.log("changed ", tmp, " things") + console.log("marking ", tmp, " nodes for update"); }; -const isValid = { +const validAttrs = ["r"]; +const validStyles = ["stroke", "fill", "visibility"]; +const validForThisElem = { ".branch": ["stroke", "path"], - ".tip": ["stroke", "fill"] + ".tip": ["stroke", "fill", "visibility", "r"] }; -const createUpdateCall = (treeElem, attrs, styles) => (selection) => { - [...attrs].filter((x) => isValid[treeElem].indexOf(x) !== -1) +const createUpdateCall = (treeElem, properties) => (selection) => { + [...properties].filter((x) => validAttrs.indexOf(x) !== -1) + .filter((x) => validForThisElem[treeElem].indexOf(x) !== -1) .forEach((attrName) => { selection.attr(attrName, (d) => d[attrName]); }); - [...styles].filter((x) => isValid[treeElem].indexOf(x) !== -1) - .forEach((styleName) => { - selection.style(styleName, (d) => d[styleName]); + [...properties].filter((x) => validStyles.indexOf(x) !== -1) + .filter((x) => validForThisElem[treeElem].indexOf(x) !== -1) + .forEach((attrName) => { + selection.style(attrName, (d) => d[attrName]); }); }; export const change = function change({ - colorBy = false, + /* booleans for what should be changed */ + changeColorBy = false, + changeVisibility = false, + changeTipRadii = false, + /* arrays of data (the same length as nodes) */ stroke = undefined, - fill = undefined + fill = undefined, + visibility = undefined, + tipRadii = undefined }) { console.log("\n** change **\n\n"); - const data = {}; - - const toChange = { - elems: new Set(), - styles: new Set(), - attrs: new Set() - }; - console.log(colorBy) + const elemsToUpdate = new Set(); + const nodePropsToModify = {}; /* modify the actual data structure */ + const svgPropsToUpdate = new Set(); /* modify the SVG */ /* the logic of converting what react is telling us to change and what we actually change */ - if (colorBy) { - toChange.styles.add("stroke").add("fill"); - data.stroke = stroke; - data.fill = fill; - toChange.elems.add(".branch").add(".tip"); + if (changeColorBy) { + /* check that fill & stroke are defined */ + svgPropsToUpdate.add("stroke").add("fill"); + nodePropsToModify.stroke = stroke; + nodePropsToModify.fill = fill; + elemsToUpdate.add(".branch").add(".tip"); + } + if (changeVisibility) { + /* check that visibility is not undefined */ + /* in the future we also change the branch visibility (after skeleton merge) */ + elemsToUpdate.add(".tip"); + svgPropsToUpdate.add("visibility"); + nodePropsToModify.visibility = visibility; } + if (changeTipRadii) { + elemsToUpdate.add(".tip"); + svgPropsToUpdate.add("r"); + nodePropsToModify.r = tipRadii; + } + /* run calculations as needed */ /* change nodes as necessary */ - updateNodesWithNewData(this.nodes, data); + updateNodesWithNewData(this.nodes, nodePropsToModify); /* calculate dt */ const dt = 700; - /* strip out toChange.elems that are not necesary */ + /* strip out elemsToUpdate that are not necesary! */ /* svg change elements */ - toChange.elems.forEach((treeElem) => { + elemsToUpdate.forEach((treeElem) => { console.log("updating treeElem", treeElem); - const updateCall = createUpdateCall(treeElem, toChange.attrs, toChange.styles); + const updateCall = createUpdateCall(treeElem, svgPropsToUpdate); this.svg.selectAll(treeElem) .filter((d) => d.update) .transition().duration(dt) diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index 6f0d8f0c4..964d87001 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -12,11 +12,11 @@ import { viewEntireTree } from "./callbacks"; export const salientPropChanges = (props, nextProps, tree) => { const dataInFlux = !nextProps.tree.loaded; const newData = tree === null && nextProps.tree.loaded; - const visibility = !!nextProps.tree.visibilityVersion && props.tree.visibilityVersion !== nextProps.tree.visibilityVersion; - const tipRadii = !!nextProps.tree.tipRadiiVersion && props.tree.tipRadiiVersion !== nextProps.tree.tipRadiiVersion; - const colorBy = !!nextProps.tree.nodeColorsVersion && - (props.tree.nodeColorsVersion !== nextProps.tree.nodeColorsVersion || - nextProps.colorByConfidence !== props.colorByConfidence); + // const visibility = !!nextProps.tree.visibilityVersion && props.tree.visibilityVersion !== nextProps.tree.visibilityVersion; + // const tipRadii = !!nextProps.tree.tipRadiiVersion && props.tree.tipRadiiVersion !== nextProps.tree.tipRadiiVersion; + // const colorBy = !!nextProps.tree.nodeColorsVersion && + // (props.tree.nodeColorsVersion !== nextProps.tree.nodeColorsVersion || + // nextProps.colorByConfidence !== props.colorByConfidence); const branchThickness = props.tree.branchThicknessVersion !== nextProps.tree.branchThicknessVersion; const layout = props.layout !== nextProps.layout; const distanceMeasure = props.distanceMeasure !== nextProps.distanceMeasure; @@ -39,9 +39,9 @@ export const salientPropChanges = (props, nextProps, tree) => { return { dataInFlux, newData, - visibility, - tipRadii, - colorBy, + visibility: false, + tipRadii: false, + colorBy: false, layout, distanceMeasure, branchThickness, @@ -55,15 +55,32 @@ export const salientPropChanges = (props, nextProps, tree) => { }; }; -const tempWrapper = (changes, nextProps, tree) => { - /* change salientPropChanges to output this! */ - if (changes.colorBy === true) { - tree.change({ - colorBy: nextProps.colorBy, - stroke: calcStrokeCols(nextProps.tree, nextProps.colorByConfidence, nextProps.colorBy), - fill: nextProps.tree.nodeColors.map((col) => rgb(col).brighter([0.65]).toString()) - }); +export const changePhyloTreeViaPropsComparison = (props, nextProps, tree) => { + console.log('changePhyloTreeViaPropsComparison') + const args = {}; + + /* colorBy change? */ + if (!!nextProps.tree.nodeColorsVersion && + (props.tree.nodeColorsVersion !== nextProps.tree.nodeColorsVersion || + nextProps.colorByConfidence !== props.colorByConfidence)) { + args.changeColorBy = true; + args.stroke = calcStrokeCols(nextProps.tree, nextProps.colorByConfidence, nextProps.colorBy); + args.fill = nextProps.tree.nodeColors.map((col) => rgb(col).brighter([0.65]).toString()); + } + + /* visibility */ + if (!!nextProps.tree.visibilityVersion && props.tree.visibilityVersion !== nextProps.tree.visibilityVersion) { + args.changeVisibility = true; + args.visibility = nextProps.tree.visibility; + } + + /* tip radii */ + if (!!nextProps.tree.tipRadiiVersion && props.tree.tipRadiiVersion !== nextProps.tree.tipRadiiVersion) { + args.changeTipRadii = true; + args.tipRadii = nextProps.tree.tipRadii; } + + tree.change(args); }; /** @@ -74,29 +91,26 @@ const tempWrapper = (changes, nextProps, tree) => { * @return {null} causes side-effects via phyloTree object */ export const updateStylesAndAttrs = (that, changes, nextProps, tree) => { - - tempWrapper(changes, nextProps, tree); - changes.colorBy = false; /* the objects storing the changes to make to the tree */ const tipAttrToUpdate = {}; const tipStyleToUpdate = {}; const branchAttrToUpdate = {}; const branchStyleToUpdate = {}; - if (changes.visibility) { - tipStyleToUpdate["visibility"] = nextProps.tree.visibility; - } - if (changes.tipRadii) { - tipAttrToUpdate["r"] = nextProps.tree.tipRadii; - } - if (changes.colorBy) { - tipStyleToUpdate["fill"] = nextProps.tree.nodeColors.map((col) => { - return rgb(col).brighter([0.65]).toString(); - }); - const branchStrokes = calcStrokeCols(nextProps.tree, nextProps.colorByConfidence, nextProps.colorBy); - branchStyleToUpdate["stroke"] = branchStrokes; - tipStyleToUpdate["stroke"] = branchStrokes; - } + // if (changes.visibility) { + // tipStyleToUpdate["visibility"] = nextProps.tree.visibility; + // } + // if (changes.tipRadii) { + // tipAttrToUpdate["r"] = nextProps.tree.tipRadii; + // } + // if (changes.colorBy) { + // tipStyleToUpdate["fill"] = nextProps.tree.nodeColors.map((col) => { + // return rgb(col).brighter([0.65]).toString(); + // }); + // const branchStrokes = calcStrokeCols(nextProps.tree, nextProps.colorByConfidence, nextProps.colorBy); + // branchStyleToUpdate["stroke"] = branchStrokes; + // tipStyleToUpdate["stroke"] = branchStrokes; + // } if (changes.branchThickness) { // console.log("branch width change detected - update branch stroke-widths") branchStyleToUpdate["stroke-width"] = nextProps.tree.branchThickness; From 7e87e5ba6b2bd5ce4788f2b73f7701dc0bc3658c Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Wed, 14 Feb 2018 16:14:24 -0800 Subject: [PATCH 04/25] branch thickness & transition logic --- src/components/tree/phyloTree/change.js | 47 ++++++++++++++----- src/components/tree/phyloTree/renderers.js | 1 + src/components/tree/reactD3Interface/index.js | 42 ++++++++++------- 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 9d12bf8c5..90eef3a98 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -16,10 +16,10 @@ const updateNodesWithNewData = (nodes, newNodeProps) => { console.log("marking ", tmp, " nodes for update"); }; -const validAttrs = ["r"]; -const validStyles = ["stroke", "fill", "visibility"]; +const validAttrs = ["r", "cx", "cy", "d"]; +const validStyles = ["stroke", "fill", "visibility", "stroke-width", "opacity"]; const validForThisElem = { - ".branch": ["stroke", "path"], + ".branch": ["stroke", "path", "stroke-width"], ".tip": ["stroke", "fill", "visibility", "r"] }; @@ -41,11 +41,13 @@ export const change = function change({ changeColorBy = false, changeVisibility = false, changeTipRadii = false, + changeBranchThickness = false, /* arrays of data (the same length as nodes) */ stroke = undefined, fill = undefined, visibility = undefined, - tipRadii = undefined + tipRadii = undefined, + branchThickness = undefined }) { console.log("\n** change **\n\n"); @@ -74,6 +76,11 @@ export const change = function change({ svgPropsToUpdate.add("r"); nodePropsToModify.r = tipRadii; } + if (changeBranchThickness) { + elemsToUpdate.add(".branch"); + svgPropsToUpdate.add("stroke-width"); + nodePropsToModify["stroke-width"] = branchThickness; + } /* run calculations as needed */ @@ -81,19 +88,37 @@ export const change = function change({ /* change nodes as necessary */ updateNodesWithNewData(this.nodes, nodePropsToModify); - /* calculate dt */ - const dt = 700; + /* calculations that depend on node data appended above */ + if (svgPropsToUpdate.has("stroke-width")) { + this.mapToScreen(); + } + /* calculate dt */ + const idealTransitionTime = 500; + let transitionTime = idealTransitionTime; + if ((Date.now() - this.timeLastRenderRequested) < idealTransitionTime * 2) { + transitionTime = 0; + } /* strip out elemsToUpdate that are not necesary! */ /* svg change elements */ + + /* there's different methods here - transition everything, snap, + hide-some-transition-some-show-all, etc etc */ elemsToUpdate.forEach((treeElem) => { - console.log("updating treeElem", treeElem); + console.log("updating treeElem", treeElem, "transition:", transitionTime); const updateCall = createUpdateCall(treeElem, svgPropsToUpdate); - this.svg.selectAll(treeElem) - .filter((d) => d.update) - .transition().duration(dt) - .call(updateCall); + if (transitionTime) { + this.svg.selectAll(treeElem) + .filter((d) => d.update) + .transition().duration(transitionTime) + .call(updateCall); + } else { + this.svg.selectAll(treeElem) + .filter((d) => d.update) + .call(updateCall); + } }); + this.timeLastRenderRequested = Date.now(); }; diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index f0bbdc308..ebc901c66 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -66,6 +66,7 @@ export const render = function render(svg, layout, distance, options, callbacks, if (drawConfidence) { this.drawConfidence(); } + this.timeLastRenderRequested = Date.now(); timerEnd("phyloTree render()"); }; diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index 964d87001..bfd436dc4 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -17,7 +17,7 @@ export const salientPropChanges = (props, nextProps, tree) => { // const colorBy = !!nextProps.tree.nodeColorsVersion && // (props.tree.nodeColorsVersion !== nextProps.tree.nodeColorsVersion || // nextProps.colorByConfidence !== props.colorByConfidence); - const branchThickness = props.tree.branchThicknessVersion !== nextProps.tree.branchThicknessVersion; + // const branchThickness = props.tree.branchThicknessVersion !== nextProps.tree.branchThicknessVersion; const layout = props.layout !== nextProps.layout; const distanceMeasure = props.distanceMeasure !== nextProps.distanceMeasure; const rerenderAllElements = nextProps.quickdraw === false && props.quickdraw === true; @@ -44,7 +44,7 @@ export const salientPropChanges = (props, nextProps, tree) => { colorBy: false, layout, distanceMeasure, - branchThickness, + branchThickness: false, branchTransitionTime, tipTransitionTime, branchLabels, @@ -80,6 +80,12 @@ export const changePhyloTreeViaPropsComparison = (props, nextProps, tree) => { args.tipRadii = nextProps.tree.tipRadii; } + /* branch thickness (stroke-width) */ + if (props.tree.branchThicknessVersion !== nextProps.tree.branchThicknessVersion) { + args.changeBranchThickness = true; + args.branchThickness = nextProps.tree.branchThickness; + } + tree.change(args); }; @@ -92,10 +98,10 @@ export const changePhyloTreeViaPropsComparison = (props, nextProps, tree) => { */ export const updateStylesAndAttrs = (that, changes, nextProps, tree) => { /* the objects storing the changes to make to the tree */ - const tipAttrToUpdate = {}; - const tipStyleToUpdate = {}; - const branchAttrToUpdate = {}; - const branchStyleToUpdate = {}; + // const tipAttrToUpdate = {}; + // const tipStyleToUpdate = {}; + // const branchAttrToUpdate = {}; + // const branchStyleToUpdate = {}; // if (changes.visibility) { // tipStyleToUpdate["visibility"] = nextProps.tree.visibility; @@ -111,19 +117,19 @@ export const updateStylesAndAttrs = (that, changes, nextProps, tree) => { // branchStyleToUpdate["stroke"] = branchStrokes; // tipStyleToUpdate["stroke"] = branchStrokes; // } - if (changes.branchThickness) { - // console.log("branch width change detected - update branch stroke-widths") - branchStyleToUpdate["stroke-width"] = nextProps.tree.branchThickness; - } + // if (changes.branchThickness) { + // // console.log("branch width change detected - update branch stroke-widths") + // branchStyleToUpdate["stroke-width"] = nextProps.tree.branchThickness; + // } /* implement style * attr changes */ - if (Object.keys(branchAttrToUpdate).length || Object.keys(branchStyleToUpdate).length) { - // console.log("applying branch attr", Object.keys(branchAttrToUpdate), "branch style changes", Object.keys(branchStyleToUpdate)) - tree.updateMultipleArray(".branch", branchAttrToUpdate, branchStyleToUpdate, changes.branchTransitionTime, changes.quickdraw); - } - if (Object.keys(tipAttrToUpdate).length || Object.keys(tipStyleToUpdate).length) { - // console.log("applying tip attr", Object.keys(tipAttrToUpdate), "tip style changes", Object.keys(tipStyleToUpdate)) - tree.updateMultipleArray(".tip", tipAttrToUpdate, tipStyleToUpdate, changes.tipTransitionTime, changes.quickdraw); - } + // if (Object.keys(branchAttrToUpdate).length || Object.keys(branchStyleToUpdate).length) { + // // console.log("applying branch attr", Object.keys(branchAttrToUpdate), "branch style changes", Object.keys(branchStyleToUpdate)) + // tree.updateMultipleArray(".branch", branchAttrToUpdate, branchStyleToUpdate, changes.branchTransitionTime, changes.quickdraw); + // } + // if (Object.keys(tipAttrToUpdate).length || Object.keys(tipStyleToUpdate).length) { + // // console.log("applying tip attr", Object.keys(tipAttrToUpdate), "tip style changes", Object.keys(tipStyleToUpdate)) + // tree.updateMultipleArray(".tip", tipAttrToUpdate, tipStyleToUpdate, changes.tipTransitionTime, changes.quickdraw); + // } if (changes.layout) { /* swap layouts */ tree.updateLayout(nextProps.layout, mediumTransitionDuration); From ad2ce97d5b20adb6745f54fa563c6a38f1c20040 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Thu, 15 Feb 2018 17:08:31 -0800 Subject: [PATCH 05/25] updateDistance --- src/components/tree/phyloTree/change.js | 214 ++++++++++++++---- .../tree/phyloTree/generalUpdates.js | 17 +- src/components/tree/phyloTree/phyloTree.js | 4 +- src/components/tree/reactD3Interface/index.js | 10 +- 4 files changed, 181 insertions(+), 64 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 90eef3a98..948205cc9 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -16,32 +16,155 @@ const updateNodesWithNewData = (nodes, newNodeProps) => { console.log("marking ", tmp, " nodes for update"); }; -const validAttrs = ["r", "cx", "cy", "d"]; -const validStyles = ["stroke", "fill", "visibility", "stroke-width", "opacity"]; -const validForThisElem = { - ".branch": ["stroke", "path", "stroke-width"], - ".tip": ["stroke", "fill", "visibility", "r"] +const genericAttrs = { + ".branch": new Set(), + ".tip": new Set(["r"]) +}; +const genericStyles = { + ".branch": new Set(["stroke", "stroke-width"]), + ".tip": new Set(["fill", "visibility", "stroke"]) +}; +const functionAttrs = { + ".tip": { + cx: (d) => d.xTip, + cy: (d) => d.yTip + }, + ".vaccineCross": { + d: (d) => d.vaccineCross + }, + ".vaccineDottedLine": { + d: (d) => d.vaccineLine + }, + ".conf": { + d: (d) => d.confLine + } +}; +const functionStyles = { + ".vaccineDottedLine": { + opacity: (d) => d.that.distance === "num_date" ? 1 : 0 + } }; +/* Returns a function which can be called as part of a D3 chain in order to modify + * the appropriate attrs & styles for this treeElem + */ const createUpdateCall = (treeElem, properties) => (selection) => { - [...properties].filter((x) => validAttrs.indexOf(x) !== -1) - .filter((x) => validForThisElem[treeElem].indexOf(x) !== -1) - .forEach((attrName) => { - selection.attr(attrName, (d) => d[attrName]); - }); - [...properties].filter((x) => validStyles.indexOf(x) !== -1) - .filter((x) => validForThisElem[treeElem].indexOf(x) !== -1) - .forEach((attrName) => { - selection.style(attrName, (d) => d[attrName]); - }); + /* generics - those which are simply taken from the node! */ + if (genericAttrs[treeElem]) { + [...properties].filter((x) => genericAttrs[treeElem].has(x)) + .forEach((attrName) => { + console.log("\tadding node attr", attrName); + selection.attr(attrName, (d) => d[attrName]); + }); + } + if (genericStyles[treeElem]) { + [...properties].filter((x) => genericStyles[treeElem].has(x)) + .forEach((styleName) => { + console.log("\tadding node style", styleName); + selection.style(styleName, (d) => d[styleName]); + }); + } + /* more complicated, functions defined above */ + if (functionAttrs[treeElem]) { + [...properties].filter((x) => functionAttrs[treeElem][x]) + .forEach((attrName) => { + console.log("\tadding fn for attr", attrName); + selection.attr(attrName, functionAttrs[treeElem][attrName]); + }); + } + if (functionStyles[treeElem]) { + [...properties].filter((x) => functionStyles[treeElem][x]) + .forEach((styleName) => { + console.log("\tadding fn for style ", styleName); + selection.attr(styleName, functionStyles[treeElem][styleName]); + }); + } + +}; + +const genericSelectAndModify = (svg, treeElem, updateCall, transitionTime) => { + if (transitionTime) { + console.log("general svg update for", treeElem); + svg.selectAll(treeElem) + .filter((d) => d.update) + .transition().duration(transitionTime) + .call(updateCall); + } else { + console.log("general svg update for", treeElem, " (NO TRANSITION)"); + svg.selectAll(treeElem) + .filter((d) => d.update) + .call(updateCall); + } +}; + +export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime) { + /* elements are often treated differently (of course!) */ + let updateCall; + + /* order is respected */ + const generals = [".tip", ".vaccineDottedLine", ".vaccineCross", ".conf", ".branch"]; + + /* treat stem / branch different, but combine normal .branch calls */ + if (elemsToUpdate.has(".branch.S") || elemsToUpdate.has(".branch.T")) { + let branchOnlyUpdate = false; + if (elemsToUpdate.has(".branch")) { + generals.splice(generals.indexOf(".branch"), 1); // remove .branch from generals + branchOnlyUpdate = createUpdateCall(".branch", svgPropsToUpdate); + } + const generateCombinedUpdateCall = (subClass) => (selection) => { + if (branchOnlyUpdate) { + branchOnlyUpdate(selection); + } + const subClassIdx = subClass === ".S" ? 0 : 1; /* is the path stored at d.branch[0] or d.branch[1] */ + selection.attr("d", (d) => d.branch[subClassIdx]); + }; + if (elemsToUpdate.has(".branch.S")) { + updateCall = generateCombinedUpdateCall(".S"); + genericSelectAndModify(this.svg, ".branch.S", updateCall, transitionTime); + } + if (elemsToUpdate.has(".branch.T")) { + updateCall = generateCombinedUpdateCall(".T"); + genericSelectAndModify(this.svg, ".branch.T", updateCall, transitionTime); + } + } + + generals.forEach((el) => { + if (elemsToUpdate.has(el)) { + updateCall = createUpdateCall(el, svgPropsToUpdate); + genericSelectAndModify(this.svg, el, updateCall, transitionTime); + } + }); + + /* non general */ + if (elemsToUpdate.has('.branchLabel')) { + this.updateBranchLabels(); + } + if (elemsToUpdate.has('.tipLabel')) { + this.updateTipLabels(); + } + if (elemsToUpdate.has('.grid')) { + if (this.grid && this.layout !== "unrooted") this.addGrid(this.layout); + else this.hideGrid(); + } + if (elemsToUpdate.has('.regression')) { + this.svg.selectAll(".regression").remove(); + if (this.layout === "clock" && this.distance === "num_date") this.drawRegression(); + } + }; +/* the main interface to changing a currently rendered tree. + * simply call change and tell it what should be changed. + * try to do a single change() call with as many things as possible in it + */ export const change = function change({ /* booleans for what should be changed */ changeColorBy = false, changeVisibility = false, changeTipRadii = false, changeBranchThickness = false, + /* change these things to this state */ + newDistance = undefined, /* arrays of data (the same length as nodes) */ stroke = undefined, fill = undefined, @@ -55,19 +178,26 @@ export const change = function change({ const nodePropsToModify = {}; /* modify the actual data structure */ const svgPropsToUpdate = new Set(); /* modify the SVG */ + /* calculate dt */ + const idealTransitionTime = 500; + let transitionTime = idealTransitionTime; + if ((Date.now() - this.timeLastRenderRequested) < idealTransitionTime * 2) { + transitionTime = 0; + } + /* the logic of converting what react is telling us to change - and what we actually change */ + and what SVG elements, node properties, svg props we actually change */ if (changeColorBy) { /* check that fill & stroke are defined */ + elemsToUpdate.add(".branch").add(".tip"); svgPropsToUpdate.add("stroke").add("fill"); nodePropsToModify.stroke = stroke; nodePropsToModify.fill = fill; - elemsToUpdate.add(".branch").add(".tip"); } if (changeVisibility) { /* check that visibility is not undefined */ /* in the future we also change the branch visibility (after skeleton merge) */ - elemsToUpdate.add(".tip"); + elemsToUpdate.add(".tip") svgPropsToUpdate.add("visibility"); nodePropsToModify.visibility = visibility; } @@ -81,44 +211,30 @@ export const change = function change({ svgPropsToUpdate.add("stroke-width"); nodePropsToModify["stroke-width"] = branchThickness; } + if (newDistance) { + elemsToUpdate.add(".tip").add(".branch.S").add(".branch.T"); + elemsToUpdate.add(".vaccineCross").add(".vaccineDottedLine").add(".conf"); + elemsToUpdate.add('.branchLabel').add('.tipLabel'); + elemsToUpdate.add(".grid").add(".regression"); + svgPropsToUpdate.add("cx").add("cy").add("d").add("opacity"); + } - - /* run calculations as needed */ - - /* change nodes as necessary */ + /* change the requested properties on the nodes */ updateNodesWithNewData(this.nodes, nodePropsToModify); - /* calculations that depend on node data appended above */ - if (svgPropsToUpdate.has("stroke-width")) { + /* run calculations as needed */ + /* distance */ + if (newDistance) this.setDistance(newDistance); + /* layout (must run after distance) */ + if (newDistance) this.setLayout(this.layout); + /* mapToScreen (must run after "stroke-width" has been set on nodes) */ + if (svgPropsToUpdate.has(["stroke-width"]) || + newDistance) { this.mapToScreen(); } - /* calculate dt */ - const idealTransitionTime = 500; - let transitionTime = idealTransitionTime; - if ((Date.now() - this.timeLastRenderRequested) < idealTransitionTime * 2) { - transitionTime = 0; - } - /* strip out elemsToUpdate that are not necesary! */ - /* svg change elements */ + this.modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime); - /* there's different methods here - transition everything, snap, - hide-some-transition-some-show-all, etc etc */ - elemsToUpdate.forEach((treeElem) => { - console.log("updating treeElem", treeElem, "transition:", transitionTime); - const updateCall = createUpdateCall(treeElem, svgPropsToUpdate); - if (transitionTime) { - this.svg.selectAll(treeElem) - .filter((d) => d.update) - .transition().duration(transitionTime) - .call(updateCall); - } else { - this.svg.selectAll(treeElem) - .filter((d) => d.update) - .call(updateCall); - } - }); this.timeLastRenderRequested = Date.now(); - }; diff --git a/src/components/tree/phyloTree/generalUpdates.js b/src/components/tree/phyloTree/generalUpdates.js index 24deb868e..87d6d7da5 100644 --- a/src/components/tree/phyloTree/generalUpdates.js +++ b/src/components/tree/phyloTree/generalUpdates.js @@ -54,6 +54,7 @@ export const updateStyleOrAttributeArray = function updateStyleOrAttributeArray( * @return null */ export const updateDistance = function updateDistance(attr, dt) { + console.warn("updateDistance is deprecated. use phylotree.change instead.") this.setDistance(attr); this.setLayout(this.layout); this.mapToScreen(); @@ -88,6 +89,7 @@ export const updateLayout = function updateLayout(layout, dt) { * @return {[type]} */ export const updateGeometry = function updateGeometry(dt) { + console.warn("updateGeometry is deprecated. use phylotree.change instead.") timerStart("updateGeometry"); this.svg.selectAll(".tip") .filter((d) => d.update) @@ -100,17 +102,10 @@ export const updateGeometry = function updateGeometry(dt) { this.svg.selectAll(".vaccineCross") .transition().duration(dt) .attr("d", (dd) => dd.vaccineCross); - if (this.distance === "num_date") { - this.svg.selectAll(".vaccineDottedLine") - .transition().duration(dt) - .style("opacity", 1) - .attr("d", (dd) => dd.vaccineLine); - } else { - this.svg.selectAll(".vaccineDottedLine") - .transition().duration(dt) - .style("opacity", 0) - .attr("d", (dd) => dd.vaccineLine); - } + this.svg.selectAll(".vaccineDottedLine") + .transition().duration(dt) + .style("opacity", () => this.distance === "num_date" ? 1 : 0) + .attr("d", (dd) => dd.vaccineLine); } const branchEls = [".S", ".T"]; diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js index b213e7cf6..56dc9b903 100644 --- a/src/components/tree/phyloTree/phyloTree.js +++ b/src/components/tree/phyloTree/phyloTree.js @@ -3,7 +3,7 @@ import { max } from "d3-array"; import { scaleLinear } from "d3-scale"; import { defaultParams } from "./defaultParams"; import { addLeafCount, createChildrenAndParents } from "./helpers"; -import { change } from "./change"; +import { change, modifySVG } from "./change"; /* PROTOTYPES */ import * as renderers from "./renderers"; @@ -28,6 +28,7 @@ const PhyloTree = function PhyloTree(reduxNodes) { -- reduxNodes[i].shell = this.nodes[i] */ this.nodes = reduxNodes.map((d) => { const phyloNode = { + that: this, n: d, /* a back link to the redux node */ x: 0, y: 0, @@ -50,6 +51,7 @@ const PhyloTree = function PhyloTree(reduxNodes) { }; PhyloTree.prototype.change = change; +PhyloTree.prototype.modifySVG = modifySVG; /* I N I T I A L R E N D E R E T C */ PhyloTree.prototype.render = renderers.render; diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index bfd436dc4..60a7ea38a 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -19,7 +19,7 @@ export const salientPropChanges = (props, nextProps, tree) => { // nextProps.colorByConfidence !== props.colorByConfidence); // const branchThickness = props.tree.branchThicknessVersion !== nextProps.tree.branchThicknessVersion; const layout = props.layout !== nextProps.layout; - const distanceMeasure = props.distanceMeasure !== nextProps.distanceMeasure; + // const distanceMeasure = props.distanceMeasure !== nextProps.distanceMeasure; const rerenderAllElements = nextProps.quickdraw === false && props.quickdraw === true; const resetViewToRoot = props.tree.idxOfInViewRootNode !== 0 && nextProps.tree.idxOfInViewRootNode === 0; /* branch labels & confidence use 0: no change, 1: turn off, 2: turn on */ @@ -43,7 +43,7 @@ export const salientPropChanges = (props, nextProps, tree) => { tipRadii: false, colorBy: false, layout, - distanceMeasure, + distanceMeasure: false, branchThickness: false, branchTransitionTime, tipTransitionTime, @@ -86,6 +86,10 @@ export const changePhyloTreeViaPropsComparison = (props, nextProps, tree) => { args.branchThickness = nextProps.tree.branchThickness; } + if (props.distanceMeasure !== nextProps.distanceMeasure) { + args.newDistance = nextProps.distanceMeasure; + } + tree.change(args); }; @@ -102,7 +106,7 @@ export const updateStylesAndAttrs = (that, changes, nextProps, tree) => { // const tipStyleToUpdate = {}; // const branchAttrToUpdate = {}; // const branchStyleToUpdate = {}; - + return; // if (changes.visibility) { // tipStyleToUpdate["visibility"] = nextProps.tree.visibility; // } From e0938da85e22d690f62989a73c2c327b77a939df Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Thu, 15 Feb 2018 18:02:12 -0800 Subject: [PATCH 06/25] confidence intervals (buggy) --- src/components/tree/phyloTree/change.js | 37 +++++++++++++++++-- src/components/tree/phyloTree/confidence.js | 15 +++++--- .../tree/phyloTree/generalUpdates.js | 5 +++ src/components/tree/phyloTree/renderers.js | 1 + src/components/tree/reactD3Interface/index.js | 13 +++++++ 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 948205cc9..5981db0e2 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -1,3 +1,5 @@ +import { calcConfidenceWidth } from "./confidence"; + const updateNodesWithNewData = (nodes, newNodeProps) => { console.log("update nodes with data for these keys:", Object.keys(newNodeProps)); @@ -42,6 +44,10 @@ const functionAttrs = { const functionStyles = { ".vaccineDottedLine": { opacity: (d) => d.that.distance === "num_date" ? 1 : 0 + }, + ".conf": { + stroke: (d) => d.stroke, + "stroke-width": calcConfidenceWidth } }; @@ -97,12 +103,12 @@ const genericSelectAndModify = (svg, treeElem, updateCall, transitionTime) => { } }; -export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime) { +export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime, extras) { /* elements are often treated differently (of course!) */ let updateCall; /* order is respected */ - const generals = [".tip", ".vaccineDottedLine", ".vaccineCross", ".conf", ".branch"]; + const generals = [".tip", ".vaccineDottedLine", ".vaccineCross", ".branch"]; /* treat stem / branch different, but combine normal .branch calls */ if (elemsToUpdate.has(".branch.S") || elemsToUpdate.has(".branch.T")) { @@ -151,6 +157,23 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra if (this.layout === "clock" && this.distance === "num_date") this.drawRegression(); } + /* confidences are hard */ + if (extras.removeConfidences) { + this.removeConfidence(transitionTime); + } else if (extras.showConfidences) { + this.drawConfidence(transitionTime); + } else if (elemsToUpdate.has(".conf")) { + if (this.confidencesInSVG) { + if (this.layout === "rect" && this.distance === "num_date") { + updateCall = createUpdateCall(".conf", svgPropsToUpdate); + genericSelectAndModify(this.svg, ".conf", updateCall, transitionTime); + } else { + this.removeConfidence(transitionTime); + } + } else { + console.warn("can't update confidences as they don't exist in the SVG"); + } + } }; /* the main interface to changing a currently rendered tree. @@ -163,6 +186,8 @@ export const change = function change({ changeVisibility = false, changeTipRadii = false, changeBranchThickness = false, + showConfidences = false, + removeConfidences = false, /* change these things to this state */ newDistance = undefined, /* arrays of data (the same length as nodes) */ @@ -190,6 +215,7 @@ export const change = function change({ if (changeColorBy) { /* check that fill & stroke are defined */ elemsToUpdate.add(".branch").add(".tip"); + if (this.confidencesInSVG) elemsToUpdate.add(".conf"); svgPropsToUpdate.add("stroke").add("fill"); nodePropsToModify.stroke = stroke; nodePropsToModify.fill = fill; @@ -197,7 +223,7 @@ export const change = function change({ if (changeVisibility) { /* check that visibility is not undefined */ /* in the future we also change the branch visibility (after skeleton merge) */ - elemsToUpdate.add(".tip") + elemsToUpdate.add(".tip"); svgPropsToUpdate.add("visibility"); nodePropsToModify.visibility = visibility; } @@ -208,6 +234,7 @@ export const change = function change({ } if (changeBranchThickness) { elemsToUpdate.add(".branch"); + if (this.confidencesInSVG) elemsToUpdate.add(".conf"); svgPropsToUpdate.add("stroke-width"); nodePropsToModify["stroke-width"] = branchThickness; } @@ -219,6 +246,7 @@ export const change = function change({ svgPropsToUpdate.add("cx").add("cy").add("d").add("opacity"); } + /* change the requested properties on the nodes */ updateNodesWithNewData(this.nodes, nodePropsToModify); @@ -234,7 +262,8 @@ export const change = function change({ } /* svg change elements */ - this.modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime); + const extras = {removeConfidences, showConfidences}; + this.modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime, extras); this.timeLastRenderRequested = Date.now(); }; diff --git a/src/components/tree/phyloTree/confidence.js b/src/components/tree/phyloTree/confidence.js index 668555ca2..ed1ca392f 100644 --- a/src/components/tree/phyloTree/confidence.js +++ b/src/components/tree/phyloTree/confidence.js @@ -1,5 +1,6 @@ export const removeConfidence = function removeConfidence(dt) { + this.confidencesInSVG = false; if (dt) { this.svg.selectAll(".conf") .transition().duration(dt) @@ -8,12 +9,12 @@ export const removeConfidence = function removeConfidence(dt) { } else { this.svg.selectAll(".conf").remove(); } - // this.props.confidence = false; }; export const drawConfidence = function drawConfidence(dt) { // this.removeConfidence(); // just in case // console.log("drawing:", this.svg.selectAll(".conf")) + this.confidencesInSVG = true; if (dt) { this.confidence = this.svg.append("g").selectAll(".conf") .data(this.nodes) @@ -31,9 +32,10 @@ export const drawConfidence = function drawConfidence(dt) { // this.props.confidence = true; }; -const confidenceWidth = (el) => +export const calcConfidenceWidth = (el) => el["stroke-width"] === 1 ? 0 : - el["stroke-width"] > 6 ? el["stroke-width"] + 6 : el["stroke-width"] * 2; + el["stroke-width"] > 6 ? el["stroke-width"] + 6 : + el["stroke-width"] * 2; export const drawSingleCI = function drawSingleCI(selection, opacity) { selection.append("path") @@ -43,19 +45,20 @@ export const drawSingleCI = function drawSingleCI(selection, opacity) { .style("stroke", (d) => d.stroke || "#888") .style("opacity", opacity) .style("fill", "none") - .style("stroke-width", confidenceWidth); + .style("stroke-width", calcConfidenceWidth); }; export const updateConfidence = function updateConfidence(dt) { + console.warn("updateConfidence is deprecated."); if (dt) { this.svg.selectAll(".conf") .transition().duration(dt) .style("stroke", (el) => el.stroke) - .style("stroke-width", confidenceWidth); + .style("stroke-width", calcConfidenceWidth); } else { this.svg.selectAll(".conf") .style("stroke", (el) => el.stroke) - .style("stroke-width", confidenceWidth); + .style("stroke-width", calcConfidenceWidth); } }; diff --git a/src/components/tree/phyloTree/generalUpdates.js b/src/components/tree/phyloTree/generalUpdates.js index 87d6d7da5..f4e575771 100644 --- a/src/components/tree/phyloTree/generalUpdates.js +++ b/src/components/tree/phyloTree/generalUpdates.js @@ -12,6 +12,7 @@ const contains = (array, elem) => array.some((d) => d === elem); * @return {[type]} */ export const updateStyleOrAttribute = function updateStyleOrAttribute(treeElem, attr, callback, dt, styleOrAttribute) { + console.warn("updateStyleOrAttribute is deprecated. use phylotree.change instead.") const attr_array = this.nodes.map((d) => callback(d)); this.updateStyleOrAttributeArray(treeElem, attr, attr_array, dt, styleOrAttribute); }; @@ -25,6 +26,7 @@ export const updateStyleOrAttribute = function updateStyleOrAttribute(treeElem, * @return {[type]} */ export const updateStyleOrAttributeArray = function updateStyleOrAttributeArray(treeElem, attr, attr_array, dt, styleOrAttribute) { + console.warn("updateStyleOrAttributeArray is deprecated. use phylotree.change instead.") timerStart("updateStyleOrAttributeArray"); this.nodes.forEach((d, i) => { const newAttr = attr_array[i]; @@ -234,6 +236,7 @@ export const updateGeometryFade = function updateGeometryFade(dt) { * @param {int} dt time in milliseconds */ export const updateMultipleArray = function updateMultipleArray(treeElem, attrs, styles, dt, quickdraw) { + console.warn("updateMultipleArray is deprecated. use phylotree.change instead.") timerStart("updateMultipleArray"); // assign new values and decide whether to update this.nodes.forEach((d, i) => { @@ -302,6 +305,7 @@ export const updateMultipleArray = function updateMultipleArray(treeElem, attrs, * @param dt -- transition time */ export const redrawAttribute = function redrawAttribute(treeElem, attr, dt) { + console.warn("redrawAttribute is deprecated. use phylotree.change instead.") this.svg.selectAll(treeElem) .filter((d) => d.update) .transition() @@ -317,6 +321,7 @@ export const redrawAttribute = function redrawAttribute(treeElem, attr, dt) { * @param dt -- transition time */ export const redrawStyle = function redrawStyle(treeElem, styleElem, dt) { +console.warn("redrawStyle is deprecated. use phylotree.change instead.") this.svg.selectAll(treeElem) .filter((d) => d.update) .transition().duration(dt) diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index ebc901c66..ce5babbc5 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -63,6 +63,7 @@ export const render = function render(svg, layout, distance, options, callbacks, if (this.layout === "clock" && this.distance === "num_date") { this.drawRegression(); } + this.confidencesInSVG = false; if (drawConfidence) { this.drawConfidence(); } diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index 60a7ea38a..5c0c76c97 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -86,10 +86,23 @@ export const changePhyloTreeViaPropsComparison = (props, nextProps, tree) => { args.branchThickness = nextProps.tree.branchThickness; } + /* change from timetree to divergence tree */ if (props.distanceMeasure !== nextProps.distanceMeasure) { args.newDistance = nextProps.distanceMeasure; } + /* confidence intervals (on means in the SVG, display means the sidebar. TODO fix this terminology!) */ + if ((props.temporalConfidence.display === true && nextProps.temporalConfidence.display === false) || + (props.temporalConfidence.on === true && nextProps.temporalConfidence.on === false)) { + args.removeConfidences = true; + } + if (nextProps.temporalConfidence.display === true && + (props.temporalConfidence.on === false && nextProps.temporalConfidence.on === true)) { + console.log('asking to show confidences') + args.showConfidences = true; + } + + tree.change(args); }; From 20e472e2c22e479278904149c3a4a264a0d32bab Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Thu, 15 Feb 2018 18:37:42 -0800 Subject: [PATCH 07/25] zooom into clade (without branch thicknesses) --- src/components/tree/index.js | 2 +- src/components/tree/phyloTree/change.js | 39 +++++++++----- src/components/tree/phyloTree/zoom.js | 2 + .../tree/reactD3Interface/callbacks.js | 26 +++++----- src/components/tree/reactD3Interface/index.js | 52 +++++++++++-------- 5 files changed, 72 insertions(+), 49 deletions(-) diff --git a/src/components/tree/index.js b/src/components/tree/index.js index b9bc909c4..3dd335ba3 100644 --- a/src/components/tree/index.js +++ b/src/components/tree/index.js @@ -67,7 +67,7 @@ class Tree extends React.Component { works out what to update, based upon changes to redux.control */ let tree = this.state.tree; const changes = salientPropChanges(this.props, nextProps, tree); - changePhyloTreeViaPropsComparison(this.props, nextProps, tree); + changePhyloTreeViaPropsComparison(this, nextProps); /* usefull for debugging: */ // console.log("CWRP Changes:", // Object.keys(changes).filter((k) => !!changes[k]).reduce((o, k) => { diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 5981db0e2..734cf9e32 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -1,5 +1,5 @@ import { calcConfidenceWidth } from "./confidence"; - +import { applyToChildren } from "./helpers"; const updateNodesWithNewData = (nodes, newNodeProps) => { console.log("update nodes with data for these keys:", Object.keys(newNodeProps)); @@ -162,16 +162,12 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra this.removeConfidence(transitionTime); } else if (extras.showConfidences) { this.drawConfidence(transitionTime); - } else if (elemsToUpdate.has(".conf")) { - if (this.confidencesInSVG) { - if (this.layout === "rect" && this.distance === "num_date") { - updateCall = createUpdateCall(".conf", svgPropsToUpdate); - genericSelectAndModify(this.svg, ".conf", updateCall, transitionTime); - } else { - this.removeConfidence(transitionTime); - } + } else if (elemsToUpdate.has(".conf") && this.confidencesInSVG) { + if (this.layout === "rect" && this.distance === "num_date") { + updateCall = createUpdateCall(".conf", svgPropsToUpdate); + genericSelectAndModify(this.svg, ".conf", updateCall, transitionTime); } else { - console.warn("can't update confidences as they don't exist in the SVG"); + this.removeConfidence(transitionTime); } } }; @@ -188,6 +184,7 @@ export const change = function change({ changeBranchThickness = false, showConfidences = false, removeConfidences = false, + zoomIntoClade = false, /* change these things to this state */ newDistance = undefined, /* arrays of data (the same length as nodes) */ @@ -238,7 +235,7 @@ export const change = function change({ svgPropsToUpdate.add("stroke-width"); nodePropsToModify["stroke-width"] = branchThickness; } - if (newDistance) { + if (newDistance || zoomIntoClade) { elemsToUpdate.add(".tip").add(".branch.S").add(".branch.T"); elemsToUpdate.add(".vaccineCross").add(".vaccineDottedLine").add(".conf"); elemsToUpdate.add('.branchLabel').add('.tipLabel'); @@ -247,17 +244,31 @@ export const change = function change({ } + /* change the requested properties on the nodes */ updateNodesWithNewData(this.nodes, nodePropsToModify); + if (zoomIntoClade) { /* must happen below updateNodesWithNewData */ + this.nodes.forEach((d) => { + d.inView = false; + d.update = true; + }); + /* if clade is terminal, use the parent as the zoom node */ + const zoomNode = zoomIntoClade.terminal ? zoomIntoClade.parent : zoomIntoClade; + console.log("ZOOM INTO CLADE", zoomNode) + applyToChildren(zoomNode, (d) => {d.inView = true;}); + } + /* run calculations as needed */ /* distance */ if (newDistance) this.setDistance(newDistance); /* layout (must run after distance) */ if (newDistance) this.setLayout(this.layout); - /* mapToScreen (must run after "stroke-width" has been set on nodes) */ - if (svgPropsToUpdate.has(["stroke-width"]) || - newDistance) { + /* mapToScreen */ + if ( + svgPropsToUpdate.has(["stroke-width"]) || + newDistance || + zoomIntoClade) { this.mapToScreen(); } diff --git a/src/components/tree/phyloTree/zoom.js b/src/components/tree/phyloTree/zoom.js index 3c0e8970a..2a26c9233 100644 --- a/src/components/tree/phyloTree/zoom.js +++ b/src/components/tree/phyloTree/zoom.js @@ -10,6 +10,7 @@ import { timerStart, timerEnd } from "../../../util/perf"; * @return {null} */ export const zoomIntoClade = function zoomIntoClade(clade, dt) { + console.warn("zoomIntoClade is deprecated. Please use phylotree.change()"); // assign all nodes to inView false and force update this.zoomNode = clade; this.nodes.forEach((d) => { @@ -53,6 +54,7 @@ export const zoomToParent = function zoomToParent(dt) { * @return {null} */ export const mapToScreen = function mapToScreen() { + console.log("mapToScreen") timerStart("mapToScreen"); /* set the range of the x & y scales */ this.setScales(this.params.margins); diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index 0fd076706..ba94e37b6 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -52,18 +52,20 @@ export const onBranchHover = function onBranchHover(d, x, y) { }; export const onBranchClick = function onBranchClick(d) { - this.Viewer.fitToViewer(); - this.state.tree.zoomIntoClade(d, mediumTransitionDuration); - /* to stop multiple phyloTree updates potentially clashing, - we change tipVis after geometry update + transition */ - window.setTimeout( - () => this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: d.n.arrayIdx})), - mediumTransitionDuration - ); - this.setState({ - hovered: null, - selectedBranch: d - }); + this.state.tree.change({zoomIntoClade: d}); + this.setState({hovered: null, selectedBranch: d}); + // this.Viewer.fitToViewer(); + // this.state.tree.zoomIntoClade(d, mediumTransitionDuration); + // /* to stop multiple phyloTree updates potentially clashing, + // we change tipVis after geometry update + transition */ + // window.setTimeout( + // () => this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: d.n.arrayIdx})), + // mediumTransitionDuration + // ); + // this.setState({ + // hovered: null, + // selectedBranch: d + // }); }; /* onBranchLeave called when mouse-off, i.e. anti-hover */ diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index 5c0c76c97..d73adeb90 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -21,13 +21,13 @@ export const salientPropChanges = (props, nextProps, tree) => { const layout = props.layout !== nextProps.layout; // const distanceMeasure = props.distanceMeasure !== nextProps.distanceMeasure; const rerenderAllElements = nextProps.quickdraw === false && props.quickdraw === true; - const resetViewToRoot = props.tree.idxOfInViewRootNode !== 0 && nextProps.tree.idxOfInViewRootNode === 0; + // const resetViewToRoot = props.tree.idxOfInViewRootNode !== 0 && nextProps.tree.idxOfInViewRootNode === 0; /* branch labels & confidence use 0: no change, 1: turn off, 2: turn on */ const branchLabels = props.showBranchLabels === nextProps.showBranchLabels ? 0 : nextProps.showBranchLabels ? 2 : 1; - const confidence = props.temporalConfidence.on === nextProps.temporalConfidence.on && props.temporalConfidence.display === nextProps.temporalConfidence.display ? 0 : - (props.temporalConfidence.on === false && nextProps.temporalConfidence.on === false) ? 0 : - (nextProps.temporalConfidence.display === false || nextProps.temporalConfidence.on === false) ? 1 : - (nextProps.temporalConfidence.display === true && nextProps.temporalConfidence.on === true) ? 2 : 0; + // const confidence = props.temporalConfidence.on === nextProps.temporalConfidence.on && props.temporalConfidence.display === nextProps.temporalConfidence.display ? 0 : + // (props.temporalConfidence.on === false && nextProps.temporalConfidence.on === false) ? 0 : + // (nextProps.temporalConfidence.display === false || nextProps.temporalConfidence.on === false) ? 1 : + // (nextProps.temporalConfidence.display === true && nextProps.temporalConfidence.on === true) ? 2 : 0; /* sometimes we may want smooth transitions */ let branchTransitionTime = false; /* false = no transition. Use when speed is critical */ @@ -48,16 +48,18 @@ export const salientPropChanges = (props, nextProps, tree) => { branchTransitionTime, tipTransitionTime, branchLabels, - resetViewToRoot, - confidence, + resetViewToRoot: false, + confidence: false, quickdraw: nextProps.quickdraw, rerenderAllElements }; }; -export const changePhyloTreeViaPropsComparison = (props, nextProps, tree) => { +export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { console.log('changePhyloTreeViaPropsComparison') const args = {}; + const props = reactThis.props; + const phylotree = reactThis.state.tree; /* colorBy change? */ if (!!nextProps.tree.nodeColorsVersion && @@ -98,12 +100,18 @@ export const changePhyloTreeViaPropsComparison = (props, nextProps, tree) => { } if (nextProps.temporalConfidence.display === true && (props.temporalConfidence.on === false && nextProps.temporalConfidence.on === true)) { - console.log('asking to show confidences') args.showConfidences = true; } + /* reset the entire tree to root view */ + if (props.tree.idxOfInViewRootNode !== 0 && nextProps.tree.idxOfInViewRootNode === 0) { + reactThis.Viewer.fitToViewer(); + reactThis.setState({selectedBranch: null, selectedTip: null}); + args.zoomIntoClade = phylotree.nodes[0]; /* the root node inside phylotree */ + } + - tree.change(args); + phylotree.change(args); }; /** @@ -159,18 +167,18 @@ export const updateStylesAndAttrs = (that, changes, nextProps, tree) => { } else if (changes.branchLabels === 1) { tree.hideBranchLabels(); } - if (changes.confidence === 1) { - tree.removeConfidence(mediumTransitionDuration); - } else if (changes.confidence === 2) { - if (changes.layout) { /* setTimeout else they come back in before the branches have transitioned */ - setTimeout(() => tree.drawConfidence(mediumTransitionDuration), mediumTransitionDuration * 1.5); - } else { - tree.drawConfidence(mediumTransitionDuration); - } - } else if (nextProps.temporalConfidence.on && (changes.branchThickness || changes.colorBy)) { - /* some updates may necessitate an updating of the CIs (e.g. ∆ branch thicknesses) */ - tree.updateConfidence(changes.tipTransitionTime); - } + // if (changes.confidence === 1) { + // tree.removeConfidence(mediumTransitionDuration); + // } else if (changes.confidence === 2) { + // if (changes.layout) { /* setTimeout else they come back in before the branches have transitioned */ + // setTimeout(() => tree.drawConfidence(mediumTransitionDuration), mediumTransitionDuration * 1.5); + // } else { + // tree.drawConfidence(mediumTransitionDuration); + // } + // } else if (nextProps.temporalConfidence.on && (changes.branchThickness || changes.colorBy)) { + // /* some updates may necessitate an updating of the CIs (e.g. ∆ branch thicknesses) */ + // tree.updateConfidence(changes.tipTransitionTime); + // } if (changes.resetViewToRoot) { viewEntireTree.bind(that)(); } From b68cea670439f01c52e5d2ec621566e07944db50 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Thu, 15 Feb 2018 19:23:06 -0800 Subject: [PATCH 08/25] branch thicknesses change during zoom to clade --- src/actions/treeProperties.js | 14 +++++- src/components/info/info.js | 6 +-- src/components/tree/phyloTree/change.js | 9 +--- .../tree/reactD3Interface/callbacks.js | 43 +++++++++++++------ src/components/tree/reactD3Interface/index.js | 17 ++++++-- src/reducers/tree.js | 2 + 6 files changed, 62 insertions(+), 29 deletions(-) diff --git a/src/actions/treeProperties.js b/src/actions/treeProperties.js index 66548a522..39c8fc91f 100644 --- a/src/actions/treeProperties.js +++ b/src/actions/treeProperties.js @@ -6,7 +6,7 @@ import { calcVisibility, import * as types from "./types"; import { updateEntropyVisibility } from "./entropy"; import { calendarToNumeric } from "../util/dateHelpers"; - +import { applyToChildren } from "../components/tree/phyloTree/helpers"; const calculateVisiblityAndBranchThickness = (tree, controls, dates, {idxOfInViewRootNode = 0, tipSelectedIdx = 0} = {}) => { const visibility = tipSelectedIdx ? identifyPathToTip(tree.nodes, tipSelectedIdx) : calcVisibility(tree, controls, dates); @@ -38,6 +38,18 @@ export const updateVisibleTipsAndBranchThicknesses = ( const { tree, controls } = getState(); if (!tree.nodes) {return;} const validIdxRoot = idxOfInViewRootNode !== undefined ? idxOfInViewRootNode : tree.idxOfInViewRootNode; + if (idxOfInViewRootNode !== tree.idxOfInViewRootNode && tree.nodes[0].shell) { + /* a bit hacky, should be somewhere else */ + tree.nodes.forEach((d) => { + d.shell.inView = false; + d.shell.update = true; + }); + if (tree.nodes[validIdxRoot].shell.terminal) { + applyToChildren(tree.nodes[validIdxRoot].shell.parent, (d) => {d.inView = true;}); + } else { + applyToChildren(tree.nodes[validIdxRoot].shell, (d) => {d.inView = true;}); + } + } const data = calculateVisiblityAndBranchThickness(tree, controls, {dateMinNumeric: controls.dateMinNumeric, dateMaxNumeric: controls.dateMaxNumeric}, {tipSelectedIdx, validIdxRoot}); dispatch({ type: types.UPDATE_VISIBILITY_AND_BRANCH_THICKNESS, diff --git a/src/components/info/info.js b/src/components/info/info.js index ce3c93aa9..3142ba4f1 100644 --- a/src/components/info/info.js +++ b/src/components/info/info.js @@ -4,17 +4,17 @@ import { connect } from "react-redux"; import Card from "../framework/card"; import computeResponsive from "../../util/computeResponsive"; import { titleFont, headerFont, medGrey, darkGrey } from "../../globalStyles"; -import { applyFilter, changeDateFilter } from "../../actions/treeProperties"; +import { applyFilter, changeDateFilter, updateVisibleTipsAndBranchThicknesses } from "../../actions/treeProperties"; import { prettyString } from "../../util/stringHelpers"; import { displayFilterValueAsButton } from "../framework/footer"; -import { CHANGE_TREE_ROOT_IDX } from "../../actions/types"; +// import { CHANGE_TREE_ROOT_IDX } from "../../actions/types"; const resetTreeButton = (dispatch) => { return (
dispatch({type: CHANGE_TREE_ROOT_IDX, idxOfInViewRootNode: 0})} + onClick={() => dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: 0}))} > {"View entire tree."}
diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 734cf9e32..4498f6647 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -211,8 +211,7 @@ export const change = function change({ and what SVG elements, node properties, svg props we actually change */ if (changeColorBy) { /* check that fill & stroke are defined */ - elemsToUpdate.add(".branch").add(".tip"); - if (this.confidencesInSVG) elemsToUpdate.add(".conf"); + elemsToUpdate.add(".branch").add(".tip").add(".conf"); svgPropsToUpdate.add("stroke").add("fill"); nodePropsToModify.stroke = stroke; nodePropsToModify.fill = fill; @@ -230,8 +229,7 @@ export const change = function change({ nodePropsToModify.r = tipRadii; } if (changeBranchThickness) { - elemsToUpdate.add(".branch"); - if (this.confidencesInSVG) elemsToUpdate.add(".conf"); + elemsToUpdate.add(".branch").add(".conf"); svgPropsToUpdate.add("stroke-width"); nodePropsToModify["stroke-width"] = branchThickness; } @@ -243,8 +241,6 @@ export const change = function change({ svgPropsToUpdate.add("cx").add("cy").add("d").add("opacity"); } - - /* change the requested properties on the nodes */ updateNodesWithNewData(this.nodes, nodePropsToModify); @@ -255,7 +251,6 @@ export const change = function change({ }); /* if clade is terminal, use the parent as the zoom node */ const zoomNode = zoomIntoClade.terminal ? zoomIntoClade.parent : zoomIntoClade; - console.log("ZOOM INTO CLADE", zoomNode) applyToChildren(zoomNode, (d) => {d.inView = true;}); } diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index ba94e37b6..12ef35d87 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -52,8 +52,8 @@ export const onBranchHover = function onBranchHover(d, x, y) { }; export const onBranchClick = function onBranchClick(d) { - this.state.tree.change({zoomIntoClade: d}); - this.setState({hovered: null, selectedBranch: d}); + this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: d.n.arrayIdx})); + // this.setState({hovered: null, selectedBranch: d}); // this.Viewer.fitToViewer(); // this.state.tree.zoomIntoClade(d, mediumTransitionDuration); // /* to stop multiple phyloTree updates potentially clashing, @@ -67,6 +67,21 @@ export const onBranchClick = function onBranchClick(d) { // selectedBranch: d // }); }; +// +export const viewEntireTree = function viewEntireTree() { + console.warn("viewEntireTree is deprecated.") + /* reset the SVGPanZoom */ + this.Viewer.fitToViewer(); + /* imperitively manipulate SVG tree elements */ + this.state.tree.zoomIntoClade(this.state.tree.nodes[0], mediumTransitionDuration); + /* update branch thicknesses / tip vis after SVG tree elemtents have moved */ + window.setTimeout( + () => this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: 0})), + mediumTransitionDuration + ); + this.setState({selectedBranch: null, selectedTip: null}); +}; + /* onBranchLeave called when mouse-off, i.e. anti-hover */ export const onBranchLeave = function onBranchLeave(d) { @@ -144,18 +159,18 @@ export const resetView = function resetView() { /* viewEntireTree: go back to the root! */ -export const viewEntireTree = function viewEntireTree() { - /* reset the SVGPanZoom */ - this.Viewer.fitToViewer(); - /* imperitively manipulate SVG tree elements */ - this.state.tree.zoomIntoClade(this.state.tree.nodes[0], mediumTransitionDuration); - /* update branch thicknesses / tip vis after SVG tree elemtents have moved */ - window.setTimeout( - () => this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: 0})), - mediumTransitionDuration - ); - this.setState({selectedBranch: null, selectedTip: null}); -}; +// export const viewEntireTree = function viewEntireTree() { +// /* reset the SVGPanZoom */ +// this.Viewer.fitToViewer(); +// /* imperitively manipulate SVG tree elements */ +// this.state.tree.zoomIntoClade(this.state.tree.nodes[0], mediumTransitionDuration); +// /* update branch thicknesses / tip vis after SVG tree elemtents have moved */ +// window.setTimeout( +// () => this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: 0})), +// mediumTransitionDuration +// ); +// this.setState({selectedBranch: null, selectedTip: null}); +// }; export const handleIconClickHOF = function handleIconClickHOF(tool) { return () => { diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index d73adeb90..441c91905 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -56,7 +56,7 @@ export const salientPropChanges = (props, nextProps, tree) => { }; export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { - console.log('changePhyloTreeViaPropsComparison') + console.log('\nchangePhyloTreeViaPropsComparison') const args = {}; const props = reactThis.props; const phylotree = reactThis.state.tree; @@ -110,6 +110,15 @@ export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { args.zoomIntoClade = phylotree.nodes[0]; /* the root node inside phylotree */ } + if (props.tree.idxOfInViewRootNode !== nextProps.tree.idxOfInViewRootNode) { + const rootNode = phylotree.nodes[nextProps.tree.idxOfInViewRootNode]; + args.zoomIntoClade = rootNode; + reactThis.setState({ + selectedBranch: nextProps.tree.idxOfInViewRootNode === 0 ? null : rootNode, + selectedTip: null, + hovered: null + }); + } phylotree.change(args); }; @@ -179,9 +188,9 @@ export const updateStylesAndAttrs = (that, changes, nextProps, tree) => { // /* some updates may necessitate an updating of the CIs (e.g. ∆ branch thicknesses) */ // tree.updateConfidence(changes.tipTransitionTime); // } - if (changes.resetViewToRoot) { - viewEntireTree.bind(that)(); - } + // if (changes.resetViewToRoot) { + // viewEntireTree.bind(that)(); + // } if (changes.rerenderAllElements) { tree.rerenderAllElements(); } diff --git a/src/reducers/tree.js b/src/reducers/tree.js index ca9241f8f..e30811351 100644 --- a/src/reducers/tree.js +++ b/src/reducers/tree.js @@ -60,11 +60,13 @@ const Tree = (state = getDefaultState(), action) => { loaded: false }); case types.CHANGE_TREE_ROOT_IDX: + console.warn("CHANGE_TREE_ROOT_IDX is deprecated"); return Object.assign({}, state, { idxOfInViewRootNode: action.idxOfInViewRootNode }); case types.CHANGE_DATES_VISIBILITY_THICKNESS: /* fall-through */ case types.UPDATE_VISIBILITY_AND_BRANCH_THICKNESS: + console.log("UPDATE_VISIBILITY_AND_BRANCH_THICKNESS done") const newStates = { visibility: action.visibility, visibilityVersion: action.visibilityVersion, From 2ee1b3d110e9e7ecc0584843a3d7afd8006f9f5f Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Thu, 15 Feb 2018 19:39:05 -0800 Subject: [PATCH 09/25] layout changes (all transitions at once) --- src/components/tree/phyloTree/change.js | 11 ++++++---- .../tree/phyloTree/generalUpdates.js | 1 + src/components/tree/phyloTree/layouts.js | 1 + src/components/tree/reactD3Interface/index.js | 21 +++++++++++-------- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 4498f6647..282691810 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -185,8 +185,9 @@ export const change = function change({ showConfidences = false, removeConfidences = false, zoomIntoClade = false, - /* change these things to this state */ + /* change these things to provided value */ newDistance = undefined, + newLayout = undefined, /* arrays of data (the same length as nodes) */ stroke = undefined, fill = undefined, @@ -233,7 +234,7 @@ export const change = function change({ svgPropsToUpdate.add("stroke-width"); nodePropsToModify["stroke-width"] = branchThickness; } - if (newDistance || zoomIntoClade) { + if (newDistance || newLayout || zoomIntoClade) { elemsToUpdate.add(".tip").add(".branch.S").add(".branch.T"); elemsToUpdate.add(".vaccineCross").add(".vaccineDottedLine").add(".conf"); elemsToUpdate.add('.branchLabel').add('.tipLabel'); @@ -258,12 +259,14 @@ export const change = function change({ /* distance */ if (newDistance) this.setDistance(newDistance); /* layout (must run after distance) */ - if (newDistance) this.setLayout(this.layout); + if (newDistance || newLayout) this.setLayout(newLayout || this.layout); /* mapToScreen */ if ( svgPropsToUpdate.has(["stroke-width"]) || newDistance || - zoomIntoClade) { + newLayout || + zoomIntoClade + ) { this.mapToScreen(); } diff --git a/src/components/tree/phyloTree/generalUpdates.js b/src/components/tree/phyloTree/generalUpdates.js index f4e575771..91e21fbd8 100644 --- a/src/components/tree/phyloTree/generalUpdates.js +++ b/src/components/tree/phyloTree/generalUpdates.js @@ -75,6 +75,7 @@ export const updateDistance = function updateDistance(attr, dt) { * @return null */ export const updateLayout = function updateLayout(layout, dt) { + console.warn("updateLayout is deprecated"); this.setLayout(layout); this.mapToScreen(); this.updateGeometryFade(dt); diff --git a/src/components/tree/phyloTree/layouts.js b/src/components/tree/phyloTree/layouts.js index 246a4ed79..9f189bf27 100644 --- a/src/components/tree/phyloTree/layouts.js +++ b/src/components/tree/phyloTree/layouts.js @@ -10,6 +10,7 @@ import { timerStart, timerEnd } from "../../../util/perf"; * ["rect", "radial", "unrooted", "clock"] */ export const setLayout = function setLayout(layout) { + console.log("set layout"); timerStart("setLayout"); if (typeof layout === "undefined" || layout !== this.layout) { this.nodes.forEach((d) => {d.update = true;}); diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index 441c91905..b4de6891b 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -18,7 +18,7 @@ export const salientPropChanges = (props, nextProps, tree) => { // (props.tree.nodeColorsVersion !== nextProps.tree.nodeColorsVersion || // nextProps.colorByConfidence !== props.colorByConfidence); // const branchThickness = props.tree.branchThicknessVersion !== nextProps.tree.branchThicknessVersion; - const layout = props.layout !== nextProps.layout; + // const layout = props.layout !== nextProps.layout; // const distanceMeasure = props.distanceMeasure !== nextProps.distanceMeasure; const rerenderAllElements = nextProps.quickdraw === false && props.quickdraw === true; // const resetViewToRoot = props.tree.idxOfInViewRootNode !== 0 && nextProps.tree.idxOfInViewRootNode === 0; @@ -31,7 +31,7 @@ export const salientPropChanges = (props, nextProps, tree) => { /* sometimes we may want smooth transitions */ let branchTransitionTime = false; /* false = no transition. Use when speed is critical */ - const tipTransitionTime = false; + // const tipTransitionTime = false; if (nextProps.colorByConfidence !== props.colorByConfidence) { branchTransitionTime = mediumTransitionDuration; } @@ -42,11 +42,11 @@ export const salientPropChanges = (props, nextProps, tree) => { visibility: false, tipRadii: false, colorBy: false, - layout, + layout: false, distanceMeasure: false, branchThickness: false, branchTransitionTime, - tipTransitionTime, + tipTransitionTime: false, branchLabels, resetViewToRoot: false, confidence: false, @@ -103,16 +103,19 @@ export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { args.showConfidences = true; } - /* reset the entire tree to root view */ - if (props.tree.idxOfInViewRootNode !== 0 && nextProps.tree.idxOfInViewRootNode === 0) { - reactThis.Viewer.fitToViewer(); - reactThis.setState({selectedBranch: null, selectedTip: null}); - args.zoomIntoClade = phylotree.nodes[0]; /* the root node inside phylotree */ + if (props.layout !== nextProps.layout) { + args.newLayout = nextProps.layout; + } + + if (nextProps.quickdraw === false && props.quickdraw === true) { + console.warn("quickdraw finished. should call rerenderAllElements"); } + /* zoom to a clade / reset zoom to entire tree */ if (props.tree.idxOfInViewRootNode !== nextProps.tree.idxOfInViewRootNode) { const rootNode = phylotree.nodes[nextProps.tree.idxOfInViewRootNode]; args.zoomIntoClade = rootNode; + reactThis.Viewer.fitToViewer(); reactThis.setState({ selectedBranch: nextProps.tree.idxOfInViewRootNode === 0 ? null : rootNode, selectedTip: null, From 1cdc192955c160bbda49998c26ca0c1a2e3d2e26 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Thu, 15 Feb 2018 19:42:51 -0800 Subject: [PATCH 10/25] remove updateStylesAndAttrs and salientPropChanges --- src/components/tree/index.js | 31 ++--- src/components/tree/reactD3Interface/index.js | 127 ------------------ 2 files changed, 8 insertions(+), 150 deletions(-) diff --git a/src/components/tree/index.js b/src/components/tree/index.js index 3dd335ba3..dd5f1b56c 100644 --- a/src/components/tree/index.js +++ b/src/components/tree/index.js @@ -13,7 +13,7 @@ import { mediumTransitionDuration } from "../../util/globals"; import HoverInfoPanel from "./infoPanels/hover"; import TipClickedPanel from "./infoPanels/click"; import computeResponsive from "../../util/computeResponsive"; -import { updateStylesAndAttrs, salientPropChanges, changePhyloTreeViaPropsComparison } from "./reactD3Interface"; +import { changePhyloTreeViaPropsComparison } from "./reactD3Interface"; import * as callbacks from "./reactD3Interface/callbacks"; import { calcStrokeCols } from "./treeHelpers"; @@ -62,37 +62,22 @@ class Tree extends React.Component { mutType: PropTypes.string.isRequired } + + /* CWRP has two tasks: (1) create the tree when it's in redux + (2) compare props and call phylotree.change() appropritately */ componentWillReceiveProps(nextProps) { - /* This both creates the tree (when it's loaded into redux) and - works out what to update, based upon changes to redux.control */ let tree = this.state.tree; - const changes = salientPropChanges(this.props, nextProps, tree); - changePhyloTreeViaPropsComparison(this, nextProps); - /* usefull for debugging: */ - // console.log("CWRP Changes:", - // Object.keys(changes).filter((k) => !!changes[k]).reduce((o, k) => { - // o[k] = changes[k]; return o; - // }, {})); - - if (changes.dataInFlux) { + if (!nextProps.tree.loaded) { this.setState({tree: null}); - return null; - } else if (changes.newData) { + } else if (tree === null && nextProps.tree.loaded) { tree = this.makeTree(nextProps); - /* extra (initial, once only) call to update the tree colouring */ - for (const k in changes) { // eslint-disable-line - changes[k] = false; - } this.setState({tree}); if (this.Viewer) { this.Viewer.fitToViewer(); } - return null; /* return to avoid an unnecessary updateStylesAndAttrs call */ + } else if (tree) { + changePhyloTreeViaPropsComparison(this, nextProps); } - if (tree) { - updateStylesAndAttrs(this, changes, nextProps, tree); - } - return null; } componentDidMount() { diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index b4de6891b..1c083a6cc 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -1,59 +1,5 @@ import { rgb } from "d3-color"; -import { mediumTransitionDuration } from "../../../util/globals"; import { calcStrokeCols } from "../treeHelpers"; -import { viewEntireTree } from "./callbacks"; -/** - * function to help determine what parts of phylotree should update - * @param {obj} props redux props - * @param {obj} nextProps next redux props - * @param {obj} tree phyloTree object (stored in the state of the Tree component) - * @return {obj} values are mostly bools, but not always - */ -export const salientPropChanges = (props, nextProps, tree) => { - const dataInFlux = !nextProps.tree.loaded; - const newData = tree === null && nextProps.tree.loaded; - // const visibility = !!nextProps.tree.visibilityVersion && props.tree.visibilityVersion !== nextProps.tree.visibilityVersion; - // const tipRadii = !!nextProps.tree.tipRadiiVersion && props.tree.tipRadiiVersion !== nextProps.tree.tipRadiiVersion; - // const colorBy = !!nextProps.tree.nodeColorsVersion && - // (props.tree.nodeColorsVersion !== nextProps.tree.nodeColorsVersion || - // nextProps.colorByConfidence !== props.colorByConfidence); - // const branchThickness = props.tree.branchThicknessVersion !== nextProps.tree.branchThicknessVersion; - // const layout = props.layout !== nextProps.layout; - // const distanceMeasure = props.distanceMeasure !== nextProps.distanceMeasure; - const rerenderAllElements = nextProps.quickdraw === false && props.quickdraw === true; - // const resetViewToRoot = props.tree.idxOfInViewRootNode !== 0 && nextProps.tree.idxOfInViewRootNode === 0; - /* branch labels & confidence use 0: no change, 1: turn off, 2: turn on */ - const branchLabels = props.showBranchLabels === nextProps.showBranchLabels ? 0 : nextProps.showBranchLabels ? 2 : 1; - // const confidence = props.temporalConfidence.on === nextProps.temporalConfidence.on && props.temporalConfidence.display === nextProps.temporalConfidence.display ? 0 : - // (props.temporalConfidence.on === false && nextProps.temporalConfidence.on === false) ? 0 : - // (nextProps.temporalConfidence.display === false || nextProps.temporalConfidence.on === false) ? 1 : - // (nextProps.temporalConfidence.display === true && nextProps.temporalConfidence.on === true) ? 2 : 0; - - /* sometimes we may want smooth transitions */ - let branchTransitionTime = false; /* false = no transition. Use when speed is critical */ - // const tipTransitionTime = false; - if (nextProps.colorByConfidence !== props.colorByConfidence) { - branchTransitionTime = mediumTransitionDuration; - } - - return { - dataInFlux, - newData, - visibility: false, - tipRadii: false, - colorBy: false, - layout: false, - distanceMeasure: false, - branchThickness: false, - branchTransitionTime, - tipTransitionTime: false, - branchLabels, - resetViewToRoot: false, - confidence: false, - quickdraw: nextProps.quickdraw, - rerenderAllElements - }; -}; export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { console.log('\nchangePhyloTreeViaPropsComparison') @@ -125,76 +71,3 @@ export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { phylotree.change(args); }; - -/** - * effect (in phyloTree) the necessary style + attr updates - * @param {obj} changes see salientPropChanges above - * @param {obj} nextProps next redux props - * @param {obj} tree phyloTree object - * @return {null} causes side-effects via phyloTree object - */ -export const updateStylesAndAttrs = (that, changes, nextProps, tree) => { - /* the objects storing the changes to make to the tree */ - // const tipAttrToUpdate = {}; - // const tipStyleToUpdate = {}; - // const branchAttrToUpdate = {}; - // const branchStyleToUpdate = {}; - return; - // if (changes.visibility) { - // tipStyleToUpdate["visibility"] = nextProps.tree.visibility; - // } - // if (changes.tipRadii) { - // tipAttrToUpdate["r"] = nextProps.tree.tipRadii; - // } - // if (changes.colorBy) { - // tipStyleToUpdate["fill"] = nextProps.tree.nodeColors.map((col) => { - // return rgb(col).brighter([0.65]).toString(); - // }); - // const branchStrokes = calcStrokeCols(nextProps.tree, nextProps.colorByConfidence, nextProps.colorBy); - // branchStyleToUpdate["stroke"] = branchStrokes; - // tipStyleToUpdate["stroke"] = branchStrokes; - // } - // if (changes.branchThickness) { - // // console.log("branch width change detected - update branch stroke-widths") - // branchStyleToUpdate["stroke-width"] = nextProps.tree.branchThickness; - // } - /* implement style * attr changes */ - // if (Object.keys(branchAttrToUpdate).length || Object.keys(branchStyleToUpdate).length) { - // // console.log("applying branch attr", Object.keys(branchAttrToUpdate), "branch style changes", Object.keys(branchStyleToUpdate)) - // tree.updateMultipleArray(".branch", branchAttrToUpdate, branchStyleToUpdate, changes.branchTransitionTime, changes.quickdraw); - // } - // if (Object.keys(tipAttrToUpdate).length || Object.keys(tipStyleToUpdate).length) { - // // console.log("applying tip attr", Object.keys(tipAttrToUpdate), "tip style changes", Object.keys(tipStyleToUpdate)) - // tree.updateMultipleArray(".tip", tipAttrToUpdate, tipStyleToUpdate, changes.tipTransitionTime, changes.quickdraw); - // } - - if (changes.layout) { /* swap layouts */ - tree.updateLayout(nextProps.layout, mediumTransitionDuration); - } - if (changes.distanceMeasure) { /* change distance metrics */ - tree.updateDistance(nextProps.distanceMeasure, mediumTransitionDuration); - } - if (changes.branchLabels === 2) { - tree.showBranchLabels(); - } else if (changes.branchLabels === 1) { - tree.hideBranchLabels(); - } - // if (changes.confidence === 1) { - // tree.removeConfidence(mediumTransitionDuration); - // } else if (changes.confidence === 2) { - // if (changes.layout) { /* setTimeout else they come back in before the branches have transitioned */ - // setTimeout(() => tree.drawConfidence(mediumTransitionDuration), mediumTransitionDuration * 1.5); - // } else { - // tree.drawConfidence(mediumTransitionDuration); - // } - // } else if (nextProps.temporalConfidence.on && (changes.branchThickness || changes.colorBy)) { - // /* some updates may necessitate an updating of the CIs (e.g. ∆ branch thicknesses) */ - // tree.updateConfidence(changes.tipTransitionTime); - // } - // if (changes.resetViewToRoot) { - // viewEntireTree.bind(that)(); - // } - if (changes.rerenderAllElements) { - tree.rerenderAllElements(); - } -}; From 583086cb3f8daabf801d8da04bb973c51b4acaa4 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 16 Feb 2018 08:23:22 -0800 Subject: [PATCH 11/25] use timerflush for zero-time transitions --- package.json | 1 + src/components/tree/phyloTree/change.js | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 478732aef..43f1d8abd 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "d3-selection": "^1.1.0", "d3-shape": "^1.2.0", "d3-time-format": "^2.0.5", + "d3-timer": "^1.0.7", "d3-zoom": "^1.1.3", "express": "^4.13.3", "express-static-gzip": "^0.2.2", diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 282691810..29670b48c 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -1,3 +1,4 @@ +import { timerFlush } from "d3-timer"; import { calcConfidenceWidth } from "./confidence"; import { applyToChildren } from "./helpers"; @@ -89,17 +90,15 @@ const createUpdateCall = (treeElem, properties) => (selection) => { }; const genericSelectAndModify = (svg, treeElem, updateCall, transitionTime) => { - if (transitionTime) { - console.log("general svg update for", treeElem); - svg.selectAll(treeElem) - .filter((d) => d.update) - .transition().duration(transitionTime) - .call(updateCall); - } else { - console.log("general svg update for", treeElem, " (NO TRANSITION)"); - svg.selectAll(treeElem) - .filter((d) => d.update) - .call(updateCall); + console.log("general svg update for", treeElem); + svg.selectAll(treeElem) + .filter((d) => d.update) + .transition().duration(transitionTime) + .call(updateCall); + if (!transitionTime) { + /* https://github.com/d3/d3-timer#timerFlush */ + timerFlush(); + console.log("\t\t--FLUSHING TIMER--"); } }; From a0d889d6068a297e8a18a4012ed32d34be242d9c Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 16 Feb 2018 08:46:37 -0800 Subject: [PATCH 12/25] svg size changes handled --- src/components/tree/index.js | 16 ++++++---------- src/components/tree/phyloTree/change.js | 17 ++++++++++------- src/components/tree/phyloTree/zoom.js | 1 + src/components/tree/reactD3Interface/index.js | 7 +++++-- src/reducers/tree.js | 1 - 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/components/tree/index.js b/src/components/tree/index.js index dd5f1b56c..ed0b52dd8 100644 --- a/src/components/tree/index.js +++ b/src/components/tree/index.js @@ -62,7 +62,6 @@ class Tree extends React.Component { mutType: PropTypes.string.isRequired } - /* CWRP has two tasks: (1) create the tree when it's in redux (2) compare props and call phylotree.change() appropritately */ componentWillReceiveProps(nextProps) { @@ -88,24 +87,21 @@ class Tree extends React.Component { } } + /* CDU is used to update phylotree when the SVG size _has_ changed (and this is why it's in CDU not CWRP) */ componentDidUpdate(prevProps) { - /* if the SVG has changed size, call zoomIntoClade so that the tree rescales to fit the SVG */ - if ( - // the tree exists AND + if ( // the tree exists AND this.state.tree && - // either the browser dimensions have changed - ( + ( // either the browser dimensions have changed prevProps.browserDimensions.width !== this.props.browserDimensions.width || prevProps.browserDimensions.height !== this.props.browserDimensions.height || // or the sidebar(s) have (dis)appeared this.props.padding.left !== prevProps.padding.left || this.props.padding.right !== prevProps.padding.right || - prevProps.panelLayout !== this.props.panelLayout /* full vs grid */ + // or we have changed between "full" & "grid" + prevProps.panelLayout !== this.props.panelLayout ) - ) { - const baseNodeInView = this.state.selectedBranch ? this.state.selectedBranch.n.arrayIdx : 0; - this.state.tree.zoomIntoClade(this.state.tree.nodes[baseNodeInView], mediumTransitionDuration); + this.state.tree.change({svgHasChangedDimensions: true}); } } diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 29670b48c..3582a0cd7 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -60,14 +60,12 @@ const createUpdateCall = (treeElem, properties) => (selection) => { if (genericAttrs[treeElem]) { [...properties].filter((x) => genericAttrs[treeElem].has(x)) .forEach((attrName) => { - console.log("\tadding node attr", attrName); selection.attr(attrName, (d) => d[attrName]); }); } if (genericStyles[treeElem]) { [...properties].filter((x) => genericStyles[treeElem].has(x)) .forEach((styleName) => { - console.log("\tadding node style", styleName); selection.style(styleName, (d) => d[styleName]); }); } @@ -75,14 +73,12 @@ const createUpdateCall = (treeElem, properties) => (selection) => { if (functionAttrs[treeElem]) { [...properties].filter((x) => functionAttrs[treeElem][x]) .forEach((attrName) => { - console.log("\tadding fn for attr", attrName); selection.attr(attrName, functionAttrs[treeElem][attrName]); }); } if (functionStyles[treeElem]) { [...properties].filter((x) => functionStyles[treeElem][x]) .forEach((styleName) => { - console.log("\tadding fn for style ", styleName); selection.attr(styleName, functionStyles[treeElem][styleName]); }); } @@ -184,6 +180,7 @@ export const change = function change({ showConfidences = false, removeConfidences = false, zoomIntoClade = false, + svgHasChangedDimensions = false, /* change these things to provided value */ newDistance = undefined, newLayout = undefined, @@ -194,7 +191,7 @@ export const change = function change({ tipRadii = undefined, branchThickness = undefined }) { - console.log("\n** change **\n\n"); + console.log("\n** phylotree.change() (time since last run:", Date.now() - this.timeLastRenderRequested, "ms) **\n\n"); const elemsToUpdate = new Set(); const nodePropsToModify = {}; /* modify the actual data structure */ @@ -233,7 +230,7 @@ export const change = function change({ svgPropsToUpdate.add("stroke-width"); nodePropsToModify["stroke-width"] = branchThickness; } - if (newDistance || newLayout || zoomIntoClade) { + if (newDistance || newLayout || zoomIntoClade || svgHasChangedDimensions) { elemsToUpdate.add(".tip").add(".branch.S").add(".branch.T"); elemsToUpdate.add(".vaccineCross").add(".vaccineDottedLine").add(".conf"); elemsToUpdate.add('.branchLabel').add('.tipLabel'); @@ -244,6 +241,8 @@ export const change = function change({ /* change the requested properties on the nodes */ updateNodesWithNewData(this.nodes, nodePropsToModify); + /* some things need to update d.inView and/or d.update. This should be centralised */ + /* TODO: list all functions which modify these */ if (zoomIntoClade) { /* must happen below updateNodesWithNewData */ this.nodes.forEach((d) => { d.inView = false; @@ -253,6 +252,9 @@ export const change = function change({ const zoomNode = zoomIntoClade.terminal ? zoomIntoClade.parent : zoomIntoClade; applyToChildren(zoomNode, (d) => {d.inView = true;}); } + if (svgHasChangedDimensions) { + this.nodes.forEach((d) => {d.update = true;}); + } /* run calculations as needed */ /* distance */ @@ -264,7 +266,8 @@ export const change = function change({ svgPropsToUpdate.has(["stroke-width"]) || newDistance || newLayout || - zoomIntoClade + zoomIntoClade || + svgHasChangedDimensions ) { this.mapToScreen(); } diff --git a/src/components/tree/phyloTree/zoom.js b/src/components/tree/phyloTree/zoom.js index 2a26c9233..f60bc2c2b 100644 --- a/src/components/tree/phyloTree/zoom.js +++ b/src/components/tree/phyloTree/zoom.js @@ -42,6 +42,7 @@ export const zoomIntoClade = function zoomIntoClade(clade, dt) { * @param {int} dt [transition time] */ export const zoomToParent = function zoomToParent(dt) { + console.warn("TODO: zoomToParent functionality needs to be built into phylotree.change()"); if (this.zoomNode) { this.zoomIntoClade(this.zoomNode.parent, dt); } diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index 1c083a6cc..54dd9145b 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -2,7 +2,6 @@ import { rgb } from "d3-color"; import { calcStrokeCols } from "../treeHelpers"; export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { - console.log('\nchangePhyloTreeViaPropsComparison') const args = {}; const props = reactThis.props; const phylotree = reactThis.state.tree; @@ -69,5 +68,9 @@ export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { }); } - phylotree.change(args); + if (Object.keys(args).length) { + console.log('\n\n** changePhyloTreeViaPropsComparison **', args); + phylotree.change(args); + } + }; diff --git a/src/reducers/tree.js b/src/reducers/tree.js index e30811351..e2ce51c7c 100644 --- a/src/reducers/tree.js +++ b/src/reducers/tree.js @@ -66,7 +66,6 @@ const Tree = (state = getDefaultState(), action) => { }); case types.CHANGE_DATES_VISIBILITY_THICKNESS: /* fall-through */ case types.UPDATE_VISIBILITY_AND_BRANCH_THICKNESS: - console.log("UPDATE_VISIBILITY_AND_BRANCH_THICKNESS done") const newStates = { visibility: action.visibility, visibilityVersion: action.visibilityVersion, From 8f3d6c1b5be5e0ed2e2c36362e0db74cba2127e3 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 16 Feb 2018 09:29:06 -0800 Subject: [PATCH 13/25] add comments & improve bits of the code --- src/components/tree/phyloTree/change.js | 97 ++++++++++++---------- src/components/tree/phyloTree/renderers.js | 16 +--- 2 files changed, 55 insertions(+), 58 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 3582a0cd7..09741edbc 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -2,6 +2,9 @@ import { timerFlush } from "d3-timer"; import { calcConfidenceWidth } from "./confidence"; import { applyToChildren } from "./helpers"; +/* loop through the nodes and update each provided prop with the new value + * additionally, set d.update -> whether or not the node props changed + */ const updateNodesWithNewData = (nodes, newNodeProps) => { console.log("update nodes with data for these keys:", Object.keys(newNodeProps)); let tmp = 0; @@ -19,15 +22,21 @@ const updateNodesWithNewData = (nodes, newNodeProps) => { console.log("marking ", tmp, " nodes for update"); }; -const genericAttrs = { + +/* the following 4 dictionaries define which attrs & styles should (potentially) + * be applied to which class (e.g. ".tip"). They are either taken directly from the + * node props (e.g. "fill" is set to the node.fill value) or a custom function + * is defined here + */ +const setAttrViaNodeProps = { ".branch": new Set(), ".tip": new Set(["r"]) }; -const genericStyles = { +const setStyleViaNodeProps = { ".branch": new Set(["stroke", "stroke-width"]), ".tip": new Set(["fill", "visibility", "stroke"]) }; -const functionAttrs = { +const setAttrViaFunction = { ".tip": { cx: (d) => d.xTip, cy: (d) => d.yTip @@ -42,7 +51,7 @@ const functionAttrs = { d: (d) => d.confLine } }; -const functionStyles = { +const setStyleViaFunction = { ".vaccineDottedLine": { opacity: (d) => d.that.distance === "num_date" ? 1 : 0 }, @@ -53,36 +62,37 @@ const functionStyles = { }; /* Returns a function which can be called as part of a D3 chain in order to modify - * the appropriate attrs & styles for this treeElem + * the appropriate attrs & styles for this treeElem. + * This checks the above dictionaries to see which properties (attr || style) are + * valid for each treeElem (className). */ const createUpdateCall = (treeElem, properties) => (selection) => { /* generics - those which are simply taken from the node! */ - if (genericAttrs[treeElem]) { - [...properties].filter((x) => genericAttrs[treeElem].has(x)) + if (setAttrViaNodeProps[treeElem]) { + [...properties].filter((x) => setAttrViaNodeProps[treeElem].has(x)) .forEach((attrName) => { selection.attr(attrName, (d) => d[attrName]); }); } - if (genericStyles[treeElem]) { - [...properties].filter((x) => genericStyles[treeElem].has(x)) + if (setStyleViaNodeProps[treeElem]) { + [...properties].filter((x) => setStyleViaNodeProps[treeElem].has(x)) .forEach((styleName) => { selection.style(styleName, (d) => d[styleName]); }); } /* more complicated, functions defined above */ - if (functionAttrs[treeElem]) { - [...properties].filter((x) => functionAttrs[treeElem][x]) + if (setAttrViaFunction[treeElem]) { + [...properties].filter((x) => setAttrViaFunction[treeElem][x]) .forEach((attrName) => { - selection.attr(attrName, functionAttrs[treeElem][attrName]); + selection.attr(attrName, setAttrViaFunction[treeElem][attrName]); }); } - if (functionStyles[treeElem]) { - [...properties].filter((x) => functionStyles[treeElem][x]) + if (setStyleViaFunction[treeElem]) { + [...properties].filter((x) => setStyleViaFunction[treeElem][x]) .forEach((styleName) => { - selection.attr(styleName, functionStyles[treeElem][styleName]); + selection.attr(styleName, setStyleViaFunction[treeElem][styleName]); }); } - }; const genericSelectAndModify = (svg, treeElem, updateCall, transitionTime) => { @@ -98,45 +108,44 @@ const genericSelectAndModify = (svg, treeElem, updateCall, transitionTime) => { } }; +/* use D3 to select and modify elements, such that a given element is only ever modified _once_ + * @elemsToUpdate {set} - the class names to select, e.g. ".tip" or ".branch" + * @svgPropsToUpdate {set} - the props (styles & attrs) to update. The respective functions are defined above + * @transitionTime {INT} - in ms. if 0 then no transition (timerFlush is used) + * @extras {dict} - extra keywords to tell this function to call certain phyloTree update methods. In flux. + */ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime, extras) { - /* elements are often treated differently (of course!) */ let updateCall; + const classesToPotentiallyUpdate = [".tip", ".vaccineDottedLine", ".vaccineCross", ".branch"]; /* order is respected */ - /* order is respected */ - const generals = [".tip", ".vaccineDottedLine", ".vaccineCross", ".branch"]; - - /* treat stem / branch different, but combine normal .branch calls */ + /* treat stem / branch specially, but use these to replace a normal .branch call if that's also to be applied */ if (elemsToUpdate.has(".branch.S") || elemsToUpdate.has(".branch.T")) { - let branchOnlyUpdate = false; - if (elemsToUpdate.has(".branch")) { - generals.splice(generals.indexOf(".branch"), 1); // remove .branch from generals - branchOnlyUpdate = createUpdateCall(".branch", svgPropsToUpdate); - } - const generateCombinedUpdateCall = (subClass) => (selection) => { - if (branchOnlyUpdate) { - branchOnlyUpdate(selection); + const applyBranchPropsAlso = elemsToUpdate.has(".branch"); + if (applyBranchPropsAlso) classesToPotentiallyUpdate.splice(classesToPotentiallyUpdate.indexOf(".branch"), 1); + const ST = [".S", ".T"]; + ST.forEach((x, STidx) => { + if (elemsToUpdate.has(`.branch${x}`)) { + if (applyBranchPropsAlso) { + updateCall = (selection) => { + createUpdateCall(".branch", svgPropsToUpdate)(selection); /* the "normal" branch changes to apply */ + selection.attr("d", (d) => d.branch[STidx]); /* change the path (differs between .S and .T) */ + }; + } else { + updateCall = (selection) => {selection.attr("d", (d) => d.branch[STidx]);}; + } + genericSelectAndModify(this.svg, `.branch${x}`, updateCall, transitionTime); } - const subClassIdx = subClass === ".S" ? 0 : 1; /* is the path stored at d.branch[0] or d.branch[1] */ - selection.attr("d", (d) => d.branch[subClassIdx]); - }; - if (elemsToUpdate.has(".branch.S")) { - updateCall = generateCombinedUpdateCall(".S"); - genericSelectAndModify(this.svg, ".branch.S", updateCall, transitionTime); - } - if (elemsToUpdate.has(".branch.T")) { - updateCall = generateCombinedUpdateCall(".T"); - genericSelectAndModify(this.svg, ".branch.T", updateCall, transitionTime); - } + }); } - generals.forEach((el) => { + classesToPotentiallyUpdate.forEach((el) => { if (elemsToUpdate.has(el)) { updateCall = createUpdateCall(el, svgPropsToUpdate); genericSelectAndModify(this.svg, el, updateCall, transitionTime); } }); - /* non general */ + /* special cases not listed in classesToPotentiallyUpdate */ if (elemsToUpdate.has('.branchLabel')) { this.updateBranchLabels(); } @@ -151,7 +160,6 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra this.svg.selectAll(".regression").remove(); if (this.layout === "clock" && this.distance === "num_date") this.drawRegression(); } - /* confidences are hard */ if (extras.removeConfidences) { this.removeConfidence(transitionTime); @@ -192,7 +200,7 @@ export const change = function change({ branchThickness = undefined }) { console.log("\n** phylotree.change() (time since last run:", Date.now() - this.timeLastRenderRequested, "ms) **\n\n"); - + console.time("phylotree.change"); const elemsToUpdate = new Set(); const nodePropsToModify = {}; /* modify the actual data structure */ const svgPropsToUpdate = new Set(); /* modify the SVG */ @@ -277,4 +285,5 @@ export const change = function change({ this.modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime, extras); this.timeLastRenderRequested = Date.now(); + console.timeEnd("phylotree.change"); }; diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index ce5babbc5..6e70751d1 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -35,7 +35,8 @@ export const render = function render(svg, layout, distance, options, callbacks, /* draw functions */ if (this.params.showGrid) this.addGrid(); - if (this.params.branchLabels) this.drawBranches(); + this.drawBranches(); + if (this.params.branchLabels) this.drawCladeLabels(); this.updateBranchLabels(); this.drawTips(); if (this.vaccines) this.drawVaccines(); this.drawCladeLabels(); @@ -46,19 +47,6 @@ export const render = function render(svg, layout, distance, options, callbacks, this.svg.selectAll(".tip").style("visibility", (d) => d["visibility"]); timerEnd("setVisibility"); } - - // setting branchLabels and tipLabels to false above in params is not working for some react-dimensions - // hence the commenting here - // if (this.params.branchLabels){ - // this.drawBranchLabels(); - // } - /* don't even bother initially - there will be too many! */ - // if (this.params.tipLabels){ - // this.updateTipLabels(100); - // } - - this.updateGeometry(10); - this.svg.selectAll(".regression").remove(); if (this.layout === "clock" && this.distance === "num_date") { this.drawRegression(); From b070bfc088b89518e3c8c83c5324a5755cdb6994 Mon Sep 17 00:00:00 2001 From: james hadfield Date: Fri, 16 Feb 2018 14:35:34 -0800 Subject: [PATCH 14/25] partial implementation of changePageQuery --- src/actions/navigation.js | 74 ++++++++++++++++++++----- src/actions/treeProperties.js | 2 +- src/actions/types.js | 1 + src/components/tree/phyloTree/change.js | 2 +- src/reducers/controls.js | 9 ++- src/reducers/tree.js | 2 + src/store/index.js | 2 +- src/util/globals.js | 2 +- 8 files changed, 73 insertions(+), 21 deletions(-) diff --git a/src/actions/navigation.js b/src/actions/navigation.js index 84b797963..f2c4b21cc 100644 --- a/src/actions/navigation.js +++ b/src/actions/navigation.js @@ -1,10 +1,10 @@ import parseParams from "../util/parseParams"; import queryString from "query-string"; import { getPost } from "../util/getMarkdown"; -import { PAGE_CHANGE, URL_QUERY_CHANGE } from "./types"; -import { updateVisibleTipsAndBranchThicknesses } from "./treeProperties"; +import { PAGE_CHANGE, URL_QUERY_CHANGE, URL_QUERY_CHANGE_WITH_COMPUTED_STATE } from "./types"; +import { updateVisibleTipsAndBranchThicknesses, calculateVisiblityAndBranchThickness } from "./treeProperties"; import { changeColorBy } from "./colors"; - +import { restoreQueryableStateToDefaults, modifyStateViaURLQuery, checkAndCorrectErrorsInState } from "../reducers/controls"; // make prefix for data files with fields joined by _ instead of / as in URL const makeDataPathFromParsedParams = (parsedParams) => { const tmp_levels = Object.keys(parsedParams.dataset).map((d) => parsedParams.dataset[d]); @@ -77,22 +77,68 @@ ARGUMENTS: (1) query - REQUIRED - {object} (2) push - OPTIONAL (default: true) - signals that pushState should be used (has no effect on the reducers) */ +// export const changePageQuery = ({query, push = true}) => (dispatch, getState) => { +// console.log("\t---------- change page query -------------"); +// const { controls, metadata } = getState(); +// dispatch({ +// type: URL_QUERY_CHANGE, +// query, +// metadata, +// pushState: push +// }); +// const newState = getState(); +// /* working out whether visibility / thickness needs updating is tricky - ideally we could avoid this dispatch in some situations */ +// dispatch(updateVisibleTipsAndBranchThicknesses()); +// if (controls.colorBy !== newState.controls.colorBy) { +// dispatch(changeColorBy()); +// } +// }; + + + + export const changePageQuery = ({query, push = true}) => (dispatch, getState) => { - const { controls, metadata } = getState(); + console.log("\t---------- change page query -------------"); + const { controls, metadata, tree } = getState(); + + /* 1 - calculate entire new state of the controls reducer */ + let newControls = Object.assign({}, controls); + newControls = restoreQueryableStateToDefaults(newControls); + newControls = modifyStateViaURLQuery(newControls, query); + newControls = checkAndCorrectErrorsInState(newControls, metadata); + + /* 2 - calculate new branch thicknesses & visibility */ + const visAndThicknessData = calculateVisiblityAndBranchThickness( + tree, + newControls, + {dateMinNumeric: newControls.dateMinNumeric, dateMaxNumeric: newControls.dateMaxNumeric}, + {tipSelectedIdx: 0, validIdxRoot: tree.idxOfInViewRootNode} + ); + visAndThicknessData.stateCountAttrs = Object.keys(newControls.filters); + + /* 3 - calculate colours */ + + + /* 4 - calculate entropy in view */ + + + dispatch({ - type: URL_QUERY_CHANGE, - query, - metadata, - pushState: push + type: URL_QUERY_CHANGE_WITH_COMPUTED_STATE, + newControls, + pushState: push, + newTree: Object.assign({}, tree, visAndThicknessData) }); - const newState = getState(); - /* working out whether visibility / thickness needs updating is tricky - ideally we could avoid this dispatch in some situations */ - dispatch(updateVisibleTipsAndBranchThicknesses()); - if (controls.colorBy !== newState.controls.colorBy) { - dispatch(changeColorBy()); - } + // const newState = getState(); + // /* working out whether visibility / thickness needs updating is tricky - ideally we could avoid this dispatch in some situations */ + // dispatch(updateVisibleTipsAndBranchThicknesses()); + // if (controls.colorBy !== newState.controls.colorBy) { + // dispatch(changeColorBy()); + // } }; + + export const browserBackForward = () => (dispatch, getState) => { const { datasets } = getState(); /* if the pathname has changed, trigger the changePage action (will trigger new post to load, new dataset to load, etc) */ diff --git a/src/actions/treeProperties.js b/src/actions/treeProperties.js index 39c8fc91f..c5d01f1e4 100644 --- a/src/actions/treeProperties.js +++ b/src/actions/treeProperties.js @@ -8,7 +8,7 @@ import { updateEntropyVisibility } from "./entropy"; import { calendarToNumeric } from "../util/dateHelpers"; import { applyToChildren } from "../components/tree/phyloTree/helpers"; -const calculateVisiblityAndBranchThickness = (tree, controls, dates, {idxOfInViewRootNode = 0, tipSelectedIdx = 0} = {}) => { +export const calculateVisiblityAndBranchThickness = (tree, controls, dates, {idxOfInViewRootNode = 0, tipSelectedIdx = 0} = {}) => { const visibility = tipSelectedIdx ? identifyPathToTip(tree.nodes, tipSelectedIdx) : calcVisibility(tree, controls, dates); /* recalculate tipCounts over the tree - modifies redux tree nodes in place (yeah, I know) */ calcTipCounts(tree.nodes[0], visibility); diff --git a/src/actions/types.js b/src/actions/types.js index 1c1e52a09..bda3b016e 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -51,3 +51,4 @@ export const PAGE_CHANGE = "PAGE_CHANGE"; export const NEW_POST = "NEW_POST"; export const URL = "URL"; export const MIDDLEWARE_ONLY_ANIMATION_STARTED = "MIDDLEWARE_ONLY_ANIMATION_STARTED"; +export const URL_QUERY_CHANGE_WITH_COMPUTED_STATE = "URL_QUERY_CHANGE_WITH_COMPUTED_STATE"; diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 09741edbc..e819a53fe 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -117,7 +117,7 @@ const genericSelectAndModify = (svg, treeElem, updateCall, transitionTime) => { export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime, extras) { let updateCall; const classesToPotentiallyUpdate = [".tip", ".vaccineDottedLine", ".vaccineCross", ".branch"]; /* order is respected */ - + console.log("modifying these elems", elemsToUpdate) /* treat stem / branch specially, but use these to replace a normal .branch call if that's also to be applied */ if (elemsToUpdate.has(".branch.S") || elemsToUpdate.has(".branch.T")) { const applyBranchPropsAlso = elemsToUpdate.has(".branch"); diff --git a/src/reducers/controls.js b/src/reducers/controls.js index edfd2ff9f..e92b068e7 100644 --- a/src/reducers/controls.js +++ b/src/reducers/controls.js @@ -38,7 +38,7 @@ const getMaxCalDateViaTree = (tree) => { }; /* need a (better) way to keep the queryParams all in "sync" */ -const modifyStateViaURLQuery = (state, query) => { +export const modifyStateViaURLQuery = (state, query) => { // console.log("Query incoming: ", query); if (query.l) { state["layout"] = query.l; @@ -85,7 +85,7 @@ const modifyStateViaURLQuery = (state, query) => { return state; }; -const restoreQueryableStateToDefaults = (state) => { +export const restoreQueryableStateToDefaults = (state) => { for (const key of Object.keys(state.defaults)) { switch (typeof state.defaults[key]) { case "string": { @@ -204,7 +204,7 @@ const modifyStateViaTree = (state, tree) => { return state; }; -const checkAndCorrectErrorsInState = (state, metadata) => { +export const checkAndCorrectErrorsInState = (state, metadata) => { /* The one (bigish) problem with this being in the reducer is that we can't have any side effects. So if we detect and error introduced by a URL QUERY (and correct it in state), we can't correct the URL */ @@ -310,6 +310,9 @@ const Controls = (state = getDefaultState(), action) => { newState = checkAndCorrectErrorsInState(newState, action.metadata); return newState; } + case types.URL_QUERY_CHANGE_WITH_COMPUTED_STATE: { + return action.newControls; + } case types.NEW_DATASET: { let base = getDefaultState(); base = modifyStateViaTree(base, action.tree); diff --git a/src/reducers/tree.js b/src/reducers/tree.js index e2ce51c7c..6c53da728 100644 --- a/src/reducers/tree.js +++ b/src/reducers/tree.js @@ -84,6 +84,8 @@ const Tree = (state = getDefaultState(), action) => { tipRadii: action.data, tipRadiiVersion: action.version }); + case types.URL_QUERY_CHANGE_WITH_COMPUTED_STATE: + return action.newTree; case types.NEW_COLORS: return Object.assign({}, state, { nodeColors: action.nodeColors, diff --git a/src/store/index.js b/src/store/index.js index 327b36f44..631ee0d75 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -7,7 +7,7 @@ import { loggingMiddleware } from "../middleware/logActions"; // eslint-disable- const middleware = [ thunk, changeURLMiddleware, // eslint-disable-line comma-dangle - // loggingMiddleware + loggingMiddleware ]; let CreateStoreWithMiddleware; diff --git a/src/util/globals.js b/src/util/globals.js index 6511938ec..99d201d2b 100644 --- a/src/util/globals.js +++ b/src/util/globals.js @@ -55,7 +55,7 @@ export const defaultDistanceMeasures = ["num_date", "div"]; export const fastTransitionDuration = 350; // in milliseconds export const mediumTransitionDuration = 700; // in milliseconds export const slowTransitionDuration = 1400; // in milliseconds -export const enableNarratives = false; +export const enableNarratives = true; export const narrativeWidth = 500; export const animationWindowWidth = 0.075; // width of animation window relative to date slider export const animationTick = 50; // animation tick in milliseconds From 35bda6b9286edfe455d9641282de2f7e0cc66745 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 10:13:01 -0800 Subject: [PATCH 15/25] changePageQuery is now a single action --- src/actions/colors.js | 36 ++++++++++++----------- src/actions/navigation.js | 58 ++++++++++++++----------------------- src/actions/types.js | 1 - src/middleware/changeURL.js | 2 +- src/reducers/controls.js | 11 +------ src/reducers/entropy.js | 6 ++++ 6 files changed, 48 insertions(+), 66 deletions(-) diff --git a/src/actions/colors.js b/src/actions/colors.js index 9b99ba388..7061c3751 100644 --- a/src/actions/colors.js +++ b/src/actions/colors.js @@ -7,6 +7,24 @@ import { timerStart, timerEnd } from "../util/perf"; import { updateEntropyVisibility } from "./entropy"; import * as types from "./types"; +export const calcColorScaleAndNodeColors = (colorBy, controls, tree, metadata) => { + let genotype; + if (colorBy.slice(0, 3) === "gt-" && controls.geneLength) { + const x = parseGenotype(colorBy, controls.geneLength); + setGenotype(tree.nodes, x[0][0], x[0][1] + 1); /* modifies nodes recursively */ + genotype = parseGenotype(colorBy, controls.geneLength); + } + + /* step 1: calculate the required colour scale */ + const version = controls.colorScale === undefined ? 1 : controls.colorScale.version + 1; + const colorScale = getColorScale(colorBy, tree, controls.geneLength, metadata.colorOptions, version, controls.absoluteDateMaxNumeric); + if (genotype) colorScale.genotype = genotype; + + /* step 2: calculate the node colours */ + const nodeColors = calcNodeColor(tree, colorScale); + return {nodeColors, colorScale, version}; +}; + /* providedColorBy: undefined | string */ export const changeColorBy = (providedColorBy = undefined) => { // eslint-disable-line import/prefer-default-export return (dispatch, getState) => { @@ -23,23 +41,7 @@ export const changeColorBy = (providedColorBy = undefined) => { // eslint-disabl return null; } const colorBy = providedColorBy ? providedColorBy : controls.colorBy; - - if (colorBy.slice(0, 3) === "gt-") { - const x = parseGenotype(colorBy, controls.geneLength); - setGenotype(tree.nodes, x[0][0], x[0][1] + 1); - } - - /* step 1: calculate the required colour scale */ - const version = controls.colorScale === undefined ? 1 : controls.colorScale.version + 1; - // console.log("updateColorScale setting colorScale to ", version); - const colorScale = getColorScale(colorBy, tree, controls.geneLength, metadata.colorOptions, version, controls.absoluteDateMaxNumeric); - /* */ - if (colorBy.slice(0, 3) === "gt-" && controls.geneLength) { - colorScale.genotype = parseGenotype(colorBy, controls.geneLength); - } - - /* step 2: calculate the node colours */ - const nodeColors = calcNodeColor(tree, colorScale); + const {nodeColors, colorScale, version} = calcColorScaleAndNodeColors(colorBy, controls, tree, metadata); /* step 3: change in mutType? */ const newMutType = determineColorByGenotypeType(colorBy) !== controls.mutType ? determineColorByGenotypeType(colorBy) : false; diff --git a/src/actions/navigation.js b/src/actions/navigation.js index f2c4b21cc..2677f725e 100644 --- a/src/actions/navigation.js +++ b/src/actions/navigation.js @@ -1,10 +1,12 @@ -import parseParams from "../util/parseParams"; import queryString from "query-string"; +import parseParams from "../util/parseParams"; import { getPost } from "../util/getMarkdown"; -import { PAGE_CHANGE, URL_QUERY_CHANGE, URL_QUERY_CHANGE_WITH_COMPUTED_STATE } from "./types"; -import { updateVisibleTipsAndBranchThicknesses, calculateVisiblityAndBranchThickness } from "./treeProperties"; -import { changeColorBy } from "./colors"; -import { restoreQueryableStateToDefaults, modifyStateViaURLQuery, checkAndCorrectErrorsInState } from "../reducers/controls"; +import { PAGE_CHANGE, URL_QUERY_CHANGE_WITH_COMPUTED_STATE } from "./types"; +import { calculateVisiblityAndBranchThickness } from "./treeProperties"; +import { calcColorScaleAndNodeColors } from "./colors"; +import { restoreQueryableStateToDefaults, modifyStateViaURLQuery, checkAndCorrectErrorsInState, checkColorByConfidence } from "../reducers/controls"; +import { calcEntropyInView } from "../util/treeTraversals"; + // make prefix for data files with fields joined by _ instead of / as in URL const makeDataPathFromParsedParams = (parsedParams) => { const tmp_levels = Object.keys(parsedParams.dataset).map((d) => parsedParams.dataset[d]); @@ -77,29 +79,9 @@ ARGUMENTS: (1) query - REQUIRED - {object} (2) push - OPTIONAL (default: true) - signals that pushState should be used (has no effect on the reducers) */ -// export const changePageQuery = ({query, push = true}) => (dispatch, getState) => { -// console.log("\t---------- change page query -------------"); -// const { controls, metadata } = getState(); -// dispatch({ -// type: URL_QUERY_CHANGE, -// query, -// metadata, -// pushState: push -// }); -// const newState = getState(); -// /* working out whether visibility / thickness needs updating is tricky - ideally we could avoid this dispatch in some situations */ -// dispatch(updateVisibleTipsAndBranchThicknesses()); -// if (controls.colorBy !== newState.controls.colorBy) { -// dispatch(changeColorBy()); -// } -// }; - - - - export const changePageQuery = ({query, push = true}) => (dispatch, getState) => { console.log("\t---------- change page query -------------"); - const { controls, metadata, tree } = getState(); + const { controls, metadata, tree, entropy } = getState(); /* 1 - calculate entire new state of the controls reducer */ let newControls = Object.assign({}, controls); @@ -115,30 +97,32 @@ export const changePageQuery = ({query, push = true}) => (dispatch, getState) => {tipSelectedIdx: 0, validIdxRoot: tree.idxOfInViewRootNode} ); visAndThicknessData.stateCountAttrs = Object.keys(newControls.filters); + const newTree = Object.assign({}, tree, visAndThicknessData); /* 3 - calculate colours */ - + if (controls.colorBy !== newControls.colorBy) { + const {nodeColors, colorScale, version} = calcColorScaleAndNodeColors(newControls.colorBy, newControls, tree, metadata); + newControls.colorScale = colorScale; + newControls.colorByConfidence = checkColorByConfidence(newControls.attrs, newControls.colorBy); + newTree.nodeColorsVersion = version; + newTree.nodeColors = nodeColors; + } /* 4 - calculate entropy in view */ - - + const [entropyBars, entropyMaxYVal] = calcEntropyInView(newTree.nodes, newTree.visibility, newControls.mutType, entropy.geneMap, entropy.showCounts); dispatch({ type: URL_QUERY_CHANGE_WITH_COMPUTED_STATE, newControls, pushState: push, - newTree: Object.assign({}, tree, visAndThicknessData) + newTree, + entropyBars, + entropyMaxYVal, + query }); - // const newState = getState(); - // /* working out whether visibility / thickness needs updating is tricky - ideally we could avoid this dispatch in some situations */ - // dispatch(updateVisibleTipsAndBranchThicknesses()); - // if (controls.colorBy !== newState.controls.colorBy) { - // dispatch(changeColorBy()); - // } }; - export const browserBackForward = () => (dispatch, getState) => { const { datasets } = getState(); /* if the pathname has changed, trigger the changePage action (will trigger new post to load, new dataset to load, etc) */ diff --git a/src/actions/types.js b/src/actions/types.js index bda3b016e..e318dedee 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -42,7 +42,6 @@ export const ADD_COLOR_BYS = "ADD_COLOR_BYS"; export const MANIFEST_RECEIVED = "MANIFEST_RECEIVED"; export const POSTS_MANIFEST_RECEIVED = "POSTS_MANIFEST_RECEIVED"; export const CHANGE_TREE_ROOT_IDX = "CHANGE_TREE_ROOT_IDX"; -export const URL_QUERY_CHANGE = "URL_QUERY_CHANGE"; export const NEW_NARRATIVE = "NEW_NARRATIVE"; export const TOGGLE_NARRATIVE = "TOGGLE_NARRATIVE"; export const ENTROPY_DATA = "ENTROPY_DATA"; diff --git a/src/middleware/changeURL.js b/src/middleware/changeURL.js index 14c4c59ae..9cb88092e 100644 --- a/src/middleware/changeURL.js +++ b/src/middleware/changeURL.js @@ -75,7 +75,7 @@ export const changeURLMiddleware = (store) => (next) => (action) => { const e = state.controls.mapAnimationDurationInMilliseconds; query.animate = `${a},${b},${c},${d},${e}`; break; - case types.URL_QUERY_CHANGE: + case types.URL_QUERY_CHANGE_WITH_COMPUTED_STATE: query = action.query; break; case types.PAGE_CHANGE: diff --git a/src/reducers/controls.js b/src/reducers/controls.js index e92b068e7..9b59ada0f 100644 --- a/src/reducers/controls.js +++ b/src/reducers/controls.js @@ -12,7 +12,7 @@ import { defaultGeoResolution, import * as types from "../actions/types"; import { calcBrowserDimensionsInitialState } from "./browserDimensions"; -const checkColorByConfidence = (attrs, colorBy) => { +export const checkColorByConfidence = (attrs, colorBy) => { return colorBy !== "num_date" && attrs.indexOf(colorBy + "_confidence") > -1; }; @@ -301,15 +301,6 @@ const getDefaultState = () => { const Controls = (state = getDefaultState(), action) => { switch (action.type) { - case types.URL_QUERY_CHANGE: { - /* because the qury isn't complete (e.g. things that are "off" or "default" aren't shown) - we must first reset all the state to the "base" (default) state, and then apply the changes defined in the query */ - let newState = Object.assign({}, state); - newState = restoreQueryableStateToDefaults(newState); - newState = modifyStateViaURLQuery(newState, action.query); - newState = checkAndCorrectErrorsInState(newState, action.metadata); - return newState; - } case types.URL_QUERY_CHANGE_WITH_COMPUTED_STATE: { return action.newControls; } diff --git a/src/reducers/entropy.js b/src/reducers/entropy.js index c3368967c..ea9b3284a 100644 --- a/src/reducers/entropy.js +++ b/src/reducers/entropy.js @@ -60,6 +60,12 @@ const Entropy = (state = {loaded: false, showCounts: false}, action) => { bars: action.data, maxYVal: action.maxYVal }); + case types.URL_QUERY_CHANGE_WITH_COMPUTED_STATE: + return Object.assign({}, state, { + loaded: true, + bars: action.entropyBars, + maxYVal: action.entropyMaxYVal + }); case types.ENTROPY_COUNTS_TOGGLE: return Object.assign({}, state, { showCounts: action.showCounts From ea3517395e8e35f8c3075c213361d0f965bd827c Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 10:29:04 -0800 Subject: [PATCH 16/25] deprecate code --- src/components/tree/phyloTree/confidence.js | 15 ---- .../{generalUpdates.js => deprecated.js} | 73 ++++++++++++++++++- src/components/tree/phyloTree/grid.js | 7 -- src/components/tree/phyloTree/phyloTree.js | 15 +--- src/components/tree/phyloTree/zoom.js | 33 --------- .../tree/reactD3Interface/callbacks.js | 44 ----------- src/reducers/tree.js | 7 +- 7 files changed, 73 insertions(+), 121 deletions(-) rename src/components/tree/phyloTree/{generalUpdates.js => deprecated.js} (82%) diff --git a/src/components/tree/phyloTree/confidence.js b/src/components/tree/phyloTree/confidence.js index ed1ca392f..baf58dbb5 100644 --- a/src/components/tree/phyloTree/confidence.js +++ b/src/components/tree/phyloTree/confidence.js @@ -47,18 +47,3 @@ export const drawSingleCI = function drawSingleCI(selection, opacity) { .style("fill", "none") .style("stroke-width", calcConfidenceWidth); }; - - -export const updateConfidence = function updateConfidence(dt) { - console.warn("updateConfidence is deprecated."); - if (dt) { - this.svg.selectAll(".conf") - .transition().duration(dt) - .style("stroke", (el) => el.stroke) - .style("stroke-width", calcConfidenceWidth); - } else { - this.svg.selectAll(".conf") - .style("stroke", (el) => el.stroke) - .style("stroke-width", calcConfidenceWidth); - } -}; diff --git a/src/components/tree/phyloTree/generalUpdates.js b/src/components/tree/phyloTree/deprecated.js similarity index 82% rename from src/components/tree/phyloTree/generalUpdates.js rename to src/components/tree/phyloTree/deprecated.js index 91e21fbd8..509d9d46f 100644 --- a/src/components/tree/phyloTree/generalUpdates.js +++ b/src/components/tree/phyloTree/deprecated.js @@ -1,6 +1,66 @@ -import { timerStart, timerEnd } from "../../../util/perf"; +/* eslint-disable */ +/* these functions are either deprecated or were in the code base but never called! */ -const contains = (array, elem) => array.some((d) => d === elem); +export const updateConfidence = function updateConfidence(dt) { + console.warn("updateConfidence is deprecated."); + if (dt) { + this.svg.selectAll(".conf") + .transition().duration(dt) + .style("stroke", (el) => el.stroke) + .style("stroke-width", calcConfidenceWidth); + } else { + this.svg.selectAll(".conf") + .style("stroke", (el) => el.stroke) + .style("stroke-width", calcConfidenceWidth); + } +}; + +export const viewEntireTree = function viewEntireTree() { + console.warn("viewEntireTree is deprecated.") + /* reset the SVGPanZoom */ + this.Viewer.fitToViewer(); + /* imperitively manipulate SVG tree elements */ + this.state.tree.zoomIntoClade(this.state.tree.nodes[0], mediumTransitionDuration); + /* update branch thicknesses / tip vis after SVG tree elemtents have moved */ + window.setTimeout( + () => this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: 0})), + mediumTransitionDuration + ); + this.setState({selectedBranch: null, selectedTip: null}); +}; + +/** + * zoom such that a particular clade fills the svg + * @param clade -- branch/node at the root of the clade to zoom into + * @param dt -- time of the transition in milliseconds + * @return {null} + */ +export const zoomIntoClade = function zoomIntoClade(clade, dt) { + console.warn("zoomIntoClade is deprecated. Please use phylotree.change()"); + // assign all nodes to inView false and force update + this.zoomNode = clade; + this.nodes.forEach((d) => { + d.inView = false; + d.update = true; + }); + // assign all child nodes of the chosen clade to inView=true + // if clade is terminal, apply to parent + if (clade.terminal) { + applyToChildren(clade.parent, (d) => {d.inView = true;}); + } else { + applyToChildren(clade, (d) => {d.inView = true;}); + } + // redraw + this.mapToScreen(); + this.updateGeometry(dt); + if (this.grid) this.addGrid(this.layout); + this.svg.selectAll(".regression").remove(); + if (this.layout === "clock" && this.distance === "num_date") this.drawRegression(); + if (this.params.branchLabels) { + this.updateBranchLabels(dt); + } + this.updateTipLabels(dt); +}; /** * as updateAttributeArray, but accepts a callback function rather than an array @@ -17,6 +77,8 @@ export const updateStyleOrAttribute = function updateStyleOrAttribute(treeElem, this.updateStyleOrAttributeArray(treeElem, attr, attr_array, dt, styleOrAttribute); }; +const contains = (array, elem) => array.some((d) => d === elem); + /** * update an attribute of the tree for all nodes * @param treeElem --- the part of the tree to update (.tip, .branch) @@ -328,3 +390,10 @@ console.warn("redrawStyle is deprecated. use phylotree.change instead.") .transition().duration(dt) .style(styleElem, (d) => d[styleElem]); }; + +export const removeGrid = function removeGrid() { + this.svg.selectAll(".majorGrid").remove(); + this.svg.selectAll(".minorGrid").remove(); + this.svg.selectAll(".gridTick").remove(); + this.grid = false; +}; diff --git a/src/components/tree/phyloTree/grid.js b/src/components/tree/phyloTree/grid.js index 827624363..f5a822ed8 100644 --- a/src/components/tree/phyloTree/grid.js +++ b/src/components/tree/phyloTree/grid.js @@ -2,13 +2,6 @@ import { max } from "d3-array"; import { timerStart, timerEnd } from "../../../util/perf"; -export const removeGrid = function removeGrid() { - this.svg.selectAll(".majorGrid").remove(); - this.svg.selectAll(".minorGrid").remove(); - this.svg.selectAll(".gridTick").remove(); - this.grid = false; -}; - export const hideGrid = function hideGrid() { this.svg.selectAll(".majorGrid").style('visibility', 'hidden'); this.svg.selectAll(".minorGrid").style('visibility', 'hidden'); diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js index 56dc9b903..adcde6388 100644 --- a/src/components/tree/phyloTree/phyloTree.js +++ b/src/components/tree/phyloTree/phyloTree.js @@ -12,7 +12,6 @@ import * as zoom from "./zoom"; import * as grid from "./grid"; import * as confidence from "./confidence"; import * as labels from "./labels"; -import * as generalUpdates from "./generalUpdates"; /* phylogenetic tree drawing function - the actual tree is rendered by the render prototype */ @@ -50,6 +49,7 @@ const PhyloTree = function PhyloTree(reduxNodes) { {leading: false, trailing: true, maxWait: this.params.mapToScreenDebounceTime}); }; +/* C H A N G E */ PhyloTree.prototype.change = change; PhyloTree.prototype.modifySVG = modifySVG; @@ -79,18 +79,6 @@ PhyloTree.prototype.drawConfidence = confidence.drawConfidence; PhyloTree.prototype.drawSingleCI = confidence.drawSingleCI; PhyloTree.prototype.updateConfidence = confidence.updateConfidence; -/* G E N E R A L U P D A T E S */ -PhyloTree.prototype.updateDistance = generalUpdates.updateDistance; -PhyloTree.prototype.updateLayout = generalUpdates.updateLayout; -PhyloTree.prototype.updateGeometry = generalUpdates.updateGeometry; -PhyloTree.prototype.updateGeometryFade = generalUpdates.updateGeometryFade; -PhyloTree.prototype.updateTimeBar = () => {}; -PhyloTree.prototype.updateMultipleArray = generalUpdates.updateMultipleArray; -PhyloTree.prototype.updateStyleOrAttribute = generalUpdates.updateStyleOrAttribute; -PhyloTree.prototype.updateStyleOrAttributeArray = generalUpdates.updateStyleOrAttributeArray; -PhyloTree.prototype.redrawAttribute = generalUpdates.redrawAttribute; -PhyloTree.prototype.redrawStyle = generalUpdates.redrawStyle; - /* L A B E L S ( T I P , B R A N C H , C O N F I D E N C E ) */ PhyloTree.prototype.drawCladeLabels = labels.drawCladeLabels; PhyloTree.prototype.updateBranchLabels = labels.updateBranchLabels; @@ -100,7 +88,6 @@ PhyloTree.prototype.showBranchLabels = labels.showBranchLabels; /* G R I D */ PhyloTree.prototype.hideGrid = grid.hideGrid; -PhyloTree.prototype.removeGrid = grid.removeGrid; PhyloTree.prototype.addGrid = grid.addGrid; /* Z O O M , F I T TO S C R E E N , E T C */ diff --git a/src/components/tree/phyloTree/zoom.js b/src/components/tree/phyloTree/zoom.js index f60bc2c2b..6039d284e 100644 --- a/src/components/tree/phyloTree/zoom.js +++ b/src/components/tree/phyloTree/zoom.js @@ -3,39 +3,6 @@ import { min, max } from "d3-array"; import { applyToChildren } from "./helpers"; import { timerStart, timerEnd } from "../../../util/perf"; -/** - * zoom such that a particular clade fills the svg - * @param clade -- branch/node at the root of the clade to zoom into - * @param dt -- time of the transition in milliseconds - * @return {null} - */ -export const zoomIntoClade = function zoomIntoClade(clade, dt) { - console.warn("zoomIntoClade is deprecated. Please use phylotree.change()"); - // assign all nodes to inView false and force update - this.zoomNode = clade; - this.nodes.forEach((d) => { - d.inView = false; - d.update = true; - }); - // assign all child nodes of the chosen clade to inView=true - // if clade is terminal, apply to parent - if (clade.terminal) { - applyToChildren(clade.parent, (d) => {d.inView = true;}); - } else { - applyToChildren(clade, (d) => {d.inView = true;}); - } - // redraw - this.mapToScreen(); - this.updateGeometry(dt); - if (this.grid) this.addGrid(this.layout); - this.svg.selectAll(".regression").remove(); - if (this.layout === "clock" && this.distance === "num_date") this.drawRegression(); - if (this.params.branchLabels) { - this.updateBranchLabels(dt); - } - this.updateTipLabels(dt); -}; - /** * zoom out a little by using the parent of the current clade * as a zoom focus. diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index 12ef35d87..11bb25e6c 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -53,35 +53,7 @@ export const onBranchHover = function onBranchHover(d, x, y) { export const onBranchClick = function onBranchClick(d) { this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: d.n.arrayIdx})); - // this.setState({hovered: null, selectedBranch: d}); - // this.Viewer.fitToViewer(); - // this.state.tree.zoomIntoClade(d, mediumTransitionDuration); - // /* to stop multiple phyloTree updates potentially clashing, - // we change tipVis after geometry update + transition */ - // window.setTimeout( - // () => this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: d.n.arrayIdx})), - // mediumTransitionDuration - // ); - // this.setState({ - // hovered: null, - // selectedBranch: d - // }); }; -// -export const viewEntireTree = function viewEntireTree() { - console.warn("viewEntireTree is deprecated.") - /* reset the SVGPanZoom */ - this.Viewer.fitToViewer(); - /* imperitively manipulate SVG tree elements */ - this.state.tree.zoomIntoClade(this.state.tree.nodes[0], mediumTransitionDuration); - /* update branch thicknesses / tip vis after SVG tree elemtents have moved */ - window.setTimeout( - () => this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: 0})), - mediumTransitionDuration - ); - this.setState({selectedBranch: null, selectedTip: null}); -}; - /* onBranchLeave called when mouse-off, i.e. anti-hover */ export const onBranchLeave = function onBranchLeave(d) { @@ -141,7 +113,6 @@ const resetGrid = function resetGrid() { } }; - export const onViewerChange = function onViewerChange() { if (this.Viewer && this.state.tree) { const V = this.Viewer.getValue(); @@ -157,21 +128,6 @@ export const resetView = function resetView() { this.Viewer.fitToViewer(); }; - -/* viewEntireTree: go back to the root! */ -// export const viewEntireTree = function viewEntireTree() { -// /* reset the SVGPanZoom */ -// this.Viewer.fitToViewer(); -// /* imperitively manipulate SVG tree elements */ -// this.state.tree.zoomIntoClade(this.state.tree.nodes[0], mediumTransitionDuration); -// /* update branch thicknesses / tip vis after SVG tree elemtents have moved */ -// window.setTimeout( -// () => this.props.dispatch(updateVisibleTipsAndBranchThicknesses({idxOfInViewRootNode: 0})), -// mediumTransitionDuration -// ); -// this.setState({selectedBranch: null, selectedTip: null}); -// }; - export const handleIconClickHOF = function handleIconClickHOF(tool) { return () => { const V = this.Viewer.getValue(); diff --git a/src/reducers/tree.js b/src/reducers/tree.js index 6c53da728..51f1873c4 100644 --- a/src/reducers/tree.js +++ b/src/reducers/tree.js @@ -59,11 +59,6 @@ const Tree = (state = getDefaultState(), action) => { return Object.assign({}, state, { loaded: false }); - case types.CHANGE_TREE_ROOT_IDX: - console.warn("CHANGE_TREE_ROOT_IDX is deprecated"); - return Object.assign({}, state, { - idxOfInViewRootNode: action.idxOfInViewRootNode - }); case types.CHANGE_DATES_VISIBILITY_THICKNESS: /* fall-through */ case types.UPDATE_VISIBILITY_AND_BRANCH_THICKNESS: const newStates = { @@ -73,7 +68,7 @@ const Tree = (state = getDefaultState(), action) => { branchThicknessVersion: action.branchThicknessVersion, idxOfInViewRootNode: action.idxOfInViewRootNode, visibleStateCounts: getValuesAndCountsOfVisibleTraitsFromTree(state.nodes, action.visibility, action.stateCountAttrs) - } + }; /* we only want to calculate totalStateCounts on the first pass */ if (!state.loaded) { newStates.totalStateCounts = getAllValuesAndCountsOfTraitsFromTree(state.nodes, action.stateCountAttrs); From 970568268dd823b0b737be83549bf660e72256e7 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 11:15:10 -0800 Subject: [PATCH 17/25] implement zoomToParent within phylotree.changes --- src/components/tree/phyloTree/change.js | 4 +- src/components/tree/phyloTree/deprecated.js | 12 ++ src/components/tree/phyloTree/layouts.js | 102 +++++++++++++++- src/components/tree/phyloTree/phyloTree.js | 7 +- src/components/tree/phyloTree/zoom.js | 115 ------------------ .../tree/reactD3Interface/callbacks.js | 20 +-- 6 files changed, 119 insertions(+), 141 deletions(-) delete mode 100644 src/components/tree/phyloTree/zoom.js diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index e819a53fe..3cb495118 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -257,8 +257,8 @@ export const change = function change({ d.update = true; }); /* if clade is terminal, use the parent as the zoom node */ - const zoomNode = zoomIntoClade.terminal ? zoomIntoClade.parent : zoomIntoClade; - applyToChildren(zoomNode, (d) => {d.inView = true;}); + this.zoomNode = zoomIntoClade.terminal ? zoomIntoClade.parent : zoomIntoClade; + applyToChildren(this.zoomNode, (d) => {d.inView = true;}); } if (svgHasChangedDimensions) { this.nodes.forEach((d) => {d.update = true;}); diff --git a/src/components/tree/phyloTree/deprecated.js b/src/components/tree/phyloTree/deprecated.js index 509d9d46f..6718b755c 100644 --- a/src/components/tree/phyloTree/deprecated.js +++ b/src/components/tree/phyloTree/deprecated.js @@ -29,6 +29,18 @@ export const viewEntireTree = function viewEntireTree() { this.setState({selectedBranch: null, selectedTip: null}); }; +/** + * zoom out a little by using the parent of the current clade + * as a zoom focus. + * @param {int} dt [transition time] + */ +export const zoomToParent = function zoomToParent(dt) { + console.warn("TODO: zoomToParent functionality needs to be built into phylotree.change()"); + if (this.zoomNode) { + this.zoomIntoClade(this.zoomNode.parent, dt); + } +}; + /** * zoom such that a particular clade fills the svg * @param clade -- branch/node at the root of the clade to zoom into diff --git a/src/components/tree/phyloTree/layouts.js b/src/components/tree/phyloTree/layouts.js index 9f189bf27..e0ca1a308 100644 --- a/src/components/tree/phyloTree/layouts.js +++ b/src/components/tree/phyloTree/layouts.js @@ -1,5 +1,6 @@ /* eslint-disable no-multi-spaces */ -import { min, sum } from "d3-array"; +/* eslint-disable space-infix-ops */ +import { min, max, sum } from "d3-array"; import { addLeafCount } from "./helpers"; import { timerStart, timerEnd } from "../../../util/perf"; @@ -257,3 +258,102 @@ export const setScales = function setScales(margins) { } } }; + + +/** +* this function sets the xScale, yScale domains and maps precalculated x,y +* coordinates to their places on the screen +* @return {null} +*/ +export const mapToScreen = function mapToScreen() { + console.log("mapToScreen") + timerStart("mapToScreen"); + /* set the range of the x & y scales */ + this.setScales(this.params.margins); + + /* find minimum & maximum x & y values, as well as # tips in view */ + this.nNodesInView = 0; + let [minY, maxY, minX, maxX] = [1000000, 0, 1000000, 0]; + this.nodes.filter((d) => d.inView).forEach((d) => { + if (d.x > maxX) maxX = d.x; + if (d.y > maxY) maxY = d.y; + if (d.x < minX) minX = d.x; + if (d.y < minY) minY = d.y; + if (d.terminal) this.nNodesInView++; + }); + + /* set the domain of the x & y scales */ + if (this.layout === "radial" || this.layout === "unrooted") { + // handle "radial and unrooted differently since they need to be square + // since branch length move in x and y direction + // TODO: should be tied to svg dimensions + const spanX = maxX-minX; + const spanY = maxY-minY; + const maxSpan = max([spanY, spanX]); + const ySlack = (spanX>spanY) ? (spanX-spanY)*0.5 : 0.0; + const xSlack = (spanX { + d.xTip = this.xScale(d.x); + d.yTip = this.yScale(d.y); + d.xBase = this.xScale(d.px); + d.yBase = this.yScale(d.py); + }); + if (this.vaccines) { + this.vaccines.forEach((d) => { + const n = 5; /* half the number of pixels that the cross will take up */ + const xTipCross = this.xScale(d.xCross); /* x position of the center of the cross */ + const yTipCross = this.yScale(d.yCross); /* x position of the center of the cross */ + d.vaccineCross = ` M ${xTipCross-n},${yTipCross-n} L ${xTipCross+n},${yTipCross+n} M ${xTipCross-n},${yTipCross+n} L ${xTipCross+n},${yTipCross-n}`; + d.vaccineLine = ` M ${d.xTip},${d.yTip} L ${xTipCross},${yTipCross}`; + }); + } + if (this.params.confidence && this.layout==="rect") { + this.nodes.forEach((d) => {d.xConf = [this.xScale(d.conf[0]), this.xScale(d.conf[1])];}); + } + + // assign the branches as path to each node for the different layouts + if (this.layout==="clock" || this.layout==="unrooted") { + this.nodes.forEach((d) => { + d.branch = [" M "+d.xBase.toString()+","+d.yBase.toString()+" L "+d.xTip.toString()+","+d.yTip.toString(), ""]; + }); + } else if (this.layout==="rect") { + this.nodes.forEach((d) => { + const stem_offset = 0.5*(d.parent["stroke-width"] - d["stroke-width"]) || 0.0; + const childrenY = [this.yScale(d.yRange[0]), this.yScale(d.yRange[1])]; + d.branch =[` M ${d.xBase - stem_offset},${d.yBase} L ${d.xTip},${d.yTip} M ${d.xTip},${childrenY[0]} L ${d.xTip},${childrenY[1]}`]; + if (this.params.confidence) d.confLine =` M ${d.xConf[0]},${d.yBase} L ${d.xConf[1]},${d.yTip}`; + }); + } else if (this.layout==="radial") { + const offset = this.nodes[0].depth; + const stem_offset_radial = this.nodes.map((d) => {return (0.5*(d.parent["stroke-width"] - d["stroke-width"]) || 0.0);}); + this.nodes.forEach((d) => {d.cBarStart = this.yScale(d.yRange[0]);}); + this.nodes.forEach((d) => {d.cBarEnd = this.yScale(d.yRange[1]);}); + this.nodes.forEach((d, i) => { + d.branch =[ + " M "+(d.xBase-stem_offset_radial[i]*Math.sin(d.angle)).toString() + + " "+(d.yBase-stem_offset_radial[i]*Math.cos(d.angle)).toString() + + " L "+d.xTip.toString()+" "+d.yTip.toString(), "" + ]; + if (!d.terminal) { + d.branch[1] =[" M "+this.xScale(d.xCBarStart).toString()+" "+this.yScale(d.yCBarStart).toString()+ + " A "+(this.xScale(d.depth)-this.xScale(offset)).toString()+" " + +(this.yScale(d.depth)-this.yScale(offset)).toString() + +" 0 "+(d.smallBigArc?"1 ":"0 ") +" 1 "+ + " "+this.xScale(d.xCBarEnd).toString()+","+this.yScale(d.yCBarEnd).toString()]; + } + }); + } + timerEnd("mapToScreen"); +}; diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js index adcde6388..06b7f2c0f 100644 --- a/src/components/tree/phyloTree/phyloTree.js +++ b/src/components/tree/phyloTree/phyloTree.js @@ -8,7 +8,6 @@ import { change, modifySVG } from "./change"; /* PROTOTYPES */ import * as renderers from "./renderers"; import * as layouts from "./layouts"; -import * as zoom from "./zoom"; import * as grid from "./grid"; import * as confidence from "./confidence"; import * as labels from "./labels"; @@ -72,6 +71,7 @@ PhyloTree.prototype.timeVsRootToTip = layouts.timeVsRootToTip; PhyloTree.prototype.unrootedLayout = layouts.unrootedLayout; PhyloTree.prototype.radialLayout = layouts.radialLayout; PhyloTree.prototype.setScales = layouts.setScales; +PhyloTree.prototype.mapToScreen = layouts.mapToScreen; /* C O N F I D E N C E I N T E R V A L S */ PhyloTree.prototype.removeConfidence = confidence.removeConfidence; @@ -90,9 +90,4 @@ PhyloTree.prototype.showBranchLabels = labels.showBranchLabels; PhyloTree.prototype.hideGrid = grid.hideGrid; PhyloTree.prototype.addGrid = grid.addGrid; -/* Z O O M , F I T TO S C R E E N , E T C */ -PhyloTree.prototype.zoomIntoClade = zoom.zoomIntoClade; -PhyloTree.prototype.zoomToParent = zoom.zoomToParent; -PhyloTree.prototype.mapToScreen = zoom.mapToScreen; - export default PhyloTree; diff --git a/src/components/tree/phyloTree/zoom.js b/src/components/tree/phyloTree/zoom.js deleted file mode 100644 index 6039d284e..000000000 --- a/src/components/tree/phyloTree/zoom.js +++ /dev/null @@ -1,115 +0,0 @@ -/* eslint-disable space-infix-ops */ -import { min, max } from "d3-array"; -import { applyToChildren } from "./helpers"; -import { timerStart, timerEnd } from "../../../util/perf"; - -/** - * zoom out a little by using the parent of the current clade - * as a zoom focus. - * @param {int} dt [transition time] - */ -export const zoomToParent = function zoomToParent(dt) { - console.warn("TODO: zoomToParent functionality needs to be built into phylotree.change()"); - if (this.zoomNode) { - this.zoomIntoClade(this.zoomNode.parent, dt); - } -}; - - -/** -* this function sets the xScale, yScale domains and maps precalculated x,y -* coordinates to their places on the screen -* @return {null} -*/ -export const mapToScreen = function mapToScreen() { - console.log("mapToScreen") - timerStart("mapToScreen"); - /* set the range of the x & y scales */ - this.setScales(this.params.margins); - - /* find minimum & maximum x & y values, as well as # tips in view */ - this.nNodesInView = 0; - let [minY, maxY, minX, maxX] = [1000000, 0, 1000000, 0]; - this.nodes.filter((d) => d.inView).forEach((d) => { - if (d.x > maxX) maxX = d.x; - if (d.y > maxY) maxY = d.y; - if (d.x < minX) minX = d.x; - if (d.y < minY) minY = d.y; - if (d.terminal) this.nNodesInView++; - }); - - /* set the domain of the x & y scales */ - if (this.layout === "radial" || this.layout === "unrooted") { - // handle "radial and unrooted differently since they need to be square - // since branch length move in x and y direction - // TODO: should be tied to svg dimensions - const spanX = maxX-minX; - const spanY = maxY-minY; - const maxSpan = max([spanY, spanX]); - const ySlack = (spanX>spanY) ? (spanX-spanY)*0.5 : 0.0; - const xSlack = (spanX { - d.xTip = this.xScale(d.x); - d.yTip = this.yScale(d.y); - d.xBase = this.xScale(d.px); - d.yBase = this.yScale(d.py); - }); - if (this.vaccines) { - this.vaccines.forEach((d) => { - const n = 5; /* half the number of pixels that the cross will take up */ - const xTipCross = this.xScale(d.xCross); /* x position of the center of the cross */ - const yTipCross = this.yScale(d.yCross); /* x position of the center of the cross */ - d.vaccineCross = ` M ${xTipCross-n},${yTipCross-n} L ${xTipCross+n},${yTipCross+n} M ${xTipCross-n},${yTipCross+n} L ${xTipCross+n},${yTipCross-n}`; - d.vaccineLine = ` M ${d.xTip},${d.yTip} L ${xTipCross},${yTipCross}`; - }); - } - if (this.params.confidence && this.layout==="rect") { - this.nodes.forEach((d) => {d.xConf = [this.xScale(d.conf[0]), this.xScale(d.conf[1])];}); - } - - // assign the branches as path to each node for the different layouts - if (this.layout==="clock" || this.layout==="unrooted") { - this.nodes.forEach((d) => { - d.branch = [" M "+d.xBase.toString()+","+d.yBase.toString()+" L "+d.xTip.toString()+","+d.yTip.toString(), ""]; - }); - } else if (this.layout==="rect") { - this.nodes.forEach((d) => { - const stem_offset = 0.5*(d.parent["stroke-width"] - d["stroke-width"]) || 0.0; - const childrenY = [this.yScale(d.yRange[0]), this.yScale(d.yRange[1])]; - d.branch =[` M ${d.xBase - stem_offset},${d.yBase} L ${d.xTip},${d.yTip} M ${d.xTip},${childrenY[0]} L ${d.xTip},${childrenY[1]}`]; - if (this.params.confidence) d.confLine =` M ${d.xConf[0]},${d.yBase} L ${d.xConf[1]},${d.yTip}`; - }); - } else if (this.layout==="radial") { - const offset = this.nodes[0].depth; - const stem_offset_radial = this.nodes.map((d) => {return (0.5*(d.parent["stroke-width"] - d["stroke-width"]) || 0.0);}); - this.nodes.forEach((d) => {d.cBarStart = this.yScale(d.yRange[0]);}); - this.nodes.forEach((d) => {d.cBarEnd = this.yScale(d.yRange[1]);}); - this.nodes.forEach((d, i) => { - d.branch =[ - " M "+(d.xBase-stem_offset_radial[i]*Math.sin(d.angle)).toString() - + " "+(d.yBase-stem_offset_radial[i]*Math.cos(d.angle)).toString() - + " L "+d.xTip.toString()+" "+d.yTip.toString(), "" - ]; - if (!d.terminal) { - d.branch[1] =[" M "+this.xScale(d.xCBarStart).toString()+" "+this.yScale(d.yCBarStart).toString()+ - " A "+(this.xScale(d.depth)-this.xScale(offset)).toString()+" " - +(this.yScale(d.depth)-this.yScale(offset)).toString() - +" 0 "+(d.smallBigArc?"1 ":"0 ") +" 1 "+ - " "+this.xScale(d.xCBarEnd).toString()+","+this.yScale(d.yCBarEnd).toString()]; - } - }); - } - timerEnd("mapToScreen"); -}; diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index 11bb25e6c..b00b910a1 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -139,23 +139,9 @@ export const handleIconClickHOF = function handleIconClickHOF(tool) { resetView.bind(this)(); // if we have clade zoom, zoom out to the parent clade if (this.state.selectedBranch && this.state.selectedBranch.n.arrayIdx) { - const dispatch = this.props.dispatch; - const arrayIdx = this.state.tree.zoomNode.parent.n.arrayIdx; - // reset the "clicked" branch, unset if we zoomed out all the way to the root - this.setState({ - hovered: null, - selectedBranch: (arrayIdx) ? this.state.tree.zoomNode.parent : null - }); - // clear previous timeout bc they potentially mess with the geometry update - if (this.timeout) { - clearTimeout(this.timeout); - } - // call phyloTree to zoom out, this rerenders the geometry - this.state.tree.zoomToParent(mediumTransitionDuration); - // wait and reset visibility - this.timeout = setTimeout(() => { - dispatch(updateVisibleTipsAndBranchThicknesses()); - }, mediumTransitionDuration); + this.props.dispatch(updateVisibleTipsAndBranchThicknesses({ + idxOfInViewRootNode: this.state.tree.zoomNode.parent.n.arrayIdx + })); } } resetGrid.bind(this)(); From 7b6c29dc01f53201c692c2b17ffa72cd42f971ac Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 11:41:29 -0800 Subject: [PATCH 18/25] clean up clade labelling functions --- src/actions/types.js | 1 - src/components/controls/controls.js | 4 +- .../controls/toggle-branch-labels.js | 36 ------ src/components/tree/index.js | 6 +- src/components/tree/phyloTree/change.js | 6 +- .../tree/phyloTree/defaultParams.js | 14 +-- src/components/tree/phyloTree/deprecated.js | 77 +++++++++++++ src/components/tree/phyloTree/labels.js | 106 +++++------------- src/components/tree/phyloTree/phyloTree.js | 4 +- src/components/tree/phyloTree/renderers.js | 6 +- .../tree/reactD3Interface/callbacks.js | 22 ---- src/reducers/controls.js | 5 - src/util/globals.js | 1 - 13 files changed, 122 insertions(+), 166 deletions(-) delete mode 100644 src/components/controls/toggle-branch-labels.js diff --git a/src/actions/types.js b/src/actions/types.js index e318dedee..d63f3bd91 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -5,7 +5,6 @@ export const REQUEST_FREQUENCIES = "REQUEST_FREQUENCIES"; export const RECEIVE_FREQUENCIES = "RECEIVE_FREQUENCIES"; export const FREQUENCIES_FETCH_ERROR = "FREQUENCIES_FETCH_ERROR"; export const BROWSER_DIMENSIONS = "BROWSER_DIMENSIONS"; -export const TOGGLE_BRANCH_LABELS = "TOGGLE_BRANCH_LABELS"; export const LEGEND_ITEM_MOUSEENTER = "LEGEND_ITEM_MOUSEENTER"; export const LEGEND_ITEM_MOUSELEAVE = "LEGEND_ITEM_MOUSELEAVE"; export const BRANCH_MOUSEENTER = "BRANCH_MOUSEENTER"; diff --git a/src/components/controls/controls.js b/src/components/controls/controls.js index 7fe1e6298..c4790694a 100644 --- a/src/components/controls/controls.js +++ b/src/components/controls/controls.js @@ -12,10 +12,9 @@ import ChooseMetric from "./choose-metric"; import PanelLayout from "./panel-layout"; import GeoResolution from "./geo-resolution"; import MapAnimationControls from "./map-animation"; -import { controlsWidth, enableAnimationDisplay } from "../../util/globals"; +import { controlsWidth } from "../../util/globals"; import { titleStyles } from "../../globalStyles"; import DataSource from "./data-source"; -import Toggle from "./toggle"; const header = (text) => ( @@ -47,7 +46,6 @@ class Controls extends React.Component { } return null; } - // restore below when perf is improved render() { const mapAndTree = this.props.panels !== undefined && this.props.panels.indexOf("map") !== -1 && this.props.panels.indexOf("tree") !== -1; diff --git a/src/components/controls/toggle-branch-labels.js b/src/components/controls/toggle-branch-labels.js deleted file mode 100644 index 156b07a12..000000000 --- a/src/components/controls/toggle-branch-labels.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; -import { connect } from "react-redux"; -import { TOGGLE_BRANCH_LABELS } from "../../actions/types"; -import { dataFont, darkGrey } from "../../globalStyles"; -import Flex from "../framework/flex"; -import { analyticsControlsEvent } from "../../util/googleAnalytics"; - -@connect() -class ToggleBranchLabels extends React.Component { - getStyles() { - return { - base: { - marginBottom: 10, - fontFamily: dataFont, - marginTop: 0, - color: darkGrey, - fontSize: 12 - } - }; - } - handleCheckboxClick() { - analyticsControlsEvent(`toggle-branch-labels`); - this.props.dispatch({type: TOGGLE_BRANCH_LABELS}); - } - render() { - const styles = this.getStyles(); - return ( - - -
show branch labels
-
- ); - } -} - -export default ToggleBranchLabels; diff --git a/src/components/tree/index.js b/src/components/tree/index.js index ed0b52dd8..205df119a 100644 --- a/src/components/tree/index.js +++ b/src/components/tree/index.js @@ -33,7 +33,6 @@ there are actually backlinks from the phylotree tree colorByConfidence: state.controls.colorByConfidence, layout: state.controls.layout, temporalConfidence: state.controls.temporalConfidence, - showBranchLabels: state.controls.showBranchLabels, distanceMeasure: state.controls.distanceMeasure, mutType: state.controls.mutType, colorScale: state.controls.colorScale, @@ -117,8 +116,7 @@ class Tree extends React.Component { { /* options */ grid: true, confidence: nextProps.temporalConfidence.display, - branchLabels: true, - showBranchLabels: false, + showCladeLabels: true, tipLabels: true, showTipLabels: true }, @@ -129,8 +127,6 @@ class Tree extends React.Component { onBranchClick: callbacks.onBranchClick.bind(this), onBranchLeave: callbacks.onBranchLeave.bind(this), onTipLeave: callbacks.onTipLeave.bind(this), - branchLabel: callbacks.branchLabel, - branchLabelSize: callbacks.branchLabelSize, tipLabel: (d) => d.n.strain, tipLabelSize: callbacks.tipLabelSize.bind(this) }, diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 3cb495118..cc6c4af1e 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -146,8 +146,8 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra }); /* special cases not listed in classesToPotentiallyUpdate */ - if (elemsToUpdate.has('.branchLabel')) { - this.updateBranchLabels(); + if (elemsToUpdate.has('.cladeLabel')) { + this.updateCladeLabels(transitionTime); } if (elemsToUpdate.has('.tipLabel')) { this.updateTipLabels(); @@ -241,7 +241,7 @@ export const change = function change({ if (newDistance || newLayout || zoomIntoClade || svgHasChangedDimensions) { elemsToUpdate.add(".tip").add(".branch.S").add(".branch.T"); elemsToUpdate.add(".vaccineCross").add(".vaccineDottedLine").add(".conf"); - elemsToUpdate.add('.branchLabel').add('.tipLabel'); + elemsToUpdate.add('.cladeLabel').add('.tipLabel'); elemsToUpdate.add(".grid").add(".regression"); svgPropsToUpdate.add("cx").add("cy").add("d").add("opacity"); } diff --git a/src/components/tree/phyloTree/defaultParams.js b/src/components/tree/phyloTree/defaultParams.js index 8fcda6794..d7daee1cb 100644 --- a/src/components/tree/phyloTree/defaultParams.js +++ b/src/components/tree/phyloTree/defaultParams.js @@ -23,14 +23,14 @@ export const defaultParams = { tipStrokeWidth: 1, tipRadius: 4, fontFamily: dataFont, - branchLabels: false, - showBranchLabels: false, - branchLabelFont: dataFont, - branchLabelFill: "#555", - branchLabelPadX: 8, - branchLabelPadY: 5, + /* C L A D E L A B E L S */ + showCladeLabels: false, + cladeLabelFont: dataFont, + cladeLabelFill: "#555", + cladeLabelPadX: 8, + cladeLabelPadY: 5, + /* TIP L A B E L S */ tipLabels: true, - // showTipLabels:true, tipLabelFont: dataFont, tipLabelFill: "#555", tipLabelPadX: 8, diff --git a/src/components/tree/phyloTree/deprecated.js b/src/components/tree/phyloTree/deprecated.js index 6718b755c..5471e234c 100644 --- a/src/components/tree/phyloTree/deprecated.js +++ b/src/components/tree/phyloTree/deprecated.js @@ -1,6 +1,83 @@ /* eslint-disable */ /* these functions are either deprecated or were in the code base but never called! */ +/** + * hide branchLabels + */ +export const hideBranchLabels = function hideBranchLabels() { + this.params.showBranchLabels = false; + this.svg.selectAll(".branchLabel").style('visibility', 'hidden'); +}; + +/** + * show branchLabels + */ +export const showBranchLabels = function showBranchLabels() { + this.params.showBranchLabels = true; + this.svg.selectAll(".branchLabel").style('visibility', 'visible'); +}; + + +/** + * hide tipLabels - this function is never called! + */ +PhyloTree.prototype.hideTipLabels = function() { + this.params.showTipLabels=false; + this.svg.selectAll(".tipLabel").style('visibility', 'hidden'); +}; + +/** + * show tipLabels - this function is never called! + */ +PhyloTree.prototype.showTipLabels = function() { + this.params.showTipLabels=true; + this.svg.selectAll(".tipLabel").style('visibility', 'visible'); +}; + +// CALLBACK +/** + * @param {node} d tree node object + * @return {string} displayed as label on the branch corresponding to the node + */ +export const branchLabel = function branchLabel(d) { + if (d.n.muts) { + if (d.n.muts.length > 5) { + return d.n.muts.slice(0, 5).join(", ") + "..."; + } + return d.n.muts.join(", "); + } + return ""; +}; + + +PhyloTree.prototype.drawTipLabels = function() { + var params = this.params; + const tLFunc = this.callbacks.tipLabel; + const inViewTerminalNodes = this.nodes + .filter(function(d){return d.terminal;}) + .filter(function(d){return d.inView;}); + console.log(`there are ${inViewTerminalNodes.length} nodes in view`) + this.tipLabels = this.svg.append("g").selectAll('.tipLabel') + .data(inViewTerminalNodes) + .enter() + .append("text") + .text(function (d){return tLFunc(d);}) + .attr("class", "tipLabel"); +} + +PhyloTree.prototype.drawBranchLabels = function() { + var params = this.params; + const bLFunc = this.callbacks.branchLabel; + this.branchLabels = this.svg.append("g").selectAll('.branchLabel') + .data(this.nodes) //.filter(function (d){return bLFunc(d)!=="";})) + .enter() + .append("text") + .text(function (d){return bLFunc(d);}) + .attr("class", "branchLabel") + .style("text-anchor","end"); +} + + export const updateConfidence = function updateConfidence(dt) { console.warn("updateConfidence is deprecated."); if (dt) { diff --git a/src/components/tree/phyloTree/labels.js b/src/components/tree/phyloTree/labels.js index e11f8a1f1..63f0b5f84 100644 --- a/src/components/tree/phyloTree/labels.js +++ b/src/components/tree/phyloTree/labels.js @@ -1,34 +1,4 @@ -/** - * hide branchLabels - */ -export const hideBranchLabels = function hideBranchLabels() { - this.params.showBranchLabels = false; - this.svg.selectAll(".branchLabel").style('visibility', 'hidden'); -}; - -/** - * show branchLabels - */ -export const showBranchLabels = function showBranchLabels() { - this.params.showBranchLabels = true; - this.svg.selectAll(".branchLabel").style('visibility', 'visible'); -}; - -/** - * hide tipLabels - this function is never called! - */ -// PhyloTree.prototype.hideTipLabels = function() { -// this.params.showTipLabels=false; -// this.svg.selectAll(".tipLabel").style('visibility', 'hidden'); -// }; - -/** - * show tipLabels - this function is never called! - */ -// PhyloTree.prototype.showTipLabels = function() { -// this.params.showTipLabels=true; -// this.svg.selectAll(".tipLabel").style('visibility', 'visible'); -// }; +import { timerFlush } from "d3-timer"; export const updateTipLabels = function updateTipLabels(dt) { this.svg.selectAll('.tipLabel').remove(); @@ -55,57 +25,39 @@ export const updateTipLabels = function updateTipLabels(dt) { } }; -export const updateBranchLabels = function updateBranchLabels(dt) { - const xPad = this.params.branchLabelPadX, yPad = this.params.branchLabelPadY; - const nNIV = this.nNodesInView; - const bLSFunc = this.callbacks.branchLabelSize; - const showBL = (this.layout === "rect") && this.params.showBranchLabels; - const visBL = showBL ? "visible" : "hidden"; - this.svg.selectAll('.branchLabel') +/** cladeLabelSize + * @param {int} n total number of nodes in current view + * @return {str} font size of the branch label, e.g. "12px" + */ +const cladeLabelSize = (n) => `${n > 1000 ? 14 : n > 500 ? 18 : 22}px`; + + +export const updateCladeLabels = function updateCladeLabels(dt) { + const visibility = this.layout === "rect" ? "visible" : "hidden"; + const labelSize = cladeLabelSize(this.nNodesInView); + this.svg.selectAll('.cladeLabel') .transition().duration(dt) - .attr("x", (d) => d.xTip - xPad) - .attr("y", (d) => d.yTip - yPad) - .attr("visibility", visBL) - .style("fill", this.params.branchLabelFill) - .style("font-family", this.params.branchLabelFont) - .style("font-size", (d) => bLSFunc(d, nNIV).toString() + "px"); + .attr("x", (d) => d.xTip - this.params.cladeLabelPadX) + .attr("y", (d) => d.yTip - this.params.cladeLabelPadY) + .style("visibility", visibility) + .style("font-size", labelSize); + if (!dt) timerFlush(); }; export const drawCladeLabels = function drawCladeLabels() { - this.branchLabels = this.svg.append("g").selectAll('.branchLabel') + const visibility = this.layout === "rect" ? "visible" : "hidden"; + const labelSize = cladeLabelSize(this.nNodesInView); + this.svg.append("g").selectAll('.cladeLabel') .data(this.nodes.filter((d) => typeof d.n.attr.clade_name !== 'undefined')) .enter() .append("text") - .style("visibility", "visible") - .text((d) => d.n.attr.clade_name) - .attr("class", "branchLabel") - .style("text-anchor", "end"); + .attr("class", "cladeLabel") + .attr("x", (d) => d.xTip - this.params.cladeLabelPadX) + .attr("y", (d) => d.yTip - this.params.cladeLabelPadY) + .style("visibility", visibility) + .style("text-anchor", "end") + .style("fill", this.params.cladeLabelFill) + .style("font-family", this.params.cladeLabelFont) + .style("font-size", labelSize) + .text((d) => d.n.attr.clade_name); }; - -// PhyloTree.prototype.drawTipLabels = function() { -// var params = this.params; -// const tLFunc = this.callbacks.tipLabel; -// const inViewTerminalNodes = this.nodes -// .filter(function(d){return d.terminal;}) -// .filter(function(d){return d.inView;}); -// console.log(`there are ${inViewTerminalNodes.length} nodes in view`) -// this.tipLabels = this.svg.append("g").selectAll('.tipLabel') -// .data(inViewTerminalNodes) -// .enter() -// .append("text") -// .text(function (d){return tLFunc(d);}) -// .attr("class", "tipLabel"); -// } - - -// PhyloTree.prototype.drawBranchLabels = function() { -// var params = this.params; -// const bLFunc = this.callbacks.branchLabel; -// this.branchLabels = this.svg.append("g").selectAll('.branchLabel') -// .data(this.nodes) //.filter(function (d){return bLFunc(d)!=="";})) -// .enter() -// .append("text") -// .text(function (d){return bLFunc(d);}) -// .attr("class", "branchLabel") -// .style("text-anchor","end"); -// } diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js index 06b7f2c0f..5d4de8f72 100644 --- a/src/components/tree/phyloTree/phyloTree.js +++ b/src/components/tree/phyloTree/phyloTree.js @@ -81,10 +81,8 @@ PhyloTree.prototype.updateConfidence = confidence.updateConfidence; /* L A B E L S ( T I P , B R A N C H , C O N F I D E N C E ) */ PhyloTree.prototype.drawCladeLabels = labels.drawCladeLabels; -PhyloTree.prototype.updateBranchLabels = labels.updateBranchLabels; +PhyloTree.prototype.updateCladeLabels = labels.updateCladeLabels; PhyloTree.prototype.updateTipLabels = labels.updateTipLabels; -PhyloTree.prototype.hideBranchLabels = labels.hideBranchLabels; -PhyloTree.prototype.showBranchLabels = labels.showBranchLabels; /* G R I D */ PhyloTree.prototype.hideGrid = grid.hideGrid; diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index 6e70751d1..7bff1600b 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -36,10 +36,9 @@ export const render = function render(svg, layout, distance, options, callbacks, /* draw functions */ if (this.params.showGrid) this.addGrid(); this.drawBranches(); - if (this.params.branchLabels) this.drawCladeLabels(); this.updateBranchLabels(); + if (this.params.showCladeLabels) this.drawCladeLabels(); this.drawTips(); if (this.vaccines) this.drawVaccines(); - this.drawCladeLabels(); if (visibility) { timerStart("setVisibility"); @@ -208,7 +207,8 @@ export const drawRegression = function drawRegression() { export const clearSVG = function clearSVG() { this.svg.selectAll('.tip').remove(); this.svg.selectAll('.branch').remove(); - this.svg.selectAll('.branchLabel').remove(); + this.svg.selectAll('.cladeLabel').remove(); + this.svg.selectAll('.tipLabel').remove(); this.svg.selectAll(".vaccine").remove(); this.svg.selectAll(".conf").remove(); }; diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index b00b910a1..3562c1749 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -148,28 +148,6 @@ export const handleIconClickHOF = function handleIconClickHOF(tool) { }; }; -/** - * @param {node} d tree node object - * @return {string} displayed as label on the branch corresponding to the node - */ -export const branchLabel = function branchLabel(d) { - if (d.n.muts) { - if (d.n.muts.length > 5) { - return d.n.muts.slice(0, 5).join(", ") + "..."; - } - return d.n.muts.join(", "); - } - return ""; -}; - -/** - * @param {node} d tree node object - * @param {int} n total number of nodes in current view - * @return {int} font size of the branch label - */ -export const branchLabelSize = (d, n) => - d.leafCount > n / 10.0 ? 12 : 0; - /** * @param {node} d tree node object * @param {int} n total number of nodes in current view diff --git a/src/reducers/controls.js b/src/reducers/controls.js index 9b59ada0f..0c1fe9bd0 100644 --- a/src/reducers/controls.js +++ b/src/reducers/controls.js @@ -262,7 +262,6 @@ const getDefaultState = () => { return { defaults, canTogglePanelLayout: true, - showBranchLabels: false, selectedLegendItem: null, selectedBranch: null, selectedNode: null, @@ -312,10 +311,6 @@ const Controls = (state = getDefaultState(), action) => { base = checkAndCorrectErrorsInState(base, action.meta); /* must run last */ return base; } - case types.TOGGLE_BRANCH_LABELS: - return Object.assign({}, state, { - showBranchLabels: !state.showBranchLabels - }); case types.LEGEND_ITEM_MOUSEENTER: return Object.assign({}, state, { selectedLegendItem: action.data diff --git a/src/util/globals.js b/src/util/globals.js index 99d201d2b..b21b7a641 100644 --- a/src/util/globals.js +++ b/src/util/globals.js @@ -28,7 +28,6 @@ export const defaultDistanceMeasure = "num_date"; export const defaultDateRange = 6; export const date_select = true; export const file_prefix = "Zika_"; -export const branchLabels = false; export const restrictTo = {"region": "all"}; export const time_window = 3.0; export const fullDataTimeWindow = 1.5; From 5eccf9111702866031cbf0d7f055e9258e93609a Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 12:26:19 -0800 Subject: [PATCH 19/25] improve initial render() --- src/components/tree/index.js | 2 +- src/components/tree/phyloTree/deprecated.js | 14 +++++ src/components/tree/phyloTree/renderers.js | 60 +++++++-------------- 3 files changed, 34 insertions(+), 42 deletions(-) diff --git a/src/components/tree/index.js b/src/components/tree/index.js index 205df119a..ebbdbce8b 100644 --- a/src/components/tree/index.js +++ b/src/components/tree/index.js @@ -113,7 +113,7 @@ class Tree extends React.Component { select(this.d3ref), this.props.layout, this.props.distanceMeasure, - { /* options */ + { /* parameters (modifies PhyloTree's defaults) */ grid: true, confidence: nextProps.temporalConfidence.display, showCladeLabels: true, diff --git a/src/components/tree/phyloTree/deprecated.js b/src/components/tree/phyloTree/deprecated.js index 5471e234c..2db354b25 100644 --- a/src/components/tree/phyloTree/deprecated.js +++ b/src/components/tree/phyloTree/deprecated.js @@ -1,6 +1,20 @@ /* eslint-disable */ /* these functions are either deprecated or were in the code base but never called! */ +/* this need a bit more work as the quickdraw functionality improves */ +export const rerenderAllElements = function rerenderAllElements() { + // console.log("rerenderAllElements") + this.mapToScreen(); + this.svg.selectAll(".branch") + .transition().duration(0) + .style("stroke-width", (d) => d["stroke-width"]); + this.svg.selectAll(".branch") + .transition().duration(0) + .filter(".S") + .attr("d", (d) => d.branch[0]); +}; + + /** * hide branchLabels */ diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index 7bff1600b..038ae9f6a 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -1,26 +1,25 @@ -import { darkGrey } from "../../../globalStyles"; import { timerStart, timerEnd } from "../../../util/perf"; /** - * @param svg -- the svg into which the tree is drawn - * @param layout -- the layout to be used, e.g. "rect" - * @param distance -- the property used as branch length, e.g. div or num_date - * @param options -- an object that contains options that will be added to this.params - * @param callbacks -- an object with call back function defining mouse behavior - * @param branchThickness (OPTIONAL) -- array of branch thicknesses - * @param visibility (OPTIONAL) -- array of "visible" or "hidden" + * @param {d3 selection} svg -- the svg into which the tree is drawn + * @param {string} layout -- the layout to be used, e.g. "rect" + * @param {string} distance -- the property used as branch length, e.g. div or num_date + * @param {object} parameters -- an object that contains options that will be added to this.params + * @param {object} callbacks -- an object with call back function defining mouse behavior + * @param {array} branchThickness -- array of branch thicknesses (same shape as tree nodes) + * @param {array} visibility -- array of "visible" or "hidden" (same shape as tree nodes) + * @param {bool} drawConfidence -- should confidence intervals be drawn? + * @param {bool} vaccines -- should vaccine crosses (and dotted lines if applicable) be drawn? + * @param {array} stroke -- stroke colour for each node (set onto each node) + * @param {array} fill -- fill colour for each node (set onto each node) * @return {null} */ -export const render = function render(svg, layout, distance, options, callbacks, branchThickness, visibility, drawConfidence, vaccines, stroke, fill) { +export const render = function render(svg, layout, distance, parameters, callbacks, branchThickness, visibility, drawConfidence, vaccines, stroke, fill) { timerStart("phyloTree render()"); - if (branchThickness) { - this.nodes.forEach((d, i) => {d["stroke-width"] = branchThickness[i];}); - } this.svg = svg; - this.params = Object.assign(this.params, options); + this.params = Object.assign(this.params, parameters); this.callbacks = callbacks; this.vaccines = vaccines ? vaccines.map((d) => d.shell) : undefined; - this.clearSVG(); /* set x, y values & scale them to the screen */ this.setDistance(distance); @@ -31,6 +30,8 @@ export const render = function render(svg, layout, distance, options, callbacks, this.nodes.forEach((d, i) => { d.stroke = stroke[i]; d.fill = fill[i]; + d.visibility = visibility[i]; + d["stroke-width"] = branchThickness[i]; }); /* draw functions */ @@ -39,21 +40,10 @@ export const render = function render(svg, layout, distance, options, callbacks, if (this.params.showCladeLabels) this.drawCladeLabels(); this.drawTips(); if (this.vaccines) this.drawVaccines(); - - if (visibility) { - timerStart("setVisibility"); - this.nodes.forEach((d, i) => {d["visibility"] = visibility[i];}); - this.svg.selectAll(".tip").style("visibility", (d) => d["visibility"]); - timerEnd("setVisibility"); - } - this.svg.selectAll(".regression").remove(); - if (this.layout === "clock" && this.distance === "num_date") { - this.drawRegression(); - } + if (this.layout === "clock" && this.distance === "num_date") this.drawRegression(); this.confidencesInSVG = false; - if (drawConfidence) { - this.drawConfidence(); - } + if (drawConfidence) this.drawConfidence(); + this.timeLastRenderRequested = Date.now(); timerEnd("phyloTree render()"); }; @@ -113,6 +103,7 @@ export const drawTips = function drawTips() { .on("mouseout", (d) => this.callbacks.onTipLeave(d)) .on("click", (d) => this.callbacks.onTipClick(d)) .style("pointer-events", "auto") + .style("visibility", (d) => d["visibility"]) .style("fill", (d) => d.fill || params.tipFill) .style("stroke", (d) => d.stroke || params.tipStroke) .style("stroke-width", () => params.tipStrokeWidth) /* don't want branch thicknesses applied */ @@ -160,19 +151,6 @@ export const drawBranches = function drawBranches() { }; -/* this need a bit more work as the quickdraw functionality improves */ -export const rerenderAllElements = function rerenderAllElements() { - // console.log("rerenderAllElements") - this.mapToScreen(); - this.svg.selectAll(".branch") - .transition().duration(0) - .style("stroke-width", (d) => d["stroke-width"]); - this.svg.selectAll(".branch") - .transition().duration(0) - .filter(".S") - .attr("d", (d) => d.branch[0]); -}; - /** * draws the regression line in the svg and adds a text with the rate estimate * @return {null} From f3ebb3ae696c03488f6f20f67d3b19dd29a2fc10 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 12:52:54 -0800 Subject: [PATCH 20/25] sync CIs between redux & phylotree --- src/components/tree/phyloTree/change.js | 7 ++--- src/components/tree/phyloTree/confidence.js | 3 --- src/components/tree/reactD3Interface/index.js | 11 ++++---- src/reducers/controls.js | 27 +++++++++++-------- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index cc6c4af1e..cb7c8d14e 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -160,10 +160,11 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra this.svg.selectAll(".regression").remove(); if (this.layout === "clock" && this.distance === "num_date") this.drawRegression(); } - /* confidences are hard */ - if (extras.removeConfidences) { + + /* confidence intervals */ + if (extras.removeConfidences && this.confidencesInSVG) { this.removeConfidence(transitionTime); - } else if (extras.showConfidences) { + } else if (extras.showConfidences && !this.confidencesInSVG) { this.drawConfidence(transitionTime); } else if (elemsToUpdate.has(".conf") && this.confidencesInSVG) { if (this.layout === "rect" && this.distance === "num_date") { diff --git a/src/components/tree/phyloTree/confidence.js b/src/components/tree/phyloTree/confidence.js index baf58dbb5..5f4c9ac0c 100644 --- a/src/components/tree/phyloTree/confidence.js +++ b/src/components/tree/phyloTree/confidence.js @@ -12,8 +12,6 @@ export const removeConfidence = function removeConfidence(dt) { }; export const drawConfidence = function drawConfidence(dt) { - // this.removeConfidence(); // just in case - // console.log("drawing:", this.svg.selectAll(".conf")) this.confidencesInSVG = true; if (dt) { this.confidence = this.svg.append("g").selectAll(".conf") @@ -29,7 +27,6 @@ export const drawConfidence = function drawConfidence(dt) { .enter() .call((sel) => this.drawSingleCI(sel, 0.5)); } - // this.props.confidence = true; }; export const calcConfidenceWidth = (el) => diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index 54dd9145b..d954ea218 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -38,13 +38,12 @@ export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { args.newDistance = nextProps.distanceMeasure; } - /* confidence intervals (on means in the SVG, display means the sidebar. TODO fix this terminology!) */ - if ((props.temporalConfidence.display === true && nextProps.temporalConfidence.display === false) || - (props.temporalConfidence.on === true && nextProps.temporalConfidence.on === false)) { + /* confidence intervals (on means in the SVG, display means shown in the sidebar) */ + if (props.temporalConfidence.display === true && nextProps.temporalConfidence.display === false) { args.removeConfidences = true; - } - if (nextProps.temporalConfidence.display === true && - (props.temporalConfidence.on === false && nextProps.temporalConfidence.on === true)) { + } else if (props.temporalConfidence.on === true && nextProps.temporalConfidence.on === false) { + args.removeConfidences = true; + } else if (nextProps.temporalConfidence.display === true && props.temporalConfidence.on === false && nextProps.temporalConfidence.on === true) { args.showConfidences = true; } diff --git a/src/reducers/controls.js b/src/reducers/controls.js index 0c1fe9bd0..30ad7c7e7 100644 --- a/src/reducers/controls.js +++ b/src/reducers/controls.js @@ -7,7 +7,6 @@ import { defaultGeoResolution, defaultLayout, mutType, twoColumnBreakpoint, - genotypeColors, reallySmallNumber } from "../util/globals"; import * as types from "../actions/types"; import { calcBrowserDimensionsInitialState } from "./browserDimensions"; @@ -237,8 +236,14 @@ export const checkAndCorrectErrorsInState = (state, metadata) => { } /* temporalConfidence */ - if (state.temporalConfidence.exists && state.layout !== "rect") { - state.temporalConfidence.display = false; + if (state.temporalConfidence.exists) { + if (state.layout !== "rect") { + state.temporalConfidence.display = false; + state.temporalConfidence.on = false; + } else if (state.distanceMeasure === "div") { + state.temporalConfidence.display = false; + state.temporalConfidence.on = false; + } } return state; @@ -337,11 +342,12 @@ const Controls = (state = getDefaultState(), action) => { }); case types.CHANGE_LAYOUT: { const layout = action.data; - /* if temporalConfidence and layout !== rect then disable confidence toggle */ - const temporalConfidence = Object.assign({}, state.temporalConfidence); - if (temporalConfidence.exists) { - temporalConfidence.display = layout === "rect"; - } + /* temporal confidence can only be displayed for rectangular trees */ + const temporalConfidence = { + exists: state.temporalConfidence.exists, + display: state.temporalConfidence.exists && layout === "rect", + on: false + }; return Object.assign({}, state, { layout, temporalConfidence @@ -349,13 +355,12 @@ const Controls = (state = getDefaultState(), action) => { } case types.CHANGE_DISTANCE_MEASURE: /* while this may change, div currently doesn't have CIs, - so they shouldn't be displayed. The SVG el's still exist, they're just of - width zero */ + so they shouldn't be displayed. */ if (state.temporalConfidence.exists) { if (state.temporalConfidence.display && action.data === "div") { return Object.assign({}, state, { distanceMeasure: action.data, - temporalConfidence: Object.assign({}, state.temporalConfidence, {display: false}) + temporalConfidence: Object.assign({}, state.temporalConfidence, {display: false, on: false}) }); } else if (state.layout === "rect" && action.data === "num_date") { return Object.assign({}, state, { From 88c3d96798f24571ee82c5380f66dd48c8402022 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 13:55:42 -0800 Subject: [PATCH 21/25] don't use transitions for CI entry / exit (too slow) --- src/components/tree/phyloTree/change.js | 6 +++--- src/components/tree/phyloTree/confidence.js | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index cb7c8d14e..58fd636dc 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -163,15 +163,15 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra /* confidence intervals */ if (extras.removeConfidences && this.confidencesInSVG) { - this.removeConfidence(transitionTime); + this.removeConfidence(); /* do not use a transition time - it's too clunky (too many elements?) */ } else if (extras.showConfidences && !this.confidencesInSVG) { - this.drawConfidence(transitionTime); + this.drawConfidence(); /* see comment above */ } else if (elemsToUpdate.has(".conf") && this.confidencesInSVG) { if (this.layout === "rect" && this.distance === "num_date") { updateCall = createUpdateCall(".conf", svgPropsToUpdate); genericSelectAndModify(this.svg, ".conf", updateCall, transitionTime); } else { - this.removeConfidence(transitionTime); + this.removeConfidence(); /* see comment above */ } } }; diff --git a/src/components/tree/phyloTree/confidence.js b/src/components/tree/phyloTree/confidence.js index 5f4c9ac0c..68aaac1cf 100644 --- a/src/components/tree/phyloTree/confidence.js +++ b/src/components/tree/phyloTree/confidence.js @@ -13,6 +13,7 @@ export const removeConfidence = function removeConfidence(dt) { export const drawConfidence = function drawConfidence(dt) { this.confidencesInSVG = true; + dt = false; if (dt) { this.confidence = this.svg.append("g").selectAll(".conf") .data(this.nodes) From 446e7a57a2563662f72ac06f3d3477daf3170348 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 15:05:06 -0800 Subject: [PATCH 22/25] restore 3-part transitions --- src/components/tree/phyloTree/change.js | 50 ++++++++++++++++++++- src/components/tree/phyloTree/confidence.js | 1 - src/components/tree/phyloTree/phyloTree.js | 3 +- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 58fd636dc..69f92f37e 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -176,6 +176,46 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra } }; +/* instead of modifying the SVG the "normal" way, this is sometimes too janky (e.g. when we need to move everything) + * step 1: fade out & remove everything except tips. + * step 2: when step 1 has finished, move tips across the screen. + * step 3: when step 2 has finished, redraw everything. No transition here. + */ +export const modifySVGInStages = function modifySVGInStages(elemsToUpdate, svgPropsToUpdate, transitionTimeFadeOut, transitionTimeMoveTips) { + elemsToUpdate.delete(".tip"); + this.hideGrid(); + let inProgress = 0; /* counter of transitions currently in progress */ + + const step3 = () => { + this.drawBranches(); + if (this.params.showGrid) this.addGrid(); + this.svg.selectAll(".tip").remove(); + this.drawTips(); + if (this.vaccines) this.drawVaccines(); + if (this.layout === "clock" && this.distance === "num_date") this.drawRegression(); + }; + + /* STEP 2: move tips */ + const step2 = () => { + if (!--inProgress) { /* decrement counter. When hits 0 run block */ + const updateTips = createUpdateCall(".tip", svgPropsToUpdate); + genericSelectAndModify(this.svg, ".tip", updateTips, transitionTimeMoveTips); + setTimeout(step3, transitionTimeMoveTips); + } + }; + + /* STEP 1. remove everything (via opacity) */ + this.confidencesInSVG = false; + this.svg.selectAll([...elemsToUpdate].join(", ")) + .transition().duration(transitionTimeFadeOut) + .style("opacity", 0) + .remove() + .on("start", () => inProgress++) + .on("end", step2); + if (!transitionTimeFadeOut) timerFlush(); +}; + + /* the main interface to changing a currently rendered tree. * simply call change and tell it what should be changed. * try to do a single change() call with as many things as possible in it @@ -205,6 +245,7 @@ export const change = function change({ const elemsToUpdate = new Set(); const nodePropsToModify = {}; /* modify the actual data structure */ const svgPropsToUpdate = new Set(); /* modify the SVG */ + let useModifySVGInStages = false; /* use modifySVGInStages rather than modifySVG */ /* calculate dt */ const idealTransitionTime = 500; @@ -246,6 +287,9 @@ export const change = function change({ elemsToUpdate.add(".grid").add(".regression"); svgPropsToUpdate.add("cx").add("cy").add("d").add("opacity"); } + if (newLayout) { + useModifySVGInStages = true; + } /* change the requested properties on the nodes */ updateNodesWithNewData(this.nodes, nodePropsToModify); @@ -283,7 +327,11 @@ export const change = function change({ /* svg change elements */ const extras = {removeConfidences, showConfidences}; - this.modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime, extras); + if (useModifySVGInStages) { + this.modifySVGInStages(elemsToUpdate, svgPropsToUpdate, transitionTime, 1000); + } else { + this.modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime, extras); + } this.timeLastRenderRequested = Date.now(); console.timeEnd("phylotree.change"); diff --git a/src/components/tree/phyloTree/confidence.js b/src/components/tree/phyloTree/confidence.js index 68aaac1cf..5f4c9ac0c 100644 --- a/src/components/tree/phyloTree/confidence.js +++ b/src/components/tree/phyloTree/confidence.js @@ -13,7 +13,6 @@ export const removeConfidence = function removeConfidence(dt) { export const drawConfidence = function drawConfidence(dt) { this.confidencesInSVG = true; - dt = false; if (dt) { this.confidence = this.svg.append("g").selectAll(".conf") .data(this.nodes) diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js index 5d4de8f72..17c3ec87b 100644 --- a/src/components/tree/phyloTree/phyloTree.js +++ b/src/components/tree/phyloTree/phyloTree.js @@ -3,7 +3,7 @@ import { max } from "d3-array"; import { scaleLinear } from "d3-scale"; import { defaultParams } from "./defaultParams"; import { addLeafCount, createChildrenAndParents } from "./helpers"; -import { change, modifySVG } from "./change"; +import { change, modifySVG, modifySVGInStages } from "./change"; /* PROTOTYPES */ import * as renderers from "./renderers"; @@ -51,6 +51,7 @@ const PhyloTree = function PhyloTree(reduxNodes) { /* C H A N G E */ PhyloTree.prototype.change = change; PhyloTree.prototype.modifySVG = modifySVG; +PhyloTree.prototype.modifySVGInStages = modifySVGInStages; /* I N I T I A L R E N D E R E T C */ PhyloTree.prototype.render = renderers.render; From f95eac36306291de1039ef23b3f0c73f0324e581 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 15:24:15 -0800 Subject: [PATCH 23/25] remove console logs. disable action logging middleware. --- src/components/tree/phyloTree/change.js | 21 ++++++++++--------- src/components/tree/phyloTree/layouts.js | 4 ++-- src/components/tree/reactD3Interface/index.js | 2 +- src/store/index.js | 2 +- src/util/globals.js | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 69f92f37e..8c8101b36 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -1,13 +1,14 @@ import { timerFlush } from "d3-timer"; import { calcConfidenceWidth } from "./confidence"; import { applyToChildren } from "./helpers"; +import { timerStart, timerEnd } from "../../../util/perf"; /* loop through the nodes and update each provided prop with the new value * additionally, set d.update -> whether or not the node props changed */ const updateNodesWithNewData = (nodes, newNodeProps) => { - console.log("update nodes with data for these keys:", Object.keys(newNodeProps)); - let tmp = 0; + // console.log("update nodes with data for these keys:", Object.keys(newNodeProps)); + // let tmp = 0; nodes.forEach((d, i) => { d.update = false; for (let key in newNodeProps) { // eslint-disable-line @@ -15,11 +16,11 @@ const updateNodesWithNewData = (nodes, newNodeProps) => { if (val !== d[key]) { d[key] = val; d.update = true; - tmp++; + // tmp++; } } }); - console.log("marking ", tmp, " nodes for update"); + // console.log("marking ", tmp, " nodes for update"); }; @@ -96,7 +97,7 @@ const createUpdateCall = (treeElem, properties) => (selection) => { }; const genericSelectAndModify = (svg, treeElem, updateCall, transitionTime) => { - console.log("general svg update for", treeElem); + // console.log("general svg update for", treeElem); svg.selectAll(treeElem) .filter((d) => d.update) .transition().duration(transitionTime) @@ -104,7 +105,7 @@ const genericSelectAndModify = (svg, treeElem, updateCall, transitionTime) => { if (!transitionTime) { /* https://github.com/d3/d3-timer#timerFlush */ timerFlush(); - console.log("\t\t--FLUSHING TIMER--"); + // console.log("\t\t--FLUSHING TIMER--"); } }; @@ -117,7 +118,7 @@ const genericSelectAndModify = (svg, treeElem, updateCall, transitionTime) => { export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, transitionTime, extras) { let updateCall; const classesToPotentiallyUpdate = [".tip", ".vaccineDottedLine", ".vaccineCross", ".branch"]; /* order is respected */ - console.log("modifying these elems", elemsToUpdate) + // console.log("modifying these elems", elemsToUpdate) /* treat stem / branch specially, but use these to replace a normal .branch call if that's also to be applied */ if (elemsToUpdate.has(".branch.S") || elemsToUpdate.has(".branch.T")) { const applyBranchPropsAlso = elemsToUpdate.has(".branch"); @@ -240,8 +241,8 @@ export const change = function change({ tipRadii = undefined, branchThickness = undefined }) { - console.log("\n** phylotree.change() (time since last run:", Date.now() - this.timeLastRenderRequested, "ms) **\n\n"); - console.time("phylotree.change"); + // console.log("\n** phylotree.change() (time since last run:", Date.now() - this.timeLastRenderRequested, "ms) **\n\n"); + timerStart("phylotree.change()"); const elemsToUpdate = new Set(); const nodePropsToModify = {}; /* modify the actual data structure */ const svgPropsToUpdate = new Set(); /* modify the SVG */ @@ -334,5 +335,5 @@ export const change = function change({ } this.timeLastRenderRequested = Date.now(); - console.timeEnd("phylotree.change"); + timerEnd("phylotree.change()"); }; diff --git a/src/components/tree/phyloTree/layouts.js b/src/components/tree/phyloTree/layouts.js index e0ca1a308..c71854b62 100644 --- a/src/components/tree/phyloTree/layouts.js +++ b/src/components/tree/phyloTree/layouts.js @@ -11,7 +11,7 @@ import { timerStart, timerEnd } from "../../../util/perf"; * ["rect", "radial", "unrooted", "clock"] */ export const setLayout = function setLayout(layout) { - console.log("set layout"); + // console.log("set layout"); timerStart("setLayout"); if (typeof layout === "undefined" || layout !== this.layout) { this.nodes.forEach((d) => {d.update = true;}); @@ -266,7 +266,7 @@ export const setScales = function setScales(margins) { * @return {null} */ export const mapToScreen = function mapToScreen() { - console.log("mapToScreen") + // console.log("mapToScreen") timerStart("mapToScreen"); /* set the range of the x & y scales */ this.setScales(this.params.margins); diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index d954ea218..98ff36334 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -68,7 +68,7 @@ export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { } if (Object.keys(args).length) { - console.log('\n\n** changePhyloTreeViaPropsComparison **', args); + // console.log('\n\n** changePhyloTreeViaPropsComparison **', args); phylotree.change(args); } diff --git a/src/store/index.js b/src/store/index.js index 631ee0d75..327b36f44 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -7,7 +7,7 @@ import { loggingMiddleware } from "../middleware/logActions"; // eslint-disable- const middleware = [ thunk, changeURLMiddleware, // eslint-disable-line comma-dangle - loggingMiddleware + // loggingMiddleware ]; let CreateStoreWithMiddleware; diff --git a/src/util/globals.js b/src/util/globals.js index b21b7a641..8c588b20b 100644 --- a/src/util/globals.js +++ b/src/util/globals.js @@ -54,7 +54,7 @@ export const defaultDistanceMeasures = ["num_date", "div"]; export const fastTransitionDuration = 350; // in milliseconds export const mediumTransitionDuration = 700; // in milliseconds export const slowTransitionDuration = 1400; // in milliseconds -export const enableNarratives = true; +export const enableNarratives = false; export const narrativeWidth = 500; export const animationWindowWidth = 0.075; // width of animation window relative to date slider export const animationTick = 50; // animation tick in milliseconds From 85d993c3b379af62ead6a9d753e1d6d71b2afe2c Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 15:34:05 -0800 Subject: [PATCH 24/25] remove quickdraw (no performance effect) --- src/components/tree/phyloTree/phyloTree.js | 4 ++-- src/components/tree/reactD3Interface/index.js | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js index 17c3ec87b..e487cf858 100644 --- a/src/components/tree/phyloTree/phyloTree.js +++ b/src/components/tree/phyloTree/phyloTree.js @@ -44,8 +44,8 @@ const PhyloTree = function PhyloTree(reduxNodes) { this.zoomNode = this.nodes[0]; addLeafCount(this.nodes[0]); /* debounced functions (AFAIK you can't define these as normal prototypes as they need "this") */ - this.debouncedMapToScreen = _debounce(this.mapToScreen, this.params.mapToScreenDebounceTime, - {leading: false, trailing: true, maxWait: this.params.mapToScreenDebounceTime}); + // this.debouncedMapToScreen = _debounce(this.mapToScreen, this.params.mapToScreenDebounceTime, + // {leading: false, trailing: true, maxWait: this.params.mapToScreenDebounceTime}); }; /* C H A N G E */ diff --git a/src/components/tree/reactD3Interface/index.js b/src/components/tree/reactD3Interface/index.js index 98ff36334..5739b71e8 100644 --- a/src/components/tree/reactD3Interface/index.js +++ b/src/components/tree/reactD3Interface/index.js @@ -51,10 +51,6 @@ export const changePhyloTreeViaPropsComparison = (reactThis, nextProps) => { args.newLayout = nextProps.layout; } - if (nextProps.quickdraw === false && props.quickdraw === true) { - console.warn("quickdraw finished. should call rerenderAllElements"); - } - /* zoom to a clade / reset zoom to entire tree */ if (props.tree.idxOfInViewRootNode !== nextProps.tree.idxOfInViewRootNode) { const rootNode = phylotree.nodes[nextProps.tree.idxOfInViewRootNode]; From e7069e191597be44a394b27cfe57dfafe722ecde Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Tue, 20 Feb 2018 15:44:42 -0800 Subject: [PATCH 25/25] Improve speed of compute responsive. Closes #499. --- src/util/computeResponsive.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/util/computeResponsive.js b/src/util/computeResponsive.js index cc1ab1f93..0ce206b61 100644 --- a/src/util/computeResponsive.js +++ b/src/util/computeResponsive.js @@ -37,13 +37,7 @@ const computeResponsive = ({ const horizontalPadding = horizontal === 1 ? 34 : 56; // derived from empirical testing, depends on Card margins const verticalPadding = 52; - /* WIDTH */ - let scrollbarWidth = 0; // sidebar scrollbar has width equal to its offsetWidth - clientWidth - const classArray = document.getElementsByClassName("sidebar"); - if (classArray.length > 0) { - scrollbarWidth = classArray[0].offsetWidth - classArray[0].clientWidth; - } - const LRpadding = padding.left + padding.right + horizontalPadding + scrollbarWidth + (padding.left + padding.right === 0 ? 0 : controlsPadding); + const LRpadding = padding.left + padding.right + horizontalPadding + (padding.left + padding.right === 0 ? 0 : controlsPadding); const width = horizontal * (browserDimensions.width - LRpadding); /* HEIGHT */