Skip to content

Commit fcb1a23

Browse files
committed
Sequence bar chart example.
1 parent 6bebe78 commit fcb1a23

10 files changed

+879
-2
lines changed

dist/data/quasispecies.fasta

+441
Large diffs are not rendered by default.

src/SequenceBarChart.jsx

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import React from "react";
2+
import { scaleLinear } from "d3-scale";
3+
import { max } from "d3-array";
4+
5+
import BaseAlignment from "./components/BaseAlignment.jsx";
6+
import SiteAxis from "./components/SiteAxis.jsx";
7+
import SequenceAxis from "./components/SequenceAxis.jsx";
8+
import Placeholder from "./components/Placeholder.jsx";
9+
import BaseSequenceBarPlot from "./components/BaseSequenceBarPlot.jsx";
10+
import fastaParser from "./helpers/fasta";
11+
import ScrollBroadcaster from "./helpers/ScrollBroadcaster";
12+
import AxisTop from "./components/AxisTop.jsx";
13+
import { nucleotide_color, nucleotide_text_color } from "./helpers/colors";
14+
import computeLabelWidth from "./helpers/computeLabelWidth";
15+
import css_grid_format from "./helpers/format";
16+
17+
function SequenceBarChart(props) {
18+
const has_sequence_data = props.fasta || props.sequence_data;
19+
if (!has_sequence_data || !props.data) return <div />;
20+
const sequence_data = props.sequence_data || fastaParser(props.fasta),
21+
{ width, bar_width, height, axis_height, site_size, label_padding } = props,
22+
label_width = computeLabelWidth(sequence_data, label_padding),
23+
full_pixel_width = sequence_data[0].seq.length * site_size,
24+
full_pixel_height = sequence_data.length * site_size,
25+
base_alignment_width = width - bar_width - label_width,
26+
base_alignment_height = height - axis_height,
27+
alignment_width = Math.min(full_pixel_width, base_alignment_width),
28+
alignment_height = Math.min(full_pixel_height, height - axis_height),
29+
scale = scaleLinear()
30+
.domain([0, max(props.data)])
31+
.range([props.left_bar_padding, bar_width - props.right_bar_padding]),
32+
container_style = {
33+
display: "grid",
34+
gridTemplateColumns: css_grid_format([
35+
label_width,
36+
base_alignment_width,
37+
bar_width
38+
]),
39+
gridTemplateRows: css_grid_format([axis_height, base_alignment_height])
40+
},
41+
scroll_broadcaster = new ScrollBroadcaster({
42+
width: full_pixel_width,
43+
height: full_pixel_height,
44+
x_pad: base_alignment_width,
45+
y_pad: base_alignment_height,
46+
bidirectional: [
47+
"alignmentjs-alignment",
48+
"alignmentjs-axis-div",
49+
"alignmentjs-bar"
50+
]
51+
});
52+
53+
return (
54+
<div id="alignmentjs-main-div" style={container_style}>
55+
<Placeholder width={label_width} height={axis_height} />
56+
<SiteAxis
57+
width={alignment_width}
58+
height={axis_height}
59+
sequence_data={sequence_data}
60+
scroll_broadcaster={scroll_broadcaster}
61+
/>
62+
<AxisTop
63+
width={bar_width}
64+
height={axis_height}
65+
scale={scale}
66+
label={props.label}
67+
/>
68+
<SequenceAxis
69+
width={label_width}
70+
height={alignment_height}
71+
sequence_data={sequence_data}
72+
site_size={site_size}
73+
scroll_broadcaster={scroll_broadcaster}
74+
/>
75+
<BaseAlignment
76+
sequence_data={sequence_data}
77+
width={alignment_width}
78+
height={alignment_height}
79+
site_color={props.site_color}
80+
text_color={props.text_color}
81+
site_size={props.site_size}
82+
molecule={props.molecule}
83+
scroll_broadcaster={scroll_broadcaster}
84+
/>
85+
<BaseSequenceBarPlot
86+
data={props.data}
87+
width={bar_width}
88+
height={alignment_height}
89+
scroll_broadcaster={scroll_broadcaster}
90+
scale={scale}
91+
/>
92+
</div>
93+
);
94+
}
95+
96+
SequenceBarChart.defaultProps = {
97+
site_color: nucleotide_color,
98+
text_color: nucleotide_text_color,
99+
label_padding: 10,
100+
left_bar_padding: 10,
101+
right_bar_padding: 20,
102+
site_size: 20,
103+
axis_height: 50,
104+
bar_width: 300,
105+
width: 960,
106+
height: 500,
107+
sender: "main",
108+
molecule: mol => mol
109+
};
110+
111+
export default SequenceBarChart;

