Skip to content

Commit

Permalink
FNA Viewer, restore webpack-dev-server.
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenshank committed Dec 17, 2018
1 parent d89329b commit 54aede2
Show file tree
Hide file tree
Showing 12 changed files with 774 additions and 57 deletions.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@
"uglifyjs-webpack-plugin": "^1.1.4",
"webpack": "4",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10",
"webpack-merge": "^4.1.1"
},
"scripts": {
"start": "npx concurrently 'yarn develop' 'yarn serve'",
"develop": "webpack --config webpack.dev.js --mode=development --watch",
"develop": "npx webpack-dev-server --config webpack.dev.js --open",
"serve": "node src/server.js",
"build-app": "npx webpack --config webpack.prod.js",
"build-lib": "npx webpack --config webpack.lib.js",
Expand All @@ -65,5 +65,6 @@
"repository": {
"type": "git",
"url": "https://github.com/veg/alignment.js"
}
},
"sideEffects": false
}
3 changes: 2 additions & 1 deletion src/Alignment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ class Alignment extends Component {
});
}
}
componentWillUpdate(nextProps) {
shouldComponentUpdate(nextProps) {
this.initialize(nextProps);
return true;
}
initialize(props) {
if (props.fasta) {
Expand Down
File renamed without changes.
54 changes: 33 additions & 21 deletions src/components/TreeAlignment.jsx → src/TreeAlignment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,25 @@ 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 BaseTree from "./BaseTree.jsx";
import fastaParser from "./../helpers/fasta";
import ScrollBroadcaster from "./../helpers/ScrollBroadcaster";
import computeLabelWidth from "../helpers/computeLabelWidth";
import sortFASTAAndNewick from "../helpers/jointSort";
import BaseAlignment from "./components/BaseAlignment.jsx";
import SiteAxis from "./components/SiteAxis.jsx";
import SequenceAxis from "./components/SequenceAxis.jsx";
import BaseTree from "./components/BaseTree.jsx";
import fastaParser from "./helpers/fasta";
import ScrollBroadcaster from "./helpers/ScrollBroadcaster";
import computeLabelWidth from "./helpers/computeLabelWidth";
import sortFASTAAndNewick from "./helpers/jointSort";

class TreeAlignment extends Component {
constructor(props) {
super(props);
this.column_sizes = [500, 200, 700];
this.row_sizes = [20, 700];
this.column_sizes = [200, 200, 560];
this.row_sizes = [props.axis_height, 700];
this.initialize(props);
}
componentWillUpdate(nextProps) {
shouldComponentUpdate(nextProps) {
this.initialize(nextProps);
return true;
}
setScrollingEvents(props) {
if (props.fasta && props.newick) {
Expand All @@ -47,9 +48,9 @@ class TreeAlignment extends Component {
initialize(props) {
if (props.fasta && props.newick) {
this.sequence_data = fastaParser(props.fasta);
this.full_pixel_height = this.props.site_size * this.sequence_data.length;
this.full_pixel_height = props.site_size * this.sequence_data.length;
this.full_pixel_width =
this.props.site_size * this.sequence_data[0].seq.length;
props.site_size * this.sequence_data[0].seq.length;
const phylotree_size = [this.full_pixel_height, this.column_sizes[0]];
const { phylotree, tree_json } = sortFASTAAndNewick(
this.sequence_data,
Expand All @@ -73,18 +74,25 @@ class TreeAlignment extends Component {
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"
};
const { axis_height } = this.props,
{ full_pixel_height } = this,
max_height = this.row_sizes[0] + this.row_sizes[1],
height = full_pixel_height
? Math.min(full_pixel_height + axis_height, max_height)
: max_height,
gridTemplateRows = [axis_height, height - axis_height],
template_css = {
display: "grid",
gridTemplateColumns: this.column_sizes.join("px ") + "px",
gridTemplateRows: gridTemplateRows.join("px ") + "px"
};
return (
<div style={template_css}>
<div />
<div />
<SiteAxis
width={this.column_sizes[2]}
height={this.row_sizes[0]}
height={axis_height}
sequence_data={this.sequence_data}
scroll_broadcaster={this.scroll_broadcaster}
/>
Expand All @@ -96,11 +104,15 @@ class TreeAlignment extends Component {
width={this.column_sizes[1]}
height={this.row_sizes[1]}
sequence_data={this.sequence_data}
site_size={this.props.site_size}
scroll_broadcaster={this.scroll_broadcaster}
/>
<BaseAlignment
width={this.column_sizes[2]}
height={this.row_sizes[1]}
site_size={this.props.site_size}
site_color={this.props.site_color}
molecule={this.props.molecule}
height={gridTemplateRows[1]}
sequence_data={this.sequence_data}
scroll_broadcaster={this.scroll_broadcaster}
/>
Expand All @@ -112,7 +124,7 @@ class TreeAlignment extends Component {
TreeAlignment.defaultProps = {
label_padding: 10,
site_size: 20,
axis_height: 20
axis_height: 25
};

module.exports = TreeAlignment;
5 changes: 4 additions & 1 deletion src/app/FASTA.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ class FASTAViewer extends Component {
text("data/CD2.fasta").then(data => this.loadFASTA(data));
}
loadFASTA(fasta) {
this.setState({ data: fastaParser(fasta) });
this.setState({
data: fastaParser(fasta),
show_differences: ""
});
}
saveFASTA() {
const blob = new Blob([fastaToText(this.state.data)], {
Expand Down
118 changes: 117 additions & 1 deletion src/app/FNA.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import React, { Component } from "react";
import { text } from "d3-fetch";
import { saveAs } from "file-saver";

import { fnaParser, fnaToText } from "../helpers/fasta";
import TreeAlignment from "../TreeAlignment.jsx";
import Button from "./Button.jsx";
import FileUploadButton from "./FileUploadButton.jsx";
import Modal from "./Modal.jsx";
import { nucleotide_color, nucleotide_difference } from "../helpers/colors";

function Immunology(props) {
return <h1>Immunology example will go here.</h1>;
Expand All @@ -11,9 +20,116 @@ function HIV(props) {
class FNAViewer extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
site_size: 20,
show_differences: ""
};
}
componentDidMount() {
text("data/CD2.fna").then(data => this.loadFNA(data));
}
loadFNA(fasta) {
this.setState({
data: fnaParser(fasta, true),
show_differences: ""
});
}
saveFNA() {
const blob = new Blob([fnaToText(this.state.data)], {
type: "text/plain:charset=utf-8;"
});
saveAs(blob, "sequences.fna");
}
siteColor() {
if (!this.state.show_differences) return nucleotide_color;
const desired_record = this.state.data.fasta.filter(
datum => datum.header == this.state.show_differences
)[0];
return nucleotide_difference(desired_record);
}
molecule() {
if (!this.state.show_differences) return molecule => molecule;
const desired_record = this.state.data.fasta.filter(
datum => datum.header == this.state.show_differences
)[0];
return (mol, site, header) => {
if (mol == "-") return "-";
if (header == desired_record.header) return mol;
return mol == desired_record.seq[site - 1] ? "." : mol;
};
}
handleFileChange = e => {
const files = e.target.files;
if (files.length == 1) {
const file = files[0],
reader = new FileReader();
reader.onload = e => {
this.loadFNA(e.target.result);
};
reader.readAsText(file);
}
document.body.click();
};
render() {
return <h1>FNA viewer will go here.</h1>;
const toolbar_style = {
display: "flex",
justifyContent: "space-between",
width: 960
};
const options = this.state.data
? this.state.data.fasta.map(datum => {
return (
<option key={datum.header} value={datum.header}>
{datum.header}
</option>
);
})
: null;
return (
<div>
<h1>FNA Viewer Application</h1>
<div style={toolbar_style}>
<FileUploadButton label="Import" onChange={this.handleFileChange} />
<Button label="Export" onClick={() => $("#modal").modal("show")} />
<span>
<label>Site size:</label>
<input
type="number"
value={this.state.site_size}
min={15}
max={100}
step={5}
onChange={e => this.setState({ site_size: e.target.value })}
/>
</span>
<span>
<label>Highlight difference from:</label>
<select
value={this.state.show_differences}
onChange={e =>
this.setState({ show_differences: e.target.value })
}
>
<option value={""}>None</option>
{options}
</select>
</span>
</div>
<TreeAlignment
{...this.state.data}
site_size={this.state.site_size}
molecule={this.molecule()}
site_color={this.siteColor()}
/>
<Modal title="Export fasta">
<Button label="Download" onClick={() => this.saveFNA()} />
<div style={{ overflowY: "scroll", width: 400, height: 400 }}>
<p>{this.state.data ? fnaToText(this.state.data) : null}</p>
</div>
</Modal>
</div>
);
}
}

Expand Down
34 changes: 34 additions & 0 deletions src/helpers/fasta.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import sortFASTAAndNewick from "./jointSort.js";

function fastaParser(fasta) {
if (typeof fasta == "object") return fasta;
var seqs = [],
Expand Down Expand Up @@ -34,5 +36,37 @@ function fastaToText(fasta) {
);
}

function fnaParser(fna, sortFASTA) {
var i = fna.length - 2,
current_char = fna[i];
while (current_char != "\n") {
i--;
current_char = fna[i];
}
const parsed_fasta = fastaParser(fna.slice(0, i + 1)),
newick = fna.slice(i + 1, fna.length - 1);
if (sortFASTA) {
sortFASTAAndNewick(parsed_fasta, newick, 20);
}
return {
fasta: parsed_fasta,
newick: newick
};
}

function fnaToText(fna) {
return (
fna.fasta
.map(record => {
return ">" + record.header + "\n" + record.seq;
})
.join("\n") +
"\n" +
fna.newick
);
}

module.exports = fastaParser;
module.exports.fastaToText = fastaToText;
module.exports.fnaParser = fnaParser;
module.exports.fnaToText = fnaToText;
File renamed without changes.
32 changes: 24 additions & 8 deletions tests/fasta.test.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
const path = require("path"),
fs = require("fs");

import fastaParser, { fastaToText } from "./../src/helpers/fasta";
import fastaParser, {
fastaToText,
fnaParser,
fnaToText
} from "./../src/helpers/fasta";

const internalFastaDataStructure = [
{ header: "Seq1", seq: "ATCGTAATTGCA" },
{ header: "Seq2", seq: "CTCGTAATGGCC" },
{ header: "Seq3", seq: "GTCGTCAATGCT" }
],
simple_path = path.resolve(__dirname, "..", "dist", "data", "Simple.fasta"),
fasta = fs.readFileSync(simple_path).toString();
simple_fasta_path = path.resolve(
__dirname,
"..",
"dist",
"data",
"Simple.fasta"
),
simple_fna_path = path.resolve(__dirname, "..", "dist", "data", "Simple.fna"),
fasta = fs.readFileSync(simple_fasta_path).toString(),
fna = fs.readFileSync(simple_fna_path).toString();
internalFastaDataStructure.number_of_sequences = 3;
internalFastaDataStructure.number_of_sites = 12;

test("Imports simple fasta correctly.", () => {
internalFastaDataStructure.number_of_sequences = 3;
internalFastaDataStructure.number_of_sites = 12;
const parsedFasta = fastaParser(fasta);
expect(parsedFasta).toEqual(internalFastaDataStructure);
});

test("Converts fasta to text correctly.", () => {
const text = fastaToText(internalFastaDataStructure);
expect(text).toEqual(fasta);
test.only("Import simple fna correctly.", () => {
const internalFnaDataStructure = {
fasta: internalFastaDataStructure,
newick: "((Seq1,Seq2),Seq3);"
},
parsed_fna = fnaParser(fna);
expect(parsed_fna).toEqual(internalFnaDataStructure);
});
7 changes: 6 additions & 1 deletion webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@ const path = require("path"),
common = require("./webpack.common.js");

module.exports = merge(common, {
devtool: "inline-source-map"
mode: "development",
devtool: "inline-source-map",
devServer: {
contentBase: path.resolve(__dirname, "dist"),
historyApiFallback: true
}
});
Loading

0 comments on commit 54aede2

Please sign in to comment.