Skip to content

Commit

Permalink
Merge pull request #12 from veg/develop
Browse files Browse the repository at this point in the history
1.1.0 Release
  • Loading branch information
stephenshank authored Sep 21, 2018
2 parents 74b0bff + e66b65a commit 3f789a2
Show file tree
Hide file tree
Showing 28 changed files with 535 additions and 81 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
node_modules
.DS_Store

.yalc
yalc.lock
yarn-error.log
dist/index.html
dist/bundle.js
dist/alignment.js
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions dist/data/H3full.new

Large diffs are not rendered by default.

File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions lib/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
index.html
alignment.js
alignment.css
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "alignment.js",
"version": "1.0.10",
"version": "1.1.0",
"main": "lib/alignment.js",
"license": "MIT",
"dependencies": {
"bootstrap": "^4.1.1",
"d3": "^4.12.2",
"jquery": "^3.3.1",
"phylotree": "0.2.0-alpha.1",
"popper.js": "^1.14.3",
"react": "^16.2.0",
"react-dom": "^16.2.0",
Expand Down Expand Up @@ -51,5 +52,9 @@
"precommit": "pretty-quick --staged",
"format": "prettier 'src/**/*.{js,jsx}' --write"
},
"files": ["lib/alignment.js", "lib/alignment.css"]
"files": ["lib/alignment.js", "lib/alignment.css"],
"repository": {
"type": "git",
"url": "https://github.com/veg/alignment.js"
}
}
12 changes: 4 additions & 8 deletions src/components/Alignment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React, { Component } from "react";
const d3 = require("d3");
const $ = require("jquery");
const _ = require("underscore");
const text_width = require("text-width");

