Skip to content

Commit

Permalink
created a simple web fronted using wasm
Browse files Browse the repository at this point in the history
  • Loading branch information
pbnjay committed Oct 29, 2019
1 parent 397ac2c commit 6b1ec52
Show file tree
Hide file tree
Showing 13 changed files with 938 additions and 19 deletions.
16 changes: 16 additions & 0 deletions data/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// +build !wasm

package data

import (
"net/http"
"net/url"
)

func httpGet(url string) (*http.Response, error) {
return http.Get(url)
}

func httpPostForm(url string, vals url.Values) (*http.Response, error) {
return http.PostForm(url, vals)
}
42 changes: 42 additions & 0 deletions data/http_wasm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// +build wasm

package data

import (
"io/ioutil"
"net/http"
"net/url"
"strings"
)

// implements http.Get but makes wasm's fetch work with CORS
func httpGet(url string) (*http.Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Add("js.fetch:mode", "cors")

resp, err := http.DefaultClient.Do(req)
bb, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
resp.Body = ioutil.NopCloser(strings.NewReader(string(bb)))
return resp, err
}

// implements http.PostForm but makes wasm's fetch work with CORS
func httpPostForm(wurl string, vals url.Values) (*http.Response, error) {
body := strings.NewReader(vals.Encode())
req, err := http.NewRequest("POST", wurl, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("js.fetch:mode", "cors")

resp, err := http.DefaultClient.Do(req)
bb, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
resp.Body = ioutil.NopCloser(strings.NewReader(string(bb)))
return resp, err
}
9 changes: 4 additions & 5 deletions data/pfam.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@ import (
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
)

const PfamGraphicURL = "http://pfam.xfam.org/protein/%s/graphic"
const PfamGraphicURL = "https://pfam.xfam.org/protein/%s/graphic"

func GetPfamGraphicData(accession string) (*GraphicResponse, error) {
queryURL := fmt.Sprintf(PfamGraphicURL, accession)
resp, err := http.Get(queryURL)
resp, err := httpGet(queryURL)
if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
fmt.Fprintf(os.Stderr, "Unable to connect to Pfam. Check your internet connection or try again later.")
Expand All @@ -57,13 +56,13 @@ func GetPfamGraphicData(accession string) (*GraphicResponse, error) {
r := data[0]
for i, x := range r.Motifs {
if x.Link != "" {
x.Link = "http://pfam.xfam.org" + x.Link
x.Link = "https://pfam.xfam.org" + x.Link
r.Motifs[i] = x
}
}
for i, x := range r.Regions {
if x.Link != "" {
x.Link = "http://pfam.xfam.org" + x.Link
x.Link = "https://pfam.xfam.org" + x.Link
r.Regions[i] = x
}
}
Expand Down
9 changes: 4 additions & 5 deletions data/uniprot.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"regexp"
Expand Down Expand Up @@ -61,7 +60,7 @@ func getValueForKey(line, key string) string {

func GetUniprotGraphicData(accession string) (*GraphicResponse, error) {
queryURL := fmt.Sprintf(UniprotDataURL, accession)
resp, err := http.Get(queryURL)
resp, err := httpGet(queryURL)
if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
fmt.Fprintf(os.Stderr, "Unable to connect to Uniprot. Check your internet connection or try again later.")
Expand Down Expand Up @@ -167,11 +166,11 @@ func GetUniprotGraphicData(accession string) (*GraphicResponse, error) {
}

func GetProtID(symbol string) (string, error) {
apiURL := `http://www.uniprot.org/uniprot/?query=` + url.QueryEscape(symbol)
apiURL := `https://www.uniprot.org/uniprot/?query=` + url.QueryEscape(symbol)
apiURL += `+AND+reviewed:yes+AND+organism:9606+AND+database:pfam`
apiURL += `&sort=score&columns=id,entry+name,reviewed,genes,organism&format=tab`

resp, err := http.Get(apiURL)
resp, err := httpGet(apiURL)
if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
fmt.Fprintf(os.Stderr, "Unable to connect to Uniprot. Check your internet connection or try again later.")
Expand Down Expand Up @@ -229,7 +228,7 @@ func GetProtMapping(dbname, geneid string) (string, error) {
"format": {"tab"},
}

resp, err := http.PostForm(apiURL, params)
resp, err := httpPostForm(apiURL, params)
if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
fmt.Fprintf(os.Stderr, "Unable to connect to Uniprot. Check your internet connection or try again later.")
Expand Down
5 changes: 5 additions & 0 deletions drawing/draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ type diagram struct {
}

func (s *Settings) prepare(changelist []string, g *data.GraphicResponse) *diagram {
// don't alter the source changelist
newchanges := make([]string, len(changelist))
copy(newchanges, changelist)
changelist = newchanges

d := &diagram{
Settings: s,
g: g,
Expand Down
22 changes: 22 additions & 0 deletions html/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Lollipops web frontend

This directory contains a simple web frontend for lollipops, using the
template provided by Go's wasm toolchain in `$GOROOT/misc/wasm/`

Building lollipops for wasm using:

cd $GOPATH/github.com/pbnjay/lollipops
GOOS=js GOARCH=wasm go build -o html/lollipops.wasm .

You can then copy the 4 files to any webserver:

index.html
lollipops.wasm
style.css
wasm_exec.js


A simple server is included here for testing purposes:

cd $GOPATH/github.com/pbnjay/lollipops/html
go run www.go
154 changes: 154 additions & 0 deletions html/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Lollipops - Protein Variant Visualization tool</title>
<link rel="stylesheet" href="style.css">
</head>

<body>
<!--
Add the following polyfill for Microsoft Edge 17/18 support:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/encoding.min.js"></script>
(see https://caniuse.com/#feat=textencoder)
-->
<script src="wasm_exec.js"></script>
<script>
if (!WebAssembly.instantiateStreaming) { // polyfill
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}

const go = new Go();
let mod, inst;
WebAssembly.instantiateStreaming(fetch("lollipops.wasm"), go.importObject).then((result) => {
mod = result.module;
inst = result.instance;
document.getElementById("runButton").disabled = false;
document.getElementById("spinner").style.display = "none";
}).catch((err) => {
console.error(err);
});

async function run() {
console.clear();
var args = ['./lollipops', "-o", "lollipops-image"];
if( document.getElementById('legend').checked ) {
args.push("-legend");
}
if( document.getElementById('labels').checked ) {
args.push("-labels");
}
var idt = document.getElementById('id_type');
idt = idt.options[idt.selectedIndex].value;
if( idt != "" ) {
args = args.concat(idt.split(" "));
}
args.push(document.getElementById('gene_id').value);
args = args.concat(document.getElementById('vars').value.split(/\s+/));
go.argv = args;
document.getElementById("spinner").style.display = "block";
await go.run(inst);
var elem = document.getElementById("lollipops-svg-container").children[0];
elem.setAttribute("viewBox", "0 0 "+elem.width.baseVal.value+" "+elem.height.baseVal.value);
elem.setAttribute("width", "100%");
elem.removeAttribute("height")

document.getElementById("printButton").disabled = false;
document.getElementById("spinner").style.display = "none";
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
}

async function toggleHelp(){
if( document.getElementById("varhelp").style.display == "block" ) {
document.getElementById("varhelp").style.display = "none";
} else {
document.getElementById("varhelp").style.display = "block";
}
}

/*
async function print() {
var elem = document.getElementById("lollipops-svg-container").children[0];
var popupWin = window.open('', '_blank', 'scrollbars=no,menubar=no,toolbar=no,location=no,status=no,titlebar=no');
popupWin.document.open();
popupWin.document.write('<html><head><style>svg{font-family:sans-serif;}</style></head><body onload="window.print()">' + elem.outerHTML + '</body></html>');
popupWin.document.close();
}*/
</script>

<fieldset class="no-print">
<label>Gene to Annotate:</label><br/>
<select id="id_type">
<option selected value="">Gene Symbol (e.g. TP53, BRCA)</option>
<option value="-Q P_REFSEQ_AC">RefSeq ID (e.g. NP_001265252.1)</option>
<option value="-Q P_ENTREZGENEID">Entrez GeneID (e.g. 4336)</option>
<option value="-Q ENSEMBL_ID">Ensembl Gene ID (e.g. ENSG00000168314)</option>
</select>:
<input type="text" id="gene_id" value="TP53"><br><br>

<label>Variants: <a href="#" onclick="toggleHelp();return false;">variant format</a></label><br>
<textarea cols="100" rows="5" id="vars">R248Q#ff99ff@131
R273C
R175H
T125@5</textarea><br>
<pre id="varhelp" style="display:none;">Currently only point mutations are supported, and may be specified as:

&lt;AMINO&gt;<b>&lt;CODON&gt;</b>&lt;AMINO&gt;&lt;#COLOR&gt;&lt;@COUNT&gt;

Only CODON is required, and AMINO tags are not parsed.

Synonymous mutations are denoted if the first AMINO tag matches the second
AMINO tag, or if the second tag is not present. Otherwise the non-synonymous
mutation color is used. The COLOR tag will override using the #RRGGBB style
provided. The COUNT tag can be used to scale the lollipop marker size so that
the area is exponentially proportional to the count indicated. Examples:

R273C -- non-synonymous mutation at codon 273
T125@5 -- synonymous mutation at codon 125 with "5x" marker sizing
R248Q#00ff00 -- green lollipop at codon 248
R248Q#00ff00@131 -- green lollipop at codon 248 with "131x" marker sizing

Separate multiple variants with a space, or place them on separate lines.
</pre></span><br>
<br>
<label><input type="checkbox" id="legend" checked> Show Legend</label><br>
<label><input type="checkbox" id="labels"> Show Lollipop Labels</label><br>

<button onClick="run();" id="runButton" disabled>Run</button>
<button onClick="window.print();" id="printButton" disabled>Print</button>

<div id="spinner" class="sk-fading-circle">
<div class="sk-circle1 sk-circle"></div>
<div class="sk-circle2 sk-circle"></div>
<div class="sk-circle3 sk-circle"></div>
<div class="sk-circle4 sk-circle"></div>
<div class="sk-circle5 sk-circle"></div>
<div class="sk-circle6 sk-circle"></div>
<div class="sk-circle7 sk-circle"></div>
<div class="sk-circle8 sk-circle"></div>
<div class="sk-circle9 sk-circle"></div>
<div class="sk-circle10 sk-circle"></div>
<div class="sk-circle11 sk-circle"></div>
<div class="sk-circle12 sk-circle"></div>
</div>
</fieldset>

<!-- canvas id="lollipops-image" style="max-width:90%;"></canvas -->
<div id="lollipops-svg-container" style="max-width:90%;"></div>

<div class="footer">
<b>Citation:</b>
<p>Jay JJ, Brouwer C (2016) Lollipops in the Clinic: Information Dense
Mutation Plots for Precision Medicine. PLoS ONE 11(8): e0160519.
doi: <a href="http://dx.doi.org/10.1371/journal.pone.0160519"
rel="nofollow">10.1371/journal.pone.0160519</a>.
</p>
<br>
<p>This tool is also available as a command-line program that may be incorporated
into your own pipelines. Open source at <a href="https://github.com/pbnjay/lollipops">https://github.com/pbnjay/lollipops</a>.</p>
</div>
</body>
</html>
Loading

0 comments on commit 6b1ec52

Please sign in to comment.