src/app.jsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ import "./styles.scss";
22
import React, { Component } from "react";
33
import ReactDOM from "react-dom";
44
import { BrowserRouter, Route, NavLink } from "react-router-dom";
5-
//import "bootstrap";
65

76
import Home from "./app/home.jsx";
87
import * as FASTA from "./app/FASTA.jsx";
98
import * as FNA from "./app/FNA.jsx";
109
import * as BAM from "./app/BAM.jsx";
11-
import Components from "./app/Components.jsx";
1210
import PreventDefaultPatch from "./prevent_default_patch";
1311

1412
function Divider(props) {
@@ -64,6 +62,7 @@ function FASTALinks(props) {
6462
<Link to="/fasta-lowercase" header="Lower case alignment" />
6563
<Link to="/fasta-svg" header="SVG alignment" />
6664
<Link to="/fasta-quasispecies" header="Quasispecies" />
65+
<Link to="/fasta-sequence-bar" header="Sequence Bar Chart" />
6766
<Link to="/fasta-ar" header="Artificial Recombination" />
6867
<Link to="/fasta-click-and-hover" header="Click handler" />
6968
</Dropdown>
@@ -133,6 +132,10 @@ class App extends Component {
133132
<Route path="/fasta-lowercase" component={FASTA.Lowercase} />
134133
<Route path="/fasta-svg" component={FASTA.SVGAlignmentExample} />
135134
<Route path="/fasta-quasispecies" component={FASTA.Quasispecies} />
135+
<Route
136+
path="/fasta-sequence-bar"
137+
component={FASTA.SequenceBarChart}
138+
/>
136139
<Route path="/fasta-ar" component={FASTA.ArtificialRecombination} />
137140
<Route
138141
path="/fasta-click-and-hover"

src/app/FASTA.jsx

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import SVGAlignmentExample from "./FASTA/svg_example.jsx";
1616
import StartAtSiteAndSequence from "./FASTA/start_at_site_and_sequence.jsx";
1717
import Lowercase from "./FASTA/lowercase.jsx";
1818
import Quasispecies from "./FASTA/quasispecies.jsx";
19+
import SequenceBarChart from "./FASTA/sequence_bar_chart.jsx";
1920
import ArtificialRecombination from "./FASTA/artificial_recombination.jsx";
2021
import ClickAndHover from "./FASTA/click_and_hover.jsx";
2122

@@ -192,6 +193,7 @@ export {
192193
Lowercase,
193194
SVGAlignmentExample,
194195
Quasispecies,
196+
SequenceBarChart,
195197
ArtificialRecombination,
196198
ClickAndHover
197199
};

src/app/FASTA/sequence_bar_chart.jsx

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from "react";
2+
3+
import SequenceBarChart from "../../SequenceBarChart.jsx";
4+
import fastaParser from "../../helpers/fasta";
5+
import DataFetcher from "../../components/DataFetcher.jsx";
6+
7+
function Spreader(props) {
8+
// Quick wrapper around DataFetcher for multiple props (potential feature)
9+
return <SequenceBarChart {...props.data} label="Quasispecies frequency" />;
10+
}
11+
12+
export default function() {
13+
return (
14+
<div>
15+
<h1>Sequence Bar Chart</h1>
16+
<DataFetcher
17+
source="data/quasispecies.fasta"
18+
modifier={fasta => {
19+
const sequence_data = fastaParser(fasta),
20+
data = sequence_data.map(
21+
record => +record.header.split("_")[1].split("-")[1]
22+
);
23+
sequence_data.forEach(record => {
24+
record.header = record.header.split("_")[0];
25+
});
26+
return { sequence_data, data };
27+
}}
28+
>
29+
<Spreader />
30+
</DataFetcher>
31+
</div>
32+
);
33+
}

src/components/AxisTop.jsx

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from "react";
2+
import { AxisTop } from "d3-react-axis";
3+
4+
function AlignmentJSAxisTop(props) {
5+
return (
6+
<div style={{ width: props.width, height: props.height }}>
7+
<svg width={props.width} height={props.height}>
8+
<text
9+
x={props.width / 2}
10+
y={props.height / 4}
11+
textAnchor="middle"
12+
alignmentBaseline="middle"
13+
>
14+
{props.label}
15+
</text>
16+
<AxisTop
17+
scale={props.scale}
18+
ticks={props.ticks}
19+
transform={`translate(0, ${props.height - 1})`}
20+
/>
21+
</svg>
22+
</div>
23+
);
24+
}
25+
26+
AxisTop.defaultProps = {
27+
ticks: [5]
28+
};
29+
30+
export default AlignmentJSAxisTop;
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React, { Component } from "react";
2+
import { scaleLinear } from "d3-scale";
3+
4+
import { vertical_scroll, handle_wheel } from "../helpers/scroll_events";
5+
6+
function SVGSequenceBarPlot(props) {
7+
const { width, site_size, data, scale, color, outline } = props;
8+
return (
9+
<svg width={width} height={site_size * data.length}>
10+
{data
11+
? data.map((datum, i) => {
12+
return (
13+
<rect
14+
x={scale(0)}
15+
y={i * site_size}
16+
width={scale(datum) - scale(0)}
17+
height={site_size}
18+
fill={color}
19+
stroke={outline}
20+
key={i}
21+
/>
22+
);
23+
})
24+
: null}
25+
</svg>
26+
);
27+
}
28+
29+
class BaseSequenceBarPlot extends Component {
30+
componentDidMount() {
31+
vertical_scroll.call(this);
32+
}
33+
handleWheel(e) {
34+
e.preventDefault();
35+
this.props.scroll_broadcaster.handleWheel(e, this.props.sender);
36+
}
37+
render() {
38+
const { width, height, div_id } = this.props,
39+
container_style = {
40+
width: width,
41+
height: height,
42+
overflowY: "scroll"
43+
};
44+
return (
45+
<div
46+
id={div_id}
47+
style={container_style}
48+
onWheel={e => this.handleWheel(e)}
49+
>
50+
<SVGSequenceBarPlot {...this.props} />
51+
</div>
52+
);
53+
}
54+
}
55+
56+
BaseSequenceBarPlot.defaultProps = {
57+
width: 500,
58+
height: 500,
59+
color: "red",
60+
outline: "black",
61+
div_id: "alignmentjs-bar",
62+
site_size: 20,
63+
sender: "main"
64+
};
65+
66+
export default BaseSequenceBarPlot;
67+
export { SVGSequenceBarPlot };

src/components/DataFetcher.jsx

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React, { Component } from "react";
2+
import { text, json } from "d3";
3+
4+
function fetcher(url) {
5+
const extension = url.split(".")[1];
6+
if (extension == "json") return json(url);
7+
return text(url);
8+
}
9+
10+
class DataFetcher extends Component {
11+
constructor(props) {
12+
super(props);
13+
this.state = {
14+
data: null
15+
};
16+
}
17+
componentDidMount() {
18+
const { source, modifier, child_prop } = this.props;
19+
let sources, modifiers, child_props;
20+
if (typeof source == "string") {
21+
sources = [source];
22+
} else {
23+
sources = source;
24+
}
25+
if (typeof modifier == "function") {
26+
modifiers = Array(sources.length).fill(modifier);
27+
} else {
28+
modifiers = modifier;
29+
}
30+
if (typeof child_prop == "string") {
31+
child_props = [child_prop];
32+
} else {
33+
child_props = child_prop;
34+
}
35+
Promise.all(sources.map(fetcher)).then(values => {
36+
const data = {};
37+
values.forEach((value, i) => {
38+
data[child_props[i]] = modifiers[i](value);
39+
});
40+
this.setState({ data });
41+
});
42+
}
43+
render() {
44+
if (!this.state.data) {
45+
return null;
46+
}
47+
return React.cloneElement(this.props.children, this.state.data);
48+
}
49+
}
50+
51+
DataFetcher.defaultProps = {
52+
modifier: data => data,
53+
child_prop: "data"
54+
};
55+
56+
export default DataFetcher;

0 commit comments

Comments
 (0)