To create a new Arbor app requires a little bit of knowledge about how Arbor works, and a working understanding of javascript.

Perhaps the easiest way to get started is to find an Arbor App that has inputs and outputs that are similar to your own. For example, several Arbor apps take a tree and a single column of numbers as input, and create a figure as output. Any of these can serve as a template for the others. To help you get started, we have a github repository of Arbor App templates.

For this exercise, I will describe how we can make an Arbor app that carries out stochastic character mapping using phytools. For this example we will do a very simple version of the app that creates a stochastic character map for a single character evolving on a tree.

Step 1. Produce working R code (could also be python)

The basic R code we will use was part of an R course taught by Liam Revell, Mike Alfaro, myself, and others. We will follow the instructions for ancestral character reconstruction, and try to reproduce this figure of anole ecomorph evolution:

plot of chunk unnamed-chunk-5

We can isolate all of the code from that page into a single script.

require(phytools)
data(anoletree)
## this is just to pull out the tip states from the
## data object - normally we would read this from file
x<-getStates(anoletree,"tips")
tree<-anoletree
rm(anoletree)

cols<-setNames(palette()[1:length(unique(x))],sort(unique(x)))

mtree<-make.simmap(tree,x,model="ER")
plotSimmap(mtree,cols,type="fan",fsize=0.8,ftype="i")

add.simmap.legend(colors=cols,prompt=FALSE,x=0.9*par()$usr[1],
    y=-max(nodeHeights(tree)),fsize=0.8)

Step 2. Convert code to work with generic data inputs.

To do this, we will have to make our function more generic so that instead of using the sample anolis data it will work for an arbitrary tree and data matrix.

# Example for data on my Desktop
variable = "ecomorph"
table = read.csv("~/Desktop/theanolefiles/anolisDataAppended.csv")
tree = read.tree("~/Desktop/theanolefiles/anolis.phy")
rownames(table)<-table[,1]
table = table[,-1]

# Paste into Arbor app starting here
require(phytools)
require(cardoonTools)

x<-table[,variable]
names(x) <- rownames(table)

cols<-setNames(palette()[1:length(unique(x))],sort(unique(x)))

mtree<-make.simmap(tree,x,model="ER")


simmap_plot <- function() {
    plotSimmap(mtree,cols,type="fan",fsize=0.8,ftype="i")
    add.simmap.legend(colors=cols,prompt=FALSE,x=0.9*par()$usr[1], y=-max(nodeHeights(tree)),fsize=0.8)
}

simmapPlot = cardoonPlot(expression(simmap_plot()), width=1000, height=1000, res=100)
simmapPlot = simmapPlot$png

You will notice that there is some funny business at the bottom for plotting. This is explained more fully on our plotting in Arbor workflows page.

Step 3. Make a working Arbor function.

Now create a working Arbor function based on the code in Step 2. You will need to use an Arbor instance that you have control over, like one that you have installed yourself. If you want to post an Arbor App on one our main AWS instances then you will need to get it working locally first and then contact me.

You might want more detail on how to do this. Once this step is complete you should have a working Arbor function called makeSimmap-app.

Step 4. Create the basic folder structure (helps to use a template)

The main Arbor apps live in the ArborWebApps github repository, so it might make sense to clone this repository and add your new app there. But that is optional and not required.

Arbor Apps are web pages. The minimum for an Arbor app, which we will use here, is to include two files: index.html, an html file that has the main structure of the app, and main.js, a javascript file to control the inputs, analyses, and reporting of results.

So - first, create a base folder, “simmap,” either in ArborWebApps or somewhere else on your computer. Within that folder, create a subfolder “js” to hold javascript files.

Now you will need to populate those folders. The next two steps will tell you how.

Step 5. Design the App page in the index.html file.

You will now make the index.html page that you need for our simmap app - or, really, for any app that has file inputs and outputs of an image.

Here is the full html that is required for the app to work. Below, I will go through the lines that have to be changed from the template to create this app.

