Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Export second tree & tanglegram to SVG #630

Merged
merged 4 commits into from
Aug 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/components/download/downloadModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ class DownloadModal extends React.Component {
return "nextstrain_"
+ window.location.pathname
.replace(/^\//, '') // Remove leading slashes
.replace(/:[^\/]+/g, '') // Remove any second tree (treeTwo/treeToo) designation.
// We only export the first tree.
.replace(/:/g, '-') // Change ha:na to ha-na
.replace(/\//g, '_'); // Replace slashes with spaces
}
makeTextStringsForSVGExport() {
Expand Down
42 changes: 33 additions & 9 deletions src/components/download/helperFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React from "react";
import { infoNotification, warningNotification } from "../../actions/notifications";
import { prettyString, formatURLString, authorString } from "../../util/stringHelpers";
import { spaceBetweenTrees } from "../tree/tree";

export const isPaperURLValid = (d) => {
return (
Expand Down Expand Up @@ -178,11 +179,13 @@ export const newick = (dispatch, filePrefix, root, temporal) => {
};

const processXMLString = (input) => {
/* split into bounding <g> (or <svg>) tag, and inner paths / shapes etc */
const parts = input.match(/^(<s?v?g.+?>)(.+)<\/s?v?g>$/);
/* split into bounding tag, and inner paths / shapes etc */
const parts = input.match(/^(<.+?>)(.+)<\/.+?>$/);
if (!parts) return undefined;

/* extract width & height from the initial <g> bounding group */
const dimensions = parts[1].match(/width="([0-9.]+)".+height="([0-9.]+)"/);
const dimensions = parts[1].match(/width.+?([0-9.]+).+height.+?([0-9.]+)/);

if (!dimensions) return undefined;
/* the map uses transform3d & viewbox */
const viewbox = parts[1].match(/viewBox="([0-9-]+)\s([0-9-]+)\s([0-9-]+)\s([0-9-]+)"/);
Expand All @@ -202,6 +205,13 @@ const createBoundingDimensionsAndPositionPanels = (panels, panelLayout, numLines
const padding = 50;
let width = 0;
let height = 0;

/* calculating the width of the tree panel is harder if there are two trees */
if (panels.secondTree) {
panels.secondTree.x = spaceBetweenTrees + panels.tree.width;
panels.tree.width += (spaceBetweenTrees + panels.secondTree.width);
}

if (panels.tree && panels.mapD3 && panels.mapTiles) {
if (panelLayout === "grid") {
width = panels.tree.width + padding + panels.mapTiles.width;
Expand Down Expand Up @@ -254,12 +264,14 @@ const createBoundingDimensionsAndPositionPanels = (panels, panelLayout, numLines
height = panels.frequencies.height;
}
}

/* add top&left padding */
if (panels.tree) {panels.tree.x += padding; panels.tree.y += padding;}
if (panels.mapD3) {panels.mapD3.x += padding; panels.mapD3.y += padding;}
if (panels.mapTiles) {panels.mapTiles.x += padding; panels.mapTiles.y += padding;}
if (panels.entropy) {panels.entropy.x += padding; panels.entropy.y += padding;}
if (panels.frequencies) {panels.frequencies.x += padding; panels.frequencies.y += padding;}
for (let key in panels) { // eslint-disable-line
if (panels[key]) {
panels[key].x += padding;
panels[key].y += padding;
}
}
width += padding*2;
height += padding*2;
const textHeight = numLinesOfText * 36 + 20;
Expand Down Expand Up @@ -289,12 +301,24 @@ const writeSVGPossiblyIncludingMapPNG = (dispatch, filePrefix, panelsInDOM, pane
const panels = {tree: undefined, mapTiles: undefined, mapD3: undefined, entropy: undefined, frequencies: undefined};
if (panelsInDOM.indexOf("tree") !== -1) {
try {
panels.tree = processXMLString((new XMLSerializer()).serializeToString(document.getElementById("d3TreeElement")));
panels.tree = processXMLString((new XMLSerializer()).serializeToString(document.getElementById("MainTree")));
panels.treeLegend = processXMLString((new XMLSerializer()).serializeToString(document.getElementById("TreeLegendContainer")));
} catch (e) {
panels.tree = undefined;
errors.push("tree");
console.error("Tree SVG save error:", e);
}
if (panels.tree && document.getElementById('SecondTree')) {
try {
panels.secondTree = processXMLString((new XMLSerializer()).serializeToString(document.getElementById("SecondTree")));
if (document.getElementById('Tangle')) {
panels.tangle = processXMLString((new XMLSerializer()).serializeToString(document.getElementById("Tangle")));
}
} catch (e) {
errors.push("second tree / tanglegram");
console.error("Second Tree / tanglegram SVG save error:", e);
}
}
}
if (panelsInDOM.indexOf("entropy") !== -1) {
try {
Expand Down
11 changes: 5 additions & 6 deletions src/components/framework/card.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,13 @@ class Card extends React.Component {
render() {
const styles = this.props.infocard ? this.getStylesInfoCard() : this.getStyles();
return (
<div style={{ ...styles.base, ...this.props.style }}>
<div style={{ ...styles.title, ...this.props.titleStyles }}>
<div id={`${this.props.title}Card`} style={{ ...styles.base, ...this.props.style }}>
<div id="CardTitle" style={{ ...styles.title, ...this.props.titleStyles }}>
{this.props.title}
</div>
<div style={{
display: "flex",
justifyContent: this.props.center ? "center" : "flex-start"
}}
<div
id="CardContentContainer"
style={{display: "flex", justifyContent: this.props.center ? "center" : "flex-start"}}
>
{this.props.children}
</div>
Expand Down
19 changes: 13 additions & 6 deletions src/components/tree/legend/legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class Legend extends React.Component {
*/
legendTitle() {
return (
<g>
<g id="Title">
<rect width={this.getTitleWidth()} height="12" fill="rgba(255,255,255,.85)"/>
<text
x={0}
Expand Down Expand Up @@ -115,7 +115,7 @@ class Legend extends React.Component {
// Works fine, but will need adjusting if title font is changed.
const offset = this.getTitleWidth();
return (
<g transform={`translate(${offset},0)`}>
<g id="Chevron" transform={`translate(${offset},0)`}>
<svg width="12" height="12" viewBox="0 0 1792 1792">
<rect width="1792" height="1792" fill="rgba(255,255,255,.85)"/>
<path
Expand Down Expand Up @@ -182,9 +182,9 @@ class Legend extends React.Component {
// transition: `${fastTransitionDuration}ms ease-in-out`
// }}>
return (
<g>
<g id="ItemsContainer">
<rect width="280" height={this.getSVGHeight()} fill="rgba(255,255,255,.85)"/>
<g transform="translate(0,20)">
<g id="Items" transform="translate(0,20)">
{items}
</g>
</g>
Expand All @@ -207,9 +207,16 @@ class Legend extends React.Component {
if (!this.props.colorScale) return null;
const styles = this.getStyles();
return (
<svg width="280" height={this.getSVGHeight()} style={styles.svg}>
<svg
id="TreeLegendContainer"
width="280"
height={this.getSVGHeight()}
style={styles.svg}
>
{this.legendItems()}
<g onClick={() => { this.toggleLegend(); }}
<g
id="TitleAndChevron"
onClick={() => this.toggleLegend()}
style={{cursor: "pointer"}}
>
{this.legendTitle()}
Expand Down
2 changes: 1 addition & 1 deletion src/components/tree/reactD3Interface/initialRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { calcBranchStrokeCols } from "../../../util/colorHelpers";
import * as callbacks from "./callbacks";

export const renderTree = (that, main, phylotree, props) => {
const ref = main ? that.d3ref : that.d3refToo;
const ref = main ? that.domRefs.mainTree : that.domRefs.secondTree;
const treeState = main ? props.tree : props.treeToo;
if (!treeState.loaded) {
console.warn("can't run renderTree (not loaded)");
Expand Down
15 changes: 9 additions & 6 deletions src/components/tree/tangle/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ class Tangle extends React.Component {
drawLines(props) {
if (!props) props = this.props; // eslint-disable-line
const thickness = props.lookup.length > 750 ? 0.25 : props.lookup.length > 100 ? 0.5 : 1;
select(this.d3ref).selectAll(".tangleLine").remove();
select(this.d3ref).selectAll("*").remove();
const makeTipPath = makeTipPathGenerator(props);
select(this.d3ref)
.append("g")
.selectAll(".tangleLine")
.data(props.lookup)
.enter()
Expand Down Expand Up @@ -83,15 +82,19 @@ class Tangle extends React.Component {
const textStyles = {position: "absolute", top: 5, zIndex: 100, fontSize: 16, color: "#000", fontWeight: 500};
const lefts = [this.props.width/2 - this.props.spaceBetweenTrees/2, this.props.width/2 + this.props.spaceBetweenTrees/2];
return (
<div>
<div style={{...textStyles, left: lefts[0]-100, width: 100, textAlign: "right"}}>
<div id="TangleContainer">
<div id="MainTreeTitle" style={{...textStyles, left: lefts[0]-100, width: 100, textAlign: "right"}}>
{this.props.leftTreeName}
</div>
<div style={{...textStyles, left: lefts[1], textAlign: "left"}}>
<div id="SecondTreeTitle" style={{...textStyles, left: lefts[1], textAlign: "left"}}>
{this.props.rightTreeName}
</div>
<div style={{position: "absolute", left: this.state.left, top: this.state.top, zIndex: 100, pointerEvents: "none"}}>
<div
id="TangleSvgContainer"
style={{position: "absolute", left: this.state.left, top: this.state.top, zIndex: 100, pointerEvents: "none"}}
>
<svg
id="Tangle"
style={{cursor: "default", width: this.props.width, height: this.props.height}}
ref={(c) => {this.d3ref = c;}}
/>
Expand Down
30 changes: 15 additions & 15 deletions src/components/tree/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ import Tangle from "./tangle";
import { attemptUntangle } from "../../util/globals";
import { untangleTreeToo } from "./tangle/untangling";

export const spaceBetweenTrees = 100;

class Tree extends React.Component {
constructor(props) {
super(props);
this.domRefs = {
mainTree: undefined,
secondTree: undefined
};
this.tangleRef = undefined;
this.state = {
hover: null,
Expand Down Expand Up @@ -103,26 +109,20 @@ class Tree extends React.Component {
};
};

renderTreeDiv({width, height, d3ref}) {
renderTreeDiv({width, height, mainTree}) {
return (
<svg style={{pointerEvents: "auto"}}
<svg
id={mainTree ? "MainTree" : "SecondTree"}
style={{pointerEvents: "auto", cursor: "default"}}
width={width}
height={height}
>
<g
id={"d3TreeElement"}
width={width}
height={height}
style={{cursor: "default"}}
ref={(c) => {this[d3ref] = c;}}
/>
</svg>
ref={(c) => {mainTree ? this.domRefs.mainTree = c : this.domRefs.secondTree = c;}}
/>
);
}

render() {
const styles = this.getStyles();
const spaceBetweenTrees = 100;
const widthPerTree = this.props.showTreeToo ? (this.props.width - spaceBetweenTrees) / 2 : this.props.width;
return (
<Card center title={"Phylogeny"}>
Expand Down Expand Up @@ -159,10 +159,10 @@ class Tree extends React.Component {
rightTreeName={this.props.showTreeToo.toUpperCase()}
/>
) : null }
{this.renderTreeDiv({width: widthPerTree, height: this.props.height, d3ref: "d3ref"})}
{this.props.showTreeToo ? <div style={{width: spaceBetweenTrees}}/> : null}
{this.renderTreeDiv({width: widthPerTree, height: this.props.height, mainTree: true})}
{this.props.showTreeToo ? <div id="treeSpacer" style={{width: spaceBetweenTrees}}/> : null}
{this.props.showTreeToo ?
this.renderTreeDiv({width: widthPerTree, height: this.props.height, d3ref: "d3refToo"}) :
this.renderTreeDiv({width: widthPerTree, height: this.props.height, mainTree: false}) :
null
}
<button
Expand Down