import fastaParser from "./../helpers/fasta";
import computeLabelWidth from "../helpers/computeLabelWidth";
import BaseAlignment from "./BaseAlignment.jsx";
import SiteAxis from "./SiteAxis.jsx";
import Placeholder from "./Placeholder.jsx";
Expand Down Expand Up @@ -51,13 +51,9 @@ class Alignment extends Component {
if (props.fasta) {
const { fasta, site_size, width, height, axis_height } = props;
this.sequence_data = fastaParser(fasta);
this.label_width =
props.label_padding +
this.sequence_data
.map(record =>
text_width(record.header, { family: "Courier", size: 14 })
)
.reduce((a, b) => Math.max(a, b), 0);
const { sequence_data } = this;
const { label_padding } = this.props;
this.label_width = computeLabelWidth(sequence_data, label_padding);
this.full_pixel_width = site_size * this.sequence_data.number_of_sites;
this.full_pixel_height =
site_size * this.sequence_data.number_of_sequences;
Expand Down
1 change: 1 addition & 0 deletions src/components/BaseAlignment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class BaseAlignment extends Component {
BaseAlignment.defaultProps = {
site_color: nucleotide_color,
text_color: nucleotide_text_color,
site_size: 20,
id: "alignmentjs"
};

Expand Down
7 changes: 7 additions & 0 deletions src/components/LargeTreeAlignment.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#alignmentjs-guideTree-div path.branch {
opacity: 0.6;
}

#alignmentjs-guideTree-div path.branch:hover {
stroke-width: 2px;
}
307 changes: 307 additions & 0 deletions src/components/LargeTreeAlignment.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
import React, { Component } from "react";
const d3 = require("phylotree/node_modules/d3");

import "phylotree/phylotree.css";
require("phylotree");

import BaseAlignment from "./BaseAlignment.jsx";
import SiteAxis from "./SiteAxis.jsx";
import SequenceAxis from "./SequenceAxis.jsx";
import fastaParser from "./../helpers/fasta";
import ScrollBroadcaster from "./../helpers/ScrollBroadcaster";
import computeLabelWidth from "../helpers/computeLabelWidth";

import "./LargeTreeAlignment.css";

class LargeTreeAlignment extends Component {
constructor(props) {
super(props);
this.column_sizes = [700, 700, 200, 700];
this.row_sizes = [20, 700];
if (props.fasta) {
this.preprocess(props);
}
}
preprocess(props) {
const { site_size } = this.props;
this.sequence_data = fastaParser(props.fasta);
const number_of_sequences = this.sequence_data.length;
this.tree_size = number_of_sequences * site_size;
this.main_tree = d3.layout
.phylotree()
.options({
"left-right-spacing": "fit-to-size",
"top-bottom-spacing": "fit-to-size",
"show-scale": false,
"align-tips": true,
"show-labels": false,
selectable: false
})
.size([this.tree_size, this.tree_size])
.node_circle_size(0);
this.parsed = d3.layout.newick_parser(props.newick);
this.main_tree(this.parsed);

const label_width = computeLabelWidth(
this.sequence_data,
this.props.label_padding
);

this.column_sizes[3] += this.column_sizes[2] - label_width;
this.column_sizes[2] = label_width;

var i = 0;
this.main_tree.traverse_and_compute(function(n) {
var d = 1;
if (!n.name) {
n.name = "Node" + i++;
}
if (n.children && n.children.length) {
d += d3.max(n.children, function(d) {
return d["count_depth"];
});
}
n["count_depth"] = d;
});

this.main_tree.resort_children(
function(a, b) {
return a["count_depth"] - b["count_depth"];
},
null,
null,
true
);

const ordered_leaf_names = this.main_tree
.get_nodes(true)
.filter(d3.layout.phylotree.is_leafnode)
.map(d => d.name);

this.sequence_data.sort((a, b) => {
const a_index = ordered_leaf_names.indexOf(a.header),
b_index = ordered_leaf_names.indexOf(b.header);
return a_index - b_index;
});
}
postprocess(props) {
this.main_tree.svg(d3.select("#alignmentjs-largeTreeAlignment")).layout();

const guide_height = this.row_sizes[1],
guide_width = this.column_sizes[0];

this.guide_tree = d3.layout
.phylotree()
.svg(d3.select("#alignmentjs-guideTree"))
.options({
"left-right-spacing": "fit-to-size",
// fit to given size top-to-bottom
"top-bottom-spacing": "fit-to-size",
// fit to given size left-to-right
collapsible: false,
// turn off the menu on internal nodes
transitions: false,
// turn off d3 animations
"show-scale": false,
// disable brush
brush: false,
// disable selections on this tree
selectable: false
})
.size([guide_height, guide_width])
.node_circle_size(0);

this.guide_tree(this.parsed).layout();

this.guide_x_scale = d3.scale
.linear()
.domain([0, this.tree_size])
.range([0, guide_width]);
this.guide_y_scale = d3.scale
.linear()
.domain([0, this.tree_size])
.range([0, guide_height]);
this.rect = d3
.select("#alignmentjs-guideTree")
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("id", "guide-rect")
.style("opacity", 0.6)
.style("stroke-width", "1px")
.style("stroke", "red")
.style("fill", "pink")
.attr("width", this.guide_x_scale(guide_width))
.attr("height", this.guide_y_scale(guide_height));

const { sequence_data } = this,
{ site_size } = props,
height = this.row_sizes[1],
width = this.column_sizes[3],
full_pixel_width = site_size * sequence_data.number_of_sites,
full_pixel_height = site_size * sequence_data.number_of_sequences;
this.scroll_broadcaster = new ScrollBroadcaster(
{ width: full_pixel_width, height: full_pixel_height },
{ width: width, height: height },
{ x_pixel: 0, y_pixel: 0 },
[
"alignmentjs-alignment",
"alignmentjs-labels-div",
"alignmentjs-largeTreeAlignment-div",
"alignmentjs-guideTree-div",
"alignmentjs-axis-div"
]
);

const { scroll_broadcaster, guide_x_scale, guide_y_scale, rect } = this;
scroll_broadcaster.setListeners();

$("#alignmentjs-guideTree-div").off("wheel");
$("#alignmentjs-guideTree-div").on("wheel", function(e) {
e.preventDefault();
const guide_x = +d3.select("#guide-rect").attr("x");
const new_guide_x = Math.min(
Math.max(guide_x + guide_x_scale(e.originalEvent.deltaX), 0),
guide_width - guide_x_scale(guide_width)
);
rect.attr("x", new_guide_x);

const guide_y = +d3.select("#guide-rect").attr("y");
const new_guide_y = Math.min(
Math.max(guide_y + guide_y_scale(e.originalEvent.deltaY), 0),
guide_height - guide_y_scale(guide_height)
);
rect.attr("y", new_guide_y);

const new_x_pixel = guide_x_scale.invert(new_guide_x),
new_y_pixel = guide_y_scale.invert(new_guide_y);
$("#alignmentjs-largeTreeAlignment-div").scrollLeft(new_x_pixel);
$("#alignmentjs-largeTreeAlignment-div").scrollTop(new_y_pixel);

const e_mock = {
originalEvent: {
deltaX: 0,
deltaY: e.originalEvent.deltaY
}
};
scroll_broadcaster.handleWheel(e_mock, "tree");
});

$("#alignmentjs-largeTreeAlignment-div").off("wheel");
$("#alignmentjs-largeTreeAlignment-div").on("wheel", function(e) {
const guide_x = +d3.select("#guide-rect").attr("x");
const new_guide_x = Math.min(
Math.max(guide_x + guide_x_scale(e.originalEvent.deltaX), 0),
guide_width - guide_x_scale(guide_width)
);
rect.attr("x", new_guide_x);

const guide_y = +d3.select("#guide-rect").attr("y");
const new_guide_y = Math.min(
Math.max(guide_y + guide_y_scale(e.originalEvent.deltaY), 0),
guide_height - guide_y_scale(guide_height)
);
rect.attr("y", new_guide_y);

const e_mock = {
originalEvent: {
deltaX: 0,
deltaY: e.originalEvent.deltaY
}
};
scroll_broadcaster.handleWheel(e_mock, "tree");
});

$("#alignmentjs-alignment").on("wheel", function(e) {
e.preventDefault();
const guide_y = +d3.select("#guide-rect").attr("y");
const new_guide_y = Math.min(
Math.max(guide_y + guide_y_scale(e.originalEvent.deltaY), 0),
guide_height - guide_y_scale(guide_height)
);
rect.attr("y", new_guide_y);
scroll_broadcaster.handleWheel(e, "alignment");
});

d3.select("#alignmentjs-guideTree").on("click", null);
d3.select("#alignmentjs-guideTree").on("click", function() {
const coords = d3.mouse(this),
new_x_pixel = guide_x_scale.invert(coords[0]),
new_y_pixel = guide_y_scale.invert(coords[1]),
current_x_fraction = scroll_broadcaster.x_fraction,
new_y_fraction = new_y_pixel / scroll_broadcaster.full_pixel_height;
scroll_broadcaster.broadcast(current_x_fraction, new_y_fraction);
rect.attr("x", coords[0]);
rect.attr("y", coords[1]);
$("#alignmentjs-largeTreeAlignment-div").scrollLeft(new_x_pixel);
$("#alignmentjs-largeTreeAlignment-div").scrollTop(new_y_pixel);
});

document
.getElementById("alignmentjs-largeTreeAlignment-div")
.addEventListener("alignmentjs_wheel_event", e => {
if (e.detail.sender == "alignment") {
$("#alignmentjs-largeTreeAlignment-div").scrollTop(e.detail.y_pixel);
}
});
}
componentDidMount() {
if (this.props.fasta) {
this.postprocess(this.props);
}
}
componentWillUpdate(nextProps) {
this.preprocess(nextProps);
}
componentDidUpdate() {
this.postprocess(this.props);
}
render() {
if (!this.props.fasta) {
return <div />;
}
const template_css = {
display: "grid",
gridTemplateColumns: this.column_sizes.join("px ") + "px",
gridTemplateRows: this.row_sizes.join("px ") + "px"
};
return (
<div style={template_css}>
<div />
<div />
<div />
<SiteAxis
width={this.column_sizes[3]}
height={this.row_sizes[0]}
sequence_data={this.sequence_data}
/>
<div id="alignmentjs-guideTree-div">
<svg id="alignmentjs-guideTree" />
</div>
<div
id="alignmentjs-largeTreeAlignment-div"
style={{ overflowX: "scroll", overflowY: "scroll" }}
>
<svg id="alignmentjs-largeTreeAlignment" />
</div>
<SequenceAxis
width={this.column_sizes[2]}
height={this.row_sizes[1]}
sequence_data={this.sequence_data}
/>
<BaseAlignment
width={this.column_sizes[3]}
height={this.row_sizes[1]}
sequence_data={this.sequence_data}
/>
</div>
);
}
}

LargeTreeAlignment.defaultProps = {
label_padding: 10,
site_size: 20
};

module.exports = LargeTreeAlignment;
Loading

0 comments on commit 3f789a2

Please sign in to comment.