lines 3-13: change the title of the page.

    <head>
        <title>Simmap - stochastic character mapping on trees</title>
        <link href=//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css rel=stylesheet>
        <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Droid+Sans:400,700">
        <link rel="stylesheet" href="../static/built/fontello/css/fontello.css">
        <link rel="stylesheet" href="../static/built/girder.ext.min.css">
        <link rel="stylesheet" href="../static/built/fontello/css/fontello.css">
        <link rel="stylesheet" href="../static/built/app.min.css">
        <link rel="stylesheet" href="../static/built/plugins/flow/flow.ext.min.css">
        <link rel="stylesheet" href="css/arbortools.css">
        <style>

lines 37-40 specify the column input box where a user chooses a single column. Since there is a template that already has that feature we don’t need to change a thing!

<!-- Select Y column -->
#column-input {
    padding: 50px;
}

lines 69-83 include brief documentation about the function, and should be modified (I didn’t finish though!)

<div class="panel-group">
  <div class="panel panel-default">
    <div class="panel-heading">
      <h4 class="panel-title">
        <a data-toggle="collapse" href="#collapse1"><b>Click here </b> for more information about stochastic character mapping</a>
      </h4>
    </div>
    <div id="collapse1" class="panel-collapse collapse">
      <div class="panel-body">
        <b>Stochastic character mapping</b> is ...
      </div>
      <div class="panel-footer">More info <a href = "http://phylogeneticcomparativemethods.com">textbook</a></div>
    </div>
  </div>
</div>

lines 141-148 are where the outputs will go. They are setup to hold both text and a single figure and don’t need to be altered for this app.

<div class="row">
    <div id="result" class="col-sm-12 full-width">
    </div>
</div>
<div class="row">
    <div id="model-plot" class="col-sm-12 text-center">
    </div>
</div>

So, basically, all that needs to be changed was the name of the page and the help info.

Step 6. Connect the page to Arbor in the /js/main.js file.

Next we make the main.js file that runs the simmap app.

Here is the full javascript that is required for the app to work. Below, I will go through the lines that have to be changed from the template to create this app.

line 12-23: the name of the arbor function must be specified here. This function has to be somewhere in the Arbor instance (can be in any collection). Beware that if the match is not perfect then girder will use a partial match - e.g. if makeSimmap-app does not exist but makeSimmap does, then it will be used!

// Lookup the ID of the analysis that we wish to perform.
app.analysisName = "makeSimmap-app";
girder.restRequest({
    path: 'resource/search',
    data: {
        q: app.analysisName,
        types: JSON.stringify(["item"])
    }
}).done(function (results) {
    app.analysisId = results["item"][0]._id;
    app.readyToAnalyze();
});

lines 138-146: these have to match the inputs and outputs of the Arbor function or workflow stated above exactly.

var inputs = {
    table:  {type: "table",  format: app.tableFormat,    data: app.table},
    tree:   {type: "tree",   format: "newick",           data: app.tree},
    variable: {type: "string", format: "text",             data: app.column}
};

var outputs = {
    simmapPlot: {type: "image", format: "png.base64"},
};

lines 160-164: this is where the result is obtained and plotted. The third part of data.result.simmapPlot.data should match the name of your function output above.

var result_url = '/item/' + this.ASRId + '/flow/' + this.taskId + '/result'
girder.restRequest({path: result_url}).done(_.bind(function (data) {
    app.modelPlot = data.result.simmapPlot.data;
  // render tree plot
  $("#model-plot").image({ data: app.modelPlot });

Step 6. Transfer your App to an Arbor vagrant instance.

ssh into an Arbor instance. Now get your new app folder onto that machine. Perhaps you, being very on the ball, have placed your app on github. Then it’s easy - just clone:

cd ~
git clone https://github.com/arborworkflows/ArborWebApps.git

Now make links to your app in /opt/flow/static.

cd /opt/flow/static
sudo ln -s ~/ArborWebApps/simmap/ .

This is almost enough. But you need to reboot girder so that it will recognize the new app.

sudo stop girder
sudo start girder

Step 7. Try the App.

Now navigate to the app, which will be up at (address of Arbor instance)/simmap/. You should be able to run your function with this nice drag and drop interface.