Skip to content

Adding regression work, need to get histogram result to plot #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 3, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
adding regression work, need to get histogram result to plot
Signed-off-by: Vanessa Sochat <vsochat@stanford.edu>
  • Loading branch information
vsoch committed Oct 2, 2019
commit fe61bd608da3afb28d3faa8b1014e300d0f5ef70
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

all:
go get github.com/aybabtme/uniplot/histogram
go get github.com/sajari/regression
GOOS=js GOARCH=wasm go build -o docs/main.wasm
79 changes: 77 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,80 @@
# Regression Wasm

This repository will serve code compiled to Wasm to perform regression in the browser
This repository serves a simple [web assembly](https://webassembly.org/) (wasm) application
to perform a regression, using data from a table in the browser, which can be loaded as a delimited file
by the user. We use a simple [regression library](https://github.com/sajari/regression) to do
the work.

*under development*
## About

### Why?

Web assembly can allow us to interact with compiled code directly in the browser,
doing away with any need for a server. While I don't do a large amount of data analysis
for my role proper, I realize that many researchers do, and so with this in mind,
I wanted to create a starting point for developers to interact with data in the browser.
The minimum conditions for success meant:

1. being able to load a delimited file into the browser
2. having the file render as a table
3. having the data be processed by a compiled wasm
4. updating a plot based on output from 3.

Thus, the application performs a simple regression based on loading data in the table,
and then plotting the result. To make it fun, I added a cute gopher logo and used an xkcd
plotting library for the result.

### Customization

The basics are here for a developer to create (some GoLang based) functions to
perform data analysis on an input file, and render back to the screen as a plot.
If you need any help, or want to request a custom tool, please don't hesitate to
[open up an issue](https://www.github.com/vsoch/regression-wasm/issues).

## Development

### Local

If you are comfortable with GoLang, and have installed [emscripten](https://emscripten.org),
you can clone the repository into your $GOPATH under the github folder:

```bash
$ mkdir -p $GOPATH/src.github.com/vsoch
$ cd $GOPATH/src.github.com/vsoch
$ git clone https://www.github.com/vsoch/regression-wasm
```

And then build the wasm.

```bash
$ make
```

And cd into the "docs" folder and start a server to see the result.

```bash
$ cd docs
$ python -m http.server 9999
```

Open the browser to http://localhost:9999


## Docker

If you don't want to install dependencies, just clone the repository, and
build the Docker image:

```bash
$ docker build -t vanessa/regression-wasm .
```

It will install [emscripten](https://emscripten.org/docs/getting_started/FAQ.html),
add the source code to the repository, and compile to wasm. You can then
run the container and expose port 80 to see the compiled interface:

```bash
$ docker run -it --rm -p 80:80 vanessa/regression-wasm
```

Then you can proceed to use the interface.
Binary file added docs/gopher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
253 changes: 253 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="@vsoch">
<title>Regression Wasm</title>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link href="https://fonts.googleapis.com/css?family=Oswald&display=swap" rel="stylesheet">
<style>
h1,h2,h3,h4,h5,h6,.navbar-brand {font-family: 'Oswald', sans-serif;}
#nav {margin-top:10px; margin-bottom:50px;}
h1,h2 {margin-bottom:20px}
#data-table {margin-bottom:20px}
#title{float:right; margin-top:30px;margin-left:10px;}
</style>
</head>
<body>
<div class="container" id="nav">
<nav class="navbar navbar-light bg-light">
<a class="navbar-brand" href="#">
<img src="gopher.png" width="100" height="100" class="d-inline-block align-top" alt="">
<div id="title">Regression Wasm</div></a> <small>Load data into the browser and plot a regression [<a href="https://www.github.com/vsoch/regression-wasm" target="_blank">about</a>]</small>
</nav>
</div>
<div class="container">
<div class="row">
<div class="col-md-12">
<p id="message"></p>
</div>
</div>
<div class="row">
<div class="col-md-6">
<h2>Data Table</h2>

<div class="form-check">
<input type="checkbox" class="form-check-input" id="headerInclude" checked>
<label class="form-check-label" for="headerInclude">Includes header row</label>
</div>

<div class="form-group">
<input type="text" class="form-control" id="inputDelim"
aria-describedby="delimHelp"
value=","
placeholder="Enter custom delimiter (if not comma)">
<small id="delimHelp" class="form-text text-muted">Enter a custom delimiter here if you don't want to use the default comma (,)</small>
</div>

<div class="form-group">
<label class="form-check-label" for="radios">Select Predictor Column</label>
<div id="radios">
</div>
</div>

<div class="form-group">
<div class="custom-file">
<input type="file"
class="custom-file-input" id="csvInput">
<label class="custom-file-label" for="inputGroupFile01">Choose data file (csv)</label>
</div>
</div>

<hr>
</div>
<div class="col-md-6">
<svg class="line-chart"></svg>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="data-table"></div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-csv/1.0.5/jquery.csv.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.xkcd@1.1/dist/chart.xkcd.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css" rel="stylesheet">
<script src="wasm_exec.js"></script>

<script>

// must be defined in Window for GoLang wasm to access
function drawPlot(string) {

console.log(string);
const svg = document.querySelector('.line-chart')
const lineChart = new chartXkcd.Line(svg, {
title: 'Monthly income of an indie developer', // optional
xLabel: 'Month', // optional
yLabel: '$ Dollors', // optional
data: {
labels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],
datasets: [{label: 'Plan',
data: [30, 70, 200, 300, 500, 800, 1500, 2900, 5000, 8000],
}, {label: 'Reality',
data: [0, 1, 30, 70, 80, 100, 50, 80, 40, 150],
}],
},
options: { // optional
yTickCount: 3,
legendPosition: chartXkcd.config.positionType.upLeft
}
});
}

$(document).ready(function(){

var starterData = [
['Murders per annum per one million inhabitants',
'Inhabitants',
'Percent with incomes below $5000',
'Percent unemployed'],
[11.2, 587000, 16.5, 6.2],
[13.4, 643000, 20.5, 6.4],
[40.7, 635000, 26.3, 9.3],
[5.3, 692000, 16.5, 5.3],
[24.8, 1248000, 19.2, 7.3],
[12.7, 643000, 16.5, 5.9],
[20.9, 1964000, 20.2, 6.4],
[35.7, 1531000, 21.3, 7.6],
[8.7, 713000, 17.2, 4.9],
[9.6, 749000, 14.3, 6.4],
[14.5, 7895000, 18.1, 6.0],
[26.9, 762000, 23.1, 7.4],
[15.7, 2793000, 19.1, 5.8],
[36.2, 741000, 24.7, 8.6],
[18.1, 625000, 18.6, 6.5],
[28.9, 854000, 24.9, 8.3],
[14.9, 716000, 17.9, 6.7],
[25.8, 921000, 22.4, 8.6],
[21.7, 595000, 20.2, 8.4],
[25.7, 3353000, 16.9, 6.7]
];

function updateRadios(number, selected) {
var number = number || 3
var selected = selected || 1

// If previous predictor column greater than columns, set to first
if (selected > number) {
selected = 1;
}

var radios = $("#radios")
radios.empty();

// Dynamically add radio buttons, account for predictor column
for (i = 1; i <= number; i++) {
if (selected == i) {
radios.append('<div class="form-check form-check-inline"><input class="form-check-input" type="radio" name="predictVar" id="predictVar" value="' + i + '" checked><label class="form-check-label" for="predictVar' + i + '">' + i + '</label>');
} else {
radios.append('<div class="form-check form-check-inline"><input class="form-check-input" type="radio" name="predictVar" id="predictVar" value="' + i + '"><label class="form-check-label" for="predictVar' + i + '">' + i + '</label>');
}
}
}

function updateRegression(data) {

// Get other fields, if it's checked, and default delim
var isChecked = $("#headerInclude").is(":checked");
var delim = $("#inputDelim").val();
var predictCol = parseInt($("#predictVar").val());

// pass along string to golang to parse, if header present, and delim
// Won't be ready on first page load
if (typeof runRegression !== "undefined") {
runRegression(data, isChecked, delim, predictCol);
}

}

function updateTable(data) {

$('#data-table').empty();
const container = document.getElementById('data-table');
const hot = new Handsontable(container, {
data: data,
rowHeaders: true,
colHeaders: true,
licenseKey: "non-commercial-and-evaluation",
tableClassName: ['table', 'table-hover', 'table-striped'],
observeChanges: true,
afterChange: function(r, c){
var data = this.getData();

// Function expects a string joined by delim
var delim = $("#inputDelim").val();

// Update radios with number of columns, and last selected
var predictCol = $("#predictVar").val();
var cols = 0;
stringData = ""
$.each(data, function(i, row){
stringData += row.join(delim) + "\n"
cols = row.length;
})
updateRadios(cols, predictCol)
updateRegression(stringData);

}
});
}
updateTable(starterData);

$("#csvInput").change(function(){

// Check that ends with csv

var file = $('#csvInput').prop('files')[0];
var reader = new FileReader();

reader.onload = function (event) {

// Use arrays in javascript for table, update it
data = $.csv.toArrays(event.target.result);
updateTable(data);

// call function to get checked, delim, and run regression
updateRegression(event.target.result);

}

// Read in the csv file
reader.readAsText(file)
})
})


if (WebAssembly) {
// WebAssembly.instantiateStreaming is not currently available in Safari
if (WebAssembly && !WebAssembly.instantiateStreaming) { // polyfill
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}

const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
} else {
console.log("WebAssembly is not supported in your browser")
}
</script>
</body>
</html>
Binary file added docs/main.wasm
Binary file not shown.
Loading